Tic Tac Toe, часть 2

Автор: admin от 13-07-2019, 22:05, посмотрело: 16

Продолжение статьи Tic Tac Toe, часть 1, в которой мы начали разработку этой игры на Svelte. В этой части мы доделаем игру до конца. Добавим команды Undo/Redo, произвольный доступ к любому шагу игры, попеременные ходы с противником, вывод статуса игры, определение победителя.

Код на REPL



На этом этапе в приложение были добавлены команды Undo/Redo. В хранилище history добавлены методы push и redo.



undo: () => update(h => { h.undo(); return h; }),
redo: () => update(h => { h.redo(); return h; }),


В класс History добавлены методы push, redo, canUndo, canRedo.



canUndo() {
    return this.current > 0;
}

canRedo() {
    return this.current < this.history.length - 1;
}

undo() {
    if (this.canUndo())
        this.current--;
}

redo() {
    if (this.canRedo())
        this.current++;
}


В метод push класса History добавлено удаление всех состояний от текущего до последнего. Если мы несколько раз выполним команду Undo и выполним клик в игровом поле, то все состояния справа от текущего до последнего будут удалены из хранилища и будет добавлено новое состояние.



push(state) {
    // remove all redo states
    if (this.canRedo()) 
        this.history.splice(this.current + 1);

    // add a new state
    this.current++;
    this.history.push(state);
}


В компоненте App добавлены кнопки Undo и Redo. Если выполнение команд не возможно, то они деактивируются.



<div>
    {#if $history.canUndo()}
    <button on:click={history.undo}>Undo</button>
    {:else}
    <button disabled>Undo</button>
    {/if}
    {#if $history.canRedo()}
    <button on:click={history.redo}>Redo</button>
    {:else}
    <button disabled>Redo</button>
    {/if}
</div>


Смена хода


Код на REPL



Выполнено попеременное появление крестика или нолика после клика мышкой.



Метод clickCell() убран их хранилища history, весь код метода перенесен в обработчик handleClick() компонента Board.



function handleClick(event) {
    let x = Math.trunc((event.offsetX + 0.5) / cellWidth);
    let y = Math.trunc((event.offsetY + 0.5) / cellHeight);
    let i = y * width + x;

    const state = $history.currentState();
    const squares = state.squares.slice();
    squares[i] = state.xIsNext ? 'X' : 'O';
    let newState = {
        squares: squares,
        xIsNext: !state.xIsNext,
    };
    history.push(newState);
}


Таким образом была устранена ранее допущенная ошибка, в хранилище была зависимость от логики этой конкретной игры. Сейчас эта ошибка устранена, и хранилище можно использовать повторно в других играх и приложениях без изменений.



Ранее состояние шага игры описывалось только массивом из 9 значений. Сейчас состояние игры определяется объектом содержащим массив и свойством xIsNext. Инициализация этого объекта в начале игры выглядит так:



let state = {
  squares: Array(9).fill(''),
  xIsNext: true,
};


И еще можно отметить, что хранилище history сейчас может воспринимать состояния описанные любым образом.



Произвольный доступ к истории ходов


Код на REPL



В хранилище history добавили метод setCurrent(current), с помощью которого устанавливаем выбранное текущее состояние игры.



setCurrent(current) {
    if (current >= 0 && current < this.history.length)
        this.current = current;
}


setCurrent: (current) => update(h => { 
    h.setCurrent(current);
    return h; 
}),


В компоненте App добавили вывод истории ходов в виде кнопок.



<ol>
    {#each $history.history as value, i}
        {#if i==0}
            <li><button on:click={() => history.setCurrent(i)}>Go to game start</button></li>
        {:else}
            <li><button on:click={() => history.setCurrent(i)}>Go to move #{i}</button></li>
        {/if}
    {/each}
</ol>


Определение победителя, вывод статуса игры


Код на REPL



Добавлена функция определения победителя calculateWinner() в отдельном файле helpers.js:



export function calculateWinner(squares) {
    const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6],
    ];
    for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
            return squares[a];
        }
    }
    return null;
}  


Добавлено производное хранилище status для определения статуса игры, здесь определяется исход игры: победитель или ничья:



export const status = derived(
    history,
    $history => { 
        if ($history.currentState()) {
            if (calculateWinner($history.currentState().squares))
                return 1;
            else if ($history.current == 9)
                return 2;
        }
        return 0;
    }
);


В компоненте App добавлен вывод статуса игры:



<div class="status">
    {#if $status === 1}
        [b]Winner: {!$history.currentState().xIsNext ? 'X' : 'O'}[/b]
    {:else if $status === 2}
        [b]Draw[/b]
    {:else}
        Next player: {$history.currentState().xIsNext ? 'X' : 'O'}
    {/if}
</div>  


В компоненте Board в обработчик клика handleClick() добавлены ограничения: невозможно выполнить клик в заполненной клетке и по окончании игры.



const state = $history.currentState();
if ($status == 1 || state.squares[i])
    return;


Игра закончена! В следующей статье рассмотрим реализацию этой же игры с помощью паттерна Command, т.е. с хранением команд Undo/Redo вместо хранения отдельных состояний.



Репозиторий на GitHub


https://github.com/nomhoi/tic-tac-toe-part2



Установка игры на локальном компьютере:



git clone https://github.com/nomhoi/tic-tac-toe-part2.git
cd tic-tac-toe-part2
npm install
npm run dev


Запускаем игру в браузере по адресу: http://localhost:5000/.



Источник: Хабр / Интересные публикации

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *