C语言-贪吃蛇

贪吃蛇游戏项目分析(第一版)

一、初始化游戏

初始化游戏全局参数

二、绘制游戏界面

使用坐标来绘制蛇的位置,并使用系统命令cls来刷新屏幕,每次移动蛇的位置都会根据头部的位置往后推来定位蛇的位置坐标

三、处理用户输入

使用键盘监听获取键盘输入值

四、使用线程

使用线程来监听键盘输入

五、主函数

项目代码

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <conio.h>
#include <windows.h>

// 边框长度、高度
#define WIDTH 35
#define HEIGTH 13

// 初始化游戏参数
int game_out = 0; // 游戏结束标志位
int snake_len = 4;
int snake_head_x = WIDTH / 2;
int snake_head_y = HEIGTH / 2;
int* snake_body_list[WIDTH * HEIGTH] = {0};
int bg_x = 0; //饼干x地址
int bg_y = 0; //饼干y地址
int up, down, left, right;
right = 1;
struct snake_zb {
int x;
int y;
};
// 初始化蛇坐标
void init_game_arg() {
struct snake_zb* head = malloc(sizeof(struct snake_zb));
head->x = snake_head_x;
head->y = snake_head_y;
snake_body_list[0] = head;
for (int i = 1;i<snake_len; i++) {
struct snake_zb* body = malloc(sizeof(struct snake_zb));
body->x = snake_head_x - i;
body->y = snake_head_y;
snake_body_list[i] = body;
}
}

// 更新饼干坐标
void update_bg_zb() {
bg_x = 1 + rand() % (WIDTH - 2);
bg_y = 1 + rand() % (HEIGTH - 2);
}
// 增加蛇长度
void snake_len_add() {
// 判断蛇是否长了
for (int i = 0; i < snake_len; i++) {
printf("%d\r\n", snake_body_list[i]);
if (snake_body_list[i] == 0) {
struct snake_zb* p = malloc(sizeof(struct snake_zb));
p->x = 0;
p->y = 0;
snake_body_list[i] = p;
}
}
}
// 更新蛇的坐标
void uprage_game_arg() {
if (right) {
for (int i = (snake_len-1); i > 0; i--) {
*snake_body_list[i] = *snake_body_list[i - 1];
*(snake_body_list[i] + 1) = *(snake_body_list[i - 1] + 1);
}
int move_x = (*snake_body_list[0]) + 1;
if (move_x > (WIDTH-2)) {
move_x = 0;
}
*snake_body_list[0] = move_x;
}
else if (left) {
for (int i = (snake_len - 1); i > 0; i--) {
*snake_body_list[i] = *snake_body_list[i - 1];
*(snake_body_list[i] + 1) = *(snake_body_list[i - 1] + 1);
}
int move_x = (*snake_body_list[0]) - 1;
if (move_x < 1) {
move_x = WIDTH-2;
}
*snake_body_list[0] = move_x;
}
else if (up) {
for (int i = (snake_len - 1); i > 0; i--) {
*snake_body_list[i] = *snake_body_list[i - 1];
*(snake_body_list[i] + 1) = *(snake_body_list[i-1] + 1);
}
int move_y = *(snake_body_list[0] + 1) - 1;
if (move_y < 1) {
move_y = HEIGTH - 2;
}
*(snake_body_list[0] + 1) = move_y;
}
else if (down) {
for (int i = (snake_len - 1); i > 0; i--) {
*snake_body_list[i] = *snake_body_list[i - 1];
*(snake_body_list[i] + 1) = *(snake_body_list[i - 1] + 1);
}
int move_y = *(snake_body_list[0] + 1) + 1;
if (move_y > (HEIGTH-2)) {
move_y = 0;
}
*(snake_body_list[0] + 1) = move_y;
}
}

