Статьи

Дротик и PHP: Наследие животных

Когда я изучал программирование на Apple II с использованием BASIC, была игра «Угадай животное». Эта игра была очень примитивной ИИ-игрой: компьютер пытается задать несколько вопросов ДА / НЕТ и получает ответ от пользователя. Основываясь на ответе, он может задавать больше вопросов Y / N, пока не попытается угадать животное.

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

Полный код был загружен на Github. Вы можете клонировать это отсюда .

Настройка базы данных

Структура базы данных для этой программы проста. Нам нужен только один стол:

CREATE TABLE `animal` ( `id` int(11) NOT NULL, `question` varchar(140) DEFAULT NULL, `y_branch` int(11) DEFAULT NULL, `n_branch` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) 

id используется для идентификации каждого вопроса / предположения; question — это вопрос, который нужно задать, или предположение, которое будет предложено; y_branch и n_branch идентифицируют идентификатор вопроса, когда пользователь отвечает Да или Нет на вопрос. В частности, если оба эти поля имеют значение «-1», это означает, что больше не нужно задавать вопросы (и программа достигла стадии предположения).

Структура SQL и исходные данные (один вопрос и два животных) можно найти в файле animal.sql .

Backend

Поскольку бэкэнд относительно прост, я буду использовать простой PHP (с PDO). Файлы находятся в каталоге server в хранилище. Сервер в основном имеет две функции:

  1. Получить вопрос или подсказку по идентификатору;
  2. Разделить узел новыми вопросами и новыми догадками с помощью пользовательского ввода;

Мы рассмотрим функцию get question:

  <?php require_once 'pdo.php'; $id=filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT); if(!$id) $id=1; $res=$cn->prepare('select * from animal where id = :id'); $res->bindParam(':id', $id); $res->execute(); $r=$res->fetch(); $ret=array(); $ret['q']=$r[1]; $ret['y']=$r[2]; $ret['n']=$r[3]; setExtraHeader(); echo json_encode($ret); ?> 

В этом файле get.php мы включили файл pdo.php для настройки соединения с базой данных. Затем мы обрабатываем ввод и делаем запрос. Наконец, мы выводим результат на внешний интерфейс (в данном случае приложение Dart).

Несколько вещей, чтобы заметить здесь:

  1. Все результаты, возвращаемые приложению Dart, должны быть в формате JSON. Таким образом, мы используем функцию json_encode для кодирования массива.
  2. Прежде чем мы действительно вернем результат, мы установили несколько дополнительных заголовков HTTP, чтобы включить CORS . Хотя все наши файлы «физически» находятся на одном компьютере, приложение Dart и серверная часть фактически работают в двух разных доменах. Без дополнительных заголовков вызов из внешнего интерфейса в внутренний не удастся. Функция setExtraHeader также определена в pdo.php .

Внешний интерфейс

Веб-программирование переднего плана было очень облегчено (или усложнено?) Через HTML5, JavaScript и другие сторонние библиотеки. Это просто должно быть намного более структурированным.

В этом уроке мы будем использовать Google Dart в качестве инструмента для разработки интерфейса.

Установка

Чтобы получить Dart IDE, пожалуйста, посетите https://www.dartlang.org и загрузите пакет для вашей платформы. Установка проста. В качестве альтернативы загрузите Webstorm, который включает в себя встроенную поддержку Dart и гораздо более стабильный и производительный, чем редактор Dart на основе Eclipse.

Dart только что выпустил стабильную версию и снял свою длинную шляпу «BETA», но она быстро развивается. На момент написания статьи я использовал Dart Editor и SDK версии 1.0.0_r30188 (STABLE).

Чтобы полностью использовать интерактивность, которую предлагает Dart, мы будем использовать новую библиотеку Polymer.

Примечание: Polymer заменяет библиотеку web_ui в старых версиях Dart. Как и Дарт, Полимер также быстро развивается. В этой программе я использовал версию 0.9.0 + 1. Некоторые синтаксис и функции могут отличаться в будущих версиях.

Polymer предлагает некоторые полезные функции при разработке внешнего интерфейса, такие как пользовательские элементы HTML, двунаправленное связывание данных, условные шаблоны, асинхронные вызовы удаленных функций и т. Д. Все эти функции будут использоваться в этой программе.

Создать приложение Polymer

Запустите Dart IDE и выберите «Файл | Новое приложение». Обязательно выберите «Веб-приложение (использующее библиотеку полимера)» в качестве типа приложения.

Мастер создаст каталог приложения и настроит все необходимые зависимости. Поскольку мы выбрали «Создать образец контента», он также создаст несколько файлов примеров. Мы можем удалить все эти примеры файлов, кроме pubspec.yaml .

