Dart和PHP:传统动物猜谜游戏

Back when I was learning programming on Apple II using BASIC, there was an Animal Guess Game. This game was a very primitive AI game: the computer tries to ask a few YES/NO questions and receives the answer from the user. Based on the answer, it may ask more Y/N questions until it tries to guess the animal.

回到我使用BASIC在Apple II上学习编程时,有一个Animal Guess游戏。 该游戏是一个非常原始的AI游戏:计算机尝试询问一些是/否问题,并从用户那里收到答案。 根据答案,它可能会问更多的是/否问题,直到尝试猜测动物为止。

In this tutorial, we will learn how to re-vitalize this program using PHP as backend and Dart as frontend. Of course, a database will be used to store all the questions and guesses of animals.

在本教程中,我们将学习如何使用PHP作为后端以及Dart作为前端来重新激活该程序。 当然,将使用数据库来存储所有有关动物的问题和猜测。

The complete code has been uploaded to Github. You can clone it from here.

完整的代码已上传到Github。 您可以从这里克隆它。

数据库设置 (Database Setup)

The database structure for this program is simple. We only need one table:

该程序的数据库结构很简单。 我们只需要一张桌子:

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 is used to identify each question/guess; question is the question to be asked or the guess to be prompted; y_branch and n_branch identifies the question id when the user answers Yes or No to the question. In particular, if both these two fields are "-1", it means there are no more questions to be asked (and the program has reached the guess stage).

id用于识别每个问题/猜测; question是要问的问题或要提示的猜测; 当用户对问题回答是或否时, y_branchn_branch标识问题ID。 特别是,如果这两个字段都为“ -1”,则意味着不再有其他问题要问(程序已经进入猜测阶段)。

The SQL structure and the initial data (one question and two animals) can be found in the animal.sql file.

SQL结构和初始数据(一个问题和两个动物)可以在animal.sql文件中找到。

后端 (Backend)

As the backend is relatively simple, I will use plain PHP (with PDO). The files are located under the server directory in the repository. The server basically has two functions:

由于后端相对简单,因此我将使用纯PHP(带有PDO)。 这些文件位于存储库中的server目录下。 服务器基本上具有两个功能:

  1. Get a question or prompts a guess by ID;

    通过ID提出问题或提示猜测;
  2. Split a node with new questions and new guesses with user's input;

    用用户的输入拆分一个带有新问题和新猜测的节点;

We will take a look at the get question function:

我们将看一下getquest函数:

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);
    ?>

In this get.php file, we included a pdo.php file to setup the database connection. Then we process the input and do the query. Finally, we output the result to the frontend (the Dart app in this case).

在此get.php文件中,我们包括了一个pdo.php文件来设置数据库连接。 然后我们处理输入并进行查询。 最后,我们将结果输出到前端(在本例中为Dart应用)。

A few things to notice here:

这里需要注意的几件事:

  1. All results returned to Dart app should be in JSON format. Thus, we use the json_encode function to encode the array.

    返回到Dart应用程序的所有结果均应为JSON格式。 因此,我们使用json_encode函数对数组进行编码。

  2. Before we actually return the result, we set some extra HTTP headers to enable CORS. Although all our files are "physically" on the same machine, the Dart app and the back end are actually running on two different domains. Without the extra headers, the call from frontend to backend will fail. The setExtraHeader function is also defined in pdo.php.

    在实际返回结果之前,我们设置了一些额外的HTTP标头以启用CORS 。 尽管我们所有文件实际上都是在同一台计算机上,但Dart应用程序和后端实际上是在两个不同的域上运行。 如果没有额外的头,则从前端到后端的调用将失败。 setExtraHeader函数也在pdo.php定义。

前端 (Frontend)

Front end web programming has been very much facilitated (or complicated?) through HTML5, JavaScript and other 3rd-party libraries. It just needs to be a lot more structured.

通过HTML5,JavaScript和其他第三方库,非常方便(或复杂?)前端Web编程。 它只是需要更加结构化。

In this tutorial, we will use Google's Dart as the tool for front end development.

在本教程中,我们将使用Google的Dart作为前端开发工具。

安装 (Installation)

To get the Dart IDE, please visit https://www.dartlang.org and download the package for your platform. The installation is straightforward. Alternatively, download Webstorm which includes native Dart support and is much more stable and performant than the Eclipse based Dart Editor.

要获取Dart IDE,请访问 https://www.dartlang.org并下载适用于您平台的软件包。 安装非常简单。 或者, 下载Webstorm ,它包含Dart原生支持,并且比基于Eclipse的Dart Editor更稳定,性能更高。

Dart has just released its stable version and taken off its long-wearing "BETA" hat, but it is evolving rapidly. At the time of writing, I am using Dart Editor and SDK version 1.0.0_r30188 (STABLE).

Dart刚刚发布了稳定的版本,并摘下了老旧的“ BETA”帽子,但是它发展Swift。 在撰写本文时,我正在使用Dart编辑器和SDK版本1.0.0_r30188(STABLE)。

To fully utilize the interactivity that Dart offers, we will use the new Polymer library.

为了充分利用Dart提供的交互性,我们将使用新的Polymer库。

Note: Polymer replaces the web_ui library in older Dart releases. Like Dart, Polymer is also rapidly evolving. The one I used in this program is version 0.9.0+1. Some of the syntax and features may be different in future versions.

注意: Polymer取代了旧版Dart中的web_ui库。 像Dart一样,Polymer也正在Swift发展。 我在该程序中使用的是0.9.0 + 1版。 某些语法和功能在将来的版本中可能会有所不同。

Polymer offers some useful features when developing the front end like custom HTML elements, bi-directional data-binding, conditional templates, asynchronous remote function calls, etc. All these features will be used in this program.

在开发前端时,Polymer提供了一些有用的功能,例如自定义HTML元素,双向数据绑定,条件模板,异步远程函数调用等。所有这些功能都将在此程序中使用。

创建聚合物应用程序 (Create a Polymer application)

Start Dart IDE, and select "File|New Application". Be sure to choose "Web application (using the polymer library)" as the application type.

启动Dart IDE,然后选择“文件|新应用程序”。 确保选择“ Web应用程序(使用聚合物库)”作为应用程序类型。

The wizard will create the app directory and set up all necessary dependencies. As we chose to "Generate sample content", it will also create a few sample files. We can delete all these sample files except pubspec.yaml.

该向导将创建应用程序目录并设置所有必要的依赖项。 当我们选择“生成示例内容”时,它还将创建一些示例文件。 我们可以删除除pubspec.yaml之外的所有这些示例文件。

Right click on the pubspec.yaml file and choose Pub Get from the menu. This will help install all the necessary libraries for a Dart/Polymer app.

右键单击pubspec.yaml文件,然后从菜单中选择Pub Get 。 这将有助于安装Dart / Polymer应用程序的所有必需库。

A typical Polymer app contains at least 3 files:

一个典型的Polymer应用程序至少包含3个文件:

  1. An HTML file as the app entry point. In this case: web/animalguess.html. In this file, normally we will setup the basic structure for an HTML file, and MUST instantiate a custom HTML element.

    一个HTML文件作为应用程序入口点。 在这种情况下: web/animalguess.html 。 在此文件中,通常我们将为HTML文件设置基本结构,并必须实例化自定义HTML元素。

  2. An HTML file that defines a custom HTML element, the layout, the script for that element, etc. In this case: web/animalguessclass.html.

    一个HTML文件,用于定义自定义HTML元素,布局,该元素的脚本等。在这种情况下: web/animalguessclass.html

  3. A DART file that implements the functionality for that custom HTML element.

    实现该自定义HTML元素功能的DART文件。

Let's discuss the key points of each file.

让我们讨论每个文件的关键点。

animalguess.html (animalguess.html)

animalguess.html file defines the overall layout of the app. It is an HTML5 compliant file with all the regular HEAD, TITLE, LINK, SCRIPT, META elements, along with a custom HTML element tag.

animalguess.html文件定义了应用程序的整体布局。 它是符合HTML5的文件,其中包含所有常规的HEAD,TITLE,LINK,SCRIPT,META元素,以及自定义HTML元素标签。



    
    
    
    Welcome to Animal Guess Game!
    
    

    
    
    
    
    
    
          

Welcome to the legacy Animal Guess Game!

Revitalized with PHP and Dart!


For the most part of the section, we really don't need to change anything. For this app, I only changed the two CSS links to link to Bootstrap CSS and my further customized CSS.

部分的大部分时间里,我们真的不需要更改任何内容。 对于此应用程序,我仅更改了两个CSS链接以链接到Bootstrap CSS和我进一步定制CSS。

In the BODY section, we have included the custom HTML element . This element is defined in animalguessclass.html and imported via the statement.

在BODY部分中,我们包括了自定义HTML元素 。 该元素在animalguessclass.html定义,并通过语句

animalguessclass.html和自定义元素 (animalguessclass.html and the custom element)

This file defines the layout, template, behavior of the custom HTML element. However, the actual code to implement the behavior is usually defined in a separate DART file (animalguessclass.dart).

