关于我使用原生JavaScript开发了一个Ai五子棋,人机对战 | 内附源码!

8,196 阅读3分钟

《五子棋》简单介绍

五子棋起源于中国,是全国智力运动会竞技项目之一,是一种两人对弈的纯策略型棋类游戏。双方分别使用黑白两色的棋子,下在棋盘直线与横线的交叉点上,先形成五子连珠者获胜。

五子棋容易上手,老少皆宜,而且趣味横生,引人入胜。它不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。

《AI五子棋》简单介绍

第一回合: 玩家下棋之后,电脑会在玩家旁边下棋。

第二回合:玩家再次下棋,电脑会通过分值计算下棋。计算方法是玩家下棋+200,如果连在一起根据个数加分,如果玩家两个连在一起则+400,计算机就会根据分数大小判断是否需要堵着,当三个连在一起的时候,计算机会根据它下期的分值和玩家下棋的分数进行判断,分值越高表示距离赢更近,如果电脑不能赢,则会立马堵住。

《AI五子棋》游戏玩法

玩家棋子为:黑棋子

电脑棋子为:红棋子

玩家下棋之后,电脑通过计算自动下棋。

玩家或电脑任意一方连续5个棋子则为一方胜利。

实现人机五子棋

代码拆分

HTML 和 CSS

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #canvas {
            box-shadow: 3px 3px 3px 3px rgb(194, 188, 188);
            cursor: pointer;
        }
    </style>
</head>

