Возможно, многие из старожилов помнят эпидемию статей с заголовками вида "%something% в 30 строк JS". А также последовавший за ней эпичный пост "Игра в 0 строк кода на чистом JS", после которого эпидемия резко сошла на нет. Полностью осознавая, что этот шедевр мне никогда не превзойти, я всё же спустя пять лет решил докинуть свои пять копеек.

Дамы и господа, вашему вниманию предлагается игра «Крестики-нолики» в нуль строк JS, а также, в отличие от игры, упомянутой выше, в нуль строк CSS (включая инлайн стили). Только голый HTML, только хардкор.


> Ссылка на игру

Выглядит неказисто, зато будет работать в любом браузере. Под катом я расскажу, почему игра без JS оказалась в рубрике «Пятничный JS», а также другие грязные подробности. Впрочем, Америку я никому не открою, если вы опытный кодер, можете под кат даже не заходить

Собственно, всё устроено очень тупо. Игра состоит из почти 6 тысяч страниц статического HTML, ссылающихся друг на друга. При тыке на клетку игрового поля происходит переход на страницу, где ход в эту клетку уже сделан. Очевидно, писать 6к страниц руками — удовольствие ниже среднего. Поэтому (сюрприз!) страницы генерируются JS-скриптами с помощью NodeJS.

Лирическое отступление
Написав предыдущую строчку, я вдруг задумался, не является ли выражение «JS-скрипт» тавтологией, как «CD-диск» или «VIP-персона». С одной стороны, вроде как является. С другой, JS — это всё-таки не аббревиатура, а сокращение несколько иной природы. Однако пост всё-таки не о филологии, потому лирическое отступление заканчивается и начинается лирическая атака.

Сначала мы строим так называемое дерево игры — совокупность всех возможных игровых состояний и переходов между ними. Начальное состояние игры в моём коде представлено следующим образом:

const initialState = {
	player: PLAYER_X,
	field: Array.from(Array(9)).map(() => EMPTY_CELL),
	moves: {}
}

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

Дальше мы начинаем, прошу прощения за тавтологию, с начального состояния и делаем следующее:

  1. Проверяем, не является ли состояние терминальным (победа крестиков, победа ноликов, ничья).
  2. Если да, добавляем информацию об этом в объект состояния и заканчиваем.
  3. Если нет, проходимся по всем клеткам поля.
  4. Для каждой пустой клетки поля создаём новое состояние игры, в котором текущий игрок сделал ход в эту клетку, а ход перешёл к следующему игроку.
  5. В поле moves текущего состояния добавляем запись о возможном ходе. Ключом в этой записи служит индекс клетки, а значением — ссылка на новое состояние.
  6. Повторяем этот алгоритм рекурсивно для всех вновь появившихся состояний.

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

Затем из каждого объекта игрового состояния мы генерируем HTML-страницу. Пройдясь по объекту moves, мы заполняем пустые клетки поля ссылками на страницы, соответствующие ходам, сделанным в эти клетки. Затем превращаем одномерный массив поля в двумерную HTML-таблицу. Добавляем всякие приятные мелочи вроде указания, который игрок ходит, и ссылки на начальную страницу — и вуаля!

Кроме режима, когда и крестики, и нолики ставятся человеком, в моём мега-инди-хите есть также возможность играть против железного мозга. Достигается это следующим образом:

  1. Сначала рекурсивно (на самом деле нет) для каждого игрового состояния вычисляется ожидаемый результат игры — тот, который будет достигнут, если обе стороны будут играть идеально.
  2. Затем дерево игры модифицируется следующим образом: вместо хода игрока мы теперь делаем сразу два хода. Второй ход — ход искусственного интеллекта. При этом из всех возможных ответов на ход игрока выбирается тот, у которого самый лучший ожидаемый результат. Таким образом, тыкнув на пустую клетку, игрок сразу переходит на позицию, где в этой клетке появился крестик (или нолик), а в какой-то другой появился нолик (или крестик).
  3. Все игровые позиции, соответствующие ходам, которые ИИ не делает, безжалостно отбрасываются.
  4. Затем из оставшихся позиций в отдельную папочку генерится HTML — абсолютно аналогично случаю двух игроков.

По сходным принципам можно реализовать любую игру с не очень большим деревом. Впрочем, если я захочу подобным образом сделать шахматы, мне кажется, гитхаб откажется это хостить =)

Кстати о гитхабе: можете посмотреть там код целиком (ссылка присутствует на заглавной странице игры). На этом, в общем-то, всё. До свидания, девочки и мальчики. До новых встреч.

