返回首页

贪吃蛇实战解析

贪吃蛇实战解析

本章完整剖析 snake.cob 的实现,将前面所学知识融会贯通。

目录

  1. 游戏概述
  2. 整体架构
  3. 数据结构设计
  4. 核心算法
  5. 显示与清屏
  6. 完整流程图
  7. 编译与运行
  8. 扩展练习

1. 游戏概述

snake.cob 是一个用 GnuCOBOL 实现的贪吃蛇游戏,特点:

  • 自动寻路:蛇自动在安全方向中贪心逼近食物,无需玩家操作
  • 随机食物:食物随机刷新,不会出现在蛇身上
  • 动态速度:分数越高移动越快
  • 多种结束条件:撞墙 / 撞自己 / 占满全场 / 步数上限

游戏规则:

元素 符号 说明
蛇身 o 蛇的身体
食物 * 吃到得分 +1,长度 +1
墙壁 # 边界,撞到即结束
空地 空格 可通行区域

2. 整体架构

程序采用模块化段落设计,主流程清晰:

1
2
3
4
5
6
7
8
9
10
11
12
PROCEDURE DIVISION.
MAIN-PROCEDURE.
PERFORM INIT-GAME
PERFORM GAME-LOOP UNTIL WS-RUNNING = 0
PERFORM SHOW-GAME
DISPLAY " "
DISPLAY "=========================================="
MOVE WS-SCORE TO WS-SCORE-OUT
DISPLAY " 游戏结束! 最终得分: " WS-SCORE-OUT
DISPLAY " 原因: " WS-END-REASON
DISPLAY "=========================================="
STOP RUN.

段落清单:

段落 职责
MAIN-PROCEDURE 主入口,控制整体流程
INIT-GAME 初始化蛇、方向、分数、随机种子
GAME-LOOP 主循环:构建网格→显示→移动→延时
BUILD-GRID 根据蛇和食物位置构建网格
SHOW-GAME 渲染游戏画面
MOVE-SNAKE 移动蛇、碰撞检测、吃食物
CHOOSE-DIR 选择最优方向(贪心算法)
TEST-DIRECTION 测试某方向是否安全
PLACE-FOOD 随机放置食物
CHECK-ON-SNAKE 检查食物是否在蛇身上
DELAY 延时控制速度
CLEAR-SCREEN ANSI 清屏

3. 数据结构设计

3.1 网格(二维表)

1
2
3
4
*> ---- 网格: 0=空 1=蛇身 2=食物 ----
01 WS-GRID.
05 WS-ROW OCCURS 12 TIMES.
10 WS-CELL OCCURS 20 TIMES PIC 9.

12 行 × 20 列的二维表,每个格子存储状态码。

3.2 蛇身(坐标数组)

1
2
3
4
5
6
*> ---- 蛇的坐标数组 (最多 240 节, 索引1=头) ----
01 WS-SNAKE.
05 WS-SEG OCCURS 240 TIMES.
10 WS-SX PIC 9(2).
10 WS-SY PIC 9(2).
01 WS-SNAKE-LEN PIC 9(3) VALUE 3.
  • 最多 240 节(= 12 × 20,占满全场)
  • 索引 1 是蛇头,索引 WS-SNAKE-LEN 是蛇尾
  • WS-SX/WS-SY 分别存储 x/y 坐标

3.3 状态变量

1
2
3
4
5
6
7
01 WS-DIR              PIC 9     VALUE 4.   *> 方向 1上2下3左4右
01 WS-FOOD-X PIC 9(2) VALUE 10. *> 食物 x
01 WS-FOOD-Y PIC 9(2) VALUE 6. *> 食物 y
01 WS-RUNNING PIC 9 VALUE 1. *> 运行标志
01 WS-SCORE PIC 9(4) VALUE 0. *> 得分
01 WS-STEPS PIC 9(4) VALUE 0. *> 步数
01 WS-MAX-STEPS PIC 9(4) VALUE 500. *> 步数上限

3.4 显示字符

1
2
3
4
01 WS-BODY-CHAR        PIC X VALUE "o".
01 WS-FOOD-CHAR PIC X VALUE "*".
01 WS-WALL-CHAR PIC X VALUE "#".
01 WS-CLEAR-SEQ PIC X(7) VALUE X"1B5B324A1B5B48".

