C++-based AI backgammon game project development tutorial

Project resource download

  1. C++-based AI backgammon game project source code compression package download address
  2. Github download address of source code of AI backgammon game project based on C++
  3. Materials required for AI backgammon game project based on C++
  4. EasyX required for C++-based AI backgammon game project

Project Description

  This project is based on C++ development, which is relatively simple overall. It realizes the backgammon game between humans and AI, and can determine the outcome and add sound effects, etc. It will definitely be no problem to follow the detailed tutorial on my blog step by step!


Project development software environment

  • Windows11
  • VS2017
  • EasyX

Project development hardware environment

  • CPU:Intel® Core™ i7-8750H CPU @ 2.20GHz 2.20 GHz
  • RAM:24GB
  • GPU:NVIDIA GeForce GTX 1060


foreword

  The following is the detailed development tutorial of the AI ​​backgammon game project based on C++. I have made detailed notes and diagrams for each step. I believe that readers can realize their own AI backgammon as long as they follow my steps step by step. Of course, readers You can also adjust the game project material according to your own preferences in order to achieve the best results. The following is the entire content of this article!


Zero, project demo

0.1 Man-machine backgammon game

Please add a picture description

0.2 Black (chess player) wins

  • Black (chess player) victory board:
    insert image description here

  • Black chess (chess player) victory judgment result:
    Please add a picture description

2.3 White (AI) wins

  • White (AI) victory board:
    Please add a picture description

  • White chess (AI) victory judgment result:
    Please add a picture description

1. Create a project

  1. After opening Microsoft Visual Studio (hereinafter referred to as VS), click "New" -> "Project"
    Please add a picture description

  2. Then enter the project name and project location, and click "OK"
    Please add a picture description

2. Import material

  1. Create a new "resource" folder in the project, ready to store the material files of the project
    Please add a picture description

  2. Import the materials used in the project into the "resource" folder in the project. Readers can use their own materials or my materials. The download link of my materials has been placed in the above blog
    Please add a picture description

3. Project framework design

3.1 Design project framework

  1. The framework of the entire project is shown in the figure below, and all codes are written according to the following four classes:
    Please add a picture description
    • Man (chess player): a person who plays chess
    • Chess (chessboard): where chess is played
    • AI (artificial intelligence): AI that plays against chess players
    • ChessGame (game control): control the basic logic of the game

3.2 Design classes according to the project framework

  1. According to the project framework just designed, we will build it one by one. First create the Man (chess player) class, right click on "Source Files", and click "Class" in "Add":
    Please add a picture description

  2. Enter "Man" in "Class Name", click "OK", and the rest of the files will be generated automatically
    Please add a picture description

  3. It can be found that it has been successfully generated
    Please add a picture description

  4. Create the other three classes in the same way as the Man (chess player) class, and the final effect is shown in the figure below:
    Please add a picture description

4. Design the main interface of the game

4.1 Design the main interface of the Chess (chessboard) class

  1. We design the main interfaces of the Chess (chessboard) class in Chess.h. These main interfaces do not need to be implemented specifically, but are only exposed to the outside, and can be personalized when waiting for external use. The code in Chess.h is as follows:
    #pragma once
    
    // 表示落子位置
    struct ChessPos
    {
          
          
    	int row;
    	int col;
    };
    
    // 表示棋子的种类
    typedef enum
    {
          
          
    	CHESS_WHITE = -1, // 白棋
    	CHESS_BLACK = 1 // 黑棋
    }chess_kind;
    
    class Chess
    {
          
          
    public:
    
    	// 棋盘初始化:加载棋盘的图片资源,初始化棋盘的相关数据
    	void init();
    
    	/*
    		判断在指定坐标(x,y)位置,是否是有效点击,
    		如果是有效点击,把有效点击的位置(行,列)保存在参数pos中
    	*/
    	bool clickBoard(int x, int y, ChessPos *pos);
    
    	// 在棋盘的指定位置(pos), 落子(chess)
    	void chessDown(ChessPos *pos, chess_kind chess);
    
    	// 获取棋盘的大小(13线、15线、19线)
    	int getGradeSize();
    
    	// 获取指定位置是黑棋,还是白棋,还是空白
    	int getChessData(ChessPos *pos);
    int getChessData(int row, int col);
    
    	// 检查棋局是否结束
    	bool checkOver();
    
    };
    

4.2 Design the main interface of AI (artificial intelligence) class

  1. Similarly, the code in AI.h is as follows:

    #pragma once
    #include "Chess.h"
    
    class AI
    {
          
          
    public:
    
    	// 初始化
    	void init(Chess *chess);
    
    	// AI下棋
    	void go();
    
    };
    

4.3 Design the main interface of the Man (chess player) class

  1. Similarly, the code in Man.h is as follows:

    #pragma once
    #include "Chess.h"
    
    class Man
    {
          
          
    
    public:
    
    	// 初始化
    	void init(Chess *chess);
    
    	// 下棋动作
    	void go();
    
    };
    

4.4 Design the main interface of ChessGame (game control) class

  1. Similarly, the code in ChessGame.h is as follows:

    #pragma once
    
    class ChessGame
    {
          
          
    
    public:
    
    	// 开始对局
    	void play();
    
    };
    

