详解Linux终端下编写“贪吃蛇”游戏

版权声明:本文为博主原创文章,未经博主允许不得转载。主页 http://fishcode.cn/ https://blog.csdn.net/jjzhoujun2010/article/details/6709827

原创文章,欢迎转载,转载请注明:http://www.fishcode.cn/linux_program_snake.html

CSDN: blog.csdn.net/jjzhoujun2010

作者:Dream Fly

 

 大一学习C语言的时候就想要用Turbo C编写一个视频小游戏出来,种种原因后面搁浅了,现在借着学习Linux系统编程的劲头,编写了一个终端下可以运行的贪吃蛇游戏,其中此视频游戏用到的一些知识和操作系统运行时候的一些简单功能有点类似,引用《Unix/Linux 编程实践教程》(Bruce Molay著)里面所介绍的视频游戏一般的编写以及同操作系统的关系的原文如下:

  一、视频游戏如何做

  (1)空间:游戏必须在计算机屏幕的特定位置画影像。程序如何控制视频显示?

  (2)时间:影像以不同的速度在屏幕上移动。以一个特定的时间间隔改变位置。程序是如何获知时间并且在特定的时间安排事情的发生?

  (3)中断:程序在屏幕上平滑移动的物体,用户可以在任何时刻产生输入。程序是如何响应中断的?

  (4)同时做几件事:游戏必须在保持几个物体移动的同时还要响应中断。程序是如何同时做多件事情而不被弄得晕头转向的?

  二、操作系统面临着类似的问题

  操作系统同样要面对这4个问题。内核将程序载入内存空间并维护每个程序在内存中所处的位置。在内核的调度下,程序以时间片间隔的方式运行,同时,内核也在特定的时刻运行特定的内部任务。内核必须在很短的时间内响应用户和外设在任何时刻的输入。同时做几件事需要一些技巧。内核是如何保证数据的有序和规整的?

  上面的都是那本书上说的,个人觉得讲的很好,看完这本后再看那本Linux圣经《Unix环境高级编程》或许更好些。回归正题吧,主要介绍一下设计一个终端下的贪吃蛇游戏所实现的功能以及所需要的几个条件和一些函数。

  本贪吃蛇实现的功能是通过吃食物来增长自己的长度,可以利用按键 'f' 实现加速和 's' 键实现减速, 'q' 键退出,方向键控制方向,蛇要是碰到自己的身体或者碰到墙或者吃到一定数量,那么游戏就结束。功能还是挺简单的吧,下面就介绍下各个步骤的设计:

  1.首先要使用终端图形库curses.h文件,由于不是C标准库,一般电脑不会自带,需要自行下载安装,ubuntu下可以这么下载  sudo apt-get install libncurses5-dev  已经替换成ncurses.h 即 new curses.h的意思,完全兼容curses。介绍下此游戏需要用到的常见的几个curses函数。

基本curse函数
initscr() 初始化curses库和tty
endwin() 关闭curses并重置tty
refresh() 刷新屏幕显示
mvaddch(y,x,c) 在坐标(y,x)处显示字符c
mvaddstr(y,x,str) 在坐标(y,x)处显示字符串str
cbreak() 开启输入立即响应
noecho() 输入不回显到屏幕
curs_set(0) 使光标不可见
attrset() 开启图形显示模式
keypad(stdscr, true) 开启小键盘方向键输入捕捉支持

更详细的可以  man ncurses   或者参见http://bbs.chinaunix.net/viewthread.php?tid=909369

  2.介绍完ncurses图形库,接下来进行屏幕绘图,我初始化屏幕效果图见下图所示:先是外围边框,然后是蛇“@”和食物“*”。

废话不多说,上代码吧。


首先是头文件 snake.h的代码:由于在纯文本模式下编程以及本人英语水平有限,可能有的注释比较别扭。

/* Game: snake		version: 1.0	date:2011/08/22
 * Author: Dream Fly
 * filename: snake.h
 */