4. 核心算法

4.1 初始化(INIT-GAME)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 INIT-GAME.
MOVE 3 TO WS-SNAKE-LEN
MOVE 4 TO WS-DIR
MOVE 0 TO WS-SCORE
MOVE 0 TO WS-STEPS
MOVE 1 TO WS-RUNNING
MOVE SPACES TO WS-END-REASON
MOVE 5 TO WS-SX(1)
MOVE 6 TO WS-SY(1)
MOVE 4 TO WS-SX(2)
MOVE 6 TO WS-SY(2)
MOVE 3 TO WS-SX(3)
MOVE 6 TO WS-SY(3)
*> 用当前时间 (HHMMSShh) 初始化随机数序列
MOVE FUNCTION CURRENT-DATE TO WS-TIMESTAMP
COMPUTE WS-SEED = FUNCTION NUMVAL(WS-TIMESTAMP(9:8))
COMPUTE WS-TMP = FUNCTION RANDOM(WS-SEED) * 1000000000
PERFORM PLACE-FOOD
PERFORM CLEAR-SCREEN.

要点:

  • 蛇初始为 3 节,水平排列在 (3,6)(4,6)(5,6)
  • 用 FUNCTION CURRENT-DATE 取当前时间,截取第 9-16 位(HHMMSSss)作为随机种子
  • FUNCTION RANDOM(种子) 初始化随机数序列