4.5 Design the specific implementation of each interface

  1. We have now generated the basic main interfaces of our project, but we still need to implement these interfaces to facilitate the use of subsequent project development. At this point we can see that there is a green wavy line under the newly created interface function:
    Please add a picture description

  2. This green wavy line is VS prompting us that we have not generated a specific implementation of this interface, so we need to implement this interface. We just need to mouse over the green squiggly line and click "Show possible fixes":
    Please add a picture description

  3. Then select the option marked in red box:
    Please add a picture description

  4. At this time, VS helps us automatically complete the specific implementation of the interface. Of course, the specific content inside needs to be filled in according to the needs of different projects. At this time, the green wavy line under the interface function no longer exists, so we only need to press "Ctrl+s" to save, and then close it. At this time, VS has already completed it for us:
    Please add a picture description

  5. All other interface functions follow the above steps to complete the specific implementation of the interface, and will not repeat them one by one. The project structure after the specific interface function is implemented is shown in the following figure:
    Please add a picture description

5. Design the basic framework of the game

  1. At this point, we have created the basic interface of the entire game and carried out preliminary implementation, but we have not yet created the framework of the game, so the following work should create the basic framework of the game. Because the game is controlled by the ChessGame class, the functions of each class should be called by the ChessGame class, so first add the following code in ChessGame.h, and the basic content of the entire game is created at this time:

    #pragma once
    #include "Man.h"
    #include "AI.h"
    #include "Chess.h"
    
    class ChessGame
    {
          
          
    
    public:
    
    	ChessGame(Man*, AI*, Chess*);
    
    	// 开始对局
    	void play();
    
    // 添加数据成员
    private:
    	Man* man;
    	AI* ai;
    	Chess* chess;
    };
    
  2. When the basic content of the game is created, we will complete the basic logic of the game. Of course, this is just a simple object-oriented logic implementation and does not involve specific development. The specific development will not be realized until later. We only need to add the following code to ChessGame.cpp at this time:

    #include "ChessGame.h"
    
    ChessGame::ChessGame(Man* man, AI* ai, Chess* chess)
    {
          
          
    
    	this->man = man;
    	this->ai = ai;
    	this->chess = chess;
    
    	ai->init(chess);
    	man->init(chess);
    
    }
    
    // 对局(开始五子棋游戏)
    void ChessGame::play()
    {
          
          
    
    	// 棋盘初始化
    	chess->init();
    
    	// 开始对局
    	while (1)
    	{
          
          
    		// 首先由棋手走
    		man->go();
    		if (chess->checkOver())
    		{
          
          
    			chess->init();
    			continue;
    		}
    
    		// 再由AI走
    		ai->go();
    		if (chess->checkOver())
    		{
          
          
    			chess->init();
    			continue;
    		}
    	}
    
    }
    
  3. At this point, the basic framework of the entire game is completed, and we are going to add specific content to this framework. Of course, before that we need to use a main function to connect the frames we just created. First create main.cpp, which is the overall logic of the game, and the specific content will be written later. Right-click on "Source File" and select "Add" -> "New Item":
    Please add a picture description

  4. Select the C++ file (.cpp), enter the name, and finally click "Add":
    Please add a picture description

  5. Add the following code to main.cpp:

    #include <iostream>
    #include "ChessGame.h"
    
    int main(void)
    {
          
          
    	Man man;
    	Chess chess;
    	AI ai;
    	ChessGame game(&man, &ai, &chess);
    
    	game.play();
    
    	return 0;
    
    }
    
  6. At this point we can run the test, click "Start execution (no debugging) (H)" in "Debug":
    Please add a picture description

  7. It can be found that our program has no problems so far:
    Please add a picture description

6. Board initialization

6.1 Use of EasyX

  1. Because the game needs to draw, we use EasyX to complete the game's drawing interface, which can help us write graphics programs. The download link of EasyX is also at the top of the blog. After downloading, double-click to open:
    Please add a picture description

  2. Click "Next":
    Please add a picture description

  3. Then select your corresponding version of the compiler to "install":
    Please add a picture description

  4. Then you will be prompted that the installation was successful:
    Please add a picture description

6.2 Design data members of the chessboard

  1. After we have installed the EasyX graphics library, we need to introduce some header files we need in Chess.h:
    Please add a picture description

  2. Then we need to add some data needed for chessboard initialization, we only need to add the following code in Chess.h:

    private:
    	IMAGE chessBlackImg; // 黑棋棋子
    	IMAGE chessWhiteImg; // 白棋棋子
    	int gradeSize; // 棋盘的大小(13线、15线、17线、19线)
    	int margin_x; // 棋盘的左侧边界
    	int margin_y; // 棋盘的顶部边界
    	float chessSize; // 棋子的大小(棋盘的小方格的大小)
    	/*
    		存储当前棋局的棋子分布数据
    		例如:chessMap[3][5]表示棋盘的第3行第5列的落子情况(0:空白;1:黑子;-1:白子)
    	*/
    	vector<vector<int>> chessMap;
    	/*
    		表示现在该谁下棋(落子)
    		true:该黑子走;false:该白子走
    	*/
    	bool playerFlag;
    