#define SNAKE_SYMBOL	'@'		/* snake body and food symbol */
#define FOOD_SYMBOL		'*'
#define MAX_NODE		30		/* maximum snake nodes */
#define DFL_SPEED		50		/* snake default speed */
#define TOP_ROW		5			/* top_row */
#define BOT_ROW		LINES - 1
#define LEFT_EDGE	0
#define RIGHT_EDGE	COLS - 1

typedef struct node			/* Snake_node structure */
{
	int x_pos;
	int y_pos;
	struct node *prev;
	struct node *next;
} Snake_Node;

struct position				/* food position structure */
{
	int x_pos;
	int y_pos;
} ;
void Init_Disp();			/* init and display the interface */
void Food_Disp();			/* display the food position */
void Wrap_Up();				/* turn off the curses */
void Key_Ctrl();			/* using keyboard to control snake */
int set_ticker(int n_msecs);/* ticker */

void DLL_Snake_Create();	/* create double linked list*/
void DLL_Snake_Insert(int x, int y);	/* insert node */
void DLL_Snake_Delete_Node();	/* delete a node */
void DLL_Snake_Delete();		/* delete all the linked list */

void Snake_Move();			/* control the snake move and judge */
void gameover(int n);		/* different n means different state */
接下来是初始化界面图形的子函数:
/* Function: Init_Disp()
 * Usage: init and display the interface
 * Return: none
 */
void Init_Disp()
{
	char wall = ' ';
	int i, j;
	initscr();
	cbreak();				/* put termial to CBREAK mode */
	noecho();
	curs_set(0);			/* set cursor invisible */

	/* display some message about title and wall */
	attrset(A_NORMAL);		/* set NORMAL first */
	attron(A_REVERSE);		/* turn on REVERSE to display the wall */
	for(i = 0; i < LINES; i++)
	{
		mvaddch(i, LEFT_EDGE, wall);
		mvaddch(i, RIGHT_EDGE, wall);
	}
	for(j = 0; j < COLS; j++)
	{
		mvaddch(0, j, '=');
		mvaddch(TOP_ROW, j, wall);
		mvaddch(BOT_ROW, j, wall);
	}
	attroff(A_REVERSE);		/* turn off REVERSE */
	mvaddstr(1, 2, "Game: snake    version: 1.0    date: 2011/08/22");
	mvaddstr(2, 2, "Author: Dream Fly	Blog: blog.csdn.net/jjzhoujun2010");
	mvaddstr(3, 2, "Usage: Press 'f' to speed up, 's' to speed down,'q' to quit.");
    mvaddstr(4, 2, "       Nagivation key controls snake moving.");
	refresh();
}

/* Function: Food_Disp()
 * Usage: display food position
 * Return: none
 */
void Food_Disp()
{
	srand(time(0));
	food.x_pos = rand() % (COLS - 2) + 1;
	food.y_pos = rand() % (LINES - TOP_ROW - 2) + TOP_ROW + 1;
	mvaddch(food.y_pos, food.x_pos, FOOD_SYMBOL);/* display the food */
	refresh();
}

/* Function: DLL_Snake_Create()
 * Usage: create double linked list, and display the snake first node
 * Return: none
 */
