Android五子棋遊戲原始碼詳解
阿新 • • 發佈:2019-02-13
最近看了鴻洋大牛的五子棋教程,受益匪淺,講的非常好,關於五子棋的遊戲原理非常清楚,並且學到了不少知識,在這裡感謝鴻洋大神的分享。我覺得我的原始碼註釋寫的非常清楚了,希望能給你帶來不少的收穫。
佈局
<?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:background="@mipmap/bg"
android:paddingTop="80dp"
android:orientation="vertical"
tools:context="com.yaodan.fivechessdemo.MainActivity">
<com.yaodan.fivechessdemo.view.ChessView
android:id="@+id/custon_chess_main"
android:layout_width ="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/bt_restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="再來一局"/>
</LinearLayout>
自定義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<Point> wPoints = new ArrayList<>(); //白棋座標的集合
private ArrayList<Point> 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 < MAX_LINE; 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 < wPoints.size(); 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 < bPoints.size(); 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<Point> whitePoints, List<Point> 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<Point> points) {
if (isFiveConnect(points)) {
return true;
}
return false;
}
/**
* 判斷是否黑棋取勝
* @param points
* @return
*/
private boolean isBlackWin(List<Point> points) {
if (isFiveConnect(points)) {
return true;
}
return false;
}
/**
* 判斷是否五子連珠
* @param points
* @return
*/
private boolean isFiveConnect(List<Point> 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<Point> points) {
//判斷橫向向右是否練成5子,points裡面存的值為int型別,所以可以進行加一或者減一的運算
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x + i, y))) {
CURRENT_NUM++;
} else {
break;
}
}
if (MAX_NUMWIN == CURRENT_NUM) {
return true;
} else {
CURRENT_NUM = 0;
}
//判斷橫向向左是否連成5子
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x - i, y))) {
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<Point> points) {
for (int i = 0; i < MAX_NUMWIN; i++) {
//判斷向下是否5子連珠
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 < MAX_NUMWIN; i++) {
if (points.contains(new Point(x, y - 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<Point> points) {
//判斷左斜下是否5子連珠
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x - i, y + i))) {
CURRENT_NUM++;
} else {
break;
}
}
if (MAX_NUMWIN == CURRENT_NUM) {
return true;
} else {
CURRENT_NUM = 0;
}
//判斷左上是否5子連珠
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x - i, y - i))) {
CURRENT_NUM++;
} else {
break;
}
}
if (MAX_NUMWIN == CURRENT_NUM) {
return true;
} else {
CURRENT_NUM = 0;
}
//判斷右上是否5子連珠
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x + i, y - i))) {
CURRENT_NUM++;
} else {
break;
}
}
if (MAX_NUMWIN == CURRENT_NUM) {
return true;
} else {
CURRENT_NUM = 0;
}
//判斷右斜上是否5子連珠
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x + i, y - i))) {
CURRENT_NUM++;
} else {
break;
}
}
if(MAX_NUMWIN==CURRENT_NUM){
return true;
}else{
CURRENT_NUM=0;
}
//判斷右斜下是否5子連珠
for (int i = 0; i < MAX_NUMWIN; i++) {
if (points.contains(new Point(x + i, y + 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;
}
}
}