自定义View实践-一个简单的棋类游戏

原文发表在我的个人博客:http://kahn.wang/a-practice-of-custom-view-a-simple-chess/

最近看了慕课网上一个通过自定义View来实现五子棋游戏的视频,想起自己小时候经常跟小伙伴玩的一个棋类游戏,于是就自己动手,跟着视频把自己的想法实践了出来。也是作为自定义View的一个练习。

规则介绍

先上几张完成后的图:
       

第二张图已经把规则介绍很清楚了,一次只能走一步,只要你的三颗棋子通过中点并且连成一条线,你就获得了胜利。当然,要玩这个游戏必须两个人,不过你也可以自己跟自己玩哈(单身狗的世界……)。改天我看能不能 写一个机器人出来,来实现人机对战。

需求分析

要实现这样一个游戏我们需要解决以下的问题:

  1. 根据每个点处的状态绘制视图
  2. 棋子是可拖动的,并且拖动的时候棋子会放大,以模仿拿起的效果
  3. 如何判断输赢
  4. 判断落点是不是符合规则,比如不能一下走两格,不能走规定外的路径
  5. 判断该轮到哪一方走,另一方则无法移动视图。
  6. 能够重新开始,重置棋盘

如何绘制

关于棋盘的绘制和棋子的绘制这里不再多说,不清楚的同学可以查看文末的源码或者看文章开头给的视频链接。这里只说一下核心逻辑。先看绘制方法:
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制棋盘
        drawBoard(canvas);
        //绘制棋子
        drawPieces(canvas);
        if (isMoving){
            drawMovingPices(canvas);
        }
    }
很简单,只有三个方法,首先是绘制棋盘,这个很简单就不再多说了,然后是根据每点状态绘制棋子,这个接下来说,然后判断是否在移动中,如果在移动中的话,绘制移动中的棋子。

绘制静止棋子

因为棋盘是一个3*3共九个点的田字格,这里我使用一个3*3的二维数组来代表每个点的状态,数组中的每个元素对应到每个点。当元素值为0时,代表所对应的点没有棋子,当元素值为1时,代表所对应的点位白棋,当元素点为2时,代表为黑棋。代码如下:
    public static final int NO_PIECES=0;
    public static final int WHITE_PIECES=1;
    public static final int BLACK_PIECES=2;
    //是否是选中状态
    public static final int SELECT_WHITE_PIECES=3;
    public static final int SELECT_BLACK_PIECES=4;
    private int[][] positionCondition={
            {WHITE_PIECES,WHITE_PIECES,WHITE_PIECES},
            {NO_PIECES,NO_PIECES,NO_PIECES},
            {BLACK_PIECES,BLACK_PIECES,BLACK_PIECES}
    };

上面还有3和4,分别代表了被选择时的白棋和黑棋,在绘制时使其绘制放大的棋子。

然后就可以根据每个点的情况来绘制棋子了,这里给出绘制棋子的方法:

private void drawPieces(Canvas canvas) {
        float lineHeight=mLineHeight; //棋盘每格的高度

        //开始绘制
        //通过判断每个点是否有棋子
        for (int i=0;i<=Max_LINE;i++) {
            for (int j=0;j<=Max_LINE;j++){
                int x=(int)(lineHeight/4);
                int halfPiecesWidth= (int) (lineHeight*ratioPiecesOfLineHeight/2);
                int selectedHalfPiecesWidth= (int) (lineHeight*ratioSelectedPiecesOfLineheight/2);
                if (positionCondition[i][j]==WHITE_PIECES){
                    canvas.drawBitmap(mWhitePieces,x+j*lineHeight-halfPiecesWidth,x+i*lineHeight-halfPiecesWidth,null);
                }else if (positionCondition[i][j]==BLACK_PIECES){
                    canvas.drawBitmap(mBlackPieces,x+j*lineHeight-halfPiecesWidth,x+i*lineHeight-halfPiecesWidth,null);
                }else if (positionCondition[i][j]==SELECT_WHITE_PIECES){
                    canvas.drawBitmap(mSelectedWhitePieces,x+j*lineHeight-selectedHalfPiecesWidth,x+i*lineHeight-selectedHalfPiecesWidth,null);
                }else if (positionCondition[i][j]==SELECT_BLACK_PIECES){
                    canvas.drawBitmap(mSelectedBlackPieces,x+j*lineHeight-selectedHalfPiecesWidth,x+i*lineHeight-selectedHalfPiecesWidth,null);
                }
            }
        }
    }

绘制移动的棋子

通过判断是否正在移动这个全局变量来决定是否要绘制移动中的棋子:
    private void drawMovingPices(Canvas canvas) {
        float selectedHalfPiecesWidth= mLineHeight*ratioSelectedPiecesOfLineheight/2;
        if (movingPieces==WHITE_PIECES){
            canvas.drawBitmap(mSelectedWhitePieces,mPointF.x-selectedHalfPiecesWidth,mPointF.y-selectedHalfPiecesWidth,null);
        }else if (movingPieces==BLACK_PIECES){
            canvas.drawBitmap(mSelectedBlackPieces,mPointF.x-selectedHalfPiecesWidth,mPointF.y-selectedHalfPiecesWidth,null);
        }
    }