void DLL_Snake_Create()
{
	Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
	head = (Snake_Node *)malloc(sizeof(Snake_Node));
	tail = (Snake_Node *)malloc(sizeof(Snake_Node));
	if(temp == NULL || head == NULL || tail == NULL)
		perror("malloc");
	temp->x_pos = 5;
	temp->y_pos = 10;
	head->prev =NULL;
	tail->next = NULL;
	head->next = temp;
	temp->next = tail;
	tail->prev = temp;
	temp->prev = head;
	mvaddch(temp->y_pos, temp->x_pos, SNAKE_SYMBOL);
	refresh();
}

  3.接下来就是蛇的移动问题,这个是核心部分以及最困难的设计部分了,我采用的是蛇用双向链表的结构来构造出来,分别有一个head 和tail指针,用来添加和删除元素。这里若要实现移动的话(未碰到食物前),就是在链表的头部(head的下一个)插入一个新元素,记录下此时的坐标,用mvaddch(y,x,c)函数添加蛇的图形'@',与此同时,在链表尾部(tail的前一个)删除一个节点,同时这里的坐标用mvaddch(y,x, ' ')添加了' '空白字符,实现删除效果,最后加上refresh(). 这样就可以看到蛇在“移动”了。当然,要是碰到食物的话,尾部节点处就不用删除,达到增长长度的效果。

  那么,接下来的问题是:如何触发蛇的移动呢?如何实现均匀移动以及通过按键 ‘f’ 或 's' 改变运动速度呢?这里我采用的是信号计时中断调用的函数  signal(SIGALRM, Snake_Move) 和 间隔计数器 来实现,通过产生相同间隔的时间片段来不断地调用Snake_Move()函数来执行相应的功能。加减速的功能是通过设定其他变量ttm, ttg来实现再此基本计数器上面再次分频的效果来加减速,ttm, ttg 越大,减速越明显,反之则相反效果。  具体的间隔计数器的函数设计见下:(参考了以上所提书本上的函数)

/* Function: set_ticker(number_of_milliseconds)
 * Usage: arrange for interval timer to issue SIGALRM's at regular intervals
 * Return: -1 on error, 0 for ok
 * arg in milliseconds, converted into whole seconds and microseconds
 * note: set_ticker(0) turns off ticker
 */
int set_ticker(int n_msecs)
{
	struct itimerval new_timeset;
	long n_sec, n_usecs;

	n_sec = n_msecs / 1000;					/* int second part */
	n_usecs = (n_msecs % 1000) * 1000L;		/* microsecond part */

	new_timeset.it_interval.tv_sec = n_sec;	/* set reload */
	new_timeset.it_interval.tv_usec = n_usecs;

	new_timeset.it_value.tv_sec = n_sec;	/* set new ticker value */
	new_timeset.it_value.tv_usec = n_usecs;

	return setitimer(ITIMER_REAL, &new_timeset, NULL);
}

  蛇的移动的函数代码如下:

void Snake_Move()
{
	static int length = 1;		/* length of snake */
	int Length_Flag = 0;		/* default snake's length no change */
	int moved = 0;
	signal(SIGALRM, SIG_IGN);
	/* judge if the snake crash the wall */
	if((head->next->x_pos == RIGHT_EDGE-1 && x_dir == 1) 
		|| (head->next->x_pos == LEFT_EDGE+1 && x_dir == -1)
		|| (head->next->y_pos == TOP_ROW+1 && y_dir == -1)
		|| (head->next->y_pos == BOT_ROW-1 && y_dir == 1))
	{
		gameover(1);
	}
	/* judge if the snake crash itself */
	if(mvinch(head->next->y_pos + y_dir, head->next->x_pos + x_dir) == '@')
		gameover(2);

	if(ttm > 0 && ttg-- == 1)
	{
		/* snake moves */
		DLL_Snake_Insert(head->next->x_pos + x_dir, head->next->y_pos + y_dir);
		ttg = ttm;		/* reset */
		moved = 1;		/* snake can move */
	}
	if(moved)
	{
		/* snake eat the food */
		if(head->next->x_pos == food.x_pos && head->next->y_pos == food.y_pos)
		{
			Length_Flag = 1;
			length++;
			/* Mission Complete */
			if(length >= MAX_NODE)
				gameover(0);
			/* reset display the food randomly */
			Food_Disp();
		}
		if(Length_Flag == 0)
		{
			/* delete the tail->prev node */
			mvaddch(tail->prev->y_pos, tail->prev->x_pos, ' ');
			DLL_Snake_Delete_Node();
		}
		mvaddch(head->next->y_pos, head->next->x_pos, SNAKE_SYMBOL);
		refresh();
	}
	signal(SIGALRM, Snake_Move);
}

主要函数的实现就是这些,以下贴上完整的源代码供大家参考,或者去这里下载:http://download.csdn.net/source/3540117

/* Game: snake		version: 1.0	date:2011/08/22
 * Author: Dream Fly
 * filename: snake.h
 */

