Checkers on React - Part 2 - Creating the Gameboard

Written by rzhelieznov | Published 2022/08/22
Tech Story Tags: react | typescript | game | javascript | javascript-development | software-development | web-development | javascript-tutorial

TLDRIn this tutorial, we will start with creating the game board. 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. Every cell will have a width and height equal to 64 px and aligned to the center for figures. This prop will tell us which type of cell we have - light or dark. We will use `display:`flex and `wrap-wrap: wrap wrap to wrap.via the TL;DR App

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

Cell

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;
}

Board

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:

CellModel and BoardModel

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


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