Linux Curses编程实现贪吃蛇
curses库 简单而言,提供UNIX中多种终端 操作光标和显示字符 的接口。我们常见的vi就是使用curses实现的。现在一般都用ncurses库。
Linux下curses函数库 Linux curses库使用 这两篇文章很详细地介绍了curses,在此就不详细介绍了。
1.ubuntu安装curses函数库
$sudo apt-get install ncurses-dev
用curses库,编译程序:
$gcc program.c -o program -lcurses
2.工作原理
curses工作在屏幕,窗口和子窗口之上。屏幕是设备全部可用显示面积(对终端是该窗口内所有可用字符位置),窗口与具体例程有关。如基本的stdscr窗口等。
curses使用两个数据结构映射终端屏幕,stdscr和curscr。stdscr是“标准屏幕”(逻辑屏幕),在curses函数库产生输出时就刷新,是默认输出窗口(用户不会看到该内容)。curscr是“当前屏幕”(物理屏幕),在调用refresh函数是,函数库会将curscr刷新为stdscr的样子。
3.常用函数
初始化相关:
initscr(); //curses程序初始化必须
cbreak(); //关闭字符流的缓冲
nonl(); //输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字元 ( 如 \n ).
noecho(); //关闭字符回显
keypad(stdscr,TRUE); //可以使用键盘上的一些特殊字元, 如上下左右
refresh(); //刷新物理屏幕
int endwin(void); //关闭所有窗口 移动光标、输出相关:
int move(int new_y, int new_x); //移动stdcsr的光标位置
addch(ch); //显示某个字元.
mvaddch(y,x,ch); //在(x,y) 上显示某个字元.
addstr(str); //显示一串字串.
mvaddstr(y,x,str); //在(x,y) 上显示一串字串.
printw(format,str); //类似 printf() , 以一定的格式输出至萤幕.
mvprintw(y,x,format,str); //在(x,y) 位置上做 printw 的工作 前缀w用于窗口(添加一个WINDOWS指针参数),mv用于光标移动(在该位置执行操作addch或printw)(添加两个坐标值参数),mvw用于在窗口(如stdscr)中移动光标。组成如下函数:
addch, waddch, mvaddch, mvwaddch
printw, wprintw, mvprintw, mvwprintw 屏幕读取:没用到就不写了
键盘输入:
//与标准io库的getchar, gets, scanf类似
int getch();
int getstr(char *string);
int getnstr(char *string, int number); //建议使用
scanw(format,&arg1,&arg2...): //如同 scanf, 从键盘读取一串字元.
清除屏幕;
int erase(void); //在屏幕的每个位置写上空白字符
int clear(void); //使用一个终端命令来清除整个屏幕
用到的curses函数
贪吃蛇,要点在于1.蛇的实现;2.移动和按键侦听的同步进行;3.碰撞的检测。
1.蛇的实现,联系到curses的特点,想到两种办法,一种是队列存储每个节点,还有一种是用头-尾-拐弯节点坐标实现。
想到蛇的长度不可能太长,所以用队列(循环数组)实现,添加头结点,删除尾节点。为了方便就没有实现循环数组,采用了一个大数组。
2.两种动作同时进行,两种方案:1可以用异步编程,涉及到信号,阻塞,可重入问题。2用多线程,或多进程,涉及到变量的共享,互斥锁等。
3.碰撞检测。为了效率可以用一个线程把它抽取出来,如果代码时间复杂度不高,可以直接写。
用信号实现按键和同时移动。又有两种方案,一种把移动放入定时信号处理函数,主程序用输入阻塞(本文采用)。一种是把移动放入主程序循环,把输入放入信号处理函数。
curses的物理屏幕刷新是根据逻辑屏幕的字符,逻辑屏幕那一块没有变动,实际屏幕也不变,所以不会像gdi编程画面 闪动,耗费资源也少。
因此可以想到每次刷新只让蛇动一个格子,蛇头,和蛇尾 局部刷新就行了。
FC游戏的卷轴移动,也是类似的道理,内存小,长时间不动的背景就让他不经过程序,也同时省了cpu。
卡马克就是根据这种方法设计出当时低速PC上第一个能卷轴移动的游戏引擎。
tanchishe.c
#include "tanchishe.h"
//2015 10 06
//#define DEBUG__
void gogo(int );
void move_here_show(int );
int collision_detec(void);
void gameover(int );
sigjmp_buf jmpbuffer; int main()
{
//函数间跳跃函数,保存进程信息,设置跳回点。(信号版,跳回后自动恢复信号屏蔽mask)
//程序首次运行略过if内代码,游戏结束调用siglongjmp(jmpbuffer, int)跳回
if(sigsetjmp(jmpbuffer,) != ){ }
init_tcs();//变量的初始化,和屏幕的初始化显示,和蛇与食物的显示 #ifndef DEBUG__
signal(SIGALRM,gogo); //设置定时信号的处理函数,用于蛇的移动,调试时,手动调用
set_ticker(ticker); //每ticher毫秒 产生信号
#endif
while(){
int i =getch();
switch (i) {
case _KEY_UP :
if(real_g_forward!=_KEY_DOWN &&real_g_forward!=_KEY_UP) //防止回跑
{g_key_forward=_KEY_UP;}break;
case _KEY_DOWN :
if(real_g_forward!=_KEY_UP&&real_g_forward!=_KEY_DOWN )
{g_key_forward=_KEY_DOWN ;}break;
case _KEY_LEFT :
if(real_g_forward!=_KEY_RIGHT&&real_g_forward!=_KEY_LEFT)
{g_key_forward=_KEY_LEFT;}break;//delay_real=&delay;
case _KEY_RIGHT:
if(real_g_forward!=_KEY_RIGHT&&real_g_forward!=_KEY_LEFT)
{g_key_forward=_KEY_RIGHT;}break;
#ifdef DEBUG__
case 'g':gogo();//raise(SIGALRM);
#endif
}
} endwin();
return ;
} void gogo(int signum)//zouba
{
//保存可能随时会变的全局变量g_key_forward(每次按下的希望前进的方向)
int temp_g_forward=g_key_forward;
move_here_show(temp_g_forward);//按照指定方向移动
collision_detec();//碰撞检测
} void move_here_show(int forward)
{
switch (forward) {
case _KEY_UP: --row; break; //蛇头结点按照指定方向的移动
case _KEY_DOWN : ++row; break;
case _KEY_LEFT: --col; break;
case _KEY_RIGHT: ++col; break;
default:return;
}
//此时更新蛇真正的前进方向
//因为此时运动才完毕,且real_g_forward随时都在键盘监听中被使用
real_g_forward=forward; //mvaddchstr(snake_s1.snake_body[last_s_node].row,
// snake_s1.snake_body[last_s_node].col,LSBLANK);
//屏幕上删除(局部擦除)蛇最后一个节点
mvaddstr(snake_s1.snake_body[last_s_node].row,
snake_s1.snake_body[last_s_node].col,BLANK);
//在队列中新增蛇移动后的头节点 坐标(蛇头始终增加到 数组的最小未用单元 (队列尾))
snake_s1.snake_body[++first_s_node].row= row;
snake_s1.snake_body[first_s_node].col = col;
//mvaddchstr(row,col,NOB);
mvaddstr(row,col,NODE); //在屏幕上显示出蛇头新的坐标 #ifdef DEBUG__
//调试用的步数,可以走MAX_SIZE步,大约1800秒,之前肯定让他死掉,这就是命
printw("%d",first_s_node+-INIT_NODE_SIZE);
#endif
move(LINES-,);refresh();
last_s_node++; //在队列中减去蛇移动后的尾节点 坐标
}
int collision_detec()
{
int i;
//检测是否食物
if(col== food_col&& row ==food_row){
srand((unsigned)time(NULL));
food_col = +rand()%(COLS-);
food_row = +rand()%(LINES-); test_dupe://防止新生成的食物在蛇身上……
for(i=last_s_node;i<first_s_node;++i){
if(food_col == snake_s1.snake_body[i].col)
if(food_row == snake_s1.snake_body[i].row ){
food_col = +rand()%(COLS-);
food_row = +rand()%(LINES-);
goto test_dupe;
}
}
//mvaddchstr(food_row,food_col,FOOD);
//这里不改,centos也正常显示,为了保险,也改成单字节版
mvaddstr(food_row,food_col,FOOD);//
last_s_node--;//muhaha //检测是否升级 ,右边数组对应level(下标)升级所需要的食物
if(++eat_num >=times_to_upgrade_each_level[level]){
//升级后定时器减少 每级应减少的时间,更新等级
ticker -=delay_sub_each_level[level++];//注意此处是累减,
//更新定时器,信号函数触发加快,移动速度加快
set_ticker(ticker);
}
score++;
max_score =max_score>score?max_score:score;
attrset(A_REVERSE); //反色下面的文字
mvprintw(,COLS/ -,"Score:%d Max Score:%d Level:%d",score,max_score,level);
attrset(A_NORMAL);
//move(LINES-1,0);refresh();//没有也可以,但是level那会闪 }
else{
//检测是否撞墙
if(col == LEFT_WALL||col == RIGHT_WALL||row == TOP_WALL||row == BUTT_WALL)
gameover();
//检测是否自攻
for(i=last_s_node;i<first_s_node-;++i){
if(col == snake_s1.snake_body[i].col)
if(row == snake_s1.snake_body[i].row )
gameover();
}
}
return ;
}
void gameover(int type)
{
set_ticker();
mvprintw(LINES/-,COLS/ -, " Game Over! ");
char * S= " Your Score is %d!";
if(score>max_score)
mvprintw(LINES/+,COLS/ -, " You Cut the Record!!!");
else S= " Your Score is %d! Too Young!!!";
if(score>)S = " Your Score is %d! Nice!!!";
mvprintw(LINES/,COLS/ -, S,score);
mvprintw(LINES/+,COLS/ -, "Press <%c> Play Again! <%c> to Quit!",AGAIN_KEY,QUIT_KEY);
refresh();
free(snake_s1.snake_body);
int geta;
while(){
if((geta =getch()) == AGAIN_KEY){
erase();
refresh();
siglongjmp(jmpbuffer, type);//game over 带着信息跳到main开始准备好的套子里……
}
else if(geta == QUIT_KEY)
exit();
}
} int set_ticker(int n_msecs) //把 以毫秒为单位的时间 转换成对应的 定时器
{
struct itimerval new_timeset;
long n_sec,n_usecs; n_sec = n_msecs/;
n_usecs=(n_msecs%)*1000L; new_timeset.it_interval.tv_sec=n_sec;
new_timeset.it_interval.tv_usec=n_usecs; new_timeset.it_value.tv_sec = n_sec;
new_timeset.it_value.tv_usec= n_usecs; return setitimer(ITIMER_REAL,&new_timeset,NULL);
}
init_tcs.c 初始化变量和屏幕的函数
#include "tanchishe.h"
int row=; //the head
int col=; int g_key_forward=_KEY_RIGHT;
int real_g_forward=_KEY_RIGHT;
int food_row =;
int food_col =;
int eat_num=;
int ticker = ;//ms struct snake_s snake_s1;
int last_s_node=;
int first_s_node; int score =;
int max_score=;//do not init in retry!
int level=;
int delay_sub_each_level[MAX_LEVEL]={};
int times_to_upgrade_each_level[MAX_LEVEL]={}; void init_tcs(void)
{
initscr();
cbreak(); //关闭缓冲
nonl();
noecho(); //关闭回显
intrflush(stdscr,FALSE);
keypad(stdscr,TRUE);
curs_set(); //光标不可见 row =LINES/-;
col =COLS/-;
eat_num=;
ticker = ;//ms
score =;
g_key_forward=_KEY_RIGHT;
real_g_forward=_KEY_RIGHT;
last_s_node=;
//first_s_node;
level=; //配置每升一级需要吃的食物,和每升一级蛇的快慢,也就是ticker的大小
int sum=delay_sub_each_level[]=;//EVERY_LEVEL_SUB_TIME;
int i;
times_to_upgrade_each_level[]=TIMES_TO_UPGRADE_EACH_LEVEL;
for(i=;i<MAX_LEVEL;++i){
times_to_upgrade_each_level[i]=times_to_upgrade_each_level[i-]+(TIMES_TO_UPGRADE_EACH_LEVEL); if(sum<ticker-){
if(i<)
delay_sub_each_level[i] =;
else if(i<)
delay_sub_each_level[i] =;
else if(i <)
delay_sub_each_level[i] =;
else
delay_sub_each_level[i] =; }
else delay_sub_each_level[i]=delay_sub_each_level[i-];
sum =sum+delay_sub_each_level[i]; }
//绘制边框
attrset(A_REVERSE);
for(i=;i<LINES;++i){
mvaddch(i,,' ');
mvaddch(i,LEFT_WALL,' ');
mvaddch(i,RIGHT_WALL,' ');
mvaddch(i,COLS-,' ');
}
for(i=;i<COLS;++i){
mvaddch(,i,' ');
mvaddch(LINES-,i,' ');
} mvprintw(,COLS/ -,"Score:%d Max Score:%d Level:%d",score,max_score,level);
mvprintw(LINES-,COLS/ -,"Eledim Walks the Earth,%c%c%c%c to Move",_KEY_UP, _KEY_DOWN, _KEY_LEFT, _KEY_RIGHT);
refresh();
attrset(A_NORMAL); //创建蛇的节点容器
snake_s1.snake_body = malloc( SNAKE_MAX_SIZE *sizeof(struct post)) ;
if(snake_s1.snake_body == NULL)
mvprintw(,,"malloc error");
memset(snake_s1.snake_body,,SNAKE_MAX_SIZE*sizeof(struct post)); srand((unsigned)time(NULL)); //no this ,rand every time return same num
#ifdef DEBUG__
food_row = LINES/ -;
food_col =COLS/ +;
#else
food_row = +rand()%(LINES-);
food_col =+rand()%(COLS-);
#endif
//初始化蛇在容器中的坐标,和显示
//snake_s1.head.row=row;
//snake_s1.head.col=col;
snake_s1.node_num =INIT_NODE_SIZE;
first_s_node=snake_s1.node_num-; for(i=;i<snake_s1.node_num;++i){
snake_s1.snake_body[i].row=row;
snake_s1.snake_body[i].col=col-snake_s1.node_num++i;
//mvaddchstr(row,col-i,NOB);
mvaddstr(row,col-i,NODE);
}
//show food
//mvaddchstr(food_row,food_col,FOOD);
mvaddstr(food_row,food_col,FOOD);
move(LINES-,);refresh();
}
tanchishe.h
#ifndef TANCHISHE_H
#define TANCHISHE_H #include <stdio.h>
#include <curses.h>
#include <time.h>
#include <sys/time.h> //timeval
#include <unistd.h> //usleep
#include <sys/select.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h> //memset
#include <setjmp.h> #define LSBLANK L" "
#define BLANK " " #define INIT_NODE_SIZE 5
#define SNAKE_MAX_SIZE 8192
#define LEFT_WALL 1
#define RIGHT_WALL (COLS-2)
#define TOP_WALL 0
#define BUTT_WALL (LINES-1)
#define NOB L"#"//
#define LSFOOD L"O"
#define NODE "#"//instead of L"#"
#define FOOD "O"
#define MAX_LEVEL 100
#define SUB_TIME_EACH_LEVEL 6
#define TIMES_TO_UPGRADE_EACH_LEVEL 6 #define _KEY_UP 'w'
#define _KEY_DOWN 's'
#define _KEY_LEFT 'a'
#define _KEY_RIGHT 'd'
#define AGAIN_KEY 'A'
#define QUIT_KEY 'Q'
struct post{
int row;
int col;
};
struct snake_s{
int node_num;
struct post head;
struct post* snake_body; }; //for all
extern struct snake_s snake_s1;
extern int row;
extern int col;
//extern struct timeval delay; extern int real_g_forward;
extern int g_key_forward;
extern int food_row;
extern int food_col ;
extern int eat_num;
extern int score ;
extern int ticker ;//ms extern int last_s_node;
extern int first_s_node; extern int max_score;//do not init in retry!
extern int level;
extern int delay_sub_each_level[MAX_LEVEL];
extern int times_to_upgrade_each_level[MAX_LEVEL];
//extern int ticker_for_col;
//extern int ticker_for_row; int set_ticker(int n_msecs);
void init_tcs(void); #endif
编译:
cc tanchishe.c init_tcs.c -lcurses
运行:
由于curses是面向终端的接口(虚拟终端也包含在内),与图形库无关,所以程序在远程ssh工具上也可以完美运行,就像vim一样。
可以考虑继续添加的特性:多人游戏 路障 吃字母 网络化 存档文件 用户记录 暂停(ctrl+z)
Linux Curses编程实现贪吃蛇的更多相关文章
- [C入门 - 游戏编程系列] 贪吃蛇篇(六) - 蛇实现
这一篇是关于设置蛇的属性的,接上一篇(五). 设置蛇的速度,很简单,只要不是负数就行了. void SNK_SetSnakeSpeed(Snake *snake, int speed) { ) sna ...
- [C入门 - 游戏编程系列] 贪吃蛇篇(四) - 食物实现
由于食物是贪吃蛇游戏中最简单的一部分,而且和其他部分关联性不强,基本上是一个独立的部分,所以我打算先实现它. 我的想法是食物必须在世界中才能被创造出来,也就是说,先有世界再有食物,所以我得先判断世界是 ...
- [C入门 - 游戏编程系列] 贪吃蛇篇(三) - 蛇定义
蛇是这个游戏的主角,要实现的功能也是最复杂的一个.因为蛇不止有属性,还有行为.它会动,还会吃东西,还会长大!而且还会死!这是很要命的.我一向看不懂复杂的代码,也写不出复杂的代码.所以对于蛇,我很纠结, ...
- [C入门 - 游戏编程系列] 贪吃蛇篇(二) - 食物定义
游戏中的食物没有那么多复杂属性,特别是贪吃蛇游戏中,我把食物看待的很简单: 1. 它必须属于世界,才能出现在世界.不可能一个不属于世界的食物,出现在世界中:但是可能存在着一个食物,它属于世界,但是却没 ...
- [C入门 - 游戏编程系列] 贪吃蛇篇(一) - 世界定义
每个游戏都有一个很明确的目的或者说游戏主题,贪吃蛇的目的很明确:蛇找到并吃掉食物.只有目的是很无聊的,算不上一个好游戏.所以设计者增加了创意:1. 吃掉食物后蛇会增长:2. 吃掉食物后分数会增加.有些 ...
- [C入门 - 游戏编程系列] 贪吃蛇篇(五) - 蛇实现
因为已经写了食物的实现,所以我不知道到底是该先写世界的实现还是蛇的实现.因为世界就是一个窗口,可以立刻在世界中看到食物的样子,对于大多数人来说,如果写完代码立刻就能看到效果,那就再好不过了.可是,我最 ...
- Linux平台下贪吃蛇游戏的运行
1.参考资料说明: 这是一个在Linux系统下实现的简单的贪吃蛇游戏,同学找帮忙,我就直接在Red Hat中调试了一下,参考的是百度文库中"maosuhan"仁兄的文章,结合自己的 ...
- JavaScript面向对象编程小游戏---贪吃蛇
1 面向对象编程思想在程序项目中有着非常明显的优势: 1- 1 代码可读性高.由于继承的存在,即使改变需求,那么维护也只是在局部模块 1- 2 维护非常方便并且成本较低. 2 这个demo是采用了 ...
- JS高级---面向对象的编程思想(贪吃蛇梳理)
面向对象的编程思想(贪吃蛇梳理) 模拟贪吃蛇游戏,做的项目 地图: 宽,高,背景颜色,因为小蛇和食物都是相对于地图显示的, 这里小蛇和食物都是地图的子元素, 随机位置显示, 脱离文档流的, 地图也需要 ...
随机推荐
- linux-centos挂载新硬盘操作
类似的文章网上已经有很多,这里是记录重要操作的命令,精简流程 精简后的命令: fdisk -ldf -hfdisk /dev/vdbfdisk -l /dev/vdbmkfs -t ext4 /dev ...
- 理解事件捕获。在限制范围内拖拽div+吸附+事件捕获
一.实现的效果是在限制范围内拖拽div+吸附+事件捕获. 这里需要理解的是事件捕获,这个事件捕获也是为了兼容div在拖拽过程中,文本不被选中这个问题. 如此良辰美景,拖拽也可以很洒脱哈.先看看图, 二 ...
- 谈谈Angular关于$watch,$apply 以及 $digest的工作原理
这篇文章主要是面向那些刚开始学AngularJs和想要了解数据绑定(data-binding)是怎么工作的, 如果你已经熟悉如何使用angularjs了,我强烈建议你不用阅读了. angularjs使 ...
- Android注解支持(Support Annotations)
注解支持(Support Annotations) Android support library从19.1版本开始引入了一个新的注解库,它包含很多有用的元注解,你能用它们修饰你的代码,帮助你发现bu ...
- web开发性能优化---扩展性能篇
1.实现代码分离 一个成熟的软件开发团队一般都不会全然手写代码.这里讲的代码分离仅仅要是开发中用到的小技巧,通过底层框架+手工代码方式结合实现高速开发和高速扩展. Code目录内文件不同意改动,目录主 ...
- 基于VMware为CentOS 6.5配置两个网卡
为CentOS 6.5配置两块网卡,一块是eth0,一块是eth1,下面以master为例 1.选择“master”-->“编辑虚拟机设置”,如下所示 2.单击“添加”,如下 3.选择“网络适配 ...
- C语言实现双向链表删除节点、插入节点、双向输出等操作
#include<cstdio> #include<cstdlib> typedef struct DoubleLinkedList { int data; struct Do ...
- JSP-tag文件使用介绍
tag文件简单创建和使用 创建标记文件(.tag) 在标记文件中写入信息 在jsp文件中,引入标记文件 通过关键字调用标记文件 举例说明: 标记文件(show.tag) <%@ tag lang ...
- XC应用系列作品(Android应用)
XC系列应用,如真题园手机客户端1.1等应用已经分别在 360手机助手.腾讯应用宝.百度手机助手.小米应用商店.豌豆荚.应用汇.木蚂蚁等安卓市场平台上线了! 本页面的系列应用是本人的开发的一Andro ...
- SQL Server 2005恢复数据库详细图文教程
不少需要用到sql2005的程序,有很多新手还是会操作,这里写个详细的图文教程送个菜鸟们,高手请飘过.适用于独立主机的朋友使用,如果你还没安装,请按照这个教程来安装 SQL Server 2005图文 ...