#define SNAKE_SYMBOL	'@'		/* snake body and food symbol */
#define FOOD_SYMBOL		'*'
#define MAX_NODE		30		/* maximum snake nodes */
#define DFL_SPEED		50		/* snake default speed */
#define TOP_ROW		5			/* top_row */
#define BOT_ROW		LINES - 1
#define LEFT_EDGE	0
#define RIGHT_EDGE	COLS - 1

typedef struct node			/* Snake_node structure */
{
	int x_pos;
	int y_pos;
	struct node *prev;
	struct node *next;
} Snake_Node;

struct position				/* food position structure */
{
	int x_pos;
	int y_pos;
} ;
void Init_Disp();			/* init and display the interface */
void Food_Disp();			/* display the food position */
void Wrap_Up();				/* turn off the curses */
void Key_Ctrl();			/* using keyboard to control snake */
int set_ticker(int n_msecs);/* ticker */

void DLL_Snake_Create();	/* create double linked list*/
void DLL_Snake_Insert(int x, int y);	/* insert node */
void DLL_Snake_Delete_Node();	/* delete a node */
void DLL_Snake_Delete();		/* delete all the linked list */

void Snake_Move();			/* control the snake move and judge */
void gameover(int n);		/* different n means different state */

/* Filename: snake.c 	version:1.0 	date: 2011/08/22
 * Author: Dream Fly 	blog: blog.csdn.net/jjzhoujun2010
 * Usage: 'f' means speed up, 's' means speed down, 'q' means quit;
 * Navigation key controls the snake moving. 
 * Compile: gcc snake.c -lncurses -o snake
 */

#include<stdio.h>
#include<stdlib.h>
#include<ncurses.h>
#include<sys/time.h>
#include<signal.h>
#include"snake.h"

struct position food;		/* food position */
Snake_Node *head, *tail;	/* double linked list's head and tail */
int x_dir = 1, y_dir = 0;	/* init dirction of the snake moving */
int ttm = 5, ttg = 5;			/* two timers defined to control speed */

void main(void)
{
	Init_Disp();			/* init and display the interface */
	Food_Disp();			/* display food */
	DLL_Snake_Create();		/* create double linked list and display snake*/
	signal(SIGALRM, Snake_Move);
	set_ticker(DFL_SPEED);
	Key_Ctrl();				/* using keyboard to control snake */
	Wrap_Up();				/* turn off the curses */
}

/* Function: Init_Disp()
 * Usage: init and display the interface
 * Return: none
 */
void Init_Disp()
{
	char wall = ' ';
	int i, j;
	initscr();
	cbreak();				/* put termial to CBREAK mode */
	noecho();
	curs_set(0);			/* set cursor invisible */

	/* display some message about title and wall */
	attrset(A_NORMAL);		/* set NORMAL first */
	attron(A_REVERSE);		/* turn on REVERSE to display the wall */
	for(i = 0; i < LINES; i++)
	{
		mvaddch(i, LEFT_EDGE, wall);
		mvaddch(i, RIGHT_EDGE, wall);
	}
	for(j = 0; j < COLS; j++)
	{
		mvaddch(0, j, '=');
		mvaddch(TOP_ROW, j, wall);
		mvaddch(BOT_ROW, j, wall);
	}
	attroff(A_REVERSE);		/* turn off REVERSE */
	mvaddstr(1, 2, "Game: snake    version: 1.0    date: 2011/08/22");
	mvaddstr(2, 2, "Author: Dream Fly	Blog: blog.csdn.net/jjzhoujun2010");
	mvaddstr(3, 2, "Usage: Press 'f' to speed up, 's' to speed down,'q' to quit.");
    mvaddstr(4, 2, "       Nagivation key controls snake moving.");
	refresh();
}

/* Function: Food_Disp()
 * Usage: display food position
 * Return: none
 */
void Food_Disp()
{
	srand(time(0));
	food.x_pos = rand() % (COLS - 2) + 1;
	food.y_pos = rand() % (LINES - TOP_ROW - 2) + TOP_ROW + 1;
	mvaddch(food.y_pos, food.x_pos, FOOD_SYMBOL);/* display the food */
	refresh();
}

