//这个安卓项目在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); //根据原来状态进行恢复
}
}