[C language] the realization of Sanzi chess and some small improvements in AI (attack, defense, occupying favorable terrain)

This blog is aimed at C language beginners. For example, the AI part of the computer is only some relatively simple improvements (attack, defense, occupying favorable terrain), and does not involve intelligent algorithms (such as game tree). Some of the codes contain my understanding of programming. If there are deficiencies, please give me more advice. In addition, in fact, enumeration can also be used to realize perfect chess (because there is very little possibility of Sanzi chess). Similar to the opening Library in chess software, the implementation method is also very simple. Interested friends can try it.

First, we define some symbols that will be used a lot. The advantage of this is that it will be easier to change. It should be emphasized here that when we write code, we must ensure the reusability of the code. For example, if we change the two symbols of row and col to 5, the corresponding code does not need to be changed too much.

#Define row / / row 3
#define COL 3 / / column

Define an array to store the data of playing chess.

char board[ROW][COL] = { 0 };

Since there should be all spaces in the array at the beginning of playing chess, we need a function to initialize this array.

init_board(board, ROW, COL);

Then implement this function.

void init_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

We also need a function to print the chessboard.

display_board(board, ROW, COL);

Next, let's implement this function. The implementation of this function is still a little complicated. The printing of chessboard is divided into horizontal division line, vertical division line and data. The horizontal split line is controlled by the outer loop, and the vertical split line and data are controlled by the inner loop. It should be noted that not every cycle needs to print the split line. Whether it is horizontal or vertical, the last cycle does not need to be printed, which can be controlled by if statement.

void display_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		//print data
		for (j = 0; j < row; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < row - 1)
			{
				printf("|");
			}
		}
		printf("\n");

		//Print split lines
		if (i < row - 1)
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
				{
					printf("|");
				}
			}
		}
		printf("\n");
	}
}

Next, players play chess, judge the situation, computers play chess, judge the situation... This requires a cycle. Players play chess first, the chess piece is O, the computer then, and the chess piece is X. (why do you use o and X? The English of Sanzi is naughts and crosses, representing "fork" and "circle" respectively.) the function realization of players and computers playing chess. Finally, let's talk about the function of judging the situation first. The player returns "O" if he wins, the computer returns "X", the draw returns "T" (tie), and if it is not, it returns "C" (continue). So the overall framework is very simple.

char ret = 0;

	while (1)
	{
		//Players play chess
		player_move(board, ROW, COL);
		display_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}

		//Computer chess
		computer_move(board, ROW, COL);
		display_board(board, ROW, COL);
		is_win(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == 'O')
	{
		printf("Player wins\n");
	}
	else if (ret == 'X')
	{
		printf("Computer win\n");
	}
	else
	{
		printf("it ends in a draw\n");
	}
	display_board(board, ROW, COL);

Paste the complete code of the game function before continuing

void game()
{
	//Store chess data
	char board[ROW][COL] = { 0 };

	//Initialize the chessboard to full space
	init_board(board, ROW, COL);

	//Print chessboard
	display_board(board, ROW, COL);

	char ret = 0;

	while (1)
	{
		//Players play chess
		player_move(board, ROW, COL);
		display_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}

		//Computer chess
		computer_move(board, ROW, COL);
		display_board(board, ROW, COL);
		is_win(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == 'O')
	{
		printf("Player wins\n");
	}
	else if (ret == 'X')
	{
		printf("Computer win\n");
	}
	else
	{
		printf("it ends in a draw\n");
	}
	display_board(board, ROW, COL);
}

Next, let's talk about the judgment of winning or losing. Just judge whether each row, column and diagonal line are all pieces of a certain side. The simple way is to directly judge whether two pairs are equal, but this is not universal enough (what if it's Gobang?), So I take a method that is not easy to think of: judge with a counter.

First define two counters corresponding to players and computers

int player_count = 0;//Count the number of players' pieces
int computer_count = 0;//Count the number of computer chess pieces

