基于react实现五子棋人机博弈

189 阅读4分钟

写在前面

之前的一段时间手撸了五子棋人机(入门级)博弈,现在写个文章复盘下过程~

效果

image.png

实现步骤

  1. 画出棋盘
  2. 实现交互落子
  3. 算胜负

画棋盘

第一步想的就是用一张背景图作为棋盘,然后用一个15*15二维数组记录棋盘的数据,数组里每个数据渲染一个div盒子,也就是225个盒子。

每个盒子样式根据数据来决定不同样式:无样式、黑子、白子。棋子的实现基本上为两种:1.使用css绘制 2. 使用图片。

于是乎第一个问题就来了

image.png

当时思考一下...

不用局限于将有线的棋盘作为容器可以外围在扩大一点 image.png

落子区域大25px(棋盘每个格子大小为50*50)的区域作为容器,这有正常排列在视觉上呈现的就算在交界处

image.png

落子

通过点击事件可以落子,返回二维数组的坐标,根据坐标修改二维数组的数据,实现落子。数据为0为渲染白子样式 为1则渲染黑子样式

image.png

实现交互落子

我这的思路是使用一个全局的标志来判断该谁落子

  // 标志该谁走棋 1是黑子 0是白子
  const [flag, setFlag] = useState<number>(0);

落子后改变falg值实现交互

// 监听flag变化如果falg为1则落子 isLiquidation暂时不用管
  useEffect(() => {
    if (flag === 1) {
      if (isLiquidation === 0) {
        robotDownChess();
      }
    }
    return () => { };
  }, [flag, isLiquidation]);
  

实现robotDownChess方法

// calculatePoint为棋盘分值算法
  const robotDownChess = (): void => {
    let [x, y] = [0, 0];
    [x, y] = calculatePoint(chessList, 0);
    if (chessList[x][y] === '') downChess(x, y, 'robot');
  };

实现downChess方法

// 数组深度克隆
  const deepCloneArr = (arr: any[]) => {
    return JSON.parse(JSON.stringify(arr));
  };

// 落子方法
  const downChess = (x: number, y: number, type = 'user') => {
    if (typeof x !== 'number' || typeof y !== 'number') {
      throw new Error('index1 或 index2 不是number类型');
    }
    const chessListCopy = deepCloneArr(chessList);
    const res = chessListCopy[x][y];
    if (typeof res === 'number' && [0, 1].includes(res)) {
      message.warn('该地方已落子');
      return;
    }

    // 落子到棋盘上的逻辑
    if (flag === 0) {
      chessListCopy[x][y] = 0;
      setChessList(chessListCopy);
      setFlag(1);
    } else {
      chessListCopy[x][y] = 1;
      setChessList(chessListCopy);
      setFlag(0);
    }
    
    // ...other code
    }


分值算法现在只是初级的,一个循环遍历的过程,主要算斜着正着的连续同色子的数量,没有算是否为活子

注:活子表示连续同色棋子没有异色棋子拦截

主要算左斜 右斜 竖直 横向 连续多少个子然后在给他们分别算分值就OK

然后通过运算白黑两子来计算自己是该防还是攻,如果分数同等则以攻为守,即对方连续3子我连续3子,电脑会优先成自己的四子,当然这也是最基本要考虑的逻辑

实现方法

/**
 * @param arr 棋盘数组
 * @param color  当前落子颜色
 * @returns  返回最佳落子位置
 */
export const calculatePoint = (arr: item[][], color: item) => {
  let point = 0; // 计算对手落子最佳点
  let point2 = 0; // 计算自身落子最佳点
  const bestLocation = [0, 0]; // 存储对手落子最佳位置
  const bestLocation2 = [0, 0]; // 存储自己最佳位置
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr[i].length; j++) {
      if (arr[i][j] === '') {
        let point3 = getWeighPoint(arr, i, j, color, weighPoint);
        if (point3 > point) {
          point = point3;
          bestLocation[0] = i;
          bestLocation[1] = j;
        }
        let point4 = getWeighPoint(arr, i, j, color === 1 ? 0 : 1, weighPoint);
        if (point4 > point2) {
          point2 = point4;
          bestLocation2[0] = i;
          bestLocation2[1] = j;
        }
      }
    }
  }
  // console.clear()
  console.log('point,point2', point, point2);
  console.log('bestLocation', bestLocation, bestLocation2);
  return point <= point2 ? bestLocation2 : bestLocation;
};

实现getWeightPoint方法

type weighType = {
  liveFour: number;
  liveThree: number;
  liveTwo: number;
  liveOne: number;
  rushFour: number;
  rushThree: number;
  rushTwo: number;
  rushone: number;
  liveFive: number;
};
export type item = 0 | 1 | '';

