成功的路上一点也不拥挤,因为坚持的人太少了。

                                                         ---简书上看到的一句话

未来请假三天顺带加上十一回家结婚,不得不说真是太坑了,去年婚假还有10天,今年一下子缩水到了3天,只能赶着十一办事了。

最近还在看数据结构,打算用java实现一遍,所以没着急写读书笔记,不过前段时间看了一个简单的五子棋游戏,记录一下。

整体效果如下,整个功能在一个自定义View里面实现:

由于主activity比较简单直接列出来:

public class MainActivity extends Activity {

    private static String TAG ="MainActivity111";

    private FiveView fiveView;
    private Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext=this;
        fiveView = (FiveView) findViewById(R.id.five);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // TODO Auto-generated method stub
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId()==R.id.action_settings){
            fiveView.setrestart();
        }
        return true;
    }

}
<RelativeLayout 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" >

    <com.lly.simple_five.FiveView
        android:id="@+id/five"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </com.lly.simple_five.FiveView>

</RelativeLayout>

上面只有一点 android:layout_centerInParent=”true” 设置自定义view居中,这样看起来比较美观。

主要代码在FiveView中实现,

考虑五子棋游戏一共有几个步骤:

1、画棋盘

2、根据用户选择在指定位置画棋子

3、判定输赢

4、需要有个重新开始的菜单,当确定输赢后重新开始游戏。

1、画棋盘

棋盘需要手动画出来,所以这里自定义了一个view

public class FiveView extends View {
    public FiveView(Context context, AttributeSet attrs) {
            super(context, attrs);
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(0x88000000);
            setBackgroundColor(0x44440000);
            mBlackList = new ArrayList<Point>();
            mWhileList = new ArrayList<Point>();

        }
    ...
}

然后重新他的两个参数的构造方法,为什么要写两个构造参数的方法呢,这是因为这里只需要用自定义view的布局,不需要自定义属性。

在构造方法里面我们初始化了画笔,棋盘的背景,并实例化保存棋子的list。

这里设置了这个view的背景颜色,以方便看出这个view的位置

小知识:

一般情况下view有三个构造方法,其中带一个参数的构造方法是在activity中new一个控件时调用的。如TextView tv = new TextView(mContext);

在xml中使用不带自定义属性的自定义控件时会调用两个参数的构造方法,如本例。

在xml中使用带自定义属性的自定义控件时,会调用带三个参数的构造方法。

这里想想,我们前面定义的view宽和高都是占满了整个屏幕,所以在手机上看到的就是一个长方行的布局,但是一般我们在现实中看到的棋盘都是正方形的,这也很好实现,

自定义view里面的测量方法能很好的解决这个问题:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Width = MeasureSpec.getSize(widthMeasureSpec);
        Hight = MeasureSpec.getSize(heightMeasureSpec);
        int WidthMode = MeasureSpec.getMode(widthMeasureSpec);

        Width = Math.min(Width, Hight);
        int measuredWidth = MeasureSpec.makeMeasureSpec(Width, WidthMode);
        setMeasuredDimension(measuredWidth, measuredWidth);

    }

如上代码 我们只需要取出测量的宽高,然后以宽高中较小的值作为棋盘的宽高,这样不管是横屏或竖屏都能得到一个正方形的棋盘了,

小知识

view中的测试模式有三种也就是上面getMode取出来的值,分别对应

match_parent

wrap_content

xxxxxdp

规范的操作在自定义测量方法里面要对这三种模式分别去出来,这里比较简单就不做出来了。

如上我们得到了棋盘的宽高。

在画棋盘之前还需要考虑下,我们棋盘应该怎么画 ,画多少条线,

这里我们在棋盘的大小发生改变时初始化一些初始化棋盘的操作

...
    private static final int MAX_LINE = 10;
...
@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBroad = Width;
        mLineWidth = mBroad * 1.0f / MAX_LINE;
        mBlackPiece = BitmapFactory.decodeResource(getResources(),
                R.drawable.stone_b1);
        mWhilePiece = BitmapFactory.decodeResource(getResources(),
                R.drawable.stone_w2);
        int dstpoint = (int)(mLineWidth*roation);
        mBlackPiece = Bitmap.createScaledBitmap(mBlackPiece, dstpoint, dstpoint, false);
        mWhilePiece = Bitmap.createScaledBitmap(mWhilePiece, dstpoint, dstpoint, false);
    }

上面代码 ,我们定义了棋盘的宽高为测量时的宽高,

定义了两条线之间的宽度,即10平分整个区域

另外初始化了黑白棋子这个在画棋子的时候具体说明。

