弹球游戏

一个弹球游戏是这样的

这个游戏用到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上使用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值