一个简单的控制台小游戏,用链表实现贪吃蛇。代码不到两百行,压缩一下可以在100行以内。通过链表,指针,光标控制,代码还是非常优雅的
很多同学刚学C语言,不知道能干嘛。其实C语言什么都能干,只不过可能比较复杂,但深入学习C语言,对于底层数据结构和算法原理学习极有好处。
以后再学习其他语言,学习速度跟坐飞机一样。
比如这个贪吃蛇小程序,实现方式有很多,数组也行,这里用链表,主要是为了练习链表和指针的使用。
一、控制光标
想让控制台成为游戏画面,printf作为输出,得先实现指定位置printf
头文件#include <conio.h>
代码:
COORD coord;
HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
//获取控制台缓冲区句柄
coord.X = x;
coord.Y = y;
//设置光标位置
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("*");
使用以上代码就可以在(x,y)位置输出,控制台左上角坐标为(0,0).
二、定时
贪吃蛇每走一步,需要间隔一段时间,所以需要定时。
curTime = GetTickCount();
if (curTime - lastTime > 300)
{
if (move(dire, &head, food, &tail) == -1)
break;
lastTime = curTime;
}
move函数是贪吃蛇的移动函数,GetTickCount()获取当前时间,当前时间和上次时间只差大于300ms就移动一次。
你也可以用一个宏定义,或者从键盘输出控制间隔时间也就是控制速度(难度)。
三、获取方向
头文件:#include <conio.h>
代码:
if (_kbhit())
{
switch (_getch())
{
case 'w':
dire = 'w'; break;
case 's':
dire = 's'; break;
case 'a':
dire = 'a'; break;
case 'd':
dire = 'd'; break;
default:break;
}
}
_kbhit()函数检测键盘是否有按下,有则返回1,无则返回0;_getch()函数获取键盘值。
我们用w,s,a,d分别表示上,下,左,右。这样写是为了比较清晰,其实直接赋值就行。
四、构造贪吃蛇
typedef struct {
int x;
int y;
}POS;
typedef struct BODY {
POS pos;
struct BODY* next;
}BODY;
BODY* head = malloc(sizeof(BODY));
BODY* body = malloc(sizeof(BODY));
BODY* tail = malloc(sizeof(BODY));
BODY* food = malloc(sizeof(BODY));
int lastTime;
int curTime;
head->pos.x = w_max / 2;
head->pos.y = h_max / 2;
body->pos.x = w_max / 2 + 1;
body->pos.y = h_max / 2;
tail->pos.x = w_max / 2 + 2;
tail->pos.y = h_max / 2;
head->next = body;
body->next = tail;
tail->next = NULL;
每一节身体,都有两部分内容,pos坐标,next指针,指向下一节的位置。初始化贪吃蛇为三节,head头,body身体,tail尾巴。
五、移动
没有吃到食物的移动:
移动位置根据方向确定。
首先检测新的位置在不在身体上,也就是链表的遍历,在这个遍历过程中,我们把尾巴的前一个位置pretail记录下来。
移动过程其实就是把原来尾巴储存的内容,变成新的头的坐标,
尾巴的前一节身体变成尾巴,
新的位置变为头,原来的头变成头的下一节。
pretail->next = NULL;//尾巴前一节指针赋空,即变成新的尾巴
(*tail)->next = *head;//原来的尾巴空间用来储存新的头结点位置
(*tail)->pos.x = newhead.pos.x;
(*tail)->pos.y = newhead.pos.y;
*head = *tail;//新的头结点指针,改为原来尾巴的储存位置
*tail = pretail;//新的尾巴结点指针,改为原来尾巴前一节的储存位置
吃到食物的移动:
其实就是食物的位置变成了头结点。
六、完整代码
如果编译不通过,只要将 malloc(sizeof(BODY))改为(BODY*)malloc(sizeof(BODY))。
自己可以加一下界面,比如开始和gameover,以及速度选择,地图大小选择。
tips:如果要做失败从头再来的功能记得释放malloc出来的空间。
#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
#define H 52//长
#define W 102//宽
#define h_max (H-1)
#define w_max (W-1)
#define h_min 1
#define w_min 1
typedef struct {
int x;
int y;
}POS;
typedef struct BODY {
POS pos;
struct BODY* next;
}BODY;
void produceFood(BODY* food)
{
food->pos.x = rand() % (W - 2) + 1;
food->pos.y = rand() % (H - 2) + 1;
show_point(food->pos.x, food->pos.y);
}
int show_point(int x, int y)
{
COORD coord;
HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
coord.X = x;
coord.Y = y; //设置光标位置
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("*");
return 0;
}
int clear_point(int x, int y)
{
COORD coord;
HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台缓冲区句柄
coord.X = x;
coord.Y = y; //设置光标位置
SetConsoleCursorPosition(ConsoleHandle, coord);
printf(" ");
return 0;
}
int move(char dire, BODY** head, BODY* food, BODY** tail)
{
BODY newhead;
BODY* search;
BODY* pretail;
pretail = NULL;
int pretail_flag = 0;
newhead.pos.x = (*head)->pos.x;
newhead.pos.y = (*head)->pos.y;
switch (dire)
{
case 'w':newhead.pos.y--; break;
case 's':newhead.pos.y++; break;
case 'a':newhead.pos.x--; break;
case 'd':newhead.pos.x++; break;
default:break;
}
if (newhead.pos.y >= h_max || newhead.pos.y < h_min || newhead.pos.x < w_min || newhead.pos.x >= w_max)//越界则退出
return -1;
if (newhead.pos.x == food->pos.x && newhead.pos.y == food->pos.y) //吃到食物
{
BODY* newHead = malloc(sizeof(BODY));//新申请一个空间并加入贪吃蛇头部
newHead->pos.x = newhead.pos.x;
newHead->pos.y = newhead.pos.y;
newHead->next = *head;
*head = newHead;
produceFood(food);//吃了食物要产生一个新的食物
return 0;
}
else//没吃到食物则按方向前进(其实就是尾巴变成新的头部,尾巴的前一个变尾巴),同时需看新的位置是否在身体上
{
search = *head;
pretail_flag = 0;
while (1)
{
if(pretail_flag==0)
{
pretail = search;
if ((pretail->next)->next == NULL)
pretail_flag = 1;
}
if (search->pos.x == newhead.pos.x && search->pos.y == newhead.pos.y)
return -1;
if (search->next == NULL)
break;
search = search->next;
}
clear_point((*tail)->pos.x, (*tail)->pos.y);
show_point(newhead.pos.x, newhead.pos.y);
//没有返回-1说明未碰撞,尾巴变新的头,pretail变新尾巴
pretail->next = NULL;
(*tail)->next = *head;
(*tail)->pos.x = newhead.pos.x;
(*tail)->pos.y = newhead.pos.y;
*head = *tail;
*tail = pretail;
return 0;
}
}
void show()//画出背景
{
COORD coord;
//获取控制台缓冲区句柄
HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
for (int i = 0; i < W; i++)
{
coord.X = i;
coord.Y = 0;
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("#");
coord.Y = H - 1;
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("#");
}
for (int i = 0; i < H; i++)
{
coord.X = 0;
coord.Y = i;
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("#");
coord.X = W - 1;
SetConsoleCursorPosition(ConsoleHandle, coord);
printf("#");
}
}
int main()
{
srand((unsigned)time(NULL));
//初始化
BODY* head = malloc(sizeof(BODY));
BODY* body = malloc(sizeof(BODY));
BODY* tail = malloc(sizeof(BODY));
BODY* food = malloc(sizeof(BODY));
int lastTime;
int curTime;
head->pos.x = w_max / 2;
head->pos.y = h_max / 2;
body->pos.x = w_max / 2 + 1;
body->pos.y = h_max / 2;
tail->pos.x = w_max / 2 + 2;
tail->pos.y = h_max / 2;
head->next = body;
body->next = tail;
tail->next = NULL;
lastTime = GetTickCount();
//
char dire;//0,1,2,3,上下左右
dire = 'w';
show();
show_point(head->pos.x, head->pos.y);
show_point(body->pos.x, body->pos.y);
show_point(tail->pos.x, tail->pos.y);
produceFood(food);
while (1)
{
if (_kbhit())
{
switch (_getch())
{
case 'w':
dire = 'w'; break;
case 's':
dire = 's'; break;
case 'a':
dire = 'a'; break;
case 'd':
dire = 'd'; break;
default:break;
}
}
curTime = GetTickCount();
if (curTime - lastTime > 300)
{
if (move(dire, &head, food, &tail) == -1)
break;
lastTime = curTime;
}
}
return 0;
}
更多推荐
C语言新手入门贪吃蛇的链表实现-控制光标位置,流畅不闪屏
发布评论