接着就需要去画棋盘了


    protected void onDraw(Canvas canvas) {
        DrawBroad(canvas);
        DrawPiece(canvas);
        checkGameOver();
    }
    private void DrawBroad(Canvas canvas) {
        for (int i = 0; i < MAX_LINE; i++) {
            float startX = mLineWidth / 2;
            float stopX = mBroad - (mLineWidth / 2);
            float startY = (float) ((0.5 + i) * mLineWidth);
            float stopY = (float) ((0.5 + i) * mLineWidth);
            canvas.drawLine(startX, startY, stopX, stopY, mPaint);

            canvas.drawLine(startY, startX, stopY, stopX, mPaint);
        }
    }
可以发现在上面代码中,调用DrawBroad 去画棋盘,分别横竖画了十条线,组成了棋盘,这里发现在横竖开始的时候都是从0.5*mLineWidth开始的,这是为了留出上面一点空隙,可以使棋子可以显示完整,这里用来一个for循环就完成了横竖线,归功于这是一个正方形,x,y坐标互换一下就可以实现画横竖线了。
到这里棋盘就已经画好了

2、画棋子

这个比较复杂 我们在分几步实现;

1)、首先要实例化两种颜色的棋子
2)、想想在现实中下五子棋的时候每人手边有一个盒子装着自己的棋子,这里我们要有我们的盒子来保存我们的棋子,
3)、画棋子是根据客户手指按下的位置进行画的,所以要实现onTouchEvent方法
4)、画棋子

1)、实例和两种颜色的棋子

这个其实上在上面初始化变量的时候就已经做过了。

private static final float roation = 3*1.0f/4;
mBlackPiece = BitmapFactory.decodeResource(getResources(),
                R.drawable.stone_b1);
        mWhilePiece = BitmapFactory.decodeResource(getResources(),
int dstpoint = (int)(mLineWidth*roation);
        mBlackPiece = Bitmap.createScaledBitmap(mBlackPiece, dstpoint, dstpoint, false);
        mWhilePiece = Bitmap.createScaledBitmap(mWhilePiece, dstpoint, dstpoint, false);

这里的一个小技巧就是让棋子占每格的3/4,这样不管我们传多大棋子图片都能正确的显示出来,并且只占3/4的格子。

2)、实现黑白棋的棋盒

其实我们在构造函数里面已经实例化了两个盒子

        mBlackList = new ArrayList<Point>();
        mWhileList = new ArrayList<Point>();

只不过这里还没有棋子,这里我们的棋盒里面的棋子其实都是下在棋盘上的棋子。

3)、用户下棋也就是触发onTouchEvent方法

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if(IsGameOver) return false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        Point p = getPoint(x,y);

        if(event.getAction()==MotionEvent.ACTION_UP){
            if(mWhileList.contains(p)||mBlackList.contains(p)){
                return false;
            }
            if(isWhile){
                mWhileList.add(p);
            }else{
                mBlackList.add(p);
            }
            invalidate();
            isWhile=!isWhile;
        }
        return true;
    }

    private Point getPoint(int x, int y) {

        return new Point((int)(x/mLineWidth),(int)(y/mLineWidth));
    }

这里当用户下棋时,获取所下的位置的坐标,这里有一个小技巧

在保存所下棋子的x,y坐标时我们让x,y分别除每个格子的大小,这样所获得的位置一定在棋盘横竖的交界点上,这个可以去体会一下,

然后当用户抬起手指时我们去判断下这个位置是不是已经有棋子了,有的话直接return false;什么都不做,没有的话,看下当前该谁下了,把这个棋子添加到对应的棋盒里,并通知刷新棋盘

4)、画棋子

最后就是把现在棋盒里面的棋子画到棋盘中

private void DrawPiece(Canvas canvas) {
        for(int i=0,n=mWhileList.size();i<n;i++){
            Point WhilePiece = mWhileList.get(i);
            canvas.drawBitmap(mWhilePiece, (WhilePiece.x+(1-roation)/2)*mLineWidth,
                    (WhilePiece.y+(1-roation)/2)*mLineWidth, mPaint);

        }
        for(int i=0,n=mBlackList.size();i<n;i++){
            Point BlackPiece = mBlackList.get(i);
            canvas.drawBitmap(mBlackPiece, (BlackPiece.x+(1-roation)/2)*mLineWidth,
                    (BlackPiece.y+(1-roation)/2)*mLineWidth, null);
        }

    }

