К концу этой статьи у вас будет игра в крестики-нолики, как в приведенной выше.

Давайте начнем с разделения постановки задачи на подмножества, которые мы затем выберем одно за другим.

Нам нужно создать игру в крестики-нолики. Итак, каковы различные аспекты этой игры?

  1. Он имеет доску 3 х 3.
  2. Есть 2 игрока.
  3. Игра начинается с пустой доски.
  4. Каждый игрок может сделать только один ход, за которым следует ход следующего игрока.
  5. Выигрывает игрок, который преодолел три последовательных блока по горизонтали, вертикали или диагонали.

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

Теперь давайте рассмотрим каждую подгруппу проблем и решим их.

1. Доска 3 x 3 -

Если вы когда-нибудь сталкивались со структурой, похожей на доску, самый простой способ создать ее - использовать свойство display:grid для родительского div этих элементов. В данном случае мы хотим получить доску 3 x 3, которую можно создать следующим образом (контейнер является классом родительского div):

.container{
    display: grid;
    grid-template-columns: 200px 200px 200px;
    grid-template-rows : 150px 150px 150px;
   }

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

Каждый div в сетке изначально будет пустым, после чего будет заменено пустое содержимое в div на значение игрока (1 или 0). Пришло время воспользоваться возможностями React. React позволяет вам прикрепить состояние к элементу, а также повторно визуализировать элемент, как только вы измените прикрепленное к нему состояние!

class TictacBoard extends Component {
    initialState = {
        value : {
            '00' : '',
            '01' : '',
            '02' : '',
            '10' : '',
            '11' : '',
            '12' : '',
            '20' : '',
            '21' : '',
            '22' : ''
        }
    };
    state = this.initialState;
    boardItems = [];
    createBoardDivs() {
        this.boardItems  = [];
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                let key = i.toString() + j.toString();
                this.boardItems.push(<div xaxis={i} yaxis={j} key=    {i.toString() + j.toString()} onClick={this.showValue}>{this.state.value[key]}</div>);
            }
        }
        return this.boardItems;
    }
render() {
    return (
        <React.Fragment>
            <div className={styles.container}>
                {this.createBoardDivs()}
            </div>
        </React.Fragment>

    );
   }
}

Здесь мы создали компонент платы, и плата будет напоминать матрицу размером 3 x 3. Каждый div будет иметь прикрепленные к нему атрибуты оси x и оси y, что позже упростит отслеживание значения в div.

2. Есть 2 игрока -

Чтобы отслеживать игроков, мы создаем ключ в состоянии компонента, который мы можем переключать каждый раз, когда делается ход. Для объекта currentPlayer изначально установлено значение «0», которое позже будет переключено, когда пользователь щелкнет любой div.

class TictacBoard extends Component {
    initialState = {
        currentPlayer : 0,
        value : {
            '00' : '',
            '01' : '',
            '02' : '',
            '10' : '',
            '11' : '',
            '12' : '',
            '20' : '',
            '21' : '',
            '22' : ''
        }
      };
    state = this.initialState;
}

3. Игра начинается с пустой доски -

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

Я создал объект значения в состоянии компонента. Чтобы сохранить уникальность каждого div, каждый ключ в объекте значения представляет собой значение оси x и значение оси y каждого div, добавленные вместе в виде строки, которые мы инициализировали пустыми значениями для запуска пустая сетка. Теперь, когда вы хотите изменить значение в div, все, что вам нужно сделать, это изменить значение соответствующего ключа внутри объекта значения состояния компонента.

class TictacBoard extends Component {
    initialState = {
        value : {
            '00' : '',
            '01' : '',
            '02' : '',
            '10' : '',
            '11' : '',
            '12' : '',
            '20' : '',
            '21' : '',
            '22' : ''
        }
    };
    state = this.initialState;
    boardItems = [];
    createBoardDivs() {
        this.boardItems  = [];
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                let key = i.toString() + j.toString();
                this.boardItems.push(<div xaxis={i} yaxis={j} key=    {i.toString() + j.toString()} onClick={this.showValue}>{this.state.value[key]}</div>);
            }
        }
        return this.boardItems;
    }
render() {
    return (
        <React.Fragment>
            <div className={styles.container}>
                {this.createBoardDivs()}
            </div>
        </React.Fragment>

    );
   }
}

