In the previous part, we initialized a new project and made a base configuration. In this tutorial, we will start with creating the game board.
The first thing which I want to add is some styles to our App.tsx
component. We need to center our app and we will use flex
for this:
/* src/App.css */
.app {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
Then I’ll create 2 folders in src
: components
- for React components, and models
- for components models
The game board will consist of many cells. In the standard game we have a board with 8 rows and 8 cells in every row, so in general 64 cells. As we can see Cell
is our base component. I’ll use the next structure for all React components:
component folder
index.ts - to reexport component
componentName.tsx - React component
componentName.css - component styles
// components/Cell/index.ts
export { Cell } from './Cell';
Here we just reexport our Cell
from Cell.tsx
// components/Cell/Cell.tsx
import React, { ReactElement } from 'react';
import './Cell.css';
import { Labels } from '../../models/Labels';
import { mergeClasses } from '../../utils/utils';
type CellProps = {
label: Labels;
};
export const Cell = ({ label }: CellProps): ReactElement => {
return <div className={mergeClasses('cell', label)}></div>;
};
This is Cell
component. It’s a simple component, which will receive label
as props. This prop will tell us which type of cell we have - light or dark. In models
folder let’s create file Labels.ts
that described our labels:
// models/Labels.ts
export enum Labels {
Light = 'light',
Dark = 'dark',
}
Also, let’s create folder utils
where we will put our helpers. The first function will be mergeClasses
It will take strings as arguments and join them in one line:
// utils/utils.ts
const mergeClasses = (...rest: string[]): string => {
return rest.join(' ');
};
In our cell component, we will have such classes as a result of mergeClasses
function - ‘cell dark’ or ‘cell light‘ based on Cell
props
And finally styles for Cell. Every cell will have a width and height equal to 64 px and aligned to the center for figures. And additional classes for cell color: dark or light
// components/Cell/Cell.css
.cell {
display: flex;
width: 64px;
height: 64px;
justify-content: center;
align-items: center;
}
.dark {
background-color: #000;
}
.light {
background-color: #e3b778;
}
The cell component is already finished, now we can create Board
component. It will consist of 64 Cells for the standard game. Board width will be equal to 64 px (cell width) multiple 8. We will use display: flex
and flex-wrap: wrap
styles to move other cells to the next row:
// components/Board/Board.css
.board {
display: flex;
flex-wrap: wrap;
width: calc(64px * 8);
height: calc(64px * 8);
}
Our board is a simple component of class board
. We also can put several Cells
into it to see how it will look:
// components/Board/Board.tsx
import React, { ReactElement } from 'react';
import './Board.css';
import { Cell } from '../Cell';
import { Labels } from '../../models/Labels';
export const Board = (): ReactElement => {
return (
<div className="board">
<Cell label={Labels.Light} />
<Cell label={Labels.Dark} />
<Cell label={Labels.Light} />
<Cell label={Labels.Dark} />
</div>
);
};
We have 4 cells, and they look good:
To create a full board with 64 cells we need to create some logic. CellModel
will represent logic for Cell
component. It will be a class with some properties, like x and y coordinates, label, and key for React:
// models/CellModel.ts
import { Labels } from './Labels';
import BoardModel from './BoardModel';
export default class CellModel {
readonly x: number;
readonly y: number;
readonly label: Labels;
board: BoardModel;
available: boolean;
key: string;
constructor(x: number, y: number, label: Labels, board: BoardModel) {
this.x = x; // x coord
this.y = y; // y coord
this.label = label;
this.board = board;
this.available = false; // is it free for figure
this.key = `${String(x)}${String(y)}`;
}
}
Now when we have CellModel
, we can use it to create BoardModel
. This class will create a full board with 64 cells:
// models/BoardModel.ts
import CellModel from './CellModel';
import { Labels } from './Labels';
export default class BoardModel {
cells: CellModel[][] = [];
cellsInRow = 8;
createCells() {
for (let i = 0; i < this.cellsInRow; i += 1) {
const row: CellModel[] = [];
for (let j = 0; j < this.cellsInRow; j += 1) {
if ((i + j) % 2 !== 0) {
row.push(new CellModel(i, j, Labels.Dark, this)); // dark
} else {
row.push(new CellModel(i, j, Labels.Light, this)); // light
}
}
this.cells.push(row);
}
}
}
We have a constant cellsInRow
= 8 . It tells us that the row will consist of 8 cells. Then we create createCells
function. This function has 2 cycles to create an array of arrays, and these will be cells for our board. We use %
to understand which cell is light or dark.
Now it’s time to create a new instance of BoardModel
and it will be in App.tsx
// src/App.tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import { Board } from './components/Board';
import BoardModel from './models/BoardModel';
function App() {
const [board, setBoard] = useState<BoardModel>(new BoardModel());
const restart = () => {
const newBoard = new BoardModel();
newBoard.createCells();
setBoard(newBoard);
};
useEffect(() => {
restart();
}, []);
return (
<div className="app">
<Board board={board} onSetBoard={setBoard} />
</div>
);
}
export default App;
Here we use useState
hook which will keep BoardModel
. restart
function just create a new BoardModel
instance and save it to board
. Then we use useEffect
hook to run it when the App component is mounted.
And now we need to update a little bit the Board
component and render Cells
:
// components/Board/Board.tsx
import React, { Fragment, ReactElement } from 'react';
import './Board.css';
import { Cell } from '../Cell';
import BoardModel from '../../models/BoardModel';
type BoardProps = {
board: BoardModel;
onSetBoard: (board: BoardModel) => void;
};
export const Board = ({ board, onSetBoard }: BoardProps): ReactElement => {
return (
<div className="board">
{board.cells.map((row, index) => (
<Fragment key={index}>
{row.map((cell, index) => (
<Cell label={cell.label} key={cell.key} />
))}
</Fragment>
))}
</div>
);
};
Now it will take 2 props: board, which is a BoardModel
, and setBoard
function. BoardModel
has cells field which is an array of arrays, so we iterate 2 times on it to render all cells. When we are done with this, we will see a full game board with 64 cells:
That’s all for this part. Link to the repo