Android小游戏——简单易懂单机人人对战五子棋源码详解

Android小游戏——简单易懂单机人人对战五子棋源码详解

最近有人来555974449刘某人的群里问五子棋源码,由于我也是Android菜鸡于是心血来潮就去研究了单机版的五子棋综合了一些大牛的博客,为了防止忘记就所以记录下来,有什么不好处欢迎指导,谢谢!
这里写图片描述

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@drawable/bg"
    tools:context="com.gx.wei.mywuziqi.MainActivity">

        <com.gx.wei.mywuziqi.WuziqiPanel
            android:layout_centerInParent="true"
            android:id="@+id/id_wuziqi"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </RelativeLayout>

自定义view的工具类

package com.gx.wei.mywuziqi;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by caobotao on 16/3/31.
 */
public class WuziqiPanel extends View {
    //画笔
    private Paint mPaint = new Paint();

  //(一)画棋盘所需的属性
    //线条数量
    private static final int MAX_LINE = 10;
    //线条的宽度
    private int mPanelWidth;
    //线条的高度
    private float mLineHeight;

    //(二)画棋子所需的属性
    //黑棋子
    private Bitmap mBlackPiece;
    //白棋子
    private Bitmap mWhitePiece;
    //比例,棋子的大小是高的四分之三
    private float rowSize = 3 * 1.0f / 4;
    //存储用户点击的坐标
    private ArrayList<Point> mWhiteArray = new ArrayList<>();
    private ArrayList<Point> mBlackArray = new ArrayList<>();
    //标记,是执黑子还是白子 ,白棋先手
    private boolean mIsWhite = true;

   //(三)游戏逻辑所需属性
    //胜利棋子数量
    private static final int MAX_COUNT_IN_LINE = 5;
    private boolean isGameOver = false;  //判读是否游戏结束
    private final int INIT_WIN = -1;            //游戏开始时的状态
    public static final int WHITE_WIN = 0;      //白棋赢
    public static final int BLACK_WIN = 1;      //黑棋赢
    public static final int NO_WIN = 2;         //和棋
    private int mGameWinResult = INIT_WIN;      //初始化游戏结果
    private OnGameStatusChangeListener listener;//游戏状态监听器

    public WuziqiPanel(Context context) {
        this(context, null);
    }

    public WuziqiPanel(Context context, AttributeSet attrs) {
        super(context, attrs);

        //0x44ff0000红色半透明,使得运行的时候可以具体的看到View所占据的大小
        //setBackgroundColor(0x44ff0000);
        initPaint();
    }
    //设置游戏状态监听器
    public void setOnGameStatusChangeListener(OnGameStatusChangeListener listener) {
        this.listener = listener;
    }
    //一.设置棋盘的大小(设置一个正方形)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //获取高宽值
        /**
         * mode分为:
         * EXACTLY:EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
         * AT_MOST:最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
         * UNSPECIFIED:未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式
         */
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int hightSize = MeasureSpec.getSize(heightMeasureSpec);
        int hightMode = MeasureSpec.getMode(heightMeasureSpec);

        //想把网格棋盘绘制成正方形
        //如果传入的是一个精确的值,就直接取值
        //同时也考虑到获得的widthSize与heightSize是设置的同样的值(如固定的100dp),但也有可能是match_parent,所以在这里取最小值
        //拿到宽和高的最小值,也就是宽
        int width = Math.min(widthSize, heightMeasureSpec);

        //根据测量模式细节处理(不懂的话百度下MeasureSpec的用法)
        //注意这里为else if,限定heightMode==MeasureSpec.UNSPECIFIED时才进入,使得逻辑更加严谨
        if (widthMode == MeasureSpec.UNSPECIFIED) {
            width = hightSize;
        } else if (hightMode == MeasureSpec.UNSPECIFIED) {
            width = widthSize;
        }
        //将宽和高设置为同样的值
        //在重写onMeasure方法时,必需要调用该方法存储测量好的宽高值
        //设置这样就是一个正方形了
        setMeasuredDimension(width, width);
    }
    //二、画棋盘里的线
    //1.先设置线的长宽
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //拿到宽
        mPanelWidth = w;
        //分割
        mLineHeight = mPanelWidth * 1.0f / MAX_LINE;

        //棋子宽度
        int mWhiteWidth = (int) (mLineHeight * rowSize);
        //修改棋子大小,根据实际的棋盘格子的宽度按照一定的比例缩小棋子
        mWhitePiece=Bitmap.createScaledBitmap(mWhitePiece,mWhiteWidth,mWhiteWidth,false);
        mBlackPiece=Bitmap.createScaledBitmap(mBlackPiece,mWhiteWidth,mWhiteWidth,false);
    }
    /**
     * 2.初始化画笔
     */
    private void initPaint() {
        //设置颜色
        mPaint.setColor(Color.BLACK);// 0x88000000(灰色可替换Color.BLACK)
        //抗锯齿(图像边缘相对清晰一点,锯齿痕迹不那么明显,即让画出来的线平滑)
        mPaint.setAntiAlias(true);
        //设置防抖动(使图像更柔和一点)
        mPaint.setDither(true);
        //设置Style设置画笔的风格为空心
        mPaint.setStyle(Paint.Style.STROKE);
        //设置线条宽度,加粗线条
        mPaint.setStrokeWidth(3);
        //获取棋子的资源文件(画棋子时加)
        initBitmap();
        /*mWhitePiece = BitmapFactory.decodeResource(getResources(), R.drawable.stone_w2);
        mBlackPiece = BitmapFactory.decodeResource(getResources(), R.drawable.stone_b1);*/
    }
    //3.开始画线,需要引入画板(onDraw方法)才能显示
    private void drawLine(Canvas canvas) {
        //获取高宽
        int w = mPanelWidth;
        float lineHeight = mLineHeight;
        //遍历,绘制线条
        for (int i = 0; i < MAX_LINE; i++) {
            //横坐标
            int startX = (int) (lineHeight / 2);
            int endX = (int) (w - lineHeight / 2);
            //纵坐标
            int y = (int) ((0.5 + i) * lineHeight);
            //绘制横
            canvas.drawLine(startX, y, endX, y, mPaint);
            //绘制纵
            canvas.drawLine(y, startX, y, endX, mPaint);
        }

    }
    //4.设置画板(初始化游戏数据)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLine(canvas);//引入画的棋盘线
        drawPieces(canvas);//引入画的棋子
        IsGameOverMethod();//判断是否胜利
    }

    //三、画棋子
    /**
     * 1.先在上面定义上面定义一些棋子的属性,上面写好了
    /**
     * 2.初始化棋子
     */
    private void initBitmap() {
        //拿到图片资源
        mWhitePiece = BitmapFactory.decodeResource(getResources(), R.drawable.stone_w2);
        mBlackPiece = BitmapFactory.decodeResource(getResources(), R.drawable.stone_b1);
    }
    //3.拿到资源之后我们就可以设置大小了,我们在上面onSizeChanged()里面设置将要显示的棋子的样式
    //4.写棋盘上的触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            //按下事件
            case MotionEvent.ACTION_UP:
                int x = (int) event.getX();
                int y = (int) event.getY();
                //封装成一个Point
                Point p = getValidPoint(x, y);
                //判断当前这个点是否有棋子了
                if(mWhiteArray.contains(p) || mBlackArray.contains(p)){
                    //点击不生效
                    return false;
                }
                //判断如果是白子就存白棋集合,反之则黑棋集合
                if (mIsWhite) {
                    mWhiteArray.add(p);
                } else {
                    mBlackArray.add(p);
                }
                //刷新
                invalidate();
                //改变值
                mIsWhite = !mIsWhite;
                break;
        }
        return true;
    }

    //不能重复点击
    private Point getValidPoint(int x, int y) {
        return new Point((int) (x / mLineHeight), (int) (y / mLineHeight));
    }
    //5.开始画棋子
    private void drawPieces(Canvas canvas) {
        for (int i = 0; i < mWhiteArray.size(); i++) {
            //获取白棋子的坐标
            Point whitePoint = mWhiteArray.get(i);
            canvas.drawBitmap(mWhitePiece, (whitePoint.x + (1 - rowSize) / 2) * mLineHeight, (whitePoint.y + (1 - rowSize) / 2) * mLineHeight, null);
        }

        for (int i = 0; i < mBlackArray.size(); i++) {
            //获取黑棋子的坐标
            Point blackPoint = mBlackArray.get(i);
            canvas.drawBitmap(mBlackPiece, (blackPoint.x + (1 - rowSize) / 2) * mLineHeight, (blackPoint.y + (1 - rowSize) / 2) * mLineHeight, null);
        }
    }
    /**
     * 四.游戏逻辑(成米字型的四种胜利的方法)
     */
    //1.先写一个判断游戏胜利的方法
    private void IsGameOverMethod() {
        //判断白棋是否有五个相同的棋子相连
        boolean mWhiteWin = checkFiveLine(mWhiteArray);
        //判断黑棋是否有五个相同的棋子相连
        boolean mBlackWin = checkFiveLine(mBlackArray);
        //判读是否平局
        boolean noWin = checkNoWin(mWhiteWin,mBlackWin);

        if (mWhiteWin) {
            mGameWinResult = WHITE_WIN;
        } else if (mBlackWin) {
            mGameWinResult = BLACK_WIN;
        } else if(noWin){
            mGameWinResult = NO_WIN;
        }
        if (mWhiteWin || mBlackWin || noWin) {
            isGameOver = true;
            //回调游戏状态接口
            if (listener != null) {
                listener.onGameOver(mGameWinResult);//让接口获得游戏结果
            }
        }
    }
    /**
     * 2.判断棋子是否有五个相同的棋子相连
     **/
    private boolean checkFiveLine(List<Point> points) {
        //遍历棋子
        for (Point p : points) {
            //拿到棋盘上的位置
            int x = p.x;
            int y = p.y;
            //四种情况胜利,横,竖,左斜,右斜
            //横
            boolean win = checkHorizontal(x, y, points);
            if (win) return true;
            //竖
            win = checkVertical(x, y, points);
            if (win) return true;
            //左斜
            win = checkLeft(x, y, points);
            if (win) return true;
            //右斜
            win = checkRight(x, y, points);
            if (win) return true;
        }
        return false;
    }
    /**
     * 判断横向的棋子
     */
    private boolean checkHorizontal(int x, int y, List<Point> points) {

        //棋子标记,记录是否有五个  =1是因为自身是一个
        int count = 1;
        //左
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y))) {
                count++;
            } else {
                break;
            }
        }
        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {
            return true;
        }
        //右
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y))) {
                count++;
            } else {
                break;
            }
        }
        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {
            return true;
        }
        return false;
    }
    /**
     * 判断纵向的棋子
     */
    private boolean checkVertical(int x, int y, List<Point> points) {

        //棋子标记,记录是否有五个  =1是因为自身是一个
        int count = 1;

        //上
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        //下
        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }
    /**
     * 判断左斜向的棋子
     *
     * @param x
     * @param y
     * @param points
     */
    private boolean checkLeft(int x, int y, List<Point> points) {

        //棋子标记,记录是否有五个  =1是因为自身是一个
        int count = 1;

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }
    /**
     * 判断右斜向的棋子
     **/
    private boolean checkRight(int x, int y, List<Point> points) {

        //棋子标记,记录是否有五个  =1是因为自身是一个
        int count = 1;

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x - i, y - i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
            //如果有
            if (points.contains(new Point(x + i, y + i))) {
                count++;
            } else {
                break;
            }
        }

        //有五个就为true
        if (count == MAX_COUNT_IN_LINE) {

            return true;
        }

        return false;
    }
    //检查是否和棋
    private boolean checkNoWin(boolean whiteWin, boolean blackWin) {
        if (whiteWin || blackWin) {
            return false;
        }
        int maxPieces = MAX_LINE * MAX_LINE;
        //如果白棋和黑棋的总数等于棋盘格子数,说明和棋
        if (mWhiteArray.size() + mBlackArray.size() == maxPieces) {
            return true;
        }
        return false;
    }

    /**
     * 五.游戏的暂时存储
     * 如果遇到接电话、屏幕旋转等情况,当重新进入游戏的时候,会使得开始的棋子的布局都丢失
     * 当View被销毁时需要保存游戏数据
     */
    private static final String INSTANCE = "instance";
    private static final String INSTANCE_GAME_OVER = "instance_game_over";
    private static final String INSTANCE_WHITE_ARRAY = "instance_white_array";
    private static final String INSTANCE_BLACK_ARRAY = "instance_black_array";

    //保存游戏数据
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(INSTANCE,super.onSaveInstanceState());
        bundle.putBoolean(INSTANCE_GAME_OVER, isGameOver);
        bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY, mWhiteArray);
        bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY, mBlackArray);
        return bundle;
    }

    //恢复游戏数据
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            isGameOver = bundle.getBoolean(INSTANCE_GAME_OVER);
            mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY);
            mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY);
            super.onRestoreInstanceState(bundle.getParcelable(INSTANCE));
            return;
        }
        super.onRestoreInstanceState(state);
    }
    //重新开始游戏
    public void restartGame() {
        mWhiteArray.clear();
        mBlackArray.clear();
        isGameOver = false;
        mGameWinResult = INIT_WIN;
        invalidate();
    }
}

游戏监听接口

package com.gx.wei.mywuziqi;

public interface OnGameStatusChangeListener {
    void onGameOver(int gameWinResult);//游戏结束
}

MainActivity

package com.gx.wei.gobang;

import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;

public class MainActivity extends AppCompatActivity{
    private WuziqiPanel mGamePanel;
    private AlertDialog.Builder alertBuilder;
    private AlertDialog alertDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        //游戏结束时弹出对话框
        alertBuilder = new AlertDialog.Builder(MainActivity.this);
        alertBuilder.setPositiveButton("再来一局", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                mGamePanel.restartGame();
            }
        });
        alertBuilder.setNegativeButton("退出游戏", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                MainActivity.this.finish();
            }
        });
        alertBuilder.setCancelable(false);
        alertBuilder.setTitle("此局结束");

        mGamePanel = (WuziqiPanel) findViewById(R.id.id_wuziqi);
        mGamePanel.setOnGameStatusChangeListener(new OnGameStatusChangeListener() {
            @Override
            public void onGameOver(int gameWinResult) {
                switch (gameWinResult) {
                    case WuziqiPanel.WHITE_WIN:
                        alertBuilder.setMessage("白棋胜利!");
                        break;
                    case WuziqiPanel.BLACK_WIN:
                        alertBuilder.setMessage("黑棋胜利!");
                        break;
                    case WuziqiPanel.NO_WIN:
                        alertBuilder.setMessage("和棋!");
                        break;
                }
                alertDialog = alertBuilder.create();
                alertDialog.show();
            }
        });
    }
}

最后打一波广告:源码已放上555974449群,“通往Android的神奇之旅”欢迎来讨论Android或非Android的学习问题!

  • 12
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值