Представляем вам перевод статьи Josh Kuttler, опубликованной на blog.bitsrc.io. Узнайте, как создать приложение «Крестики-нолики», используя React и TypeScript.



Простая игра в крестики-нолики создана по модульному принципу и загружена на сайт Bit. Вы можете изменять компоненты моей игры и тестировать ее онлайн на Bit PlayGround при помощи NPM, Yarn или Bit. Для этого перейдите к моей коллекции компонентов.

Когда создаешь игры типа «Крестики-нолики» по модульному принципу, трудно найти причину, по которой компоненты UI могут снова когда-либо использоваться. Поэтому я сосредоточился в основном на игровых утилитах.

Для программирования я выбрал язык TypeScript — скомпилировал код при помощи TypeScript на сайте Bit. Затем воспользовался фреймворком Mocha для тестирования.

Чтобы установить компоненты из моего проекта, сначала настройте bit.dev в качестве реестра области (скопируйте и вставьте на своем устройстве). Это следует сделать только один раз! При дальнейшем использовании сайта Bit проводить повторную настройку не понадобится.

npm config set '@bit:registry' https://node.bit.dev

Затем установите компонент при помощи менеджеров пакетов Yarn или NPM:

npm i @bit/joshk.tic-tac-toe-game.game
yarn add @bit/joshk.tic-tac-toe-game.game

Компонент «игра»


Компонент «игра» является основным компонентом моего приложения — он создан при помощи одного компонента Board и двух компонентов Prime React.

Я использовал компоненты Button и Input-text для экрана настройки — протестировать и посмотреть их код можно здесь.



Установите компоненты PrimeReact в свой проект:

yarn add @bit/primefaces.primereact.inputtext
yarn add @bit/primefaces.primereact.button

После настройки параметров можно кликнуть на «Играть» и… играть!

Компонент Board


Компонент Board создает динамическую таблицу при помощи Props, устанавливает очередь для игроков и определяет победителя. Протестировать и посмотреть код можно здесь.



Компонент Square


Компонент Square — это обычная ячейка, которая получает значение с опциональным цветом и отправляет ивент компоненту Board при изменении значения. Протестировать и посмотреть код можно здесь.



Функция Empty cell


Функция Empty cell — это вспомогательная функция для функции Winner-calc, которая проверяет, есть ли пустые ячейки в таблице игры.

Bit позволяет увидеть документы компонента и результаты тестов:



Код функции
/**
 * @description
 * check if 2d array have an empty cell
 * @param {{Array.<string[]>}} matrix 2d array
 * @param {number} rowsNum number of rows
 * @param {number} colsNum number of columns
 * @returns {boolean} return true if empty cell was found, and false if not.
 * @example
 * import haveEmptyCell from '@bit/joshk.tic-tac-toe-game.utils.have-empty-cell';
 *
 * const matrix = [
 *          ['X', 'O', 'X'],
 *          ['O', 'X', 'O'],
 *          ['O', 'X', 'O']
 *      ];
 * const result = haveEmptyCell(matrix, 3, 3);
 *
 * export default result
 * @example
 * import haveEmptyCell from '@bit/joshk.tic-tac-toe-game.utils.have-empty-cell';
 *
 * const matrix = [
 *          ['X', 'O', 'X'],
 *          ['O', '', 'O'],
 *          ['O', 'X', 'O']
 *      ];
 * const result = haveEmptyCell(matrix, 3, 3);
 *
 * export default result
 * @example
 * import haveEmptyCell from '@bit/joshk.tic-tac-toe-game.utils.have-empty-cell';
 *
 * const matrix = [
 *          ['X', 'O', 'X'],
 *          ['O', , 'O'],
 *          ['O', 'X', 'O']
 *      ];
 * const result = haveEmptyCell(matrix, 3, 3);
 *
 * export default result
 * @example
 * import haveEmptyCell from '@bit/joshk.tic-tac-toe-game.utils.have-empty-cell';
 *
 * const matrix = [
 *          ['X', 'O', 'X'],
 *          ['O', null, 'O'],
 *          ['O', 'X', 'O']
 *      ];
 * const result = haveEmptyCell(matrix, 3, 3);
 *
 * export default result
 */

function haveEmptyCell(matrix: Array<Array<string>>, rowsNum: number, colsNum: number): boolean {
    let empty: boolean = false;
    for (let x = 0; x < rowsNum; x++) {
        for (let y = 0; y < colsNum; y++) {
            const element: any = matrix[x][y];
            if (!element) {
                empty = true;
                break;
            }
        }
        if (empty)
            break;
    }
    return empty;
}

export default haveEmptyCell


Функция Winner calculation


Winner calculation — это функция, которая вычисляет победителя по горизонтальной, вертикальной и диагональной плоскостям.

Bit позволяет увидеть документы компонента и результаты тестов:



