利用链表和结构体来编写贪吃蛇小游戏,这边还会用点线程,分别有以下模块:
1、地图规划
2、蛇身体的初始化
3、蛇的方向移动
4、食物的初始化和随机出现
5、蛇撞墙和咬自己的尾巴
我们在设计地图的时候,需要用到ncurses,头文件 #include
ncurses(new curses)是一个程序库,它提供了API,可以允许程序员编写独立于终端的基于文本的用户界面。它是一个虚拟终端中的“类GUI”应用软件工具箱。它还优化了屏幕刷新方法,以减少使用远程shell时遇到的延迟。我们需要在虚拟机中装以下ncurses库
sudo apt-get install libncurses5-dev
sudo apt-get install ncurses-doc//这是ncurses的文件内容 可以查看ncurse的函数说明
man endwin//查看endwin函数的说明
使用ncurses的主要语句:
initscr(); //使用ncurses首先要初始化一下
printw("hello");//输出语句
getch();//等待用户输入
endwin();//结束并退出ncurses
move(y,x); // 将光标移动至(x,y)处。
refresh() ; //经过refresh()函数刷新之后才能够显示到屏幕上。
设计一个20x20的地图,我们可以使用两个for循环输出我们的行和列
void gamepic(){ //地图规划
int col;//lie
int row;//行
for (row = 0; row < 20; row++) {
if (row == 0) {
for (col = 0; col < 20; col++) {
printw("--");
}
}
printw("\n");
for (col = 0; col <= 20; col++) {
if (col == 0 || col == 20) {
printw("|");
} else{
printw(" ");
}
if (row == 19) {
printw("\n");
for (col = 0; col < 20; col++) {
printw("--");
}
}
}
}
这边对蛇身体的构造,我们用链表和结构体来完成身体的构造,结构体内包含行和列、下一个节点指针。通过坐标的形式显示我们蛇的身子
struct Snake {
int row;
int col;
struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL; //这时我们将链表的头结点和尾指针设定为全局变量
void initSnake() { //蛇身体的初始化
struct Snake *p;
while (head != NULL) {
p = head;
head = head->next;
free(p);
}
head = (struct Snake *)malloc(sizeof(struct Snake));
head->row = 2;
head->col = 2;
head->next = NULL;
tail = head;
addNode(); //添加蛇的身子
addNode();
}
void addNode(){
strcut Snake *new=(struct Snake *)malloc(sizeof(struct Snake));
new->next=NULL;
tail->next=new; //添加节点
tail=new;
}
链表创建完毕,现在来显示蛇的身体,这时我们可以通过一个指针对链表进行遍历,如果此时指针所指的行和列符合地图上的行和列,显示蛇的身子。
int Snake_body(int i, int j) { //对蛇的身体链表进行遍历 跟地图的行和列比较,相同返回1,不同返回0
struct Snake *p;
p = head;
while (p != NULL) {
if (p->row == i && p->col == j) {
return 1;
}
p = p->next;
}
return 0;
}
//对地图规划的函数进行修改
for (col = 0; col <= 20; col++) {
if (col == 0 || col == 20) {
printw("|");
} else if (Snake_body(row, col)) { //显示蛇的身体
printw("[]");
} else {
printw(" ");
}
}
那么蛇的方向移动是怎么回事呢,假如我们向右移动,就是将我们的链表在右边添加一个节点,然后将最左边的节点删除,再通过更新我们的地图,来达到移动的效果。所以我们要写一个删除节点的函数
void deleteNode() {
struct Snake *p;
p = head;
head = head->next; //将头结点进行移动
free(p);
}
void movesnake() { //蛇身子的移动
addNode();
deleteNode();
}
那么如何让蛇一直动起来呢,这时候我们可以利用ncurses的refresh函数,它有刷新屏幕的功能
void refreshJieMian() {
while (1) {
movesnake();
gamepic();
refresh();
usleep(100000); //延迟1ms
}
}
接下来我们要实现通过按键来实现蛇方向的移动,在ncurese中有可以获得上下左右键的值
keypad(stdscr, 1); //将这句话加入initNcurses函数中,意思是从键盘获取上下左右的值
int dir;//设置变量dir为全局变量
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
void turn(int direction) {
if (abs(dir) != abs(direction)) { //以绝对值的方式让蛇单方向行走
dir = direction;
}
}
void changeDir() { //从键盘中获取上下左右的信息
while (1) {
key = getch();
switch (key) {
case KEY_DOWN:
turn(DOWN);
break;
case KEY_UP:
turn(UP);
break;
case KEY_LEFT:
turn(LEFT);
break;
case KEY_RIGHT:
turn(RIGHT);
break;
}
}
}
switch (dir) { //上下左右的移动,将这段加入movesnake函数中
case UP: //上
new->row = tail->row - 1;
new->col = tail->col;
break;
case DOWN: //下
new->row = tail->row + 1;
new->col = tail->col;
break;
case LEFT: //左
new->row = tail->row;
new->col = tail->col - 1;
break;
case RIGHT: //右
new->row = tail->row;
new->col = tail->col + 1;
break;
}
struct Snake food; //用蛇的结构体来定义一个食物
void initfood() {
int x = rand() % 20; //随机取值,因为地图是20x20的,要在这范围中出现,对20求余数
int y = rand() % 20;
food.row = x;
food.col = y;
}
int Snake_food(int i, int j) { //跟身子的显示道理一样
if (food.row == i && food.col == j) {
return 1;
}
return 0;
}
//对将食物的显示加入地图规划函数中
for (col = 0; col <= 20; col++) {
if (col == 0 || col == 20) {
printw("|");
} else if (Snake_body(row, col)) {
printw("[]");
} else if (Snake_food(row, col)) {
printw("##");
} else {
printw(" ");
}
}
对链表的尾指针的值进行判断,超过地图的范围就将它初始化
int ifsnakedie() {
struct Snake *p;
p = head;
if (tail->row == -1 || tail->row == 20 || tail->col == 0 || //撞墙
tail->col == 20) {
return 1;
}
while (p->next != NULL) {
if (p->row == tail->row && p->col == tail->col) { //咬到自己的尾巴
return 1;
}
p = p->next;
}
return 0;
}
//修改移动函数
void movesnake() {
addNode();
if (Snake_food(tail->row, tail->col)) { //if判断的作用,如果吃到食物就让身子边长,加一个节点,并重新显示食物的位置。没有吃到则不变
initfood();
} else {
deleteNode();
}
if (ifsnakedie()) {
initSnake();
}
}
贪吃蛇的具体功能函数已经完成,在最后我们可以将入线程来完成最后的步骤
//主函数 线程的头文件一定要加 #include
void main() {
pthread_t t1;
pthread_t t2;
initNcurses();
initSnake();
gamepic();
pthread_create(&t1, NULL, refreshJieMian, NULL); //在refreshJieMian函数名前记得要加*
pthread_create(&t2, NULL, changeDir, NULL); //在changeDir函数名前记得要加*
while (1) ;
getch();
endwin();
}