First check each row. If it is a chess piece of a certain party, the corresponding counter will increase automatically, and then judge whether the counter is equal to the corresponding number of rows (or columns). For example, Sanzi will judge whether it is 3.

    int i = 0;
	int j = 0;

	int player_count = 0;//Count the number of players' pieces
	int computer_count = 0;//Count the number of computer chess pieces

	//Judge whether each line is all pieces of a certain side
	for (i = 0; i < row; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == 'O')
			{
				player_count++;
			}
			if (board[i][j] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

Similarly, judge each column

    //Judge whether each column is all pieces of a certain side
	for (i = 0; i < col; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < row; j++)
		{
			if (board[j][i] == 'O')
			{
				player_count++;
			}
			if (board[j][i] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

The row and column labels of the main diagonal are equal

    //
	//Judge whether the main diagonal is all pieces of a certain side
	//  0 1 2
	//0 O
	//1   O
	//2     O
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][i] == 'O')
		{
			player_count++;
		}
		if (board[i][i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

The sum of row and column labels of the sub diagonal is the fixed value. For example, the sum of (0,2), (1,1), (2,0) row and column labels is 2.

    //
	//Judge whether the sub diagonals are all pieces of a certain side
	//  0 1 2
	//0     O  0,2
	//1   O    1,1
	//2 O      2,0
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][col - 1 - i] == 'O')
		{
			player_count++;
		}
		if (board[i][col - 1 - i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

To judge the draw, we only need to judge whether the chessboard is full. Therefore, we specially write a function is_ Full. The static here is because we only use is_ Used in the source file where win is located (because is_full is a function written specifically for is_win).,

static int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;//Not full
			}
		}
	}
	return 1;//Full
}

Then call is_full judge the draw.

    //Judge the draw
	if (is_full(board, row, col) == 1)
	{
		return 'T';//tie draw
	}

If the tie is not a draw, continue the game. The complete code of the function to judge the winner or loser is as follows (including is_full)

static int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;//Not full
			}
		}
	}
	return 1;//Full
}

