热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android自定义View实现五子棋游戏

这篇文章主要为大家详细介绍了Android自定义View实现五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了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 

3. 重写 onMeasure() 方法,强制将 View 大小变为正方形,代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  int heightSize = MeasureSpec.getSize(heightMeasureSpec);

  int min = widthSize 

之所以设置为 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  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  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  5;
 }
 private boolean isOverD(int x, int y, Chess.Color color) {
  int amount = 0;
  for (int i = x, j = y; i = 0; i++, j--) {
   if (mChessArray[i][j].getColor() == color) {
    amount++;
   } else {
    break;
   }
  }
  for (int i = x, j = y; i >= 0 && j  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 布局文件如下,非常简单,我相信不用多说都能看懂:

<&#63;xml version="1.0" encoding="utf-8"&#63;>


 

 

  

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);
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • This document outlines the recommended naming conventions for HTML attributes in Fast Components, focusing on readability and consistency with existing standards. ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 从零开始构建完整手机站:Vue CLI 3 实战指南(第一部分)
    本系列教程将引导您使用 Vue CLI 3 构建一个功能齐全的移动应用。我们将深入探讨项目中涉及的每一个知识点,并确保这些内容与实际工作中的需求紧密结合。 ... [详细]
  • 本报告涵盖了个人博客账号和码云账号的注册过程,以及对网络工程专业学习的反思与展望。通过回顾初入大学时的专业选择,分析当前的专业知识和技能水平,并对未来的职业规划进行了详细讨论。 ... [详细]
  • 本文详细介绍了 com.facebook.drawee.view.SimpleDraweeView 中的 setScaleType 方法,提供了多个实际代码示例,并解释了其在不同场景下的应用。 ... [详细]
author-avatar
小TMM_
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有