6.3 Constructing the chessboard

  1. We need to use the data of the chessboard class we just created to create the chessboard. First, we need to write a function to create the chessboard, so we add the following code to Chess.h:

    Chess(int gradeSize, int maiginX, int marginY, float chessSize);
    
  2. Then mouse over the function you just created and click "Show possible fixes":
    Please add a picture description

  3. Then select the content in the red box:
    Please add a picture description

  4. Then press "Ctrl+S" to save:
    Please add a picture description

  5. Next, we will use the data created just now to construct the chessboard, just add the following code to Chess.cpp:

    // 构造棋盘
    Chess::Chess(int gradeSize, int marginX, int marginY, float chessSize)
    {
          
          
    	this->gradeSize = gradeSize;
    	this->margin_x = marginX;
    	this->margin_y = marginY;
    	this->chessSize = chessSize;
    	playerFlag = CHESS_BLACK;
    	for (int i = 0; i < gradeSize; i++)
    	{
          
          
    		vector<int> row;
    		for (int j = 0; j < gradeSize; j++)
    		{
          
          
    			row.push_back(0);
    		}
    		chessMap.push_back(row);
    	}
    }
    
  6. Then come to main.cpp, use the constructor we just created, and pass in the parameters to construct the chessboard:
    Please add a picture description

  7. Then let's test it again, click "Start execution (without debugging) (H)" in "Debug":
    Please add a picture description

  8. It can be found that, so far, our program has no problems:
    Please add a picture description

6.4 Board initialization

  1. After right-clicking on the project, click "Properties":
    Please add a picture description

  2. In the "Character Set" of "General", select "Use multi-byte character set":
    Please add a picture description

  3. Add the following header files and related libraries to Chess.cpp to play music:

    #include <mmsystem.h>
    #pragma comment(lib,"winmm.lib")
    
  4. Add the following code to Chess.cpp to see the actual chessboard and play music:

    // 棋盘初始化
    void Chess::init()
    {
          
          
    	// 创建游戏窗口
    	initgraph(897, 895);
    	// 显示棋盘图片
    	loadimage(0, "resource/棋盘2.jpg");
    	// 播放开始提示音
    	mciSendString("play resource/start.wav", 0, 0, 0);
    	// 加载黑棋和白棋棋子的图片
    	loadimage(&chessBlackImg, "resource/black.png", chessSize, chessSize, true);
    	loadimage(&chessWhiteImg, "resource/white.png", chessSize, chessSize, true);
    	// 棋盘清零
    	for (int i = 0; i < gradeSize; i++)
    	{
          
          
    		for (int j = 0; j < gradeSize; j++)
    		{
          
          
    			chessMap[i][j] = 0;
    		}
    	}
    	// 确定谁先下棋
    	playerFlag = true;
    }
    
  5. Then we test it:
    Please add a picture description

  6. It is found that the chessboard has been successfully displayed and the music has been successfully played:
    Please add a picture description

7. Realization of Chess Players Playing Chess

7.1 Player initialization

  1. Add chessboard data members to the chess player class, and add the following code in Man.h:

    private:
    	Chess* chess;
    

7.2 Initialization of chess player's chess function

  1. When the chess player class is initialized, pass in the board class pointer, just replace the init function in Man.cpp with the following code:

    // 棋手初始化
    void Man::init(Chess * chess)
    {
          
          
    	this->chess = chess;
    }
    
  2. In order to realize the chess player’s function of playing chess, replace the go function in Man.cpp with the following code:

    // 棋手下棋
    void Man::go()
    {
          
          
    	// 鼠标函数
    	MOUSEMSG msg;
    	// 落子位置
    	ChessPos pos;
    	while (1)
    	{
          
          
    		// 获取鼠标点击消息
    		msg = GetMouseMsg();
    		// 通过chess对象,来判断落子位置是否有效
    		if (msg.uMsg == WM_LBUTTONDOWN && chess->clickBoard(msg.x, msg.y, &pos))
    		{
          
          
    			break;
    		}
    	}
    	// 落子
    	chess->chessDown(&pos, CHESS_BLACK);
    }
    

