一个弹球游戏是这样的
这个游戏用到curses库来绘制图形。
技术上用到了中断、定时器,来实现速度的定时,用户按键的响应。需要理解如何安全地同时做几件事,堵塞忽略或递归。
本文并不详细介绍这些东西。但提供一种实现游戏的代码。
Unix系统中中断被称为信号。
信号可由signal函数管理。早期Unix提供三种方式供选择
(1) 默认操作(一般是终止进程),如signal(SIGALRM, SIG_DFL)
(2) 忽略信号, 如signal(SIGALRM, SIG_IGN)
(3) 执行一个函数, 如signal(SIGALRM, ball_move)
一段修改一个数据结构的代码如果在运行时被打断将刀子数据的不完整或损毁,则称这段代码为临界区。
程序处理信号时必须决定哪段代码为临界区,这段代码应当保护,一个简单的方法就是堵塞或忽略哪些将要使用或修改特定数据的信号。
而进入游戏后我们用方式(3)递归调用处理函数。递归调用的一个危险是可重入问题。一个信号或函数,如果在激活状态下能被调用而
不引起任何问题就称之为可重入的。
球在屏幕上移动,用户控制按键上下移动球拍。间隔计数器控制球的运动。它们使用同一个光标,有没有临界区呢?
我们的动画的基础是,绘制图像-擦除图像-绘制图像。当动作之间插入时间间隔就实现了闪烁,新的图像和原来图像位置不一样就实现了
移动。界面上的提示语,当需要更新时,擦除旧的,写上新的。擦除的简单办法是写空格。
球的水平移动是容易的。一个定时器,每次移动一个单位。下一个问题是如何斜着运动?
为了防止图像跳跃,我们仍然每次移动一个单位。假如我们每两个计时信号向右移动一个单位,每六个计时信号向上移动一个单位。就实现了
一种对角线移动。这里我们使用两个计时器,一个记录水平移动的计时数,另一个记录竖直移动的计时数。
我使用了两个随机数来控制球的速度,所以球的速度是随机的,当然每次的开场第一球都一样。
这些在代码中体现。
bounce.c文件
/**
* bounce.c 1.0
* bounce a character (default is 'O') around the screen
* defined by some parmeters
*
* user input: s slow down x component, S slow y component
* f speed up x component, F speed y component
* q quit
*
* build: gcc bounce.c set_ticker.c -l curses -o bounce
*/
#include<stdio.h>
#include<curses.h>
#include<signal.h>
#include "bounce.h"
struct ppball the_ball;
int dir; // racket move value
int racket_y; // racket ordinate
int scores = 0;
void display_info(void);
int main(void)
{
int c;
set_up();
while((c = getchar()) != 'q' && c != 'n' ) // q is quit
{
switch(c)
{
case 'p':
case 'P':
dir = -1; // up
break;
case 'l':
case 'L':
dir = 1; // down
break;
case ' ': // suspend
break;
default:
break;
}
}
wrap_up();
}
/**
* init structure and other stuff
*/
void set_up(void)
{
the_ball.y_pos = Y_INIT;
the_ball.x_pos = X_INIT;
the_ball.y_ttg = Y_TTM;
the_ball.y_ttm = Y_TTM;
the_ball.x_ttg = X_TTM;
the_ball.x_ttm = X_TTM;
the_ball.y_dir = 1;
the_ball.x_dir = 1;
the_ball.symbol = DFL_SYMBOL;
dir = 0;
racket_y = Y_INIT;
initscr();
noecho();
crmode();
printf("\033[?25l"); // hide cursor
signal(SIGINT, SIG_IGN) ;
mvaddch(the_ball.y_pos, the_ball.x_pos, the_ball.symbol);
refresh();
signal(SIGALRM, ball_move);
set_ticker(1000 / TICKS_PER_SEC);
paint_edge();
display_info();
racket(racket_y, racket_y);
}
void racket(int y, int old_y)
{
move(old_y , RIGHT_EDGE + 1);
vline(' ', RACKET_LEN);
move(y, RIGHT_EDGE + 1);
vline(ACS_CKBOARD, RACKET_LEN);
dir = 0; // reset
refresh();
}
void racket_move(void)
{
int y_cur;
//int moved;
y_cur = racket_y;
//moved = 0;
racket_y += dir;
if(racket_y < TOP_ROW) // cross the border
{
racket_y = TOP_ROW;
}
if(racket_y + RACKET_LEN > BOT_ROW)
{
racket_y = BOT_ROW - RACKET_LEN + 1;
}
racket( racket_y, y_cur);
}
void display_info(void)
{
move(TOP_ROW - 2, RIGHT_EDGE - 15);
addstr("SCORES:");
move(TOP_ROW - 2, RIGHT_EDGE - 5);
printw("%d", scores);
refresh();
}
void reset(void)
{
scores = 0;
move(TOP_ROW - 2, RIGHT_EDGE - 5);
addstr(" ");
printw("%d", scores);
refresh();
}
void change_speed(void)
{
the_ball.x_ttm = rand() % 5 + 0;
the_ball.y_ttm = rand() % 8 + 1;
}
void paint_edge(void)
{
move(TOP_ROW - 1, LEFT_EDGE);
hline('-', RIGHT_EDGE - LEFT_EDGE + 1);
move(TOP_ROW , LEFT_EDGE - 1);
vline(ACS_VLINE, BOT_ROW - TOP_ROW + 1);
//move(TOP_ROW + 1, RIGHT_EDGE);
//vline('|', BOT_ROW - TOP_ROW);
move(BOT_ROW + 1, LEFT_EDGE);
hline('-', RIGHT_EDGE - LEFT_EDGE + 1);
refresh();
}
void wrap_up(void)
{
set_ticker(0);
endwin(); // put back to normal
printf("\033[?25h"); // display cursor
}
void game_over(void)
{
mvaddstr( OVER_Y, OVER_X, "GAME OVER" );
mvaddstr( AGAIN_Y , AGAIN_X, "AGAIN ? Y/N" );
mvaddstr( QUIT_Y, QUIT_X, "QUIT(q)" );
mvaddstr( RESET_Y, RESET_X, "RESET(r)");
refresh();
beep();
}
void again(void)
{
mvaddstr( OVER_Y, OVER_X, " " );
mvaddstr( AGAIN_Y, AGAIN_X, " " );
mvaddstr( QUIT_Y, QUIT_X, " " );
mvaddstr( RESET_Y, RESET_X, " ");
move(TOP_ROW - 2, RIGHT_EDGE + 1); // put in order
vline(' ', BOT_ROW - TOP_ROW + 3);
move(TOP_ROW - 2, RIGHT_EDGE + 2);
vline(' ', BOT_ROW - TOP_ROW + 3);
refresh();
}
void ball_move(int signum)
{
int y_cur;
int x_cur;
int moved;
int racket_y_cur;
int option;
signal(SIGALRM, SIG_IGN); // don't get caught now
y_cur = the_ball.y_pos; // old spot
x_cur = the_ball.x_pos; //
moved = 0;
if( (the_ball.y_ttm > 0) && (the_ball.y_ttg-- == 1) )
{
the_ball.y_pos += the_ball.y_dir; // move;
the_ball.y_ttg = the_ball.y_ttm; // reset
moved = 1;
}
if( (the_ball.x_ttm > 0) && (the_ball.x_ttg -- == 1) )
{
the_ball.x_pos += the_ball.x_dir; // move
the_ball.x_ttg = the_ball.x_ttm; // reset
moved = 1;
}
if(moved)
{
mvaddch(y_cur, x_cur, BLANK);
mvaddch(y_cur, x_cur, BLANK);
mvaddch(the_ball.y_pos, the_ball.x_pos, the_ball.symbol);
bounce_or_lose(&the_ball);
if(the_ball.x_pos > RIGHT_EDGE)
{
game_over();
// signal(SIGALRM, again);
option = getch();
switch(option)
{
case 'y':
case 'Y':
again();
set_up();
display_info();
break;
case 'N':
case 'n':
case 'q':
wrap_up();
exit(1);
break;
case 'r':
case 'R':
reset();
break;
default:
break;
}
}
move(LINES - 1, COLS -1);
refresh();
}
racket_move();
display_info();
signal(SIGALRM, ball_move); // for unreliable systems
}
int bounce_or_lose(struct ppball *bp)
{
int return_val = 0;
if(bp -> y_pos == TOP_ROW)
{
bp->y_dir = 1;
return_val = 1;
}
else if(bp->y_pos == BOT_ROW)
{
bp->y_dir = -1;
return_val = 1;
}
if(bp->x_pos == LEFT_EDGE)
{
bp->x_dir = 1;
return_val = 1;
}
else if( (bp->x_pos == RIGHT_EDGE) &&
(bp->y_pos >= racket_y) &&
(bp->y_pos <= racket_y + RACKET_LEN ) )
{
change_speed();
scores += 10;
bp->x_dir = -1;
return_val = 1;
}
else if(bp->x_pos > RIGHT_EDGE)
{
bp->x_pos = RIGHT_EDGE + 1;
}
return return_val;
}
bounce.h文件
/**
* bounce.h
*/
#ifndef _BOUNCE_H
#define _BOUNCE_H
#define BLANK ' '
#define DFL_SYMBOL 'O'
#define TOP_ROW 5
#define BOT_ROW 20
#define LEFT_EDGE 10
#define RIGHT_EDGE 70
#define X_INIT 10 // starting col
#define Y_INIT 10 // starting row
#define TICKS_PER_SEC 50 // affects speed
#define X_TTM 5
#define Y_TTM 8
#define RACKET_LEN 2
#define OVER_Y (BOT_ROW + TOP_ROW) / 2 - 3
#define OVER_X (RIGHT_EDGE - LEFT_EDGE) / 2 + 5
#define AGAIN_Y (BOT_ROW + TOP_ROW) / 2
#define AGAIN_X (RIGHT_EDGE - LEFT_EDGE) / 2 + 4
#define RESET_Y BOT_ROW - 2
#define RESET_X RIGHT_EDGE - 20
#define QUIT_Y BOT_ROW - 2
#define QUIT_X RIGHT_EDGE - 8
struct ppball
{
int y_pos;
int x_pos;
int y_ttm;
int x_ttm;
int y_ttg;
int x_ttg;
int y_dir;
int x_dir;
char symbol;
} ;
void set_up(void); // init structure and other stuff
void wrap_up(void);
void ball_move(int signum);
int bounce_or_lose(struct ppball *bp);
void paint_edge(void);
void racket(int y, int old_y);
void racket_move(void);
void game_over(void);
void again(void);
#endif
定时器文件set_ticker.c
/**
* set_ticker.c
* arranges for interval timer to issue SIGALRMs at regular intervals
* return -1 on error; 0 for OK
*/
#include<stdio.h>
#include<sys/time.h>
#include<signal.h>
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long long n_sec, n_usecs;
n_sec = n_msecs / 1000; // int part
n_usecs = (n_msecs % 1000) * 1000; // renmainder
new_timeset.it_interval.tv_sec = n_sec; // set reload
new_timeset.it_interval.tv_usec = n_usecs; // new ticker value
new_timeset.it_value.tv_sec = n_sec; // store this
new_timeset.it_value.tv_usec = n_usecs; //
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
编译执行。
最终游戏效果如下:
使用p、l 键控制球拍上下移动。y 再来一场, r 清空分数,q 退出(任何时候皆可退出)。
球的速度是随机的。不能在Windows上使用。