【项目思维】通过编写一个贪吃蛇小程序,并移植到嵌入式设备上,解析编程思维的本质
这篇文章是一个 专门服务于新手向 的博客,通过编写一个贪吃蛇小程序,并将这个小游戏移植到嵌入式设备上(运行在 STM32 上、通过 OLED 显示、GPIO 按键控制的贪吃蛇小游戏)。
最重要的是,通过经历这个过程 讲述编程思维的本质。 在我们初学技术时,很多时候会被海洋一般的技术所围绕,这个时候需要通过一个小项目将此前学过的所有技术栈融合贯通的运用起来。而这个过程中,不仅是技术实现的展示,更是一次对 编程思维本质的深度剖析。
技术无远弗届,而这其中所蕴含的 编程思维 和 制作一个完整项目的思维方式,才是更为关键的能力。往后的博客更新中,我会通过大型项目陆续介绍 项目运作能力。现在这篇文章解释 “什么是编程的思维”。
一、第一阶段,项目的初始版本
首先,我们需要用原生代码的方式编写一个小项目的初始模型:
下面是对 贪吃蛇小游戏实现(C语言 + 控制台)的流程步骤分解:
- ✅ 控制台显示界面
- ✅ 键盘控制(上下左右)
- ✅ 食物生成、得分、撞墙/自咬结束
- ✅ 清晰的函数组织结构
- ✅ 每行代码都有注释
- ✅ 提供伪代码 + 流程图式结构说明
项目结构与设计
✅ 1. 游戏流程伪代码
开始游戏:初始化地图和蛇生成食物显示初始界面进入主循环:如果有键盘输入,更新方向移动蛇判断是否吃到食物判断是否撞墙或撞自己如果游戏结束,跳出循环重新绘制地图延时控制速度
✅ 2. 游戏核心模块划分(函数设计)(基于伪代码)
函数名 | 功能 |
---|---|
init_game() | 初始化地图、蛇、食物 |
draw_map() | 绘制游戏地图 |
draw_snake() | 绘制蛇身 |
generate_food() | 随机生成食物 |
move_snake() | 控制蛇运动 |
check_collision() | 检查是否撞墙或自撞 |
process_input() | 获取用户按键 |
game_loop() | 主循环 |
✅ 3. 每一步功能汇总
步骤 | 功能 |
---|---|
1️⃣ 初始化游戏 | 设置蛇初始位置、长度、方向 |
2️⃣ 生成食物 | 随机不重复位置 |
3️⃣ 用户输入 | 通过 w a s d 改变方向 |
4️⃣ 移动蛇身 | 改变头部,移动身体,判断是否吃食物 |
5️⃣ 碰撞检测 | 判断撞墙或自撞 |
6️⃣ 绘制界面 | 显示蛇、食物、边框、分数 |
7️⃣ 游戏结束 | 打印 Game Over 和分数 |
完整 C 语言代码(Windows 控制台)[第一版]
📌 当前编译环境:Windows + gcc(支持 <conio.h>
)
由于内容较为简答,目前不需要模块化。
#include <stdio.h>
#include <conio.h> // 用于 _kbhit() 和 _getch()
#include <windows.h> // 用于 Sleep() 和清屏
#include <stdlib.h> // 用于 rand()// 地图大小
#define WIDTH 40
#define HEIGHT 20// 方向定义
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGHT 4// 蛇身最大长度
#define MAX_SNAKE_LENGTH 100// 蛇结构
typedef struct {int x;int y;
} Point;// 游戏数据
Point snake[MAX_SNAKE_LENGTH]; // 蛇身数组
int snake_length = 3; // 初始长度为3
int direction = RIGHT; // 初始方向向右
Point food; // 食物位置
int score = 0; // 游戏得分
int game_over = 0; // 游戏结束标志// 函数声明
void init_game();
void draw_map();
void draw_snake();
void generate_food();
void move_snake();
void check_collision();
void process_input();
void goto_xy(int x, int y);
int is_snake_body(int x, int y);
void game_loop();// 主函数
int main() {init_game(); // 初始化游戏game_loop(); // 开始游戏循环return 0;
}// 初始化游戏
void init_game() {// 初始蛇身snake[0].x = 10;snake[0].y = 5;snake[1].x = 9;snake[1].y = 5;snake[2].x = 8;snake[2].y = 5;generate_food(); // 生成第一个食物draw_map(); // 绘制初始地图
}// 绘制地图边框
void draw_map() {system("cls"); // 清屏for (int y = 0; y <= HEIGHT; y++) {for (int x = 0; x <= WIDTH; x++) {if (x == 0 || x == WIDTH || y == 0 || y == HEIGHT)printf("#"); // 边界else if (x == food.x && y == food.y)printf("@"); // 食物else if (is_snake_body(x, y))printf("O"); // 蛇身elseprintf(" "); // 空白}printf("\n");}printf("Score: %d\n", score); // 显示分数
}// 判断坐标是否在蛇身上
int is_snake_body(int x, int y) {for (int i = 0; i < snake_length; i++) {if (snake[i].x == x && snake[i].y == y)return 1;}return 0;
}// 随机生成食物,不能生成在蛇身上
void generate_food() {do {food.x = rand() % (WIDTH - 2) + 1;food.y = rand() % (HEIGHT - 2) + 1;} while (is_snake_body(food.x, food.y));
}// 移动蛇身
void move_snake() {// 保存头部位置Point new_head = snake[0];// 根据方向更新头部位置switch (direction) {case UP: new_head.y--; break;case DOWN: new_head.y++; break;case LEFT: new_head.x--; break;case RIGHT: new_head.x++; break;}// 吃到食物if (new_head.x == food.x && new_head.y == food.y) {snake_length++; // 蛇身变长score += 10; // 得分增加generate_food(); // 生成新食物}// 蛇身移动:从尾到头依次移动位置for (int i = snake_length - 1; i > 0; i--) {snake[i] = snake[i - 1];}// 更新头部snake[0] = new_head;// 检查是否撞墙或自咬check_collision();
}// 检查碰撞
void check_collision() {// 撞墙if (snake[0].x <= 0 || snake[0].x >= WIDTH || snake[0].y <= 0 || snake[0].y >= HEIGHT) {game_over = 1;}// 撞自己for (int i =<