Рубрики
Без рубрики

Как кодировать игру жизни с реагированием

Пабло Regen Игра Life включает в себя двумерную ортогональную сетку квадратных клеток, каждый из которых находится в одном из двух возможных состояний, живых или мертвых. На каждом шагу каждая клетка взаимодействует с восемью соседними соседями, следуя простому набору правил, приводящих к рождениям и

от Pablo Regen

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

Это игра с нулевой игрой. Его эволюция определяется его начальным состоянием, не требуя дальнейшего входа от игроков. Один взаимодействует с игрой, создавая начальную конфигурацию и наблюдая, как она развивается, или для продвинутых игроков, создавая шаблоны с конкретными свойствами.

Правила

  1. Любая живая клетка с меньшим количеством двух живых соседей умирает, как будто по недопустимой
  2. Любая живая клетка с двумя или тремя живыми соседями живет на следующем поколении
  3. Любая живая клетка с более чем тремя живыми соседями умирает, как будто по переоценке
  4. Любая мертвая клетка с ровно тремя живыми соседями становится живой клеткой, Как будто путем воспроизводства

Хотя игра может быть идеально записана ванильным JavaScript, я был счастлив пройти вызов реакцией. Итак, начнем.

Настройка реакции

Есть несколько способов настроить реагирование, но если вы новичок, я рекомендую проверить Создать приложение React документы и гадость , а также подробный обзор React по Tania Rascia Отказ

Проектирование игры

Основное изображение наверху – моя реализация игры. Сетка для доски, содержащая свет (живой) и темные (мертвые) клетки, показывает эволюцию игры. Контроллеры позволяют вам запускать/остановить, пойти на один шаг за раз, настроить новую доску или очистить ее, чтобы экспериментировать со своими собственными шаблонами, нажав на отдельные ячейки. Слайдер контролирует скорость, а поколение информирует количество завершенных итераций.

В дополнение к главному компоненту, содержанию состояния, я отдельно создаю функцию для генерации состояния всей платы с нуля, компонент для сетки доски и еще один для ползунка.

Настройка App.js.

Во-первых, давайте импортируем реагирую и реагирую. Компонент от «реагирования». Затем установите, сколько рядов и столбцов имеет сеть доски. Я иду с 40 на 60, но не стесняйтесь играть с разными номерами. Затем приходите отдельные функции и функциональные компоненты (обратите внимание на заглавную первую букву), описанную выше, а также классный компонент, содержащий состояние и методы, включая один. Наконец, давайте экспортируем основное приложение компонента.

import React, { Component } from 'react';

const totalBoardRows = 40;
const totalBoardColumns = 60;

const newBoardStatus = () => {};
const BoardGrid = () => {};
const Slider = () => {};

class App extends Component {
    state = {};

    // Methods ...

    render() {
        return (
            
        );
    }
}

export default App;

Создание статуса ячейки новой доски

Так как нам нужно знать статус каждой ячейки и Его 8 соседей для каждой итерации, давайте создадим функцию, которая возвращает массив массивов, каждый из которых содержит ячейки с логическими значениями. Количество массивов в главном массиве сопоставит количество строк, а количество значений в каждой из этих массивов будет соответствовать количеству столбцов. Таким образом, каждое логическое значение будет представлять состояние каждой ячейки «живой» или «мертвых». Параметр функции по умолчанию по умолчанию менее чем на 30% вероятность того, чтобы быть живым, но выпала свободно, чтобы экспериментировать с другими числами.

const newBoardStatus = (cellStatus = () => Math.random() < 0.3) => {
    const grid = [];
    for (let r = 0; r < totalBoardRows; r++) {
        grid[r] = [];
        for (let c = 0; c < totalBoardColumns; c++) {
            grid[r][c] = cellStatus();
        }
    }
    return grid;
};

/* Returns an array of arrays, each containing booleans values
(40) [Array(60), Array(60), ... ]
0: (60) [true, false, true, ... ]
1: (60) [false, false, false, ... ]
2: (60) [false, false, true, ...]
...
*/

Генерация сетки доски

Давайте определим функциональный компонент, который создает сеть доски и назначает его переменной. Функция получает состояние всего статуса доски и метод, который позволяет пользователям переключать состояние отдельных ячеек в качестве реквизита. Этот метод определяется на главном компоненте, где проводится все состояние приложения.

Каждая ячейка представлена таблицей и имеет атрибут классов знамена, значение которого зависит от логического значения соответствующей ячейки платы. Игрок, начисленный на ячейку, приводит к тому, что метод, переданный в виде реквизитов, вызываемых со строкой ячейки и местоположением столбца в качестве аргумента.

Проверьте Подъемное состояние Вверх Для получения дополнительной информации о передаче методов как реквизиты, и не забудьте добавить Ключи Отказ