7.3 Judging whether the player's chess position is valid

  1. The most important point in playing chess is to let the computer know where the chess is played. How to solve this problem? We can see the diagram below:
    Please add a picture description

  2. The chess piece must fall at the junction of the two lines, a total of four points, so we first need to calculate the distance between the position of the chess piece and the four points. Here we need to set a "threshold value". If the distance between the position of the chess piece and a certain point is less than this "threshold value", it is considered that this point is the real position of the chess piece. Otherwise, the chess piece will not be placed. Half, also note that the subscript of the two-dimensional array stored in the computer starts from 0. At this point we only need to add the following code to Chess.cpp:

    #include <math.h>
    
    // 判断落子是否有效
    bool Chess::clickBoard(int x, int y, ChessPos * pos)
    {
          
          
    	// 真实的落子列坐标
    	int col = (x - margin_x) / chessSize;
    	// 真实的落子行坐标
    	int row = (y - margin_y) / chessSize;
    	// 落子的左上角列坐标
    	int leftTopPosX = margin_x + chessSize * col;
    	// 落子的左上角行坐标
    	int leftTopPosY = margin_y + chessSize * row;
    	// 鼠标点击位置距离真实落子位置的阈值
    	int offset = chessSize * 0.4;
    	// 落子距离四个角的距离
    	int len;
    	// 落子是否有效
    	bool res = false;
    	do
    	{
          
          
    		// 落子距离左上角的距离
    		len = sqrt((x - leftTopPosX) * (x - leftTopPosX) + (y - leftTopPosY) * (y - leftTopPosY));
    		// 如果落子距离左上角的距离小于阈值并且当前位置没有棋子,就保存当前落子位置,并设置落子有效
    		if (len < offset)
    		{
          
          
    			pos->row = row;
    			pos->col = col;
    			if (chessMap[pos->row][pos->col] == 0)
    			{
          
          
    				res = true;
    			}
    			break;
    		}
    		// 落子距离右上角的距离
    		int x2 = leftTopPosX + chessSize;
    		int y2 = leftTopPosY;
    		len = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
    		// 如果落子距离右上角的距离小于阈值并且当前位置没有棋子,就保存当前落子位置,并设置落子有效
    		if (len < offset)
    		{
          
          
    			pos->row = row;
    			pos->col = col + 1;
    			if (chessMap[pos->row][pos->col] == 0)
    			{
          
          
    				res = true;
    			}
    			break;
    		}
    		// 落子距离左下角的距离
    		x2 = leftTopPosX;
    		y2 = leftTopPosY + chessSize;
    		len = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
    		// 如果落子距离右上角的距离小于阈值并且当前位置没有棋子,就保存当前落子位置,并设置落子有效
    		if (len < offset)
    		{
          
          
    			pos->row = row + 1;
    			pos->col = col;
    			if (chessMap[pos->row][pos->col] == 0)
    			{
          
          
    				res = true;
    			}
    			break;
    		}
    		// 落子距离右下角的距离
    		x2 = leftTopPosX + chessSize;
    		y2 = leftTopPosY + chessSize;
    		len = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
    		// 如果落子距离右上角的距离小于阈值并且当前位置没有棋子,就保存当前落子位置,并设置落子有效
    		if (len < offset)
    		{
          
          
    			pos->row = row + 1;
    			pos->col = col + 1;
    			if (chessMap[pos->row][pos->col] == 0)
    			{
          
          
    				res = true;
    			}
    			break;
    		}
    	} while (0);
    	// 返回落子是否有效的判断结果
    	return res;
    }
    
  3. At this point, we can judge whether the position of the ball is valid. In order to verify that there is no problem with our code, we need to verify it. We add the following code to Chess.cpp. After the test is successful, we can delete the added code:
    Please add a picture description

  4. Add the following code to Man.cpp to print the placement position. Similarly, you can delete the added code after the test is successful:
    Please add a picture description

  5. At this point we can come to main.cpp for testing:
    Please add a picture description

  6. It can be found that the drop position is obtained correctly, which shows that there is no problem with our code. After the test is successful, delete the two codes added above:
    Please add a picture description

