The project involved the secondary development of a command-line Gomoku (Five-in-a-Row) game. The original project lacked a graphical user interface (GUI) and intuitive controls, relying on console output and coordinate input. The primary improvements centered on introducing a visual interface using the EasyX graphics library and implementing mouse-based interaction.
Original Project Limitations:
- Absence of a graphical interface; the board and pieces were represented by text symbols printed to the console.
- The entire board was reprinted to the console after every move, leading to visual clutter.
- No mouse support; placing a piece required manually entering grid corodinates.
Key Implementations:
1. Graphical Board Rendering
The Panel::print() method was rewritten to draw the game board visually. It loads a background image and overlays a grid using line-drawing functions, creating a clear 19x19 playing surface.
void Panel::print() {
IMAGE img;
loadimage(&img, _T("./board.jpg"));
setbkcolor(WHITE);
int linePos = 38;
setlinecolor(BLACK);
// Draw horizontal grid lines
for (int i = 0; i < this->P_size; i++) {
line(0, linePos, 975, linePos);
linePos += 38;
}
// Draw vertical grid lines
linePos = 38;
for (int i = 0; i < this->P_size; i++) {
line(linePos, 0, linePos, 975);
linePos += 38;
}
}
2. Mouse-Based Piece Placement
The Player::set1() method was modified to handle mouse input. It waits for a left-click event (WM_LBUTTONDOWN), maps the pixel coordinates to the nearest grid intersection, draws a circle representing the piece, and updates the internal game state array.
void Player::set1() {
while (1) {
if (MouseHit) {
ExMessage msg = getmessage(EX_MOUSE);
if (msg.message == WM_LBUTTONDOWN && msg.x < 950) {
int gridX = msg.x / 38; // Convert pixel to grid coordinate
int gridY = msg.y / 38;
setfillcolor(WHITE);
// Draw piece at calculated center point
fillcircle(gridX * 38 + 19, gridY * 38 + 19, 15);
this->boardState[gridX][gridY] = this->playerType;
break; // Exit loop after successful placement
}
}
}
}
3. Game Control Button
A button() function creates a visual "Exit" button in the interface. It uses rounded rectangle drawing and text rendering functions.
void button() {
setfillcolor(WHITE);
setlinecolor(BLACK);
fillroundrect(950, 900, 1050, 950, 20, 20);
settextcolor(BLACK);
settextstyle(30, 20, _T("KaiTi"));
outtextxy(960, 905, _T("Exit"));
}
4. Main Game Loop
The core loop alternates turns between two player objects (p1 and p2), each handling a mouse click to place a piece. After each move, it checks for a win condition using a Judgment() method.
while (true) {
p1.placePiece(); // Waits for player 1's mouse click
if (p1.checkWin()) {
displayWinMessage("Player 1 Wins");
resetGame();
continue;
}
p2.placePiece(); // Waits for player 2's mouse click
if (p2.checkWin()) {
displayWinMessage("Player 2 Wins");
resetGame();
continue;
}
}
5. Audio Feedback
A PlayMusicOnce() function was added using MCI commands to play sound effects, enhancing the user experience.
void PlayMusicOnce(TCHAR fileName[]) {
TCHAR cmd[100];
_stprintf(cmd, _T("open %s alias sfx"), fileName);
mciSendString(_T("close sfx"), NULL, 0, NULL);
mciSendString(cmd, NULL, 0, NULL);
mciSendString(_T("play sfx"), NULL, 0, NULL);
}
Development Challenge:
A significant challenge was managing the turn-based flow within the event-driven mouse input system. The initial implementation used a single loop that did not properly sequester the input waiting state for each player. The solution was to encapsulate the mouse wait logic within each player's placePiece() method, ensuring it completed one placement before returning control to the main loop for win-checking and turn switching.