正文
snake.c:
#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#define NUM 60
struct direct //用来表示方向的
{
int cx;
int cy;
};
typedef struct node //链表的结点
{
int cx;
int cy;
struct node *back;
struct node *next;
} node;
void initGame(); //初始化游戏
int setTicker(int); //设置计时器
void show(); //显示整个画面
void showInformation(); //显示游戏信息(前两行)
void showSnake(); //显示蛇的身体
void getOrder(); //从键盘中获取命令
void over(int i); //完成游戏结束后的提示信息
void creatLink(); //(带头尾结点)双向链表以及它的操作
void insertNode(int x, int y);
void deleteNode();
void deleteLink();
int ch; //输入的命令
int hour, minute, second; //时分秒
int length, tTime, level; //(蛇的)长度,计时器,(游戏)等级
struct direct dir, food; //蛇的前进方向,食物的位置
node *head, *tail; //链表的头尾结点
int main()
{
/**
* curses.h宏 定义了 nitscr() , endwin()
*/
initscr();
// 初始化
initGame();
/**
* signal.h宏 定义了 signal()
* SIGALRM 14 A 由alarm(2)发出的信号
* 调用 show()
*/
signal(SIGALRM, show);
// 获取键盘命令
getOrder();
endwin();
return 0;
}
void initGame()
{
cbreak(); // 把终端的CBREAK模式打开
noecho(); // 关闭回显
curs_set(0); // 把光标置为不可见
keypad(stdscr, true); // 使用用户终端的键盘上的小键盘
srand(time(0)); // 设置随机数种子
// 初始化各项数据
hour = minute = second = tTime = 0;
length = 1;
dir.cx = 1;
dir.cy = 0;
ch = 'A';
food.cx = rand() % COLS; // COLS 命令行列宽
food.cy = rand() % (LINES-2) + 2; // LINES 命令行行高
creatLink(); // 创建一个双向链表
setTicker(20); // 设置计时器
}
/* 设置计时器(这个函数是书本上的例子,有改动)
* n_msecs 设置间隔时长 s
* 1s后启动定时器
*/
int setTicker(int n_msecs)
{
/**
* time.h 宏:
* struct itimerval {
* struct timeval it_interval; // next value 间隔时长
* struct timeval it_value; // current value 运行时间
* };
* struct timeval {
* long tv_sec; // seconds 秒
* lonng tv_usec // microseconds 微秒
* };
*/
struct itimerval new_timeset;
// n_sec 秒 , n_usecs 微秒
long n_sec, n_usecs;
// 间隔
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ; // L长整型
new_timeset.it_interval.tv_sec = n_sec;
new_timeset.it_interval.tv_usec = n_usecs;
// 运行时间
n_msecs = 1;
n_sec = n_msecs / 1000 ;
n_usecs = ( n_msecs % 1000 ) * 1000L ;
new_timeset.it_value.tv_sec = n_sec ;
new_timeset.it_value.tv_usec = n_usecs ;
// 设置定时器
// int setitimer(int which, const struct itimerval *value,struct itimerval *ovalue);
// setitimer()将value指向的结构体设为计时器的当前值,假设ovalue不是NULL,将返回计时器原有值。
return setitimer(ITIMER_REAL, &new_timeset, NULL);
}
void showInformation()
{
tTime++;
if (tTime >= 1000000)
tTime = 0;
if (1 != tTime % 50)
return;
move(0, 3); // curses库定义: move(y,x): 将游标移动至 x,y 的位置.
// 显示时间
printw("time: %d:%d:%d %c", hour, minute, second);
second++;
if (second > NUM) {
second = 0;
minute++;
}
if (minute > NUM) {
minute = 0;
hour++;
}
//显示长度,等级
move(1, 0);
int i;
for (i=0; i<COLS; i++)
addstr("-");
move(0, COLS/2 - 5);
printw("length: %d", length);
move(0, COLS-10);
level = length / 3 + 1;
printw("level: %d", level);
}
//蛇的表示是用一个带头尾结点的双向链表来表示的,
//蛇的每一次前进,都是在链表的头部增加一个节点,在尾部删除一个节点
//如果蛇吃了一个食物,那就不用删除节点了
void showSnake()
{
if(1 != tTime % (30-level)) {
return;
}
//判断蛇的长度有没有改变
bool lenChange = false;
//显示食物
move(food.cy, food.cx);
printw("@");
//如果蛇碰到墙,则游戏结束
if((COLS-1==head->next->cx && 1==dir.cx)
|| (0==head->next->cx && -1==dir.cx)
|| (LINES-1==head->next->cy && 1==dir.cy)
|| (2==head->next->cy && -1==dir.cy))
{
over(1);
return;
}
//如果蛇头砬到自己的身体,则游戏结束
if('*' == mvinch(head->next->cy+dir.cy, head->next->cx+dir.cx) )
{
over(2);
return;
}
insertNode(head->next->cx+dir.cx, head->next->cy+dir.cy);
//蛇吃了一个“食物”
if(head->next->cx==food.cx && head->next->cy==food.cy)
{
lenChange = true;
length++;
//恭喜你,通关了
if(length >= 50)
{
over(3);
return;
}
//重新设置食物的位置
food.cx = rand() % COLS;
food.cy = rand() % (LINES-2) + 2;
}
if(!lenChange)
{
move(tail->back->cy, tail->back->cx);
printw(" ");
deleteNode();
}
move(head->next->cy, head->next->cx);
printw("*");
}
void show()
{
// SIGALRM 14 A 由alarm(2)发出的信号
signal(SIGALRM, show); // 设置中断信号
showInformation();
showSnake();
refresh(); // 刷新真实屏幕
/* refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时,
curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完成. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上.
减少萤幕重绘的时间.如果是 initscr() 後第一次呼叫 refresh(), curses 将做清除萤幕的工作. */
}
// 获取键盘命令
void getOrder()
{
//建立一个死循环,来读取来自键盘的命令
while(1)
{
ch = getch();
if(KEY_LEFT == ch)
{
dir.cx = -1;
dir.cy = 0;
}
else if(KEY_UP == ch)
{
dir.cx = 0;
dir.cy = -1;
}
else if(KEY_RIGHT == ch)
{
dir.cx = 1;
dir.cy = 0;
}
else if(KEY_DOWN == ch)
{
dir.cx = 0;
dir.cy = 1;
}
setTicker(20);
}
}
// 结束命令
void over(int i)
{
// 显示结束原因
move(0, 0); // 定位光标到开头
int j;
for(j=0; j<COLS; j++) {
addstr(" "); // curses库 addstr(str): 显示一串字串.
}
move(0, 2);
if(1 == i)
addstr("Crash the wall. Game over");
else if(2 == i)
addstr("Crash itself. Game over");
else if(3 == i)
addstr("Mission Complete");
setTicker(0); //关闭计时器
deleteLink(); //释放链表的空间
}
//创建一个双向链表
void creatLink()
{
// 临时链表
// void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
node *temp = (node *)malloc( sizeof(node) );
head = (node *)malloc( sizeof(node) ); // 链表头
tail = (node *)malloc( sizeof(node) ); // 链表尾
temp->cx = 5;
temp->cy = 10;
head->back = NULL;
head->next = temp;
tail->next = NULL;
temp->next = tail;
tail->back = temp;
temp->back = head;
}
//在链表的头部(非头结点)插入一个结点
void insertNode(int x, int y)
{
node *temp = (node *)malloc( sizeof(node) );
temp->cx = x;
temp->cy = y;
temp->next = head->next;
head->next = temp;
temp->back = head;
temp->next->back = temp;
}
//删除链表的(非尾结点的)最后一个结点
void deleteNode()
{
node *temp = tail->back;
node *bTemp = temp->back;
bTemp->next = tail;
tail->back = bTemp;
temp->next = temp->back = NULL;
free(temp);
temp = NULL;
}
// 删除整个链表
void deleteLink()
{
while (head->next != tail)
{
deleteNode();
}
head->next = tail->back = NULL;
free(head); /* 使用 free() 函数释放内存 */
free(tail);
}
编译:
gcc snake.c -lcurses -o snake
要加上 -lcurses,不然报错。 显式的加入库,即用-l<库名>,因为有库并不等于在编译的时候就能链接上,你还得告诉编译器去哪里找,-l<库名>这句其实就相当于一个路径/**/**/库名。库名>库名>
参考资料
100个C语言经典小程序和C语言编写的小游戏,带注释和解析 http://c.biancheng.net/cpp/u/yuanma/
linux下curses库介绍 https://blog.csdn.net/csdelete/article/details/52552008
Linux curses库使用 https://www.cnblogs.com/mengfanrong/p/4072214.html
Linux struct itimerval使用方法 http://www.manongjc.com/article/91653.html
C语言中的signal函数 https://www.cnblogs.com/jhcelue/p/7256859.html