js+canvas+less to realize AI Gobang games_ ☆ the past goes with the wind ☆ blog

1, Foreword Use js+canvas+less to make a simple AI Gobang game. AI players in the game are very powerful and have the fu...
(1) Draw chessboard
(2) Draw chess pieces
(3) Players play chess
(4) Computer chess
(5) Repentance chess function
(6) Undo repentance function
(7) Decide whether to win or lose
(8) Draw

1, Foreword

Use js+canvas+less to make a simple AI Gobang game. AI players in the game are very powerful and have the function of repentance chess. Here is a brief introduction to the JS part. For the game UI part, please see the source code.

2, Development process

(1) Draw chessboard

Here, use canvas to draw a 15 x 15 chessboard.

// Draw chessboard function chessBoard() { for (var i = 0; i < chessWidth; i++) { // Draw a checkerboard horizontal segment ctx.save(); ctx.beginPath(); ctx.moveTo(20, 20 + i * 40); ctx.lineTo(580, 20 + i * 40); ctx.stroke(); // ctx.restore(); // Draw checkerboard vertical segments ctx.save(); ctx.beginPath(); ctx.moveTo(20 + i * 40, 20); ctx.lineTo(20 + i * 40, 580); ctx.stroke(); ctx.restore(); } }

(2) Draw chess pieces

Our chess pieces should be drawn on the intersection of horizontal and vertical lines, and the mouse click capture drawing function should be realized, that is, when the player clicks a range centered on the intersection (as shown in the red area below), the chess pieces can also fall on the intersection of lines.


There are two drawing methods. The first one is to round the coordinates relative to the chessboard after clicking the chessboard with the mouse (because our chessboard interval is 40, divide the coordinates by 40 to round), and then transfer the rounded coordinates to the drawing chessboard function to determine the placement position. The second way is to traverse the array, calculate the coordinates of the current position, and then draw the chess pieces. It is not difficult to see that the first method is more efficient than the second.

// Draw chess pieces function drawChess(eventX, eventY, flag) { ctx.fillStyle = flag ? "#000" : "#fff"; ctx.beginPath(); ctx.arc(20 + eventX * 40, 20 + eventY * 40, 10, 0, 360 * Math.PI / 180, true); ctx.fill(); // Focus the chess pieces on the intersection of lines (mode 2, low performance) // var wrap = document.querySelector(".wrapper"); // eventX = event.clientX - wrap.offsetLeft; // eventY = event.clientY - wrap.offsetTop; // for (var i = 0; i < chessWidth; i++) { // for (var j = 0; j < chessWidth; j++) { // if (eventX >= (20 + j * 40 - 20) && eventX <= (20 + j * 40 + 20) && eventY >= (20 + i * 40 - 20) && eventY <= (20 + i * 40 + 20)) { // eventX =0 * j + 20; // eventY = 40 * i + 20; // break; // } // } // } // ctx.beginPath(); // ctx.arc(eventX, eventY, 10, 0, 360 * Math.PI, true); // ctx.fill(); }

(3) Players play chess

With the chessboard and pieces, you are about to start playing chess. There is no difficulty for players to play chess. What you need to pay attention to is the rounding of the coordinates (eventX,eventY) passed to the drawing chessboard function and the association with the UI.

//Players play chess chess.addEventListener("click", function (event) { event = event || window.event; // If it's not the player's turn if (isMan == false) { alert("Don't worry, it's not your turn yet."); return } // If the game is over if (gameOver == true) { alert("The game is over, please click restart."); return } // Make the piece fall on the focus of the chessboard line eventX = Math.floor(event.offsetX / 40); eventY = Math.floor(event.offsetY / 40); console.log(eventX); // If there is no drop in the current position, you can drop if (chessPlace[eventX][eventY] == 0) { // Luozi drawChess(eventX, eventY, true); // Record data playerData(); // Drop sound downMp3.play(); // Can repent isRetract = true; // Repentance cannot be revoked isUnretract = false; // You can start over isRestart = true; // Save the location of the drop chessPlace[eventX][eventY] = 1; // Judge whether to win or lose if (win(eventX, eventY, num = 1)) { // game over gameOver = true; // Record data playerData(true); // Play winning music winMp3.play(); // The computer can no longer be left behind isMan = true; // Prompt for another set var choice = confirm("You won. Another game?"); if (choice) { // Empty chessboard clear(); // Empty drop steps and scores playerData("", "clearPath"); computerData("", "clearPath"); gameOver = false; } } // Judge whether there is a draw else if (tie()) { // game over gameOver = true; // It's the player's turn to play chess in the next game isMan = true; // Do you want another round var choice = confirm("Draw, another game?"); if (choice) { // Empty chessboard clear(); // Empty drop steps and scores playerData("", "clearPath"); computerData("", "clearPath"); gameOver = false; } } else { // It's the computer's turn isMan = false; } } else { alert("The current position has been occupied, please choose another position"); } if (gameOver == false && isMan == false) { // Computer chess computerDown(); } });