7.4 Realize chess players playing chess

  1. In order to realize chess board placement, first add the following code to Chess.cpp. It should be noted that the left side of the drawing is the upper left corner, so in order to make the center point of the chess piece at the junction of the row line and the column line, the row and column coordinates of the chess piece need to be reduced. 0.5 times the size of the grid, this needs extra attention:

    // 棋盘落子
    void Chess::chessDown(ChessPos * pos, chess_kind chess)
    {
          
          
    	// 加载落子音效
    	mciSendString("play resource/down7.wav", 0, 0, 0);
    	// 获取棋子的落子位置,需要注意绘图的左边是左上角,所以为了让棋子的中心点在行线和列线的交界处,棋子的行和列坐标都需要减0.5倍的棋格大小
    	int x = margin_x + chessSize * pos->col - 0.5 * chessSize;
    	int y = margin_y + chessSize * pos->row - 0.5 * chessSize;
    	// 根据棋子类型在对应位置生成棋子图片
    	if (chess == CHESS_WHITE)
    	{
          
          
    		putimage(x, y, &chessWhiteImg);
    	}
    	else
    	{
          
          
    		putimage(x, y, &chessBlackImg);
    	}
    }
    
  2. Then we tested it and found that the chess pieces can be placed successfully, and the sound effect is no problem, but there are black borders around each chess piece, and these black borders should definitely not exist:
    Please add a picture description

  3. The black borders appear on the chess pieces after they are placed because Easyx does not support images in png format. To solve this problem, we only need to add the following function to Chess.cpp:

    // 解决Easyx不支持png格式图片的函数
    void putimagePNG(int x, int y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
    {
          
          
    	// 变量初始化
    	DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
    	DWORD* draw = GetImageBuffer();
    	DWORD* src = GetImageBuffer(picture); // 获取picture的显存指针
    	int picture_width = picture->getwidth(); // 获取picture的宽度,EASYX自带
    	int picture_height = picture->getheight(); // 获取picture的高度,EASYX自带
    	int graphWidth = getwidth();       // 获取绘图区的宽度,EASYX自带
    	int graphHeight = getheight();     // 获取绘图区的高度,EASYX自带
    	int dstX = 0;    // 在显存里像素的角标
    
    	// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
    	for (int iy = 0; iy < picture_height; iy++)
    	{
          
          
    		for (int ix = 0; ix < picture_width; ix++)
    		{
          
          
    			int srcX = ix + iy * picture_width; // 在显存里像素的角标
    			int sa = ((src[srcX] & 0xff000000) >> 24); // 0xAArrggbb;AA是透明度
    			int sr = ((src[srcX] & 0xff0000) >> 16); // 获取RGB里的R
    			int sg = ((src[srcX] & 0xff00) >> 8);   // G
    			int sb = src[srcX] & 0xff;              // B
    			if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
    			{
          
          
    				dstX = (ix + x) + (iy + y) * graphWidth; // 在显存里像素的角标
    				int dr = ((dst[dstX] & 0xff0000) >> 16);
    				int dg = ((dst[dstX] & 0xff00) >> 8);
    				int db = dst[dstX] & 0xff;
    				draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)  // 公式: Cp=αp*FP+(1-αp)*BP  ; αp=sa/255 , FP=sr , BP=dr
    					| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)         // αp=sa/255 , FP=sg , BP=dg
    					| (sb * sa / 255 + db * (255 - sa) / 255);              // αp=sa/255 , FP=sb , BP=db
    			}
    		}
    	}
    }
    
  4. Then modify the Chess::chessDown function in Chess.cpp as shown below:
    Please add a picture description

  5. At this point, let's test again, and we can find that the black border is gone, and the sound effect is no problem:
    Please add a picture description

  6. Although the move effect has been realized now, it just shows it and does not store the move data in the computer. We created a two-dimensional array before to store the move data, so we should store our move information in the second dimension array. First add the following function to the private of Chess.h:

    // 将落子信息存储到二维数组中
    void updateGameMap(ChessPos* pos);
    
  7. Then add the following function to Chess.cpp:

    // 将落子信息存储在二维数组中
    void Chess::updateGameMap(ChessPos * pos)
    {
          
          
    	// 存储落子信息
    	chessMap[pos->row][pos->col] = playerFlag ? CHESS_BLACK : CHESS_WHITE;
    	// 黑白方交换行棋
    	playerFlag = !playerFlag;
    }
    
  8. Then call Chess::updateGameMap in the Chess::chessDown function in Chess.cpp:
    Please add a picture description

  9. At this point, the player's move information has been stored in the two-dimensional array of the computer, which is convenient for our follow-up operations

8. Realization of AI playing chess

8.1 AI initialization

  1. When doing AI initialization, we have to consider two data members:

    • Chessboard object: Indicates which board to play chess on
    • Scoring array: store AI's value evaluation of all the points on the chessboard, so that AI can make optimal decisions
  2. Based on the above analysis, we first add two data members to AI.h:

    private:
    	// 棋盘对象
    	Chess* chess;
    	// 评分数组
    	vector<vector<int>> scoreMap;
    
  3. Then add the following code to AI.cpp:

    // AI初始化
    void AI::init(Chess * chess)
    {
          
          
    	this->chess = chess;
    	int size = chess->getGradeSize();
    	for (int i = 0; i < size; i++)
    	{
          
          
    		vector<int> row;
    		for (int j = 0; j < size; j++)
    		{
          
          
    			row.push_back(0);
    		}
    		scoreMap.push_back(row);
    	}
    }
    

8.2 The principle of AI playing chess

  1. The principle of AI chess playing is much more complicated than that of chess players, because chess players play artificial chess and do not need computer program calculations, while AI chess needs to find the best position to play chess according to the position of chess players. Optimal strategy, that is to say, AI needs to calculate the scores of all possible placement points on the board, and then select a point with the highest score to move. For the scoring of a possible placement point, we can understand it this way: this position may be black A piece of chess, or a piece of white chess, imagines this position as a battleground. What we need to do is to judge whether the value gained by the black piece from capturing this position is more, or the value obtained by the white piece from capturing this position. If Black gets more value from capturing this position, then we should let white get here, that is, let white destroy black to get more value; if white gets more value from this position , then let white download this position to obtain more value, because at this time the AI ​​is playing white, so we need to let the AI ​​obtain more value as much as possible

  2. For AI, after each drop, there are eight directions around the drop. For each drop point, the scoring calculation should be performed in the eight directions of the point. The scoring calculation standard is to determine how many pieces have already been placed in each direction. Consecutive pawns are up. Assume that there is a possible drop point as shown in the black dot in the figure below:
    Please add a picture description

  3. According to the picture above, it can be found that there are eight directions around the move. AI first calculates how valuable it would be if the chess player made a move in this possible position, and then calculates how much value AI would have if it made a move in the same position. So how to judge the size of the value? We can use the number of consecutive pieces as the criterion for judging. If black or white pieces are placed at this position, how many consecutive black or white pieces are there in one of the eight directions at this position? Judging criteria, if the number of consecutive black or white pieces is more, the value of placing a piece at this position is greater

  4. Since we need to judge the value based on the number of consecutive black or white chess pieces, we should have a basic understanding of the common chess shapes in backgammon, which will help us judge the value of different situations. Common chess shapes in backgammon are as follows:

    • Even two:

      the first case the second case
      Please add a picture description Please add a picture description
    • live three:

      the first case the second case
      Please add a picture description Please add a picture description
    • Dead three:

      the first case the second case
      Please add a picture description Please add a picture description
    • live four

      the first case the second case
      Please add a picture description Please add a picture description
    • dead four

      the first case the second case
      Please add a picture description Please add a picture description
    • five in a row (win)

      the first case
      Please add a picture description Please add a picture description
  5. For each chess shape caused by different placement situations, we need to give corresponding scores to facilitate AI to make judgments, so as to select the optimal placement point. The scoring standards for different chess colors and different chess shapes are shown in the figure below. This scoring standard may not be optimal, but the level of AI Gomoku designed according to this scoring standard has exceeded the level of most players. If you need to challenge more Difficulty backgammon player level, subsequent iterative optimization can be carried out. In addition, it should be noted that in our game, the chess player holds black pieces and the AI ​​holds white pieces:

    Target Chess black chess white chess
    even two 10 10
    dead three 30 25
    live three 40 50
    dead four 60 55
    live four 200 10000
    five in a row (win) 20000 30000

