Android自定义View实现五子棋游戏
作者:魂恋云 发布时间:2021-12-25 19:32:55
本文实例为大家分享了Android实现五子棋游戏的具体代码,供大家参考,具体内容如下
直接上效果图
原理
从棋盘到棋子,到开始下棋的各类点击事件,均在 ChessView 中实现,这个 View 没有提供自定义属性(因为我觉得没有必要~~~)。
项目GitHub地址:Wuziqi
实现步骤
1.新建一个棋子类,这个类非常简单,代码如下:
public class Chess {
public enum Color {BLACK, WHITE, NONE}
private Color color;
public Chess(){
this.color = Color.NONE;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
每个棋子类有三种状态,即 WHITE,BLACK,NONE。这里我们使用枚举来表示这三种状态。
2. 自定义 ChessView 类,这个类就是核心类了,我们这个五子棋的所有逻辑都是在这个类里面实现。构造方法初始化各个字段,代码如下:
public ChessView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化字段 mEveryPlay,悔棋会用到
initEveryPlay();
// 初始化每个棋子,设置属性为 NONE
initChess();
// 初始化棋盘画笔
initBoardPaint();
// 初始化棋子画笔
initChessPaint();
// 初始化背景画笔
initBgPaint();
}
各个方法的具体实现如下:
private void initEveryPlay() {
// 初始化 List 大小,此方法不影响 list.size() 返回值
mEveryPlay = new ArrayList<>(225);
}
private void initChess() {
mChessArray = new Chess[15][15];
for (int i = 0; i < mChessArray.length; i++) {
for (int j = 0; j < mChessArray[i].length; j++) {
mChessArray[i][j] = new Chess();
}
}
}
private void initChessPaint() {
mChessPaint = new Paint();
mChessPaint.setColor(android.graphics.Color.WHITE);
mChessPaint.setAntiAlias(true);
}
private void initBoardPaint() {
mBoardPaint = new Paint();
mBoardPaint.setColor(android.graphics.Color.BLACK);
mBoardPaint.setStrokeWidth(2);
}
private void initBgPaint() {
mBgPaint = new Paint();
mBgPaint.setColor(android.graphics.Color.GRAY);
mBgPaint.setAntiAlias(true);
}
3. 重写 onMeasure() 方法,强制将 View 大小变为正方形,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int min = widthSize < heightSize ? widthSize : heightSize;
// 五子棋标准棋盘线条数目为 15 x 15,为了后面计算坐标方便,我们将 View 的宽高处理为 16 的整数倍
min = min / 16 * 16;
setMeasuredDimension(min, min);
}
之所以设置为 16 的整数倍而不是 15,是因为如果设置成 15,那么棋盘的背景就会跟棋盘最边界的线条重合,此时如果有棋子落在边界,棋子将不能显示完全。
4. 重点来了,重写 onDraw() 方法,绘制出棋盘,代码如下:
@Override
protected void onDraw(Canvas canvas) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();
int avg = height / 16;
canvas.drawRect(0, 0, width, height, mBgPaint);
for (int i = 1; i < 16; i++) {
// 画竖线
canvas.drawLine(avg * i, avg, avg * i, height - avg, mBoardPaint);
// 画横线
canvas.drawLine(avg, avg * i, width - avg, avg * i, mBoardPaint);
}
for (int i = 1; i < 16; i++) {
for (int j = 1; j < 16; j++) {
switch (mChessArray[i - 1][j - 1].getColor()) {
case BLACK:
mChessPaint.setColor(android.graphics.Color.BLACK);
break;
case WHITE:
mChessPaint.setColor(android.graphics.Color.WHITE);
break;
case NONE:
continue;
}
canvas.drawCircle(avg * i, avg * j, avg / 2 - 0.5f, mChessPaint);
}
}
}
这样我们就将整个棋盘画出来了,之后我们只需要改变数组 mChessArray[][] 里面对象的 Color 属性,再调用 invalidate() 方法便可以刷新棋盘了。
5. 接下来,我们便要处理点击事件,实现对弈的逻辑了,重写 onTouchEvent() 方法,代码如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 如果棋盘被锁定(即胜负已分,返回查看棋局的时候)
// 此时只允许查看,不允许落子了
if (isLocked) {
return true;
}
float x = event.getX();
float y = event.getY();
// 以点击的位置为中心,新建一个小矩形
Rect rect = getLittleRect(x, y);
// 获得上述矩形包含的棋盘上的点
Point point = getContainPoint(rect);
if (point != null) {
// 若点不为空,则刷新对应位置棋子的属性
setChessState(point);
// 记录下每步操作,方便悔棋操作
mEveryPlay.add(point);
if (gameIsOver(point.x, point.y)) {
// 游戏结束弹窗提示
showDialog();
}
// 更改游戏玩家
isBlackPlay = !isBlackPlay;
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
下面分别来说说调用到的各个方法的实现思路:
getLittleRect()
/**
* 以传入点为中心,获得一个矩形
*
* @param x 传入点 x 坐标
* @param y 传入点 y 坐标
* @return 所得矩形
*/
private Rect getLittleRect(float x, float y) {
int side = getMeasuredHeight() / 16;
int left = (int) (x - side / 2);
int top = (int) (y - side / 2);
int right = (int) (x + side / 2);
int bottom = (int) (y + side / 2);
return new Rect(left, top, right, bottom);
}
getContainPoint()
/**
* 获取包含在 rect 中并且是能够下棋的位置的点
*
* @param rect 矩形
* @return 返回包含的点,若没有包含任何点或者包含点已有棋子返回 null
*/
private Point getContainPoint(Rect rect) {
int avg = getMeasuredHeight() / 16;
for (int i = 1; i < 16; i++) {
for (int j = 1; j < 16; j++) {
if (rect.contains(avg * i, avg * j)) {
Point point = new Point(i - 1, j - 1);
// 包含点没有棋子才返回 point
if (mChessArray[point.x][point.y].getColor() == Chess.Color.NONE) {
return point;
}
break;
}
}
}
return null;
}
showDialog()
顺便一提,这个方法用的是 v7 包里面的对话框,因为这样可以在版本较低的安卓平台下也可以得到不错的显示效果,效果如下:
/**
* 游戏结束,显示对话框
*/
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("游戏结束");
if (isBlackPlay) {
builder.setMessage("黑方获胜!!!");
} else {
builder.setMessage("白方获胜!!!");
}
builder.setCancelable(false);
builder.setPositiveButton("重新开始", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
resetChessBoard();
dialog.dismiss();
}
});
builder.setNegativeButton("返回查看", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
isLocked = true;
dialog.dismiss();
}
});
builder.show();
}
setChessState()
/**
* 重新设定用户所点位置的棋子状态
*
* @param point 棋子的位置
*/
private void setChessState(Point point) {
if (isBlackPlay) {
mChessArray[point.x][point.y].setColor(Chess.Color.BLACK);
} else {
mChessArray[point.x][point.y].setColor(Chess.Color.WHITE);
}
invalidate();
}
以上几个方法都较为简单不多说了,接下来重点讲一下判断游戏结束的逻辑。
- gameIsOver ()
/**
* 判断游戏是否结束,游戏结束标志:当前落子位置与其他同色棋子连成 5 个
*
* @param x 落子位置 x 坐标
* @param y 落子位置 y 坐标
* @return 若连成 5 个,游戏结束,返回 true,负责返回 false
*/
private boolean gameIsOver(int x, int y) {
Chess.Color color = mChessArray[x][y].getColor();
return isOverA(x, y, color) || isOverB(x, y, color) || isOverC(x, y, color) || isOverD(x, y, color);
}
这个方法用来判断游戏是否结束,思路便是以当前落子位置为基准,去寻找竖直、水平、左上至右下、左下至右上四个方向是否连成 5 子,分别对应 isOverA(), isOverB(), isOverC(), isOverD() 四个方法,这四个方法的实现如下:
private boolean isOverA(int x, int y, Chess.Color color) {
int amount = 0;
for (int i = y; i >= 0; i--) {
if (mChessArray[x][i].getColor() == color) {
amount++;
} else {
break;
}
}
for (int i = y; i < mChessArray[x].length; i++) {
if (mChessArray[x][i].getColor() == color) {
amount++;
} else {
break;
}
}
// 循环执行完成后,当前落子位置算了两次,故条件应是大于 5
return amount > 5;
}
private boolean isOverB(int x, int y, Chess.Color color) {
int amount = 0;
for (int i = x; i >= 0; i--) {
if (mChessArray[i][y].getColor() == color) {
amount++;
} else {
break;
}
}
for (int i = x; i < mChessArray.length; i++) {
if (mChessArray[i][y].getColor() == color) {
amount++;
} else {
break;
}
}
// 循环执行完成后,当前落子位置算了两次,故条件应是大于 5
return amount > 5;
}
private boolean isOverC(int x, int y, Chess.Color color) {
int amount = 0;
for (int i = x, j = y; i >= 0 && j >= 0; i--, j--) {
if (mChessArray[i][j].getColor() == color) {
amount++;
} else {
break;
}
}
for (int i = x, j = y; i < mChessArray.length && j < mChessArray[i].length; i++, j++) {
if (mChessArray[i][j].getColor() == color) {
amount++;
} else {
break;
}
}
// 循环执行完成后,当前落子位置算了两次,故条件应是大于 5
return amount > 5;
}
private boolean isOverD(int x, int y, Chess.Color color) {
int amount = 0;
for (int i = x, j = y; i < mChessArray.length && j >= 0; i++, j--) {
if (mChessArray[i][j].getColor() == color) {
amount++;
} else {
break;
}
}
for (int i = x, j = y; i >= 0 && j < mChessArray[i].length; i--, j++) {
if (mChessArray[i][j].getColor() == color) {
amount++;
} else {
break;
}
}
// 循环执行完成后,当前落子位置算了两次,故条件应是大于 5
return amount > 5;
}
6. 最后定义两个公有方法,方便 Activity 调用,用来执行悔棋和重置棋盘操作。两个方法代码如下:
/**
* 悔棋,实现思路为:记录每一步走棋的坐标,若点击了悔棋,
* 则拿出最后记录的坐标,对 mChessArray 里面对应坐标的
* 棋子进行处理(设置颜色为 NONE),并移除集合里面最后
* 一个元素
*/
public void retract() {
if (mEveryPlay.isEmpty()) {
return;
}
Point point = mEveryPlay.get(mEveryPlay.size() - 1);
mChessArray[point.x][point.y].setColor(Chess.Color.NONE);
mEveryPlay.remove(mEveryPlay.size() - 1);
isLocked = false;
isBlackPlay = !isBlackPlay;
invalidate();
}
/**
* 重置棋盘
*/
public void resetChessBoard() {
for (Chess[] chessRow : mChessArray) {
for (Chess chess : chessRow) {
chess.setColor(Chess.Color.NONE);
}
}
mEveryPlay.clear();
isBlackPlay = true;
isLocked = false;
invalidate();
}
到此, ChessView 已经写完了,接下来只要在布局文件里面声明即可。
7. 在 activity_main 布局文件如下,非常简单,我相信不用多说都能看懂:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<com.yangqi.wuziqi.ChessView
android:id="@+id/chessView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/chessView"
android:layout_margin="20dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/bt_retract"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="悔棋" />
<Button
android:id="@+id/bt_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重新开始"/>
</LinearLayout>
</RelativeLayout>
8. 最后一步了,只需要在 MainActivity 里面拿到 ChessView 对象和两个 Button 按钮,即可实现悔棋与重新开始:
package com.yangqi.wuziqi;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button bt_reset;
private Button bt_retract;
private ChessView chessView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
initListener();
}
private void initListener() {
bt_reset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
chessView.resetChessBoard();
}
});
bt_retract.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
chessView.retract();
}
});
}
private void initUI() {
bt_reset = (Button) findViewById(R.id.bt_reset);
bt_retract = (Button) findViewById(R.id.bt_retract);
chessView = (ChessView) findViewById(R.id.chessView);
}
}
来源:https://blog.csdn.net/qq_34883044/article/details/68501046


