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

И вокруг уже всё изменилось. Трава не такая зеленая, колбаса не по ГОСТу, по 2х2 Сейлор Мун не крутят с утра. Особенно жалко тех, кто и вправду крут, ведь они больше не смогут отведать «супер-батончик Финт». 

Со всем можно смириться, но вот только одно не дает мне покоя. Не смогли мы с сестрой в 90-е дозвонится в передачу «Позвоните Кузе». Шансов поиграть у нас конечно же не было, но мечта осталась.

Но это же Хабр, а не клуб любителей ностальгировать. Поэтому сегодня мы напишем простейший веб сервис, затем вооружимся голосовым ботом VoiceBox. И в результате реализуем функции управления с телефона для простенькой игры на движке Godot.

Даже если вы не знакомы с продуктом от компании MTT, всё равно есть смысл почитать статью, ведь в следующем материале мы вместе с вами напишем нехитрую аркаду про Кузьму и подключим к ней управление с телефона.

Оглавление:

Пара слов про домовенка Кузю

«Позвоните Кузе» — это телепередача выходящая в конце 90-х на телеканале РТР. По сути отечественная адаптаций датской передачи про телевизионного тролля Hugo, которая успешно разошлась во множестве стран.

Отсылка к х/ф «Москва слезам не верит»
Отсылка к х/ф «Москва слезам не верит»

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

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

Поясню: там нет режима тонального набора. 

Ведущие всячески подбадривали игроков, особенно когда у них были проблемы с управлением. 

В детстве я думал, что игроки просто «тупят» и мы бы с сестрой справились бы куда, лучше. Однако, пересмотрев пару роликов в более осознанном возрасте, я стал подозревать, что скорее всего проблемы крылись в оборудовании и телефонных линиях.

Именно этот прекрасный эффект неотзывчивого управления с помощью тонального набора на телефоне мы сегодня повторим.

Что будем делать

На Хабре есть интересное интервью про то, как энтузиасты Кузю восстанавливали. Мы сегодня так делать не будем. Главная цель для меня: написать игру, в которой можно управлять персонажем с помощью обычного телефона, в том числе городского.

Поэтому вместо домовёнка Кузи у нас будет ничуть не менее обаятельный, собственноручно нарисованный кот по имени Кузьма. Который решил сбежать от бабы Яги в лучший мир на остров котов, где все котики живут в любви и сытости.

На самом деле игра будет выглядеть так.
На самом деле игра будет выглядеть так.

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

В целом план такой:

  1. Написать простой веб-сервис для управления персонажем через запросы к API.

  2. Подготовить сценарий для VoiceBox, который будет по нажатию клавиш в тональном режиме, вызывать API и передавать в него команду для движения персонажа вверх или вниз.

  3. Написать мини-игру на движке Godot 4 и собрать всё вместе.

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

Схема взаимодействия:

Схема взаимодействия. Игру рассмотрим в следующей статье.
Схема взаимодействия. Игру рассмотрим в следующей статье.

Как видите схема достаточно простая.  Поэтому предлагаю перейти ближе к делу.

Веб-сервис

Как вы уже поняли из схемы, VoiceBox и наша игра будут общаться через веб-сервис.

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

Материалы для сервиса можно взять в GitHub.

Сервис состоит из следующих компонентов:

  • command.php — скрипт, в котором реализованы GET и POST методы API

  • control.db — база данных SQLite3

  • log.txt — файл лога запросов к API создаётся после первого запроса к нему.

База данных

Состоит всего из одной таблицы commands:

Описание единственной таблицы БД
Описание единственной таблицы БД

Поля таблицы:

  • id — идентификатор записи 

  • key — команда up / down

  • phone — телефон, с которого пришел запрос к API, нужно чтобы определить звонящего.

  • timestamp — используем для поиска последнего запроса

  • is_readed — флаг того, что команда прочитана.

Я оставил данный набор полей на случай, если вы решите доработать логику. В принципе если поставить задачу вырезать всё лишнее, то нам нужны только столбцы phone, key, is_readed.

Скрипт command.php

Простейший скрипт на языке PHP, будет работать практически на любом хостинге.

Полный листинг скрипта под спойлером:

Код API
<?php

// Heaers
header("Access-Control-Allow-Orgin: *");
header("Access-Control-Allow-Methods: *");
header("Content-Type: application/json");

// Get parameters
$method = $_SERVER['REQUEST_METHOD'];
$phone = $_REQUEST["phone"];
$command = $_REQUEST["command"];

$dt = date('c', time()); // get currtent time and date