// 游戏界面
void draw_border(int bg_x, int bg_y) {
// 绘制游戏边框
for (int i = 0; i < HEIGTH; i++) {
for (int j = 0; j < WIDTH; j++) {
// 打印上下边框
if ((i == 0) || (i == (HEIGTH - 1))) {
printf("#");
}
else {
//打印侧边框
if ((j == 0) || (j == (WIDTH - 1))) {
printf("#");
}
else {
// 画蛇
int is_snake = 0;
int head_x = *snake_body_list[0];
int head_y = *(snake_body_list[0] + 1);
for (int k = 0; k < snake_len; k++) {
int x = *snake_body_list[k];
int y = *(snake_body_list[k] + 1);
if (j == x && i == y) {
//printf("k=%d,x=%d,y=%d\r\n",k,x,y);
if (k == 0) {
printf("$");
is_snake = 1;
// 吃到饼干
if ((x== bg_x) && (y== bg_y)) {
snake_len++;
update_bg_zb();
snake_len_add();
break;
}
}
else if ((head_x == x) && (head_y == y)) {// 碰到自己身体则游戏结束
game_out = 1;
//exit(-1);
}
else {
printf("0");
is_snake = 1;
}
continue;
}
};
if ((j == bg_x) && (i == bg_y)) {
printf("G");
continue;
}
if (!is_snake) printf(" ");
}
};
// 每一行换行
if (j == (WIDTH - 1)) printf("\r\n");
}
}
// 显示游戏分数
printf("游戏得分:%d\r\n", (snake_len-4));
}

// 动态循环渲染游戏界面(刷帧)
void dynamic_update_game() {
int key;
update_bg_zb();
while (1)
{
if (game_out) {
break;
}
system("cls");
// 更新蛇坐标
uprage_game_arg();
// 重新画图
draw_border(bg_x, bg_y);
}
}

// 创建线程监听键盘
void __stdcall linsent_key() {
int key;
while (1) {
if (game_out) break;
key = _getch();
if (key == 0xE0) {
key = _getch();
if ((key == 72) && !down) { // 上
up = 1;
down = 0;
left = 0;
right = 0;
}
else if ((key == 80) && !up) { // 下
down = 1;
up = 0;
left = 0;
right = 0;
}
else if ((key == 75) && !right) { // 左
left = 1;
up = 0;
down = 0;
right = 0;
}
else if ((key == 77) && !left) { // 右
right = 1;
up = 0;
down = 0;
left = 0;
}
}
//printf("key=%d\r\n", key);
//printf("right=%d, up=%d, down=%d, left=%d\r\n", right, up, down, left);
//if (key == 27) break;
}
}

// 游戏结束
void end_game() {
// 释放内存空间
for (int i = 1; i < snake_len; i++) {
free(snake_body_list[i]);
snake_body_list[i] = NULL;
}
}

// 函数入口
int main() {
srand(time(0)); // 设置随机种子
init_game_arg();
draw_border(0,0);
// 创建鼠标监听线程
HANDLE hThread = CreateThread(
NULL, // 默认安全属性
0, // 默认栈大小
linsent_key, // 线程函数
NULL, // 参数
0, // 默认创建标志(立即运行)
NULL // 不需要线程ID
);
// 等待线程结束(可选)
//WaitForSingleObject(hThread, INFINITE);
// 主线程继续执行(异步)
// 动态渲染
dynamic_update_game();
// 关闭句柄(避免泄漏)
CloseHandle(hThread);
// 释放空间
end_game();
// 输出结束语
printf("游戏结束\r\n");
system("pause");
return 0;
}

项目运行截图

image-20250803134212027

贪吃蛇游戏项目分析(第二版)

基于第一版的项目使用的优化技术点

1、定点输出

2、颜色输出

3、隐藏光标

4、单链管理游戏节点

5、狸猫换太子技术

项目结构

image-20250808065932348

项目代码

Game.h
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#pragma once
#include <windows.h>
#include <memory.h>
#include <time.h>

// 墙宽高
#define WIDTH 30
#define HEIGTH 30
// 初始蛇的长度
#define SnakeLen 3
// 蛇单元
// 单链实现蛇体本身
typedef struct _SnakeCell {
int x;
int y;
struct _SnakeCell* pNext;
}SnakeCell,*PSnakeCell;
// 食物位置
typedef struct _Food {
int x;
int y;
}Food,*PFood;
// 整个蛇体
typedef struct _GameStatus {
PSnakeCell psnake;
Food food;
int live_status;
int dir;
int scores;
}GameStatus, * PGameStatus;
// 游戏状态
enum GameStatue {
NORMAL = 1,
SELF_DEATH,
WALL_DEATH
};
// 蛇体方向
enum SnakeDir {
UP = 1,
DOWN,
LEFT,
RIGHT
};
// 隐藏鼠标样式
void HideCursor();

