“我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛”
代码仓库,走过路过点一个 Star ✨
画一个棋盘
棋盘的组成
首先认识一下国际象棋的棋盘,棋盘由 8 * 8 黑白交替的格子组成,坐标轴是在白方左下角从左往右是A-H,从下往上是1-8。
用代码实现
最初在构思棋盘的时候,我准备画一个 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;
这里把 ThemeContenxtProvider
多封装了一层是为了方便可以在这个 Provider 中直接使用其他 Hooks,可以做一些数据初始化的操作,而非直接把 Ctx.Provider
暴露出去,这样需要在外层组件里面去维护这个数据初始化的逻辑。
详细内容可以移步React Hooks文档