Android Studio五子棋开发实战:从自定义View到AI算法实现

Android Studio五子棋开发实战:从自定义View到AI算法实现 简介这是一份面向Android初学者与进阶开发者的五子棋实战项目资源聚焦Android Studio IDE环境下的移动端游戏开发全流程实践帮助学习者掌握布局设计、事件响应、游戏逻辑封装与状态管理等核心能力。压缩包共49个文件包含17个XML布局与配置文件定义界面结构与资源引用、15个PNG/JPG棋盘与棋子素材、4个Kotlin源码文件实现MainActivity、ChessBoard、GameRule等关键逻辑类以及Gradle构建脚本、Git版本配置等工程必需文件整体大小877KB结构规范开箱即用。已有2304人学习下载资源目录清晰呈现标准Android模块化结构app/src/main/下完整代码与资源组织附带可直接运行的调试配置与基础音效支持适合用于课程设计、技能实训或独立项目复现助你从零构建可交互、可验证胜负逻辑的完整五子棋应用。1. 从零到一为什么选择Android Studio开发五子棋如果你是一名Android开发者或者对移动应用开发感兴趣想找一个兼具趣味性和技术挑战的练手项目那么用Android Studio开发一个五子棋游戏绝对是个绝佳的选择。这听起来像是个老生常谈的“入门级”项目但真正动手做下来你会发现它远不止“画个棋盘、摆几个棋子”那么简单。它几乎覆盖了一个标准Android应用从UI绘制、事件交互、业务逻辑到AI算法集成的完整链路是一个检验你综合能力的“试金石”。我最初选择这个项目就是想验证一下自己对Android基础知识的掌握程度。五子棋的规则简单但要把规则用代码严谨地表达出来并且提供一个流畅、美观的用户界面需要你系统地思考。从最开始的“画个棋盘都费劲”到后来能实现人机对战甚至引入简单的AI算法整个过程就像打通关一样每解决一个问题都能获得实实在在的成就感。更重要的是这个项目麻雀虽小五脏俱全它涉及到的技术点比如View的自定义绘制、触摸事件处理、RecyclerView的灵活运用用于历史记录、游戏状态管理、乃至简单的算法设计都是你日后开发更复杂应用时不可或缺的基石。很多人一提到Android Studio首先想到的是安装、配置、汉化这些前期工作。确实一个顺畅的开发环境是高效编码的前提。但我想说的是与其在环境配置上反复折腾不如尽快进入项目实战。五子棋项目就是一个完美的起点它能让你在解决实际问题的过程中自然而然地熟悉Android Studio的各种功能比如布局编辑器、日志查看器、调试工具等。当你看着自己写的代码能让棋盘上的棋子按照规则落下并能准确判断输赢时那种感觉比单纯看教程要深刻得多。2. 项目骨架搭建环境、工程结构与核心类设计在开始敲代码之前我们需要把项目的“地基”打好。这包括确保开发环境就绪以及规划好整个项目的代码结构。一个清晰的结构能让后续的开发、调试和维护事半功倍。2.1 开发环境准备与避坑指南首先你需要安装并配置好Android Studio。虽然网络上有大量的“Android Studio安装教程”但根据我的经验最容易出问题的环节往往不是安装本身而是后续的SDK下载、Gradle构建和模拟器创建。Android Studio安装核心要点下载与安装建议从官方网站下载最新稳定版。安装过程中注意选择安装路径尽量不要包含中文或空格。安装组件时确保勾选了“Android Virtual Device”AVD这是后续创建模拟器所必需的。首次运行与SDK配置首次启动时Android Studio会引导你下载SDK软件开发工具包。这里的关键是选择一个网络通畅的镜像源。如果你在国内可以将SDK Manager中的“Android SDK Online Update Site”的URL替换为国内镜像地址例如阿里云或腾讯云的镜像这能极大提升下载速度避免卡在“Downloading…”阶段。Gradle构建新建或导入项目时经常会遇到Gradle构建缓慢或失败的问题。这是因为Gradle的默认仓库jcenter()或google()在国内访问可能不稳定。解决方案是修改项目根目录下的build.gradle文件注意是Project级别的不是Module级别的在buildscript和allprojects的repositories块中添加阿里云的Maven仓库镜像。// 在build.gradle文件中添加 allprojects { repositories { maven { url https://maven.aliyun.com/repository/public/ } maven { url https://maven.aliyun.com/repository/google/ } // 原有的google()和jcenter()可以注释掉或保留在后 google() mavenCentral() } }模拟器AVD创建这是另一个“重灾区”。如果遇到“Android Virtual Device Unavailable”或性能极差通常是电脑的虚拟化技术如Intel HAXM或AMD Hyper-V未开启或冲突导致的。检查BIOS重启电脑进入BIOS设置确保CPU的虚拟化技术Intel VT-x或AMD SVM是开启状态。Windows Hyper-V冲突如果你使用的是Windows 10/11专业版或企业版并且开启了Hyper-V功能它会与HAXM冲突。对于五子棋这种轻量级应用我建议关闭Hyper-V使用HAXM以获得更好的模拟器性能。可以在“Windows功能”中关闭Hyper-V然后通过SDK Manager重新安装Intel x86 Emulator Accelerator (HAXM installer)。替代方案如果电脑不支持或不想折腾虚拟化可以考虑使用真机调试或者试用Android Studio新推出的更轻量的模拟器如Canary版本中的新模拟器。注意关于汉化我个人不推荐初学者一上来就使用汉化包。Android Studio的英文界面能帮助你更准确地搜索错误信息和官方文档。熟悉了关键菜单的位置后英文界面并不会成为障碍反而能让你更好地融入全球开发者社区。2.2 工程结构与核心类职责划分一个结构清晰的项目就像一本目录清晰的书。对于我们的五子棋应用我建议采用以下简单的MVCModel-View-Controller模式进行架构虽然简单但足以清晰地分离职责。com.yourcompany.gobang (包名) ├── model (数据模型层) │ ├── ChessBoard.java // 棋盘数据模型核心逻辑所在 │ ├── Player.java // 玩家枚举黑方、白方、AI │ └── GameState.java // 游戏状态枚举进行中、黑胜、白胜、平局 ├── view (视图层) │ ├── ChessBoardView.java // 自定义View负责绘制棋盘和棋子 │ └── activity_main.xml // 主界面布局包含ChessBoardView和按钮等 ├── controller (控制层) │ └── MainActivity.java // 主Activity协调View和Model处理用户输入 └── ai (可选AI层) └── SimpleAI.java // 简单的AI算法实现核心类解析ChessBoard这是游戏的大脑。它内部维护一个二维数组比如int[][] board来表示棋盘状态0为空1为黑子2为白子。它提供落子方法placePiece(int x, int y, Player player)并在每次落子后调用checkWin(int x, int y)方法来判断是否有一方获胜。所有游戏规则如是否重复落子、是否超出边界、是否五子连珠都在这里实现。ChessBoardView这是游戏的脸面。它继承自View在onDraw(Canvas canvas)方法中使用Canvas的API绘制棋盘网格和棋子。它还需要重写onTouchEvent(MotionEvent event)方法来监听用户的触摸事件将触摸坐标转换为棋盘上的行列索引并通知MainActivity处理落子逻辑。MainActivity这是游戏的神经中枢。它持有ChessBoard和ChessBoardView的实例。当ChessBoardView捕获到触摸事件时MainActivity会调用ChessBoard.placePiece()尝试落子。根据落子结果成功、失败、获胜MainActivity更新游戏状态并调用ChessBoardView.invalidate()重绘界面或者弹出对话框提示胜负。这样的分离使得代码易于理解和维护。比如你想把棋盘从15x15改成19x19只需要修改ChessBoard中的数组大小和ChessBoardView中的绘制逻辑MainActivity几乎不用动。如果你想加入网络对战功能也只需要在MainActivity中增加网络通信模块而不必大动干戈地修改视图或核心逻辑。3. 核心实现绘制、交互与胜负判定有了清晰的架构我们就可以开始实现最核心的三个部分了绘制棋盘、处理用户点击、以及判断游戏输赢。这是整个项目从“概念”变为“可玩”的关键步骤。3.1 自定义View绘制棋盘与棋子ChessBoardView的自定义绘制是UI部分的核心。我们需要精确计算棋盘上每个交叉点的位置。关键步骤与计算逻辑测量与布局在onMeasure中我们通常希望棋盘是一个正方形。可以设定View的宽度和高度为相同值通常取宽高中较小的那个以确保棋盘在屏幕上完整显示且不变形。计算网格与落子点在onDraw方法中我们需要知道棋盘网格的起始坐标(startX,startY)、网格间距(gridWidth)以及棋盘大小(boardSize如15)。// 假设棋盘边距为padding float padding 50f; float contentWidth getWidth() - 2 * padding; float gridWidth contentWidth / (boardSize - 1); // 网格间距 // 绘制棋盘网格 Paint gridPaint new Paint(); gridPaint.setColor(Color.BLACK); gridPaint.setStrokeWidth(2f); for (int i 0; i boardSize; i) { // 画横线 float startY padding i * gridWidth; canvas.drawLine(padding, startY, padding contentWidth, startY, gridPaint); // 画竖线 float startX padding i * gridWidth; canvas.drawLine(startX, padding, startX, padding contentWidth, gridPaint); }绘制棋子遍历ChessBoard模型中的二维数组对于每一个有棋子的位置计算其中心坐标并绘制圆形。// 绘制黑子或白子 Paint piecePaint new Paint(); for (int i 0; i boardSize; i) { for (int j 0; j boardSize; j) { if (board[i][j] ChessBoard.BLACK) { piecePaint.setColor(Color.BLACK); drawPiece(canvas, i, j, piecePaint); } else if (board[i][j] ChessBoard.WHITE) { piecePaint.setColor(Color.WHITE); piecePaint.setStyle(Paint.Style.FILL); drawPiece(canvas, i, j, piecePaint); // 为白子添加一个细黑边使其在白色棋盘上更清晰 piecePaint.setStyle(Paint.Style.STROKE); piecePaint.setStrokeWidth(2f); piecePaint.setColor(Color.BLACK); drawPiece(canvas, i, j, piecePaint); } } } private void drawPiece(Canvas canvas, int row, int col, Paint paint) { float centerX padding col * gridWidth; float centerY padding row * gridWidth; float radius gridWidth * 0.4f; // 棋子半径略小于网格间距的一半 canvas.drawCircle(centerX, centerY, radius, paint); }实操心得绘制白棋时如果不加描边在浅色背景上会几乎看不见。添加一个黑色的描边(Paint.Style.STROKE)是提升视觉效果的一个小技巧。另外棋子半径radius的取值很关键取gridWidth * 0.4f左右看起来比较协调既不会太小显得小气也不会太大导致棋子挤在一起。3.2 触摸事件处理将点击转换为落子用户是通过触摸屏幕来下棋的我们需要把屏幕坐标(x, y)转换成棋盘的(row, col)索引。Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() MotionEvent.ACTION_DOWN) { float touchX event.getX(); float touchY event.getY(); // 1. 判断触摸点是否在棋盘区域内 if (touchX padding || touchX getWidth() - padding || touchY padding || touchY getHeight() - padding) { return false; // 触摸点在棋盘外不处理 } // 2. 将触摸坐标转换为棋盘行列索引 // 思路计算触摸点相对于棋盘左上角(padding, padding)的位置除以网格间距再四舍五入 float relativeX touchX - padding; float relativeY touchY - padding; int col Math.round(relativeX / gridWidth); int row Math.round(relativeY / gridWidth); // 3. 确保索引在有效范围内 (0 到 boardSize-1) col Math.max(0, Math.min(col, boardSize - 1)); row Math.max(0, Math.min(row, boardSize - 1)); // 4. 通过接口回调将落子位置通知给Activity if (listener ! null) { listener.onPiecePlaced(row, col); } return true; // 事件已消费 } return super.onTouchEvent(event); }这里使用Math.round()进行四舍五入是最简单直观的方式用户体验也比较好——用户点击位置离哪个交叉点最近就落在哪里。你也可以尝试其他策略比如向下取整(Math.floor)但四舍五入是最符合直觉的。3.3 胜负判定算法Java实现的核心逻辑这是整个游戏的“裁判”逻辑也是算法部分的核心。五子棋的胜负判定规则很简单在横、竖、左斜、右斜四个方向上是否有连续五个同色棋子。高效判定思路我们不需要每次判定都扫描整个15x15的棋盘。更高效的做法是在每次落子后以该落子点为中心向四个方向进行探测。因为只有新落的这个子才有可能形成新的五连珠。public class ChessBoard { private int[][] board; // 棋盘0空1黑2白 private int boardSize 15; private int currentPlayer 1; // 当前玩家1为黑 /** * 在指定位置落子并检查是否获胜 * param row 行 * param col 列 * param player 玩家 (1或2) * return 落子是否成功以及游戏状态 */ public GameResult placePiece(int row, int col, int player) { // 1. 边界和重复落子检查 if (row 0 || row boardSize || col 0 || col boardSize) { return new GameResult(false, GameState.PLAYING, “位置超出棋盘范围”); } if (board[row][col] ! 0) { return new GameResult(false, GameState.PLAYING, “该位置已有棋子”); } if (player ! currentPlayer) { return new GameResult(false, GameState.PLAYING, “不是该玩家的回合”); } // 2. 落子 board[row][col] player; // 3. 检查是否获胜 if (checkWin(row, col, player)) { GameState state (player 1) ? GameState.BLACK_WIN : GameState.WHITE_WIN; return new GameResult(true, state, “恭喜获胜”); } // 4. 检查是否平局棋盘已满 if (isBoardFull()) { return new GameResult(true, GameState.DRAW, “棋盘已满平局”); } // 5. 切换玩家 currentPlayer (currentPlayer 1) ? 2 : 1; return new GameResult(true, GameState.PLAYING, “落子成功”); } private boolean checkWin(int row, int col, int player) { // 四个方向向量横、竖、左上到右下、右上到左下 int[][] directions {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; for (int[] dir : directions) { int count 1; // 统计连续棋子数当前刚落下的子算一个 // 向正方向搜索 count countDirection(row, col, dir[0], dir[1], player); // 向反方向搜索 count countDirection(row, col, -dir[0], -dir[1], player); // 如果连续数达到5则获胜 if (count 5) { return true; } } return false; } private int countDirection(int row, int col, int dRow, int dCol, int player) { int count 0; int r row dRow; int c col dCol; // 沿着(dRow, dCol)方向一直找直到棋子颜色不同或超出边界 while (r 0 r boardSize c 0 c boardSize board[r][c] player) { count; r dRow; c dCol; } return count; } private boolean isBoardFull() { for (int i 0; i boardSize; i) { for (int j 0; j boardSize; j) { if (board[i][j] 0) { return false; } } } return true; } }算法细节解析directions数组定义了四个方向水平向右(0,1)、垂直向下(1,0)、右下对角线(1,1)、左下对角线(1,-1)。checkWin方法遍历这四个方向。对于每个方向它从落子点开始分别向正反两个方向计数连续的相同颜色的棋子。countDirection是核心的计数函数。它沿着给定的(dRow, dCol)方向步进直到遇到不同颜色的棋子或棋盘边界为止。为什么正反方向计数后count初始值为1因为落子点(row, col)本身就是一个同色棋子。所以最终判断条件是count 5。避坑提示这里有一个初学者极易忽略的边界问题。在countDirection的while循环条件中必须先判断坐标(r, c)是否在棋盘范围内(r 0 r boardSize c 0 c boardSize)然后再去访问board[r][c]。如果顺序反了当r或c越界时程序会抛出ArrayIndexOutOfBoundsException异常。这个顺序是防御性编程的基本功。4. 功能增强从人机对战到游戏体验优化一个基础的五子棋已经完成了。但我们可以让它变得更好玩、更友好。这里主要探讨两个方向引入一个简单的AI对手以及优化游戏的整体体验。4.1 实现一个“不那么笨”的简单AI一个“地狱难度”的AI需要用到博弈树搜索如Minimax算法配合Alpha-Beta剪枝和复杂的评估函数这超出了入门项目的范畴。但我们可以实现一个“有点脑子”的初级AI让它至少会进攻和防守。思路基于评分的贪心算法评估函数为棋盘上每一个空位计算一个“分数”。分数越高表示在这个位置落子价值越大。评分规则可以非常简化成五直接获胜最高分例如10000分。活四两头无阻挡的四连子次高分例如5000分。冲四一头被堵的四连子较高分例如1000分。活三中等分数例如500分。活二较低分数例如100分。防守价值同时考虑我方在此落子能形成的棋型和对方在此落子能形成的棋型。如果对方在此落子能成活四或冲四那么我方在此落子防守的价值也很高。AI决策遍历所有空位计算每个空位对AI白方和对玩家黑方的评分。可以取一个加权和例如AI进攻分 * 1.2 防守玩家分 * 1.0。选择综合分数最高的位置落子。public class SimpleAI { private ChessBoard board; public Move getBestMove(ChessBoard board, int aiPlayer) { this.board board; int humanPlayer (aiPlayer 1) ? 2 : 1; int bestScore Integer.MIN_VALUE; Move bestMove new Move(-1, -1); int boardSize board.getSize(); for (int i 0; i boardSize; i) { for (int j 0; j boardSize; j) { if (board.isEmpty(i, j)) { // 计算在此落子对AI的进攻价值 int attackScore evaluatePosition(i, j, aiPlayer); // 计算如果玩家在此落子的威胁即AI在此防守的价值 int defendScore evaluatePosition(i, j, humanPlayer); // 综合评分可以给进攻稍高的权重 int totalScore (int)(attackScore * 1.2 defendScore * 1.0); if (totalScore bestScore) { bestScore totalScore; bestMove.row i; bestMove.col j; } } } } return bestMove; } private int evaluatePosition(int row, int col, int player) { // 这是一个非常简化的评估函数 // 模拟在此落子 board.placePieceForEvaluation(row, col, player); // 需要一个不改变真实棋盘状态的临时方法 int score 0; // 检查四个方向根据形成的棋型加分 // ... 这里需要实现具体的棋型判断逻辑代码较长略去细节 ... board.undoPlacePieceForEvaluation(row, col); // 撤销模拟落子 return score; } }这个AI虽然简单但已经比随机落子聪明很多了。它会主动寻找能让自己形成好棋型的位置同时也会去堵住玩家可能形成好棋型的位置。你可以通过调整评分规则和权重来改变AI的“性格”是更激进还是更保守。4.2 游戏体验优化与功能扩展一个完整的游戏应用除了核心玩法还需要考虑用户体验。这里有几个可以立刻着手优化的点游戏状态管理在MainActivity中清晰管理游戏状态准备、玩家回合、AI思考、游戏结束。在AI思考时可以显示一个“思考中…”的提示并禁用棋盘触摸防止玩家乱点。悔棋功能在ChessBoard模型中维护一个StackMove来记录每一步棋。悔棋时从栈中弹出上一步将棋盘对应位置清零并切换当前玩家。注意需要同时处理玩家悔棋和AI悔棋通常是连续退两步。重新开始与退出提供明显的按钮。重新开始功能需要重置ChessBoard数据、清空历史栈、重置游戏状态并重绘视图。音效与震动落子、获胜、无效操作等都可以添加简单的音效和震动反馈让游戏更有质感。可以使用SoundPool播放短音效使用Vibrator实现震动注意权限声明。历史棋谱记录使用RecyclerView展示每一步棋的序号和位置例如“第12手黑方 (8, 7)”。点击历史记录可以快速复盘到那一步这需要保存每一步之后的完整棋盘状态快照对内存有一定要求可以用序列化存储或只记录步骤并实时重算。界面美化给棋盘加上木质纹理背景使用更精致的棋子图片带光泽感替代纯色圆添加一个美观的状态栏显示当前回合玩家。这些都可以通过自定义Drawable和图片资源来实现。关于性能对于15x15的棋盘即使是最简单的全盘遍历算法在现代手机上性能也完全不是问题。但如果你计划实现更复杂的AI如深度搜索就需要考虑将AI计算放在子线程中避免阻塞UI线程导致应用无响应ANR。可以使用AsyncTask或更现代的Kotlin协程、RxJava来处理。5. 调试、测试与发布前检查代码写完了游戏能跑了但别急着收工。一个负责任的项目还需要经过充分的调试和测试。5.1 利用Android Studio高效调试Android Studio的调试器是你最好的朋友。断点调试在checkWin、onTouchEvent等关键函数入口打上断点。运行应用当代码执行到断点时会暂停你可以查看此时所有变量的值单步执行(F8)步入函数(F7)这对于理解程序执行流程、排查逻辑错误至关重要。Log日志在关键位置使用Log.d(“Gobang”, “落子位置” row “,” col)输出日志。通过Android Studio的Logcat窗口过滤标签“Gobang”可以清晰地看到程序的运行轨迹。这在调试触摸坐标转换、AI决策过程时特别有用。布局检查器Layout Inspector如果你的棋盘显示有问题比如不显示、位置错乱可以运行应用后点击Android Studio工具栏的“Tools - Layout Inspector”。它可以让你看到当前Activity视图的完整层级和每个View的属性帮你快速定位是测量布局问题还是绘制问题。5.2 专项测试用例编写系统的测试用例可能有点大材小用但手动进行一些专项测试是必要的。边界测试点击棋盘的最左上角、最右下角、以及四个边线中心点确保落子位置准确不会越界。规则测试重复落子在已有棋子的位置点击应用应提示无效且棋盘状态不变。胜负判定故意摆出横、竖、斜四种五连珠棋型测试checkWin函数是否能正确判定。平局判定手动下满整个棋盘除了最后一个位置然后在最后一个位置落子看是否会正确判定平局。AI基础测试让AI先手观察其落子是否集中在天元中心点附近这是合理的开局。制造一个活三看AI是否会去防守。用户体验测试旋转屏幕游戏过程中旋转手机检查棋盘状态是否保持需要在Activity中通过onSaveInstanceState保存和恢复游戏数据。中断测试游戏中途切换到其他应用或接电话再返回游戏状态是否正常。悔棋链测试连续多次悔棋再重新下子确保棋盘状态和历史记录正确无误。5.3 发布前的最后清单如果你打算把这个应用分享给朋友或者上传到某个应用市场需要发布正式签名APK下面这些事情需要检查应用图标替换掉默认的Android机器人图标在res/mipmap目录下放置不同分辨率的图标。应用名称在AndroidManifest.xml和res/values/strings.xml中修改应用名称为“五子棋”或你喜欢的名字。权限清理检查AndroidManifest.xml移除所有不必要的权限比如我们如果没使用震动就不要声明VIBRATE权限。代码混淆与压缩在build.gradle中开启minifyEnabled和shrinkResources这能减小APK体积并增加一点反编译难度。注意为需要反射的类如果有添加混淆规则。生成签名APK使用Android Studio的“Generate Signed Bundle / APK”向导创建一个密钥库jks文件并为APK签名。务必保管好这个密钥库文件和密码这是你应用的唯一身份凭证未来更新应用必须使用相同的密钥签名。多渠道打包可选如果你需要为不同平台打包不同的APK可以考虑使用productFlavors。做完这些一个由你亲手打造的、功能完整的Android五子棋应用就真正完成了。从环境搭建到算法实现从界面绘制到体验优化这个项目虽然不大但它像一条线串起了Android应用开发的许多核心知识点。下次当你再看到“Android Studio RecyclerView是什么”、“如何判断五子棋输赢”这类问题时你心里已经有了从理论到实践的完整答案。更重要的是你拥有了将一个想法通过代码一步步变成现实的能力。这才是这个项目带给你的比代码本身更宝贵的东西。本文还有配套的精品资源点击获取