C语言单链表贪吃蛇小游戏

时间:2022-03-12 07:40:00

C语言实现单链表控制台贪吃蛇小游戏,供大家参考。

编译环境:vs2019

需求:

统计游戏开始后的时间,控制贪吃蛇;吃到食物蛇身加长,得分加一;碰墙或蛇头碰到身体减一条生命;生命消耗完则结束游戏。

思路:

使用wasd键控制蛇的移动方向,蛇头碰到食物得分加一,并在地图上随机产生一个食物,累加得分,碰墙或碰自己减一条生命,并初始化整条蛇,生命值为0时结束游戏。

做法:

使用单链表控制贪吃蛇移动的核心思想就是:链表存储贪吃蛇所有坐标,每次循环贪吃蛇不断向一个方向插入一个新的结点作为新的蛇头,按下按键控制新蛇头产生的位置,然后从新蛇头处遍历链表输出蛇身到上一个蛇尾,清除上一个蛇尾的痕迹,并释放相关结点。

每次向链表插入新节点后,判断新节点的坐标是否和食物的坐标重合,如果重合本轮循环不释放清除蛇尾结点,反之释放清除上一个蛇尾的结点。

另外,在写蛇生命相关代码的时候,还需要注意一下哪些值应该初始化,哪些值不应该初始化。
只要明白了贪吃蛇运动的核心思想,整个程序其实就不难写出来。

难点:

wsad控制贪吃蛇上下左右移动,并清除蛇尾。

说明:

使用单链表实现贪吃蛇的核心思想是我一开始没有想到的,部分相关代码我学习并借鉴了一些网络上搜索到的代码,如果有违反了相关版权协议,请告知我修改相关代码。

注意:

由于编译器原因程序中_kbhit()和_getch()函数可能在其他编译器上编译会出现错误,解决办法是去掉函数前面的“_”。

运行效果:

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
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
 
void HideCursor();  //光标隐藏
void gotoxy(int x, int y); //光标定位
 
typedef struct snake
{
 int x;
 int y;
 struct snake* next;
}snake;
 
#define WIDTH 100  //控制台窗口宽度
#define HEIGHT 30  //控制台窗口高度
#define SNAKEN 4  //贪吃蛇初始长度
#define LIFE 3  //初始生命次数
#define SPEED 200  //游戏速度、循环休眠时间
#define U 1   //使用宏代替需要数字代替的蛇的行动方向
#define D 2   //宏名含义是各方向英文单词首字母
#define L 3   //蛇的状态,U:上 ;D:下;L:左 R:右
#define R 4  
 
 
void dtxxcsh()  //输出地图
{
 
 for (int i = 1; i < WIDTH-1; i++) //输出上下面墙
 {
 gotoxy(i, 26);
 printf("-");
 gotoxy(i, 0);
 printf("-");
 }
 for (int i = 0; i < HEIGHT-3; i++) //输出左右两面墙
 {
 gotoxy(0, i);
 printf("|");
 gotoxy(99, i);
 printf("|");
 }
 gotoxy(24, 28);
 printf("得分: 0  生命: %d  时间: 0  ",LIFE);
 //xy 30,28可用得分数值 14个空格
}
 
 
int foodx, foody;  //食物位置坐标
 
void sjcsswhs()  //随机产生一个食物
{
 srand(time(NULL));
 
 foodx = rand() % (WIDTH - 4) + 2; //使用宏运算随机数最大值需要加括号
 
 while (foodx % 2)   //如果食物的x坐标不是偶数,再获取一个x坐标
 {
 foodx = rand() % (WIDTH - 4) + 2; //宽度
 }
 
 foody = rand() % (HEIGHT - 7) + 3; //高度
 
 gotoxy(foodx, foody);
 printf("★");
}
 
snake* head; //蛇头指针
 
void cshs()   //初始化蛇的位置
{
 snake *tail;  //蛇尾指针
 int i;
 
 tail = (snake*)malloc(sizeof(snake));
 tail->next = NULL;
 tail->x = HEIGHT-6;
 tail->y = 8;
 
 //贪吃蛇初始长度5 SNAKEN
 for (i = 1; i <= SNAKEN; i++)  //在蛇尾处创建链表
 {
 head = (snake*)malloc(sizeof(snake));
 head->next = tail;
 
 head->x = 24 + i * 2;   //head->x这个数必须为偶数,和食物坐标偶数对应
 head->y = 8;
 tail = head;    //此时蛇尾指针指向蛇头
 }
 
 while (tail)
 {
 gotoxy(tail->x, tail->y);
 printf("■");
 tail = tail->next;
 }
}
 
int status = R;  //蛇前进状态
 
snake* p = NULL; //工作指针
snake* nexthead; //下一个蛇头
int score = 0;  //得分
 
