П’ятничний JS: гра в 0 рядків JS і CSS
Можливо, багато хто з старожилів пам’ятає епідемію статей із заголовками типу “%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: {}
}
У ньому міститься інформація про те, чий зараз хід і який стан ігрового поля. У майбутньому В ньому також з’явиться інформація про те, які кроки можуть бути зроблені і до яких станів вони приведуть, а також деякі інші приємні речі.
Далі ми починаємо, прошу вибачення за тавтологію, з початкового стану і робимо наступне:
- Перевіряємо, чи не є стан термінальним (перемога хрестиків, перемога нуликів, нічия).
- Якщо так, додаємо інформацію про це в об’єкт стану і закінчуємо.
- Якщо ні, проходимся по всім клітинам поля.
- Для кожної порожній клітини поля створюємо новий стан гри, в якому поточний гравець зробив хід у цю клітину, а хід перейшов до наступного гравця.
- У полі
moves
поточного стану додаємо запис про можливий час. Ключем до цього запису служить індекс клітини, а значенням — посилання на новий стан. - Повторюємо цей алгоритм рекурсивно для всіх знов з’явилися станів.
Насправді мій код трохи складніше, я за звичкою розгорнув рекурсію в цикл, а замість посилань на інші стани в moves
зберігаються їх рядкові ключі в одному асоціативному масиві. Але це все деталі.
Потім з кожного об’єкта ігрового стану ми генеруємо HTML-сторінку. Пройшовшись по об’єкту moves
, ми заповнюємо порожні клітини поля посиланнями на сторінки, відповідні ходів, зроблених в ці клітини. Потім перетворюємо одновимірний масив поля у двовимірну HTML-таблиці. Додаємо всякі приємні дрібниці на зразок вказівки, який гравець ходить, і посилання на початкову сторінку — і вуаля!
Крім режиму, коли і хрестики і нулики ставляться людиною, в моєму мега-інді-хіті є також можливість грати проти залізного мозку. Досягається це наступним чином:
- Спочатку рекурсивно (насправді немає) для кожного ігрового стану обчислюється очікуваний результат гри — той, який буде досягнутий, якщо обидві сторони будуть грати ідеально.
- Потім дерево гри модифікується таким чином: замість ходу гравця ми тепер робимо відразу два ходи. Другий хід — хід штучного інтелекту. При цьому з усіх можливих відповідей на хід гравця вибирається той, у якого найкращий очікуваний результат. Таким чином, тицьнувши на порожню клітку, гравець одразу переходить на позицію, де в цій клітці з’явився хрестик (або нулик), а в якійсь іншій з’явився нулик (або хрестик).
- Всі ігрові позиції, відповідні ходах, які ІЇ не робить, безжально відкидаються.
- Потім з решти позицій в окрему папочку генерується HTML — абсолютно аналогічно нагоди двох гравців.
За схожими принципами можна реалізувати будь-яку гру з не дуже великим деревом. Втім, якщо я захочу подібним чином зробити шахи, мені здається, гитхаб відмовиться це хостити =)
До речі про гітхабі: можете подивитися там код (посилання є на головній сторінці гри). На цьому, загалом-то, все. До побачення, дівчатка і хлопчики. До нових зустрічей.
P. S. Заміна переносів рядки з Windows-style на Unix-style — це дуже довго, коли мова йде про 6 тисяч файлів. Я пошкодував, що не подбав про це на етапі написання коду, але все-таки мужньо дотерпів до кінця git add
.