时间:2023-08-23 17:12:56来源:互联网
阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就学习了一下,写篇博客梳理一下自己的思路,加深一下印象
视频链接:http://www.imooc.com/learn/641
一.棋盘
我们一看就知道,我们必须自定义View,这里我们定义一个GameView来做游戏主类,第一步,先测量,我们这里不难知道,五子棋他的棋盘是一个正方形,所以我们需要去测量
/**
* 测量
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取高宽值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int hightSize = MeasureSpec.getSize(heightMeasureSpec);
int hightMode = MeasureSpec.getMode(heightMeasureSpec);
//拿到宽和高的最小值,也就是宽
int width = Math.min(widthSize, heightMeasureSpec);
//根据测量模式细节处理
if (widthMode == MeasureSpec.UNSPECIFIED) {
width = hightSize;
} else if (hightMode == MeasureSpec.UNSPECIFIED) {
width = widthSize;
}
//设置这样就是一个正方形了
setMeasuredDimension(width, width);
}
这里的逻辑还是十分简单的,我们拿到长和宽去比较一下,设置这个View的长宽Wie最小值,就是一个正方形了,所以我们的layout_main.xml是这样写的
xmlns:tools="http://schemas.android.com/tools"
android:layout_ >
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/main_bg"
>
android:layout_ > android:layout_height="match_parent" /> 这里我在构造方法中设置了一个半透明的红色背景,是在我们调试的时候可以更加清晰的看清楚GameView的大小,所以,运行的结果 二.线条 这个应该也算是棋盘的一部分吧,就是棋盘上的线条,我们应该怎么去画,首先,我们要去定义一些属性 //线条数量 private static final int MAX_LINE = 10; //线条的宽度 private int mPanelWidth; //线条的高度 private float mLineHeight; 然后,我们要去确定大小 /** * 测量大小 * * @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); //拿到宽 mPanelWidth = w; //分割 mLineHeight = mPanelWidth * 1.0f / MAX_LINE; } 不要着急,这些都只是一些准备的工作,我们画线条是必须要在onDraw(0方法里的,但是前期我们要准备一只画笔,对吧,所以我们要初始化画笔 /** * 初始化画笔 */ private void initPaint() { //设置颜色 mPaint.setColor(0x88000000); //抗锯齿 mPaint.setAntiAlias(true); //设置防抖动 mPaint.setDither(true); //设置Style mPaint.setStyle(Paint.Style.STROKE); } 现在我们可以去绘制了,我们在OnDraw(0方法里写一个drawLine方法来专门绘制线条 /** * 绘制棋盘的方法 * * @param canvas */ private void drawLine(Canvas canvas) { //获取高宽 int w = mPanelWidth; float lineHeight = mLineHeight; //遍历,绘制线条 for (int i = 0; i < MAX_LINE; i ) { //横坐标 int startX = (int) (lineHeight / 2); int endX = (int) (w - lineHeight / 2); //纵坐标 int y = (int) ((0.5 i) * lineHeight); //绘制横 canvas.drawLine(startX, y, endX, y, mPaint); //绘制纵 canvas.drawLine(y, startX, y, endX, mPaint); } } 我们运行一下 好的,这里,注意一下,我在activity_main.xml中定义了一个 android:gravity="center" 属性,所以让他居中,同样的,我们在initPaint中加上点代码让我们看的更加直观一点 //设置颜色 mPaint.setColor(Color.BLACK); //设置线条宽度 mPaint.setStrokeWidth(3); 同样的,我们把构造法里的设置背景的测试代码注释掉 //测试代码 //setBackgroundColor(0x44ff0000); 这样,我们运行一下 得,我们现在有模有样了 三.棋子 棋子我们事先准备好了两张图片,但是这里我们要考虑他的大小的问题了,我们的思路是让他是行高的四分之三大小,所以先声明 //黑棋子 private Bitmap mBlack; //白棋子 private Bitmap mWhite; //比例,棋子的大小是高的四分之三 private float rowSize = 3 * 1.0f / 4; 然后我们定义一个方法区初始化Bitmap /** * 初始化棋子 */ private void initBitmap() { //拿到图片资源 mBlack = BitmapFactory.decodeResource(getResources(), R.drawable.stone_black); mWhite = BitmapFactory.decodeResource(getResources(), R.drawable.stone_white); } 拿到资源之后我们就可以设置大小了,我们在onSizeChanged()里面设置 //棋子宽度 int mWhiteWidth = (int) (mLineHeight * rowSize); //修改棋子大小 mWhite = Bitmap.createScaledBitmap(mWhite, mWhiteWidth, mWhiteWidth, false); mBlack = Bitmap.createScaledBitmap(mBlack, mWhiteWidth, mWhiteWidth, false); 不过棋子可没我们想象的那么简单,我们要点击一下再去绘制一个棋子,这样的思路该怎么去实现呢?我们实现它的点击事件,这里先定义几个变量 //存储用户点击的坐标 private List mWhiteArray = new ArrayList(); private List mBlackArray = new ArrayList(); //标记,是执黑子还是白子 ,白棋先手 private boolean mIsWhite = true; 这样才和触摸事件相得映彰 /** * 触摸事件 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { //按下事件 case MotionEvent.ACTION_UP: int x = (int) event.getX(); int y = (int) event.getY(); //封装成一个Point Point p = getValidPoint(x, y); //判断当前这个点是否有棋子了 if(mWhiteArray.contains(p) || mBlackArray.contains(p)){ //点击不生效 return false; } //判断如果是白子就存白棋集合,反之则黑棋集合 if (mIsWhite) { mWhiteArray.add(p); } else { mBlackArray.add(p); } //刷新 invalidate(); //改变值 mIsWhite = !mIsWhite; break; } return true; } 这样,有几点是要说明一下的,首先我们new Point的时候为了避免重复绘制我们是实现了一个方法 /** * 不能重复点击 * * @param x * @param y * @return */ private Point getValidPoint(int x, int y) { return new Point((int) (x / mLineHeight), (int) (y / mLineHeight)); } 紧接着我们就判断,要是重复点击,返回false,而且我们在action选择也是选择了ACTION_UP,为什么?为什么不是ACTION_DOWN?因为这个毕竟是一个View,父View会拦截(某些场景),所以我们选在UP上才是合情合理的 >好的,当我们点击之后就要绘制棋子了,这里我们也写一个方法 /** * 绘制棋子的方法 * * @param canvas */ private void drawPieces(Canvas canvas) { for (int i = 0; i < mWhiteArray.size(); i ) { //获取白棋子的坐标 Point whitePoint = mWhiteArray.get(i); canvas.drawBitmap(mBlack, (whitePoint.x (1 - rowSize) / 2) * mLineHeight, (whitePoint.y (1 - rowSize) / 2) * mLineHeight, null); } for (int i = 0; i < mBlackArray.size(); i ) { //获取黑棋子的坐标 Point blackPoint = mBlackArray.get(i); canvas.drawBitmap(mWhite, (blackPoint.x (1 - rowSize) / 2) * mLineHeight, (blackPoint.y (1 - rowSize) / 2) * mLineHeight, null); } } OK,我们实际运行一下 四.游戏逻辑 现在什么都有了,基本上都可用玩了,但是还少了重要的一点就是游戏结束,你到了五颗也需要判断是否胜利呀,对吧,我们写一个方法,在每次绘制完成之后就去判断是否有赢家 /** * 判断是否胜利 */ private void checkWin() { //判断白棋是否有五个相同的棋子相连 boolean mWhiteWin = checkFiveLine(mWhiteArray); //判断黑棋是否有五个相同的棋子相连 boolean mBlackWin = checkFiveLine(mBlackArray); //只要有一个胜利,游戏就结束 if (mWhiteWin || mBlackWin) { mIsGameOver = true; mIsWhiteWin = mWhiteWin; Toast.makeText(getContext(), mIsWhiteWin ? "白棋胜利" : "黑棋胜利", Toast.LENGTH_SHORT).show(); } } 好的,我们重点逻辑就在checkFiveLine这个方法上了,这里,我们所知道的胜利有四种情况 我们先定义一个常量 //胜利棋子数量 private static final int MAX_COUNT_IN_LINE = 5; OK,接下来我们可以实现以下胜利的逻辑了 /** * //判断棋子是否有五个相同的棋子相连 * * @param points * @return */ private boolean checkFiveLine(List points) { //遍历棋子 for (Point p : points) { //拿到棋盘上的位置 int x = p.x; int y = p.y; /** * 四种情况胜利,横,竖,左斜,右斜 */ //横 boolean win = checkHorizontal(x, y, points); if (win) return true; //竖 win = checkVertical(x, y, points); if (win) return true; //左斜 win = checkLeft(x, y, points); if (win) return true; //右斜 win = checkRight(x, y, points); if (win) return true; } return false; } 我们不管哪个方向只要返回true就返回true,然后弹Toast,这里,四个方向的逻辑 - 横 /** * 判断横向的棋子 * * @param x * @param y * @param points */ private boolean checkHorizontal(int x, int y, List points) { //棋子标记,记录是否有五个 =1是因为自身是一个 int count = 1; //左 for (int i = 1; i < MAX_COUNT_IN_LINE; i ) { //如果有 if (points.contains(new Point(x - i, y))) { count ; } else { break; } } //有五个就为true 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; } } //有五个就为true if (count == MAX_COUNT_IN_LINE) { return true; } return false; } - 横 /** * 判断纵向的棋子 * * @param x * @param y * @param points */ private boolean checkVertical(int x, int y, List points) { //棋子标记,记录是否有五个 =1是因为自身是一个 int count = 1; //上 for (int i = 1; i < MAX_COUNT_IN_LINE; i ) { //如果有 if (points.contains(new Point(x, y - i))) { count ; } else { break; } } //有五个就为true 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; } } //有五个就为true if (count == MAX_COUNT_IN_LINE) { return true; } return false; } - 左斜 /** * 判断左斜向的棋子 * * @param x * @param y * @param points */ private boolean checkLeft(int x, int y, List points) { //棋子标记,记录是否有五个 =1是因为自身是一个 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; } } //有五个就为true 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; } } //有五个就为true if (count == MAX_COUNT_IN_LINE) { return true; } return false; } - 右斜 /** * 判断右斜向的棋子 * * @param x * @param y * @param points */ private boolean checkRight(int x, int y, List points) { //棋子标记,记录是否有五个 =1是因为自身是一个 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; } } //有五个就为true 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; } } //有五个就为true if (count == MAX_COUNT_IN_LINE) { return true; } return false; } 这样,我们运行一下 嘿嘿,好玩吧! 五.游戏状态存储 这个就是当我们游戏挂后台之后,再回来游戏就没了,我们应该存储他的状态,让他每一次进入的时候要是上一句没有下完接着下,那我们该怎么去实现呢?和Activity一样,我们View也有存储状态的方法 /** * 存储状态 * * @return */ @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(INSTANCE, super.onSaveInstanceState()); bundle.putBoolean(INSTANCE_GAMEOVER, mIsGameOver); bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY, mWhiteArray); bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY, mBlackArray); return bundle; } /** * 重新运行 * * @param state */ @Override protected void onRestoreInstanceState(Parcelable state) { //取值 if (state instanceof Bundle) { Bundle bundle = (Bundle) state; mIsGameOver = bundle.getBoolean(INSTANCE_GAMEOVER); mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY); mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY); //调用 super.onRestoreInstanceState(bundle.getParcelable(INSTANCE)); return; } super.onRestoreInstanceState(state); } 这样就可以了,但是,有一点要知道,不要忘记在布局文件上给控件加上ID,不然状态不会存储哦 android:id="@ id/mGameView" android:layout_ > android:layout_height="match_parent" /> 六.再来一局 既然我们的游戏逻辑差不多了,那我们应该考虑一下当你胜利的时候,你是不是应该再来一局,所以我们还要实现这个逻辑,这个很简单 /** * 再来一局 */ public void RestartGame() { mWhiteArray.clear(); mBlackArray.clear(); mIsGameOver = false; mIsWhiteWin = false; invalidate(); } 这样,我们就可以直接调用了,我们来看看MainActivity package com.lgl.fiverow; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; /** * 五子棋游戏 */ public class MainActivity extends AppCompatActivity { //重来按钮 private FloatingActionButton fab; //游戏 private GameView game; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); game = (GameView) findViewById(R.id.mGameView); fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { game.RestartGame(); } }); } } OK,我们最终运行一下 OK,到这里,就算开发完成了Demo下载:http://download.csdn.net/detail/qq_26787115/9521011我的群:555974449欢迎你加入!