作者PsMonkey (痞子军团团长)
看板java
标题[GWT] 对战吧~踩地雷 [下]
时间Thu Aug 26 05:45:04 2010
网志版:
http://pt2club.blogspot.com/2010/08/gwt_26.html
跟电脑(web server)对战的故事大概是这样的:
1.跟 server 要求开始一场游戏
2.跟 server 取得游戏资讯
3.根据游戏资讯绘制画面
4.将玩家踩的地点传送给 server
4.1命中地雷→更新游戏资讯
4.2没有命中→轮到 AI 踩地雷→更新游戏资讯
5.检查是否有某方获胜?
5.1有→结束。
5.2没有→回到步骤 3
黄色的部份是跟 server 有关的部份。
那麽,以 RPC、或说以程式的讲法
server 只需要提供两个 method
startGame():负责开游戏
回传:该场 game 的 uid
shoot():传送玩家踩的地点
参数:uid, x, y
回传:GameInfo 物件
因为懒得多设计一堆有的没的
所以开场之後让 client 端程式自动踩 (-1,-1) 这个非法位置
就会进入到步骤 2~4 的循环当中 [逃]
好了,终於要进入到 GWT RPC 的部份了。
这里打算跳过理论、架构的部份
直接以程式码来说明一切...
首先,是要有一个 MineService 的 interface
继承自 com.google.gwt.user.client.rpc.RemoteService
里头就是宣告上头说得那两个 method
@RemoteServiceRelativePath("mineRPC")
public interface MineService extends RemoteService {
public String startGame();
public GameInfo shoot(String id, int x, int y) throws Exception;
}
至於那个 annotation 先跳过,後头会解释
再来是一个对应的 interface,名称通常是後头补 Async
public interface MineServiceAsync {
void startGame(AsyncCallback<String> callback);
void shoot(String id, int x, int y,
AsyncCallback<GameInfo> callback);
}
这两个 class 必须放在 gwt compiler 会处理的目录下(例如 client)
有 interface 自然有实做的 class
MineService 对应实做 class 通常叫做 MineServiceImpl
会长成这样子:
public class MineServiceImpl extends RemoteServiceServlet
implements MineService {
//FIXME change your ai here!
private AI_Interface ai = new RandomAI();
@Override
public String startGame() {
String id = UUID.randomUUID().toString();
MineGM setting = new MineGM();
setServer(id, setting);
return id+",Random";
}
private HttpSession getSession(){
return this.getThreadLocalRequest().getSession();
}
private void setServer(String id, MineGM setting){
getSession().setAttribute(id+"ID", setting);
}
private MineGM getServer(String id) throws Exception{
String name = id+"ID";
if(getSession().getAttribute(name)!=null){
return (MineGM) getSession().getAttribute(name);
}else{
throw new Exception("还没开局");
}
}
@Override
public GameInfo shoot(String id, int x, int y) throws Exception{
MineGM server = getServer(id);
if(x==-1 || y==-1 || server.getMap()[x][y]!=-1){
return MineGM.toGameInfo(server);
}
if(!server.shoot(x, y, MineGM.USER)){
int[] xy = new int[2];
do{
ai.guess(MineGM.toGameInfo(server), xy);
}while(server.shoot(xy[0], xy[1], MineGM.AI));
}
setServer(id, server);
return MineGM.toGameInfo(server);
}
}
这个 class 还会继承 RemoteServiceServlet
往上追溯,parent 是 HttpServlet
也就是说,MineServiceImpl 也是一个 HttpServlet
虽然 GWT RPC 很神奇地包装好许多东西
但终究还是 base on JSP
所以写这个 class 时,就不用管 GWT 的重重限制
只要 web.xml 有设定正确就好
讲到 web.xml,回头讲一下 MineService 的 annotation
用 RemoteServiceRelativePath 设定 servlet-mapping 会比较方便
<!-- in web.xml -->
<servlet-mapping>
<servlet-name>mineRPC</servlet-name>
<url-pattern>/_mine/mineRPC</url-pattern>
</servlet-mapping>
url-pattern 的值,前半段 _mine 是在 gwt.xml 中
设定 <module rename-to='_mine' >
後半段 mineRPC 就是在 MineService 设定的值
(没有用这个 annotation,得要多好几行烦死人的 code)
至於游戏资讯,我选择塞在 session 当中
在 RemoteServiceServlet 要取得 session 比较罗唆一点
得要这样才能取得 session
this.getThreadLocalRequest().getSession()
其余的程式码... 嗯... 不在 GWT 的范围当中,跳过 XD
server 端的程式码解决了,现在来看 client 端
client 端必须透过 MineServiceAsync 来呼叫 RPC
不过用法有点奇怪...... Orz
首先要先这样写,取得一个 MineServiceAsync 的 object
MineServiceAsync msa = GWT.create(MineService.class);
然後就可以用 msa.shoot() 来告诉 server 要踩哪个位置
但是事情还没完,除了标准的 parameter
得要传一个为 AsyncCallback 的 parameter
这是让 server 端处理完毕後可以 callback 的一个手段
初期通常都会用 anonymous class 来解决
所以程式码会长得像这样:
msa.shoot(this.gameID, hitX, hitY, new AsyncCallback<GameInfo>(){
@Override
public void onFailure(Throwable caught) {
Window.alert("shoot : "+caught.getLocalizedMessage());
}
@Override
public void onSuccess(GameInfo result) {
setShootResult(result);
}
});
这边要注意两件事情
其一,RPC 传递/回传的 class(及其 field)
除了 primitive data type 外
一定得 implements IsSerializable
还必须是 GWT 允许的 class
此外,自订的 class 还必须让在 GWT compiler 会处理的目录下
不然实际跑起来就会有(很难看懂的)错误讯息
其二,呼叫完 msa.shoot() 之後
下一步并不会执行 onSuccess()/onFailure()
更正确来说,在写 client 端程式码的时候
并不会知道 onSuccess()/onFailure() 什麽时候会呼叫到
端看 server 处理以及网路传输的速度... etc
这是 callback 的特性,在踩地雷的 case 当中并不会造成困扰
但在其他实务上,如果发现怎麽 RPC 回传值都不正确
那大概就是忘记这个性质所导致的...... Orz
喔对... 都忘记讲一个大前提了
要用 GWT RPC,server 必须是 JSP container
同时也来讲讲 GWT RPC 的好处 \囧/
一言以蔽之就是「
通通传便便」
client 不用组 query string 或 post 内容
server 端不用准备对应的 url
client/server 都不需要剖析传递的资料
甚至可以传递(客制化的) exception
在 client 端的 onFailure() 可以分门别类处理......
程式码看起来、写起来都很 OO、都很 Java
以一个 Java Programmer 来说,有什麽比这个更快乐的事情呢? XD
(连 xml 都没有用到呢! [握拳])
好了,「对抗电脑版的踩地雷」拆解到这里
只剩下地雷区要设定 click 的 handler
收到 GameInfo 之後要更新画面
以及电脑 AI 设计这些功能
相信你一定可以自己写的很开心的,就跳过不细谈了
如果你想偷懒想拿写好的程式码来执行看看,
可以到
http://wiki.psmonkey.org/changelog/mine 下载。
也欢迎投稿你的 AI 设计
http://www.psmonkey.org/gwt-product/mineAI_wanted.jsp
Enjoy GWT and have fun! \囧/
--
钱锺书:
说出来的话
http://www.psmonkey.org
比不上不说出来的话
Java 版 cookcomic 版
只影射着说不出来的话
and more......
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 219.70.214.149
1F:推 nvidia:好文监赏 08/26 10:48
※ 编辑: PsMonkey 来自: 219.70.214.149 (08/26 12:25)
update 档案下载... 之前的版本实在太糟糕了 XD
※ 编辑: PsMonkey 来自: 219.70.214.149 (08/26 21:38)
2F:推 kanandg1:推~不过能否帮GameInfo的method加注解:) 08/27 16:29
※ 编辑: PsMonkey 来自: 219.70.183.212 (09/02 16:18)
3F:推 psychedelico:GOOD 09/10 01:28