这步就很简单了 循环遍历棋盒里面的棋子并把它画出来,唯一要注意的就是棋子的位置,

 (WhilePiece.x+(1-roation)/2)*mLineWidth,
                    (WhilePiece.y+(1-roation)/2)*mLineWidth

看下图:

以第一个点的x坐标为例:

因为开始的时候设定的棋盘开始的x坐标离我们View的左边距是0.5*mLineWidth,然后在初始化棋子的时候棋子的大小是3/4*mLineWidth,现在我们要计算棋子的左边距到view左边距的距离

所以棋子的x位置就应该是(0.5-3/4/2)*mLineWidth,第二个点就是(1.5-3/4/2)*mLineWidht,其中0.5,1.5是我们下子的位置系数即WhilePiece.x,所以最后提取出来就是(WhilePiece.x+(1-roation)/2)*mLineWidth,,竖坐标也是同意道理,这个要好好理解一下

3、判定输赢

首先要确定在哪里判断是否游戏一方胜利,可以看出在画棋子的时候是最好的时机了 当游戏结束后就不再画棋子。


    private void checkGameOver() {
        boolean Whilewin = checkWhileFive(mWhileList);
        boolean Blackwin = checkWhileFive(mBlackList);
        if(Whilewin){
            IsGameOver=true;
            Toast.makeText(getContext(), "白旗胜利", Toast.LENGTH_SHORT).show();
        }
        if(Blackwin){
            IsGameOver=true;
            Toast.makeText(getContext(), "黑棋胜利", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean checkWhileFive(List<Point> points) {
        for(Point p:points){
            int x = p.x;
            int y = p.y;

             boolean Horizewin =checkHorizefive(x,y,points);
             boolean verwin =checkverfive(x,y,points);
             boolean leftwin =checkleftfive(x,y,points);
             boolean reghitwin =checkreghitfive(x,y,points);
             if(Horizewin||verwin||leftwin||reghitwin){
                 return true;
             }

        }
        return false;
    }
    private boolean checkHorizefive(int x, int y, List<Point> points) {
        int count=1;
        for(int i=1;i<FIVE_WIN;i++){
            if(points.contains(new Point(x+i,y))){
                count++;
            }else{
                break;
            }
        }
        if(count ==FIVE_WIN) return true;
        for(int i=1;i<FIVE_WIN;i++){
            if(points.contains(new Point(x-i,y))){
                count++;
            }else{
                break;
            }
        }
        if(count ==FIVE_WIN) return true;
        return false;

    }

上面代码首先在ondraw中调用checkGameover方法检查游戏是否结束

检查的方法就是分别去判断白棋和黑球是否达到五子连珠的效果。

上面贴出来了横向五子连珠的判断,取出当前棋子循环检查他的左边是否有五个相同颜色的棋子,有就返回游戏结束,没有的话在去检查右边是否有五个相同颜色的棋子,有的话返回游戏结束,这样横竖,左斜,右斜都判断后就可以确定游戏是否结束,

结束的话在onTouchEvent方法中直接返回false表示我们不需要这个事件了。

到此这个简单的五子棋差不多就完成了。

4、添加重新开始菜单

为了使它更完善一点我们加入了 重新开始的菜单键,

这个应该没什么难度,主要就是按下重新开始的时候

修改一些变量的初始值

public void setrestart() {
        IsGameOver=false;
        mWhileList.clear();
        mBlackList.clear();
        invalidate();
    }

最后的最后当app异常退出的时候,发现下了半天的棋子没有保存,因此这里加入

    private String INSTANCE = "instaNce";
    private String INSTANCE_GEMEOVER = "instance_gameover";
    private String INSTANCE_WHILEARRAY = "instance_whilearray";
    private String INSTANCE_BLACKARRAY = "instance_blackarray";

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(INSTANCE, super.onSaveInstanceState());
        bundle.putBoolean(INSTANCE_GEMEOVER, IsGameOver);
        bundle.putParcelableArrayList(INSTANCE_WHILEARRAY, mWhileList);
        bundle.putParcelableArrayList(INSTANCE_BLACKARRAY, mBlackList);

        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if(state instanceof Bundle){
            Bundle bundle = (Bundle) state;
            IsGameOver = bundle.getBoolean(INSTANCE_GEMEOVER);
            mWhileList = bundle.getParcelableArrayList(INSTANCE_WHILEARRAY);
            mBlackList = bundle.getParcelableArrayList(INSTANCE_BLACKARRAY);
            super.onRestoreInstanceState(bundle.getBundle(INSTANCE));
            return;
        }
        super.onRestoreInstanceState(state);
    }

处理异常退出的情况。

GAME OVER。。。。

源码

github地址

自定义View实现五子棋游戏的更多相关文章

  1. 利用自定义View实现扫雷游戏

    游戏规则: 简单版的扫雷事实上就是一个9×9的矩阵,其中有十个点是雷,非雷方块的数字代表该方块周围八个方块中雷的个数.通过长按某一方块(方块会变红)认定该方块为玩家认为的雷,通过短按某一方块来“展开” ...

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

    Android实训案例(八)--单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局 阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就 ...

  3. android愤怒小鸟游戏、自定义View、掌上餐厅App、OpenGL自定义气泡、抖音电影滤镜效果等源码

    Android精选源码 精练的范围选择器,范围和单位可以自定义 自定义View做的小鸟游戏 android popwindow选择商品规格颜色尺寸效果源码 实现Android带有锯齿背景的优惠样式源码 ...

  4. Android -- 自定义View小Demo,绘制钟表时间(一)

    1,昨天刚看了hongyang大神推荐的自定义时钟效果(传动门:http://www.jianshu.com/users/a45d19d680af/),效果还是不错的,自己又在github上找了找,发 ...

  5. (转载)自定义View——弹性滑动

    滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍. 一.scrollTo与ScrollBy View提供了专门的方法用于实现滑动效果,分 ...

  6. 【朝花夕拾】Android自定义View篇之(八)多点触控(上)MotionEvent简介

    前言 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2 ...

  7. 自定义View4-塔防小游戏第一篇:一个防御塔+多个野怪(简易版)*

    塔防小游戏 第一篇:一个防御塔+多个野怪(简易版)    1.canvas画防御塔,妖怪大道,妖怪行走路线    2.防御塔攻击范围是按照妖怪与防御塔中心距离计算的,大于防御塔半径则不攻击,小于则攻击 ...

  8. 自定义view(一)

    最近在学习自定义view  一遍看一别学顺便记录一下 1.View的测量-------->onMeasure() 首先,当我们要画一个图形的时候,必须知道三个数据:位置,长度,宽度   才能确定 ...

  9. Android 自定义View及其在布局文件中的使用示例

    前言: 尽管Android已经为我们提供了一套丰富的控件,如:Button,ImageView,TextView,EditText等众多控件,但是,有时候在项目开发过程中,还是需要开发者自定义一些需要 ...

随机推荐

  1. amd屏幕亮度无法调整,无法调节亮度

    1:CMD+R键打开"运行",输入"regedit"进入注册表 2:搜索"KMD_EnableBrightnessInterface2",找 ...

  2. 下载python的Crypto库出现的问题的解决:ModuleNotFoundError: No module named 'Crypto'

    在网上找了很多下载Crypto的方法,感觉作用都不算很大,然后自己瞎搞瞎搞就搞好了

  3. [ZJOI 2008]泡泡堂BNB

    Description 题库链接 双方 \(n\) 人,给出每人的战斗力,赢一场加 \(2\) 分,平局 \(1\) 分,失败不得分.求最大和最小的得分. \(1\leq n\leq 100000\) ...

  4. [SDOI 2008]仪仗队

    Description 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是 ...

  5. [JSOI2007]建筑抢修

    Description 小刚在玩JSOI提供的一个称之为“建筑抢修”的电脑游戏:经过了一场激烈的战斗,T部落消灭了所有z部落的 入侵者.但是T部落的基地里已经有N个建筑设施受到了严重的损伤,如果不尽快 ...

  6. bzoj 5286: [Hnoi2018]转盘

    Description Solution 首先注意到一个点不会走两次,只会有停下来等待的情况,把序列倍长 那么如果枚举一个起点\(i\),答案就是 \(min(max(T[j]+n-(j-i)-1)) ...

  7. bzoj 3244: [Noi2013]树的计数

    Description 我们知道一棵有根树可以进行深度优先遍历(DFS)以及广度优先遍历(BFS)来生成这棵树的DFS序以及BFS序.两棵不同的树的DFS序有可能相同,并且它们的BFS序也有可能相同, ...

  8. 洛谷P2572 [SCOI2010]序列操作

    线段树 pushdown写的很浪~ #include<cstdio> #include<cstdlib> #include<algorithm> #include& ...

  9. allocator

    allocator: 通常c++内存配置和释放操作是这样的: class Fo{}; Fo *p = new Fo; delete p; new算式主要有三个阶段: 调用::operator new配 ...

  10. ●BZOJ 4665 小w的喜糖

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4665 题解: 容斥,dp令 v[i] 表示原来拥有i类糖果的人数. (一个套路,首先把每个糖 ...