Недавно я прочитал статью об играх, написанных на чистом 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)


  1. begor
    16.06.2016 12:35
    +8

    Собственно, а что необычного в консольной игре на скриптовом языке? В чем тут принципиальная разница между PHP или, допустим, Python?


  1. kambur
    16.06.2016 12:36
    -7

    Ничего необчного здесь нет. Просто никому не приходит в голову делать такое. Мне вот пришло — я сделал.


    1. Zibx
      16.06.2016 13:27

      Приходило. А потом уходило. Если хочется странного — в природе существуют php-qt биндинги, а вот статья на хабре о них.


      1. kambur
        16.06.2016 13:29

        Спасибо за ссылку.


  1. zcasper
    16.06.2016 12:40

    можно в последующем добавить немного экшена, чем нибудь вроде https://pecl.php.net/package/opengl или https://github.com/phpopengl, хоть это уже и не совсем консоль, но и не браузер точно )


    1. Assada
      16.06.2016 12:52

      Ну вот. опять ночи без сна =)


  1. WapGeaR
    16.06.2016 12:57
    +3

    Это, наверное, самая странная статья за последнее время.
    Если игра на css действительно вызывает удивление, то игра написанная на php — что в этом странного? Это же полноценный язык, на котором пишутся в том числе и игры. Непонятно.
    Или должно быть странным то, что это работает через консоль? — Да вроде тоже обычная вещь.
    В чем смысл то?


    1. kambur
      16.06.2016 13:05
      -3

      Все очень просто. Хоть PHP и полноценный язык программирования, я не нашел в сети ни одной игры написанной на нем. Мне стало интересно, а возможно ли это? Моя статья — поиск ответа на этот вопрос. Кроме того, я так и не до сих пор и нашел решения, как можно на PHP сделать динамическую игру.
      Моя статья для тех, кого тоже мучают подобные вопросы.


      1. Sergiy
        16.06.2016 13:14
        +2

        Если вас мучают такие вопросы, то у вас много свободного времени.


      1. jonic
        16.06.2016 13:16

        а очень просто, PHPDaemon, fork, какой нибудь shared_memory и вуаля


      1. WapGeaR
        16.06.2016 13:23

        Приношу извинения, что пишу в новую ветку. мобильное приложение отказывается реагировать на кнопку ответа.
        Касательно вашего интереса — на пхп все же желательно писать бэкэнд, а вот для фронта все же лучше использовать что-нибудь динамическое (те же сокеты). Примеров подобных связок огромное множество. А вот пытаться сделать динамику на пхп на мой взгляд глупо, ибо пхп обычно умирает после выполнения задачи и делать что-либо динамическое на нем представляется возможным (если вообще возможно) только с диким количеством костылей.


        1. kambur
          16.06.2016 13:32

          Все правильно. Именно поэтому решение такой задачи — отличная зарядка для ума, способ отвлечься от основной работы.


  1. bizzonaru
    16.06.2016 13:31

    А вот с использованием ncurses не пробывали сделать?
    https://habrahabr.ru/post/186570/


    1. kambur
      16.06.2016 13:34
      +1

      Спасибо за ссылку, я обязательно попробую!


  1. bromzh
    16.06.2016 13:39

    Сокобан на sed — вот это необычно. Реверси (64 строки) или пятнашки (10 строк) с графическим интерфейсом на тикле — тоже круто.
    А простые консольные крестики-нолики на высокоуровневом языке выглядят как-то банально.