// write request in log
$fw = fopen("log.txt", "a+");
fwrite($fw, $phone." ".$command." ".$dt."\r\n");
fclose($fw);

//connect to DB
$db = new SQLite3('control.db');


switch ($method) {
 // GET method   
    case "GET":

        // get last key command
        $sql = "SELECT `key`   FROM commands WHERE `phone` = '$phone' AND `is_readed`=0  ORDER BY `timestamp` DESC";
        $result_key = $db->querySingle($sql);
        // set other not not processed to processed
        // (in this simple case we use only last not processed command, but you can improve it)
        $sql = "UPDATE commands SET `is_readed`=1  WHERE `phone` = '$phone' AND `is_readed`=0   ";
        $result = $db->querySingle($sql, true);
        
        // set API response
        $response = array('key' => $result_key, 'phone'=> $phone  );

        break;
 // POST method   
    case "POST":
        // add new not processed command in DB
        $sql = "INSERT INTO commands (`key`,`phone`, `timestamp`, `is_readed`) VALUES('$command','$phone','$dt',0)";
        $result = $db->querySingle($sql);
        // set API response
        $response = array('key' => $command, 'phone'=> $phone );
        break;
    default:
        echo '{"error":"unknown method"}';
        break;
}

//return response
echo json_encode($response);

Поскольку код примитивный я не буду его отдельно разбирать.

Просто опишу методы API.

Метод [GET] command.php

Получает последнюю команду для перемещения персонажа и помечает её прочитанной.

Request Query params:

  • phone (string) — номер телефона, с помощью которого управляем игрой (должен совпадать с тем, что будет прописан в конфигах игры).

Response

  • key (string) — команда для перемещения персонажа (up / down);

  • phone (string) — номер телефона, с помощью которого управляем игрой.

Example:

Пример успешного запроса (код 200 ОК)

Запрос:

URL: {BaseUrl}/command.php?phone=79001112233

Ответ:

Если есть необработанная команда:

{
   "key": "down",
   "phone": "79001112233"
}

Если нет необработанной команды:

{
   "key": null,
   "phone": "779001112233"
}

Алгоритм:

  • получаем телефон из запроса;

  • ищем в БД все необработанные записи, у которых совпадает телефон и флаг is_readed = 0;

  • возвращаем самую последнюю по времени запись;

  • всем остальным проставляем  is_readed = 1, чтобы больше не мешались.

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

Метод [POST] command.php

Записывает новую команду для перемещения персонажа.

Request Query params:

  • phone (string) — номер телефона, с помощью которого управляем игрой;

  • command (string) — up / down команда на перемещение персонажа.

Параметры лучше передать в теле запроса, но я не хотел усложнять код.

Response

  • key (string) — команда для перемещения персонажа (up / down);

  • phone (string) — номер телефона, с помощью которого управляем игрой. 

Example:

Пример успешного запроса (код 200 ОК).

Запрос:

{BaseUrl}/command.php?phone=79001112233&command=up

Ответ:

{
   "key": "up",
   "phone": "79001112233"
}

Проверить сервис можно с помощью cURL или Postman, не обязательно сразу подключаться к VoiceBox.

Алгоритм:

  • получаем телефон и команду из запроса:

  • записываем их в БД с флагом is_readed = 0.

Логи - log.txt

Чтобы убедится, что сервис работает мы пишем логи.

Я пишу в логи телефон, команду, и дату+время запроса.

Пример лога:

79001112233 up 2023-06-25T01:01:00+03:00  // POST
79001112233  2023-06-25T01:01:00+03:00  //GET

Сценарий VoiceBox

Я уже как-то писал туториал по работе со сценариями VoiceBox.

Кстати, с тех пор у сервиса появилась вполне себе достойная документация, поэтому в этой статье объяснять буду кратко и только то, что необходимо.

Для тех, кто не помнит: VoiceBox — это голосовой бот от компании МТТ. Бот может выполнять разные функции, от простого интерактивного меню, до сложных сценариев с голосовым управлением. Бот может как принимать так и совершать вызовы.

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

Алгоритм действий предельно прост.

  • обработать входящий вызов;

  • поприветствовать пользователя;

  • включить интерактивное меню — обработать нажатие клавиши на телефоне;

  • Если нажата клавиша 2 или 8 отправить HTTPS запрос на метод  [POST] command.php;

    • если нажата 2  параметр command = up;

    • если нажата 8  параметр command = down;

  • после исчерпания лимита выбора команд в меню попрощаться с пользователем;

  • завершить вызов.

Если вы внимательно читали алгоритм, то заметили упоминание о лимите команд. Он действительно есть. 

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

