前言
这是一个基于C语言链表开发的贪吃蛇游戏
其实贪吃蛇游戏要解决的主要问题就是
1、这个游戏的基本组成元素和数据结构
2、如何初始化贪吃蛇并正常行走
如何判断事件发生
游戏说明
按方向键上下左右,可以实现蛇移动方向的改变。
超出边界或者蛇头撞到蛇尾,游戏结束。
实现代码
#include<curses.h>
#include<stdlib.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
typedef struct Snake
{
int hang;//横坐标
int lie;//纵坐标
struct Snake* next;//下一个节点地址
}Snake;
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;
struct Snake food;
void initfood()
{
int x = rand() % 20;//横坐标
int y = rand() % 20;//纵坐标
food.hang = x;
food.lie = y;
}
int hasfood(int i,int j)
{
if(food.hang == i && food.lie == j)
{
return 1;
}
return 0;
}
int hasSnake(int i,int j)
{
struct Snake*p = head;
while(p != NULL)
{
if(p->hang == i && p->lie == j)
{
return 1;
}
p = p->next;
}
return 0;
}
void initNcurses()
{
initscr();
keypad(stdscr,1);
noecho();
}
void gamePic()
{
int hang;//行
int lie;//列
move(0,0);
//从第0行开始循环
for(hang = 0;hang<20;hang++)
{
if(hang ==0)
{
for(lie = 0;lie<20;lie++)
{
printw("--");
}
printw("\n");
}
//-----------------------------------------------------------------
if(hang>0 &&hang<=19)
{
for(lie = 0;lie<=20;lie++)
{
if(lie == 0||lie == 20)
{
printw("|");
}
//打印蛇的位置
else if(hasSnake(hang,lie))
{
printw("[]");
//打印食物的位置
}else if(hasfood(hang,lie))
{
printw("##");
}
else{
printw(" ");
}
}
printw("\n");
}
//------------------------------------------------------
if(hang ==19)
{
for(lie =0;lie<20;lie++)
{
printw("--");
}
printw("\n");
printw("My name is ChenYi 2023.3.2,food,hang =%d food.lie =%d ,key = %d\n ",food.hang,food.lie,key);
}
}
}
void addnode()
{
//创建新节点
Snake *new = (Snake*)malloc(sizeof(Snake));
new->next = NULL;
switch(dir)
{
case UP:
new->hang =tail->hang-1;
new->lie = tail->lie;
break;
case DOWN:
new->hang =tail->hang+1;
new->lie = tail->lie;
break;
case LEFT:
new->hang =tail->hang;
new->lie = tail->lie-1;
break;
case RIGHT:
new->hang =tail->hang;
new->lie = tail->lie+1;
break;
}
//尾巴指向新节点
tail->next = new;
tail = new;
}
//初始化头节点,并赋值
void initSnake()
{
struct Snake *p;
dir = RIGHT;
//删除节点
while(head!=NULL)
{
//临时变量保存头节点位置
p = head;
head = head->next;
//释放头节点
free(head);
}
//初始化食物函数
initfood();
//初始化头节点
head = (Snake *)malloc(sizeof(Snake));
//横坐标为2
head->hang = 2;
//纵坐标为1
head->lie = 1;
head->next = NULL;
//让头和尾都指向一个节点
tail = head;
//申请3个节点,让蛇初始长度为三
addnode();
addnode();
addnode();
}
void delnode()
{
Snake *new;
new = head;
head = head->next;
free(new);
}
int ifsnakedle()
{
struct Snake *p;
p = head;
//判断蛇有没有超出边界
if(tail->hang < 0|| tail->lie == 0 ||tail->hang == 20|| tail->lie == 20 )
{
return 1;
}
//判断蛇头有没有咬住尾巴
while(p->next != NULL)
{
if(p->hang == tail->hang && p->lie == tail->lie)
{
return 1;
}
p = p->next;
}
return 0;
}
void moveSnake()
{
//添加新节点
addnode();
//如果蛇头坐标和食物坐标一样,说明食物被吃掉了
if(hasfood(tail->hang,tail->lie))
{
//初始话食物,
initfood();
}else
{
//删除节点
delnode();
}
//判断有没有咬到自己或超出边界
if(ifsnakedle())
{
//咬到自己或超出边界了,初始化
initSnake();
}
}
void refreshjiemian()
{
while(1)
{
moveSnake();
gamePic();
refresh();
//控制蛇的移动速度,
usleep(100000);
}
}
void trun(int diretction)
{
if(abs(dir) != abs(diretction))
{
dir = diretction;
}
}
void changgeDir()
{
while(1)
{
key = getch();
switch(key)
{
case KEY_DOWN:
trun(DOWN);
break;
case KEY_UP:
trun(UP);
break;
case KEY_LEFT:
trun(LEFT);
break;
case KEY_RIGHT:
trun(RIGHT);
break;
}
}
}
int main()
{
pthread_t t1;
pthread_t t2;
initNcurses();
initSnake();//初始化创建节点
gamePic();//遍历
pthread_create(&t1,NULL,refreshjiemian,NULL);
pthread_create(&t2,NULL,changgeDir,NULL);
while(1);
getch();
endwin();
return 0;
}
代码讲解
1,定义一个结构体,结构体当中存储着该段蛇身的位置坐标,并且定义了两个结构体指针,指向蛇头和蛇尾。
初始化
1.初始化结构体,为蛇头(head)申请一个空间,并对其赋初始值。
2.(160-170)行,是用来释放上一轮创建的空间的,如果不释放上一轮创建的空间,那每一次从新开始都会去创建新的空间,造成空间浪费,所以这边加了释放空间的代码,实现思路如下,先创建一个节点p用来保存头节点(head)的地址,(注意,直接释放头节点会找不到下一个节点,所以需要一个临时变量来先保存),在释放头节点(head);
3.封装一个食物函数,使用rand取模求余,赋值给食物的横坐标和纵坐标,让食物随机出现在屏幕上。
4,如何添加节点和控制蛇头的方向?
1 ,我先申请一个了新的节点,通过控制节点里面的数据来实现控制蛇头方向,
这里我采用了swich函数,通过调用curses库来获取键盘上输入的信息。
如果现在的蛇头在{4,9}位置上,往上走的话,纵坐标不变,横坐标坚毅,就是在{3,9}处添加一个新节点。
遍历
1.第一部分(71-81)
横坐标从零开始循环,如果横坐标满足条件等于0,继续执行下一步,纵坐标从零开始循环,并且 打印虚线--(上框),下框同理。
第二部分(84-106)
1.先判断横坐标,有没有在规定范围内,如果在,纵坐标从零开始扫描,并且根 据 条件打印左右框。
2.打印蛇的位置,通过调用hasSnake函数,每次扫描的时候都会把,横纵坐标传进去与蛇的坐标相匹配,该函数返回一的时候打印蛇的位置。
3。打印食物的位置也是如此,每次遍历都会把横纵坐标传进去,与之匹配,返回一打印食物位置。
1.第三部分(109-119)
横坐标从零开始循环,如果横坐标满足条件等于19,继续执行下一步,纵坐标从零开始循环,并且 打印虚线--(上框),下框同理。
如何让蛇移动和刷新界面同时实现呢?
这里我使用pthread创建了两个线程一个运行蛇的移动,一个来运行界面的刷新。
蛇要怎么才能移动?
如果向右移动的话,就给蛇头添加一个节点,蛇尾删除一个节点,其他方向同理。
在第225行调用hasfood判断了一下,如果蛇头吃到食物(蛇头坐标和食物坐标一致),只添加节点不删除尾巴节点。
1,判断蛇有没有超出边界。和判断蛇有没有咬住自己的尾巴。
如何判断蛇有没有咬住自己的尾巴?这里我使用了while循环来遍历链表,如果当前这个node的下一个不等于空,就开始判断蛇头的坐标等不等于蛇尾的坐标,如果等于说明撞上了,直接返回一,初始化,游戏将重新开始。
记录第一次写博客,写的不好还望大家多指导。