P.S. Замена переносов строки с Windows-style на Unix-style — это очень долго, когда речь идёт о 6 тысячах файлов. Я пожалел, что не позаботился об этом на этапе написания кода, но всё-таки мужественно дотерпел до конца git add.

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


  1. klubben
    03.08.2018 12:34
    +1

    В детстве я был уверен что игры так и делаются, художники сидят и рисуют все возможные варианты кадров «Марио»


    1. Sirion Автор
      03.08.2018 12:39

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


    1. rjhdby
      03.08.2018 13:47
      +1

      Помнится в далеком детстве на голом .bat делал книгу-игру. Все как полагается, одна страница — один .bat файл :)


      1. Sirion Автор
        03.08.2018 13:54

        батники хотя бы аргументы могут друг другу передавать)


    1. Alexey2005
      05.08.2018 17:18

      Объёмы современных игр растут так быстро, что скоро уже вариант «отрендерить каждую сцену в каждом ракурсе» будет весить меньше, нежели всё это раздутое нагромождение нормально-текстурных карт. 360 кадров на точку (рендер с поворотом на 1 градус), расстояние между точками равно одному шагу персонажа. Всё это пожать с помощью какого-нибудь H.264.
      И не только весить будет меньше, но и ещё заработает даже на нетбуках, не требуя GeForce 1080Ti для каких-то жалких 30 FPS.


  1. Biga
    03.08.2018 12:54

    Осталось сделать игру в 0 строк на js, css и html. Хмм.


    1. Sirion Автор
      03.08.2018 13:06

      Игра в гляделки с пустой страницей?


      1. Biga
        03.08.2018 13:15
        +1

        У http есть статусы (200, 404, etc.), заголовки, там много всего интересного. Наверняка можно придумать что-нибудь забавное. Например, написать однорукого бандита на конфигах nginx.


        1. Sirion Автор
          03.08.2018 13:19
          +1

          Игру в напёрстки с адресной строкой в качестве гуя)


      1. AxisPod
        03.08.2018 13:17

        WebAssembly)))


    1. i86com
      04.08.2018 09:03

      Ну, для крестиков-ноликов, технически, даже карандаш с бумагой не обязателен — достаточно воображения.



  1. nickolaym
    03.08.2018 14:08

    ручками cr/lf править — это и впрямь ненормальное программирование. В одну строку на баше делается же!


    1. mayorovp
      03.08.2018 14:17

      А кто их ручками правил-то?..


    1. Sirion Автор
      03.08.2018 14:21

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


  1. GRaAL
    03.08.2018 14:48
    +1

    Я в детстве так и писал крестики-нолики на QBasic.

    If (игрок1 поставили крестик слева-вверху) then 
      рисуем крестик слева-вверху
      if (игрок2 поставил нолик справа-снизу) then
        ...
      end if
    end if
    


    Я был тогда очень целеустремлен и усидчив.


  1. sw0rl0k
    03.08.2018 17:58
    +5

    спустя пять лет

    Специально пошел посмотреть на дату той статьи. Ёшкин кот, вчера же только этот тренд был…


    1. a-tk
      03.08.2018 19:53

      Жаль только демка не работает…


  1. dmitryredkin
    03.08.2018 22:12
    +2

    Пффф!
    Вы так говорите, как будто это что-то новое.
    В начале 2000-х на телефонах фирмы Siemens ещё не было J2ME, а играть уже хотелось.
    Вот тогда-то я узнал, что такое WML-games.
    Были среди них и довольно увлекательные, например — "быки и коровы"


    1. dmitryredkin
      03.08.2018 22:33
      +1

      поправка: wmlc-игры.
      З.Ы. А еще, благодаря самописному скрипту text2wmlc я и книжки на нем умудрялся читать… Эх, было времечко!


      1. SopaXT
        03.08.2018 22:46

        А можно поподробнее про WMLC? Как на нём писали и т.п.
        Нигде не могу найти толкового описания функционала WAP.


        1. dmitryredkin
          05.08.2018 21:19

          Боюсь, не смогу вам ничем помочь.
          Очень уж давно все было… Но форум сименс-клуба оказывается до сих пор жив!
          Возможно, там что-то удастся найти…


          1. neit_kas
            06.08.2018 05:00

            Заброшенный такой, мхом поросший. Воочию наблюдаешь, как выглядит вроде бы недавняя, но уже история.


    1. Sirion Автор
      04.08.2018 00:15

      Ну, я таки не очень претендовал на новизну) Более того, ещё в две тысячи лохматом году я делал что-то похожее даже не на HTML, а на BB-кодах на каком-то блогосервисе.


  1. Gribs
    04.08.2018 01:27

    Аналогичным образом можно сделать некое подобие калькулятора на HTML. Что безусловно докажет остальным что HTML — полновесный язык программирования.


  1. bolk
    06.08.2018 11:03
    +1

    2011-й год: bolknote.ru/all/3451