其中mPointF.x和mPointF.y为触摸事件为MOVE时获得的坐标。

这样需求1和2得到解决。

输赢判断

因为之前是用一个二维矩阵来对应到每个点,所以在判断输赢时只需要判断过中心的四条直线对应到数组中的元素是否相等就行,代码如下:
private void checkIsGameOver() {
    boolean a=positionCondition[1][1]==positionCondition[0][0]
            &&positionCondition[1][1]==positionCondition[2][2];
    boolean b=positionCondition[1][1]==positionCondition[0][1]
            &&positionCondition[1][1]==positionCondition[2][1];
    boolean c=positionCondition[1][1]==positionCondition[0][2]
            &&positionCondition[1][1]==positionCondition[2][0];
    boolean d=positionCondition[1][1]==positionCondition[1][0]
            &&positionCondition[1][1]==positionCondition[1][2];
    if (a||b||c||d){
        if (positionCondition[1][1]==WHITE_PIECES){
            Toast.makeText(getContext(),R.string.white_goal,Toast.LENGTH_LONG).show();
        }else if (positionCondition[1][1]==BLACK_PIECES){
            Toast.makeText(getContext(),R.string.black_goal,Toast.LENGTH_LONG).show();
        }
    }
}

其中a、b、c、d代表胜利时的四个位置,当任何一个发生时,游戏结束。因为无论哪种位置都通过中点,所以只需要判断中点是哪一方,就能确定是哪一方胜利。

这样需求3就能够满足

规则外的判断

如果走的路径不符合规则,则棋子会回到原位,主要判断代码如下:
                boolean judge=(upi+upj)%2!=0&&(downi+downj)%2!=0;  //不能走在规定外的斜线
                if (positionCondition[upj][upi]==NO_PIECES  //落点必须没有棋子
                        &&Math.abs(upi-downi)<=1   //不能走两格以上
                        &&Math.abs(upj-downj)<=1
                        &&!judge  //不能走规定外的斜线
                        &&Math.abs(upi-downi)+Math.abs(upj-downj)!=0 //不能原地不动
                        &&isMoving  //确定移动了
                        ){
                    positionCondition[upj][upi]=movingPieces;
                    mCounts=mCounts+1;
                    //判断是哪个棋子先走
                    if (mCounts==1){
                        mFirst=movingPieces;
                    }
                }else {
                    //棋子回到原位
                    positionCondition[downj][downi]=movingPieces;
                }

其中upi和upj代表落点的位置,downi和downj代表起点的位置,因为对应到touch事件中的DOWN和UP。

其中对于该轮到哪个棋子走的判断,这里引入两个变量,一个mFirst,用来记录是哪个棋子先走的,一个mCounts,用来记录步数,两个初始值都为0,当每一步完成时,设置mCounts加1。上面代码中有这个判断,如果mCounts=1,即第一步完成时,就把第一步移动的棋子赋值给mFirst。

那么如何判断是否轮到呢,很简单,我们发现,如果已走的步数为偶数时,那么当前是轮到第一个走的一方移动棋子了,如果为奇数,则轮到另一方,具体代码如下:

                //正在移动
                if (mFirst==0
                        ||(mFirst==movingPieces&&mCounts%2==0)     //是否轮到的条件,如果没轮到则不移动
                        ||(mFirst!=movingPieces&&mCounts%2==1)){
                    isMoving=true;
                }else {
                    //如果不轮到走的话之前的要放大显示
                    positionCondition[downj][downi] =movingPieces+ 2;
                }

这个判断写在touch事件的MOVE事件中,如果不是轮到你而你点击了话,就相应放大该棋子但不作移动(isMoving为false).

至此需求4和需求5均得到满足,游戏就基本开发完成了。

重新开始

游戏结束后要重新开始,只需要将一开始的二维数组恢复初始值,并设置mFirst和mCounts为0就可以了。

这里写一个公共方法来响应actionBar上的重新开始操作:

    //重新开始游戏
    public void restartGame(){
        for (int i=0;i<=Max_LINE;i++){
            positionCondition[0][i]=WHITE_PIECES;
        }
        for (int i=0;i<=Max_LINE;i++){
            positionCondition[1][i]=NO_PIECES;
        }
        for (int i=0;i<=Max_LINE;i++){
            positionCondition[2][i]=BLACK_PIECES;
        }
        mFirst=0;
        mCounts=0;
        invalidate();
    }


源码放在GitHub上,欢迎大家star:https://github.com/w-kahn/SimpleChess









猜你喜欢

转载自blog.csdn.net/w_kahn/article/details/51485527