(4) Computer chess

Computer chess should be the most complex step in the whole game making process. Here I use the five tuple and scorecard algorithm to realize computer AI chess.
1. Quintuple:
The Gobang board has a size of 15 x 15. There are 572 quintuples in all four directions. Each quintuple is given a score (or weight). The score contributed by this quintuple to each position is the score of the quintuple itself. For the whole board, the score of each position is the sum of the scores of all quintuples in the four directions of the position,
Then select the position with the highest score from all empty positions, which is the optimal position of the computer.
2. Score sheet:
Here is a set of the best scorecard given by a foreign leader, which can make AI very smart.

function chessScore(playerNum, computerNum) { // Machine attack // 1. If there are both human and machine falls, the score is 0 if (playerNum > 0 && computerNum > 0) { return 0; } // 2. If all pieces are empty and there are no pieces, it will be divided into 7 points if (playerNum == 0 && computerNum == 0) { return 7; } // 3. If the machine drops a child, the score is 35 if (computerNum == 1) { return 35; } // 4. If the machine falls into two pieces, the score is 800 if (computerNum == 2) { return 800; } // 5. If the machine falls three times, the score is 15000 if (computerNum == 3) { return 15000; } // 6. If the machine falls into four pieces, the score is 800000 if (computerNum == 4) { return 800000; } // Machine defense // 7. If a player loses a child, it will be divided into 15 points if (playerNum == 1) { return 15; } // 8. If the player loses two children, the score is 400 if (playerNum == 2) { return 400; } // 9. If the player loses three children, the score is 1800 if (playerNum == 3) { return 1800; } // 10. If the player loses four children, the score is 100000 if (playerNum == 4) { return 100000; } return -1; //In other cases, an error occurs and the code is not executed }

The empty position with the largest weight is obtained by traversing the quintuple, which is the optimal position of AI.

function computerDown() { // Initialize score group for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth; j++) { score[i][j] = 0; } } // Number of black chess (players) in quintuple var playerNum = 0; // Number of white chess (computer) in quintuple var computerNum = 0; // Five tuple temporary score var tempScore = 0; // Maximum score var maxScore = -1; // Horizontal search for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth - 4; j++) { for (var k = j; k < j + 5; k++) { // If it's a player's son if (chessPlace[k][i] == 1) { playerNum++; } else if (chessPlace[k][i] == 2) { //If it's a computer computerNum++; } } // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (var k = j; k < j + 5; k++) { score[k][i] += tempScore; } // Clear the number of pieces in the quintuple and the temporary score of the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Vertical search for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth - 4; j++) { for (var k = 0; k < j + 5; k++) { // If it's a player's son if (chessPlace[i][k] == 1) { playerNum++; } else if (chessPlace[i][k] == 2) { //If it's a computer computerNum++; } } // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (var k = j; k < j + 5; k++) { score[i][k] += tempScore; } // Clear the number of pieces and instantaneous score value in the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Backslash search // Upper part of backslash for (var i = chessWidth - 1; i >= 4; i--) { for (var k = i, j = 0; j < chessWidth && k >= 0; j++, k--) { var m = k; //x 14 13 var n = j; //y 0 1 for (; m > k - 5 && k - 5 >= -1; m--, n++) { // If it's a player's son if (chessPlace[m][n] == 1) { playerNum++; } else if (chessPlace[m][n] == 2) { //If it's a computer computerNum++; } } // Note that in oblique judgment, it may not form a quintuple (close to the four top corners of the chessboard), so this situation should be ignored if (m == k - 5) { // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (m = k, n = j; m > k - 5; m--, n++) { score[m][n] += tempScore; } } // Clear the number of pieces in the quintuple and the temporary score of the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Lower part of backslash for (var i = 1; i < 15; i++) { for (var k = i, j = chessWidth - 1; j >= 0 && k < 15; j--, k++) { var m = k; //y 1 var n = j; //x 14 for (; m < k + 5 && k + 5 <= 15; m++, n--) { // If it's a player's son if (chessPlace[n][m] == 1) { playerNum++; } else if (chessPlace[n][m] == 2) { //If it's a computer computerNum++; } } // Note that in oblique judgment, it may not form a quintuple (close to the four top corners of the chessboard), so this situation should be ignored if (m == k + 5) { // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (m = k, n = j; m < k + 5; m++, n--) { score[n][m] += tempScore; } } // Clear the number of pieces in the quintuple and the temporary score of the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Forward slash search // Upper part of forward slash for (var i = 0; i < chessWidth - 1; i++) { for (var k = i, j = 0; j < chessWidth && k < chessWidth; j++, k++) { var m = k; var n = j; for (; m < k + 5 && k + 5 <= chessWidth; m++, n++) { // If it's a player's son if (chessPlace[m][n] == 1) { playerNum++; } else if (chessPlace[m][n] == 2) { //If it's a computer computerNum++; } } // Note that in oblique judgment, it may not form a quintuple (close to the four top corners of the chessboard), so this situation should be ignored if (m == k + 5) { // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (m = k, n = j; m < k + 5; m++, n++) { score[m][n] += tempScore; } } // Clear the number of pieces in the quintuple and the temporary score of the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Lower part of forward slash for (var i = 1; i < chessWidth - 4; i++) { for (var k = i, j = 0; j < chessWidth && k < chessWidth; j++, k++) { var m = k; var n = j; for (; m < k + 5 && k + 5 <= chessWidth; m++, n++) { // If it's a player's son if (chessPlace[n][m] == 1) { playerNum++; } else if (chessPlace[n][m] == 2) { //If it's a computer computerNum++; } } // Note that in oblique judgment, it may not form a quintuple (close to the four top corners of the chessboard), so this situation should be ignored if (m == k + 5) { // The number of black and white chess in each quintuple is transferred into the scoring table tempScore = chessScore(playerNum, computerNum); // Add a score for each position of the quintuple for (m = k, n = j; m < k + 5; m++, n++) { score[n][m] += tempScore; } } // Clear the number of pieces in the quintuple and the temporary score of the quintuple playerNum = 0; computerNum = 0; tempScore = 0; } } // Find the position with the highest score from the empty position for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth; j++) { if (chessPlace[i][j] == 0 && score[i][j] > maxScore) { goalX = i; goalY = j; maxScore = score[i][j]; } } } if (goalX != -1 && goalY != -1 && chessPlace[goalX][goalY] == 0) { // Luozi drawChess(goalX, goalY, false); // Save game data computerData(); // Save the drop at this location chessPlace[goalX][goalY] = 2; // Judge whether to win or lose if (win(goalX, goalY, num = 2)) { // game over gameOver = true; // Save game data computerData(true); // Next round player drop isMan = true; var choice = confirm("You lost, another game?"); // Playback failed sound failMp3.play(); if (choice) { // Empty chessboard clear(); // Empty drop steps and scores playerData("", "clearPath"); computerData("", "clearPath"); gameOver = false; } } else if (tie()) { // game over gameOver = true; // It's the player's turn to play chess in the next game isMan = true; // Do you want another round var choice = confirm("Draw, another game?"); if (choice) { // Empty chessboard clear(); // Empty drop steps and scores playerData("", "clearPath"); computerData("", "clearPath"); gameOver = false; } } else { // It's the player's turn to play chess isMan = true; } } }

(5) Repentance chess function

The function of repentance chess is mainly to empty the pieces at the position of repentance chess. Here I use the method of fixed-point clearing and redrawing. First, we draw a circle of the same size as the chess pieces at the coordinates of the chess pieces to cover the chess pieces on the chessboard. At this time, we will find that not only the chess pieces are covered, but also the chessboard lines in the covered area of the chess pieces on the chessboard are covered, so we have to fill in the covered lines.

a. Before repentance:

b. After repentance:


c. Through the observation in the figure, we can find that the missing part is the previous mouse click capture area, so we only need to fill in the lines of this area.

// Empty pieces function clearChess(x, y) { // Clear the chess pieces in this position ctx.clearRect(x * 40, y * 40, 40, 40); // Clear the position of the chess piece and mark it as zero chessPlace[x][y] = 0; // Draw the cleared chessboard line x = x * 40 + 20; y = y * 40 + 20; // Draw a horizontal line ctx.beginPath(); ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y); ctx.stroke(); // Draw vertical lines ctx.beginPath(); ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20); ctx.stroke(); }

d. Backgammon function:

// Repentance chess retract.addEventListener("click", function () { // Trigger button click sound clickSound.play(); if (gameOver) { alert("The game is over. I can't repent!"); } else if (isRetract == false) { alert("Can't repent!"); } else { isRetract = false; //It means that you have repented your chess pieces and can't repent any more isUnretract = true; //Repentance chess can be revoked only after repentance chess pieces clearChess(eventX, eventY); //Clear the player's chess pieces at the target position clearChess(goalX, goalY); //Clear computer chess pieces at the target location playerData("", "retract"); //Reset player game data computerData("", "retract"); //Reset PC game data } });

(6) Undo repentance function

The undo repentance function is relatively simple. You only need to redraw the chess pieces of the previous step.

// Undo repentance unretract.addEventListener("click", function () { // Trigger button click sound clickSound.play(); if (gameOver) { alert("The game is over, you can't undo it!"); } else if (isUnretract == false) { alert("Cannot undo!"); } else { isUnretract = false; //You cannot undo it again after you undo it isRetract = true; //After canceling repentance chess, you can repent chess again (current position) drawChess(eventX, eventY, true); //Draw the player's chess pieces at the target position drawChess(goalX, goalY, false); //Draw computer chess pieces at the target position chessPlace[eventX][eventY] = 1; chessPlace[goalX][goalY] = 2; playerData(); //Reset player game data computerData(); //Reset PC game data } });

(7) Decide whether to win or lose

There are many ways to decide whether to win or lose here, but the only constant principle is Wuzi Lianzhu.
Method 1: traverse the whole chessboard to find five connected beads.

for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth; j++) { // Horizontal win if (chessPlace[i][j] != 0 && i < chessWidth - 4 && chessPlace[i][j] == chessPlace[i + 1][j] && chessPlace[i][j] == chessPlace[i + 2][j] && chessPlace[i][j] == chessPlace[i + 3][j] && chessPlace[i][j] == chessPlace[i + 4][j]) { return flag = "win"; } // Vertical win if (chessPlace[i][j] != 0 && j < chessWidth - 4 && chessPlace[i][j] == chessPlace[i][j + 1] && chessPlace[i][j] == chessPlace[i][j + 2] && chessPlace[i][j] == chessPlace[i][j + 3] && chessPlace[i][j] == chessPlace[i][j + 4]) { return flag = "win"; } // Positive slash wins if (chessPlace[i][j] != 0 && i < chessWidth - 4 && j < chessWidth - 4 && chessPlace[i][j] == chessPlace[i + 1][j + 1] && chessPlace[i][j] == chessPlace[i + 2][j + 2] && chessPlace[i][j] == chessPlace[i + 3][j + 3] && chessPlace[i][j] == chessPlace[i + 4][j + 4]) { return flag = "win"; } } } //Backslash wins for (var i = 0; i < chessWidth; i++) { for (var j = chessWidth - 1; j > 3; j--) { if (chessPlace[i][j] != 0 && chessPlace[i][j] == chessPlace[i + 1][j - 1] && chessPlace[i][j] == chessPlace[i + 2][j - 2] && chessPlace[i][j] == chessPlace[i + 3][j - 3] && chessPlace[i][j] == chessPlace[i + 4][j - 4]) { return flag = "win"; } } }

Method 2: look from the current position to the surrounding position.

function win(eventX, eventY, num) { // Save the number of the same pieces connected together var count = 0; // Save the current chess coordinates var x = eventX; var y = eventY; // Horizontal win for (var i = x - 1; i >= 0; i--) { if (chessPlace[i][y] == num) { count++; } else { break; } } for (var i = x + 1; i < chessWidth; i++) { if (chessPlace[i][y] == num) { count++; } else { break; } } if (count >= 4) { return true; } count = 0; // Vertical win for (var i = y - 1; i >= 0; i--) { if (chessPlace[x][i] == num) { count++; } else { break; } } for (var i = y + 1; i < chessWidth; i++) { if (chessPlace[x][i] == num) { count++; } else { break; } } if (count >= 4) { return true; } count = 0; // Positive slash wins for (var i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) { if (chessPlace[i][j] == num) { count++; } else { break; } } for (var i = x + 1, j = y + 1; i < chessWidth && j < chessWidth; i++, j++) { if (chessPlace[i][j] == num) { count++; } else { break; } } if (count >= 4) { return true; } count = 0; // Backslash wins for (var i = x - 1, j = y + 1; i >= 0 && j < chessWidth; i--, j++) { if (chessPlace[i][j] == num) { count++; } else { break; } } for (var i = x + 1, j = y - 1; i < chessWidth && j >= 0; i++, j--) { if (chessPlace[i][j] == num) { count++; } else { break; } } if (count >= 4) { return true; } count = 0; }

Here I use the second method, and I also recommend you to use the second method, because the first method is not efficient, and there is a bug that the backslash sometimes cannot determine the outcome.

(8) Draw

By traversing the whole chessboard to see if there is an empty position, if there is, it is not a draw, on the contrary, it is a draw.

function tie() { var count = 0; for (var i = 0; i < chessWidth; i++) { for (var j = 0; j < chessWidth; j++) { if (chessPlace[i][j] != 0) { count++; } else { break; } } } if (count == 225) { return true; } }

Here, our Gobang has almost been developed. The next step is the processing on the UI. Of course, I only designed man-machine combat here, not player to player. If you are interested, you can try to add a player to player module.

3, Complete game rendering

1. Menu:

2. Game part:

4, Display of operation results

AI Gobang online preview

5, Project source code

Gobang project source code

10 September 2021, 04:36 | Views: 6004

Add new comment

For adding a comment, please log in
or create account

0 comments