/* Function: DLL_Snake_Create()
 * Usage: create double linked list, and display the snake first node
 * Return: none
 */
void DLL_Snake_Create()
{
	Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
	head = (Snake_Node *)malloc(sizeof(Snake_Node));
	tail = (Snake_Node *)malloc(sizeof(Snake_Node));
	if(temp == NULL || head == NULL || tail == NULL)
		perror("malloc");
	temp->x_pos = 5;
	temp->y_pos = 10;
	head->prev =NULL;
	tail->next = NULL;
	head->next = temp;
	temp->next = tail;
	tail->prev = temp;
	temp->prev = head;
	mvaddch(temp->y_pos, temp->x_pos, SNAKE_SYMBOL);
	refresh();
}

/* Function: Snake_Move()
 * Usage: use Navigation key to control snake moving, and judge
 * if the snake touch the food.
 * Return:
 */
void Snake_Move()
{
	static int length = 1;		/* length of snake */
	int Length_Flag = 0;		/* default snake's length no change */
	int moved = 0;
	signal(SIGALRM, SIG_IGN);
	/* judge if the snake crash the wall */
	if((head->next->x_pos == RIGHT_EDGE-1 && x_dir == 1) 
		|| (head->next->x_pos == LEFT_EDGE+1 && x_dir == -1)
		|| (head->next->y_pos == TOP_ROW+1 && y_dir == -1)
		|| (head->next->y_pos == BOT_ROW-1 && y_dir == 1))
	{
		gameover(1);
	}
	/* judge if the snake crash itself */
	if(mvinch(head->next->y_pos + y_dir, head->next->x_pos + x_dir) == '@')
		gameover(2);

	if(ttm > 0 && ttg-- == 1)
	{
		/* snake moves */
		DLL_Snake_Insert(head->next->x_pos + x_dir, head->next->y_pos + y_dir);
		ttg = ttm;		/* reset */
		moved = 1;		/* snake can move */
	}
	if(moved)
	{
		/* snake eat the food */
		if(head->next->x_pos == food.x_pos && head->next->y_pos == food.y_pos)
		{
			Length_Flag = 1;
			length++;
			/* Mission Complete */
			if(length >= MAX_NODE)
				gameover(0);
			/* reset display the food randomly */
			Food_Disp();
		}
		if(Length_Flag == 0)
		{
			/* delete the tail->prev node */
			mvaddch(tail->prev->y_pos, tail->prev->x_pos, ' ');
			DLL_Snake_Delete_Node();
		}
		mvaddch(head->next->y_pos, head->next->x_pos, SNAKE_SYMBOL);
		refresh();
	}
	signal(SIGALRM, Snake_Move);
}

/* Function: set_ticker(number_of_milliseconds)
 * Usage: arrange for interval timer to issue SIGALRM's at regular intervals
 * Return: -1 on error, 0 for ok
 * arg in milliseconds, converted into whole seconds and microseconds
 * note: set_ticker(0) turns off ticker
 */
int set_ticker(int n_msecs)
{
	struct itimerval new_timeset;
	long n_sec, n_usecs;

	n_sec = n_msecs / 1000;					/* int second part */
	n_usecs = (n_msecs % 1000) * 1000L;		/* microsecond part */

	new_timeset.it_interval.tv_sec = n_sec;	/* set reload */
	new_timeset.it_interval.tv_usec = n_usecs;

	new_timeset.it_value.tv_sec = n_sec;	/* set new ticker value */
	new_timeset.it_value.tv_usec = n_usecs;

	return setitimer(ITIMER_REAL, &new_timeset, NULL);
}

/* Function: Wrap_Up()
 * Usage: turn off the curses
 * Return: none
 */
void Wrap_Up()
{
	set_ticker(0);		/* turn off the timer */
	getchar();
	endwin();
	exit(0);
}

