<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>原生JS+Canvas实现五子棋 AI简单算法</title>
<style>
canvas { border: 1px solid black; }
</style>
</head>
<body>
<canvas id="board" width="450" height="450"></canvas>
<script>
const boardSize = 15;
const cellSize = 30;
状态:0无子,1黑子,2蓝子 + 8个方向计算:顺时针 上下,右上左下,右左,右下左上
let board = Array(boardSize).fill().map(() => Array(boardSize).fill(0));
let board1 = Array(boardSize).fill().map(() => Array(boardSize).fill([0,0,0,0,0,0,0]));//记录空子8各方向的黑子状态
let board2 = Array(boardSize).fill().map(() => Array(boardSize).fill([0,0,0,0,0,0,0]));//记录空子8各方向的蓝子状态
let board3 = Array(boardSize).fill().map(() => Array(boardSize).fill(0));//记录空子8各方向的黑子数
let board4 = Array(boardSize).fill().map(() => Array(boardSize).fill(0));//记录空子8各方向的蓝子数
const ctx = document.getElementById('board').getContext('2d');
const player = 1;
const ai = 2;
let status = 0;
//console.info(board);
// 辅助函数
//function isGameOver(board) { /* ... */ }
//检查棋盘状态
function isGameOver(board,i,j) {
if (isWinner(board, i, j, board[i][j])) {
return true;
}
return false;
}
function isWinner(board, x, y, s) {
console.info('isWinner');
//8个方向计算 顺时针 上下,右上左下,右左,右下左上
const directions = [[0, 1], [0, -1],[1, 1], [-1, -1],[1, 0], [-1, 0],[1, -1],[-1, 1]];
for (const [dx, dy] of directions) {
let count = 0;
//let count1 = 0;
let nx = x, ny = y;
let nx1 = x, ny1 = y;
let num = 0;
let tnx = x, tny= y;
while (true) {
nx += dx;
ny += dy;
//console.info('tplayer->board:['+tnx+','+tny+']');
//console.info('player->board:['+nx+','+ny+']:'+board[nx][ny]);
if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] === s) {
//只统计连续5个棋子
if ((nx-tnx)>1 || (nx-tnx) <-1 ) {
break;
}
if ((ny-tny)>1 || (ny-tny) <-1 ) {
break;
}
count++;
//console.info('s->board:['+nx+','+ny+']:'+board[nx][ny]);
tnx = nx;
tny= ny;
if (count === 4) {
console.info('play'+s+': game over');
if (s==1) {
status = 1;
}else {
status = 2;
}
return true;
}
}
num++;
if (num > 5) break;
}
}
return false;
}
//生成移动列表 可落子的坐标
function getAvailableMoves(board) {
//console.info('getAvailableMoves');
const moves = [];
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (board[i][j] == 0) {
moves.push([i, j]);
}
}
}
return moves;
}
//执行移动
function makeMove(board, move, s) {
//console.info('makeMove');
const [x, y] = move;
board[x][y] = s;
}
//Minimax 算法
function minimax(x,y,s) {
//8个方向计算 顺时针 上下,右上左下,右左,右下左上
const directions = [[0, 1], [0, -1],[1, 1], [-1, -1],[1, 0], [-1, 0],[1, -1],[-1, 1]];
let j =0;
let getds = [];
for (const [dx, dy] of directions) {
let count = 0;
//let count1 = 0;
let nx = x, ny = y;
let num = 0;
let tnx = x, tny= y;
while (true) {
nx += dx;
ny += dy;
//console.info('tplayer->board:['+tnx+','+tny+']');
//console.info('player->board:['+nx+','+ny+']:'+board[nx][ny]);
if (nx >= 0 && nx < boardSize && ny >= 0 && ny < boardSize && board[nx][ny] === s) {
//只统计连续5个棋子
if ((nx-tnx)>1 || (nx-tnx) <-1 ) {
break;
}
if ((ny-tny)>1 || (ny-tny) <-1 ) {
break;
}
count++;
//console.info('s->board:['+nx+','+ny+']:'+board[nx][ny]);
tnx = nx;
tny= ny;
if (count === 5) {
console.info(s+', s game over');
return true;
}
}
num++;
if (num > 5) break;
}
getds[j]=count;
j++;
}
if (s==1) {//记录白子状态
board1[x][y] = [getds[0],getds[1],getds[2],getds[3],getds[4],getds[5],getds[6],getds[7]];
}
if (s==2) {//记录黑子状态
board2[x][y] = [getds[0],getds[1],getds[2],getds[3],getds[4],getds[5],getds[6],getds[7]];
}
return false;
}
// AI 走法
function aiMove(board) {
console.info('aiMove');
const moves = getAvailableMoves(board);//获取空子坐标
//计算8个方向的棋子
for (const move of moves) {
const [x, y] = move;
minimax(x,y,player);//计算自己8个方向的棋子数
minimax(x,y,ai);//计算AI8个方向的棋子数
}
let i=0,i1=0,j=0,j1=0;
let minmax1 = 0;
let minmax2 = 0;
for (const move of moves) {//获取这个空子8个方向计算自己的最大棋子数
const [x, y] = move;
//i = x;
//j = y;
//console.info([x, y]);
const [x1, y1,x2, y2,x3, y3,x4, y4] = board1[x][y];
//console.info([x1, y1,x2, y2,x3, y3,x4, y4]);
if ( (x1+y1==4) || (x2+y2==4) || (x3+y3==4) || (x4+y4==4) ){
board3[x][y] = 4;
continue;
}
if ( (x1+y1==3) || (x2+y2==3) || (x3+y3==3) || (x4+y4==3) ){
board3[x][y] = 3;
continue;
}
if ( (x1+y1==2) || (x2+y2==2) || (x3+y3==2) || (x4+y4==2) ){
board3[x][y] = 2;
continue;
}
if ( (x1+y1==1) || (x2+y2==1) || (x3+y3==1) || (x4+y4==1) ){
board3[x][y] = 1;
continue;
}
}
console.info('-------------------');
for (const move of moves) { //获取这个空子8个方向计算AI的最大棋子数
const [x, y] = move;
//console.info([x, y]);
const [x1, y1,x2, y2,x3, y3,x4, y4] = board2[x][y];
//console.info([x1, y1,x2, y2,x3, y3,x4, y4]);
if ( (x1+y1==4) || (x2+y2==4) || (x3+y3==4) || (x4+y4==4) ){
board4[x][y] = 4;
continue;
}
if ( (x1+y1==3) || (x2+y2==3) || (x3+y3==3) || (x4+y4==3) ){
board4[x][y] = 3;
continue;
}
if ( (x1+y1==2) || (x2+y2==2) || (x3+y3==2) || (x4+y4==2) ){
board4[x][y] = 2;
continue;
}
if ( (x1+y1==1) || (x2+y2==1) || (x3+y3==1) || (x4+y4==1) ){
board4[x][y] = 1;
continue;
}
}
let tmpmax = 0;
for (var n=0;n< boardSize;n++) {//获取这个空子8个方向自己最大棋子数的坐标
for (var m=0;m< boardSize;m++) {
if (board[n][m] == 0 && board3[n][m] > minmax1) {
//console.info('minmax1:'+minmax1);
minmax1 = board3[n][m];//;
i = n;
j = m;
}
}
}
//tmpmax = 0;
for (var n=0;n< boardSize;n++) {//获取这个空子8个方向AI最大棋子数的坐标
for (var m=0;m< boardSize;m++) {
if (board[n][m] == 0 && board4[n][m] > minmax2) {
//console.info('minmax1:'+minmax1);
minmax2 = board4[n][m];//;
i1 = n;
j1 = m;
}
}
}
//console.info(board3);
//console.info(board4);
console.info('minmax1:'+minmax1);
console.info('minmax2:'+minmax2);
if (minmax1 > minmax2) {//做出判断,如果对方出现的棋子数大于自己就先封杀对方,否则自己赢优先
board[i][j] = ai;
console.info('minmax1:',i,j);
isGameOver(board,i,j);
} else {
board[i1][j1] = ai;
console.info('minmax2:',i1,j1);
isGameOver(board,i1,j1);
}
// console.info(board1);
//console.info(board2);
}
// 绘制棋盘
function drawBoard() {
//console.info('drawBoard');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
ctx.strokeRect(j * cellSize, i * cellSize, cellSize, cellSize);
if (board[i][j] === player) {
ctx.beginPath();
ctx.arc((j * cellSize + cellSize / 2), (i * cellSize + cellSize / 2), cellSize / 3, 0, 2 * Math.PI);
ctx.fillStyle = 'black';
ctx.fill();
} else if (board[i][j] === ai) {
//ctx.fillRect((j * cellSize + 2), (i * cellSize + 2), cellSize - 4, cellSize - 4);
ctx.beginPath();
ctx.arc((j * cellSize + cellSize / 2), (i * cellSize + cellSize / 2), cellSize / 3, 0, 2 * Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
}
}
}
//console.info(4);
}
//延时下棋
function timedMsg(board)
{
var t=setTimeout("aiMove("+board+")",300)
}
// 玩家下棋
document.getElementById('board').addEventListener('click', function (e) {
const rect = e.target.getBoundingClientRect();
const x = Math.floor((e.clientX - rect.left) / cellSize);
const y = Math.floor((e.clientY - rect.top) / cellSize);
if (status >0 ) {
console.info('game over');
return ;
}
if ( board[y][x] == 0) {
board[y][x] = player;
console.info([y,x]);
if (!isGameOver(board,y,x)) {
aiMove(board);
}
drawBoard();
}else {
console.info("已有棋子不允许下");
}
});
drawBoard();
</script>
</body>
</html>
原生JS+Canvas实现五子棋 AI简单算法
最新推荐文章于 2024-08-04 16:33:59 发布