Щелкните правой кнопкой pubspec.yaml файл pubspec.yaml и выберите Pub Get из меню. Это поможет установить все необходимые библиотеки для приложения Dart / Polymer.

Типичное приложение Polymer содержит как минимум 3 файла:

  1. HTML-файл в качестве точки входа в приложение. В этом случае: web/animalguess.html . В этом файле обычно мы устанавливаем базовую структуру для файла HTML и ДОЛЖНЫ создавать экземпляр пользовательского элемента HTML.
  2. Файл HTML, который определяет пользовательский элемент HTML, макет, скрипт для этого элемента и т. Д. В этом случае: web/animalguessclass.html .
  3. Файл DART, который реализует функциональность для этого пользовательского элемента HTML.

Давайте обсудим ключевые моменты каждого файла.

animalguess.html

Файл animalguess.html определяет общий макет приложения. Это HTML5-совместимый файл со всеми обычными элементами HEAD, TITLE, LINK, SCRIPT, META, а также с пользовательским тегом HTML-элемента.

  <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Welcome to Animal Guess Game!</title> <link rel="stylesheet" href="css/bootstrap.css"> <link rel="stylesheet" href="css/animal.css"> <!-- import the underlying class --> <link rel="import" href="animalguessclass.html"> <script type="application/dart">import 'package:polymer/init.dart';</script> <script src="packages/browser/dart.js"></script> </head> <body> <div class="container"> <h1>Welcome to the legacy Animal Guess Game!</h1> <p><em>Revitalized with PHP and Dart!</em></p> </div> <hr> <animal-guess></animal-guess> </body> </html> 

Большую часть раздела <head></head> нам действительно не нужно ничего менять. Для этого приложения я изменил только две CSS-ссылки, чтобы они ссылались на Bootstrap CSS и мои дальнейшие настройки CSS.

В разделе BODY мы включили пользовательский HTML-элемент <animal-guess> . Этот элемент определен в animalguessclass.html и импортирован с помощью animalguessclass.html <link rel="import" href="animalguessclass.html"> .

animalguessclass.html и пользовательский элемент

Этот файл определяет макет, шаблон, поведение пользовательского элемента HTML. Однако фактический код для реализации поведения обычно определяется в отдельном файле DART ( animalguessclass.dart ).

  <polymer-element name="animal-guess"> <template> <div class="container"> <template if="{{!gameinprogress}}"> <h3>Let's get started!</h3> <button on-click="{{newGame}}">Click me</button> </template> ... <template if="{{gameinprogress}}"> <div class="row"> <div class="col-md-6">{{qid}}. {{question}}</div> <template if="{{!reachedend}}"> <div class="col-md-6"> <a href="#" on-click="{{YBranch}}">Yes</a>&nbsp;&nbsp;<a href="#" on-click="{{NBranch}}">No</a> </div> </template> </div> </template> ... </template> <script type="application/dart" src="animalguessclass.dart"></script> </polymer-element> 

Вышеприведенная выдержка показывает фундаментальную структуру файла HTML для элемента Polymer.

<polymer-element name="animal-guess"></polymer-element> должны быть представлены для определения элемента. Пожалуйста, обратите внимание на атрибут name . Он имеет то же значение, которое мы используем в animalguess.html ( "animal-guess" ).

Есть условные экземпляры шаблонов. Например:

  <template if="{{!gameinprogress}}"> <h3>Let's get started!</h3> <button on-click="{{newGame}}">Click me</button> </template> 

HTML-код между <template></template> не будет отображаться, если gameinprocess имеет значения false. gameinprogress — это переменная, которая будет разработана позже.

Также обратите внимание, что мы подключили событие click элемента button к обработчику события ( "newgame" ). Мы также обсудим это позже.

Вообще говоря, этот HTML-файл ничем не отличается от традиционного HTML-файла или HTML-шаблона. Мы можем использовать все виды HTML-элементов в этом файле.

Примечание: радио кнопки могут быть использованы. Но есть некоторые вопросы, связанные с привязкой стоимости. Таким образом, в этой реализации мы используем только текстовые поля для ввода. Могут быть проблемы, связанные с привязкой данных для других типов элементов управления формы, но мы не рассматриваем эту тему здесь.

Также в этом файле мы объявили, что будем использовать animalguessclass.dart в качестве сценария для этого элемента.

Полный код animalguessclass.html можно найти в web каталоге.

animalguessclass.dart

