# 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
event = event || window.event;
// If it's not the player's turn
if (isMan == false) {
return
}
// If the game is over
if (gameOver == true) {
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 {
}
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
// Trigger button click sound
clickSound.play();
if (gameOver) {
alert("The game is over. I can't repent!");
} else if (isRetract == false) {
} 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
// Trigger button click sound
clickSound.play();
if (gameOver) {
alert("The game is over, you can't undo it!");
} else if (isUnretract == false) {
} 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 2. Game part: # 4, Display of operation results

AI Gobang online preview

# 5, Project source code

Gobang project source code

Posted on Fri, 10 Sep 2021 04:36:53 -0400 by Alt_F4