8.3 AI scores the chess game

  1. With the above analysis of the principle of AI chess playing, we will write the code according to the results of the analysis. First, we need to define a function to process the value score calculation of AI's chess moves. Add the following function to .h:

    private:
    	// AI对棋局进行评分
    	void calculateScore();
    
  2. Add the following code to AI.cpp:

    // AI对棋局进行评分计算
    void AI::calculateScore()
    {
          
          
    	// 棋手方(黑棋)有多少个连续的棋子
    	int personNum = 0;
    	// AI方(白棋)有多少个连续的棋子
    	int aiNum = 0;
    	// 该方向上空白位的个数
    	int emptyNum = 0;
    	// 将评分向量数组清零
    	for (int i = 0; i < scoreMap.size(); i++)	
    	{
          
          
    		for (int j = 0; j < scoreMap[i].size(); j++)
    		{
          
          
    			scoreMap[i][j] = 0;
    		}
    	}
    	// 获取棋盘大小
    	int size = chess->getGradeSize();
    	// 对可能的落子点的八个方向进行价值评分计算
    	for (int row = 0; row < size; row++)
    	{
          
          
    		for (int col = 0; col < size; col++)
    		{
          
          
    			// 只有当前位置没有棋子才是可能的落子点
    			if (chess->getChessData(row, col) == 0)
    			{
          
          
    				// 控制八个方向
    				for (int y = -1; y <= 0; y++)
    				{
          
          
    					for (int x = -1; x <= 1; x++)
    					{
          
          
    						// 重置棋手方(黑棋)有多少个连续的棋子
    						personNum = 0;
    						// 重置AI方(白棋)有多少个连续的棋子
    						aiNum = 0;
    						// 重置该方向上空白位的个数
    						emptyNum = 0;
    						// 消除重复计算
    						if (y == 0 && x != 1)
    						{
          
          
    							continue;
    						}
    						// 原坐标不计算在内
    						if (!(y == 0 && x == 0))
    						{
          
          
    							// 假设黑棋在该位置落子,会构成什么棋形?此时是黑棋的正向计算
    							for (int i = 1; i <= 4; i++)
    							{
          
          
    								int curRow = row + i * y;
    								int curCol = col + i * x;
    								if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 1)
    								{
          
          
    									personNum++;
    								}
    								else if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 0)
    								{
          
          
    									emptyNum++;
    									break;
    								}
    								else
    								{
          
          
    									break;
    								}
    							}
    							// 黑棋的反向计算
    							for (int i = 1; i <= 4; i++)
    							{
          
          
    								int curRow = row - i * y;
    								int curCol = col - i * x;
    								if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 1)
    								{
          
          
    									personNum++;
    								}
    								else if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 0)
    								{
          
          
    									emptyNum++;
    									break;
    								}
    								else
    								{
          
          
    									break;
    								}
    							}
    							// 连二
    							if (personNum == 1)
    							{
          
          
    								scoreMap[row][col] += 10;
    							}
    							// 连三
    							else if (personNum == 3)
    							{
          
          
    								// 死三
    								if (emptyNum == 1)
    								{
          
          
    									scoreMap[row][col] += 30;
    								}
    								// 活三
    								else if (emptyNum == 2)
    								{
          
          
    									scoreMap[row][col] += 40;
    								}
    							}
    							// 连四
    							else if (personNum == 3)
    							{
          
          
    								// 死四
    								if (emptyNum == 1)
    								{
          
          
    									scoreMap[row][col] += 60;
    								}
    								// 活四
    								else if (emptyNum == 2)
    								{
          
          
    									scoreMap[row][col] += 200;
    								}
    							}
    							// 连五
    							else if (personNum == 4)
    							{
          
          
    								scoreMap[row][col] += 20000;
    							}
    							// 清空空白棋子个数
    							emptyNum = 0;
    							// 假设白棋在该位置落子,会构成什么棋形?此时是白棋的正向计算
    							for (int i = 1; i <= 4; i++)
    							{
          
          
    								int curRow = row + i * y;
    								int curCol = col + i * x;
    								if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == -1)
    								{
          
          
    									aiNum++;
    								}
    								else if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 0)
    								{
          
          
    									emptyNum++;
    									break;
    								}
    								else
    								{
          
          
    									break;
    								}
    							}
    							// 白棋的反向计算
    							for (int i = 1; i <= 4; i++)
    							{
          
          
    								int curRow = row - i * y;
    								int curCol = col - i * x;
    								if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == -1)
    								{
          
          
    									aiNum++;
    								}
    								else if (curRow >= 0 && curRow < size && curCol >= 0 && curCol < size && chess->getChessData(curRow, curCol) == 0)
    								{
          
          
    									emptyNum++;
    									break;
    								}
    								else
    								{
          
          
    									break;
    								}
    							}
    							// 白色棋子无处可下
    							if (aiNum == 0)
    							{
          
          
    								scoreMap[row][col] += 5;
    							}
    							// 连二
    							else if (aiNum == 1)
    							{
          
          
    								scoreMap[row][col] += 10;
    							}
    							// 连三
    							else if (aiNum == 3)
    							{
          
          
    								// 死三
    								if (emptyNum == 1)
    								{
          
          
    									scoreMap[row][col] += 25;
    								}
    								// 活三
    								else if (emptyNum == 2)
    								{
          
          
    									scoreMap[row][col] += 50;
    								}
    							}
    							// 连四
    							else if (aiNum == 3)
    							{
          
          
    								// 死四
    								if (emptyNum == 1)
    								{
          
          
    									scoreMap[row][col] += 55;
    								}
    								// 活四
    								else if (emptyNum == 2)
    								{
          
          
    									scoreMap[row][col] += 10000;
    								}
    							}
    							// 连五
    							else if (aiNum >= 4)
    							{
          
          
    								scoreMap[row][col] += 30000;
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    }
    

