前一篇我们设计了数据对象,今天我们来描述整个游戏的运转流程。

我拿数位板手写了一个流程示意图,如图:



首先概述一下这一整个思路:

当玩家选择进行游戏之后,会跳转到一个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/蓝牙的通讯功能后,再来进行这些操作。

接下来是整个游戏的大头,也就是游戏的回合中。这个回合中由三个部分组成,其一:AIMethod()即AI策略,其二:本地人类玩家自己的操作,其三:网络玩家已完成操作,你需要从收到的数据那里模拟他的操作即可。

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<complete_arrow_uesd;i++)complete_arrow[i].setVisibility(View.INVISIBLE);
            System.out.println("注意:完成标志隐藏完毕");
            init_game();
            ready_to_start_game();
        }
        catch (Exception e){
            e.printStackTrace();
            System.out.println("注意:重启游戏失败");
            return false;
        }
        return true;
    }

进入turn_end()阶段,我们需要同tur_begin()阶段一样,进行收信发信判定;但有一点要注意,由于我们在设计阶段,就把网络对战时所有的AI策略放在服务器进行模拟,因此此时的收信发信规则,需要考虑到是否为服务器的AI,如果是,需要把AI的行动发送给所有玩家。代码如下:

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通信的简单实例以及常见问题[超时/主线程阻塞]

更多推荐

android游戏开发实例-可局域网对战的飞行棋(二)