// 设置鼠标位置
void SetPosOutput(int x, int y);

// 设置颜色输出
void SetConColor(int rgb);

// 画墙体
void InitWall();

// 初始化蛇位置
void InitSnake(PGameStatus pgames);

// 初始化食物位置
void InitFood(PGameStatus pgames);

// 输出游戏说明
void Readme();

// 输出游戏分数
void OutScores(PGameStatus pgames);

// 循环游戏体
void PlayGameExce(PGameStatus pgames);

// 蛇体移动
void SnakeMove(PGameStatus pgames);

// 判断蛇体是否死亡
void IsSnakeDeath(PGameStatus pgames);
Game.c
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include "Game.h"

void HideCursor()
{
CONSOLE_CURSOR_INFO conCursor = {0};
conCursor.dwSize = 1;
conCursor.bVisible = FALSE;
HANDLE hStdoutput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(hStdoutput, &conCursor);
}

void SetPosOutput(int x, int y)
{
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

void SetConColor(int rgb)
{

//0 = 黑色 8 = 灰色
//1 = 蓝色 9 = 淡蓝色
//2 = 绿色 A = 淡绿色
//3 = 浅绿色 B = 淡浅绿色
//4 = 红色 C = 淡红色
//5 = 紫色 D = 淡紫色
//6 = 黄色 E = 淡黄色
//7 = 白色 F = 亮白色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), rgb);
}

void InitWall()
{
for (int w = 0; w < WIDTH; w++) {
for (int h = 0;h < HEIGTH;h++) {
if (w == 0 || w == WIDTH-1) {
SetConColor(0xC);
SetPosOutput(w * 2,h);
printf("□");
}
else if (h == 0 || h == HEIGTH-1) {
SetConColor(0xC);
SetPosOutput(w * 2, h);
printf("□");
}
}
}
}

void InitSnake(PGameStatus pgames)
{
PSnakeCell cell = NULL;
for (int i = 0;i<SnakeLen;i++) {
cell = malloc(sizeof(SnakeCell));
cell->x = WIDTH / 2 + i;
cell->y = HEIGTH / 2;
cell->pNext = NULL;
if (pgames->psnake == NULL) {
pgames->psnake = cell;
}
else {
cell->pNext = pgames->psnake;
pgames->psnake = cell;
}
//输出蛇体
PSnakeCell tmp = pgames->psnake;
SetConColor(0xD);
while (tmp) {
SetPosOutput(tmp->x * 2, tmp->y);
printf("■");
tmp = tmp->pNext;
}
}
}

void InitFood(PGameStatus pgames) {
// 设置种子
srand(time(NULL));
// 食物的坐标不能跟蛇一样
int f_x = 0;
int f_y = 0;
while (1) {
int is_eque = 0;
PSnakeCell tmp = pgames->psnake;
while (tmp) {
f_x = rand() % (WIDTH - 2) + 1;
f_y = rand() % (HEIGTH - 2) + 1;
if ((tmp->x == f_x) && (tmp->y == f_y)) {
is_eque = 1;
}
tmp = tmp->pNext;
}
if (!is_eque) break;
}
pgames->food.x = f_x;
pgames->food.y = f_y;
// 输出食物位置
SetPosOutput(pgames->food.x*2, pgames->food.y);
SetConColor(0xE);
printf("●");
}

void Readme()
{
SetConColor(9);
SetPosOutput(WIDTH*2 + 3, HEIGTH/2 - 10);
printf("游戏说明");
SetPosOutput(WIDTH * 2 + 3, HEIGTH / 2 - 8);
printf("↑ ↓← →");
}

void OutScores(PGameStatus pgames)
{
SetConColor(9);
SetPosOutput(WIDTH * 2 + 3, HEIGTH / 2 - 5);
printf("游戏分数:%d", pgames->scores);
}

void PlayGameExce(PGameStatus pgames)
{
while (pgames->live_status== NORMAL) {
// 获取上下左右键
if (GetAsyncKeyState(VK_UP) && pgames->dir != DOWN) {
//printf("上键");
pgames->dir = UP;
}
else if (GetAsyncKeyState(VK_DOWN) && pgames->dir != UP) {
//printf("下键");
pgames->dir = DOWN;
}
else if (GetAsyncKeyState(VK_LEFT) && pgames->dir != RIGHT) {
//printf("左键");
pgames->dir = LEFT;
}
else if (GetAsyncKeyState(VK_RIGHT) && pgames->dir != LEFT) {
//printf("右键");
pgames->dir = RIGHT;
}
// 蛇体移动
SnakeMove(pgames);
// 判断蛇体是否死亡
IsSnakeDeath(pgames);
// 输出分数
OutScores(pgames);
// 刷新频率
Sleep(300);
};
// 如果碰到墙壁
if (pgames->live_status == WALL_DEATH) {
MessageBox(NULL, L"碰到墙壁,游戏结束!", NULL, NULL);
}
// 如果碰到自身
if (pgames->live_status == SELF_DEATH) {
MessageBox(NULL, L"碰到自身,游戏结束!", NULL, NULL);
}
}

void SnakeMove(PGameStatus pgames)
{
int add = 0;
// 创建一个新的蛇单元
PSnakeCell new_cell = malloc(sizeof(SnakeCell));
// 动态改变蛇的坐标
if (new_cell != NULL && pgames->dir == UP) {
new_cell->x = pgames->psnake->x;
new_cell->y = pgames->psnake->y - 1;
}
else if (pgames->dir == DOWN)
{
new_cell->x = pgames->psnake->x;
new_cell->y = pgames->psnake->y +1;
}
else if (pgames->dir == LEFT)
{
new_cell->x = pgames->psnake->x - 1;
new_cell->y = pgames->psnake->y;
}
else if (pgames->dir == RIGHT)
{
new_cell->x = pgames->psnake->x + 1;
new_cell->y = pgames->psnake->y;
}
// 狸猫换太子
new_cell->pNext = pgames->psnake;
pgames->psnake = new_cell;
PSnakeCell tmp = pgames->psnake;
// 如果吃到食物
if ((tmp->x == pgames->food.x) && (tmp->y == pgames->food.y)) {
add = 1;
pgames->scores = pgames->scores + 1;
InitFood(pgames);
};
// 输出蛇体
while (tmp) {
if (!add && tmp->pNext != NULL && tmp->pNext->pNext == NULL) {
SetPosOutput(tmp->pNext->x * 2, tmp->pNext->y);
printf(" ");
free(tmp->pNext);
tmp->pNext = NULL;
}
else{
SetConColor(0xD);
SetPosOutput(tmp->x * 2, tmp->y);
printf("■");
//printf("x=%d,y=%d\r\n", tmp->x, tmp->y);
tmp = tmp->pNext;
}
}
printf("\r\n");
}

void IsSnakeDeath(PGameStatus pgames)
{
// 判断是否碰到墙死亡
if (pgames->psnake->x >= WIDTH-1 || pgames->psnake->x <= 0) {
pgames->live_status = WALL_DEATH;
}
else if (pgames->psnake->y >= HEIGTH-1 || pgames->psnake->y <= 0) {
pgames->live_status = WALL_DEATH;
}
// 判断是否碰到自身死亡
PSnakeCell tmp = pgames->psnake->pNext;
while (tmp) {
if ((tmp->x == pgames->psnake->x) && (tmp->y == pgames->psnake->y)) {
pgames->live_status = SELF_DEATH;
break;
}
tmp = tmp->pNext;
}
}

Main.c
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
#include <stdio.h>
#include <stdlib.h>
#include "Game.h"


int main() {
GameStatus games = { 0 };
games.live_status = NORMAL;
games.dir = RIGHT;
games.scores = 0;
// 清场
system("cls");
// 隐藏光标
HideCursor();
// 绘制墙体
InitWall();
// 初始化蛇体
InitSnake(&games);
// 初始化食物
InitFood(&games);
// 游戏说明
Readme();
// 游戏循环玩
PlayGameExce(&games);
// 回归光标位置
//SetPosOutput(WIDTH*2,HEIGTH);
//system("pause");
return 0;
}

游戏运行截图

image-20250808065304907