8.4 Realize AI playing chess

  1. After the calculation of the value scores in all directions of each possible drop point is completed, the AI ​​can be allowed to "think" and select the point with the highest value score for the drop. First add the following code to AI.h:

    private:
    	// 找出价值评分最高的点落子
    	ChessPos think();
    
  2. Then add the following code to Chess.h:
    Please add a picture description

  3. Then add the following code to AI.cpp:

    // 找出价值评分最高的点落子
    ChessPos AI::think()
    {
          
          
    	// 计算各个方向的价值评分
    	calculateScore();
    	// 获取棋盘大小
    	int size = chess->getGradeSize();
    	// 存储多个价值最大值的点
    	vector<ChessPos> maxPoints;
    	// 初始价值最大值
    	int maxScore = 0;
    	// 遍历搜索价值评分最大的点
    	for (int row = 0; row < size; row++)
    	{
          
          
    		for (int col = 0; col < size; col++)
    		{
          
          
    			if (chess->getChessData(row, col) == 0)
    			{
          
          
    				if (scoreMap[row][col] > maxScore)
    				{
          
          
    					maxScore = scoreMap[row][col];
    					maxPoints.clear();
    					maxPoints.push_back(ChessPos(row, col));
    				}
    				else if (scoreMap[row][col] == maxScore)
    				{
          
          
    					maxPoints.push_back(ChessPos(row, col));
    				}
    			}
    		}
    	}
    	// 如果有多个价值最大值点,随机获取一个价值最大值点的下标
    	int index = rand() % maxPoints.size();
    	// 返回价值最大值点
    	return maxPoints[index];
    }
    
  4. Then add the following code to AI.cpp:

    // AI下棋
    void AI::go()
    {
          
          
    	// AI计算后的落子点
    	ChessPos pos = think();
    	// AI假装思考,给棋手缓冲时间
    	Sleep(1000);
    	// 在AI计算后的落子点落子
    	chess->chessDown(&pos, CHESS_WHITE);
    }
    
  5. Modify the Chess::getGradeSize function and the two Chess::getChessData functions in Chess.cpp as follows:
    Please add a picture description

  6. Then I tested it and found that I can play chess normally, and my intelligence is not bad. Readers can adjust the assignment of the value score according to their own experience, so as to make AI have higher intelligence:
    Please add a picture description

9. Realization of winning and losing judgment

