简介:这份资源是一份Android五子棋小游戏的课程设计报告,面向移动应用开发课程的学生、毕业设计选题者以及需要Android项目实战参考的开发者。报告围绕一款支持人机对战与人人对战的五子棋应用展开,涵盖项目背景、开发技术与环境、MVC系统架构、SQLite数据库、详细设计、运行演示及心得体会等完整章节,可帮助读者理解从需求分析到功能落地的全过程。压缩包内为1个doc文档,约8.29MB,图文并茂、格式规范,正文约9362字,适合直接作为课程设计或毕业设计报告的写作模板与结构参考。目前已有320人学习下载。读者可从中获取AI落子评分决策、胜负判定、悔棋与新局、背景音乐开关、游戏介绍与规则展示等模块的设计思路,并借鉴Java、Android Studio与MVC分层在小型安卓项目中的组织方式,快速搭建自己的开发框架。
1. 一份能跑起来的五子棋课设,到底该长什么样
每年到了学期中后段,总有人来问同一件事:Android 五子棋小游戏的课程设计报告怎么写才能既过查重、又能真跑起来。我见过太多翻车现场——报告里贴的代码和 APK 里的行为对不上,棋盘画得歪歪扭扭,胜负判定在斜向五连时漏判,答辩时老师随手点两下就露馅。这份东西的核心其实不是"报告",而是一个能自洽的工程:项目背景讲清为什么做、开发环境写清用什么做、详细设计说清怎么做的、运行演示证明真能做出来、心得体会交代踩过哪些坑。它适合两类人:一类是刚学完 Android 基础、需要交一份完整课设的学生;另一类是带课设、想快速判断一份报告含金量的指导者。下面我按自己带过几届课设的经验,把这条链路拆成能照着复现的步骤,重点放在那些报告里不会写、但一跑就暴露的细节上。
2. 开发环境与工程骨架:别在第一步就把自己埋了
2.1 环境选型:为什么我坚持用原生 View 而不是游戏引擎
课设场景下最常见的错误是"杀鸡用牛刀"。有人一上来就上游戏引擎,结果环境配置占了报告一半篇幅,核心算法反而没时间写。五子棋是回合制、无物理、无动画刚需的场景,用 Android 原生View+onDraw完全够用,而且代码量可控、答辩时能逐行讲清楚。
我一般推荐的环境组合是:Android Studio 作为 IDE,Gradle 做构建,最低 SDK 定在 API 24(Android 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:id="@+id/gameView" android:layout_width="match_parent" android:layout_height="match_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 Deque<int[]> 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:configChanges="orientation|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 里剥离出来,用最快的方式验证。我带课设时,凡是主动做了这一步的,答辩时判胜逻辑几乎没被问倒过。这个习惯我保留到现在——核心算法先脱离框架跑通,再往界面里接,能省掉大量"到底是逻辑错还是绘制错"的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取