Previous parts: Part 1, Part 2
I’m continuing my pet project, but before starting the next part I want to make some updates. Right now all imports in the project look something like this: import {Board} from ‘../../components/Board‘
or import {BoardModel} from ‘../models/BoardModel‘
. But I prefer to use absolute imports: import {Board} from ‘components/Board‘
, import {BoardModel} from ‘models/BoardModel‘
. To achieve this we just need to add some lines to our tsconfig.json
file and create paths:
"paths": {
"components/*": ["./components/*"],
"models/*": ["./models/*"],
"utils/*": ["./utils/*"],
"images/*": ["./images/*"]
}
Now we can import components, models, utils and images in any place of the application just using absolute paths.
Let’s continue with development. As we already have a game board, we can try to create a figure and place it on the cell. To do this, firstly we need to create a base model class that will describe the figure. (I’ve spent some time choosing names for figures. In English-speaking countries and in non-English there are different common naming, so I decided to use Piece
for general figures and Dame
for Piece which became crowned). Figure names described in FigureNames
enum:
// src/models/FigureNames.ts
export enum FigureNames {
Piece = 'Piece',
Dame = 'Dame',
}
// src/models/FigureModel.ts
import { Labels } from 'models/Labels';
import { CellModel } from 'models/CellModel';
import pieceImgLight from 'images/light.png';
import pieceImgDark from 'images/brown.png';
import { FigureNames } from 'models/FigureNames';
class FigureModel {
label: Labels;
imageSrc: string;
isDame: boolean;
cell: CellModel;
name: FigureNames;
constructor(label: Labels, cell: CellModel) {
this.label = label;
this.cell = cell;
this.cell.figure = this;
this.isDame = false;
this.name = FigureNames.Piece;
this.imageSrc = label === Labels.Light ? pieceImgLight : pieceImgDark;
}
}
export { FigureModel };
FigureModel
has next properties:
label
- our labelimageSrc
- this will be the source to figure imageisDame
- bool property for crowned and uncrowned piecescell
- this is the CellModel
on which figure is stayname
- figure name
In the constructor, we initialized these properties, and use the name FigureNames.Piece
by default for all pieces as they are uncrowned from the beginning. imageSrc
depends on the piece label. I’ve tried to find some good png
or svg
icons for pieces, but this task is a little bit tricky, I’m still searching, so now I’ll use some temporary images. Maybe someone has any ideas where to find good and free svg
icons for uncrowned and crowned pieces
We will use this class for all our pieces. And we will create a new figure in BoardModel
. So let’s add new methods to this class. First is a getCell
. It will return the cell from its coordinates. (As we remember our cells have x and y cords. On a game board, we have 8 rows and 8 cells in a row. For example x: 3, y: 1 will mean row number 2 and cell number 4 in this row). And method addFigure
which will create the new figure.
// src/models/BoardModel.ts
getCell(x: number, y: number): CellModel {
return this.cells[y][x];
}
addFigure(label: Labels, x: number, y: number) {
new FigureModel(label, this.getCell(x, y));
}
addFigure
takes label and cords as arguments and creates a new Figure instance.
Full BoardModel
class:
import { CellModel } from './CellModel';
import { Labels } from './Labels';
import { FigureModel } from 'models/FigureModel';
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)); // black
} else {
row.push(new CellModel(i, j, Labels.Light, this)); // white
}
}
this.cells.push(row);
}
}
getCell(x: number, y: number): CellModel {
return this.cells[y][x];
}
addFigure(label: Labels, x: number, y: number) {
new FigureModel(label, this.getCell(x, y));
}
}
export { BoardModel };
We also need to update CellModel
and add figure property to it, so our cell will know about the figure that is staying on it:
// src/models/CellModel.ts
import { Labels } from './Labels';
import { BoardModel } from './BoardModel';
import { FigureModel } from 'models/FigureModel';
class CellModel {
readonly x: number;
readonly y: number;
readonly label: Labels;
figure: FigureModel | null; // our figure
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)}`;
this.figure = null; // null by default
}
}
export { CellModel };
Cell components also need to be updated, to have the ability to render the image based on props:
// src/components/Cell/Cell.tsx
import React, { ReactElement } from 'react';
import './Cell.css';
import { mergeClasses } from 'utils/utils';
import { CellModel } from 'models/CellModel';
type CellProps = {
cell: CellModel;
};
export const Cell = ({ cell }: CellProps): ReactElement => {
const { figure, label } = cell;
return (
<div className={mergeClasses('cell', label)}>
{figure?.imageSrc && <img className="icon" src={figure.imageSrc} alt={figure.name} />}
</div>
);
};
Component get cell
prop which is a CellModel
. We just destruct figure
from it and check if figure.imageSrc
exists, then we render the image - {figure?.imageSrc && <img className="icon" src={figure.imageSrc} alt={figure.name} />}
In component styles we need to add new class .icon:
// src/components/Cell/Cell.css
.icon {
width: 64px;
height: 64px;
}
Everything is ready and let’s try to create some random figures. In App.tsx
in the restart function let’s call some addFigures
:
// src/App.tsx
const restart = () => {
const newBoard = new BoardModel();
newBoard.createCells();
newBoard.addFigure(Labels.Dark, 1, 2);
newBoard.addFigure(Labels.Dark, 3, 4);
newBoard.addFigure(Labels.Light, 5, 6);
newBoard.addFigure(Labels.Light, 7, 2);
setBoard(newBoard);
};
And we can see that 4 figures are added to our game board:
In the next parts, we will continue our journey.