实现HI-LO猜游戏

2023-10-14 08:10
文章标签 实现 游戏 hi lo

本文主要是介绍实现HI-LO猜游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

让我们来创建一个基本的 Hi-Lo 猜游戏.

游戏中, 电脑选择一个1到10之间的数字。你需要点击链接来尝试猜这个谜底。最后,电脑告诉你,你需要多少次来猜对谜底。这个简单的例子会包含许多Tapestry中重要的概念:

  • 拆分应用到各自独立页面
  • 页面之间的信息传递
  • 响应用户操作
  • 在服务端保存客户端Session信息

我们将使用小模块迭代的方式来创建这个小应用,Tapestry使得开发很容易。

我们的页面很简单,有三个: Index (首页), Guess and GameOver. 首页是应用说明和开始游戏的链接。 Guess 页有10个供用户点击的链接,反馈信息如「太高」「太低」。 GameOver 页告诉用户使用了多少次数猜中谜底。

首页

让我们来编辑首页和模板. 修改 Index.tml 为:

Index.tml
< html t:type = "layout" title = "Hi/Lo Guess"
     xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_3.xsd" >
     < p >
         I'm thinking of a number between one and ten ...
     </ p >
     < p >
         < a href = "#" >start guessing</ a >
     </ p >
</ html >

编辑与之对应的 Index.java, 删除 body (可以选择保留import或不):

Index.java
package com.example.tutorial1.pages;
public class Index
{
}

运行应用:

然而, 点击链接并没有做任何操作,因为它只是一个 <a> 标签, 不是一个 Tapestry 组件.先让我们来想一下用户点击链接后,应该要做什么:

  • 电脑选中一个1到10之间的随机数
  • 选择次数初始化或重置为0
  • 链接用户到Guess页来开始游戏

第一步,识别用户点击 "start guessing". 在典型的Web应用框架中,也行我们会想到URL和处理器或XML配置文件。但这里是Tapestry,因此我们会在类中使用组件和方法。

首先, 使用组件. 在跳转到Guess页前,我们需要执行一个操作(选择数字). ActionLink 组件正是我们需要的:创建一个URL链接,触发类中一个操作...现在对我们来讲尚早。现在修改 <a> 标签 ActionLink 组件:

Index.tml (partial)
< p >
     < t:actionlink t:id = "start" >start guessing</ t:actionlink >
</ p >

如果你刷新浏览器,悬停鼠标在  "start guessing"上, 你会看到 /tutorial1/index.start, 包含页面的名字(index)和组件的id(start)。

如果点击链接,你会得到一个错误:

 

Tapestry试图告诉我们需要提供一个事件处理器。那会是什么?

事件处理器就是一个Java类具有特殊名字的方法。规则是 onEventnameFromComponent-id ... 这里我们需要一个方法 onActionFromStart(). 我们如何知道这个是正确的名字呢?因为这正是ActionLink做的事情,也是它命名为ActionLink的原因。

Tapestry 给了我们可选项,如果你不喜欢便捷命名,你可以使用 @OnEvent 注解来替换,该注解可以任意命名你的方法。详细方法可以在 Tapestry Users' Guide 查看. 在本教程中,我们使用便捷命名规范。

当处理组件请求时(诸如ActionLink组件触发), Tapestry 会找到组件,并触发组件事件。这回调用服务端的代码来设置用户的客户端操作。我们先来创建一个空的处理器:

Index.java
package com.example.tutorial1.pages;
public class Index
{
     void onActionFromStart()
     {
     }
}

在浏览器中,刷新页面,再试一下刚刚失败的组件请求...或者我们重启应用。总之,我们要在重新渲染页面后操作。

注意事件处理器方法没必要是 public; 可以是 protected, private, or 包内 private (本例中就是). 为了方便使用,这类的方法都是包内 private。

哼... 现在相信方法被触发了吧。还不够好....怎么才能快速的确保正确呢?我们可以使用抛出异常的方法,不过很丑。

我们使用 添加 @Log 注解的方式:

Index.java (partial)
import org.apache.tapestry5.annotations.Log;
. . .
     @Log
     void onActionFromStart()
     {
     }

当你点击链接时,你可以在 Eclipse 控制台看到以下信息输出:

[DEBUG] pages.Index [ENTER] onActionFromStart()
[DEBUG] pages.Index [ EXIT] onActionFromStart
[INFO] AppModule.TimingFilter Request time: 3 ms
[INFO] AppModule.TimingFilter Request time: 5 ms

@Log 注解 指导 Tapestry 来记录log和退出。在日志里,你会看到方法的传入的参数,返回值...... 当然还有方法内抛出的异常信息。这是一个强大的Debug工具。本例是Tapestry的开始项目,我们基本不会用到它。

