Hacker Challenge: Gomoku (Human vs Computer) - Try It Yourself
Test your problem-solving skills in this challenging human vs. computer hacker stage.
We'll cover the following
Gomoku game (human vs. computer)
Since we have completed the Gomoku human vs. human implementation, let's discuss the human vs. computer approach. However, before we head into the implementation details, we should have a look at the requirements of this approach:
The number of players is restricted to two; this is done to ensure that we have one human and one computer player to simplify the algorithm.
The computer can randomly place its symbol in any valid board position.
The computer should perform its turn by keeping in mind the following two points:
The computer must prioritize placing its symbol where it has a winning chance.
If that's not the case, then the computer must prioritize placing its symbol where it can block the opponent's win.
Your implementation
#include <iostream> #include<time.h> #include <unistd.h> #define CAPACITY 100 using namespace std; // Old "init" implementation /* 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 init(char Board[][CAPACITY], int& dim, char pName[2][30], char pSym[], int& turn, int& winCount) { // Write your implementation here } void printBoard(char Board[][CAPACITY], int dim) { 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 verticalCheck(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 = verticalCheck(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 (horizontal || vertical || leftDiagonal || rightDiagonal); } 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; } // Old "turnChange" implementation /* void turnChange(int& turn, int NOP) { turn = (turn + 1) % NOP; } */ void turnChange(int& turn) { // Write your implementation here } void HvsC(char Board[][CAPACITY], int& ri, int& ci, int dim, char pSym, int winCount, char CS, char HS) { // Write your implementation here } int main() { cout <<"\n\n\t\t\tThe Game of Gomoku\n\n"; cout <<"\n\n\t\t\t Let's begin!\n\n"<<endl; srand(time(0)); char choice; char Board[CAPACITY][CAPACITY]; char pName[2][30] = {"Human", "Computer"}; // "Human" will be replaced by the name of the player in init function char pSym[2]; // Update this int turn, ri, ci, dim, winCount; bool gameOver = false; int winner = -1; cout <<"Initializing..."<<endl << endl; init(Board, dim, pName, pSym, turn, winCount); do { printBoard(Board, dim); // Old human move implementation /* do { userInput(ri, ci, pName[turn], pSym[turn]); if (!validInput(ri, ci, Board, dim)) { cout << "Invalid Input" << endl; } } while (!validInput(ri, ci, Board, dim)); */ /* Implement the functionality for both human and computer moves. Note that the human move will be similar to the previous implementation of stage II. But For the computer move, we should invoke the function 'HvsC' which will select the coordinates (ri, ci) in the same way as the human move. */ updateBoard(Board, dim, ri, ci, pSym[turn]); printBoard(Board, dim); gameOver = isWin(Board, dim, winCount, pSym[turn]); if (gameOver) winner = turn; if (!isDraw(Board, dim)) gameOver = true; if (!gameOver) turnChange(turn); } while (!gameOver); cout << endl; if (winner == -1) cout << "Game Draw!" << endl; else { cout << pName[turn] << " has won!" << endl; } return 0; }
Directions
The steps below serve as a guideline for implementing the human vs. computer approach.
Restricting the number of players to two
Since we are restricting the number of players to two, there's no need for the NOP
variable. Hence, the implementation of the init()
and turnChange()
functions have to be altered because both use the NOP
variable.
Secondly, it'll be useful to create an enum
to differentiate between the human and computer players.
Updated version of the init()
function
In the previous version, the init()
function asked for the number of players from the user and stored the value inside the NOP
variable. Based on this variable, it asked the user for the names and symbols of said players. Since we'll only have one player in this version, all of this is unnecessary.
Updated version of the turnChange()
function
In the previous version, the modulus was taken by the number of players (NOP
). However, in this case, we should take the modulus by 2
.
Implementing the computer's move
For the computer's move, we have primarily two cases that we will implement in the HvsC()
function:
The random case
The winning case
The random case
In the random case, we need to generate a random number for both the row and the column and ensure that the selected position is valid (the isValid()
function will come in handy here).
The winning case
This case is not as straightforward as the previous one because we have to ensure all three of the following:
If the computer can win by placing a symbol, it should make that move.
Otherwise, the computer should place a symbol to block the opponent's win.
The above two points should hold, even when the consecutive symbols are less than
winCount - 1
.
The computer must check all the valid positions of the board with respect to the first two conditions. Hence, for each valid position, we should do the following:
Place the computer's/human's symbol temporarily on the board.
Invoke the
isWinHere()
function and, iftrue
is returned, we don't need to check for further moves; therefore, we return the function.
Lastly, we also need to enclose the above logic inside another for
loop that runs from winCount
to 1
. This will help us ensure condition number three.
Differentiating between the human's and computer's move
Finally, all that is left for us is to invoke the logic for the human's and the computer's move separately inside the main()
function, and our game is complete.
Note that the human movement will be similar to the previous implementation of stage II. However, for the computer’s move, we should invoke the function HvsC()
, which will select the coordinates (ri
, ci
) in the same way as the human move.
We hope that you were able to solve this problem on your own. If you weren't, tune into the next lesson to look at the solution.