如何写一个国际象棋的游戏(第三部分)【React Context在更换主题中的使用】

746 阅读3分钟

“我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛

代码仓库,走过路过点一个 Star ✨

画一个棋盘

棋盘的组成

首先认识一下国际象棋的棋盘,棋盘由 8 * 8 黑白交替的格子组成,坐标轴是在白方左下角从左往右是A-H,从下往上是1-8。

220px-Chess_(PSF).jpg

images.png

用代码实现

最初在构思棋盘的时候,我准备画一个 svg 的棋盘出来,使用了一个二维数组去描述棋盘,用0表示白色的格子,1表示黑色的格子。

export const grid = [
    [0, 1, 0, 1, 0, 1, 0, 1],
    [1, 0, 1, 0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0, 1, 0, 1],
    [1, 0, 1, 0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0, 1, 0, 1],
    [1, 0, 1, 0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0, 1, 0, 1],
    [1, 0, 1, 0, 1, 0, 1, 0]
];

那么用代码写出来就会长这样

<svg width={8 * gridSize} height={8 * gridSize} stroke={borderColor}>
  <g
     width={gridSize}
     height={gridSize}
     key={`${rowIndex}-${colIndex}`}
  >
    <rect
      x={colIndex * gridSize}
      y={rowIndex * gridSize}
      width={gridSize}
      height={gridSize}
      style={{ fill: col === 1 ? blackGrid : whiteGrid }}
    />
  </g>
  
  ... 64 个格子
</svg>

在 svg 里面换行其实很简单,只需要控制好x,y的值就好了,第一行第一个格子坐标是 [0, 0],那么第二个坐标就是 [0, 50],第二行第一个坐标是 [50, 0],第二个是 [50, 50],以此类推,不难得出 [colIndex * gridSize, rowIndex * gridSize] 这个坐标。

最终一个 svg 棋盘会长成这样

<svg width={8 * gridSize} height={8 * gridSize} stroke={borderColor}>
  {grid.map((row, rowIndex) => {
    return row.map((col, colIndex) => {
      return <g
        width={gridSize}
        height={gridSize}
        key={`${rowIndex}-${colIndex}`}
      >
        <rect
          x={colIndex * gridSize}
          y={rowIndex * gridSize}
          width={gridSize}
          height={gridSize}
          style={{ fill: col === 1 ? blackGrid : whiteGrid }}
        />
      </g>
    })
  })}
</svg>

第二个方案

因为这个最终还是要做一个可玩的版本,所以还是有必要用 div 写一个出来的,最简单的方式就是用 flex,通过 flex-flow: row-wrap;这个属性就能很容易实现出来。

.board {
    display: flex;
    flex-flow: row wrap;
}
.cell {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    flex-grow: 1;
    flex-shrink: 0;
    width: 12.5%;
    height: 12.5%;
    cursor: pointer;
}

同样通过两个循环把格子画出来,剩下来的交给布局去实现就好了,是不需要计算坐标的。

<div 
  className='board'
  style={{
    width: 8 * gridSize,
    height: 8 * gridSize,
    border: `1px solid ${borderColor}`,
  }}>
  {chessboard.board().map((row: object[], rowIndex: number) => {
    return row.map((item: any, colIndex: number) => {
      return <Cell
               key={getGridAxis({ row: rowIndex, col: colIndex })}
               gridAxis={getGridAxis({ row: rowIndex, col: colIndex })}
               item={item}
               rowIndex={rowIndex}
               colIndex={colIndex}
             />
    })
  })}
</div>

更换棋盘的主题

在上面代码中,我并没有直接把颜色这些 hardcode 进 css 文件,因为考虑到更换主题,我准备了两个主题,默认主题,和木质主题。

export const defaultTheme = {
    borderColor: 'lightgrey',
    whiteGrid: '#fff',
    blackGrid: 'lightgrey',
    whitePieceColor: '#2b2b2b',
    blackPieceColor: '#2b2b2b',
    gridSize: 50,
    fontSize: 40,
}
​
export const woodenTheme = {
    borderColor: '#AD9278',
    whiteGrid: '#D1BF9D',
    blackGrid: '#AD9278',
    whitePieceColor: '#2b2b2b',
    blackPieceColor: '#2b2b2b',
    gridSize: 50,
    fontSize: 40,
}

通过 React Context API 可以很方便的实现这个功能,首先我们创建一个 Context 对象。

interface ThemeProps {
    borderColor: string;
    whiteGrid: string;
    blackGrid: string;
    whitePieceColor: string;
    blackPieceColor: string;
    gridSize: number;
    fontSize: number;
}
interface ThemeContextProps {
    theme: ThemeProps;
    selectTheme: (t: string) => void;
}
export const ThemeContext = createContext<ThemeContextProps>({} as ThemeContextProps);

接着我们需要包装一个 Provider 包在 App 的最外层

const ThemeContenxtProvider: FC = ({ children }) => {
    const [theme, setTheme] = useState<ThemeProps>(defaultTheme);
    return <ThemeContext.Provider
        value={{
            theme,
            selectTheme: (t: string) => {
                switch (t) {
                    case 'wooden':
                        setTheme(woodenTheme);
                        break;
                    default:
                        setTheme(defaultTheme);
                        break;
                }
            },
        }}
    >
        {children}
    </ThemeContext.Provider>
};
export default ThemeContenxtProvider;
function App() {
    return (
      <ThemeContenxtProvider>
        <div className="container-md">
          <div className="row">
            <div className="sm-4 col main-container">
              <h3 className=''>Simple Chess Board</h3>
              <p className=''>Made by banana with ❤️</p>
              <ChessBoard />
              <ThemeSelect />
            </div>
          </div>
        </div>
      </ThemeContenxtProvider>
    );
}
export default App;

default-theme.png

wooden-theme.png

这里把 ThemeContenxtProvider多封装了一层是为了方便可以在这个 Provider 中直接使用其他 Hooks,可以做一些数据初始化的操作,而非直接把 Ctx.Provider暴露出去,这样需要在外层组件里面去维护这个数据初始化的逻辑。

详细内容可以移步React Hooks文档