char is_win(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;

	int player_count = 0;//Count the number of players' pieces
	int computer_count = 0;//Count the number of computer chess pieces

	//Judge whether each line is all pieces of a certain side
	for (i = 0; i < row; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == 'O')
			{
				player_count++;
			}
			if (board[i][j] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

	//Judge whether each column is all pieces of a certain side
	for (i = 0; i < col; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < row; j++)
		{
			if (board[j][i] == 'O')
			{
				player_count++;
			}
			if (board[j][i] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

	//
	//Judge whether the main diagonal is all pieces of a certain side
	//  0 1 2
	//0 O
	//1   O
	//2     O
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][i] == 'O')
		{
			player_count++;
		}
		if (board[i][i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

	//
	//Judge whether the sub diagonals are all pieces of a certain side
	//  0 1 2
	//0     O  0,2
	//1   O    1,1
	//2 O      2,0
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][col - 1 - i] == 'O')
		{
			player_count++;
		}
		if (board[i][col - 1 - i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

	//Judge the draw
	if (is_full(board, row, col) == 1)
	{
		return 'T';//tie draw
	}

	return 'C';//continue
}

We haven't written the function of playing chess between players and computers. Let's start with the function of players playing chess. We need to judge whether the coordinates are occupied and whether the coordinates are legal. It's simple.

void player_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;

	printf("Players play chess:>\n");

	while (1)
	{
		printf("Please enter coordinates:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = 'O';
				break;
			}
			else
			{
				printf("Coordinates occupied, please re-enter\n");
			}
		}
		else
		{
			printf("Illegal coordinates, please re-enter\n");
		}
	}
}

Then there is the function of computer chess. If you play chess at random, it is very simple. You only need to generate random coordinates.

        //Play chess at random
		x = rand() % row;
		y = rand() % col;

		if (board[x][y] == ' ')
		{
			board[x][y] = 'X';
			return;
		}

But before that, make some judgment (because there are some obviously good moves). It is described as follows:

First: seize the place that strategists must fight for (middle position)

        //The middle position is a battleground for strategists. If the player doesn't get down, it will be blocked
		if (board[(row - 1) / 2][(col - 1) / 2] == ' ')
		{
			board[(row - 1) / 2][(col - 1) / 2] = 'X';
			return;
		}

Second: one shot must kill position, which can't be missed (attack is the best defense)

In the specific implementation, you need to assume a certain position, and then call the function to judge the win or loss. If you find that you have won, go here. If you have not won, it will be deemed that nothing has happened.

        //If the computer can win in a certain position, it will win in this position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'X';
					char ret = is_win(board, row, col);
					if (ret == 'X')
					{
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

Third: sense danger and block the opponent's chess (defensive counterattack)

With the second point, it's very simple here. It's just assuming that the player plays a move of chess.

        //If the player can win in the next position, block the position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'O';
					char ret = is_win(board, row, col);
					if (ret == 'O')
					{
						board[i][j] = 'X';
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

The complete code of computer chess function is as follows

void computer_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("Computer chess:>\n");

	int i = 0;
	int j = 0;

	while (1)
	{
		//The middle position is a battleground for strategists. If the player doesn't get down, it will be blocked
		if (board[(row - 1) / 2][(col - 1) / 2] == ' ')
		{
			board[(row - 1) / 2][(col - 1) / 2] = 'X';
			return;
		}

		//If the computer can win in a certain position, it will win in this position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'X';
					char ret = is_win(board, row, col);
					if (ret == 'X')
					{
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

		//If the player can win in the next position, block the position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'O';
					char ret = is_win(board, row, col);
					if (ret == 'O')
					{
						board[i][j] = 'X';
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

		//Play chess at random
		x = rand() % row;
		y = rand() % col;

		if (board[x][y] == ' ')
		{
			board[x][y] = 'X';
			return;
		}
	}
}

Add: this algorithm is much better than random chess, but there are still some defects. It is not difficult to win the computer. If you want to achieve perfect chess, you can use enumeration (but the applicability of the code will decline, such as Gobang, which is difficult to enumerate), or use higher-order algorithms (such as game tree), but that is relatively complex. I will write a separate blog to explain later.

Finally, this project can be divided into three files: game H (definition of symbols and declaration of functions), game C (specific implementation of game functions), test C (test of game function). The code is as follows:

game.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROW 3 / / line
#define COL 3 / / column

//Initialize chessboard
void init_board(char board[ROW][COL], int row, int col);

//Print chessboard
void display_board(char board[ROW][COL], int row, int col);

//Players play chess
void player_move(char board[ROW][COL], int row, int col);

//Computer chess
void computer_move(char board[ROW][COL], int row, int col);

//Judge whether to win or lose
char is_win(char board[ROW][COL], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

static int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;//Not full
			}
		}
	}
	return 1;//Full
}

char is_win(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;

	int player_count = 0;//Count the number of players' pieces
	int computer_count = 0;//Count the number of computer chess pieces

	//Judge whether each line is all pieces of a certain side
	for (i = 0; i < row; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == 'O')
			{
				player_count++;
			}
			if (board[i][j] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

	//Judge whether each column is all pieces of a certain side
	for (i = 0; i < col; i++)
	{
		player_count = 0;
		computer_count = 0;
		for (j = 0; j < row; j++)
		{
			if (board[j][i] == 'O')
			{
				player_count++;
			}
			if (board[j][i] == 'X')
			{
				computer_count++;
			}
		}
		if (player_count == col)
		{
			return 'O';
		}
		if (computer_count == col)
		{
			return 'X';
		}
	}

	//
	//Judge whether the main diagonal is all pieces of a certain side
	//  0 1 2
	//0 O
	//1   O
	//2     O
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][i] == 'O')
		{
			player_count++;
		}
		if (board[i][i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

	//
	//Judge whether the sub diagonals are all pieces of a certain side
	//  0 1 2
	//0     O  0,2
	//1   O    1,1
	//2 O      2,0
	//

	player_count = 0;
	computer_count = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][col - 1 - i] == 'O')
		{
			player_count++;
		}
		if (board[i][col - 1 - i] == 'X')
		{
			computer_count++;
		}
	}
	if (player_count == col)
	{
		return 'O';
	}
	if (computer_count == col)
	{
		return 'X';
	}

	//Judge the draw
	if (is_full(board, row, col) == 1)
	{
		return 'T';//tie draw
	}

	return 'C';//continue
}

