export const is_empty = 'isEmpty'; /** * 机器人 */ export const is_white = 'isWhite'; export const is_black = 'isBlack'; /** * [y, x] === [row, col] === [行,列] */ export const directions = [ [1, 0], // 垂直方向 [0, 1], // 水平方向 [1, 1], // \ 方向 [1, -1] // / 方向 ] /** * 检查 n 是否在 start 与 end 之间,但不包括 end。 * 如果 end 没有指定,那么 start 设置为0。 * 如果 start 大于 end,那么参数会交换以便支持负范围。 * @param {number} n * @param {number} start * @param {number|undefined} end */ function inRange(n, start = 0, end) { if (end === undefined) { [start, end] = [0, start] } if (start > end) { [start, end] = [end, start] } return n >= start && n < end } /** * 产生一个包括 lower 与 upper 之间的数。 * 如果只提供一个参数返回一个0到提供数之间的数。 * @param {number} lower * @param {number} upper */ function random(lower = 0, upper = 1) { // 交换 lower 和 upper 的值(如果 lower 大于 upper) if (lower > upper) { [lower, upper] = [upper, lower]; } // 计算范围内的整数数量 const range = upper - lower + 1; // 生成随机整数 const randomInteger = Math.floor(Math.random() * range) + lower; return randomInteger; } /** * 检查四个方向连续的棋子数 * @param {object} param * @param {number} param.row 行 y * @param {number} param.col 列 x * @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 let res = [] for (let i = 0; i < directions.length; i++) { res = [[row, col]]; const [dy, dx] = directions[i]; let x = col + dx; let y = row + dy; // 向正反两个方向扩展,检查是否有连续的五个相同棋子 while (inRange(x, COL) && inRange(y, ROW) && board[y][x] === player) { res.push([y, x]) x += dx; y += dy; } x = col - dx; y = row - dy; while (inRange(x, COL) && inRange(y, ROW) && board[y][x] === player) { res.push([y, x]) x -= dx; y -= dy; } // 出现五连珠,返回胜利 if (res.length >= win_size) { return res; } } // 当前空位,可以结成多少颗连珠 return false; } /** * 机器人下棋位置 * @param {(is_empty|is_white|is_black)[][]} board 棋盘 * @param {number} win_size 赢棋的棋子颗数 * @returns {number[]} */ export function robotPlay(board, win_size) { let maxScorePos = []; let maxScore = -1; // 空位 对每个空位进行评分 board.map((item, row) => { return item.map((_item, col) => { if (_item === is_empty) { let score = 0; // 评估每个空位置的价值,从八个方向去计算 score = directions.reduce((r, [y, x]) => { const square = getDirectionScore(board, row, col, [y, x], win_size) return r + (10 ** square) }, 0) // 选取分数最高的空位 if (score >= maxScore) { maxScorePos = [row, col]; maxScore = score; } } }) }) return maxScorePos; } /** * 计分 * 零颗棋子记 个位数 的积分 * 一颗棋子记 十位数 的积分 * 两颗棋子记 百位数 的积分 * 三颗棋子记 千位数 的积分 * 以此类推 * 计算公式 10**(n) * * 是以一条线的记录积分,一个位置上正负方向为一条线 * 同样是三颗棋子,没有挡住的比挡住一边的分数要大 * 当一个位置上 横 竖 斜 反斜 位置上都有棋子 * 积分最大为 4 * (10**8) = 400000000 * 积分最小为 4 * (10**0) = 4 * * 边界判断 * 如果在棋盘边缘,需要判断连子最多有几颗,小于 win_size 颗,可以直接放弃当前位置 * * 黑白棋棋盘积分判断 * 比较黑白棋的棋盘最高积分,积分大的位置优先下,避免总是在防守 * * 随机下棋 * 如果一个判定回合,棋盘积分中最高的积分有多个,则随机选择一个位置,避免有规律 * */ /** * 获取获取边界距离 * @param {number} size * @param {number} num */ function getBoundary(size, num) { const HALF = Math.floor(size / 2) // 先下取整 return (num > HALF ? size - num : num) + 1 // 当前位置算进去 } /** * 获取这一边方向的信息,连珠颗数,延伸出去的空位数 * @param {(is_empty|is_white|is_black)[][]} board 棋盘 * @param {number} row 行 * @param {number} col 列 * @return {num: number, empty_num: number} 说明:num: 棋子个数;empty_num: 空格数量 */ function getJoinInfo(board, row, col, [y, x]) { const ROW = board.length const COL = board[0].length // 连续棋子数 let num = 0 // 一侧到边缘的距离 let empty_num = 0 // 不包括当前位置 let _row = row + y let _col = col + x while (true) { if (!inRange(_row, ROW) || !inRange(_col, COL)) { break; } const item = board[_row][_col] if (item === is_white) { break; } // 连珠计数 if (item === is_black) { num += 1 } // 计算空位数 if (item === is_empty) { empty_num += 1 } _row += y _col += x } return { num, empty_num, } } /** * 获取一个方向上的棋子数,八个方向 * @param {(is_empty|is_white|is_black)[]} board 棋盘 * @param {number} row 行 y * @param {number} col 列 x * @param {(is_empty|is_white|is_black)[]} board 棋盘 * @param {number} win_size 需要几个棋子才赢 * @returns */ function getDirectionScore(board, row, col, [y, x], win_size) { const { num: r_num, empty_num: r_empty_num } = getJoinInfo(board, row, col, [y, x]) const { num: l_num, empty_num: l_empty_num } = getJoinInfo(board, row, col, [y * -1, x * -1]) return r_num + l_num }