
简介面向Android游戏开发学习者的完整中国象棋项目基于Java实现覆盖棋盘绘制、走棋规则、人机交互与AI对弈等关键模块尤其适合正在学习Android游戏开发、准备课程设计或希望阅读完整工程源码的初学者与进阶者。压缩包共110个文件大小仅4.47MB包含66个PNG图片资源、10个Java源文件、20个class编译文件、3个MP3音效、XML布局配置以及可直接安装的APK包可快速在真机或模拟器上运行并观察游戏整体效果。源码围绕中国象棋的核心玩法展开通过Canvas绘制棋盘和棋子利用MotionEvent处理触摸选棋与落子再结合Minimax或Alpha-Beta剪枝的评估函数实现电脑自动走棋同时res目录下的图片、音效与布局文件以及db、xml等配置展示了Android资源管理、项目目录划分和素材引用的常见做法。资源已有521人学习下载从UI渲染、事件响应到AI决策、性能优化均能在源码中找到对应实现是一份具有直接参考价值的Android游戏实战案例。 做 Android 开发这几年陆陆续续接触了不少游戏类项目但要说最能练基本功的我不开玩笑真的首推中国象棋源码这种棋盘类应用。一套完整的 Android 中国象棋项目涉及自定义 View 绘制、坐标换算、触摸交互、规则引擎、AI 博弈搜索、网络对战协议几乎把一个移动端开发者的短板全部覆盖了一遍。这篇文章就围绕我这套实际维护的 Android 中国象棋源码展开从技术选型到关键代码再到调试过程里踩过的那些坑尽量把核心内容讲透。适合有一定 Android 基础、想完整走一遍棋盘类应用开发流程的朋友参考。1. 项目整体设计与技术选型思路1.1 一套象棋源码到底解决了什么问题很多人觉得中国象棋 APP 不就是画个棋盘、放几张棋子图片、写个点击事件吗其实真上手后会发现完全不是这么回事。你先要处理棋盘的九宫斜线、楚河汉界、炮兵初始位置这些界面细节然后要把每个棋子的走法规则写对马的蹩马腿、相的塞象眼、炮的隔子打、将帅不能照面每一项都是独立的逻辑分支。更别提 AI 对战要有局面评估、走法搜索双人模式要考虑传输协议、掉线处理。这就像盖房子UI 是外墙涂料规则引擎才是承重墙AI 是精装修哪一步偷懒后面都要加倍还回来。我自己最初写这套源码时最大的失误就是一上来就堆 UI结果规则引擎设计得一团糟后来被迫重构了三版。所以这篇文章里我会按照“引擎优先、UI 次之、AI 跟上、联网扩展”的顺序来讲这个顺序本身就是避坑指南。1.2 技术栈选型为什么坚持用自定义 View网上搜 Android 象棋源码会有不少方案用 SurfaceView、Unity 甚至 WebView 套 H5。但我最终还是选了自定义 View Java 这条路理由很直接棋类应用的核心是“低频交互 状态驱动”一局棋的棋盘重绘次数非常有限SurfaceView 那种为高频画面设计的双缓冲机制在这里属于杀鸡用牛刀而且会引入线程同步、生命周期管理等一堆额外复杂度。自定义 View 的 onDraw 方法配合 invalidate 触发重绘逻辑清晰、易于调试性能也完全足够。这里要补充一句如果你用的是 Android Studio 环境直接在 build.gradle 里配置好 Java 版本依赖就行。我写这套源码时用的是 Java 8没引入第三方库棋盘绘制、规则引擎、AI 搜索全部基于 Android 原生 API 实现这样任何人拿到源码都能直接编译运行也方便改成 Kotlin 版本。不要把简单问题复杂化能用原生解决的就不引依赖这是做棋类小游戏的一条重要原则。2. 棋盘渲染与棋子交互的完整实现2.1 棋盘坐标体系与 UI 坐标的换算中国象棋棋盘是 9 列 10 行的网格我直接用int[10][9]二维数组来表示局面数组下标board[row][col]对应棋盘的逻辑坐标其中 row 范围 0~9col 范围 0~8。棋子用整数常量表示红方用正数黑方用负数判断是不是同一方只需看符号是否一致非常方便。public class Board { public static final int EMPTY 0; public static final int R_KING 1; // 帅 public static final int R_ADVISOR 2; // 仕 public static final int R_ELEPHANT 3; // 相 public static final int R_KNIGHT 4; // 马 public static final int R_ROOK 5; // 车 public static final int R_CANNON 6; // 炮 public static final int R_PAWN 7; // 兵 private int[][] board new int[10][9]; }绘制棋盘时先计算格子边长cellSize再留出一定边距margin。每个棋子的圆心坐标就是margin col * cellSize和margin row * cellSize这里不难真正容易出错的是反向换算用户点击屏幕时触摸点坐标转换成棋盘坐标要用“四舍五入”而不是直接整除。如果只用(x - margin) / cellSize触摸点在格子边缘附近时会因为精度问题选到错误的格子导致误触频发。我实际用的是int col (int) Math.round((x - margin) * 1.0 / cellSize); int row (int) Math.round((y - margin) * 1.0 / cellSize);然后再加一个越界判断落在棋盘外就返回 EMPTY保证数组访问安全。这套换算逻辑写一次后面所有触摸事件、合法走法高亮、落子动画都能复用减少了很多重复代码。2.2 触摸滑动选子与落点反馈的交互设计交互部分我建议用一套简单的状态机空闲、选中棋子、移动中。空闲状态下点击到己方棋子则进入“选中”状态并通过invalidate()触发重绘在选中棋子上画一个半透明蓝色圆环作为视觉反馈同时用 List 保存该棋子的所有合法走法把可落子的位置画成小圆点。移动过程中棋子位置跟随手指移动松手时判断落点是否在合法走法列表里不在就把棋子弹回原位。这套逻辑听起来简单实现时有个关键点合法走法列表必须在选中棋子的那一刻就生成好并且只生成一次。不能在 ACTION_UP 时才去计算因为用户在滑动过程中会频繁触发重绘每次重绘都重新计算整棵走法树的话卡顿会非常明显。我实测过一次全棋子走法生成在手机上大约耗时 1~5 毫秒看似不多但叠加触摸事件的频繁触发就很容易掉帧。还有一点棋子图片不建议直接用系统绘制圆形加文字的方式虽然开发效率高但视觉上比较简陋。我后来改用两张位图红方棋子一张、黑方棋子一张通过 Canvas 的 drawBitmap 绘制边缘加一层淡淡的渐变圆环观感好很多。UI 细节这种地方不用急着优化先把交互逻辑跑通最后统一打磨。3. 规则引擎走法生成与胜负判断实战3.1 每种棋子的走法生成与特殊绊脚规则规则引擎是整盘棋的灵魂我把它独立成一个类输入棋盘状态和棋子坐标输出一个ListMoveMove 就是包含 fromRow、fromCol、toRow、toCol 的简单数据结构。每个棋子的走法生成逻辑看着麻烦其实规律性很强。车最简单四个方向直线延伸遇到己方棋子停止遇到敌方棋子则把这个位置加入走法后停止。炮的规则和车类似但多了一个“翻山”判断前方没有炮架时不能吃子有炮架时只能越过炮架吃子且炮架只能有一个。马走日字需要判断蹩马腿的位置是否为空相走田字需要判断象眼位置同时不能越过楚河汉界士只能走九宫内斜线将帅只能在九宫内直线一步同时要处理“照面”规则。兵卒最特殊过河前只能前进过河后才能左右移动。这些规则里最容易写错的是马和炮。马的蹩马腿判断我曾经把位置搞反过一次明明马在左边却去检测右边的格子是否为空导致 AI 走出一堆不合法的拐弯马。炮的判断更隐蔽吃掉敌方棋子的前提是跳过且只能跳过一个棋子如果炮和目标之间有多个棋子那是不能吃子的。测试的时候建议把每种棋子的走法单独打印出来和棋谱上验证过的合法走法逐一比对这是最笨但最有效的方法。3.2 将军、将死与困毙的判定流程走法生成只是基础规则引擎的难点在于将军和将死判定。我的做法比较笨但非常可靠模拟走一步棋然后看己方将帅是否暴露在对方的攻击范围内如果在就说明这一步会被将军或者走完仍然处于被将军状态直接撤销。判断某个位置是否被攻击做法是扫描全盘所有敌方棋子生成它们的所有合法走法看是否有任何走法的目标位置是将帅所在位置。这个方法计算量稍大但逻辑简单不容易出错。将死判定就是在搜索所有合法走法后发现任何走法都无法解除被将军状态。困毙则是我方当前没有合法走法但并没有被将军。这两种情况在实际对局中都要判负处理。我在 AI 搜索里会用深度值微调分数把“快一步将死”的权重调高这样 AI 会优先选择更快的杀棋路线观感上也更聪明。另外要提醒一下规则引擎和 UI 必须彻底分离。我在这套源码里把 Board、Move、RuleEngine 全部做成纯 Java 类不依赖任何 Android 控件这样不仅方便写单元测试也为后面 AI、网络对战模块复用逻辑提供了基础。很多初学者把棋盘数组放在自定义 View 内部导致规则逻辑和绘制逻辑高度耦合一改 UI 全盘崩这种架构从一开始就不该出现。4. AI 对战算法估值函数与 Alpha-Beta 搜索4.1 估值函数设计把棋力优劣变成数字AI 对战的直观水平取决于估值函数是不是合理。我用的是经典的“棋子基础分 位置价值分”方案。棋子基础分参考主流做法帅 10000、车 900、炮 500、马 450、相/仕 250、兵 100。位置价值分则是一张 10×9 的二维数组给不同位置的棋子加分比如马在中心区域的活跃度远高于边角兵过河后位置分显著提升。private int evaluate() { int score 0; for (int row 0; row 10; row) { for (int col 0; col 9; col) { int piece board[row][col]; if (piece EMPTY) continue; int value PIECE_VALUE[Math.abs(piece)] positionScore(row, col, piece); score (piece 0) ? value : -value; } } return score; // 正数代表红方有利 }位置价值表不要一开始就追求完美先用一套大致合理的数值让 AI 能跑起来再通过和不同等级的玩家对战记录来不断微调。我试过把某个版本的“相”的位置分调太高结果 AI 开局疯狂走相不出动大子场面非常诡异。后来我加入了一点点随机化让 AI 在分数相当的走法中随机选择一个走棋就不会每次都千篇一律了这个体验细节对玩家来说很重要。4.2 Alpha-Beta 剪枝搜索的实际实现AI 的搜索算法我用的是 Minimax 加 Alpha-Beta 剪枝这个组合是棋类 AI 的标配。原理可以这样理解我走一步棋时假设对方也会走对他最有利的那一步所以我需要把所有可能的走法都试一遍看哪个分支最终的局势对我最有利这就是 Minimax。Alpha-Beta 则是在搜索中维护两个边界值一旦发现当前分支已经不可能比之前的分支更优就直接剪掉不再深入大幅减少搜索量。public int alphaBeta(int depth, int alpha, int beta, boolean isRedTurn) { if (depth 0) return evaluate(); ListMove moves generateAllMoves(isRedTurn); if (moves.isEmpty()) { // 用 depth 让 AI 偏向更快将死或更晚被将死 return isRedTurn ? Integer.MIN_VALUE depth : Integer.MAX_VALUE - depth; } if (isRedTurn) { int best Integer.MIN_VALUE; for (Move move : moves) { makeMove(move); best Math.max(best, alphaBeta(depth - 1, alpha, beta, false)); undoMove(move); alpha Math.max(alpha, best); if (beta alpha) break; // 剪枝 } return best; } else { int best Integer.MAX_VALUE; for (Move move : moves) { makeMove(move); best Math.min(best, alphaBeta(depth - 1, alpha, beta, true)); undoMove(move); beta Math.min(beta, best); if (beta alpha) break; } return best; } }没有剪枝的搜索是指数级增长的三层搜索大约要评估上百万个节点在手机上会明显卡顿。加了 Alpha-Beta 剪枝后同样深度搜索速度可以提升几倍甚至一个数量级所以这个优化是必须做的不是可选项。4.3 搜索深度、耗时与棋力的平衡AI 搜索深度直接决定棋力和流畅度。棋力最弱的 1 到 2 层新手玩家会觉得 AI 很蠢3 层是比较甜的平衡点手机上一手棋大概几百毫秒既有一定攻击性又不卡顿4 层以上就要看机型了中端手机上经常要两秒以上而且对开局库要求高否则前面几步就耗时严重。我最终默认用 3 层在设置菜单里提供“困难”档位给高端机用 4 层。这里有个很实用的技巧在走子生成排序上做一点优化先尝试吃子走法和将军走法Alpha-Beta 剪枝的效率会明显提升。因为好的走法越早被发现剪枝就能越早触发。代码上只需要在 generateAllMoves 后加一个简单的排序成本很小但对搜索速度的影响非常显著。我实测在同样 3 层深度下排序后的搜索时间可以缩短 40% 左右。5. 双人模式与局域网对战扩展5.1 同屏双人对战的实现要点同屏双人模式其实就是把规则引擎和 UI 交互里的“轮到哪一方走棋”逻辑改一下不限制玩家只能点己方棋子红黑双方都由当前屏幕前的人操作。唯一要注意的是旋转屏幕后的 UI 反向问题因为两个人面对面坐一方看到的是倒置的棋盘。这个属于体验优化项我建议在设置里加一个“翻转棋盘”开关默认关闭需要时手动打开不必强制自动识别反而容易出错。网络对战和同屏双人最大的区别在于走棋权限控制。联机端每走一步都要发送自己的走法给对方并且要把棋盘状态同步过去防止出现双方看到的局面不一致的 bug。我在实现中对每个走法增加了一个递增的序号接收方按序号处理乱序就缓存等待有效避免了异步消息导致的界面错乱。5.2 基于 Socket 的远程对弈协议设计讲完同屏双人再展开说说我扩展的局域网 Socket 对战。这套协议我设计得很简单走法消息一共 5 个字节第 1 字节是消息类型第 2 到 4 字节分别是 fromRow、fromCol、toRow、toCol第 5 字节是预留的校验位。这种纯二进制协议比 JSON 传输省流量解析也更快棋类应用完全够用。开局前用固定字符串做握手校验比如 “HELLO_CHESS”防止连错应用。考虑到我的源码是 Android 端Socket 通信必须放在子线程里不能在主线程直接读写。我封装了一个简单的 ClientThread / ServerThread走法通过一个 Handler 回调到 UI 线程。断线重连这块我没做得很深入只是检测到连接异常后弹出一个 Toast 提示回到主菜单重新匹配。如果你要做公网对战建议再加一层轻量级协议比如 NIO 或者 Netty但这属于后期扩展了不是象棋源码核心。6. 调试记录与避坑指南6.1 触摸偏移和误触引发的交互问题我最早一版在真机上跑时触摸选子总有一种“差一点点”的感觉点兵却选中了旁边的马。排查了半天发现问题出在getX和getRawX的混用上。自定义 View 在布局中有偏移时getRawX是相对屏幕的坐标而绘制棋子用的却是相对 View 的坐标两者不统一就导致了触摸点偏移。解决方法是统一用event.getX()和event.getY()所有坐标计算都基于 View 自身的相对坐标。另一个常见坑是绘制圆环反馈时用了canvas.drawCircle但棋子的半径是按cellSize * 0.8画的导致视觉上选中高亮比棋子还大看着很怪。后来我把棋子半径、高亮圆环半径、可走位置小圆点半径全部抽象成常量统一按cellSize的比例计算微调一个参数就能全局生效。这种小细节不影响功能但真机上手体验差别很大建议一开始就定义好。6.2 性能优化与常见崩溃排查对局中如果出现崩溃十有八九是数组越界。棋盘是 10×9 的数组某些旋转逻辑或 AI 搜索时如果没做边界判断很容易访问到负数下标或 10、9 这样的越界下标。我的建议是所有走法生成函数开头统一调用一个 isInside 方法做边界检查虽然多了一层调用但换来的是心里踏实。另外AI 搜索会大量创建 Move 对象如果直接 new 会导致 GC 频繁中低端机上会卡顿。我把 Move 设计成一个具备复用机制的容器搜索过程中循环使用同一个对象池整体内存分配量下降非常明显。还有一次闪退让我印象深刻是因为在onDraw里调用了generateAllMoves而那个方法内部又调用了evaluate()结果在极端局面下计算出超长走法列表绘制一帧耗时几十毫秒直接触发了 ANR。定位后发现还是架构问题绘制逻辑绝不应该包含规则计算。我把走法生成全部挪到ACTION_DOWN事件里onDraw只做纯绘制性能问题迎刃而解。6.3 各机型兼容性与体验调优记录Android 机型的碎片化在象棋 App 上也体现得淋漓尽致。有些全面屏手机底部有手势条遮挡了棋盘最下面一行兵卒的点击区域有些平板设备密度不同棋子和文字会显得过大或过小。我的做法是布局根部用FrameLayout包裹棋盘 View让棋盘始终居中显示并且根据屏幕宽高动态计算cellSize保证棋子在所有分辨率下都能完整显示。文字绘制用Paint.setTextSize配合密度缩放极端情况下也不会溢出棋子边界。写到这我再分享一个我在调试 AI 时发现的小技巧打开 Android Studio 的布局边界检查工具在开发者选项里开启“显示布局边界”能看到每个棋子的实际绘制区域和触摸区域是否对齐。很多时候触摸不准不是逻辑问题而是绘制区域和事件接收区域有像素级的偏差用这个工具一抓一个准。这套源码整体的开发过程里规则引擎的重构花的精力最多但也正是这部分让我对棋类应用的架构有了很深的理解后面再接任何棋盘类项目基本都能做到心里有数。本文还有配套的精品资源点击获取