Этот файл является драйвером для этого приложения. Он имеет всю логику, которая управляет поведением программы. Давайте посмотрим на некоторые ключевые разделы.

 import 'package:polymer/polymer.dart'; import 'dart:html'; import 'dart:convert'; @CustomTag('animal-guess') class AnimalGuess extends PolymerElement { @published bool gameinprogress=false; @published String question=''; @published String myguess=''; @published int qid=1; int yBranch; int nBranch; ... AnimalGuess.created() : super.created() { // The below 2 lines make sure the Bootstrap CSS will be applied var root = getShadowRoot("animal-guess"); root.applyAuthorStyles = true; } void newGame() { gameinprogress=true; win=false; lost=false; reachedend=false; qid=1; getQuestionById(qid); } void getQuestionById(qid) { var path='http://animal/get.php?id=$qid'; var req=new HttpRequest(); req..open('GET', path) ..onLoadEnd.listen((e)=>requestComplete(req)) ..send(''); } void requestComplete(HttpRequest req) { if (req.status==200) { Map res=JSON.decode(req.responseText); myguess=res['q']; yBranch=res['y']; nBranch=res['n']; if (yBranch==-1 && nBranch==-1) // No more branches and we have reached the "guess" { question='Is it a/an $myguess?'; } else { question=myguess; } } } } 

Первые 3 оператора импорта импортируют необходимые библиотеки, используемые в этом сценарии. При использовании Polymer и DOM требуются первые два, а при декодировании JSON нам также необходим третий. Чтобы увидеть другие пакеты и библиотеки, см. Ссылку на API и репозиторий пакетов .

@CustomTag('animal-guess') определяет пользовательский тег, который мы будем использовать. Он имеет то же имя, что и в animalguess.html и animalguessclass.html .

В определении класса мы видим несколько объявлений переменных. Polymer использует @published слово @published для объявления «публичной» переменной (например, флага gameinprogress который указывает, gameinprogress ли игра и используется ли для определения, какой шаблон показывать), и он будет доступен в скрипте, а также в связанном html-файле ( animalguessclass.html ). Тем самым мы создали «двунаправленную» привязку данных.

Остальные — объявления функций. Большинство функций будут «обработчиками событий» для событий «по on-click » в вышеупомянутом animalguess.html . Другие типы обработчиков событий также доступны.

Несколько примечаний:

  • В конструкторе класса мы делаем трюк, чтобы убедиться, что Bootstrap CSS может быть применен к нашему пользовательскому тегу HTML (« animal-guess »). Проблема здесь разработана в этой статье от Stackoverflow . По сути, Bootstrap «не знает о ShadowDOM и пытается извлечь узлы из DOM с помощью глобальных селекторов». Но в Polymer мы почти обязаны использовать пользовательский элемент, и Shadow DOM существует. Таким образом, «поворот» заключается в том, чтобы убедиться, что созданный нами ShadowDOM будет работать с Bootstrap и иметь нужные нам стили CSS.
  • Функция обратного вызова ( requestComplete ) подключается к объекту HttpRequest. Используемый синтаксис является новым в Polymer и называется «сцепленным» вызовом метода. Он отличается от обозначения одной точки и использует две точки. Это эквивалентно следующим 3 утверждениям:
 req.open(...); req.onLoadEnd(...)...; req.send(...); 
  • В функции requestComplete мы сначала проверяем код состояния HTTP (200 означает, что все в порядке), а затем используем переменную типа Map для хранения декодированного объекта JSON. Эта переменная будет иметь точную пару «ключ-значение» в качестве результата JSON, который возвращается с нашего удаленного сервера. В нашем случае внутренний «удаленный» сервер находится на той же машине (работает на 80 порте), а приложение при запуске в Dart будет на порте 3030. Таким образом, в этом смысле они находятся в двух разных доменах, и заголовки CORS должны быть представлены в возвращенном HTTP-ответе.

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

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

Развернуть, чтобы быть автономным приложением

Текущая программа может быть запущена только в собственном браузере Dart (сильно настроенный браузер на основе Chrome, который поддерживает интерпретатор Dart — вы автоматически загружаете его при загрузке Dart SDK). Чтобы сделать приложение автономным, мы можем скомпилировать приложение Dart в JavaScript.

Для этого щелкните файл « build.dart » в корневом каталоге проекта и выберите « Tools | Pub Build . После некоторой обработки в корневом каталоге проекта появится новый каталог « build », содержащий все файлы, необходимые для запуска. это как отдельное приложение. Мы можем просто скопировать все эти файлы на сайт, и он будет запущен.

Вывод

В этом уроке мы повторно оживили унаследованную игру Guess Animal AI с использованием современных технологий: базы данных, Dart и PHP. Целью данного руководства было продемонстрировать бесшовную интеграцию всех частей и сделать полезное многофункциональное веб-приложение очень структурированным способом.

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