##最近在看unix/linux实践教程这本书来学习linux系统编程,看到第七章里面有用到基于事件编程写一个弹球游戏的练习,觉得挺有趣的就把完整的实现大概写了一下,首先弹球游戏需要在屏幕绘图,那就需要用到一个叫curses的图形库,安装命令如下:sudo apt-get install libncurses5-dev
,安装完后在程序中添加头文件#include "curses.h"
即可使用里面的绘图api,关于curses库的api网上有很多也很详细,这里就不赘述了。另外要注意在用gcc编译时需要加-lcurses
编译,否则编译会报错,之后就可以愉快的编程了。
##首先我们要知道视频是通过改变屏幕的像素点来实现图像变化的效果的,弹球游戏就是个简化的视频游戏。在这个游戏里有球,边框,挡板,其中边框是固定的,球和挡板是会移动的。要实现固定的边框只需调用mvaddch
来绘制就行了,但记住要让图像显示需要调用refresh
将图形从缓冲区加载到屏幕,这样的好处是只需要对比改变的图像位置就可以实现刷图的效果,大大减少数据传输,目前视频流也是基于这种原理。而要实现会移动的球就需要间隔的刷屏,故需要用到timer定时器来实现,timer定时器计时结束会产生一个SIGALRM信号,这个信号会打断正在运行的主程序,进入signal
中设置的回调函数,在回调函数里判断球的移动位置如果球的位置改变了就刷新屏幕,执行完回调函数自动返回被打断的位置继续执行,类似于单片机中的定时器中断。那么问题来了挡板是通过响应外部键盘输入来进行移动的这该怎么实现呢?有三种办法,第一种是阻塞等待输入,有输入程序就响应并判断,但是如果程序需要在运行时运行其他重要的运算这就显然不是个好办法,第二种是不断的轮询来访问输入缓冲区的值,有数据就判断否则就跳过,这显然比第二个办法好,但如果不想将判断函数增加到主函数中也不想一直在间隔查询浪费cpu呢?那还有第三种方法异步输入,通过设置stdin的ASYNC位,当有输入时产生SIGIO信号并告知进程,进程被打断并进入设置好的回调函数进行按键数值的处理(挡板的移动(a,d)|球的定时器次数的增减(x,X,y,Y)|程序的退出(q)),显然这是最好的办法了。那还有最后一个就是挡板与球的交互怎么实现呢?这只需在球的回调函数中获取挡板的位置如果碰到挡板就回弹,否则结束游戏。大概的实现思路就是这样子,程序的实现还有很多方式,下面的程序只是简单的实现,有时间会将程序的耦合度降低,玩法拓展一下。
##源码如下:
/*
* @Description:
* @Version:
* @Autor: wigen
* @Date: 2021-04-26 11:59:57
* @LastEditors: wigen
* @LastEditTime: 2021-05-04 17:29:44
*/
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "curses.h"
#include "unistd.h"
#include "signal.h"
#include "sys/time.h"
#include "fcntl.h"
#include "termios.h"
#define UPEDGE 10 //上边框值
#define DOWNEDGE 30 //下边框值
#define LEFTEDGE 10 //左边框值
#define RIGHTEDGE 40 //右边框值
#define X_TIM 1 //球的定时器次数初始值
#define Y_TIM 1
#define BALL_SYMBOL 'o' //球的符号
#define BALL_BLANK ' ' //球的空白符号
#define TICK_PER_SECOND 20 //每秒钟定时器发生次数
#define BORAD "------"//挡板符号
#define BORAD_BLANK " "//挡板的空白符号
struct ppball {
int x_pos, y_pos, //球的当前位置
x_ttm, y_ttm, //球的定时器发生次数值
x_ttg, y_ttg, //定时器发生次数自减值
x_dir, y_dir; //单次移动的距离
char symbol;
}ball;
int board_x, board_y;
int quit = 0;
void ball_init(void);
static int set_timer(int time_ms);
void move_process(int sig);
static int bounce_or_lose(struct ppball*);
void board_move(char);
void tty_async(void);
void sigio_process(int sig);
int main(int argv, char* argc[])
{
initscr();
clear();
ball_init();
noecho(); //关闭回显
crmode(); //单字符接收|关闭接收缓冲
tty_async(); //异步输入(非阻塞接收)
signal(SIGALRM, move_process);
signal(SIGIO, sigio_process);
while (!quit)
{
pause();
}
refresh();
set_timer(0);
endwin();
}
/** 球的图形|定时器,以及边框初始化
* @param {*}
* @return {*}
*/
void ball_init(void)
{
ball.x_pos = LEFTEDGE+1;
ball.y_pos = UPEDGE+1;
ball.x_ttg = ball.x_ttm = X_TIM;
ball.y_ttg = ball.y_ttm = Y_TIM;
ball.x_dir = ball.y_dir = 1;
ball.symbol = BALL_SYMBOL;
board_x = (LEFTEDGE+RIGHTEDGE)/2;
board_y = DOWNEDGE;
for(int i=UPEDGE; i<=DOWNEDGE; i++)
{
mvaddch(i, LEFTEDGE, '|');
mvaddch(i, RIGHTEDGE, '|');
}
for(int i=LEFTEDGE; i<=RIGHTEDGE; i++)
{
mvaddch(UPEDGE, i, '-');
// mvaddch(DOWNEDGE, i, '-');
}
mvaddstr(board_y, board_x, BORAD);
mvaddch(ball.y_pos, ball.x_pos, ball.symbol);
refresh();
if(set_timer(1000/TICK_PER_SECOND) == -1)
perror("timer_set_error");
}
/** 设置定时器时间
* @param {int} time_ms
* @return {*}
*/
static int set_timer(int time_ms)
{
struct itimerval new_timerset;
new_timerset.it_interval.tv_sec = time_ms/1000;
new_timerset.it_interval.tv_usec = (time_ms%1000)*1000;
new_timerset.it_value.tv_sec = time_ms/1000;
new_timerset.it_value.tv_usec = (time_ms%1000)*1000;
return setitimer(ITIMER_REAL, &new_timerset, NULL);
}
/** 开启当前进程的异步输入模式,进程接收标准输入将以SIGIO信号处理,而非阻塞
* @param {*}
* @return {*}
*/
void tty_async(void)
{
int flag;
fcntl(0, F_SETOWN, getpid());
flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, (flag | O_ASYNC));
}
/** SIGIO信号处理函数
* @param {int} sig
* @return {*}
*/
void sigio_process(int sig)
{
int c;
c = getchar();
switch (c)
{
case 'x':
ball.x_ttm--;
if(ball.x_ttm < 0)
ball.x_ttm = 0;
break;
case 'X':
ball.x_ttm++;
break;
case 'y':
ball.y_ttm--;
if(ball.y_ttm < 0)
ball.y_ttm = 0;
break;
case 'Y':
ball.y_ttm++;
break;
case 'a':
if(board_x > LEFTEDGE+1)
board_move(c);
break;
case 'd':
if(board_x < (RIGHTEDGE-strlen(BORAD)))
board_move(c);
break;
case 'q':
quit = 1;
break;
default:
break;
}
}
/** 根据键值处理挡板的移动
* @param {char} ch
* @return {*}
*/
void board_move(char ch)
{
if(ch == 'a')
{
mvaddstr(board_y, board_x, BORAD_BLANK);
board_x--;
mvaddstr(board_y, board_x, BORAD);
move(LINES-1, COLS-1);
refresh();
}
else if(ch == 'd')
{
mvaddstr(board_y, board_x, BORAD_BLANK);
board_x++;
mvaddstr(board_y, board_x, BORAD);
move(LINES-1, COLS-1);
refresh();
}
}
/** 定时器回调函数处理球的位置
* @param {int} sig
* @return {*}
*/
void move_process(int sig)
{
int x_cur, y_cur, moved = 0;
signal(SIGALRM, SIG_IGN);
x_cur = ball.x_pos;
y_cur = ball.y_pos;
if(ball.x_ttm > 0 && ball.x_ttg-- == 0)
{
ball.x_pos += ball.x_dir;
ball.x_ttg = ball.x_ttm;
moved = 1;
}
if(ball.y_ttm > 0 && ball.y_ttg-- == 0)
{
ball.y_pos += ball.y_dir;
ball.y_ttg = ball.y_ttm;
moved = 1;
}
if(moved)
{
mvaddch(y_cur, x_cur, BALL_BLANK);
mvaddch(ball.y_pos, ball.x_pos, ball.symbol);
bounce_or_lose(&ball);
move(LINES-1, COLS-1);
refresh();
if(ball.y_pos == DOWNEDGE -1)
if(ball.x_pos < board_x-1 || ball.x_pos > (board_x+strlen(BORAD)))
{
signal(SIGALRM, SIG_DFL);
return;
}
}
signal(SIGALRM, move_process);
}
/** 碰墙回弹函数
* @param {struct} ppball
* @return {*}
*/
static int bounce_or_lose(struct ppball* ball_p)
{
int return_val = 0;
if(ball_p->y_pos == UPEDGE+1)
{
ball_p->y_dir = 1;
return_val = ball_p->y_dir;
}else if(ball_p->y_pos == DOWNEDGE)
{
ball_p->y_dir = -1;
return_val = ball_p->y_dir;
}
if(ball_p->x_pos == LEFTEDGE+1)
{
ball_p->x_dir = 1;
return_val = ball_p->x_dir;
}else if(ball_p->x_pos == RIGHTEDGE-1)
{
ball_p->x_dir = -1;
return_val = ball_p->x_dir;
}
return return_val;
}