代码实现:
#include<stdio.h>
#include<curses.h>
#include<signal.h>
#include<sys/time.h>
#include<stdlib.h>
#define BIRD '@'
#define BLANK ' '
#define PIPE '+'
/**定义管道结构体**/
typedef struct Pipe
{
int x;//列
int y;//横
struct Pipe* next;
}Pipe_node;//管道节点
Pipe_node* head,*tail;
void create_list();
void show_pipe();//显示管道
void clear_pipe();
void move_pipe();
int bird_y,bird_x;//代表小鸟坐标
void show_bird();//显示小鸟
void clear_bird();//清除小鸟
void move_bird();//移动小鸟
void init_curses();//curses库初始化
int set_timer(int ms_t);//设置定时器--ms
void handler(int sig)
{
Pipe_node* cur;
Pipe_node* new;
/**小鸟下落**/
clear_bird();
bird_y++;
show_bird();
/**游戏结束判断**/
if((char)inch() == PIPE)
{
set_timer(0);
endwin();
exit(1);
}
cur = head->next;
if(cur->x == 0)
{
int i,j = 0;
for(i = cur->x;i < cur->x + 10;i++)
{
for(j = 0;j<cur->y;j++)
{
move(j,i);
addch(BLANK);
}
for(j = cur->y+5;j<25;j++)
{
move(j,i);
addch(BLANK);
}
refresh();
}
head->next = cur->next;
free(cur);
new = (Pipe_node*)malloc(sizeof(Pipe_node));
new->x = tail->x + 20;
new->y = rand() % 11 +5;
new->next = NULL;
tail->next = new;
tail = new;
}
/**管道移动**/
clear_pipe();
move_pipe();
show_pipe();
}
int main()
{
bird_y = 15;//行
bird_x = 10;//列
init_curses();
signal(SIGALRM,handler);
set_timer(500);//500ms
srand(time(0));
create_list();
show_pipe();
show_bird();
move_bird();
return 0;
}
void init_curses()
{
initscr();//进入curses模式
curs_set(0);//禁止光标显示
noecho();//禁止输入字符显示
keypad(stdscr,1);//启动功能键
start_color();//启动颜色机制
init_pair(1,COLOR_WHITE,COLOR_RED);
init_pair(2,COLOR_WHITE,COLOR_GREEN);
}
int set_timer(int ms_t)
{
struct itimerval timer;
long t_sec,t_usec;
int ret;
t_sec = ms_t / 1000;//s
t_usec = (ms_t % 1000) * 1000; //us
timer.it_value.tv_sec = t_sec;
timer.it_value.tv_usec = t_usec;//首次启动定时值
timer.it_interval.tv_sec = t_sec;
timer.it_interval.tv_usec = t_usec;//定时时间间隔
ret = setitimer(ITIMER_REAL,&timer,NULL);
return ret;
}
void show_bird()
{
attron(COLOR_PAIR(1));
move(bird_y,bird_x);
addch(BIRD);
refresh();
attroff(COLOR_PAIR(1));
}
void clear_bird()
{
move(bird_y,bird_x);
addch(BLANK);
refresh();
}
void move_bird()
{
char key;
while(1)
{
key = getch();
if(key == ' ')
{
clear_bird();
bird_y--;
show_bird();
/**游戏结束判断**/
if((char)inch() == PIPE)
{
set_timer(0);
endwin();
exit(1);
}
}
}
}
void show_pipe()
{
Pipe_node* cur = head->next;
int i;
int j;
attron(COLOR_PAIR(2));
while(cur)
{
for(i = cur->x;i < (cur->x) + 10;i++)
{
for(j = 0;j<cur->y;j++)
{
move(j,i);
addch(PIPE);
}
for(j = (cur->y) + 5;j < 25;j++)
{
move(j,i);
addch(PIPE);
}
}
refresh();
cur = cur->next;
}
attroff(COLOR_PAIR(2));
}
void create_list()
{
Pipe_node* cur;
Pipe_node* new;
head = (Pipe_node*)malloc(sizeof(Pipe_node));//头结点
head->next = NULL;
cur = head;//指向头结点
int i = 0;
for(i = 0;i<5;i++)
{
new = (Pipe_node*)malloc(sizeof(Pipe_node));
new->x = (i+1)*20;
new->y = rand()%11+5;//管道的长度是5-15行
new->next = NULL;
cur->next = new;//链接新节点和头结点
cur = new;//指向新节点为尾节点
}
tail = cur;//更新尾节点
}
void clear_pipe()
{
Pipe_node* next = head->next;
int i;
int j;
while(next)
{
for(i = next->x;i < (next->x) + 10;i++)
{
for(j = 0;j < (next->y);j++)
{
move(j,i);
addch(BLANK);
}
for(j = (next->y) + 5;j<25;j++)
{
move(j,i);
addch(BLANK);
}
}
refresh();
next = next->next;
}
}
void move_pipe()
{
Pipe_node* cur;
cur = head->next;
while(cur)
{
cur->x--;
cur = cur->next;
}
}
各模块注释:
函数声明:
void create_list(): 创建管道链表。在游戏开始时调用,生成初始的一些管道。
void show_pipe(): 显示管道。遍历管道链表,将管道在屏幕上显示出来。
void clear_pipe(): 清除管道。清除屏幕上所有的管道。
void move_pipe(): 移动管道。遍历管道链表,将所有的管道向左移动一个单位。
void show_bird(): 显示小鸟。在屏幕上显示小鸟的位置。
void clear_bird(): 清除小鸟。清除屏幕上小鸟的位置。
void move_bird(): 移动小鸟。监听用户输入,当按下空格键时,使小鸟上升一格。
void init_curses(): 初始化 curses 库。在程序开始时调用,进入 curses 模式,设置一些终端属性。
int set_timer(int ms_t): 设置定时器。用于定时触发 SIGALRM 信号,以便实现游戏中小鸟的下落和管道的移动。
void handler(int sig):
这是一个信号处理函数,用于处理 SIGALRM 信号。具体功能如下:
小鸟下落:首先调用 clear_bird() 清除当前小鸟的位置,然后将小鸟的纵坐标 bird_y 增加 1,表示小鸟向下移动了一格,最后调用 show_bird() 在新位置显示小鸟。
游戏结束判断:通过检查小鸟下一个位置的字符,如果该位置的字符是管道 PIPE,则游戏结束。在 curses 库中,可以使用 inch() 函数获取当前光标位置的字符。如果检测到小鸟碰到管道,就会调用 set_timer(0) 停止定时器,然后调用 endwin() 结束 curses 模式,最后调用 exit(1) 退出程序。
管道移动和生成新管道:如果当前管道已经移动到屏幕左侧边缘(即 cur->x == 0),则表示需要移除该管道,并生成新的管道。首先,清除屏幕上该管道的位置。然后,将当前头指针 head 指向下一个管道节点,同时释放当前节点的内存。接着,生成新的管道节点 new,其横坐标为尾节点的横坐标加上一定的距离(例如,20),纵坐标为随机生成的值。最后,更新尾指针 tail 指向新的尾节点。
管道移动和显示:最后调用 clear_pipe() 清除屏幕上的所有管道,然后调用 move_pipe() 将所有管道向左移动一个单位,最后调用 show_pipe() 显示移动后的管道。
这个处理函数实现了游戏中小鸟的下落、游戏结束判断以及管道的移动和生成
mian:
这是 main() 函数,是程序的入口点。它完成了以下操作:
初始化小鸟的初始位置 bird_y 和 bird_x,分别设置为第 15 行和第 10 列。
调用 init_curses() 函数初始化 curses 库,准备开始游戏。
使用 signal(SIGALRM, handler) 设置了一个定时器信号 SIGALRM 的处理函数为 handler,即当定时器到达指定时间时,会调用 handler 函数处理。
调用 set_timer(500) 设置定时器,以 500 毫秒为间隔触发 SIGALRM 信号,即每 500 毫秒触发一次,用于控制小鸟的下落和管道的移动。
调用 srand(time(0)) 初始化随机数种子,以当前时间为参数,保证每次运行程序时生成的随机数不同。
调用 create_list() 函数创建初始的管道链表。
调用 show_pipe() 函数在屏幕上显示初始的管道。
调用 show_bird() 函数在屏幕上显示初始位置的小鸟。
调用 move_bird() 函数监听用户输入,控制小鸟的移动。
最后返回 0,表示程序正常运行结束。
void init_curses()
这是 init_curses() 函数,用于初始化 curses 库,具体功能如下:
initscr() 函数用于初始化 curses 库,将终端设置为 curses 模式,以便使用 curses 提供的功能。
curs_set(0) 函数禁止光标显示,以免干扰游戏界面的显示。
noecho() 函数禁止输入字符的显示,这样用户输入的字符不会直接显示在终端上,而是被程序接收并处理。
keypad(stdscr, 1) 函数启用功能键,使得 curses 库能够接收并处理终端上的特殊按键(例如方向键、功能键等)。
start_color() 函数用于启动 curses 库的颜色机制,使得程序能够使用彩色显示。
init_pair(1, COLOR_WHITE, COLOR_RED) 和 init_pair(2, COLOR_WHITE, COLOR_GREEN) 函数分别初始化了两个颜色对,用于设置小鸟和管道的颜色。这里使用了颜色对,将白色作为前景色,红色作为背景色,以及将白色作为前景色,绿色作为背景色,来实现不同的显示效果。
这个函数在游戏开始时被调用,对 curses 库进行了必要的初始化,以确保游戏界面的正常显示和用户交互功能的实现。
int set_timer(int ms_t);
这是 set_timer() 函数,用于设置定时器,具体功能如下:
接收一个参数 ms_t,表示定时器的时间间隔,单位为毫秒。
将毫秒转换为秒和微秒,以适配 struct itimerval 结构体的成员类型。
将毫秒转换为秒,然后将剩余的毫秒转换为微秒。
初始化一个 struct itimerval 类型的变量 timer,用于设置定时器的参数。
将 it_value 成员设置为首次启动定时器的时间,即 tv_sec 设置为转换后的秒数,tv_usec 设置为转换后的微秒数。
将 it_interval 成员设置为定时器的时间间隔,也就是每次定时器触发后再次触发的时间间隔,同样设置为转换后的秒数和微秒数。
使用 setitimer() 函数设置实时定时器 ITIMER_REAL,并将 timer 作为参数传入。
这样设置的定时器将会以实时时间为基准,定时器触发后会产生 SIGALRM 信号。
函数返回一个整数值,表示设置定时器的结果,通常为 0 表示成功,-1 表示失败。
这个函数被用于在游戏中设置一个定时器,以控制小鸟的下落速度和管道的移动速度
小鸟的操作:
这是一组函数,用于控制小鸟在游戏界面中的显示和移动,以及处理游戏结束的逻辑。
void show_bird(): 这个函数用于显示小鸟在游戏界面中的位置。
首先调用 attron(COLOR_PAIR(1)) 函数,启用颜色对1,以设置小鸟的颜色。
然后调用 move() 函数将光标移动到小鸟的位置(由全局变量 bird_y 和 bird_x 控制),调用 addch() 函数在光标所在位置添加小鸟字符 BIRD,最后调用 refresh() 函数刷新屏幕。
最后调用 attroff(COLOR_PAIR(1)) 函数,关闭颜色对1,恢复原有颜色设置。
void clear_bird(): 这个函数用于清除小鸟在游戏界面中的位置。
首先调用 move() 函数将光标移动到小鸟的位置,然后调用 addch() 函数在光标所在位置添加空白字符 BLANK,最后调用 refresh() 函数刷新屏幕。
void move_bird(): 这个函数用于控制小鸟的移动。
函数使用一个无限循环来等待用户按键输入,只有在用户按下空格键时才执行移动操作。
当用户按下空格键时,首先调用 clear_bird() 函数清除小鸟当前位置的显示,然后将小鸟的行坐标 bird_y 减1,表示小鸟向上移动一个位置,接着调用 show_bird() 函数重新在新位置显示小鸟。
在移动小鸟后,函数会检查小鸟所在位置是否与管道重叠,如果重叠则会调用 set_timer(0) 函数停止定时器,然后调用 endwin() 函数关闭 curses 模式,最后调用 exit(1) 函数退出程序,以表示游戏结束。
这组函数控制了小鸟在游戏中的显示和移动,同时处理了游戏结束的逻辑。
void show_pipe():
这个函数用于显示管道在游戏界面中的位置。
Pipe_node* cur = head->next;: 首先,定义一个指针 cur,指向管道链表中的第一个管道节点,跳过头结点。
int i; int j;: 定义循环变量 i 和 j,用于遍历管道的列和行。
attron(COLOR_PAIR(2));: 调用 attron() 函数启用颜色对2,以设置管道的颜色。
while(cur) { ... }: 使用一个循环遍历管道链表中的所有管道节点。
for(i = cur->x; i < (cur->x) + 10; i++) { ... }: 遍历管道所在的列,从管道节点的列坐标开始,到列坐标加上10(管道长度)为止。对于每一列:
for(j = 0; j < cur->y; j++) { ... }: 遍历管道上半部分的行,从第0行到管道节点的横坐标减1行为止。对于每一行,在当前列的位置显示管道字符 PIPE。
for(j = (cur->y) + 5; j < 25; j++) { ... }: 遍历管道下半部分的行,从管道节点的横坐标加上5行开始,直到第25行为止。对于每一行,在当前列的位置显示管道字符 PIPE。
refresh();: 在循环结束后调用 refresh() 函数刷新屏幕,以显示更新后的管道。
cur = cur->next;: 将指针 cur 移动到下一个管道节点,以继续遍历。
attroff(COLOR_PAIR(2));: 调用 attroff() 函数关闭颜色对2,恢复原有颜色设置。
void create_list()
这个函数用于创建初始的管道链表。
Pipe_node* cur;: 定义指针 cur,用于遍历链表。
Pipe_node* new;: 定义指针 new,用于创建新的管道节点。
head = (Pipe_node*)malloc(sizeof(Pipe_node));: 分配内存给头结点。
head->next = NULL;: 将头结点的指针域指向空,表示链表为空。
cur = head;: 将 cur 指针指向头结点,作为链表的起始点。
for(i = 0; i < 5; i++) { ... }: 使用循环创建5个初始管道节点。
new = (Pipe_node*)malloc(sizeof(Pipe_node));: 分配内存给新的管道节点。
new->x = (i + 1) * 20;: 设置新节点的列坐标为 (i + 1) * 20,确保管道节点之间的水平间距。
new->y = rand() % 11 + 5;: 随机生成管道节点的横坐标,范围在5到15之间,以确保管道长度在5到15行之间。
new->next = NULL;: 将新节点的指针域设为空,因为它是最后一个节点。
cur->next = new;: 将新节点连接到当前节点的后面。
cur = new;: 将 cur 指针移动到新节点,作为链表的尾节点。
tail = cur;: 更新尾节点为链表的最后一个节点。
void clear_pipe()
这个函数用于清除当前屏幕上的所有管道。
Pipe_node* next = head->next;: 定义指针 next 指向头结点的下一个节点,即第一个管道节点。
int i;: 定义整型变量 i 用于循环迭代列坐标。
int j;: 定义整型变量 j 用于循环迭代横坐标。
while(next) { ... }: 当 next 指针不为空时,即还有管道节点未清除时,执行循环。
for(i = next->x; i < (next->x) + 10; i++) { ... }: 遍历当前管道节点的列坐标范围,即管道的横向范围。
for(j = 0; j < (next->y); j++) { ... }: 遍历当前管道节点的上半部分,即管道的上半部分高度范围。
move(j, i);: 将光标移动到指定位置。
addch(BLANK);: 在当前位置添加一个空白字符,以清除该位置上的管道。
for(j = (next->y) + 5; j < 25; j++) { ... }: 遍历当前管道节点的下半部分,即管道的下半部分高度范围。
move(j, i);: 将光标移动到指定位置。
addch(BLANK);: 在当前位置添加一个空白字符,以清除该位置上的管道。
refresh();: 刷新屏幕,使更新生效。
next = next->next;: 将 next 指针指向下一个管道节点,以便继续清除下一个管道节点。
void move_pipe();
这个函数用于移动管道,使其向左移动一个单位。
Pipe_node* cur;: 声明一个指针 cur,用于遍历管道链表。
cur = head->next;: 将 cur 指向管道链表的第一个节点,即头结点的下一个节点,表示从第一个管道开始移动。
while(cur) { ... }: 当 cur 指针不为空时,即还有管道节点需要移动时,执行循环。
cur->x--;: 将当前管道节点的列坐标减1,实现向左移动一个单位。
cur = cur->next;: 将 cur 指针移动到下一个管道节点,以便继续移动下一个管道节点。