前一篇我们设计了数据对象,今天我们来描述整个游戏的运转流程。
我拿数位板手写了一个流程示意图,如图:
首先概述一下这一整个思路:
当玩家选择进行游戏之后,会跳转到一个Activity。那么在跳转后,我们就要开始载入资源,比如音效、图片素材等。这个载入可能会比较耗时,所以我们可以另开一个线程进行载入,然后通过myHandler进行通知主线程更新UI并进行初始化游戏的动作。另外,由于要实现网络对战的功能,所以我们还要另开一个线程进行网络对接的操作。
load_resources()主要包括载入图像,以及进行一些按键绑定。这个你们可以轻松实现,无非一些FindviewbyId、setOnClickListner,略去。
资源加载完之后,我们要开始初始化游戏的数据,如init_game():
private void init_game() throws Exception{
try{
complete_arrow_uesd=0;//初始化已用完成箭头标志的数目
whosTurn=(int)(Math.random()*4);//随机决定谁先开始
roll_num=0;
}
catch (Exception e){
e.printStackTrace();
System.out.println("注意:初始化游戏失败");
throw e;
}
System.out.println("注意:初始化游戏完毕");
}
在这个函数中,我们不需要做太多操作,只需要把一些游戏常用数值进行初始化。基于这个思想,你可以往里面填充你所需要的其他内容。
接下来,游戏开始。游戏一旦开始,我们需要读取当前对局所有玩家的总体数据。还记得我们曾经设置过一个ConfigHelper类么?所以现在,这个game_start()可以这么写:
private void game_begin(){
//游戏开始时的操作
chdm=new ConfigHelper(Value.Local);//初始化游戏设定,当前游戏为线下游戏
chdm.setHost(true);
chdm.setGreenType(Value.AI);//绿方为AI,下同
chdm.setBlueType(Value.AI);
chdm.setYellowType(Value.AI);
//填充你所需要的其他内容
System.out.println("游戏开始!");
turn_begin();//设定装载完毕,回合开始
}
现在,一个回合正式开始了。
在最初的设计需求中,我提到过我们要满足一个玩家"托管"状态的功能。
所以在这个流程中,我们要对当前回合的玩家进行判断,如果是AI,那么执行AI策略;同时还需要判断当前是否为网络游戏,如果是,判断是否为服务器,如果是服务器,要负责向具体的玩家收信,并把收信内容转发给其他的Online玩家;如果不是,就单纯从服务器收信,并对收到的数据进行处理就行了。我是这么设计的:
private void turn_begin(){
//回合开始
resetRoll();//重置骰子
System.out.println("玩家:["+Value.PlayerName[whosTurn]+"]的回合!Type:"+chdm.getPlayerType(whosTurn)+",请投掷骰子!");
//判断当前玩家是人还是AI
if(chdm.getGameType()==Value.Local){
//如果不是网络对战
if(chdm.getPlayerType(whosTurn)==Value.AI){
//当前玩家是AI
//AIMethod(Value.AIType[0]);//为什么不这么写//因为执行太快了,人类结束回合轮到AI,刷的一下AI就完成操作了...你根本不知道刚刚发生了啥
myHandler.postDelayed(AIDelay,2000);//执行AI策略,延迟2秒
}
return ;//本地游戏的话,不执行下面的语句
}
//如果是网络对战
else if(chdm.isHost()){
//如果当前玩家是服务器,需要进行转发、AI()策略
if(chdm.getPlayerType(whosTurn)==Value.Online){
//服务器收信
myHandler.post(HostrecMsg);
}
else if(chdm.getPlayerType(whosTurn)==Value.AI){
myHandler.postDelayed(AIDelay,2000);
}
else return;//服主自己的回合,可以开始正常游玩
}
else {
//是客户端
if(chdm.getPlayerType(whosTurn)!=Value.LocalHuman){
//网络用户或AI时,收取服务器信息并更新UI
myHandler.post(ClientrecMsg);
}
else return;//客户端自己的回合,可以开始游玩
}
}
注意:我在这段代码里写的ClientxxxMsg()和HostxxxMsg()只是个空办法,我没有具体的实现,只是这么示意而已。你可以先放着不管,等到我们后面完成了关于TCP/蓝牙的通讯功能后,再来进行这些操作。AI策略和网络玩家操作我们暂时放过,因为他们都只不过是对"本地人类玩家自己的操作"所需实现的内容的一种自动化实现而已。
现在回忆我们的数据对象:棋子Chessman,我在它之中写了几个基本的方法:
move(int x,int y),移动到(x,y);
move(int steps),按预定路线,移动steps步
Killed(),棋子被击杀
Fly(),棋子起飞
Completed(),棋子完成了旅途
所以,对于[回合中]阶段的设计,我们有如下设计:
Roll(),简单来说就是获取一个随机的骰子点数,并进行一些其他的判定的操作,比如当前玩家无法行动,直接结束该玩家的回合。
go(Chessman c),按骰子的点数移动选中的棋子c,包含对。
在go(Chessman c)中,棋子移动完毕后,马上对当前位置进行一次判断,Judge(Chessman c);
你可能会注意到为什么Judge(Chessman c)的传参是Chessman而不是当前坐标。原因很简单,别忘了我们在Chessman数据对象的设计中,包含了get当前坐标的方法。根据传参一致性原则,这里的传参用Chessman不仅便于后期升级维护,也利于理解。
Judge()应当包含两部分,即当前棋子特殊位置判断和击杀判定KillJudge():特殊位置判定有两种,如可以飞行的棋格或是可跳跃的棋格;击杀判定就是判定当前棋格是否有可击杀的对手棋子。Judge()之后,即完成了一切结算,进入回合结束阶段turn_end();
当一枚棋子完成他的旅途,我们除了进行c.Completed()操作,还应当对该玩家的所有棋子进行判定,若全部都完成了旅途,则改玩家胜利,不再进入turn_end()阶段,直接进行GameOver(),并准备restart_game();
这部分的代码比较长,不方便整段贴进来,你们可以到>这里下载<。restart_game()如下:
private boolean restart_game(){
try{
init_CheesmanEntity_posALL();//重设所有棋子的位置
for(int i=0;i<4;i++){
red[i].resetStatusALL();
yellow[i].resetStatusALL();
blue[i].resetStatusALL();
green[i].resetStatusALL();
}
System.out.println("注意:位置初始完毕");
chdm.resetStatusALL();
System.out.println("注意:游戏设置初始化完毕");
for(int i=0;i
private void turn_end(){
System.out.println("注意:玩家["+Value.PlayerName[whosTurn]+"]进入回合结束阶段");
if(chdm.getGameType()==Value.Local);//本地游戏
else if(chdm.isHost()){
//网络游戏且为主机
myHandler.post(HostsendMsg);
}
else if(!chdm.isHost()){
//网络游戏且为客户端
myHandler.post(ClientsendMsg);
}
if(roll_num!=6)next_turn();//非6的情况下结束回合
else turn_begin();//6的情况下继续新回合
}
注意到,我这里弄了一个对骰子点数是否为6的判断,如果是6,则加一个回合,否则,进入next_turn(),切换下一名玩家:
private void next_turn(){
//开始下一回合
whosTurn=(whosTurn+1)%4;
turn_begin();
}
现在只剩最后一个问题,要怎么把点击到的棋子变成go(Chessman c)中可用的传参?我写了这样一个方法:
private Cheesman view_to_cheesman(int FACTION,View v){
if(FACTION==Value.red){
//System.out.println("注意:从red获取棋子");
for(int i=0;i<4;i++){//获取当前点击的View的坐标并比对,最后选中相应的Chessman
if(red[i].getX()==(int)v.getX()/half_kuan && red[i].getY()==(int)(v.getY()-left_top_y)/half_kuan)return red[i];
}
}
else if(FACTION==Value.yellow){
for(int i=0;i<4;i++){
if(yellow[i].getX()==(int)v.getX()/half_kuan && yellow[i].getY()==(int)(v.getY()-left_top_y)/half_kuan)return yellow[i];
}
}
else if(FACTION==Value.blue){
for(int i=0;i<4;i++){
if(blue[i].getX()==(int)v.getX()/half_kuan && blue[i].getY()==(int)(v.getY()-left_top_y)/half_kuan)return blue[i];
}
}
else if(FACTION==Value.green){
for(int i=0;i<4;i++){
if(green[i].getX()==(int)v.getX()/half_kuan && green[i].getY()==(int)(v.getY()-left_top_y)/half_kuan)return green[i];
}
}
else System.out.println("注意:从View获取棋子失败:不是你的回合");
System.out.println("注意:从View获取棋子失败");
return null;
}
当然,你们可以更聪明一点,直接写一个回调就好了。
现在,我可以给出完整的Value.java了。你们可以看到我给四个阵营分别规划了他们的路线xxxPathX|Y[],这样,就可以很方便地使用Chessman.now_pos标记当前移动和特殊棋格了。同时,对于Chessman.move(int steps)方法,也有了其填充内容。
public class Value {
static final int red=0;
static final int yellow=1;
static final int blue=2;
static final int green=3;
static final int AI=0;
static final int OnlineHuman=1;
static final int LocalHuman=2;
static final int Online=0;
static final int Local=1;
static final int AIType[]=new int[]{0,1,2,3};
static final int Teminal=56;//
static final int Conflict=53;
static final int jump_point[]=new int[]{
2,6,10,14,22,26,30,34,38,
};
static final String PlayerName[]=new String[]{"Red","Yellow","Blue","Green"};
static final int redPathx[]=new int[]{
25,
23,24,24,23,25,27,29,//7
31,32,32,32,32,32,31,//7
29,27,25,23,24,24,23,//7
21,19,17,15,13,//5
11,10,10,11,9,7,5,//7
3,2,2,2,2,2,3,//7
5,7,9,11,10,10,11,//7
13,15,//2
17,17,17,17,17,17,17//7
};
static final int yellowPathx[]=new int[]{
33,
31,29,27,25,23,24,24,
23,21,19,17,15,13,11,
10,10,11,9,7,5,3,
2,2,2,2,2,//5
3,5,7,9,11,10,10,
11,13,15,17,19,21,23,
24,24,23,25,27,29,31,
32,32,//2
32,29,27,25,23,21,19
};
static final int bluePathx[]=new int[]{
9,
11,10,10,11,9,7,5,
3,2,2,2,2,2,3,
5,7,9,11,10,10,11,
13,15,17,19,21,//5
23,24,24,23,25,27,29,
31,32,32,32,32,32,31,
29,27,25,23,24,24,23,
21,19,
17,17,17,17,17,17,17
};
static final int greenPathx[]=new int[]{
1,
3,5,7,9,11,10,10,
11,13,15,17,19,21,23,
24,24,23,25,27,29,31,
32,32,32,32,32,//5
31,29,27,25,23,24,24,
23,21,19,17,15,13,11,
10,10,11,9,7,5,3,
2,2,//2
2,5,7,9,11,13,15//7
};
static final int redPathy[]=new int[]{
1,
3,5,7,9,11,10,10,
11,13,15,17,19,21,23,
24,24,23,25,27,29,31,
32,32,32,32,32,//5
31,29,27,25,23,24,24,
23,21,19,17,15,13,11,
10,10,11,9,7,5,3,
2,2,//2
2,5,7,9,11,13,15
};
static final int yellowPathy[]=new int[]{
25,
23,24,24,23,25,27,29,
31,32,32,32,32,32,31,
29,27,25,23,24,24,23,
21,19,17,15,13,//5
11,10,10,11,9,7,5,
3,2,2,2,2,2,3,
5,7,9,11,10,10,11,
13,15,//2
17,17,17,17,17,17,17
};
static final int bluePathy[]=new int[]{
33,
31,29,27,25,23,24,24,
23,21,19,17,15,13,11,
10,10,11,9,7,5,3,
2,2,2,2,2,//5
3,5,7,9,11,10,10,
11,13,15,17,19,21,23,
24,24,23,25,27,29,31,
32,32,//2
32,29,27,25,23,21,19
};
static final int greenPathy[]=new int[]{
9,
11,10,10,11,9,7,5,
3,2,2,2,2,2,3,
5,7,9,11,10,10,11,
13,15,17,19,21,//5
23,24,24,23,25,27,29,
31,32,32,32,32,32,31,
29,27,25,23,24,24,23,
21,19,//2
17,17,17,17,17,17,17
};
}
下一篇,我们开始讲AI策略的设计和网络部分的收发信规则的讲解。
另外,关于游戏资源载入的优化问题,我可能会放到最后一篇讲。
如果你对代码中关于Handler的内容不甚了解,你可以试着去看我的另外一篇关于Handler、Looper、MessageQueue也就是消息循环机制的讲解:
[图解法结合源码]理解、记忆Handler、Looper、MessageQueue之间的关系
也可以提前到这里看有关网络TCP收发信内容的知识:
Android TCP通信的简单实例以及常见问题[超时/主线程阻塞]