Когда я изучал программирование на 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
в хранилище. Сервер в основном имеет две функции:
- Получить вопрос или подсказку по идентификатору;
- Разделить узел новыми вопросами и новыми догадками с помощью пользовательского ввода;
Мы рассмотрим функцию 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).
Несколько вещей, чтобы заметить здесь:
- Все результаты, возвращаемые приложению Dart, должны быть в формате JSON. Таким образом, мы используем функцию
json_encode
для кодирования массива. - Прежде чем мы действительно вернем результат, мы установили несколько дополнительных заголовков 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 файла:
- HTML-файл в качестве точки входа в приложение. В этом случае:
web/animalguess.html
. В этом файле обычно мы устанавливаем базовую структуру для файла HTML и ДОЛЖНЫ создавать экземпляр пользовательского элемента HTML. - Файл HTML, который определяет пользовательский элемент HTML, макет, скрипт для этого элемента и т. Д. В этом случае:
web/animalguessclass.html
. - Файл 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> <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. Целью данного руководства было продемонстрировать бесшовную интеграцию всех частей и сделать полезное многофункциональное веб-приложение очень структурированным способом.
Если у вас есть какие-либо отзывы или вопросы, пожалуйста, оставьте их в комментариях ниже, и я быстро на них отвечу. Если вы нашли эту статью интересной и хотели бы расширить ее, пожалуйста, поделитесь ею со своими друзьями и коллегами, чтобы мы могли оценить интерес и планировать соответственно.