const BoardGrid = ({ boardStatus, onToggleCellStatus }) => {
    const handleClick = (r,c) => onToggleCellStatus(r,c);

    const tr = [];
    for (let r = 0; r < totalBoardRows; r++) {
        const td = [];
        for (let c = 0; c < totalBoardColumns; c++) {
            td.push(
                 handleClick(r,c)}
                />
            );
        }
        tr.push({td});
    }
    return {tr}
; };

Создание слайдера скорости

Этот функциональный компонент создает ползунок, позволяющий игрокам изменить скорость итераций. Он получает состояние текущей скорости и метод для обработки изменений скорости как реквизиты. Вы можете попробовать разные минимальные, максимальные и шаговые значения. Результаты изменения скорости в способе, переданном в виде реквизит, вызываемых с желаемой скоростью в качестве аргумента.

const Slider = ({ speed, onSpeedChange }) => {
    const handleChange = e => onSpeedChange(e.target.value);

    return (
        
    );
};

Главный компонент

Поскольку он содержит состояние приложения, давайте сделаем его классовым компонентом. Обратите внимание, что я не использую Крючки , новое добавление в реакции 16.8, которые позволяют использовать состояние и другие функции реагирования без записи класса. Я предпочитаю использовать Синтаксис экспериментальных открытых классов Так что я не связываю методы внутри конструктора.

Давайте рассмотрим это.

Состояние

Я определяю состояние в качестве объекта со свойствами для статуса доски, количества генерации, игре, запущенной или остановленной и скоростью итераций. Когда игра запускается, статус доски клетки будет возвращен призовом к функции, которая генерирует новый статус доски. Генерация начинается в 0, а игра будет работать только после того, как пользователь решит. Скорость по умолчанию составляет 500 мс.

class App extends Component {
    state = {
        boardStatus: newBoardStatus(),
        generation: 0,
        isGameRunning: false,
        speed: 500
    };

    // Other methods ...

}

Кнопка запуска/остановки

Функция, которая возвращает другой элемент кнопки в зависимости от состояния игры: работает или остановлено.

class App extends Component {
    state = {...};

    runStopButton = () => {
        return this.state.isGameRunning ?
         :
        ;
    }
    
    // Other methods ...
}

Прозрачная и новая доска

Методы для обработки запроса игроков, чтобы начать с новой случайной статуса ячейки случайной доски или полностью очистить доску, чтобы они могли поэкспериментировать, переключающийся отдельным состоянием ячейки. Разница между ними состоит в том, что тот, который очищает доску, устанавливает состояние для всех клеток в FALSE, а другой не передает никаких аргументов в метод NewboardStatus, поэтому состояние каждой ячейки становится по умолчанию случайное логическое значение.

class App extends Component {
    state = {...};
    runStopButton = () => {...}
    
    handleClearBoard = () => {
        this.setState({
            boardStatus: newBoardStatus(() => false),
            generation: 0
        });
    }

    handleNewBoard = () => {
        this.setState({
            boardStatus: newBoardStatus(),
            generation: 0
        });
    }
    
    // More methods ...
    
 }

Внутренний статус клетки

Нам нужен способ обрабатывать запросы игроков для переключения отдельного состояния ячейки, что полезно для эксперимента с пользовательскими шаблонами непосредственно на плате. Компонент BoardGrid вызывает его каждый раз, когда игрок нажимает на ячейку. Он устанавливает состояния состояния доски, позвонив функцию и Передача его предыдущим состоянием в качестве аргумента Отказ

Функция Deep Clones Статус предыдущей доски, чтобы избежать модификации его посредством ссылки при обновлении отдельной ячейки на следующей строке. (Использование Const ClondboardStatus = [... BoardStatus] будет изменять исходный статус, поскольку распространение синтаксиса эффективно идет на один уровень глубоко при копировании массива, поэтому он может быть подходящим для копирования многомерных массивов . Обратите внимание, что Json.parse (json.stringify (obj)) Не работает, если клонированный объект использует функции). Функция, наконец, возвращает обновленную клонированную доску состояния доски, эффективно обновляя состояние доски.

Для глубокого клонирования проверить здесь , здесь и здесь Отказ

class App extends Component {
    state = {...};
    runStopButton = () => {...}
    handleClearBoard = () => {...}
    handleNewBoard = () => {...}

    handleToggleCellStatus = (r,c) => {
        const toggleBoardStatus = prevState => {
            const clonedBoardStatus = JSON.parse(JSON.stringify(prevState.boardStatus));
            clonedBoardStatus[r][c] = !clonedBoardStatus[r][c];
            return clonedBoardStatus;
        };

        this.setState(prevState => ({
            boardStatus: toggleBoardStatus(prevState)
        }));
    }
    
    // Other methods ...
    
}

Генерация следующего шага

