export const is_empty = 'isEmpty'; /** * 机器人 */ export const is_white = 'isWhite'; export const is_black = 'isBlack'; export const directions = [ [1, 0], // 水平方向 [0, 1], // 垂直方向 [1, 1], // 右下方向 [1, -1] // 左下方向 ] /** * 检查四个方向连续的棋子数 * @param {object} param * @param {number} param.row 行 * @param {number} param.col 列 * @param {(is_empty|is_white|is_black)[]} param.board 棋盘 * @param {is_white|is_black|is_empty} [param.player] 当前棋子类型 * @param {number} param.win_size 需要几个棋子才赢 * @returns {[number, number][] | false} 赢棋的下标二维数组 或者 不能赢 */ export function checkWin({ row, col, board, player, win_size }) { const ROW = board.length; const COL = board[0].length const TYPE = player ?? board[row][col] let res = [] for (let i = 0; i < directions.length; i++) { res = [[row, col]]; const [dx, dy] = directions[i]; let x = row + dx; let y = col + dy; // 向正反两个方向扩展,检查是否有连续的五个相同棋子 while (x >= 0 && x < ROW && y >= 0 && y < COL && board[x][y] === TYPE) { res.push([x, y]) x += dx; y += dy; } x = row - dx; y = col - dy; while (x >= 0 && x < ROW && y >= 0 && y < COL && board[x][y] === TYPE) { res.push([x, y]) x -= dx; y -= dy; } // 出现五连珠,返回胜利 if (res.length >= win_size) { return res; } } // 当前空位,可以结成多少颗连珠 return false; } /** * 机器人下棋位置 * @param {(is_empty|is_white|is_black)[][]} board 棋盘 * @param {is_white} robot 机器人 * @param {number} win_size 赢棋的棋子颗数 * @returns {number[]} */ export function robotPlay(board, robot, win_size) { let maxScorePos = []; let maxScore = -1; // 空位 const empty_points = board.map((item, row) => { return item.flatMap((_item, col) => _item === is_empty ? [[row, col]] : []) }).flat(1) // 对每个空位进行评分 for (let e_i = 0; e_i < empty_points.length; e_i++) { const point = empty_points[e_i] let score = 0; const [row, col] = point; // 判断下子后是否获胜 const win = checkWin({ row, col, board, player: robot, win_size }); if (win) { return [row, col] } else { // 判断对手是否能在这一个空位上获胜 const oppWin = checkWin({ row, col, board, player: is_black, win_size }); if (oppWin) { return [row, col] } else { // 这里要应该是要返回这个位置的分数 score = estimateScore({ row, col, board, win_size }) } } // 选取分数最高的空位 if (score >= maxScore) { maxScorePos = [row, col]; maxScore = score; } }; return maxScorePos; } const direction_4 = [ // \ [[-1, -1], [1, 1]], // | [[0, -1], [0, 1]], // / [[1, -1], [-1, 1]], // - [[-1, 0], [1, 0]] ] /** * 计分 * 零颗棋子记 个位数 的积分 * 一颗棋子记 十位数 的积分 * 两颗棋子记 百位数 的积分 * 三颗棋子记 千位数 的积分 * 以此类推 * 计算公式 10**(n) * * 是以一条线的记录积分,一个位置上正负方向为一条线 * 当一个位置上 横 竖 斜 反斜 位置上都有棋子 * 积分最多为 4 * (10**8) = 400000000 * 积分最小为 4 * (10**0) = 4 * * 边界判断 * 如果在棋盘边缘,需要判断连子最多有几颗,小于5颗,可以直接放弃当前位置 */ /** * 获取获取边界距离 * @param {number} size * @param {number} num */ function getBoundary(size, num) { const HALF = size / 2 return (num > HALF ? size - num : num) + 1 // 当前位置算进去 } /** * 获取一个一个方向上的棋子数 * @param {*} board * @param {*} row * @param {*} col * @param {*} param3 * @param {*} piece_type * @returns */ function getDirectionScore(board, row, col, [x, y], piece_type) { const ROW = board.length const COL = board[0].length let res = 0 let _row = row + x let _col = col + y while (true) { if (_row < 0 || _col < 0 || !board[_row] || board[_row][_col] !== piece_type) { break; } res += 1 _row += x _col += y } // 判断边距 return res } /** * 评估每个空位置的价值,从八个方向去计算, * 计分规则 1,10,100,1000,10000,100000 根据棋子数量计分,每多一颗棋子,分数乘以10 * @param {object} Estimate * @param {object} Estimate.board * @param {number} Estimate.row * @param {number} Estimate.col * @return {number} */ function estimateScore({ board, row, col, win_size }) { return direction_4.reduce((r, [p1, p2]) => { const square = getDirectionScore(board, row, col, p1, win_size, is_white) + getDirectionScore(board, row, col, p2, win_size, is_white) return r + 10 ** square }, 0) }