用两百行不到的代码实现五子棋

245 阅读3分钟

项目地址

前言

这个是我一个面试的笔试题,感觉挺有意思的,所以写篇文章分享一下

题目介绍

使用HTML CSS制作一个五子棋游戏(使用react或者VUE框架更好),要求:

  1. 完成棋盘的绘制(20*20)
  2. 白子先行然后黑子后行 点击交汇处落子 黑白交替出现
  3. 当五子相连时(包含斜边相连) 提示获胜信息

项目结构设计

这里我的设计方式是分为逻辑层和渲染层。这样渲染层可以在扩展时单独做不同的更改

  • 逻辑层就是给出一个输入命令的结构和一个给视图渲染的二维数据。
  • 渲染层就是可以获取用户事件并且执行命令,同时可以渲染视图

逻辑层设计

设计要求

  • 命令的黑白子不停交换
  • 在每次输入命令时,检查是否会连成五子

逻辑层实现

逻辑层的主要职责就是维护一个二维数组,这个二维数组可以映射棋盘。我设置的二维数组是,0表示该点没有子,1表示该点有白子,2表示该点有黑子。检查的方式就是以这个最后下棋的点为准,往一个方向和它的反方向进行计数,子数超过五,则认为最后下棋方胜利。

  • 这个是检查是否胜利的代码
    type Pos = [number, number];
    //米字方向枚举
    const directions: Pos[] = [
      [-1, 0],
      [-1, 1],
      [0, 1],
      [1, 1],
    ];
    function checkIsWin() {
        //state.lastExeGrid 最后一次下棋的命令,
        if (!state.lastExeGrid) return;
        const [x, y] = state.lastExeGrid;
        const player = state.grid[x][y] as Player;
        for (const [ox, oy] of directions) {
          let len = 1;
          for (let i = 1; i <= 4; i++) {
            const x1 = x + i * ox;
            const y1 = y + i * oy;
            if (x1 >= state.grid.length || x1 < 0) break;
            if (y1 >= state.grid[0].length || y1 < 0) break;
            if (state.grid[x1][y1] == player) {
              len++;
            } else {
              break;
            }
          }

          for (let i = 1; i <= 4; i++) {
            const x1 = x - i * ox;
            const y1 = y - i * oy;
            if (x1 >= state.grid.length || x1 < 0) break;
            if (y1 >= state.grid[0].length || y1 < 0) break;
            if (state.grid[x1][y1] == player) {
              len++;
            } else {
              break;
            }
          }
          if (len >= 5) {
            state.winer = player;
            break;
          }
        }
      }

渲染层实现

  • 渲染层实现方式其实有两种方案,当时我打算用canvas实现。实现方式就是用两个canvas,第一个canvas充当背景图,第二个canvas就是用来渲染棋子。但是我太久没有canvas了,而且我觉得用dom的方式比较简单。感兴趣的可以去实现
  • dom节点的写法,也是分两层,第一层是放棋子,第二层是放棋盘。我这直接放代码了

背景图渲染

        //css样式设置
        .display-heckerborder {
          display: flex;
          width: 420px;
          flex-wrap: wrap;
          background-color: rgb(234, 188, 121);
        }
        .display-cell {
          box-sizing: border-box;
          width: 20px;
          height: 20px;
          border: solid black 1px;

          display: flex;
          align-items: center;
          justify-content: center;

          overflow: hidden;
        }
        //dom设置
        <div className='display-heckerborder'>
          {
            new Array(21 * 21).fill(null).map(() => {
              return <div className='display-cell'></div>
            })
          }
        </div>

放置棋盘的设置

这个我先写了个格子组件,这个格子组件就是根据接受的参数来决定是否展示棋子和棋子的颜色。然后在把格子放到容器里生成20 * 20的格子。同时接受点击事件处理。

//css样式
.cell {
  width: 20px;
  height: 20px;

  display: flex;
  align-items: center;
  justify-content: center;
}
.circle {
  height: 15px;
  width: 15px;
  border-radius: 50%;
}
//函数组件
function Cell(props: CellProps) {
  const { player, clickHandle = () => { } } = props;
  //是否显示棋子
  let circleStyle: React.CSSProperties = {};
  if (!player) circleStyle.visibility = 'hidden';
  else circleStyle = { backgroundColor: player == Player.white ? 'white' : 'black' }
  return (<div className='cell' onClick={clickHandle}>
    <div className='circle' style={circleStyle} ></div>
  </div>)
}