Вот где следующая игровая итерация генерируется путем установки состояния состояния доски до возвращенного значения функции. Он также добавляет один в состояние поколения, чтобы сообщить игроку, насколько до сих пор было произведено итерация.

Функция («NextStep») определяет две переменные: состояние доски и состояние глубокой клонированной доски. Затем функция рассчитывает количество соседей (внутри доски) со значением true для отдельной ячейки, когда он называется. Из-за правил нет необходимости считать более четырех настоящих соседей на клетку. Наконец, и, согласно правилам, он обновляет отдельную клетку к клонированному совету к клонированному совету и возвращает статус клонированной доски, который используется в STETSTATE.

class App extends Component {
    state = {...};
    runStopButton = () => {...}
    handleClearBoard = () => {...}
    handleNewBoard = () => {...}
    handleToggleCellStatus = () => {...}

    handleStep = () => {
        const nextStep = prevState => {
            const boardStatus = prevState.boardStatus;
            const clonedBoardStatus = JSON.parse(JSON.stringify(boardStatus));
			
            const amountTrueNeighbors = (r,c) => {
                const neighbors = [[-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];
                return neighbors.reduce((trueNeighbors, neighbor) => {
                    const x = r + neighbor[0];
                    const y = c + neighbor[1];
                    const isNeighborOnBoard = (x >= 0 && x < totalBoardRows && y >= 0 && y < totalBoardColumns);
                    /* No need to count more than 4 alive neighbors */
                    if (trueNeighbors < 4 && isNeighborOnBoard && boardStatus[x][y]) {
                        return trueNeighbors + 1;
                    } else {
			return trueNeighbors;
		    }
                }, 0);
            };
			
            for (let r = 0; r < totalBoardRows; r++) {
                for (let c = 0; c < totalBoardColumns; c++) {
                    const totalTrueNeighbors = amountTrueNeighbors(r,c);
					
                    if (!boardStatus[r][c]) {
                        if (totalTrueNeighbors === 3) clonedBoardStatus[r][c] = true;
                    } else {
                        if (totalTrueNeighbors < 2 || totalTrueNeighbors > 3) clonedBoardStatus[r][c] = false;
                    }
                }
            }
			
            return clonedBoardStatus;
        };
		
        this.setState(prevState => ({
            boardStatus: nextStep(prevState),
            generation: prevState.generation + 1
        }));
    }
	
    // Other methods ...
}

Обработка изменений скорости и действий запуска/остановки

Эти 3 способа устанавливают только значение состояния для свойств скорости и Isgamerunning.

Затем в методе жизненного цикла ComponentDiDUpdate давайте очистим и/или установите таймер в зависимости от различных комбинаций значений. Таймер расписан вызов методу HandleStep по указанным интервалам скорости.

class App extends Component {
    state = {...};
    runStopButton = () => {...}
    handleClearBoard = () => {...}
    handleNewBoard = () => {...}
    handleToggleCellStatus = () => {...}
    handleStep = () => {...}
                        
    handleSpeedChange = newSpeed => {
        this.setState({ speed: newSpeed });
    }

    handleRun = () => {
        this.setState({ isGameRunning: true });
    }

    handleStop = () => {
        this.setState({ isGameRunning: false });
    }

    componentDidUpdate(prevProps, prevState) {
        const { isGameRunning, speed } = this.state;
        const speedChanged = prevState.speed !== speed;
        const gameStarted = !prevState.isGameRunning && isGameRunning;
        const gameStopped = prevState.isGameRunning && !isGameRunning;

        if ((isGameRunning && speedChanged) || gameStopped) {
            clearInterval(this.timerID);
        }

        if ((isGameRunning && speedChanged) || gameStarted) {
            this.timerID = setInterval(() => {
                this.handleStep();
            }, speed);
        }
    }
                        
    // Render method ...
}

Метод рендера

Последний метод в компоненте приложения возвращает желаемую структуру и информацию о том, чтобы отображаться. Поскольку состояние принадлежит компоненту приложения, мы передаем состояние и методы к компонентам, которые нуждаются в их реквизитах.

class App extends Component {
    // All previous methods ...

    render() {
        const { boardStatus, isGameRunning, generation, speed } = this.state;

        return (
            

Game of Life

{' -'} {`Generation: ${generation}`}
{this.runStopButton()}
); } }

Экспорт приложения по умолчанию

Наконец, давайте экспортируем приложение по умолчанию ( Экспортное приложение по умолчанию; ), который импортируется вместе с стилями из «index.scss» по «index.js», а затем отображается до DOM.

Вот и все! ?

Проверьте Полный код на Github и Играть в игру здесь Отказ Попробуйте эти Узор ниже или создайте свой собственный для удовольствия.

Спасибо за прочтение.

Оригинал: “https://www.freecodecamp.org/news/coding-the-game-of-life-with-react-7de2385b7356/”