为什么一个链接我们会看到两个请求呢? Tapestry  使用的方式基于 Post/Redirect/Get 模式.实际上,Tapestry 在每个组件事件里 执行了重定向。所以,第一个请求来执行操作,第二个来重写渲染页面。可以在浏览器里看到,因为URL仍然是 "/tutorial1" (t渲染页面的 URL ).稍后提到。

准备好下一步,将Index 和Guess 页面链接起来。Index 选择一个数字,并传递到Guess页。

先来思考一个 Guess 页. 它需要一个变量来存储谜底值,还需要一个方法接受Index页传的值。

Guess.java
package com.example.tutorial1.pages;
public class Guess
{
     private int target;
     void setup( int target)
     {
         this .target = target;
     }
}

在Index.java同级文件夹里创建 Guess.java 文件 .然后,修改Index 来触发Guess的setup() 方法 :

Index.java (revised)
package com.example.tutorial1.pages;
import java.util.Random;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Log;
public class Index
{
     private final Random random = new Random(System.nanoTime());
     @InjectPage
     private Guess guess;
     @Log
     Object onActionFromStart()
     {
         int target = random.nextInt( 10 ) + 1 ;
         guess.setup(target);
         return guess;
     }
}

新的事件处理器选择了谜底值,并告诉了Guess页面。因为Tapestry 是管理环境,我们不需要创建Guess 的实例....Guess页的生命周期由Tapestry来管理,也是Tapestry的职责所在。当然,我们需要 @InjectPage 注解来请求Tapestry管理Guess页。

在Tapestry中所有的组件类或页面类里的属性都必须不是 public.

现在我们有了 Guess 页实例, 我们可以像平常一样调用它的方法。

从事件处理方法返回页面实例是由Tapestry发送一个 客户端重定向 到返回页,而不是由当前页重定向。一旦点击了链接"start guessing" ,用户将会看到Guess 页。

当自己创建应用时,一定要确保不可变的对象是线程安全的。看起来每个线程都有自己的对象,其实公用一个。普通实例不是这样。幸好,Random是线程安全的。 

点击链接,看我们会看到什么:

啊!我们还没有创建Guess页的模板。Tapestry期望我们创建一个,所以最后创建它。

src/main/resources/com/example/tutorial/pages/Guess.tml
<html t:type= "layout" title= "Guess The Number"
     xmlns:t= "http://tapestry.apache.org/schema/tapestry_5_3.xsd" >
     <p>
         The secret number is: ${target}.
     </p>
   
</html>

点击浏览器返回按钮,再点击"start guessing" .我们会看到:

滚动一下,你会看到Guess.tml 模板里有个错误。我们在Guess.java里有个属性叫target,但它是private,所以页面模板获取不到。

我们只需要在Guess类里添加getter和setter方法,或者,使用Tapestry注解:

@Property
private int target;

 @Property 注解指导Tapestry为我们生成getter 和setter 方法。如果你需要模板引用属性,那你就这样做吧。

还差一点我们就完成了,刷新页面,你会看到谜底值是0!

啥情况?我们的最小值也是1啊...谜底值去哪里了?

像刚刚上面提到,Tapestry在处理事件请求后,发送到客户端一个重定向。也就是说,页面会在新的请求完成后重新渲染。与此同时,Tapestry在请求完成后清除实例的属性变量值。也就是说,在组件请求时,谜底值不是0,但新页面在请求渲染后,谜底值成为了默认值0.

解决方法:标记属性变量为持久化(可以指定持久到什么时候),也就是 @Persist 注解所做的 :

@Property 
@Persist
private int target;

这里没有对持久化数据进行任何操作(稍后讲解),只是存在了HttpSession中,在请求间共享。

返回到首页,点击链接,我们会得到谜底值:

对于开始项目,这些足够用了。现在编辑Guess 页,让用户可以来猜谜底。我们会展示猜的次数并在用户猜谜底时增加它。我们稍后再考虑太高和太低问题。

在构建Tapestry 页面时,有时会先创建Java然后创建模板,也有时先创建模板再创建Java。两种方式都可以。这里我们先创建模板,然后在思考在Java 里我们需要什么才能协同工作起来。

Guess.tml (修订版)

< html t:type = "layout" title = "Guess The Number"
     xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_3.xsd"
     xmlns:p = "tapestry:parameter" >
  
     < p >
         The secret number is: ${target}.
     </ p >
  
     < strong >Guess number ${guessCount}</ strong >
  
     < p >Make a guess from the options below:</ p >
  
     < ul class = "list-inline" >
         < t:loop source = "1..10" value = "current" >
             < li >
             < t:actionlink t:id = "makeGuess" context = "current" >${current}
             </ t:actionlink >
             </ li >
         </ t:loop >
     </ ul >
  