此文件定义自定义HTML元素的布局,模板,行为。 但是,用于实现该行为的实际代码通常是在单独的DART文件( animalguessclass.dart )中定义的。

 
        
        
    

The above excerpt shows the fundamental structure of an HTML file for a Polymer element.

上面的摘录显示了Polymer元素HTML文件的基本结构。

must be presented to define an element. Please note the name attribute. It has the same value we use in animalguess.html ("animal-guess").

来定义元素。 请注意name属性。 它具有与animalguess.html ( "animal-guess" )中使用的值相同的值。

There are conditional template instantiations. For example:

有条件模板实例化。 例如:

The HTML code between won't be rendered unless gameinprocess is false. gameinprogress is a variable that will be elaborated later.

除非gameinprocess为false,否则不会呈现之间HTML代码。 gameinprogress是一个变量,稍后将进行阐述。

Also, note that we have hooked a button element's click event to an event handler ("newgame"). We will also discuss this later.

另外,请注意,我们已经将按钮元素的click事件挂接到了事件处理程序( "newgame" )上。 我们稍后还将讨论。

Generally speaking, this HTML file isn't that different from a traditional HTML file, or an HTML template. We can use all kinds of HTML elements in this file.

一般来说,此HTML文件与传统HTML文件或HTML模板没有什么不同。 我们可以在此文件中使用各种HTML元素。

Note: Radio buttons can be used. But there are some issues related to the binding of the value. So in this implementation, we only use text boxes for input. There may be issues related to data binding for other types of form controls, but we are not covering this topic here.

注意:可以使用单选按钮。 但是,还有一些与值的绑定有关的问题。 因此,在此实现中,我们仅使用文本框进行输入。 可能存在与其他类型的表单控件的数据绑定有关的问题,但是我们不在此讨论。

Also, in this file, we declared that we will use animalguessclass.dart as the script for this element.

另外,在此文件中,我们声明将使用animalguessclass.dart作为此元素的脚本。

The complete code for animalguessclass.html can be found in the web directory.

animalguessclass.html可以在web目录中找到animalguessclass.html的完整代码。

动物类 (animalguessclass.dart)

This file is the driver for this app. It has all the logic that drives the behavior of the program. Let's take a look at some key sections.

此文件是此应用程序的驱动程序。 它具有驱动程序行为的所有逻辑。 让我们看一些关键部分。

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;
      }
    }
  }
}

The first 3 import statements import the necessary libraries used in this script. When using Polymer and DOM, the first two are required, and when decoding JSON we also need the third. To see other packages and libraries, see the API reference and the package repository.

前3个import语句导入此脚本中使用的必要库。 使用Polymer和DOM时,前两个是必需的,而解码JSON时,我们也需要第三个。 要查看其他软件包和库,请参阅API参考和软件包存储库 。

@CustomTag('animal-guess') defines the custom tag we will be using. It has the same name as it appears in animalguess.html and animalguessclass.html.

@CustomTag('animal-guess')定义了我们将使用的自定义标签。 它具有与animalguess.htmlanimalguessclass.html出现的名称相同的名称。

In the class definition, we see a few variable declarations. Polymer uses @published keyword to declare a "public" variable (like gameinprogress flag that indicates if the game is started and is used to decide which template to show) and it will be accessible in the script as well as in the related html file (animalguessclass.html). By doing so, we have created "bi-directional" data binding.

在类定义中,我们看到一些变量声明。 Polymer使用@published关键字声明一个“公共”变量(例如gameinprogress标志,该标志指示游戏是否已启动并用于决定要显示哪个模板),并且可以在脚本以及相关的html文件中进行访问( animalguessclass.html )。 这样,我们创建了“双向”数据绑定。

The rest are the function declarations. Most of the functions will be "event-handlers" to the "on-click" events in the aforementioned animalguess.html. Other types of event handlers are also available.

其余的是函数声明。 大多数功能将是前面提到的animalguess.htmlon-click ”事件的“事件处理程序”。 其他类型的事件处理程序也可用。

A few things of note:

