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:

  1. 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.

  2. The computer can randomly place its symbol in any valid board position.

  3. The computer should perform its turn by keeping in mind the following two points:

    1. The computer must prioritize placing its symbol where it has a winning chance.

    2. 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;
}




Editor for your implementation

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:

  1. The random case

  2. 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:

  1. If the computer can win by placing a symbol, it should make that move.

  2. Otherwise, the computer should place a symbol to block the opponent's win.

  3. 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:

  1. Place the computer's/human's symbol temporarily on the board.

  2. Invoke the isWinHere() function and, if true 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.