4. Каждый игрок может сделать только один ход, за которым следует ход следующего игрока.

Чтобы убедиться в этом, мы перейдем к функции, которая обрабатывает щелчок по каждому div.

showValue = (event) => {
    let xAxis = event.target.getAttribute("xaxis");
    let yAxis = event.target.getAttribute("yaxis");
    let keyToChange = xAxis + yAxis;

   const newValue = {...this.state.value};
      newValue[keyToChange] = this.state.currentPlayer ? '0' : '1';
   this.setState((prevState) => {
      return {
        currentPlayer: prevState.currentPlayer ? 0 : 1,
        value: newValue
    }
  });
  this.checkWinner(xAxis, yAxis, newValue[keyToChange], newValue);
}

Когда игрок нажимает на div, мы сначала выясняем, какой div был нажат, путем извлечения атрибутов xaxis и yaxis из этого div. Теперь мы генерируем ключ, который мы хотели бы изменить в объекте значения, и устанавливаем новое состояние в компоненте с новым объектом значения. Кроме того, значение, отображаемое в div, переключается между 0 и 1 каждый раз, когда мы нажимаем на div.

5.Найти победителя -

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

Теперь есть 4 условия, при которых игрок может выиграть:

Отслеживайте следующее из текущей позиции игрока / текущего div, по которому щелкнули:

Предположим, что выбран элемент div (1,2) - 1 является осью x элемента div, а 2 - осью y.

  1. Отслеживайте все горизонтальные div в этом div - (1, 0), (1, 1)
  2. Отслеживайте все вертикальные блоки в этом блоке - (0,2), (2, 2)

Вы видите закономерность в двух вышеупомянутых случаях? Для case-1 ось x остается такой же, как ось x выбранного div, а ось y изменяется с 0 на 2. Точно так же для case-2 ось y остается такой же, как и y. - ось div, по которой щелкнули мышью, и ось X изменится с 0 на 2.

3. Отслеживайте все диагональные div, начиная с верхнего левого угла доски и заканчивая нижним правым углом доски (только если оси X и Y текущего нажатого div равны).

Причина в том, что на этой диагонали расположены следующие блоки div - (0, 0), (1, 1), (2, 2).

4. Отслеживайте все диагональные div, начиная с правого верхнего угла доски и заканчивая нижним левым углом доски (только если сумма осей x и y текущего нажатого div равна 2).

Это так, потому что на этой диагонали расположены следующие блоки div - (0, 2), (1, 1), (2, 0).

checkWinner(xAxis, yAxis, currentValue, values) {
    let allValues = {...values};
    let won = true;
    let x = 0, y = 0;
   
    //case 1 : check horizontally
    while(x >= 0 && x < 3) {
        if(allValues[x.toString() + yAxis.toString()] !== currentValue) {
            won = false;
            break;
        }
        x++;
    }
   //case 2 : check vertically
    if(!won) {
        won = true;
        while(y >= 0 && y < 3) {
            if(allValues[xAxis.toString() + y.toString()] !== currentValue) {
                won = false;
                break;
            }
            y++
        }
    }
   //case 3 : check top-left to bottom-right diagonal
   if(!won && xAxis === yAxis) {
        won = true;
        x = 0;
        y = 0;
        while(x < 3 && y < 3) {
            if(allValues[x.toString() + y.toString()] !== currentValue) {
                won = false;
                break;
            }
            x++;
            y++;
        }
    }
  //case 4: check top-right to bottom-left diagonal

    if(!won && (Number.parseInt(xAxis) + Number.parseInt(yAxis) === 2)) {
        won = true;
      for(let i = 0; i < 3; i++) {
          for(let j = 0; j < 3; j++) {
              if(i + j === 2) {
                  if(allValues[i.toString() + j.toString()] !== currentValue) {
                      won = false;
                      break;
                  }
              }
          }
      }
    }
    if(won) {
        this.setState({winner : `The winner of this game is ${currentValue}`});
     }


}

И у вас есть свои собственные крестики-нолики!

Вы можете добавить несколько других модификаций в этот код, например: отключить сетку, как только будет найден победитель, добавить таймер для игры, добавить опцию «кто пойдет первым», добавить тег SVG, чтобы показать линию через div победителя. .

Чтобы увидеть полный код, посетите ссылку GitHub:



Больше контента на plainenglish.io