const weighPoint: weighType = {
  liveFive: 100000,
  liveFour: 10000, // 4活子 权值最大
  rushFour: 5000, // 冲4
  liveThree: 1000, // 3活子
  rushThree: 500, // 冲3
  liveTwo: 50,
  rushTwo: 2,
  liveOne: 1,
  rushone: 0,
};
// 计算棋盘每个点的分值
export function getWeighPoint(
  board: item[][],
  x: number,
  y: number,
  color: item,
  weigh: weighType,
): number {

  let point = 0;
  let b1 = board[x][y + 1];
  // 判断是否是活子
  if (b1 === color && y + 1 <= 14) {
    // 判断是否是冲4
    if (
      b1 === board[x][y + 1] &&
      b1 === board[x][y + 2] &&
      b1 === board[x][y + 3] &&
      b1 === board[x][y + 4]
    ) {
      point += weigh.rushFour;
    }
    // 判断是否是冲3
    if (b1 === board[x][y + 1] && b1 === board[x][y + 2]) {
      point += weigh.rushThree;
    }
  }

  // 判断横向
  let count = 1;
  for (let i = 1; i < 5; i++) {
    if (x - i >= 0 && board[x - i][y] === color) {
      count++;
    } else {
      break;
    }
  }
  for (let i = 1; i < 5; i++) {
    if (x + i < 15 && board[x + i][y] === color) {
      count++;
    } else {
      break;
    }
  }
  if (count >= 5) {
    point += weigh.liveFive;
    if (count === 5) {
      point += weigh.liveFour; // 冲4
    }
  } else if (count === 4) {
    point += weigh.rushFour;
  } else if (count === 3) {
    point += weigh.liveThree;
  } else if (count === 2) {
    point += weigh.rushThree;
  } else if (count === 1) {
    point += weigh.rushTwo;
  }
  // 判断竖向
  count = 1;
  for (let i = 1; i < 5; i++) {
    if (y - i >= 0 && board[x][y - i] === color) {
      count++;
    } else {
      break;
    }
  }
  for (let i = 1; i < 5; i++) {
    if (y + i < 15 && board[x][y + i] === color) {
      count++;
    } else {
      break; // 如果没有满足条件,则跳出循环
    }
  }
  if (count >= 5) {
    point += weigh.liveFive;

    if (count === 5) {
      point += weigh.liveFour; // 冲4
    }
  } else if (count === 4) {
    point += weigh.rushFour;
  } else if (count === 3) {
    point += weigh.liveThree;
  } else if (count === 2) {
    point += weigh.rushThree;
  } else if (count === 1) {
    point += weigh.rushTwo;
  }
  // 判断右斜向
  count = 1;
  for (let i = 1; i < 5; i++) {
    if (x - i >= 0 && y - i >= 0 && board[x - i][y - i] === color) {
      count++;
    } else {
      break;
    }
  }
  for (let i = 1; i < 5; i++) {
    if (x + i < 15 && y + i < 15 && board[x + i][y + i] === color) {
      count++;
    } else {
      break;
    }
  }
  if (count >= 5) {
    point += weigh.liveFive;

    if (count === 5) {
      point += weigh.liveFour; // 冲4
    }
  } else if (count === 4) {
    point += weigh.rushFour;
  } else if (count === 3) {
    point += weigh.liveThree;
  } else if (count === 2) {
    point += weigh.rushThree;
  } else if (count === 1) {
    point += weigh.rushTwo;
  }
  // 判断左斜向

  count = 1;
  for (let i = 1; i < 5; i++) {
    if (x - i >= 0 && y + i < 15 && board[x - i][y + i] === color) {
      count++;
    } else {
      break;
    }
  }
  for (let i = 1; i < 5; i++) {
    // 如果没有满足条件,则跳出循环
    if (x + i < 15 && y - i >= 0 && board[x + i][y - i] === color) {
      count++;
    } else {
      break;
    }
  }
  if (count >= 5) {
    point += weigh.liveFive;

    if (count === 5) {
      point += weigh.liveFour; // 冲4
    }
  } else if (count === 4) {
    point += weigh.rushFour;
  } else if (count === 3) {
    point += weigh.liveThree;
  } else if (count === 2) {
    point += weigh.rushThree;
  } else if (count === 1) {
    point += weigh.rushTwo;
  }

  return point;
}

得出落子的最高分后相当于机器人赋了一点五子棋的基本逻辑,可以下棋啦~

最后算胜负

算胜负就是比较简单暴力的过程,只要在落子后立即算


  const downChess = (x: number, y: number, type = 'user') => {

// other code

   for (let i = 0; i < chessListCopy.length; i++) {
      const chess = chessListCopy[i];
      for (let j = 0; j < chess.length; j++) {
        if ([0, 1].includes(chess[j])) {
          // x轴连子
          if (
            chess[j] === chess[j + 1] &&
            chess[j] === chess[j + 2] &&
            chess[j] === chess[j + 3] &&
            chess[j] === chess[j + 4]
          ) {
            showWin(chess[j]);
            return;
          }
          // 斜着连子
          else if (
            chessListCopy[i][j] === chessListCopy[i + 1][j + 1] &&
            chessListCopy[i][j] === chessListCopy[i + 2][j + 2] &&
            chessListCopy[i][j] === chessListCopy[i + 3][j + 3] &&
            chessListCopy[i][j] === chessListCopy[i + 4][j + 4]
          ) {
            showWin(chessListCopy[i][j]);
            return;
          }
          // y轴连子
          else if (
            i <= 10 &&
            chessListCopy[i][j] === chessListCopy[i + 1][j] &&
            chessListCopy[i][j] === chessListCopy[i + 2][j] &&
            chessListCopy[i][j] === chessListCopy[i + 3][j] &&
            chessListCopy[i][j] === chessListCopy[i + 4][j]
          ) {
            showWin(chessListCopy[i][j]);
            return;
          }
          // 斜着连子
          else if (
            i <= 10 &&
            chessListCopy[i][j] === chessListCopy[i + 1][j - 1] &&
            chessListCopy[i][j] === chessListCopy[i + 2][j - 2] &&
            chessListCopy[i][j] === chessListCopy[i + 3][j - 3] &&
            chessListCopy[i][j] === chessListCopy[i + 4][j - 4]
          ) {
            showWin(chessListCopy[i][j]);
            return;
          }
        }
      }
    }
    
    }

个人源码git地址:gitee.com/lbxw/script…

dev分支 src/pages/game/gobang

落子的分值算法还有很大优化空间