Gomoku Stage 2: Solution
Learn to implement the Gomoku Player stage 2.
Now that we know how to start and continue the game, let’s implement the second stage, where we decide how and when to stop the game.
When does the game stop?
- When either player wins the game.
- When the game is drawn.
So let’s incorporate the stopping conditions in our code.
Stage 2 implementation
Extending the memory
To declare a game to be over, we need to check whether someone has won the game after each turn.
We would need a bool
variable called gameOver
, which can initially be set to false
.
We can have a variable called winner
set to -1
initially.
Checking for the winner
After displaying the updated board (revised), we can create a function that checks whether we have a winner after each turn. Let’s call it the isWin()
function.
We would need a winCount
(to see if the win count of a certain symbol has been reached), pSym
symbol (to check that particular symbol for win count), the array for the board Board[][CAPACITY]
(on which we are playing) of the dimension dim
. Finally, we would also need the row and column indices of the position (ri
and ci
).
Our function prototype would then be as follows:
bool isWin(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci)
As for its implementation, we might need to check for the winCount
in all directions.
Here comes the interesting part. You’d would naturally think that to see if a player has won or not after each turn, we would need to check for the winCount
number of consecutive symbols vertically up and down, horizontally left and right, as well as left and right diagonally (in all directions).
However, that is not the case. After each turn, if we were to run a loop that checks for winCount
symbols starting from the first cell to the last cell of the grid, then we would not need to check for the symbols vertically upwards and horizontally towards the left. This is because each cell’s vertically upward and horizontally left cells would have already been checked.
Let’s see the animation below for better understanding.
So, our isWin()
function iterates over all cells and invokes the win-checking method, isWinHere()
, on each cell. The following is the implementation of the isWin()
function:
bool isWin(char Board[][CAPACITY], int dim, int winCount, char pSym){for (int ri = 0; ri < dim; ri++){for (int ci = 0; ci < dim; ci++){if (isWinHere(Board, dim, winCount, pSym, ri, ci))return true;}}return false;}
Consequently, our implementation of the isWinHere()
function is as follows:
bool isWinHere(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){bool horizontal = horizontalCheck(Board, dim, winCount, pSym, ri, ci);bool vertical = vericalCheck(Board, dim, winCount, pSym, ri, ci);bool leftDiagonal = leftDiagonalCheck(Board, dim, winCount, pSym, ri, ci);bool rightDiagonal = rightDiagonalCheck(Board, dim, winCount, pSym, ri, ci);// return true if either direction contains winCount number of the same symbol i.e. we have a winnerreturn (horizontal || vertical || leftDiagonal || rightDiagonal);}
Now, let’s implement the direction functions.
a. Checking win horizontally
Starting with the bool
function, horizontalCheck()
, we want to check if there are winCount
number of symbols from left to right. So, let’s write a for
loop that runs from 0
times to winCount
.
Now, when checking horizontally, what has changed in terms of rows and columns?
The row remains the same but the number of columns increases, right? So, inside this for
loop, we’ll check for the same symbol in the row ri
and increasing columns ci + i
where i = 0
and less than winCount
of array Board[][]
. If within this loop, the horizontal cells do not contain the symbol, we return false
, else, we return true
. Look at the code below:
bool horizontalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){for (int i = 0; i < winCount; i++){if (Board[ri][ci + i] != pSym)return false;}return true;}
There’s still a little concern we need to address. What if the horizontal cell being checked is in the last column? Think of any right-most cell of our grid. If we were to check horizontally from that position, it would check for the winCount
number of cells outside our grid. We don’t want that. So, let’s add a condition before the for
loop that checks if the ci + winCount - 1
is greater than or equal to the dimension dim
. If it is, we return false
.
So the function becomes:
bool horizontalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){if (ci + winCount - 1 >= dim)return false;for (int i = 0; i < winCount; i++){if (Board[ri][ci + i] != pSym)return false;}return true;}
b. Checking win vertically
Our function, verticalCheck()
, is very similar to the function above, except it checks for the winCount
number of symbols vertically. Try and write its implementation in the editor below.
Your function should return true
if there are vertical winCount
number of symbols.
#include <iostream>using namespace std;bool verticalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){// Write code here.return false;}
Click the “Show Solution” button below to see its solution:
c. Checking win diagonally 1 (from top-left to down-right)
Next, we have the rightDiagonalCheck()
function that checks for the winCount
number of symbols from left to right diagonally.
Again, this function is also very similar to the previous one. Except here, both the number of rows and columns increase as we go down diagonally.
Try and write its implementation in the editor below:
bool rightDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){// Write code here.}
Click the “Show Solution” button below to see its solution:
d. Checking win diagonally 2 (from top-right to down-left)
Similarly, the leftDiagonalCheck()
function checks for the winCount
number of symbols from right to left diagonally.
In this direction, the number of rows increases, and the number of columns decreases as we go down diagonally from right to left.
bool leftDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci){if (ci - (winCount - 1) < 0)return false;for (int i = 0; i < winCount; i++){if (Board[ri + i][ci - i] != pSym)return false;}return true;}
Checking for the game drawn
Now, let’s check for the other game-stopping conditions to see if the game is drawn or not.
For this, we can simply return false
if any cell is equal to the dash -
. If none equals the dash, then we return true
.
So the function would be:
bool isDraw(char Board[][CAPACITY], int dim){for (int ri = 0; ri < dim; ri++){for (int ci = 0; ci < dim; ci++){if (Board[ri][ci] == '-')return false;}}return true;}
Changes in the main-flow
Let us incorporate main()
function to handle these three things one by one.
Winning conditions
Match-drawn condition
Output appropriate message when the game ends
Converting the game into a tournament format will allow players to play repeatedly after each game, with the option to continue or end the tournament being prompted after each game
Winning conditions
After the updateBoard()
function call in main()
, we’ll invoke the isWin()
function and store the result in the gameOver
variable. Secondly, we’ll also add a condition that if gameOver
is true
, we set the winner
variable to the current turn
.
The changes have been highlighted below:
int main() {// Initializing memory...do {...updateBoard(Board, dim, ri, ci, pSym[turn]);gameOver = isWin(Board, dim, winCount, pSym[turn]);if (gameOver)winner = turn;} while(true);}
Match-drawn conditions
Next, we’ll invoke the isDraw()
function and if it returns true
, we’ll set the gameOver
variable to true
.
We add the following code after the winning condition:
int main() {// Initializing memory...do {...updateBoard(Board, dim, ri, ci, pSym[turn]);gameOver = isWin(Board, dim, winCount, pSym[turn]);if (gameOver)winner = turn;else if (isDraw(Board, dim))gameOver = true;} while(true);}
Finally, we also need to make sure that the game progresses (meaning the turn changes while gameOver
is false
) and terminates when gameOver
is true
. Therefore, we do it in the following way:
int main() {// Initializing memory...do {...updateBoard(Board, dim, ri, ci, pSym[turn]);gameOver = isWin(Board, dim, winCount, pSym[turn]);if (gameOver)winner = turn;else if (isDraw(Board, dim))gameOver = true;if (!gameOver)turnChange(turn, NOP);} while(!gameOver);}
Output appropriate message when game ends
After the while
loop ends, we utilize the winner
variable to determine whether the game was drawn (if the value is -1
) or game was won (if the value is not -1
), and print an appropriate message as follows:
int main() {// Initializing memory...do {...} while(!gameOver);cout << endl;if (winner == -1)cout << "Game Draw!" << endl;else{cout << pName[turn] << " has won!" << endl;winnerCount[winner]++;}}
Convert game into a tournament
To convert the game into a tournament, we simply enclose the existing do-while
loop inside another one, that based on the user’s choice—either re-runs it or terminates it. Lastly, we show player stats at the end.
int main() {// Initializing memory...do {do {...} while(!gameOver);... // input choice (which should be declared in the memory part)}while (choice == 'y'|| choice == 'Y');for (int i = 0; i < 2; i++){cout << "Player" << pName[i] << " has won " << winnerCount[i] << " times";}}
Placing it together
The following is the complete code:
#include <iostream> #include <iomanip> #include <time.h> using namespace std; #define CAPACITY 100 void init(char Board[][CAPACITY], int& dim, char pName[2][30], char pSym[], int&NOP, int& turn, int& winCount); void printBoard(char Board[][CAPACITY], int dim); void userInput(int& ri, int& ci, char pName[], char pSym); bool validInput(int ri, int ci, char Board[][CAPACITY], int dim); void updateBoard(char Board[][CAPACITY], int dim, int ri, int ci, char pSym); bool horizontalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci); bool vericalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci); bool rightDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci); bool leftDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci); bool isWinHere(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci); bool isWin(char Board[][CAPACITY], int dim, int winCount, char pSym); bool isDraw(char Board[][CAPACITY], int dim); void turnChange(int& turn, int NOP); int main() { cout <<"\n\n\t\t\tThe Game of Gomoku...!!!\n\n"; cout <<"\n\n\t\t\t Let us play...!\n\n"<<endl; srand(time(0)); char choice; char Board[CAPACITY][CAPACITY]; char pName[2][30]; char pSym[2]; int turn, ri, ci, dim, NOP, winCount; bool gameOver = false; int winnerCount[2] = { 0, 0 }; int winner = -1; do { cout <<"Initializing...!!!"<<endl; init(Board, dim, pName, pSym, NOP, turn, winCount); do { printBoard(Board, dim); do { userInput(ri, ci, pName[turn], pSym[turn]); if (!validInput(ri, ci, Board, dim)) { cout << "Invalid Input" << endl; } } while (!validInput(ri, ci, Board, dim)); updateBoard(Board, dim, ri, ci, pSym[turn]); gameOver = isWin(Board, dim, winCount, pSym[turn]); if (gameOver) winner = turn; else if (isDraw(Board, dim)) gameOver = true; if (!gameOver) turnChange(turn, NOP); } while (!gameOver); cout << endl; if (winner == -1) cout << "Game Draw!" << endl; else { cout << pName[turn] << " has won!" << endl; winnerCount[winner]++; } cout << "Do you want to play again? y/n "; cin >> choice; } while (choice == 'y'|| choice == 'Y'); for (int i = 0; i < 2; i++) { cout << "Player" << pName[i] << " has won " << winnerCount[i] << " times"; } } void init(char Board[][CAPACITY], int& dim, char pName[2][30], char pSym[], int&NOP, int& turn, int& winCount) { cout << "Dimension: "; cin >> dim; cout << "Win Count: "; cin >> winCount; cout << "# of Players: "; cin >> NOP; for (int i = 0; i < NOP; i++) { cout << "Enter player " << i + 1 << "'s name: "; cin >> pName[i]; // or cin.getline(Pname[i]) } for (int i = 0; i < NOP; i++) { cout << "Enter player " << i + 1 << "'s symbol: "; cin >> pSym[i]; } for (int ri = 0; ri < dim; ri++) { for (int ci = 0; ci < dim; ci++) { Board[ri][ci] = '-'; } } turn = rand() % 2; } void printBoard(char Board[][CAPACITY], int dim) { system("clear"); // system("clear"); for (int ri = 0; ri < dim; ri++) { for (int ci = 0; ci < dim; ci++) { cout << Board[ri][ci] << " "; } cout << endl; } } void userInput(int& ri, int& ci, char pName[], char pSym) { cout << pName << "'s Turn to place '" << pSym << " ' (ri, ci): " ; cin >> ri; cin >> ci; ri--; ci--; } bool validInput(int ri, int ci, char Board[][CAPACITY], int dim) { return ((ri >= 0 && ci >= 0) && (ri <= dim && ci <= dim) && Board[ri][ci] == '-'); } void updateBoard(char Board[][CAPACITY], int dim, int ri, int ci, char pSym) { Board[ri][ci] = pSym; } bool horizontalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci) { if (ci + winCount - 1 >= dim) return false; for (int i = 0; i < winCount; i++) { if (Board[ri][ci + i] != pSym) return false; } return true; } bool vericalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci) { if (ri + winCount - 1 >= dim) return false; for (int i = 0; i < winCount; i++) { if (Board[ri + i][ci] != pSym) return false; } return true; } bool rightDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci) { if (ri + winCount - 1 >= dim) return false; for (int i = 0; i < winCount; i++) { if (Board[ri + i][ci + i] != pSym) return false; } return true; } bool leftDiagonalCheck(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci) { if (ci - (winCount - 1) < 0) return false; for (int i = 0; i < winCount; i++) { if (Board[ri + i][ci - i] != pSym) return false; } return true; } bool isWinHere(char Board[][CAPACITY], int dim, int winCount, char pSym, int ri, int ci) { bool horizontal = horizontalCheck(Board, dim, winCount, pSym, ri, ci); bool vertical = vericalCheck(Board, dim, winCount, pSym, ri, ci); bool leftDiagonal = leftDiagonalCheck(Board, dim, winCount, pSym, ri, ci); bool rightDiagnal = rightDiagonalCheck(Board, dim, winCount, pSym, ri, ci); return (horizontal || vertical || leftDiagonal || rightDiagnal); } bool isWin(char Board[][CAPACITY], int dim, int winCount, char pSym) { for (int ri = 0; ri < dim; ri++) { for (int ci = 0; ci < dim; ci++) { if (isWinHere(Board, dim, winCount, pSym, ri, ci)) return true; } } return false; } bool isDraw(char Board[][CAPACITY], int dim) { for (int ri = 0; ri < dim; ri++) { for (int ci = 0; ci < dim; ci++) { if (Board[ri][ci] == '-') return false; } } return true; } void turnChange(int& turn, int NOP) { turn = (turn + 1) % NOP; }