
简介这份资源是一份Android五子棋小游戏的课程设计报告面向移动应用开发课程的学生、毕业设计选题者以及需要Android项目实战参考的开发者。报告围绕一款支持人机对战与人人对战的五子棋应用展开涵盖项目背景、开发技术与环境、MVC系统架构、SQLite数据库、详细设计、运行演示及心得体会等完整章节可帮助读者理解从需求分析到功能落地的全过程。压缩包内为1个doc文档约8.29MB图文并茂、格式规范正文约9362字适合直接作为课程设计或毕业设计报告的写作模板与结构参考。目前已有320人学习下载。读者可从中获取AI落子评分决策、胜负判定、悔棋与新局、背景音乐开关、游戏介绍与规则展示等模块的设计思路并借鉴Java、Android Studio与MVC分层在小型安卓项目中的组织方式快速搭建自己的开发框架。1. 一份能跑起来的五子棋课设到底该长什么样每年到了学期中后段总有人来问同一件事Android 五子棋小游戏的课程设计报告怎么写才能既过查重、又能真跑起来。我见过太多翻车现场——报告里贴的代码和 APK 里的行为对不上棋盘画得歪歪扭扭胜负判定在斜向五连时漏判答辩时老师随手点两下就露馅。这份东西的核心其实不是报告而是一个能自洽的工程项目背景讲清为什么做、开发环境写清用什么做、详细设计说清怎么做的、运行演示证明真能做出来、心得体会交代踩过哪些坑。它适合两类人一类是刚学完 Android 基础、需要交一份完整课设的学生另一类是带课设、想快速判断一份报告含金量的指导者。下面我按自己带过几届课设的经验把这条链路拆成能照着复现的步骤重点放在那些报告里不会写、但一跑就暴露的细节上。2. 开发环境与工程骨架别在第一步就把自己埋了2.1 环境选型为什么我坚持用原生 View 而不是游戏引擎课设场景下最常见的错误是杀鸡用牛刀。有人一上来就上游戏引擎结果环境配置占了报告一半篇幅核心算法反而没时间写。五子棋是回合制、无物理、无动画刚需的场景用 Android 原生ViewonDraw完全够用而且代码量可控、答辩时能逐行讲清楚。我一般推荐的环境组合是Android Studio 作为 IDEGradle 做构建最低 SDK 定在 API 24Android 7.0目标 SDK 跟当前稳定版走。为什么最低定 24因为再往下要处理一堆权限和兼容分支课设没必要。语言用 Java 或 Kotlin 都行但报告里要统一别一半 Java 一半 Kotlin老师看着乱。工程结构建议这样分app/src/main/java/com/example/gomoku/ ├── MainActivity.java // 入口承载棋盘 View ├── GameView.java // 自定义 View负责绘制与触摸 ├── GameLogic.java // 纯逻辑落子、判胜、悔棋 └── ChessBoard.java // 棋盘数据结构与状态把逻辑和绘制分开是关键。很多人的GameView里塞了判胜、绘制、触摸、AI 四件事最后改一个 bug 牵出三个。GameLogic不依赖任何 Android 类这样你甚至能在电脑上写个main方法单测判胜逻辑不用每次装 APK。2.2 从零建工程到棋盘能画出来的最小步骤第一步新建 Empty Views Activity 工程包名自定。第二步把默认布局改成只放一个自定义 View!-- res/layout/activity_main.xml -- com.example.gomoku.GameView android:idid/gameView android:layout_widthmatch_parent android:layout_heightmatch_parent /第三步写GameView的骨架先只画网格确认坐标系对了再往下做public class GameView extends View { private int boardSize 15; // 15x15 标准棋盘 private float cellSize; // 每格边长运行时算 private float originX, originY; // 棋盘左上角起点 private Paint linePaint; public GameView(Context context, AttributeSet attrs) { super(context, attrs); linePaint new Paint(); linePaint.setColor(0xFF333333); linePaint.setStrokeWidth(3f); linePaint.setAntiAlias(true); } Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 取短边留出边距保证棋盘是正方形 float boardWidth Math.min(w, h) * 0.9f; cellSize boardWidth / (boardSize - 1); originX (w - boardWidth) / 2f; originY (h - boardWidth) / 2f; } Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 画横线 for (int i 0; i boardSize; i) { float y originY i * cellSize; canvas.drawLine(originX, y, originX (boardSize - 1) * cellSize, y, linePaint); } // 画竖线 for (int i 0; i boardSize; i) { float x originX i * cellSize; canvas.drawLine(x, originY, x, originY (boardSize - 1) * cellSize, linePaint); } } }这里有个参数必须讲清楚cellSize是在onSizeChanged里算的不是构造函数里。因为构造时 View 还没有实际尺寸你拿不到宽高。这是新手最常翻的车——在构造函数里算坐标结果全是 0棋盘画到屏幕外。boardSize - 1是因为 15 个交叉点之间有 14 段间隔这个 off-by-one 在判胜和落子坐标换算里还会再出现一次务必记牢。2.3 触摸坐标到棋盘下标的换算棋盘画出来了接下来要把手指点的地方翻译成第几行第几列。这一步的精度直接决定手感Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() ! MotionEvent.ACTION_DOWN) return true; float x event.getX(); float y event.getY(); // 四舍五入到最近的交叉点 int col Math.round((x - originX) / cellSize); int row Math.round((y - originY) / cellSize); // 边界检查防止点到棋盘外 if (row 0 || row boardSize || col 0 || col boardSize) return true; // 交给逻辑层处理落子 if (gameLogic.place(row, col)) { invalidate(); // 触发重绘 } return true; }Math.round而不是(int)强转是因为强转是向下取整会导致每个交叉点左上方半格都判给前一个点手感偏移。这个细节报告里可以不写但代码里必须有。invalidate()是告诉系统这个 View 脏了重新调 onDraw不调用的话你落了子屏幕也不变很多人卡在这里以为是逻辑没生效。3. 详细设计判胜、悔棋与 AI 的三块硬骨头3.1 胜负判定四个方向扫描与边界处理判胜是五子棋的灵魂也是最容易写错的地方。核心思路每次落子后只检查经过这个点的四条线横、竖、两条斜看是否有连续五个同色。不需要全盘扫描那样既慢又容易漏。// GameLogic.java private static final int[][] DIRS {{0,1},{1,0},{1,1},{1,-1}}; // 横竖正斜反斜 public boolean checkWin(int row, int col, int color) { for (int[] d : DIRS) { int count 1; // 当前这颗子算一个 // 正方向延伸 count countDirection(row, col, d[0], d[1], color); // 反方向延伸 count countDirection(row, col, -d[0], -d[1], color); if (count 5) return true; } return false; } private int countDirection(int row, int col, int dr, int dc, int color) { int n 0; int r row dr, c col dc; while (r 0 r boardSize c 0 c boardSize board[r][c] color) { n; r dr; c dc; } return n; }逻辑说明DIRS里四个方向覆盖了所有可能的五连。每个方向从落子点向两侧数count初始为 1 代表落下的这颗。countDirection里的边界判断r 0 r boardSize必须写在访问board[r][c]之前否则数组越界直接崩。参数dr/dc是方向增量正负号控制往哪边数这样一套代码复用两个方向比写八段循环干净得多。注意count 5而不是 5。虽然标准规则五连即胜但如果你以后想加长连禁手之类的变体用留了扩展余地。课设里用更稳不会因为某些边界情况漏判。3.2 悔棋功能用栈保存落子历史悔棋看着简单实现时很多人用把最后一颗子颜色改回去的土办法结果遇到连续悔棋就乱套。正确做法是用一个栈记录每一步private final Dequeint[] history new ArrayDeque(); // 存 {row, col, color} public boolean place(int row, int col) { if (board[row][col] ! EMPTY) return false; // 已有子拒绝 int color currentPlayer; board[row][col] color; history.push(new int[]{row, col, color}); if (checkWin(row, col, color)) { winner color; } else { currentPlayer (color BLACK) ? WHITE : BLACK; } return true; } public boolean undo() { if (history.isEmpty()) return false; int[] last history.pop(); board[last[0]][last[1]] EMPTY; winner 0; // 撤销后清空胜负状态 currentPlayer last[2]; // 轮回到被撤销的那一方 return true; }ArrayDeque比Stack类更推荐Stack是遗留类方法带同步开销。history.push存的是三元组悔棋时把棋盘对应位置清空、把当前玩家设回被撤销的那一方、清掉胜负标记。这里有个坑如果已经分出胜负悔棋后必须把winner清零否则界面还显示黑方胜但棋盘已经能继续下了状态不一致。3.3 简单 AI评分表驱动的落子选择课设里加个 AI 能显著提升报告档次但别上深度学习那是另一个课题。用评分表就够了对每个空位分别计算如果黑下这里得多少分如果白下这里得多少分取最大值。// 对某个空位评估在指定方向上形成的棋型分数 private int evaluatePoint(int row, int col, int color) { int total 0; for (int[] d : DIRS) { int count 1; int block 0; // 被对方堵住的端数 // 正方向 int r row d[0], c col d[1]; while (inBoard(r, c) board[r][c] color) { count; r d[0]; c d[1]; } if (!inBoard(r, c) || board[r][c] ! EMPTY) block; // 反方向同理 r row - d[0]; c col - d[1]; while (inBoard(r, c) board[r][c] color) { count; r - d[0]; c - d[1]; } if (!inBoard(r, c) || board[r][c] ! EMPTY) block; total scoreOf(count, block); } return total; } private int scoreOf(int count, int block) { if (count 5) return 100000; // 直接成五 if (count 4 block 0) return 10000; // 活四 if (count 4 block 1) return 1000; // 冲四 if (count 3 block 0) return 1000; // 活三 if (count 3 block 1) return 100; // 眠三 if (count 2 block 0) return 100; // 活二 return 10; }参数说明count是连子数block是两端被堵的数量。评分表是这套 AI 的全部智慧数值不用精确但要保证活四 冲四 活三这个量级关系否则 AI 会做出反直觉的走法。实际落子时遍历所有空位算evaluatePoint(row,col,AI) * 1.1 evaluatePoint(row,col,HUMAN)乘 1.1 是让 AI 略微偏向进攻避免一味防守导致棋局拖沓。这个系数可以调报告里可以写经过若干局对弈调整。4. 避坑与排查那些让课设当场翻车的细节4.1 棋盘画出来了但落子位置整体偏移现象点击交叉点棋子画在旁边半格。原因onDraw里画棋子的坐标换算和onTouchEvent里的换算用了不同公式或者originX/originY在onDraw里重新算了一遍但和onSizeChanged不一致。解决把坐标换算抽成一个方法gridToPixel(row, col)绘制和触摸都调它保证单一数据源。4.2 斜向五连判不出来现象横竖能判胜斜着连五个没反应。原因DIRS数组只写了{0,1}和{1,0}漏了斜向。或者斜向的边界判断写反了r和c的增减方向不匹配。解决把四个方向打印出来逐个测用固定棋局验证——手动摆一个反斜五连看checkWin返回什么。4.3 连续悔棋后玩家颜色错乱现象悔一步正常悔两步后该黑下却显示白下。原因undo里只清了棋盘没恢复currentPlayer或者恢复时用了错误的颜色。解决history里存了color悔棋时直接currentPlayer last[2]不要自己推断。4.4 旋转屏幕后棋局全没了现象手机一转棋盘清空。原因Activity 重建GameView重新构造board数组被重置。解决在GameLogic里实现状态保存或者简单点在AndroidManifest.xml里给 Activity 加android:configChangesorientation|screenSize让系统不重建。课设里后者够用但报告里要说明这是权衡正式产品应该用ViewModel保存状态。4.5 报告里的截图和实际运行不一致现象答辩时老师发现报告截图里的棋盘是 15 路实际跑出来是 19 路。原因改代码后没重新截图或者截图来自早期版本。解决定稿前重新跑一遍所有截图从同一版本 APK 里出截图里的步数、胜负状态要和文字描述对得上。这个坑不涉及技术但挂的人最多。5. 运行演示与心得体会怎么写才不像凑字数5.1 运行演示用一组固定棋谱证明功能完整运行演示章节最忌讳只放一张棋盘初始状态的图。我一般建议设计一组固定操作序列覆盖所有功能点然后逐步截图步骤操作预期结果验证功能1启动应用显示 15x15 空棋盘初始化2黑方落子天元交叉点出现黑子落子与绘制3白方斜向连下四子白子依次出现轮流落子4黑方完成斜向五连弹出黑方胜斜向判胜5点击悔棋最后一子消失轮到黑方悔棋与状态恢复6切换 AI 模式AI 在合理位置应招AI 落子这组序列的好处是每一步都对应一个可验证的功能老师照着点一遍就能确认。截图时把状态栏时间也截进去证明是真实运行而非拼图。5.2 心得体会写具体的技术决策不写空泛感想心得体会部分别写通过这次课设我学到了很多。写你实际做的取舍。比如为什么判胜用增量扫描而不是全盘扫描——因为全盘扫描在 15x15 上虽然也就 225 个点但每次落子都扫一遍代码里要处理更多边界增量扫描只查四条线逻辑更集中。再比如为什么 AI 用评分表而不是搜索树——搜索树要考虑深度和性能课设周期内调不出稳定效果评分表几十行就能跑且行为可解释答辩时能讲清楚每一步为什么这么下。还可以写一个真实的调试过程斜向判胜最初漏判排查发现是DIRS里反斜方向写成了{1,1}而不是{1,-1}导致两个斜向实际是同一个方向。这种细节写进报告比任何套话都有说服力也证明你真的跑过、错过、改过。5.3 一个能加分的收尾技巧把判胜逻辑抽出来单测如果时间允许在报告最后附一段纯 Java 的判胜测试不依赖 Android 环境public class LogicTest { public static void main(String[] args) { GameLogic g new GameLogic(15); // 摆一个反斜五连 int[][] moves {{0,4},{1,3},{2,2},{3,1},{4,0}}; for (int[] m : moves) { g.place(m[0], m[1]); } // 最后一子落下后应判黑胜 System.out.println(winner g.getWinner()); // 期望输出 1黑 } }这段代码能在任何装了 JDK 的机器上跑不依赖模拟器。它的价值在于把判胜对不对这个最核心的问题从 UI 里剥离出来用最快的方式验证。我带课设时凡是主动做了这一步的答辩时判胜逻辑几乎没被问倒过。这个习惯我保留到现在——核心算法先脱离框架跑通再往界面里接能省掉大量到底是逻辑错还是绘制错的排查时间。希望帮到你。本文还有配套的精品资源点击获取