作者:yinowl
2005年1月
1.1版注:完善了游戲輸贏的判斷,由於考慮的比較匆忙,沒有非常仔細地考量算法的優劣程度,目的主要對游戲的輸贏進行一種實現,嵌入到程序中,給大家做為一個參考,由於其他內容和1.0版完全相同,我就把輸贏實現這一節提到最前面,後面再補上所有的內容。
游戲輸贏
最後要說的是判斷游戲的輸贏。我的思路是,每次在玩家著棋(按下5)時,判斷棋局的輸贏,然後用一個 Alert 顯示哪一方贏了以及當前的比分,返回後開始新的一局。判斷的邏輯是,在當前所下的棋子的0/180度、90/270度、45/225度、135/315度四個方向上分別往兩頭按照由近至遠的順序判斷各5個棋子是否是當前下棋方的棋子,如果是則累加到一個變量上,如果在到達5之前出現“否”的情況,則中止在這一方向或這一角度的判斷,變量歸1(應為當前棋子肯定是當前下棋方下完的棋子)並進行下一個方向或角度。在判斷是否是當前方時,用當前 isPlayer1 變量和棋子對象的 isPlayer1 變量進行比交。源代碼如下:
int player1win,player2win; protected synchronized void keyPressed(int keyCode) { ... else if (action == Canvas.FIRE) { if(chesses[selectedY][selectedX]==null){ chesses[selectedY][selectedX]=new Chesses(this.isPlayer1); if(checkWin()){ String winner; if(isPlayer1){ winner="白方勝利"; player1win++; } else{ winner="紅方勝利"; player2win++; } try{ Thread.sleep(3000); }catch(Exception e){ } Alert winAlert=new Alert("", winner+"\n白方 "+player1win+" : "+player2win+" 紅方", null,AlertType.INFO); winAlert.setTimeout(Alert.FOREVER); Display.getDisplay(gobang).setCurrent(winAlert,this); init(); repaint(); } this.isPlayer1=!this.isPlayer1;//切換下棋方 } } repaint(); } private boolean checkWin(){ int num=1; if(num<5){ num=1; for(int i=1;i<=4;i++){ if(isPlayer1(selectedX-i,selectedY)){ num++; } else break; } for(int i=1;i<=4;i++){ if(isPlayer1(selectedX+i,selectedY)){ num++; } else break; } } if(num<5){ num=1; for(int i=1;i<=4;i++){ if(isPlayer1(selectedX,selectedY-i)){ num++; } else break; } for(int i=1;i<=4;i++){ if(isPlayer1(selectedX,selectedY+i)) num++; else break; } } if(num<5){ num=1; for(int i=1;i<=4;i++){ if(isPlayer1(selectedX-i,selectedY-i)) num++; else break; } for(int i=1;i<=4;i++){ if(isPlayer1(selectedX+i,selectedY+i)) num++; else break; } } if(num<5){ num=1; for(int i=1;i<=4;i++){ if(isPlayer1(selectedX+i,selectedY-i)) num++; else break; } for(int i=1;i<=4;i++){ if(isPlayer1(selectedX-i,selectedY+i)) num++; else break; } } if(num>=5) return true; else return false; } private boolean isPlayer1(int y,int x){ if(x<=15 && x>=0 && y<=15 && y>=0 && chesses[x][y]!=null){ if(chesses[x][y].isPlayer1==this.isPlayer1) return true; else return false; } else return false; }
介紹
這是我學習j2me入門後的第一個作品,當然這也是一個極其簡單的作品(沒有電腦AI,只能是兩個人對戰),現在我把當時的設計思路寫成這篇文檔,希望對想入門J2ME的朋友在J2ME的流程,按鍵響應,繪圖等方面有所幫助,同時也希望大家指出錯誤和改進程序。
注意
代碼列出解釋的形式仿照《J2ME Game Programming》一書,按照程序功能思路給出相關代碼,一個文件的代碼會根據功能在不同的小節給出,文章結束了,代碼也就完整了。這不同於通常書中的代碼以文件為單位一次全部給出,我認為這樣更有助於讓大家了解一個程序從設計到最後完成的思路。
設計
數據結構:由於五子棋是一個二維棋類游戲,所有首先想到的是定義一個Chesses類來表示棋子,Chesses有一個boolean型的變量isPlayer1來區分該棋子是哪玩家下的,然後用一個Chess類型的二維數組來包含棋盤上的所有棋子。考慮到移動設備的資源有限,盡可能減少系統資源占用,我考慮不在數組建立後直接生成數組的每一個對象,而是把每一個棋子對象(Chesses)放在游戲的進行中生成,也就是說在游戲進行時,玩家每下一步棋,在數組相應位置生成該棋子的對象,這樣可以避免還沒有下的棋子在一開始就占用了系統內存
流程:游戲按照棋子的二維數組進行繪制棋子,玩家下棋後,程序修改數組相應位置,設置isPlayer1值,然後重新繪制(repaint),就更新了棋盤界面。由於游戲的功能簡單,也為了使游戲的操作盡可能的簡便,我不在游戲進入時設計菜單,而是直接開始對戰,在對戰界面,設置了重新開始和退出的按鈕。即運行即玩,一鍵開始,一鍵重來,一鍵退出。
玩家切換:棋類游戲有一個問題需要注意,就是提示當前由哪方下棋,為了節省界面空間,簡化游戲界面,我在棋盤外圍加了一個3個像素寬的框,框的顏色就是當前下棋方的顏色,如圖:
應用程序類:Gobang.Java
接下來就開始完成游戲中的每一個類,首先就是一個MIDlet類。Gobang類繼承自MIDlet類,用於連接設備的應用程序管理器(Application Manager),通過方法startApp,pauseApp,destroyApp來通知游戲的開始,暫停和銷毀結束。源代碼如下:
package com.occo.J2ME.game.gobang;
import javax.microedition.lcdui.Display; import Javax.microedition.midlet.MIDlet;
public class Gobang extends MIDlet {
GobangCanvas gobang;//定義游戲界面的Canvas類GobangCanvas的對象gobang
public Gobang() { super(); gobang=new GobangCanvas(this);//生成GobangCanvas類的對象gobang }
protected void startApp(){ Display.getDisplay(this).setCurrent(gobang); //在屏幕上繪出游戲見面gobang }
protected void pauseApp(){ }
protected void destroyApp(boolean arg0){ }
}
游戲界面類:GobangCanvas.Java
GobangCanvas類是游戲的核心類,繼承自Canvas,此類將完成游戲的邏輯、繪圖、控制、互動等所有功能,此類的框架代碼如下:
package com.occo.J2ME.game.gobang;
import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import Javax.microedition.lcdui.Graphics;
public class GobangCanvas extends Canvas implements CommandListener{
protected Gobang gobang;
public GobangCanvas(){ }
public GobangCanvas(Gobang gobang){ this.gobang=gobang; }
protected void paint(Graphics g) { }
}
棋子類:Chesses.Java
此類定義了一個棋子,棋盤上的每一個棋子都對應著一個Chesses的對象,整個棋盤是一個Chesses類型的二維數組,源代碼如下:
package com.occo.J2ME.game.gobang;
public class Chesses {
boolean isPlayer1;
public Chesses() { }
public Chesses(boolean isPlayer1) { this.isPlayer1=isPlayer1; }
}
添加圖形圖像
到現在,我們已經完成了游戲的一個基本框架,接下來,我們就可以來繪制游戲的每一個部件了
首先是五子棋的一些初始設置,添加如下代碼到GobangCanvas.Java
... int empty;//游戲界面到屏幕邊緣的留空 int canvasW,canvasH;//畫布的長和寬 int chessLength;//棋子的直徑 int chessMapLength,chessMapGrid,chessGridLength; //棋盤的邊長,棋盤一邊格子數,每格寬度 int chessMapX,chessMapY;//棋盤左上角x,y坐標 int selectedX,selectedY;//選擇框在棋盤格局上的x,y位置 boolean isPlayer1;//是否是玩家1 Chesses[][] chesses;//棋子數組 boolean newGame;//是否是新的游戲
public GobangCanvas(Gobang gobang){ newGame=true; empty=10; canvasW=getWidth()-empty;canvasH=getHeight()-empty; chessMapGrid=15; chesses=new Chesses[chessMapGrid+1][chessMapGrid+1]; if(canvasW>canvasH){ chessMapLength=canvasH-canvasH%chessMapGrid; chessMapX=(canvasW-chessMapLength)/2+empty/2; chessMapY=(canvasH%chessMapGrid)/2+empty/2; } else{ chessMapLength=canvasW-canvasW%chessMapGrid; chessMapX=(canvasW%chessMapGrid)/2+empty/2; chessMapY=(canvasH-chessMapLength)/2+empty/2; } chessGridLength=chessMapLength/chessMapGrid; chessLength=chessGridLength-1; selectedX=selectedY=chessMapGrid/2; isPlayer1=true; }
最先要繪制的是棋盤,棋盤是正方形,但屏幕有矩形的,所以棋盤邊長要按短邊計,但短邊未必是棋盤格子數的整數倍,因此
棋盤邊長 = 短邊 - 短邊 % 格子數
因為棋盤要居中,所以在算左上角坐標時,記得也要把留空(empty)除以2,以下是畫棋盤的代碼:
protected void paintMap(Graphics g){ for(int i=0;i<chessMapGrid;i++){ for(int j=0;j<chessMapGrid;j++){ g.setColor(128,128,128); g.drawRect(chessMapX+j*chessGridLength, chessMapY+i*chessGridLength, chessGridLength,chessGridLength); } } }
然後是繪制選擇框,注意:選擇框的selectedX,selectedY並不是在畫布上的x,y坐標,而是在棋子數組(chesses)中的位置,源代碼如下:
protected void paintSelected(Graphics g){ g.setColor(0,0,255); g.drawRect(chessMapX+selectedX*chessGridLength-chessGridLength/2, chessMapY+selectedY*chessGridLength-chessGridLength/2, chessGridLength,chessGridLength); }
接著是按照棋子二維數組繪制已經下了的棋子。玩家每下一次棋,就修改數組,在重新繪圖的時候就能繪出,源代碼如下:
protected void paintChesses(Graphics g){ for(int i=0;i<=chessMapGrid;i++){ for(int j=0;j<=chessMapGrid;j++){ if(chesses[i][j]!=null){ if(chesses[i][j].isPlayer1) g.setColor(255,255,255); else g.setColor(255,0,0); g.fillArc(chessMapX+j*chessGridLength-chessLength/2, chessMapY+i*chessGridLength-chessLength/2, chessLength,chessLength,0,360); } } } }
最後是繪制玩家提示框,並且把所有部件的繪制匯總在paint()方法中,注意繪制的順序,顯而易見,應該先繪制提示框-棋盤-選擇框-棋子:
protected void paintPlayer(Graphics g,boolean isPlayer1){ if(isPlayer1) g.setColor(255,255,255); else g.setColor(255,0,0); g.drawRect(1,1,getWidth()-2,getHeight()-2); g.drawRect(2,2,getWidth()-4,getHeight()-4); g.drawRect(3,3,getWidth()-6,getHeight()-6); } protected void paint(Graphics g) { g.setColor(0x00000000); g.fillRect(0, 0, getWidth(), getHeight()); paintPlayer(g,isPlayer1); paintMap(g); paintSelected(g); paintChesses(g); }
響應玩家操作
接下來應該添加命令按鈕和游戲操作控制。我們在游戲中需要有兩個按鈕,重新開始和退出,此外還必須接收玩家控制選擇框的操作上下左右和著棋,添加代碼到GobangCanvas.Java:
Command exitCmd; Command restartCmd;
public GobangCanvas(Gobang gobang){ ... restartCmd = new Command("重新開始", Command.SCREEN, 0); exitCmd = new Command("退出", Command.EXIT, 0); addCommand(restartCmd); addCommand(exitCmd); setCommandListener(this);
} private void init(){ if(newGame){ chesses=new Chesses[chessMapGrid+1][chessMapGrid+1]; isPlayer1=true; selectedX=selectedY=chessMapGrid/2; } } public void commandAction(Command c, Displayable d) { if (c == exitCmd) { gobang.destroyApp(false); gobang.notifyDestroyed(); }else if(c==restartCmd){ init();//初始化棋盤,把棋盤清空,重新開始游戲 repaint(); } } protected synchronized void keyPressed(int keyCode) { int action = getGameAction(keyCode); if (action == Canvas.LEFT ) { selectedX=(--selectedX+chessMapGrid+1)%(chessMapGrid+1); } else if (action == Canvas.RIGHT) { selectedX=(++selectedX)%(chessMapGrid+1); } else if (action == Canvas.UP) { selectedY=(--selectedY+chessMapGrid+1)%(chessMapGrid+1); } else if (action == Canvas.DOWN) { selectedY=(++selectedY)%(chessMapGrid+1); } else if (action == Canvas.FIRE) { if(chesses[selectedY][selectedX]==null){ chesses[selectedY][selectedX]=new Chesses(this.isPlayer1); //checkWin(); this.isPlayer1=!this.isPlayer1;//切換下棋方 } } repaint(); } private void checkWin(){ }
至此,游戲的所有繪圖部分全都給出,有一點需要指出,雙緩沖顯示技術,由於現在有的手機已直接內置了雙緩沖,這裡我們就不在詳細說明,有興趣可以查閱相關文檔。
總結
整個游戲已經全部完成,大家一定會覺得很簡單吧,這個游戲其實只使用了J2ME-midp1.0種很少的內容,但已經是一個完整的游戲了,希望對一些朋友有所幫助。當然我們完全可以進行一些擴展,比如加上片頭動畫,加上聲音,加上電腦AI,加上藍牙對戰功能(已經完成,下次專門寫一篇文檔),這樣游戲就慢慢的完善,並且具有商業價值,願大家學J順利,多多交流(MSN:[email protected] QQ:47599318 E-mail:[email protected])。