/* Function: Key_Ctrl()
 * Usage: using keyboard to control snake action; 'f' means speed up,
 * 's' means speed down, 'q' means quit, navigation key control direction.
 * Return: none
 */
void Key_Ctrl()
{
	int c;
	keypad(stdscr, true);		/* use little keyboard Navigation Key */
	while(c = getch(), c != 'q')
	{
		if(c == 'f')
		{
			if(ttm == 1)
				continue;
			ttm--;
		}
		else if(c == 's')
		{
			if(ttm == 8)
				continue;
			ttm++;
		}
		if(c == KEY_LEFT)
		{
			if(tail->prev->prev->prev != NULL && x_dir == 1 && y_dir == 0)
				continue; /* it can't turn reverse when snake have length */
			x_dir = -1;
			y_dir = 0;
		}
		else if(c == KEY_RIGHT)
		{
			if(tail->prev->prev->prev != NULL && x_dir == -1 && y_dir == 0)
				continue;
			x_dir = 1;
			y_dir = 0;
		}
		else if(c == KEY_UP)
		{
			if(tail->prev->prev->prev != NULL && x_dir == 0 && y_dir == 1)
				continue;
			x_dir = 0;
			y_dir = -1;
		}
		else if(c == KEY_DOWN)
		{
			if(tail->prev->prev->prev != NULL && x_dir == 0 && y_dir == -1)
				continue;
			x_dir = 0;
			y_dir = 1;
		}
	}
}

/* Function: DLL_Snake_Insert(int x, int y)
 * Usage: Insert node in the snake.
 * Return: none
 */
void DLL_Snake_Insert(int x, int y)
{
	Snake_Node *temp = (Snake_Node *)malloc(sizeof(Snake_Node));
	if(temp == NULL)
		perror("malloc");
	temp->x_pos = x;
	temp->y_pos = y;
	temp->prev = head->next->prev;
	head->next->prev = temp;
	temp->next = head->next;
	head->next = temp;
}

/* Function: gameover(int n)
 * Usage: gameover(0) means Mission Completes; gameover(1) means crashing
 * the wall; gameover(2) means crash itself.
 * Return: none
 */
void gameover(int n)
{
	switch(n)
	{
		case 0: 
			mvaddstr(LINES / 2, COLS / 3 - 4, "Mission Completes,press any key to exit.\n");
			break;
		case 1:
			mvaddstr(LINES/2, COLS/3 - 4, "Game Over, crash the wall,press any key to exit.\n");
			break;
		case 2:
			mvaddstr(LINES/2, COLS/3 - 4, "Game Over, crash yourself,press any key to exit.\n");
			break;
		default:
			break;
	}
	refresh();
	/* delete the whole double linked list */
	DLL_Snake_Delete();
	Wrap_Up();
}

/* Function: DLL_Snake_Delete_Node()
 * Usage: delete a tail node, not the whole linked list
 * Return: none
 */
void DLL_Snake_Delete_Node()
{
	Snake_Node *temp;
	temp = tail->prev;
	tail->prev = tail->prev->prev;
	temp->prev->next = tail;
	free(temp);
}

/* Function: DLL_Snake_Delete()
 * Usage: delete the whole double linked list
 * Return: none
 */
void DLL_Snake_Delete()
{
	while(head->next != tail)
		DLL_Snake_Delete_Node();
	head->next = tail->prev = NULL;
	free(head);
	free(tail);
}

  通过本程序可以加强自己对信号间隔计数器的理解,以及终端图形编程的理解和了解设计的此类游戏的一般思路,也实现了我大一学习C语言所想要实现的想法。本人第一次在CSDN上面把自己的一些编程想法写的比较详细,不足之处还请指出,共同讨论。

  参考资料:《Unix/Linux编程实践教程》    (美)Bruce Molay 著       杨宗源   黄海涛   译               清华大学出版社

http://note.sdo.com/my#!note/preview/xJ29Q~jAYzOpnM01Y0004K       curses库的使用

http://blog.sina.com.cn/s/blog_4c3b26e10100sd7b.html   贪吃蛇双链表模型



没有更多推荐了,返回首页