void init_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

void display_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		//print data
		for (j = 0; j < row; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < row - 1)
			{
				printf("|");
			}
		}
		printf("\n");

		//Print split lines
		if (i < row - 1)
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
				{
					printf("|");
				}
			}
		}
		printf("\n");
	}
}

void player_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;

	printf("Players play chess:>\n");

	while (1)
	{
		printf("Please enter coordinates:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = 'O';
				break;
			}
			else
			{
				printf("Coordinates occupied, please re-enter\n");
			}
		}
		else
		{
			printf("Illegal coordinates, please re-enter\n");
		}
	}
}

void computer_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("Computer chess:>\n");

	int i = 0;
	int j = 0;

	while (1)
	{
		//The middle position is a battleground for strategists. If the player doesn't get down, it will be blocked
		if (board[(row - 1) / 2][(col - 1) / 2] == ' ')
		{
			board[(row - 1) / 2][(col - 1) / 2] = 'X';
			return;
		}

		//If the computer can win in a certain position, it will win in this position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'X';
					char ret = is_win(board, row, col);
					if (ret == 'X')
					{
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

		//If the player can win in the next position, block the position
		for (i = 0; i < row; i++)
		{
			for (j = 0; j < col; j++)
			{
				if (board[i][j] == ' ')
				{
					board[i][j] = 'O';
					char ret = is_win(board, row, col);
					if (ret == 'O')
					{
						board[i][j] = 'X';
						return;
					}
					board[i][j] = ' ';
				}
			}
		}

		//Play chess at random
		x = rand() % row;
		y = rand() % col;

		if (board[x][y] == ' ')
		{
			board[x][y] = 'X';
			return;
		}
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void menu()
{
	printf("*******************************\n");
	printf("*********** 1. play ***********\n");
	printf("*********** 0. exit ***********\n");
	printf("*******************************\n");
}

void game()
{
	//Store chess data
	char board[ROW][COL] = { 0 };

	//Initialize the chessboard to full space
	init_board(board, ROW, COL);

	//Print chessboard
	display_board(board, ROW, COL);

	char ret = 0;

	while (1)
	{
		//Players play chess
		player_move(board, ROW, COL);
		display_board(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}

		//Computer chess
		computer_move(board, ROW, COL);
		display_board(board, ROW, COL);
		is_win(board, ROW, COL);
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == 'O')
	{
		printf("Player wins\n");
	}
	else if (ret == 'X')
	{
		printf("Computer win\n");
	}
	else
	{
		printf("it ends in a draw\n");
	}
	display_board(board, ROW, COL);
}

void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));

	do
	{
		menu();
		printf("Please enter:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//printf("Gobang \ n");
			game();
			break;
		case 0:
			printf("Exit the game\n");
			break;
		default:
			printf("Wrong selection, reselect\n");
			break;
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}

Where game The first line of H is to prevent the header file from being repeatedly referenced, which can be omitted. game.c and test The first line of C is written because I tested the code in the VS compiler to solve the problem of scanf function error. If it is not a VS series compiler, you can omit this line of code.

It's not easy to create. If you think this blog is helpful to you, please point out a free praise. Thank you.

Tags: C Algorithm Back-end programming language

Posted by ryy705 on Fri, 13 May 2022 23:06:01 +0300