《五子棋》简单介绍
五子棋起源于中国,是全国智力运动会竞技项目之一,是一种两人对弈的纯策略型棋类游戏。双方分别使用黑白两色的棋子,下在棋盘直线与横线的交叉点上,先形成五子连珠者获胜。
五子棋容易上手,老少皆宜,而且趣味横生,引人入胜。它不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。
《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>
接下来该使用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();
}
做到这里,我们的格子已经成型了
赢法
赢法原理
这一步是计算五子棋的每一种赢法,使用for循环将每一种赢法计算出来,并且添加到数组当中。
拿横线赢法举例,他会找到这五个,形成一种赢法:
第二次循环,沿着上一次找到的绘制+1,接着找:
直到找到最后一中赢法
竖线也是如此,就不多解释了,都是一个原理,把他竖过来。
斜线的第一步是这样的:
当一中颜色的排列,符合一种赢法,游戏将胜利!
赢法代码
// 赢法数组
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();
}
到这里,人机五子棋已经完成了
结尾
本人还是个学生,有很多不足的地方,希望各位大佬们给小弟指点指点。
我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!