[GWT] 對戰吧~踩地雷! [下]

看板java作者 (痞子軍團團長)時間15年前 (2010/08/26 05:45), 編輯推噓3(300)
留言3則, 3人參與, 最新討論串1/1
網誌版: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

08/26 10:48, , 1F
好文鑑賞
08/26 10:48, 1F
※ 編輯: PsMonkey 來自: 219.70.214.149 (08/26 12:25) update 檔案下載... 之前的版本實在太糟糕了 XD ※ 編輯: PsMonkey 來自: 219.70.214.149 (08/26 21:38)

08/27 16:29, , 2F
推~不過能否幫GameInfo的method加註解:)
08/27 16:29, 2F
※ 編輯: PsMonkey 來自: 219.70.183.212 (09/02 16:18)

09/10 01:28, , 3F
GOOD
09/10 01:28, 3F
文章代碼(AID): #1CTOxZAy (java)