Код функции
/**
 * @description
 * check winner horizontal, vertical and diagonal
 * @param {Array.<string[]>} matrix 2d array with X and O 
 * @param {number} rowsNum number of rows
 * @param {number} colsNum number of columns
 * @param {number} numToWin the number of matching to win
 * @param {number} lastRow the row number of the square player click
 * @param {number} lastCol the column number of the square player click
 * @returns {string} return the winner, X or O or '' if no one win. 
 * @example
 * import winnerCalc from '@bit/joshk.tic-tac-toe-game.utils.winner-calc';
 *
 * const matrix = [
 *   ['O', 'O', 'X'],
 *   ['O', 'X', ''],
 *   ['X', '', '']
 * ];
 * const result = winnerCalc(matrix, 3, 3, 3, 0, 2);
 *
 * export default result
 */

import haveEmptyCell from '../HaveEmptyCell'

function winnerCalc(matrix: Array<Array<string>>, rowsNum: number, colsNum: number, numToWin: number, lastRow: number, lastCol: number): string {
    let winner: string = '';
    let match: number = 0;
    const lastValue: string = matrix[lastRow][lastCol];

    //check Horizontal
    for (let c = 0; c < colsNum; c++) {
        let currentValue = matrix[lastRow][c];
        if (currentValue === lastValue)
            match++;
        else match = 0;
        if (match === numToWin) {
            winner = lastValue;
            break;
        }
    }
    if (winner !== '')
        return winner;

    match = 0;
    //check Vertical
    for (let r = 0; r < rowsNum; r++) {
        let currentValue = matrix[r][lastCol];
        if (currentValue === lastValue)
            match++;
        else match = 0;
        if (match === numToWin) {
            winner = lastValue;
            break;
        }
    }
    if (winner !== '')
        return winner;

    //check diagonal top-left to bottom-right - include middle
    match = 0;
    for (let r = 0; r <= rowsNum - numToWin; r++)
    {
        let rowPosition = r;
        for (let column = 0; column < colsNum && rowPosition < rowsNum; column++)
        {
            const currentValue = matrix[rowPosition][column];
            if (currentValue === lastValue)
                match++;
            else match = 0;
            if (match === numToWin)
            {
                winner = lastValue;
                break;
            }
            rowPosition++;
        }
        if (winner !== '') break;
    }
    if (winner !== '')
        return winner;

    //check diagonal top-left to bottom-right - after middle
    match = 0;
    for (let c = 1; c <= colsNum - numToWin; c++)
    {
        let columnPosition = c;
        for (let row = 0; row < rowsNum && columnPosition < colsNum; row++)
        {
            let currentValue = matrix[row][columnPosition];
            if (currentValue === lastValue)
                match++;
            else match = 0;
            if (match === numToWin)
            {
                winner = lastValue;
                break;
            }
            columnPosition++;
        }
        if (winner !== '') break;
    }
    if (winner !== '')
        return winner;

    //check diagonal bottom-left to top-right - include middle
    match = 0;
    for (let r = rowsNum - 1; r >= rowsNum - numToWin - 1; r--)
    {
        let rowPosition = r;
        for (let column = 0; column < colsNum && rowPosition < rowsNum && rowPosition >= 0; column++)
        {
            let currentValue = matrix[rowPosition][column];
            if (currentValue === lastValue)
                match++;
            else match = 0;
            if (match === numToWin)
            {
                winner = lastValue;
                break;
            }
            rowPosition--;
        }
        if (winner !== '') break;
    }
    if (winner !== '')
        return winner;

    //check diagonal bottom-left to top-right - after middle
    match = 0;
    for (let c = 1; c < colsNum; c++)
    {
        let columnPosition = c;
        for (let row = rowsNum - 1; row < rowsNum && row >= 0 && columnPosition < colsNum && columnPosition >= 1; row--)
        {
            console.log(`[${row}][${columnPosition}]`);
            let currentValue = matrix[row][columnPosition];
            if (currentValue === lastValue)
                match++;
            else match = 0;
            if (match === numToWin)
            {
                winner = lastValue;
                break;
            }
            columnPosition++;
        }
        if (winner !== '') break;
    }
    if (winner !== '')
        return winner;

    if(haveEmptyCell(matrix, rowsNum, colsNum) === false) {
        winner = '-1';
    }

    return winner;
}

export default winnerCalc


Проект доступен в моей коллекции на Bit и в моём репозитории GitHub.

Не стесняйтесь комментировать эту статью и подписывайтесь на мой Twitter.

Комментарии (3)


  1. token
    13.06.2019 17:48

    Отлично, а у вас нет планов чего нибудь ещё проще? К примеру как вывести строку в браузер на реакте, тайпскрипте, моче и сборщик обязательно чтоб был описан.


    1. namikiri
      13.06.2019 19:07
      +1

      Получившийся продукт™ обязательно обеспечить серверной частью на пихтоне и фласке, и, конечно же, засунуть всё это в доскер, доскер в кубернетес, кубернетес в облако. Вот это современно, по-нашему.


  1. mafia8
    13.06.2019 22:58

    Вот поэтому надо проверять, на что похоже название вашего продукта на других языках.