原生JS+Canvas实现五子棋 AI简单算法

<!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>

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值