Checkers on React - Part 3 - Figure

Written by rzhelieznov | Published 2022/08/25
Tech Story Tags: react | typescript | game-development | checkers-game | react-native | react-native-development | react-tutorial | create-react-native-app

TLDRCheckers-on-react-project is 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’s.’` But I prefer to use absolute imports: 'import {boardModel} from 'models/BoardModel' Instead we can import components, models, utils and images in any place of the application just using absolute paths.via the TL;DR App

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 label
  • imageSrc - this will be the source to figure image
  • isDame - bool property for crowned and uncrowned pieces
  • cell - this is the CellModel on which figure is stay
  • name - 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.

Link to the repo


Written by rzhelieznov | Javascript and React fan. Also love reading, traveling, pc gaming :)
Published by HackerNoon on 2022/08/25