最近看了鸿洋大牛的五子棋教程,受益匪浅,讲的非常好,关于五子棋的游戏原理非常清楚,并且学到了不少知识,在这里感谢鸿洋大神的分享。我觉得我的源码注释写的非常清楚了,希望能给你带来不少的收获。
布局<?xml version="1.0" encoding="utf-8"?>
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/bg"
android:paddingTop="80dp"
android:orientation="vertical"
tools:context="com.yaodan.fivechessdemo.MainActivity">
android:id="@+id/custon_chess_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/bt_restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="再来一局"/>
自定义View的工具类package com.yaodan.fivechessdemo.view;import android.app.AlertDialog;import android.content.Context;import android.content.DialogInterface;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.util.Log;import android.view.MotionEvent;import android.view.View;import com.yaodan.fivechessdemo.R;import com.yaodan.fivechessdemo.contral.IsChessWin;import java.util.ArrayList;/**
* Created by swk on 2016/12/5.
*/public class ChessView extends View { private int mPanelWith; //棋盘的宽度(棋盘使方形的)
private float mLineHeigth; //棋盘每一个空格的高度
private int MAX_LINE = 10; //棋盘的行数
private Paint mPint = new Paint(); //创建画笔
private Bitmap wPieces; //白棋
private Bitmap bPieces; //黑棋
private ArrayList wPoints = new ArrayList<>(); //白棋坐标的集合
private ArrayList bPoints = new ArrayList<>(); //黑棋坐标的集合
private float radioPoeces = 1.0f * 3 / 4; //棋子与棋格的大小比例
private boolean mIsWitch = true; //判断是否白子画在棋盘上
private boolean isGameOver = false; //判读是否游戏结束
private Context mContext; private IsChessWin isChessWin; //一个用来处理胜利与否的逻辑
private String TAG = "CHESSVIEW"; public ChessView(Context context) { super(context);
mContext = context;
init();
} public ChessView(Context context, AttributeSet attrs) { super(context, attrs);
mContext = context;
init();
} public ChessView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);
mContext = context;
init();
} /**
* 再来一局方法
*/
public void myreStart() {
wPoints.clear();
bPoints.clear();
isGameOver = false;
Log.i(TAG, "myreStart: " + wPoints.size() + ":::" + bPoints.size());
invalidate();
} /**
* 初始化
*/
public void init() { //设置画笔的颜色
mPint.setColor(Color.BLACK); //设置抗锯齿功能(图像边缘相对清晰一点,锯齿痕迹不那么明显)
mPint.setAntiAlias(true); //设置防抖动功能(使图像更柔和一点)
mPint.setDither(true); //设置画笔的风格为空心
mPint.setStyle(Paint.Style.STROKE); //获取棋子的资源文件
wPieces = BitmapFactory.decodeResource(getResources(), R.drawable.stone_w2);
bPieces = BitmapFactory.decodeResource(getResources(), R.drawable.stone_b1);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /**
* 获取view的宽度和mode
* 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 heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //由于棋盘是正方形,所以要从长和宽选取最短的
int width = Math.min(widthSize, heightSize); //如果上方有一个是UNSPECIFIED,相对应的有一个尺寸是0,如果有一个是0,那么width就是0显示不出来
if (widthMode == MeasureSpec.UNSPECIFIED) {
width = heightSize;
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
width = widthSize;
} //设置实际的长和宽设置上去
setMeasuredDimension(width, width);
} /**
* 当view的尺寸改变时,会回掉这个方法
*
* @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);
mPanelWith = w;
mLineHeigth = mPanelWith * 1.0f / MAX_LINE; int piecesWidth = (int) (mLineHeigth * radioPoeces); //按照以前存在的位图按照一定的比例构建一个新的位图
wPieces = Bitmap.createScaledBitmap(wPieces, piecesWidth, piecesWidth, true);
bPieces = Bitmap.createScaledBitmap(bPieces, piecesWidth, piecesWidth, true);
} /**
* 获取坐标的集合
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
isGameOver = isChessWin.isGameOverMethod(wPoints, bPoints); if (isGameOver) {
showDialog(); return false;
} int action = event.getAction(); if (action == MotionEvent.ACTION_UP) { int x = (int) event.getX(); int y = (int) event.getY();
Point point = getSimulatePoint(x, y); //如果黑棋的集合或者白棋的集合包含这个坐标,那么返回false
//contains和eequals比较的不是内存空间的地址,而是x,y值是否一致
if (wPoints.contains(point) || bPoints.contains(point)) { return false;
} if (mIsWitch) {
wPoints.add(point);
} else {
bPoints.add(point);
}
mIsWitch = !mIsWitch; //刷新View
invalidate(); return true;
} return true;
} /**
* 根据真实的坐标模拟出绝对值坐标
*
* @param x
* @param y
* @return
*/
public Point getSimulatePoint(int x, int y) { return new Point((int) (x / mLineHeigth), (int) (y / mLineHeigth));
} /**
* 显示白棋或者黑棋获胜的提示的对话框
*/
public void showDialog() {
String successText = isChessWin.isWhiteWinFlag() ? "白棋获胜!" : "黑棋获胜!"; new AlertDialog.Builder(mContext)
.setMessage("恭喜" + successText + ",是否再来一局?")
.setCancelable(false)
.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override
public void onClick(DialogInterface dialogInterface, int i) {
myreStart();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
} @Override
protected void onDraw(Canvas canvas) { super.onDraw(canvas);
drawBoard(canvas);
drawPieces(canvas); //下面的实例用来判断是否胜利
isChessWin = new IsChessWin(mContext);
isGameOver = isChessWin.isGameOverMethod(wPoints, bPoints); //判断是否结束游戏
if(isGameOver){
showDialog();
}
} /**
* 画棋盘的线
*
* @param canvas
*/
public void drawBoard(Canvas canvas) { int w = mPanelWith; float lineHeight = mLineHeigth; //画十条线
for (int i = 0; i
int startX = (int) (lineHeight / 2); //设置终点X横坐标为宽度减去半个lineHeight(棋盘空格宽度)
int endX = (int) (w - lineHeight / 2); int y = (int) ((0.5 + i) * lineHeight); //画横线
canvas.drawLine(startX, y, endX, y, mPint); //画纵线,坐标反过来
canvas.drawLine(y, startX, y, endX, mPint);
}
} /**
* 画棋子
*/
public void drawPieces(Canvas canvas) {
Log.i(TAG, "drawPieces: " + wPoints.size() + ":::" + bPoints.size()); for (int i = 0; i
Point point = wPoints.get(i); //drawBitmap是将图片的右下角为坐标
canvas.drawBitmap(wPieces, ((point.x + (1 - radioPoeces) / 2) * mLineHeigth), (point.y + (1 - radioPoeces) / 2) * mLineHeigth, null);
} for (int i = 0; i
Point point = bPoints.get(i);
canvas.drawBitmap(bPieces, ((point.x + (1 - radioPoeces) / 2) * mLineHeigth), (point.y + (1 - radioPoeces) / 2) * mLineHeigth, null);
}
} private static final String INSTANCE = "instance"; private static final String INSTANCE_GAMEOVER = "instance_gameover"; private static final String INSTANCE_WHITEARRAY = "instance_whitearray"; private static final String INSTANCE_BLACKARRAY = "instance_blackarray"; /**
* 当view因为某种原因(比如系统回收)销毁时,保存状态
*
* @return
*/
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle(); //保存系统默认状态
bundle.putParcelable(INSTANCE, super.onSaveInstanceState()); //保存是否游戏结束的值
bundle.putBoolean(INSTANCE_GAMEOVER, isGameOver); //保存白棋的子数
bundle.putParcelableArrayList(INSTANCE_WHITEARRAY, wPoints); //保存黑棋的子数
bundle.putParcelableArrayList(INSTANCE_BLACKARRAY, bPoints); return bundle;
} /**
* 取出保存的值
*
* @param state
*/
@Override
protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
isGameOver = bundle.getBoolean(INSTANCE_GAMEOVER);
wPoints = bundle.getParcelableArrayList(INSTANCE_WHITEARRAY);
bPoints = bundle.getParcelableArrayList(INSTANCE_BLACKARRAY); super.onRestoreInstanceState(bundle.getParcelable(INSTANCE)); return;
} super.onRestoreInstanceState(state);
}
}
判断是否胜利的类package com.yaodan.fivechessdemo.contral;import android.content.Context;import android.graphics.Point;import java.util.List;/**
* Created by swk on 2016/12/5.
* 判断是否胜利类
*/public class IsChessWin { private boolean isGameOver = false; //判断是否游戏结束
private boolean isWhiteWin; //判断是否白棋胜
private int MAX_NUMWIN = 5; //设置5子连在一起胜利
private int CURRENT_NUM = 0; private Context mContext; private boolean isRestart=false; public IsChessWin(Context context) { super();
mContext = context;
} /**
* 判断是否胜利
* @param whitePoints
* @param blackPoints
* @return
*/
public boolean isGameOverMethod(List whitePoints, List blackPoints) { boolean whiteWin = isWhiteWin(whitePoints); boolean blackWin = isBlackWin(blackPoints); if (whiteWin || blackWin) {
isGameOver = true;
isWhiteWin = whiteWin;
} return isGameOver;
} /**
* 返回是否白棋取胜
* @return
*/
public boolean isWhiteWinFlag(){ return isWhiteWin;
} /**
* 判断是否白棋取胜
* @param points
* @return
*/
private boolean isWhiteWin(List points) { if (isFiveConnect(points)) { return true;
} return false;
} /**
* 判断是否黑棋取胜
* @param points
* @return
*/
private boolean isBlackWin(List points) { if (isFiveConnect(points)) { return true;
} return false;
} /**
* 判断是否五子连珠
* @param points
* @return
*/
private boolean isFiveConnect(List points) { for (Point p : points) { int x = p.x; int y = p.y; if (isHorizontalFive(x, y, points)) { return true;
} else if (isVerticalFive(x, y, points)) { return true;
} else if (isSkewFive(x, y, points)) { return true;
}
} return false;
} /**
* 判断是否横向五子连珠
* @param x
* @param y
* @param points
* @return
*/
private boolean isHorizontalFive(int x, int y, List points) { //判断横向向右是否练成5子,points里面存的值为int类型,所以可以进行加一或者减一的运算
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} //判断横向向左是否连成5子
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} return false;
} /**
* 判断是否竖直五子连珠
* @param x
* @param y
* @param points
* @return
*/
private boolean isVerticalFive(int x, int y, List points) { for (int i = 0; i
if (points.contains(new Point(x, y + i))) {
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} //判断向上是否5子连珠
for (int i = 0; i
CURRENT_NUM++; if (5 == CURRENT_NUM) { return true;
}
} else {
CURRENT_NUM = 0; break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} return false;
} /**
* 判断斜着是否五子连珠
* @param x
* @param y
* @param points
* @return
*/
private boolean isSkewFive(int x, int y, List points) { //判断左斜下是否5子连珠
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} //判断左上是否5子连珠
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} //判断右上是否5子连珠
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if (MAX_NUMWIN == CURRENT_NUM) { return true;
} else {
CURRENT_NUM = 0;
} //判断右斜上是否5子连珠
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if(MAX_NUMWIN==CURRENT_NUM){ return true;
}else{
CURRENT_NUM=0;
} //判断右斜下是否5子连珠
for (int i = 0; i
CURRENT_NUM++;
} else { break;
}
} if(MAX_NUMWIN==CURRENT_NUM){ return true;
}else{
CURRENT_NUM=0;
} return false;
}
}
主方法的调用package com.yaodan.fivechessdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import com.yaodan.fivechessdemo.view.ChessView;public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_reatart; private ChessView chessView; @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_reatart = (Button) findViewById(R.id.bt_restart);
chessView= (ChessView) findViewById(R.id.custon_chess_main);
btn_reatart.setOnClickListener(this);
} @Override
public void onClick(View view) { switch (view.getId()) { case R.id.bt_restart:
chessView.myreStart(); break;
}
}
}