visit
Previous parts: Part 1, Part 2, Part 3
Lets update method addFigure
to addFigures
:
// src/models/BoardModel
addFigures() {
this.cells.forEach((row, rowIndex) => {
row.forEach((cell, cellIndex) => {
if (rowIndex <= 2 && cell.label === Labels.Dark) {
new FigureModel(Labels.Dark, this.getCell(cellIndex, rowIndex)); // add dark pieces to first 3 rows
} else if (rowIndex >= this.cells.length - 3 && cell.label === Labels.Dark) {
new FigureModel(Labels.Light, this.getCell(cellIndex, rowIndex)); // add light pieces to last 3 rows
}
});
});
}
We iterate through our cells (which is an array of arrays) and check the row index and cell label. We need dark cells and the first 3 rows for one side and the last 3 rows for another side of the board. And than in App.tsx
we use this function.
The game board is ready and we can start to create game logic. But before this, I want to update a little bit of the game board design and add labels. In chess and checkers, rows are labelled from 1 to 8 and columns from A to H. I want to add such labels, this will help us with more easiest visualization.
I decided to use the next approach - in Board.tsx
, when we render cells, we know rows and cell indexes. Let’s pass them to the Cell component as props:
// src/components/Board/Board.tsx
{board.cells.map((row, rowIndex) => (
<Fragment key={rowIndex}>
{row.map((cell, cellIndex) => (
<Cell
cell={cell}
key={cell.key}
rowIndex={rowIndex}
cellIndex={cellIndex}
/>
))}
</Fragment>
))}
// src/components/Cell/Cell.tsx
{(rowIndex === 0 || rowIndex === 7) && (
<div className={mergeClasses('board-label', rowIndex === 0 ? 'top' : 'bottom')}>
{Letters[cellIndex]}
</div>
)}
{(cellIndex === 0 || cellIndex === 7) && (
<div className={mergeClasses('board-label', cellIndex === 0 ? 'left' : 'right')}>
{8 - rowIndex}
</div>
)}
// src/models/Letters.ts
export enum Letters {
A,
B,
C,
D,
E,
F,
G,
H,
}
// src/components/Cell/Cell.css
.board-label {
position: absolute;
}
.top {
transform: translateY(-50px);
}
.bottom {
transform: translateY(50px);
}
.left {
transform: translateX(-50px);
}
.right {
transform: translateX(50px);
}
So, it seems that the base design is complete, and we can start to create game logic. I want to start with figure selection.
In Board.tsx
we will create the component state. It will be used to save selected Cells. And a handler will check if cell have figure, than it will be saved to state:
// src/components/Board/Board.tsx
const [selected, setSelected] = useState<CellModel>();
const handleFigureClick = (cell: CellModel) => {
if (cell.figure) {
setSelected(cell);
}
};
Than we will pass handleFigureClick
and selected
to cells.
Full Board.tsx
// src/components/Board.tsx
export const Board = ({ board }: BoardProps): ReactElement => {
const [selected, setSelected] = useState<CellModel>();
const handleFigureClick = (cell: CellModel) => {
if (cell.figure) {
setSelected(cell);
}
};
return (
<div className="board">
{board.cells.map((row, rowIndex) => (
<Fragment key={rowIndex}>
{row.map((cell, cellIndex) => (
<Cell
cell={cell}
key={cell.key}
rowIndex={rowIndex}
cellIndex={cellIndex}
selected={selected?.x === cell.x && selected.y === cell.y} // check if selected cell coords equal to rendered cell
onFigureClick={handleFigureClick}
/>
))}
</Fragment>
))}
</div>
);
};
Cell component will take these props and set handler to img
element. Also, we will check if Cell
is selected, then add selected
class to the element.
Full Cell.tsx
component:
// src/components/Cell/Cell.tsx
type CellProps = {
cell: CellModel;
rowIndex: number;
cellIndex: number;
selected: boolean;
onFigureClick: (cell: CellModel) => void;
};
export const Cell = ({
cell,
rowIndex,
cellIndex,
selected,
onFigureClick,
}: CellProps): ReactElement => {
const { figure, label } = cell;
const handleFigureClick = () => onFigureClick(cell);
return (
<div className={mergeClasses('cell', label, selected ? 'selected' : '')}>
{figure?.imageSrc && (
<img
className="icon"
src={figure.imageSrc}
alt={figure.name}
onClick={handleFigureClick}
/>
)}
{(rowIndex === 0 || rowIndex === 7) && (
<div className={mergeClasses('board-label', rowIndex === 0 ? 'top' : 'bottom')}>
{Letters[cellIndex]}
</div>
)}
{(cellIndex === 0 || cellIndex === 7) && (
<div className={mergeClasses('board-label', cellIndex === 0 ? 'left' : 'right')}>
{8 - rowIndex}
</div>
)}
</div>
);
};
In Cell styles I want to add some animation to animate selected figure and add cursor: pointer
to icon class:
// src/components/Cell/Cell.css
.icon {
width: 64px;
height: 64px;
cursor: pointer;
}
.selected .icon {
animation: scaling 0.5s infinite alternate;
}
@keyframes scaling {
0% {
transform: scale(1);
}
100% {
transform: scale(1.3);
}
}