4.2 方向选择(贪心算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CHOOSE-DIR.
MOVE 99 TO WS-BEST-DIST
MOVE 0 TO WS-BEST-DIR
PERFORM VARYING WS-TRY-DIR FROM 1 BY 1
UNTIL WS-TRY-DIR > 4
PERFORM TEST-DIRECTION
IF WS-SAFE = 1
COMPUTE WS-DIST =
FUNCTION ABS(WS-TRY-X - WS-FOOD-X) +
FUNCTION ABS(WS-TRY-Y - WS-FOOD-Y)
IF WS-DIST < WS-BEST-DIST OR
(WS-DIST = WS-BEST-DIST AND WS-TRY-DIR = WS-DIR)
MOVE WS-DIST TO WS-BEST-DIST
MOVE WS-TRY-DIR TO WS-BEST-DIR
END-IF
END-IF
END-PERFORM
IF WS-BEST-DIR NOT = 0
MOVE WS-BEST-DIR TO WS-DIR
END-IF.

算法:

  1. 遍历 4 个方向
  2. 对每个方向调用 TEST-DIRECTION 判断是否安全
  3. 安全则计算到食物的曼哈顿距离 |dx| + |dy|
  4. 选择距离最小的方向(平局时保持原方向,避免抖动)

4.3 方向安全性检测(TEST-DIRECTION)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
TEST-DIRECTION.
MOVE 0 TO WS-SAFE
EVALUATE WS-TRY-DIR
WHEN 1
COMPUTE WS-TRY-Y = WS-SY(1) - 1
MOVE WS-SX(1) TO WS-TRY-X
WHEN 2
COMPUTE WS-TRY-Y = WS-SY(1) + 1
MOVE WS-SX(1) TO WS-TRY-X
WHEN 3
COMPUTE WS-TRY-X = WS-SX(1) - 1
MOVE WS-SY(1) TO WS-TRY-Y
WHEN 4
COMPUTE WS-TRY-X = WS-SX(1) + 1
MOVE WS-SY(1) TO WS-TRY-Y
END-EVALUATE
IF WS-TRY-X >= 1 AND WS-TRY-X <= 20
AND WS-TRY-Y >= 1 AND WS-TRY-Y <= 12
MOVE 0 TO WS-HIT
PERFORM VARYING WS-K FROM 1 BY 1
UNTIL WS-K > WS-SNAKE-LEN - 1 OR WS-HIT = 1
IF WS-SX(WS-K) = WS-TRY-X
AND WS-SY(WS-K) = WS-TRY-Y
MOVE 1 TO WS-HIT
END-IF
END-PERFORM
IF WS-HIT = 0
MOVE 1 TO WS-SAFE
END-IF
END-IF.

检测两步:

  1. 边界检测:新坐标是否在 120(x)和 112(y)范围内
  2. 自撞检测:新坐标是否与蛇身(除尾节)重合

4.4 移动蛇(MOVE-SNAKE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 MOVE-SNAKE.
PERFORM CHOOSE-DIR
EVALUATE WS-DIR
WHEN 1
COMPUTE WS-NEW-Y = WS-SY(1) - 1
MOVE WS-SX(1) TO WS-NEW-X
...
END-EVALUATE
*> 撞墙检测
IF WS-NEW-X < 1 OR WS-NEW-X > 20
MOVE "撞到墙壁" TO WS-END-REASON
MOVE 0 TO WS-RUNNING
EXIT PARAGRAPH
END-IF
...
*> 吃到食物: 加分变长
IF WS-NEW-X = WS-FOOD-X AND WS-NEW-Y = WS-FOOD-Y
ADD 1 TO WS-SCORE
ADD 1 TO WS-SNAKE-LEN
IF WS-SNAKE-LEN >= 240
MOVE "蛇占满全场, 获胜!" TO WS-END-REASON
MOVE 0 TO WS-RUNNING
ELSE
PERFORM PLACE-FOOD
END-IF
END-IF
*> 身体依次后移, 新坐标作为蛇头
MOVE 1 TO WS-I
PERFORM UNTIL WS-I >= WS-SNAKE-LEN
COMPUTE WS-K = WS-SNAKE-LEN - WS-I + 1
COMPUTE WS-J = WS-K - 1
MOVE WS-SX(WS-J) TO WS-SX(WS-K)
MOVE WS-SY(WS-J) TO WS-SY(WS-K)
ADD 1 TO WS-I
END-PERFORM
MOVE WS-NEW-X TO WS-SX(1)
MOVE WS-NEW-Y TO WS-SY(1).

流程:

  1. 选方向 → 计算新蛇头坐标
  2. 撞墙检测 → 结束
  3. 自撞检测 → 结束
  4. 吃食物检测 → 加分、变长、刷新食物
  5. 身体后移(从尾到头)→ 新坐标写入蛇头

4.5 随机放置食物(PLACE-FOOD)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 PLACE-FOOD.
MOVE 1 TO WS-ON-SNAKE
MOVE 0 TO WS-ATTEMPT
PERFORM UNTIL WS-ON-SNAKE = 0 OR WS-ATTEMPT >= 500
ADD 1 TO WS-ATTEMPT
COMPUTE WS-FOOD-X =
FUNCTION INTEGER(FUNCTION RANDOM * 20) + 1
COMPUTE WS-FOOD-Y =
FUNCTION INTEGER(FUNCTION RANDOM * 12) + 1
PERFORM CHECK-ON-SNAKE
END-PERFORM
*> 随机失败则按行扫描空位
IF WS-ON-SNAKE = 1
PERFORM VARYING WS-K FROM 1 BY 1
UNTIL WS-K > 240 OR WS-ON-SNAKE = 0
COMPUTE WS-FOOD-Y = (WS-K - 1) / 20 + 1
COMPUTE WS-FOOD-X = FUNCTION MOD(WS-K - 1, 20) + 1
PERFORM CHECK-ON-SNAKE
END-PERFORM
END-IF.

策略:

  1. 随机生成坐标,最多尝试 500 次
  2. 若随机失败(蛇几乎占满),则按行扫描找空位
  3. FUNCTION INTEGER(FUNCTION RANDOM * 20) + 1 生成 1~20 的随机整数

5. 显示与清屏

5.1 ANSI 清屏序列

1
01 WS-CLEAR-SEQ        PIC X(7)  VALUE X"1B5B324A1B5B48".

X"1B5B324A1B5B48" 是十六进制字节序列:

字节 含义
1B ESC(转义符)
5B [
32 4A 2J(清屏)
1B 5B 48 ESC [ H(光标归位)

等价于终端命令 \033[2J\033[H。

5.2 清屏段落

1
2
CLEAR-SCREEN.
DISPLAY WS-CLEAR-SEQ WITH NO ADVANCING.

WITH NO ADVANCING 抑制换行,直接输出转义序列。

5.3 渲染画面(SHOW-GAME)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SHOW-GAME.
PERFORM CLEAR-SCREEN
MOVE WS-SCORE TO WS-SCORE-OUT
MOVE WS-SNAKE-LEN TO WS-LEN-OUT
DISPLAY "=========================================="
DISPLAY " COBOL 贪吃蛇 得分: " WS-SCORE-OUT
" 长度: " WS-LEN-OUT
DISPLAY "=========================================="
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 12
MOVE SPACES TO WS-LINE
MOVE WS-WALL-CHAR TO WS-LINE(1:1)
PERFORM VARYING WS-J FROM 1 BY 1 UNTIL WS-J > 20
EVALUATE WS-CELL(WS-I, WS-J)
WHEN 0 MOVE " " TO WS-DISP-CHAR
WHEN 1 MOVE WS-BODY-CHAR TO WS-DISP-CHAR
WHEN 2 MOVE WS-FOOD-CHAR TO WS-DISP-CHAR
WHEN OTHER MOVE "?" TO WS-DISP-CHAR
END-EVALUATE
MOVE WS-DISP-CHAR TO WS-LINE(WS-J + 1:1)
END-PERFORM
MOVE WS-WALL-CHAR TO WS-LINE(22:1)
DISPLAY WS-LINE
END-PERFORM
DISPLAY "==========================================".

要点:

  • 用 WS-LINE(22 字符)逐行拼接
  • WS-LINE(1:1) 是左墙,WS-LINE(22:1) 是右墙
  • WS-LINE(WS-J + 1:1) 用引用修改逐格填充
  • WS-SCORE-OUT 是编辑型 PIC ZZZ9,前导零显示为空格

6. 完整流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────┐
│ INIT-GAME │ 初始化蛇、方向、随机种子
└──────┬──────┘
│
▼
┌─────────────────────────────┐
│ GAME-LOOP (循环) │
│ ┌───────────────────────┐ │
│ │ BUILD-GRID │ │ 构建网格
│ │ SHOW-GAME │ │ 渲染画面
│ │ MOVE-SNAKE │ │ 移动蛇
│ │ ├─ CHOOSE-DIR │ │ 选方向
│ │ │ └─ TEST-DIR │ │ 测安全
│ │ ├─ 碰撞检测 │ │
│ │ └─ 吃食物/PLACE-FOOD│ │ 刷新食物
│ │ DELAY │ │ 延时
│ └───────────────────────┘ │
│ 直到 WS-RUNNING = 0 │
└──────────────┬──────────────┘
│
▼
┌─────────────┐
│ 显示结束信息 │
│ STOP RUN │
└─────────────┘

7. 编译与运行

使用项目脚本

1
2
3
4
5
# 方式一:使用专用脚本
./run_snake.sh

# 方式二:使用通用脚本
./cobol.sh run snake

手动编译

1
2
cobc -x -o build/snake snake.cob
./build/snake

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
==========================================
COBOL 贪吃蛇 得分: 0 长度: 3
==========================================
# #
# #
# #
# ooo #
# #
# #
# #
# * #
# #
# #
# #
# #
==========================================

8. 扩展练习

学完本教程后,可尝试以下扩展:

  1. 手动控制:用 ACCEPT 读取键盘输入,让玩家控制方向
  2. 难度分级:增加难度选择,调整初始速度
  3. 障碍物:在网格中随机生成障碍物
  4. 最高分记录:用文件处理保存历史最高分
  5. 彩色显示:用 ANSI 颜色序列让蛇身、食物显示不同颜色
  6. 穿墙模式:撞墙时从对面出现而非结束
  7. 更智能的寻路:用 BFS/A* 算法替代贪心,避免自困

提示: 彩色显示可参考清屏序列的写法,例如:

1
2
3
01 WS-RED    PIC X(5) VALUE X"1B5B33316D".  *> \033[31m
01 WS-GREEN PIC X(5) VALUE X"1B5B33326D". *> \033[32m
01 WS-RESET PIC X(4) VALUE X"1B5B306D". *> \033[0m

上一章: 子程序与调用
下一章: 抽出・集计・突合