猜你喜欢
- 最近在做一个平板的应用,底部的BACK HOME 还有电池WIFI的那一条STATUS_BAR设置全屏后怎么也去不掉,查找资料后,发现一个比
- Selenium IDE 是Firefox 浏览器的一个插件, 它会记录你对Firefox的操作,并且可以回放它的操作。 用法简单,不过我觉
- 今天在做数据分页显示的时候遇到了一个问题,经过测试,证实是Tomcat 6的一个bug,我所用的版本为:apache-tomcat-6.0.
- int、String的类型转换int -> Stringint i=12345;String s="";第一种方法
- 一、ANR说明和原因1.1 简介ANR全称:Application Not Responding,也就是应用程序无响应。1.2 原因Andr
- 1、Hutool工具简介HuTool工具(糊涂工具),第三方插件工具,简化操作,是国产的一个产品,界面简洁易懂,比较人性化。(上班可能经常用
- 最初,XML 语言仅仅是意图用来作为 HTML 语言的替代品而出现的,但是随着该语言的不断发展和完善,人们越来越发现它所具有的优点:例如标记
- Mybatis配置返回为修改影响条数mybatis执行update()方法默认返回为匹配的更新记录条数,现在需要将update()方法修改为
- C# WinForm控件的拖动和缩放是个很有用的功能。实现起来其实很简单的,主要是设计控件的MouseDown、MouseLeave、Mou
- 本文实例讲述了C#使用HtmlAgilityPack抓取糗事百科内容的方法。分享给大家供大家参考。具体实现方法如下:Console.Writ
- 实现 bean 初始化、摧毁方法的配置与处理spring支持我们自定义 bean 的初始化方法和摧毁方法。配置方式可以通过 xml 的 in
- MybatisPlus特性•无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑•损耗小:启动即会自动注入基本 CURD,性能
- 前言最近新整了个博客网站,同事在gitee上找的,还不错,gitee上的地址在这里:拾壹博客管理系统。别人的业务,再好也有不满足自己的地方,
- Win32的API函数是微软自己的东西,可以直接在C#中直接调用,在做WinForm时还是很有帮助的。有时候我们之直接调用Win32 的AP
- 最近,阿里开源的nacos比较火,可以和springcloud和dubbo共用,对dubbo升级到springcloud非常的方便。这里学习
- 一、安装及配置Genymotion(1)由于Eclipse中自带的SDK模拟器,启动之慢,不说了 现在给大家介绍一种比较快的模拟器Genym
- 1.准备工作第一步就是先要注册一个支付宝的账号(注册这里不说,不是重点),然后登入官方首页,去到应用列表里面找到沙箱应用。基本信息的APPI
- 概览Android 平台包含蓝牙网络堆栈支持,此支持能让设备以无线方式与其他蓝牙设备交换数据。应用框架提供通过 Android Blueto
- 1. Vscode安装Visual studio code是微软发布的一个运行于 Mac OS X、Windows和 Linux 之上的,针
- 想要在IntelliJ IDEA编辑器里面修改,新建Class文件或者jsp文件或者js文件时候文件头自带的Created by {User