9.1 Dealing with wins and losses

  1. First, add the following function to Chess.h, the purpose is to check who wins and who loses at present, and then process the victory or defeat according to the result of the check:

    private:
        // 检查当前谁嬴谁输,如果胜负已分就返回true,否则返回false
        bool checkWin();
    
  2. Add the following header files to Chess.cpp

    #include <conio.h>
    
  3. Modify the Chess::checkOver function in Chess.cpp to read as follows:

    // 胜负判定
    bool Chess::checkOver()
    {
          
          
    	// checkWin()函数来检查当前谁嬴谁输,如果胜负已分就返回true,否则返回false
    	if (checkWin())
    	{
          
          
            // 暂停
    		Sleep(1500);![请添加图片描述](https://img-blog.csdnimg.cn/4dbfb593cf904d2dbf7a140e2a4bbb9c.png)
    
    		// 说明黑棋(棋手)赢
    		if (playerFlag == false)
    		{
          
          
    			mciSendString("play resource/不错.mp3", 0, 0, 0);
    			loadimage(0, "resource/胜利.jpg");
    		}
    		// 说明白棋(AI)赢
    		else
    		{
          
          
    			mciSendString("play resource/失败.mp3", 0, 0, 0);
    			loadimage(0, "resource/失败.jpg");
    		}
    		// 暂停
    		_getch();
    		return true;
    	}
    	return false;
    }
    

9.2 Principles of winning and losing

  1. The above process of dealing with the outcome is a basic framework for our outcome determination, and its core part is the checkWin function, which is how we determine who wins and who loses. We can think of it like this: for a certain position, we need to judge whether its eight directions are connected to five pieces, but each time we judge, we can also judge whether the opposite direction is connected to five pieces according to the offset position, so You only need to judge four major directions and eight minor directions. Suppose we first judge the horizontal direction, as shown in the figure below:
    Please add a picture description

  2. It can be seen that for a certain drop position, we first judge whether the five consecutive positions to the right from this position are the same color, and then shift the initial drop point to the left by one, two, three, four, five positions and then Judging whether there are five consecutive pieces of the same color, if it is satisfied, it wins, otherwise it does not win. In this way, we can judge two small directions at the same time in the judgment of a general direction, and complete our judgment of victory and defeat. The judgment of victory and defeat in other directions is the same

9.3 Realization of winning and losing judgment

  1. With the above principle analysis, we can write code. First, add a data member of a certain drop point position in Chess.h:

    private:
    	// 某一落子点的位置
    	ChessPos lastPos;
    
  2. Then add the following code to the Chess::updateGameMap function in Chess.cpp:
    Please add a picture description

  3. Then add the following code to Chess.cpp:

    // 检查当前谁嬴谁输,如果胜负已分就返回true,否则返回false
    bool Chess::checkWin()
    {
          
          
    	// 某一落子点的位置
    	int row = lastPos.row;
    	int col = lastPos.col;
    	// 落子点的水平方向
    	for (int i = 0; i < 5; i++)
    	{
          
          
    		if (((col - i) >= 0) && ((col - i + 4) < gradeSize) && (chessMap[row][col - i] == chessMap[row][col - i + 1]) && (chessMap[row][col - i] == chessMap[row][col - i + 2]) && (chessMap[row][col - i] == chessMap[row][col - i + 3]) && (chessMap[row][col - i] == chessMap[row][col - i + 4]))
    		{
          
          
    			return true;
    		}
    	}
    	// 落子点的垂直方向
    	for (int i = 0; i < 5; i++)
    	{
          
          
    		if (((row - i) >= 0) && ((row - i + 4) < gradeSize) && (chessMap[row - i][col] == chessMap[row - i + 1][col]) && (chessMap[row - i][col] == chessMap[row - i + 2][col]) && (chessMap[row - i][col] == chessMap[row - i + 3][col]) && (chessMap[row - i][col] == chessMap[row - i + 4][col]))
    		{
          
          
    			return true;
    		}
    	}
    	// 落子点的右斜方向
    	for (int i = 0; i < 5; i++)
    	{
          
          
    		if (((row + i) < gradeSize) && (row + i - 4 >= 0) && (col - i >= 0) && ((col - i + 4) < gradeSize) && (chessMap[row + i][col - i] == chessMap[row + i - 1][col - i + 1]) && (chessMap[row + i][col - i] == chessMap[row + i - 2][col - i + 2]) && (chessMap[row + i][col - i] == chessMap[row + i - 3][col - i + 3]) && (chessMap[row + i][col - i] == chessMap[row + i - 4][col - i + 4]))
    		{
          
          
    			return true;
    		}
    	}
    	// 落子点的左斜方向
    	for (int i = 0; i < 5; i++)
    	{
          
          
    		if (((row - i + 4) < gradeSize) && (row - i >= 0) && (col - i >= 0) && ((col - i + 4) < gradeSize) && (chessMap[row - i][col - i] == chessMap[row - i + 1][col - i + 1]) && (chessMap[row - i][col - i] == chessMap[row - i + 2][col - i + 2]) && (chessMap[row - i][col - i] == chessMap[row - i + 3][col - i + 3]) && (chessMap[row - i][col - i] == chessMap[row - i + 4][col - i + 4]))
    		{
          
          
    			return true;
    		}
    	}
    	return false;
    }
    
  4. After writing, we can test it:

    • Black (chess player):
      • The board surface where black (chess player) wins:insert image description here
      • Judgment of black (chess player) winning:
        Please add a picture description
    • White chess (AI):
      • White chess (AI) winning board:
        insert image description here
      • Judgment of white (AI) winning:Please add a picture description
  5. It can be seen that whether black (chess player) wins or white (AI) wins, the judgment of victory or defeat can be displayed normally. And press the Enter key to automatically start the next round


Summarize

  The above is the entire content of the C++-based AI backgammon game project development tutorial. We can see that we have achieved our goal, but we can still make some optimizations in the future, such as the optimization of AI's value score for moves, the regret function, the main interface menu, etc. Wait, if I have time later, I will still update this blog. If readers love to study and are interested, they can also complete the optimization part by themselves, because the overall thinking is relatively clear, and the logic has not changed much, so it is also easy to optimize. easier. Then this blog will come to an end for the time being, see you in the next blog!

Guess you like

Origin blog.csdn.net/IronmanJay/article/details/129685796
Recommended