作者: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:yinowl@163.com qq:47599318 e-mail:yinowl@163.com)。
闽公网安备 35060202000074号