1、全部代码(代码相应功能详见注释)
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define UP 1
#define DOWN -1
#define LEFT 2
#define RIGHT -2
struct Snake
{
int i;
int j;
struct Snake *next;
};
struct Snake *head = NULL;
struct Snake *tail = NULL;
int key;
int dir;
struct Snake food;
void initFood()
{
int x;
int y;
x = rand()%20; //rand()可随机取大于等于0的自然数,注意取余操作可使得x、y均满足(0<=x,y<=20)将食物“#”限制在游戏边界以内
y = rand()%20;
food.i = x;
if (y != 0){ //当food的列等于0时,测试代码时发现界面没有打印“#”
food.j = y;
}
}
int eatFood(int i, int j)
{
if (food.i == i && food.j == j){
return 1;
}
return 0;
}
void addSnakeNode()
{
struct Snake *new = (struct Snake *)malloc(sizeof(struct Snake));
switch(dir){
case UP:
new->i = tail->i - 1;
new->j = tail->j;
break;
case DOWN:
new->i = tail->i + 1;
new->j = tail->j;
break;
case LEFT:
new->i = tail->i;
new->j = tail->j - 1;
break;
case RIGHT:
new->i = tail->i;
new->j = tail->j + 1;
break;
}
new->next = NULL;
tail->next = new;
tail = new;
}
void deleteSnakeNode()
{
struct Snake *p = head;
head = head->next;
free(p);
}
void initSnakeNode()
{
struct Snake *p = head;
while(head != NULL){ //这里不能直接free(head)因为后面还会再此使用head结构体指针
p = head;
head = head->next;
free(p);
}
dir = RIGHT;
initFood();
head = (struct Snake *)malloc(sizeof(struct Snake));
head->i = 2;
head->j = 2;
head->next = NULL;
tail = head;
addSnakeNode();
addSnakeNode();
addSnakeNode();
}
void initNcurse()
{
initscr(); //ncurses界面初始化函数
keypad(stdscr, 1); //ncurses库自带的keypad函数,允许用户终端的键盘,允许getch()函数获取功能键
noecho(); //不回显用户输入的内容
}
int printSnakeNode(int i, int j)
{
struct Snake *p = head;
while(p != NULL){
if (p->i == i && p->j == j){
return 1;
}
p = p->next;
}
return 0;
}
void gamePic() //设计游戏边界
{
int i;
int j;
move(0, 0); //move( int begin_x, int begin_y)//移动光标,基于标准屏幕stdscr(将移动后的界面覆盖掉先前的界面)
for(i = 0; i < 20; i++){
if (i == 0){
for(j = 0; j < 20; j++){
printw("--");
}
printw("\n");
}
if (i >= 0 && i <= 19){
for(j = 0; j <= 20; j++){
if (j == 0 || j == 20){
printw("|");
}
else if (printSnakeNode(i, j) == 1){
printw("[]");
}
else if (eatFood(i, j) == 1){
printw("##");
}
else {
printw(" ");
}
}
printw("\n");
}
if (i == 19){
for(j = 0; j < 20; j++){
printw("--");
}
printw("\n");
}
}
printw("Designed by: kk key value: %d\n", key);
printw("food.hang = %d, food.lie = %d\n", food.i, food.j);
}
int snakeDie()//两种情况:1、撞向边界死亡;2、自己撞自己死亡
{
struct Snake *p = head;
if (tail->i < 0 || tail->j == 0 || tail->i == 20 || tail->j == 20){ //撞向游戏边界死亡
return 1;
}
while(p->next != NULL){
//当p走到🐍的尾巴tail时,以下条件语句恒成立则会出现一直初始化snake,故应该是p->next!=NULL,避免尾巴与尾巴自身比较
if (p->i == tail->i && p->j == tail->j){
return 1; //撞向自己死亡
}
p = p->next;
}
return 0;
}
void moveSnakeNode()
{
addSnakeNode();
if (eatFood(tail->i, tail->j) == 1){
initFood(); //当吃到食物“#”初始化“#”,即蛇只增节点不删除节点
}
else {
deleteSnakeNode(); //其余情况就是蛇增加尾巴节点删除头节点
}
if (snakeDie() == 1){
initSnakeNode();
}
}
void *refreshSnakeView() //刷新界面线程
{
while(1){
moveSnakeNode();
gamePic();
refresh(); //每次在屏幕绘制之后,需要调用refresh()刷新屏幕
usleep(100000);
}
}
void turnByAbs(int direction) //以绝对值改变来控制🐍是否转向
{
if (abs(dir) != abs(direction)){
dir = direction;
}
}
void *changeDir() //🐍转向线程
{
while(1){
key = getch();
switch(key){
case KEY_UP:
turnByAbs(UP);
break;
case KEY_DOWN:
turnByAbs(DOWN);
break;
case KEY_LEFT:
turnByAbs(LEFT);
break;
case KEY_RIGHT:
turnByAbs(RIGHT);
break;
}
}
}
int main(int argc, char *argv[])
{
pthread_t t1;
pthread_t t2;
initNcurse();
initSnakeNode();
gamePic();
//创建两个线程同时运行
pthread_create(&t1, NULL, refreshSnakeView, NULL);
pthread_create(&t2, NULL, changeDir, NULL);
while(1);//等待输入,避免主程序提前结束
getch(); //接收键盘上的输入一个字符, 相当于getchar()
endwin(); //ncurses自带库函数,避免终端界面错乱,结束ncurse屏幕绘画
return 0;
}
2、代码测试
这样就可以实现贪吃蛇小游戏了(当然在调试代码过程中会出现各式各样的问题,不要放弃,把基础打好,一定可以找到问题所在的地方并解决好它)