Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局
Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局
阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就学习了一下,写篇博客梳理一下自己的思路,加深一下印象
一.棋盘
我们一看就知道,我们必须自定义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是这样写的
<?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:orientation="vertical"
android:background="@drawable/main_bg"
>
<com.lgl.fiverow.GameView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这里我在构造方法中设置了一个半透明的红色背景,是在我们调试的时候可以更加清晰的看清楚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<Point> mWhiteArray = new ArrayList<>();
private List<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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<Point> 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,不然状态不会存储哦
<com.lgl.fiverow.GameView
android:id="@+id/mGameView"
android:layout_width="match_parent"
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欢迎你加入!
Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局的更多相关文章
- Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!
Android实训案例(四)--关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程! 关于2048,我看到很多大神,比如医生,郭神,所以我也研究了一段时间,还好是研究 ...
- Android实训案例(九)——答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程
Android实训案例(九)--答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程 项目也是偷师的,决心研究一下数据库.所以写的还是很详细的,各位看官,耐着性子看完,实现结果不重要 ...
- Android实训案例(六)——四大组件之一BroadcastReceiver的基本使用,拨号,短信,SD卡,开机,应用安装卸载监听
Android实训案例(六)--四大组件之一BroadcastReceiver的基本使用,拨号,短信,SD卡,开机,应用安装卸载监听 Android中四大组件的使用时重中之重,我这个阶段也不奢望能把他 ...
- Android实训案例(五)——四大组件之一ContentProvider的使用,通讯录的实现以及ListView的优化
Android实训案例(五)--四大组件之一ContentProvider的使用,通讯录的实现 Android四大组件是啥这里就不用多说了,看图吧,他们之间通过intent通讯 我们后续也会一一的为大 ...
- Android实训案例(三)——实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果!
Android实训案例(三)--实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果! 感叹离春节将至,也同时感叹时间不等人,一年又一年,可是我依然是android道路上的小菜鸟,这篇讲 ...
- Android实训案例(二)——Android下的CMD命令之关机重启以及重启recovery
Android实训案例(二)--Android下的CMD命令之关机重启以及重启recovery Android刚兴起的时候,着实让一些小众软件火了一把,切水果,Tom猫,吹裙子就是其中的代表,当然还有 ...
- Android实训案例(一)——计算器的运算逻辑
Android实训案例(一)--计算器的运算逻辑 应一个朋友的邀请,叫我写一个计算器,开始觉得,就一个计算器嘛,很简单的,但是写着写着发现自己写出来的逻辑真不严谨,于是搜索了一下,看到mk(没有打广告 ...
- Android实训案例(七)——四大组件之一Service初步了解,实现通话录音功能,抽调接口
Service Service的神奇之处,在于他不需要界面,一切的操作都在后台操作,所以很多全局性(手机助手,语音助手)之类的应用很长需要这个,我们今天也来玩玩 我们新建一个工程--ServiceDe ...
- Android实训案例(七)——四大组件之中的一个Service初步了解,实现通话录音功能,抽调接口
Service Service的奇妙之处.在于他不须要界面,一切的操作都在后台操作,所以非常多全局性(手机助手,语音助手)之类的应用非常长须要这个.我们今天也来玩玩 我们新建一个project--Se ...
随机推荐
- 无忧代理免费ip爬取(端口js加密)
起因 为了训练爬虫技能(其实主要还是js技能-),翻了可能有反爬的网站挨个摧残,现在轮到这个网站了:http://www.data5u.com/free/index.shtml 解密过程 打开网站,在 ...
- webpack4.1.1的使用详细教程
安装全局webpack cnpm install -g webpack 安装全局webpack-cli npm install -g webpack-cli 初始化:生成package.json文件 ...
- 关于Linux下软件包aptitude的相关操作
aptitude+回车 - 进入aptitude操作界面,可以对预览查看各种软件包 aptitude show package_name - 列出与XXX相关的软件包信息,但是并不能看到该软件包所安装 ...
- 没有JavaScript的基础,我可以学习Angular2吗?
Can I learn and understand Angular2 without understanding JavaScript? 没有JavaScript基础我能学习和理解Angular2吗 ...
- linux网络编程之二-----多播(组播)编程
多播编程实例 服务器端 下面是一个多播服务器的例子.多播服务器的程序设计很简单,建立一个数据包套接字,选定多播的IP地址和端口,直接向此多播地址发送数据就可以了.多播服务器的程序设计,不需要服务器加入 ...
- XCode使用技巧
XCode使用技巧 自动生成get.set方法 @property 用法 #import <Foundation/Foundation.h> @interface People : NSO ...
- 热烈庆祝自已厉精13年开发的 DB查询分析器 7.01(最新版本) 在中关村在线本月获得近6000次的下载量
中国本土程序员马根峰(CSDN专访马根峰:海量数据处理与分析大师的中国本土程序员)推出的个人作品----万能数据库查询分析器,中文版本 DB 查询分析器.英文版本DB Query Analyzer.它 ...
- How to kill a particular user terminal on Linux
Intro. Sometimes, the application we launched from command promp failed to exit. What we require is ...
- Mac上如何完美的转换epub至mobi供kindle观看
网上有很多书籍资源的格式都是epub(我们不谈及pdf格式了,你懂得-),epub格式是无法直接在kindle上观赏的,除非你越狱kinde后,安装扩展插件 我们可以将epub转换为mobi格式,网上 ...
- PHP Ajax JavaScript 实现 无刷新附件上传
普通表单 前端页面 后台处理 带有文件的表单 刷新方式 前端界面 后台页面 无刷新方式 大文件上传 POST极值 upload极值 上传细节 前端页面 后台处理 总结 对一个网站而言,有一个基本的不可 ...