Поэтому мне пришлось выкручиваться. Я решил что 640 КБ на самом деле хватит всем 12 раз ввести команду вверх или вниз, достаточно, чтобы пройти игру. Так что я просто руками повторил 4 блока с интерактивным меню. В крайнем случае всегда можно перезвонить еще раз.

Теперь вы готовы увидеть простой и одновременно запутанный алгоритм сценария, под название voicebox-godot.

Схема сценария voicebox-godot
Схема сценария voicebox-godot

Если не обращать внимания на вереницу связей, то все достаточно просто.

Давайте пробежимся по основным блокам:

Входящий сценарий

Определяет основные параметры вызова. Для меня было важно, что вызов входящий (то есть мы звоним работу).

Голос Филиппа мне показался вполне подходящим для этой игры. Хотя можно выбрать женский или другой мужской голос.

Из этого блока мы переходим в «Приветствие»

Приветствие

Это блок «Проигрыватель». В нашем случае он озвучивает записанный текст.

По совету Н. я решил процитировать знаменитый фильм.

Далее переходим в блок «Интерактивное меню»

Ввод данных (1 - 4)

Все блоки данных в целом одинаковые.

В блоках обрабатывается по 3 запроса на ввод.

Если нажата «2», переходим в блок «Send Up».

Если нажата «8», переходим в блок «Send Down».

Для блоков 1-3 после исчерпания лимита настроен переход в блок «Ввод данных» под следующим порядковым номером.

«Ввод данных 4» после исчерпания лимита посещений идёт в блок «Перезвоните».

Send Up / Send Down

Блоки для отправки https запроса к API. По сути одинаковые, различаются только параметром command (up / down).

Стоит обратить внимание на подстановку переменной {{number_a}}.

Это встроенная переменная, в которую записывается номер абонента звонящего голосовому боту. Вы можете нажать кнопку «проверить» и отправить пробный запрос.

В таком случае вам придется вручную ввести значение переменной {{number_a}}в модальном окне. Это логично, потому что в данном случае голосовому боту никто не звонит.

В случае успеха мы возвращаемся на самый первый  «Ввод данных». Если там исчерпан лимит посещений, мы спустимся по цепочке.

В случае ошибки, завершаем вызов.

Перезвоните

Тоже проигрыватель. Произносит текст: «Перезвоните».

Конец

Блок отбоя вызова. У него нет параметров.

После создания сценария, не забудьте его сохранить.

Далее необходимо перейти в раздел кампаний и создать новую кампанию, к которой будет привязан наш сценарий.

Введите название, установите тип «Входящие звонки» выберите сценарий, выберите номер, на который будем звонить (его может быть необходимо приобрести).

Подключаем сценарий к новой кампании
Подключаем сценарий к новой кампании

Ну вот и всё, план на сегодня выполнен.

Осталось присоединить все что мы сегодня сделали к игре. Но это мы сделаем в следующий раз. 

Во второй части статьи мы с вами создадим мини-игру на движке Godot 4, затем подключим к ней веб-сервис и сценарий VoiceBox.

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

Подписывайтесь, чтобы не пропустить вторую часть материала.

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


  1. NutsUnderline
    11.07.2023 05:40
    -1

    На Демодуляции предлагали поиграть именно в Позвоните Кузе https://habr.com/ru/articles/593179/


    1. BosonBeard Автор
      11.07.2023 05:40
      +5

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


      1. NutsUnderline
        11.07.2023 05:40
        -2

        Я к тому что можно было бы там насладиться ретро, и возможно не изобретать велосипед :) Но и к тому что есть определенное количество ностальгирующих по этой штуке


        1. BosonBeard Автор
          11.07.2023 05:40
          +3

          Можно все что угодно пока мы живы)


  1. prishol
    11.07.2023 05:40
    +3

    Хорошая адаптация под кота Кузьму:) Позабавила отсылка из "Москва слезам не верит"


  1. HardWrMan
    11.07.2023 05:40
    +2

    Однако, пересмотрев пару роликов в более осознанном возрасте, я стал подозревать, что скорее всего проблемы крылись в оборудовании и телефонных линиях.

    Там помимо озвученной проблемы добавлялся ощутимый лаг на 1-2 секунды (в зависимости от региона звонящего) и, собственно, сама подстава. Есть ролики, где отчётливо слышно, что звонящий нажимает кнопку вовремя, DTMF декодирование показывает, что кнопка правильная, но она "почему-то" не срабатывает.


    1. telecomgod
      11.07.2023 05:40
      +1

      Везде обман, ужас :)