注意事项:

  • In the class constructor, we do a trick to make sure the Bootstrap CSS can be applied to our custom HTML tag ("animal-guess"). The issue here is elaborated in this article from Stackoverflow. Basically, Bootstrap "doesn't know about ShadowDOM and attempts to fetch nodes from the DOM using global selectors." But in Polymer, we are almost mandated to use a custom element and Shadow DOM is in existence. So the "turnaround" is just to make sure the ShadowDOM we created will work with Bootstrap and have the CSS styles we wanted.

    在类构造函数中,我们做了一个技巧,以确保可以将Bootstrap CSS应用于我们的自定义HTML标记(“ animal-guess ”)。 本文的问题在Stackoverflow的这篇文章中得到了阐述。 基本上,Bootstrap“不了解ShadowDOM,而是尝试使用全局选择器从DOM中获取节点。” 但是在Polymer中,几乎要强制我们使用自定义元素,并且存在Shadow DOM。 因此,“周转”只是为了确保我们创建的ShadowDOM可与Bootstrap配合使用并具有我们想要CSS样式。

  • The callback function (requestComplete) is hooked up to the HttpRequest object. The syntax used is new in Polymer and is called "chained" method calling. It is different from a single dot notation and uses two dots. It is equivalent to the below 3 statements:

    回调函数( requestComplete )连接到HttpRequest对象。 所使用的语法在Polymer中是新的,称为“链接”方法调用。 它与单点表示法不同,使用两个点。 它等效于以下3条语句:

req.open(...);
req.onLoadEnd(...)...;
req.send(...);
  • In the requestComplete function, we first test the HTTP status code (200 means everything is OK) and then use a Map type variable to store the decoded JSON object. That variable will have an exact "key-value" pair as the JSON result that is returned from our remote server. In our case, the back end "remote" server is on the same machine (running on 80 port) and the app, when launched in Dart, will be on 3030 port. So in this sense, they are in two different domains and CORS headers must be presented in the returned HTTP response.

    requestComplete函数中,我们首先测试HTTP状态代码(200表示一切正常),然后使用Map类型变量存储已解码的JSON对象。 该变量将具有一个精确的“键值”对,作为从我们的远程服务器返回的JSON结果。 在我们的案例中,后端“远程”服务器位于同一台计算机上(在80端口上运行),并且在Dart中启动时,该应用程序将在3030端口上运行。 因此,从这个意义上讲,它们位于两个不同的域中,并且必须在返回的HTTP响应中显示CORS标头。

Below is a screen shot when the computer exhausts its questions but makes a wrong guess. Then it prompts for a new question to distinguish its guess and the user's animal:

下面是计算机耗尽问题但做出错误猜测时的屏幕截图。 然后它提示输入一个新问题,以区分其猜测和用户的动物:

At this point, the app is already functional: back end to provide the data and the front end to provide the app logic and presentation. At least one improvement can be done: using radio button form controls to receive answers for the new question and restrain the user's input. I'll leave that up to you.

至此,该应用程序已经可以运行:后端提供数据,前端提供应用程序逻辑和表示。 至少可以完成一项改进:使用单选按钮表单控件来接收新问题的答案并限制用户的输入。 我留给你。

部署成为独立应用程序 (Deploy to be a standalone app)

The current program can only be run in Dart's own browser (a highly customized Chrome-based browser that supports Dart interpreter – you automatically download it when you download the Dart SDK). To make the app standalone, we can compile the Dart app into JavaScript.

当前程序只能在Dart自己的浏览器(运行高度自定义的基于Chrome的浏览器,支持Dart解释器-在下载Dart SDK时自动下载)中运行。 为了使应用程序独立,我们可以将Dart应用程序编译为JavaScript。

To do so, click on the "build.dart" file in project root, and select "Tools | Pub Build. After some processing, a new "build" directory will appear in the project root directory which contains all the files required to run it as a standalone app. We can simply copy all those files into a site and it will be up and running.

为此,请在项目根目录中单击“ build.dart ”文件,然后选择“ Tools | Pub Build 。经过一些处理,新的“ build ”目录将出现在项目根目录中,其中包含运行所需的所有文件它可以作为一个独立的应用程序。我们只需将所有这些文件复制到一个站点中,它就会启动并运行。

结论 (Conclusion)

In this tutorial, we re-vitalized a legacy Guess Animal AI Game using modern technologies: a database, Dart and PHP. The purpose of the tutorial was to demonstrate a seamless integration of all parts and make a useful rich web application in a very structured way.

在本教程中,我们使用现代技术(数据库,Dart和PHP)重新激活了旧版的Guess Animal AI游戏。 本教程的目的是演示所有部分的无缝集成,并以非常结构化的方式制作有用的富Web应用程序。

If you have some feedback or questions, please leave them in the comments below and I'll address them promptly. If you've found this article interesting and would like to see it expanded on, please share it with your friends and colleagues so we can gauge the interest and plan accordingly.

如果您有任何反馈或疑问,请在下面的评论中留下,我会尽快解决。 如果您觉得本文有趣,并希望扩展本文的范围,请与您的朋友和同事分享,以便我们评估您的兴趣并做出相应的计划。

翻译自: https://www.sitepoint.com/dart-php-legacy-animal-guess-game/

你可能感兴趣的:(数据库,python,java,web,javascript,ViewUI)