</ html >

看起来我们需要一个 guessCount 属性.

这里有一个新的组件 Loop 组件. Loop 组件会遍历传给它的source 参数,并把每个值渲染到子元素里。在渲染前,会更新遍历的属性值。 

这里有个特殊的属性表达式, 1..10, 生成1到10 的之间的数字。使用 Loop 组件时,我们通常遍历 List ,Collection 比如数据大查询结果。

Loop 组件将会设置current 属性值为1,渲染到body里(li标签和action link组件)。然后,设置current值为2,渲染....直到10.

注意我们对ActionLink组件的操作,我们不仅要知道用户点击了链接,我们还需要知道用户点击了哪一个链接。这里的context参数允许添加一个参数值到url后面,可以在事件处理器里接受到这个参数。

 ActionLink 的URL将会是 /tutorial1/guess.makeguess/3. 页面名字, "Guess",  组件 id, "makeGuess", 和context 值, "3".

Guess.java (修订版)
package com.example.tutorial1.pages;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
public class Guess
{
     @Property
     @Persist
     private int target, guessCount;
     @Property
     private int current;
     void setup( int target)
     {
         this .target = target;
         guessCount = 1 ;
     }
     void onActionFromMakeGuess( int value)
     {
         guessCount++;
     }
}

修订版的Guess包含了两个新的属性:current 和 guessCount. 还有一个处理器来处理 makeGuess ActionLink 组件; 目前只增加了guessCount。

注意 onActionFromMakeGuess() 方法现在有一个参数:  context 值会在URL中被ActionLink编码 .当用户点击链接,Tapestry会自动从URL提取字符串,转换成int值,传递给事件处理器方法。

现在,页面部分可以操作了:

接下来,我们要检查用户选择的数字和谜底值的对比,或是太高,或是太低,或是正好相等来给用户一个反馈。如果相等,我们要跳转到GameOver页面,并展示一条信息 "You guessed the number 5 in 2 guesses".

编辑Guess页,我们需要一个新属性来存储反馈信息,还有一个注入的GameOver页面。

Guess.java (partial)
@Property
@Persist (PersistenceConstants.FLASH)
private String message;
@InjectPage
private GameOver gameOver;

我们看到在@Persist 注解有了一个持久化策略叫做 FLASH ,Session里的一种,但只对一次请求有效...它的特殊设计是为了这样的反馈信息,如果点击F5来刷新页面,页面会重新渲染,信息会消失。

接下来,我们需要一些逻辑代码写在 onActionFromMakeGuess() 事件处理方法:

Guess.java (partial)
Object onActionFromMakeGuess( int value)
{
     if (value == target)
     {
         gameOver.setup(target, guessCount);
         return gameOver;
     }
     guessCount++;
     message = String.format( "Your guess of %d is too %s." , value,
         value < target ? "low" : "high" );
     return null ;
}

简单的代码,如果猜对了,然后返回GameOver页面,并重定向。否则,增加猜的次数,格式化反馈用户信息。

在模板中,我们只需要添加展示信息:

Guess.tml (partial)
< strong >Guess number ${guessCount}</ strong >
< t:if test = "message" >
     < p >
         < strong >${message}</ strong >
     </ p >
</ t:if >

这里使用到了 Tapestry的 If 组件.  If 组件判断test里的参数,如果结果为true,渲染到页面。参数没有必要非得是boolean值,Tapestry认为 null,0,空集合为false,非0为true等等。对于String 来说(比如message),空字符串(null,或只有空格或tab)视为false,非空即为true。

我们可以这样订单"GameOver" 页:

GameOver.java
package com.example.tutorial1.pages;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
public class GameOver
{
     @Property
     @Persist
     private int target, guessCount;
     
     void setup( int target, int guessCount)
     {
         this .target = target;
         this .guessCount = guessCount;
     }
}
GameOver.tml
< html t:type = "layout" title = "Game Over"
     xmlns:t = "http://tapestry.apache.org/schema/tapestry_5_3.xsd"
     xmlns:p = "tapestry:parameter" >
     < p >
         You guessed the number
         < strong >${target}</ strong >
         in
         < strong >${guessCount}</ strong >
         guesses.
     </ p >
   
</ html >

当猜对后,结果如下:

这里总结了Tapestry的基本用法;链接页面,页面传值。

本应用还有其它扩展空间:比如从GameOver 页面开始新游戏(请不要使用重复代码来完成)。另外,稍后我们会讲解其它方式来共享页面间的信息,会比现在设置-持久化的方式简单。

下面,我们来看看Tapestry如何处理HTML表单和输入框。

接下来: 使用模型编辑表单来创建用户表单

这篇关于实现HI-LO猜游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/209221

相关文章

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug