Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局


阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就学习了一下,写篇博客梳理一下自己的思路,加深一下印象

  • 视频链接:http://www.imooc.com/learn/641

一.棋盘

我们一看就知道,我们必须自定义View,这里我们定义一个GameView来做游戏主类,第一步,先测量,我们这里不难知道,五子棋他的棋盘是一个正方形,所以我们需要去测量

 /**     * 测量     *     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //获取高宽值        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int hightSize = MeasureSpec.getSize(heightMeasureSpec);        int hightMode = MeasureSpec.getMode(heightMeasureSpec);        //拿到宽和高的最小值,也就是宽        int width = Math.min(widthSize, heightMeasureSpec);        //根据测量模式细节处理        if (widthMode == MeasureSpec.UNSPECIFIED) {            width = hightSize;        } else if (hightMode == MeasureSpec.UNSPECIFIED) {            width = widthSize;        }        //设置这样就是一个正方形了        setMeasuredDimension(width, width);    }

这里的逻辑还是十分简单的,我们拿到长和宽去比较一下,设置这个View的长宽Wie最小值,就是一个正方形了,所以我们的layout_main.xml是这样写的

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:background="@drawable/main_bg"    >    <com.lgl.fiverow.GameView        android:layout_width="match_parent"        android:layout_height="match_parent" />LinearLayout>

这里我在构造方法中设置了一个半透明的红色背景,是在我们调试的时候可以更加清晰的看清楚GameView的大小,所以,运行的结果

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第1张图片

二.线条

这个应该也算是棋盘的一部分吧,就是棋盘上的线条,我们应该怎么去画,首先,我们要去定义一些属性

    //线条数量    private static final int MAX_LINE = 10;    //线条的宽度    private int mPanelWidth;    //线条的高度    private float mLineHeight;

然后,我们要去确定大小

/**     * 测量大小     *     * @param w     * @param h     * @param oldw     * @param oldh     */    @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;    }

不要着急,这些都只是一些准备的工作,我们画线条是必须要在onDraw(0方法里的,但是前期我们要准备一只画笔,对吧,所以我们要初始化画笔

     /**     * 初始化画笔     */    private void initPaint() {        //设置颜色        mPaint.setColor(0x88000000);        //抗锯齿        mPaint.setAntiAlias(true);        //设置防抖动        mPaint.setDither(true);        //设置Style        mPaint.setStyle(Paint.Style.STROKE);    }

现在我们可以去绘制了,我们在OnDraw(0方法里写一个drawLine方法来专门绘制线条

 /**     * 绘制棋盘的方法     *     * @param canvas     */    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);        }    }

我们运行一下

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第2张图片

好的,这里,注意一下,我在activity_main.xml中定义了一个

android:gravity="center"

属性,所以让他居中,同样的,我们在initPaint中加上点代码让我们看的更加直观一点

//设置颜色mPaint.setColor(Color.BLACK);//设置线条宽度mPaint.setStrokeWidth(3);

同样的,我们把构造法里的设置背景的测试代码注释掉

//测试代码//setBackgroundColor(0x44ff0000);

这样,我们运行一下

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第3张图片

得,我们现在有模有样了

三.棋子

棋子我们事先准备好了两张图片,但是这里我们要考虑他的大小的问题了,我们的思路是让他是行高的四分之三大小,所以先声明

 //黑棋子 private Bitmap mBlack; //白棋子 private Bitmap mWhite;//比例,棋子的大小是高的四分之三 private float rowSize = 3 * 1.0f / 4;

然后我们定义一个方法区初始化Bitmap

     /**     * 初始化棋子     */    private void initBitmap() {        //拿到图片资源        mBlack = BitmapFactory.decodeResource(getResources(), R.drawable.stone_black);        mWhite = BitmapFactory.decodeResource(getResources(), R.drawable.stone_white);    }

拿到资源之后我们就可以设置大小了,我们在onSizeChanged()里面设置

 //棋子宽度 int mWhiteWidth = (int) (mLineHeight * rowSize); //修改棋子大小 mWhite = Bitmap.createScaledBitmap(mWhite, mWhiteWidth, mWhiteWidth, false); mBlack = Bitmap.createScaledBitmap(mBlack, mWhiteWidth, mWhiteWidth, false);

不过棋子可没我们想象的那么简单,我们要点击一下再去绘制一个棋子,这样的思路该怎么去实现呢?我们实现它的点击事件,这里先定义几个变量

    //存储用户点击的坐标    private List mWhiteArray = new ArrayList<>();    private List mBlackArray = new ArrayList<>();    //标记,是执黑子还是白子 ,白棋先手    private boolean mIsWhite = true;

这样才和触摸事件相得映彰

 /**     * 触摸事件     *     * @param event     * @return     */    @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;    }

这样,有几点是要说明一下的,首先我们new Point的时候为了避免重复绘制我们是实现了一个方法

/**     * 不能重复点击     *     * @param x     * @param y     * @return     */    private Point getValidPoint(int x, int y) {        return new Point((int) (x / mLineHeight), (int) (y / mLineHeight));    }

紧接着我们就判断,要是重复点击,返回false,而且我们在action选择也是选择了ACTION_UP,为什么?为什么不是ACTION_DOWN?因为这个毕竟是一个View,父View会拦截(某些场景),所以我们选在UP上才是合情合理的

好的,当我们点击之后就要绘制棋子了,这里我们也写一个方法

 /**     * 绘制棋子的方法     *     * @param canvas     */    private void drawPieces(Canvas canvas) {        for (int i = 0; i < mWhiteArray.size(); i++) {            //获取白棋子的坐标            Point whitePoint = mWhiteArray.get(i);            canvas.drawBitmap(mBlack, (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(mWhite, (blackPoint.x + (1 - rowSize) / 2) * mLineHeight, (blackPoint.y + (1 - rowSize) / 2) * mLineHeight, null);        }    }

OK,我们实际运行一下

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第4张图片

四.游戏逻辑

现在什么都有了,基本上都可用玩了,但是还少了重要的一点就是游戏结束,你到了五颗也需要判断是否胜利呀,对吧,我们写一个方法,在每次绘制完成之后就去判断是否有赢家

  /**     * 判断是否胜利     */    private void checkWin() {        //判断白棋是否有五个相同的棋子相连        boolean mWhiteWin = checkFiveLine(mWhiteArray);        //判断黑棋是否有五个相同的棋子相连        boolean mBlackWin = checkFiveLine(mBlackArray);        //只要有一个胜利,游戏就结束        if (mWhiteWin || mBlackWin) {            mIsGameOver = true;            mIsWhiteWin = mWhiteWin;            Toast.makeText(getContext(), mIsWhiteWin ? "白棋胜利" : "黑棋胜利", Toast.LENGTH_SHORT).show();        }    }

好的,我们重点逻辑就在checkFiveLine这个方法上了,这里,我们所知道的胜利有四种情况

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第5张图片

我们先定义一个常量

    //胜利棋子数量    private static final int MAX_COUNT_IN_LINE = 5;

OK,接下来我们可以实现以下胜利的逻辑了

 /**     * //判断棋子是否有五个相同的棋子相连     *     * @param points     * @return     */    private boolean checkFiveLine(List 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;    }

我们不管哪个方向只要返回true就返回true,然后弹Toast,这里,四个方向的逻辑

 /**     * 判断横向的棋子     *     * @param x     * @param y     * @param points     */    private boolean checkHorizontal(int x, int y, List 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;    }
 /**     * 判断纵向的棋子     *     * @param x     * @param y     * @param points     */    private boolean checkVertical(int x, int y, List 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 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;    }
  • 右斜
 /**     * 判断右斜向的棋子     *     * @param x     * @param y     * @param points     */    private boolean checkRight(int x, int y, List 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;    }

这样,我们运行一下

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第6张图片

嘿嘿,好玩吧!

五.游戏状态存储

这个就是当我们游戏挂后台之后,再回来游戏就没了,我们应该存储他的状态,让他每一次进入的时候要是上一句没有下完接着下,那我们该怎么去实现呢?和Activity一样,我们View也有存储状态的方法

  /**     * 存储状态     *     * @return     */    @Override    protected Parcelable onSaveInstanceState() {        Bundle bundle = new Bundle();        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());        bundle.putBoolean(INSTANCE_GAMEOVER, mIsGameOver);        bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY, mWhiteArray);        bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY, mBlackArray);        return bundle;    }    /**     * 重新运行     *     * @param state     */    @Override    protected void onRestoreInstanceState(Parcelable state) {        //取值        if (state instanceof Bundle) {            Bundle bundle = (Bundle) state;            mIsGameOver = bundle.getBoolean(INSTANCE_GAMEOVER);            mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY);            mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY);            //调用            super.onRestoreInstanceState(bundle.getParcelable(INSTANCE));            return;        }        super.onRestoreInstanceState(state);    }

这样就可以了,但是,有一点要知道,不要忘记在布局文件上给控件加上ID,不然状态不会存储哦

 <com.lgl.fiverow.GameView        android:id="@+id/mGameView"        android:layout_width="match_parent"        android:layout_height="match_parent" />

六.再来一局

既然我们的游戏逻辑差不多了,那我们应该考虑一下当你胜利的时候,你是不是应该再来一局,所以我们还要实现这个逻辑,这个很简单

   /**     * 再来一局     */    public void RestartGame() {        mWhiteArray.clear();        mBlackArray.clear();        mIsGameOver = false;        mIsWhiteWin = false;        invalidate();    }

这样,我们就可以直接调用了,我们来看看MainActivity

package com.lgl.fiverow;import android.support.design.widget.FloatingActionButton;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;/** * 五子棋游戏 */public class MainActivity extends AppCompatActivity {    //重来按钮    private FloatingActionButton fab;    //游戏    private GameView game;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        game = (GameView) findViewById(R.id.mGameView);        fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                game.RestartGame();            }        });    }}

OK,我们最终运行一下

Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局_第7张图片

OK,到这里,就算开发完成了

Demo下载:http://download.csdn.net/detail/qq_26787115/9521011

我的群:555974449欢迎你加入!

更多相关文章

  1. Android 图形:绘制渐变色奥运五环图形,游戏文字,验证码,Matrix旋转,缩
  2. 合体!PSP 和 Android 3.0 携手打造游戏手机王
  3. 闲聊一下android 3D 网络游戏
  4. 评论:Android真不适合做游戏
  5. 大学毕业论文-基于Android的五子棋游戏设计
  6. android拼图游戏

随机推荐

  1. JDK安装及注意事项
  2. 最丝滑的k8s部署工具Sealos,你说香不香?
  3. M1 版 MacBook SSD 为何会损耗巨大?
  4. 操作系统-进程的初步实现
  5. Linux内核开发双机调试(KGDB)
  6. 佳能iC MF4712怎么使用按键【扫描到计算
  7. MongoDB Server对于Sort排序能够支持的最
  8. vim命令
  9. LINUX 学习之路 yum仓库
  10. 公有云上基于微服务架构 SAAS 产品研发实