<body>
    <canvas id="canvas" width="450" height="450"></canvas>```
</body>

</html>

image.png

接下来该使用JavaScript控制canvas绘制格子

绘制地图

JavaScript代码部分

获取canvas

let canvas = document.querySelector('#canvas');
let ctx = canvas.getContext('2d');

绘制格子

// 设置颜色
ctx.strokeStyle = '#B9B9B9';
// 横线
for (let i = 0; i < 15; i++) {
    ctx.moveTo(15, 15 + i * 30);
    ctx.lineTo(435, 15 + i * 30);
    ctx.stroke();
}
// 竖线
for (let i = 0; i < 15; i++) {
    ctx.moveTo(15 + i * 30, 15);
    ctx.lineTo(15 + i * 30, 435);
    ctx.stroke();
}

做到这里,我们的格子已经成型了

image.png

赢法

赢法原理

这一步是计算五子棋的每一种赢法,使用for循环将每一种赢法计算出来,并且添加到数组当中。

拿横线赢法举例,他会找到这五个,形成一种赢法:

image.png

第二次循环,沿着上一次找到的绘制+1,接着找:

image.png

直到找到最后一中赢法

image.png

竖线也是如此,就不多解释了,都是一个原理,把他竖过来。

斜线的第一步是这样的:

image.png

当一中颜色的排列,符合一种赢法,游戏将胜利!

image.png

赢法代码

// 赢法数组
let wins = [];
for (let i = 0; i < 15; i++) {
    wins[i] = [];
    for (let j = 0; j < 15; j++) {
        wins[i][j] = [];
    }
}

let count = 0;
// 横线
for (let i = 0; i < 15; i++) {
    for (let j = 0; j < 11; j++) {
        for (let k = 0; k < 5; k++) {
            wins[j + k][i][count] = true;
        }
        count++;
    }
}

// 竖线
for (let i = 0; i < 15; i++) {
    for (let j = 0; j < 11; j++) {
        for (let k = 0; k < 5; k++) {
            wins[i][j + k][count] = true;
        }
        count++;
    }
}

// 正斜线
for (let i = 0; i < 11; i++) {
    for (let j = 0; j < 11; j++) {
        for (let k = 0; k < 5; k++) {
            wins[i + k][j + k][count] = true;
        }
        count++;
    }
}

// 反斜线
for (let i = 0; i < 11; i++) {
    for (let j = 14; j > 3; j--) {
        for (let k = 0; k < 5; k++) {
            wins[i + k][j - k][count] = true;
        }
        count++;
    }
}

下棋

首先定义一个二维数组,标记坐标是否已经下了棋子

let chessboard = [];
for (let i = 0; i < 15; i++) {
    chessboard[i] = [];
    for (let j = 0; j < 15; j++) {
        chessboard[i][j] = 0;
    }
}
// 标记谁来下棋
let me = true;
// 判断游戏是否结束
let over = false;
// 记录用户在赢法上的分支
let myWin = [];
// 记录计算机在赢法上的分值;
let aiWin = [];
for (let i = 0; i < count; i++) {
    myWin[i] = 0;
    aiWin[i] = 0;
}

获取点击位置

获取距离画布canvas的距离,然后去除以30(格子的宽度),在进行取整。

每次下棋都需要判断是否赢,只要赢法等于5,则胜利。

// 获取坐标
let x = e.offsetX;
let y = e.offsetY;

// 舍弃小数
let i = Math.floor(x / 30);
let j = Math.floor(y / 30);
if (chessboard[i][j] == 0) {
    // 标记这个地方有一个子了
    chessboard[i][j] = 1;
    oneStep(i, j, me);
    for (let k = 0; k < count; k++) {
        if (wins[i][j][k]) {
            myWin[k]++;
            if (myWin[k] == 5) {
                over = true;
                alert('你赢了')
            }
        }
    }
}

玩家下棋

玩家下棋比下面的计算机下棋简单很多,只需要在点击的格子绘制一个棋子出来就可以了。

// 玩家下棋
function oneStep(i, j, me) {
    // 下一个棋子
    ctx.beginPath();
    ctx.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
    ctx.closePath();
    let color;
    if (me) {
        color = '#000000';
    } else {
        color = 'red';
    }
    ctx.fillStyle = color;
    ctx.fill();
}

计算机下棋原理

玩家点击之后,执行电脑下棋ai函数

里面有两个数组,是用来存储分值用的。

分值根据赢法的数组去进行判断一个赢法数组里面,玩家或电脑占了几个,占的越多说明分值越高,越高代表距离赢越近,这是电脑进行判断的重要部分。

玩家下一个棋子分值+200,则电脑必须更多,比如+220,如果一样的话,两个分值相等,电脑也不知道下哪里,会随便下棋(如果两个棋子中间夹了对方的棋子,则这个赢法的分值不会叠加)

一个赢法中达到了4个一种棋子的时候,电脑的分值会提高到最高,这样的目的就是下棋不会乱下,让电脑连成5个。

// 计算机下棋
function ai() {
    // 空白子在用户所占用赢法上的分值
    let myScore = [];
    // 空白子在计算机所占赢法上的分值
    let aiScore = [];

    for (let i = 0; i < 15; i++) {
        myScore[i] = [];
        aiScore[i] = [];
        for (let j = 0; j < 15; j++) {
            myScore[i][j] = 0;
            aiScore[i][j] = 0;
        }
    }
    // 空白字的最大分值
    let max = 0;
    // 最大分值空白子所在的坐标
    let x = 0, y = 0;
    for (let i = 0; i < 15; i++) {
        for (let j = 0; j < 15; j++) {
            // 判断是不是空白子
            if (chessboard[i][j] == 0) {
                for (let k = 0; k < count; k++) {
                    if (wins[i][j][k]) {
                        if (myWin[k] == 1) {
                            myScore[i][j] += 200;
                        } else if (myWin[k] == 2) {
                            myScore[i][j] += 400;
                        } else if (myWin[k] == 3) {
                            myScore[i][j] += 2000;
                        } else if (myWin[k] == 4) {
                            myScore[i][j] += 10000;
                        }
                        if (aiWin[k] == 1) {
                            aiScore[i][j] += 220;
                        } else if (aiWin[k] == 2) {
                            aiScore[i][j] += 420;
                        } else if (aiWin[k] == 3) {
                            aiScore[i][j] += 2200;
                        } else if (aiWin[k] == 4) {
                            aiScore[i][j] += 20000;
                        }
                    }
                }
                if (myScore[i][j] > max) {
                    max = myScore[i][j];
                    x = i;
                    y = j;
                } else if (myScore[i][j] == max) {
                    if (aiScore[i][j] > max) {
                        max = aiScore[i][j];
                        x = i;
                        y = j;
                    }
                }
                if (aiScore[i][j] > max) {
                    max = aiScore[i][j];
                    x = i;
                    y = j;
                } else if (aiScore[i][j] == max) {
                    if (myScore[i][j] > max) {
                        max = myScore[i][j];
                        x = i;
                        y = j;
                    }
                }
            }
        }
    }

完整代码展示

canvas.onclick = function (e) {
    // 判断游戏是否结束
    if (over) return;

    // 判断是否可以下棋
    if (!me) return;

    // 获取坐标
    let x = e.offsetX;
    let y = e.offsetY;

    // 舍弃小数
    let i = Math.floor(x / 30);
    let j = Math.floor(y / 30);
    if (chessboard[i][j] == 0) {
        // 标记这个地方有一个子了
        chessboard[i][j] = 1;
        oneStep(i, j, me);
        for (let k = 0; k < count; k++) {
            if (wins[i][j][k]) {
                myWin[k]++;
                if (myWin[k] == 5) {
                    over = true;
                    alert('你赢了')
                }
            }
        }
        if (!over) {
            me = !me;
            // 计算机下棋
            ai();
        }
    }
}

// 计算机下棋
function ai() {
    // 空白子在用户所占用赢法上的分值
    let myScore = [];
    // 空白子在计算机所占赢法上的分值
    let aiScore = [];

    for (let i = 0; i < 15; i++) {
        myScore[i] = [];
        aiScore[i] = [];
        for (let j = 0; j < 15; j++) {
            myScore[i][j] = 0;
            aiScore[i][j] = 0;
        }
    }
    // 空白字的最大分值
    let max = 0;
    // 最大分值空白子所在的坐标
    let x = 0, y = 0;
    for (let i = 0; i < 15; i++) {
        for (let j = 0; j < 15; j++) {
            // 判断是不是空白子
            if (chessboard[i][j] == 0) {
                for (let k = 0; k < count; k++) {
                    if (wins[i][j][k]) {
                        if (myWin[k] == 1) {
                            myScore[i][j] += 200;
                        } else if (myWin[k] == 2) {
                            myScore[i][j] += 400;
                        } else if (myWin[k] == 3) {
                            myScore[i][j] += 2000;
                        } else if (myWin[k] == 4) {
                            myScore[i][j] += 10000;
                        }
                        if (aiWin[k] == 1) {
                            aiScore[i][j] += 220;
                        } else if (aiWin[k] == 2) {
                            aiScore[i][j] += 420;
                        } else if (aiWin[k] == 3) {
                            aiScore[i][j] += 2200;
                        } else if (aiWin[k] == 4) {
                            aiScore[i][j] += 20000;
                        }
                    }
                }
                if (myScore[i][j] > max) {
                    max = myScore[i][j];
                    x = i;
                    y = j;
                } else if (myScore[i][j] == max) {
                    if (aiScore[i][j] > max) {
                        max = aiScore[i][j];
                        x = i;
                        y = j;
                    }
                }
                if (aiScore[i][j] > max) {
                    max = aiScore[i][j];
                    x = i;
                    y = j;
                } else if (aiScore[i][j] == max) {
                    if (myScore[i][j] > max) {
                        max = myScore[i][j];
                        x = i;
                        y = j;
                    }
                }
            }
        }
    }
    
    // 玩家下棋
    oneStep(x, y, me);
    
    chessboard[x][y] = 1;
    for (let k = 0; k < count; k++) {
        if (wins[x][y][k]) {
            aiWin[k] += 1;
            if (aiWin[k] == 5) {
                alert('计算机赢了')
                over = true;
            }
        }
    }
    if (!over) me = !me;
}
// 玩家下棋
function oneStep(i, j, me) {
    // 下一个棋子
    ctx.beginPath();
    ctx.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
    ctx.closePath();
    let color;
    if (me) {
        color = '#000000';
    } else {
        color = 'red';
    }
    ctx.fillStyle = color;
    ctx.fill();
}

到这里,人机五子棋已经完成了

结尾

本人还是个学生,有很多不足的地方,希望各位大佬们给小弟指点指点。

9150e4e5gw1f9rw2mivlvg204603v77t.gif

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!