Недавно я прочитал статью об играх, написанных на чистом CSS. Это было бомбой! Никогда мне в голову не пришла бы идея использовать язык описания таблиц стилей, как инструмент для разработки игр. Зато появилась другая — использовать для этого PHP.
Какую же игру выбрать для реализации? Воображение сразу же подсунуло целую кучу срелялок-леталок (спасибо 8 битному детству), но разум подсказывал — начни с простого. Поэтому я выбрал старые добрые крестики-нолики.
Для себя решил, что игра должна быть обязательно консольной (PHP игра, которая исполняется в браузере — слишком простая задача, а я не ищу легких путей). Все готово, пора начинать наш квест.
Шаг 1: игровое поле
Поскольку наша игра будет консольной, то прорисовывать изображения и надписи на экране мы будем с помощью символов.
private static function showTitle() {
self::clearScreen();
echo <<<EOD
=========================================================
=========================================================
##### ## ## ##### #### #### ## # ##### ####
## ## ## ## ## ## ## ## ## ### ## ## ##
##### ###### ##### ## ### ###### ## # # #### ####
## ## ## ## ## ## ## ## ## # ## ##
## ## ## ## #### ## ## ## # ##### ####
=========================================================
=========================================================
1 | 2 | 3
--- --- ---
HOW TO PLAY: input number of cell you chose 4 | 5 | 6
--- --- ---
7 | 8 | 9
Chose 0 to exit the game.
=========================================================
=========================================================
EOD;
}
private static function gameOver($whoWin = -1) {
sleep(1);
self::clearScreen();
if ($whoWin == -1) {
echo <<<EOD
=========================================================
=========================================================
#### #### ## # ##### #### ## ## ##### #####
## ## ## ### ## ## ## ## ## ## ## ## ##
## ### ###### ## # # #### ## ## ## ## #### #####
## ## ## ## ## # ## ## ## #### ## ## ##
#### ## ## ## # ##### #### ## ##### ## ##
=========================================================
=========================================================
EOD;
}
if ($whoWin == 1) {
echo <<<EOD
=========================================================
=========================================================
## ## #### ## ## ## ## ###### ## ##
#### ## ## ## ## ## ## ## ### ##
## ## ## ## ## ## # ## ## ## # ##
## ## ## ## ## ####### ## ## ##
## #### #### ## ## ###### ## ##
=========================================================
=========================================================
EOD;
}
if ($whoWin == 0) {
echo <<<EOD
=========================================================
=========================================================
## ## #### ## ## ## #### #### #####
#### ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## #### ####
## ## ## ## ## ## ## ## ## ##
## #### #### ###### #### #### #####
=========================================================
=========================================================
EOD;
}
echo "\n\n Do you want play again (Y/N)? : ";
$userChoice = strtolower(substr(fgets(STDIN), 0, 1));
if ($userChoice == 'y') {
self::runGame();
} else {
exit();
}
}
Шаг 2: ход игрока
Пользователь имеет возможность выбирать номер ячейки, куда будет поставлен крестик. Компьютер же играет ноликами. Каждый раз после того, как выбор сделан, игровое поле обновляется и выводится на экран.
private static function applyMove($field, $xStatrtPosition, $yStartPosition) {
foreach ($field as $fieldRow) {
$yCurrentPosition = $yStartPosition;
foreach ($fieldRow as $fieldCell) {
self::$gameField[$xStatrtPosition][$yCurrentPosition] = $fieldCell;
$yCurrentPosition++;
}
$xStatrtPosition++;
}
self::drawField();
}
private static function drawField() {
self::clearScreen();
self::showTitle();
echo "\n";
foreach (self::$gameField as $line) {
foreach ($line as $cell) {
echo $cell;
}
echo "\n";
}
}
При этом не забываем контролировать и проверять выбор игрока.
private static function checkUserChoice($userChoice) {
return (in_array($userChoice, [1,2,3,4,5,6,7,8,9,0]) &&
array_search($userChoice, array_merge(self::$playerMoves[0], self::$playerMoves[1])) === false);
}
Шаг 3: ход компьютера
Самый простой вариант — что называется "пальцем в небо", простая случайная выборка.
private static function applyComputerMove() {
$movesLeft = array_diff([1,2,3,4,5,6,7,8,9], array_merge(self::$playerMoves[0], self::$playerMoves[1]));
shuffle($movesLeft);
return array_pop($movesLeft);
}
Но давайте добавим нашем сопернику немного интеллекта.
private static function applyComputerMove() {
$movesLeft = array_diff([1,2,3,4,5,6,7,8,9], array_merge(self::$playerMoves[0], self::$playerMoves[1]));
foreach (self::$winnerFields as $winnerCombination) {
$availableMove = [];
if (count(array_intersect($winnerCombination, self::$playerMoves[0])) == 2) {
$availableMove = array_diff($winnerCombination, array_intersect($winnerCombination, self::$playerMoves[0]));
}
if (count(array_intersect($winnerCombination, self::$playerMoves[1])) == 2) {
$availableMove = array_diff($winnerCombination, array_intersect($winnerCombination, self::$playerMoves[1]));
}
if (count(array_intersect($availableMove, $movesLeft)) != 0) {
return array_pop($availableMove);
}
}
if (in_array(5, $movesLeft)) {
return 5;
}
shuffle($movesLeft);
$computerMove = array_pop($movesLeft);
return $computerMove;
}
Шаг 4: кто же выиграл
После каждого сделанного шага (как игрока так и компьютера) следует проверять не появился ли победитель.
private static $winnerFields = [
[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7],
[2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7],
];
private static function checkWinner($isPlayer) {
foreach (self::$winnerFields as $winnerCombination) {
if (count(array_intersect($winnerCombination, self::$playerMoves[(int) $isPlayer])) == 3) {
self::gameOver((int) $isPlayer);
}
}
if ((count(self::$playerMoves[0]) + count(self::$playerMoves[1])) == 9) {
self::gameOver();
}
}
Шаг 5: интерактивность
Перед тем, как рисовать обновленное игровое поле, надо стереть старое. Казалось бы, что может быть проще — просто очищаем весь экран.
Но тут меня ждал неприятный сюрприз. Конструкция
system("cls");
не работает, если консоль запущена из-под Windows. Панику в сторону. Подход "гугл в помощь" в конце концов принес таки долгожданный результат.
private static function clearScreen() {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
popen('cls', 'w');
} else {
system("clear");
}
}
Шаг 6: идем дальше
Объединяем все кусочки пазла в один класс — и вот, желаемая вершина достигнута GitHub. Однако оглянувшись я увидел, что это вершина не горы, а только маленького бугорка, а глаза уже смотрят туда, где вдалеке маячат консольные PHP экшены и игры на чистом sql.
Комментарии (15)
kambur
16.06.2016 12:36-7Ничего необчного здесь нет. Просто никому не приходит в голову делать такое. Мне вот пришло — я сделал.
Zibx
16.06.2016 13:27Приходило. А потом уходило. Если хочется странного — в природе существуют php-qt биндинги, а вот статья на хабре о них.
WapGeaR
16.06.2016 12:57+3Это, наверное, самая странная статья за последнее время.
Если игра на css действительно вызывает удивление, то игра написанная на php — что в этом странного? Это же полноценный язык, на котором пишутся в том числе и игры. Непонятно.
Или должно быть странным то, что это работает через консоль? — Да вроде тоже обычная вещь.
В чем смысл то?kambur
16.06.2016 13:05-3Все очень просто. Хоть PHP и полноценный язык программирования, я не нашел в сети ни одной игры написанной на нем. Мне стало интересно, а возможно ли это? Моя статья — поиск ответа на этот вопрос. Кроме того, я так и не до сих пор и нашел решения, как можно на PHP сделать динамическую игру.
Моя статья для тех, кого тоже мучают подобные вопросы.WapGeaR
16.06.2016 13:23Приношу извинения, что пишу в новую ветку. мобильное приложение отказывается реагировать на кнопку ответа.
Касательно вашего интереса — на пхп все же желательно писать бэкэнд, а вот для фронта все же лучше использовать что-нибудь динамическое (те же сокеты). Примеров подобных связок огромное множество. А вот пытаться сделать динамику на пхп на мой взгляд глупо, ибо пхп обычно умирает после выполнения задачи и делать что-либо динамическое на нем представляется возможным (если вообще возможно) только с диким количеством костылей.kambur
16.06.2016 13:32Все правильно. Именно поэтому решение такой задачи — отличная зарядка для ума, способ отвлечься от основной работы.
bromzh
16.06.2016 13:39Сокобан на sed — вот это необычно. Реверси (64 строки) или пятнашки (10 строк) с графическим интерфейсом на тикле — тоже круто.
А простые консольные крестики-нолики на высокоуровневом языке выглядят как-то банально.
begor
Собственно, а что необычного в консольной игре на скриптовом языке? В чем тут принципиальная разница между PHP или, допустим, Python?