void snakemove() //蛇前进,上U,下D,左L,右R
{
 nexthead = (snake*)malloc(sizeof(snake));
 
 if (status == U)
 {
 nexthead->y = head->y - 1; //确定新蛇头的下一个坐标 x,y
 nexthead->x = head->x;
 }
 if (status == D)  //下
 {
 nexthead->y = head->y + 1;
 nexthead->x = head->x;
 }
 if (status == L)  //左
 {
 nexthead->x = head->x - 2;
 nexthead->y = head->y;
 }
 if (status == R)  //右
 {
 nexthead->x = head->x + 2;
 nexthead->y = head->y;
 }
 nexthead->next = head;
 head = nexthead;
 p = head;
 
 if (p->x == foodx && p->y == foody) //判断蛇头的位置是否和食物的位置重合
 {
 while (p)   //输出尾结点
 {
  gotoxy(p->x, p->y);
  if (p == head)
  printf("●");
  else
  printf("■");
  p = p->next;  //因为每次运动都是新创建一个头结点再删除一个尾结点,
 }    //所以要增加一节身体,只需要这次循环不释放尾结点,并输出一次尾结点就好了
 
 //sjcsswhs();   //碰到食物则再产生一个食物
 
 score++;
 gotoxy(32, 28);
 printf("%d", score);
 }
 else
 {
 while (p->next->next)  //不输出尾结点
 {
  gotoxy(p->x, p->y);
  if (p == head)
  printf("●");
  else
  printf("■");
  p = p->next;
 }
 
 gotoxy(p->next->x, p->next->y);
 printf(" ");
 free(p->next);
 p->next = NULL;
 }
 
 p = head;
 while (p)   //如果食物的坐标刷新到了蛇身上则再产生一个食物 *
 {
 if (p->x == foodx && p->y == foody)
  sjcsswhs();
 p = p->next;
 }
}
 
void czfxhs()   //操作方向函数,接收从键盘输入的按键,控制贪吃蛇行进方向
{
 char ch = _getch();
 switch (ch)
 {
 case 'w':
  if(status != D)
  status = U; break;
 case 's':
  if (status != U)
  status = D; break;
 case 'a':
  if (status != R)
  status = L; break;
 case 'd':
  if (status != L)
  status = R; break;
 case ' ':
  _getch(); break; //空格暂停
 }
}
 
int yxjstjjsmz_1()   //生命掉落条件1咬自己
{
 snake* self = head->next; //self为蛇身上的一个结点
 while (self)
 {
 if (self->x == head->x && self->y == head->y) //head和self的成员作比较,蛇头一直存在,这里遍历的是蛇身
 {
  return 1;
 }
 self = self->next;
 }
 return 0;
}
 
int yxjstjjsmz_2()   //生命掉落条件2碰墙
{
 if (head->x <= 1 || head->x >= 98 || head->y <= 0 || head->y >= 26)
 return 1;
 
 return 0;
}
 
int i = LIFE - 1;   //变量存储生命次数
 
void qcsytmslbhs()   //清除并释放上一条蛇留下来的痕迹,更新生命信息
{
 p = head;
 int _x_ = p->x;   //用于保存死掉的蛇的蛇头处的位置,用于输出被蛇头顶掉的墙壁
 int _y_ = p->y;
 while (head)
 {
 gotoxy(head->x, head->y);
 printf(" ");
 head = head->next;
 free(p);
 p = head;
 }
 gotoxy(52, 28);   //更新生命信息
 printf("%d", i);
 
 if (_y_ == 0 || _y_ == HEIGHT - 4) //用于在蛇死掉后,蛇头的位置输出被清除蛇头顶替掉的墙壁
 {
 gotoxy(_x_, _y_);
 printf("--");
 }
 else if (_x_ == WIDTH - 2)
 {
 gotoxy(_x_+1, _y_);
 printf("|");
 }
 else if(_x_ == 0)
 {
 gotoxy(_x_, _y_);
 printf("|");
 }
}
 
void sbjsjmhs()   //失败结束界面
{
 p = head;   //释放内存
 while (head)
 {
 head = head->next;
 free(p);
 p = head;
 }
 
 system("cls");
 gotoxy(45, 12);
 printf("游戏结束!");
 gotoxy(44, 14);
 printf("最终得分:%d", score);
 gotoxy(0,28);
}
 
int updatetime()   //获取一次电脑现在的时间
{
 int now;
 SYSTEMTIME system_time; 
 GetLocalTime(&system_time);
 now = system_time.wMinute * 60 + system_time.wSecond;
 return now;
}
 
int time_1 = updatetime();   //保存游戏刚开始的时间
 
void gametime()    //写在每次循环之内
{
 int time_2 = updatetime() - time_1; //更新游戏开始后时间,用现在的时间减去刚开始的时间
 gotoxy(72, 28);
 printf("%d s", time_2);
}
 
int main()//主函数
{
 system("mode con cols=100 lines=30"); //设置控制台大小
 system("title 贪吃蛇游戏");  //设置标题
 HideCursor();    //隐藏光标
 
 sjcsswhs();    //初始化随机产生一个食物
 dtxxcsh();    //初始化地图、信息
 
 for (; i >= 0; i--)    //生命
 {
 cshs();     //初始化蛇的位置
 status = R;    //初始化运动方向
 
 while (1)
 {
  snakemove();   //蛇行动动画,方向被控制变量控制
 
  if (_kbhit())
  {
  czfxhs();   //接收键盘按键,控制控制变量
  }
  
  if (yxjstjjsmz_1() || yxjstjjsmz_2())//两个掉落生命的条件
  break;
 
  gametime();    //更新游戏时间
 
  Sleep(SPEED);
 }
 qcsytmslbhs();    //清除上一条蛇留下来的痕迹,更新生命信息
 }
 
 sbjsjmhs();     //失败结束界面
 
 return 0;
}
 
void HideCursor()
{
 CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
 SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
 
void gotoxy(int x, int y)
{
 COORD pos = { x,y };
 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

不足之处:

因为这是我第一次使用链表做一个完整的小程序,所以对链表的运用还很稚嫩,在代码规范和严谨性上面还有很多问题。

另外关于“食物如果刷新到蛇身上则再随机产生一个食物”(172行)相关代码,我不是很确定到底完不完善。

作为一名c语言新手,我对未知的知识始终抱有学习和谦卑的态度,如有贵人能够对我的程序提出建议,我将不胜感激。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_46239972/article/details/104132471