之前发布的AS安卓五子棋代码详细解释版本(原创,转载请联系)

//这个安卓项目在300行左右
package com.example.myapplication;
//import关键字是用来导入包的,package就相当于我们的姓,类class就相当于我们的名,接着在代码中就只用使用类,而不用写包名(姓)
import android.content.Context; //Context描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程,比如具体到某一个界面activity,对于view也属于一种context
import android.graphics.Bitmap; //bitmap位图图像格式
import android.graphics.BitmapFactory; //对我们导入的图片做一些操作
import android.graphics.Canvas; //画图canvas类必备,确定画线还是画什么东西
import android.graphics.Paint; //绘图,比如设置颜色
import android.graphics.Point; //点
import android.os.Bundle; //后方我将这个做注释
import android.os.Parcelable; //后方我将这个做注释
import android.util.AttributeSet; //使用AttributeSet来完成控件类的构造函数,自定义的view也需要这个支持
import android.view.MotionEvent; //触摸必须要用到的类
import android.view.View; //view视图
import android.widget.Toast; //toast显示浮动窗口
import androidx.annotation.Nullable; //有了这个之后,比如参数可以空
import java.util.ArrayList; //数组,可以插入各种类型的数据
import java.util.List; //数组,不能插入其他数据类型的数据
//extends view是一个父类(基类/超类),新的类是子类(派生类),实例就是一个例子,一个特定的变量值
//类是不能用private修饰的,当类被修饰成private没有任何意思,因为外部想要这个类时由于private的存在而无能为力
public class WuziqiPanel extends View //声明的公共类WuziqiPanel,在其他类中可以调用,但是private类型只能在本类(本java文件)中使用
{
//new&delete;free&malloc
private int mPanelWidth;//一个格子宽度
private float mLineHeight;//一个格子高度,由于是正方形,当然一样了,我这里要用mlineheight去后面做计算所以使用float类型
private int MAX_LINE = 12;//棋盘的画线最多需要多少列/多少行,这个意思是每行最多下几个棋子,或者每列最多下几个棋子
private float ratioPieceOfLineHight=3*1.0f/4; //我们绘制的棋子不能连在一块,这个意思是占半个宽度或者高度的3/4,所以最后的每个方块还有1/4个mlineheight长度
//转化为浮点数的方法是×1.0f
private Paint mPaint=new Paint(); //new是开辟一片内存空间,Paint类之后是mPaint(我定义的成员变量)
private Bitmap mWhitePiece; //Bitmap类声明,白色棋子
private Bitmap mBlackPiece; //Bitmap类声明,黑色棋子
private boolean mIsWhite= true; //白棋先手或者当前轮到白棋
private ArrayList mWhiteArray= new ArrayList<>(); //新建一个数组保存Point类型的数据
private ArrayListmBlackArray= new ArrayList<>();
private boolean mIsGameOver; //游戏是不是结束
private boolean mIsWhiteWinner; //true白棋胜利,false黑棋胜利
private int MAX_COUNT_IN_LINE=5; //五子连珠定义
//下面是核心代码WuziqiPanel是自定义的view
public WuziqiPanel(Context context,AttributeSet attrs) {//@Nullable注解:参数可为空值
super(context, attrs);
//setBackgroundColor(0x44ff0000);这是当时的测试view是否成功,使用的背景颜色(半透明)
//super只在子类中出现
,而且super有三种用法
【1】 super.xxx;
xxx可以是类的属性。
例如super.name;即从子类中获取父类name属性的值

    //【2】 super.xxx();

xxx()可以是类中的方法名。
super.xxx();的意义是直接访问父类中的xxx()方法并调用

    //【3】 super();

此方法意义是直接调用父类的某个构造方法,
super(无参/有参)即调用父类中的某个构造方法
init(); //对paint进行初始化,这个初始化里面有后续的所有方法,包罗万象
}
//对paint所有的画线工作进行初始化,所使用的方法setcolor,setantialias,setdither,setstyle,decoderesource
private void init() {
mPaint.setColor(0x8B008B00); //设置一个半深色画线
mPaint.setAntiAlias(true); //防锯齿
mPaint.setDither(true); //防抖动
mPaint.setStyle(Paint.Style.STROKE); //设置字体风格为空心,实心用Paint.Style.FILL
mBlackPiece = BitmapFactory.decodeResource(getResources(),R.drawable.stone_b1); //R这个类是自动生成的
//BitmapFactory.decodeResource(?,?)这个带两个参数的方法
//第一个参数是包含你要加载的位图资源文件的对象(一般写成 getResources()就ok了);第二个是你需要加载的位图资源的Id
mWhitePiece = BitmapFactory.decodeResource(getResources(),R.drawable.stone_w2);
}
@Override
//xml页面布局文件中选择使用
//match_parent或者fill_parent(兼容低版本)强制性让它布满屏幕
//wrap_content 大小刚好足够显示当前控件里的内容
//relativelayout相对布局,意思就是我们在布局的时候可以选择使用不同的参照物
//自定义View时,测量尺寸的onMeasure方法,就是父(大)视图向子(大视图中的视图)视图要求尺寸,getsize和getmode方法,setMeasuredDimension方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);  //MeasureSpec是view中的内部类
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);  //宽度
    
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);//高度
    
    int width = Math.min(widthSize,heightSize);  //想要一个正方形棋盘,所以我们取值为二者最小值,width变量极其重要
    
    if(widthMode == MeasureSpec.UNSPECIFIED){  //unspecified不对view大小做限制
    //获取模式Mode,有三种,UNSPECIFIED不做限制,AT_MOST(最多指定的大小),EXACTLY(完全)父视图决定子视图大小,子视图被完全束缚在父视图内部
        width = heightSize;   //如果宽度没有被限制,那么width等于高度
    }else if(heightMode == MeasureSpec.UNSPECIFIED){
        width = widthSize;    //如果高度没有被限制,那么width等于宽度
    }  //其他情况,没被限制也毫无办法,只能顺应
    
    setMeasuredDimension(width,width);  //子视图向父视图报告已经确定宽度和高度,通过调用setMeasuredDimension方法
}
//onSizeChanged是回调方法,它的方法名已经告诉我们了,这个方法会在这个view的大小发生改变是被回调,就是这个方法执行,然后返回到onMeasure方法,然后再需要的时候重写
@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 pieceWidth = (int) (mLineHeight*ratioPieceOfLineHight);   
    //修改棋子大小,方法createScaledBitmap
    mWhitePiece = Bitmap.createScaledBitmap(mWhitePiece,pieceWidth,pieceWidth,false);                                       
    mBlackPiece = Bitmap.createScaledBitmap(mBlackPiece,pieceWidth,pieceWidth,false);
    /*createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)
       Parameters:①src 对资源src进行缩放  ②dstWidth  缩放成宽dstWidth  ③dstHeight 缩放成高dstHeight  ④filter 过滤       
       如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响
    */
}
//触发事件ontouchevent方法getaction方法,contains方法
@Override
public boolean onTouchEvent(MotionEvent event) { //用户交互,触发事件
    if(mIsGameOver) return false;  //如果游戏已经结束,那我对这个事件不感兴趣
    int action=event.getAction();   //收到用户动作调用getAction方法
    if(action==MotionEvent.ACTION_UP){
        int x=(int)event.getX();   //用户点击的坐标
        int y=(int)event.getY();

        Point p= getValidPoint(x,y);    //自定义方法getValidPoint
        if(mWhiteArray.contains(p) || mBlackArray.contains(p)){
            return false;
        }//这个落子点是不是合法,我们得判断是不是合法,比如不能覆盖
        if(mIsWhite){
            mWhiteArray.add(p);  //下的是白子,放到白子数列
        }else{
            mBlackArray.add(p);
        }
        invalidate();    //我下完之后,对View进行刷新重绘,等待下一次绘制
        mIsWhite=!mIsWhite;     //让对方下棋

    }
    return true; //view对这个touch事件感兴趣,交给本view处理
}
//返回棋子的合法位点,用int来控制误差防止棋子下偏
private Point getValidPoint(int x, int y) {
    return new Point((int)(x/mLineHeight),(int)(y/mLineHeight));
}
//绘制方法onDraw
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawBoard(canvas);
    drawPieces(canvas);
    checkGameOver();
}
//检验游戏结束,这是自定义的方法
private void checkGameOver() {
    //判断是否在line上满足五个棋子
    boolean whiteWin = checkFiveInLine(mWhiteArray);  //构建一个方法是否五子连珠
    boolean blackWin = checkFiveInLine(mBlackArray);
    if(whiteWin || blackWin){
        mIsGameOver = true;
        mIsWhiteWinner = whiteWin;
        String text =mIsWhiteWinner?"白棋胜!":"黑棋胜!";  //语法糖,三目运算符
        Toast.makeText(getContext(),text,Toast.LENGTH_SHORT).show();  
        //Toast 是一个 View 视图,快速的为用户显示少量的信息,Toast 在应用程序上浮动显示信息给用户,就是我们手机上的昙花一现的提示信息框
        //第一个参数:当前的上下文环境,getcontext方法返回的是当前View运行在哪个Activity Context中
        //第二个参数:要显示的字符串,若写成this,意思是当前的一些实例的引用
        //第三个参数:显示的时间长短,默认的有两个LENGTH_LONG(长)和LENGTH_SHORT(短)
    }
}
//是否五子连珠,创建判断方法
private boolean checkFiveInLine(List<Point> points) {
    for(Point p:points){ //对每一个棋子进行循环
        int x = p.x;
        int y = p.y;
        //四种情况,具体在下方四种方法之中实现
        boolean win = checkHor(x,y,points);
        if(win) return true;
        win = checkVer(x,y,points);
        if(win) return true;
        win = checkLeftDia(x,y,points);
        if(win) return true;
        win = checkRightDia(x,y,points);
        if(win) return true;
    }
    return false;
}
//右斜方向(与横向相似,实现简单)
private boolean checkRightDia(int x, int y, List<Point> points) {
    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;

    }
    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;
    }
    if(count==MAX_COUNT_IN_LINE) return true;
    return false;

}
//左斜方向(与横向相似,实现简单)
private boolean checkLeftDia(int x, int y, List<Point> points) {
    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;

    }
    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;

    }
    if(count==MAX_COUNT_IN_LINE) return true;
    return false;

}
//判断纵向(与横向相似,实现简单)
private boolean checkVer(int x, int y, List<Point> points) {
    int count=1;
    for(int i=1;i<MAX_COUNT_IN_LINE;i++){
        if(points.contains(new Point(x,y+i)))
            count++;
        else break;

    }
    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;

    }
    if(count==MAX_COUNT_IN_LINE) return true;
    return false;
}
//判断是否横向有五个棋子相连
private boolean checkHor(int x, int y, List<Point> points) {
    int count=1;  //已经下了一个棋子,再去判断
    for(int i=1;i<MAX_COUNT_IN_LINE;i++){
        if(points.contains(new Point(x+i,y)))
            count++;
        else break;  //如果不是,立马结束for

    }//右侧
    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;

    }//左侧
    if(count==MAX_COUNT_IN_LINE) return true;
    
    return false; //other

}
//绘制棋子drawBitmap方法绘制存在的图片
private void drawPieces(Canvas canvas) { 
    int n = mWhiteArray.size();   //拿到白棋子尺寸
    int m = mBlackArray.size();   //拿到黑棋子尺寸
    for(int i=0;i<n;i++){
        Point WhitePoint = mWhiteArray.get(i);
        canvas.drawBitmap(mWhitePiece,
                (WhitePoint.x+(1-ratioPieceOfLineHight)/2)*mLineHeight,
                (WhitePoint.y+(1-ratioPieceOfLineHight)/2)*mLineHeight,null);  //确定棋子的横坐标和纵坐标
    }//白色
    for(int i=0;i<m;i++){
        Point BlackPoint = mBlackArray.get(i);
        canvas.drawBitmap(mBlackPiece,
                (BlackPoint.x+(1-ratioPieceOfLineHight)/2)*mLineHeight,
                (BlackPoint.y+(1-ratioPieceOfLineHight)/2)*mLineHeight,null);
    }//黑色
}
//绘制棋盘drawline画线方法
private void drawBoard(Canvas canvas) {
   
    int w = mPanelWidth;  //每个格子宽度
    float lineHeight = mLineHeight;  //每个格子高度

    for(int i =0;i<MAX_LINE;i++){  //画12条竖线
        int startX = (int) (lineHeight/2);
        int endX = (int) (w-lineHeight/2);
        int y = (int) ((0.5+i)*lineHeight);  //竖线的画线范围为y的取值范围
        canvas.drawLine(startX,y,endX,y,mPaint);  //drawLine画线方法
    }
    for(int i =0;i<MAX_LINE;i++){  //画12条横线
        int startY = (int) (lineHeight/2);
        int endY = (int) (w-lineHeight/2);
        int x = (int) ((0.5+i)*lineHeight);   //横线的画线范围为x的取值范围
        canvas.drawLine(x,startY,x,endY,mPaint);
    }
}
//再来一局invalidate方法
public void reStart(){
    mWhiteArray.clear();
    mBlackArray.clear();
    mIsGameOver = false;  //对状态变量进行初始化更改
    mIsWhiteWinner = false;
    invalidate();  //View进行刷新重新绘制
} 
//储存和恢复棋盘状态,比如你接电话,安卓系统可能会杀死该进程,你再打开的话需要保存
//接下来是实现这一功能的所有代码
private static final String INSTANCE = "instance";
private static final String INSTANCE_OVER = "instance_over";
private static final String INSTANCE_WHITE_ARRAY = "instance_white_array";
private static final String INSTANCE_BLACK_ARRAY = "instance_black_array";
//private static final:翻译成人话就是:该变量只能在当前这个类中被使用,并且是带有static修饰的静态方法中被调用,加了final(不可变的)则该属性的值将不能被改变
//static是静态变量,不用static修饰是实例变量(均可调用)
@Override
protected Parcelable onSaveInstanceState(){     //保存方法onSaveInstanceState,Parcelable是个类,一般用作接口的数值传递,通常和bundle类在一起,也是传值用的类
    Bundle bundle = new Bundle();
    //putxxx方法
    bundle.putParcelable(INSTANCE,super.onSaveInstanceState());    //传值方法,上面要用put,下面在接口中用get
    bundle.putBoolean(INSTANCE_OVER,mIsGameOver);  //游戏结束的判断状态
    bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY,mWhiteArray);  //白棋全部状态
    bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY,mBlackArray);   //黑棋全部状态
    return bundle;    //这个返回的bundle值会在接下来的方法中调用和传递
}
@Override
protected void onRestoreInstanceState(Parcelable state) {   //恢复方法onRestoreInstanceState
    if(state instanceof Bundle){    //获取bundle状态
        Bundle bundle = (Bundle) state;
        //getxxx方法
        mIsGameOver = bundle.getBoolean(INSTANCE_OVER);
        mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY);
        mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY);
        super.onRestoreInstanceState(bundle.getParcelable(INSTANCE));
        return;
    }
    super.onRestoreInstanceState(state);   //根据原来状态进行恢复
}

}

发布了17 篇原创文章 · 获赞 2 · 访问量 1618

猜你喜欢

转载自blog.csdn.net/weixin_44033021/article/details/104288382
今日推荐