主要设计思路:
- 主要数据结构:双向循环链表
- 蛇的移动:每次在蛇头添加2个节点,在蛇尾删除2个节点
- 蛇的转向:通过一组方向坐标与蛇的位置坐标的运算来实现转向
- 蛇吃食物:只在蛇头添加2个节点,而不必在蛇尾删除2个节点
- 蛇的死亡:撞上边界或自身
*
然后给出实现中的一点说明:
- 之所以每次操作2个节点,是因为我所绘制的游戏界面比较大,这样能使游戏效果更明显点
- 而且为了当蛇身比较长的时候,对蛇身的折叠缠绕能够明显的区分开,而不是混成一团,我们让蛇每次移动2行或2列
- 还有一个需要注意的点:当你给普通字符着色后,它所对应的一个整形值就不再是原来的值了,它会在原来的基础上附加上颜色属性。所以不同颜色的相同字符其实是不同的,不能一概视之。
游戏的核心逻辑:
正常情况下,程序一直处于监听键盘的状态,但一旦有信号中断产生,就转而去执行display_snake函数;执行完毕后,又处于监听键盘的状态… 直至程序运行结束(这里用到了一点简单的signal方面的知识)。这个机制应该是整个程序设计的难点了,我也是在参考了许多博客,再加上一段时间的思考之后,才想明白的。
还有一点就是,你得确保你的电脑上有curses库,绝大多数unix系统应该会自带curses库,如果没有的话,那么你得自行安装,安装方法很简单,网上也有很多,这里就不多说了。
下面便是程序的有关代码:
首先是一些声明
#define DELAY 150 /* 设置延时 */
/* 蛇的活动地图的大小 */
#define ROW (LINES - 3)
#define COL (COLS - 24)
typedef struct snake { /* 蛇身节点 */
int sx; /* 行坐标 */
int sy; /* 列坐标 */
struct snake *last;
struct snake *next;
} Snake;
extern int dx, dy;
extern Snake *head, *tail;
extern int score;
void init(void);
void over(void);
void draw_initscr();
void draw_overscr();
void draw_map();
void display_snake(int);
void display_score();
void set_color(void);
int set_ticker(int);
void creat_snake(void);
void creat_food(void);
void add_snake(void);
void del_snake(void);
void lock_snake(void);
int is_eat_food(void);
int is_crash(void);
void get_keyboard(void);
主函数很简单(^_^)a
main.c
Snake *head, *tail; /* 蛇头、蛇尾 */
int dx, dy; /* 一组方向坐标 */
int score; /* 得分 */
int main(void)
{
init();
signal(SIGALRM, display_snake);
get_keyboard(); /* 监听键盘 */
endwin();
return 0;
}
proctrl.c
/* init函数:游戏初始化 */
void init(void)
{
initscr(); /* 初始化curses */
start_color(); /* 初始化颜色表 */
set_color(); /* 设置颜色 */
box(stdscr, ACS_VLINE, ACS_HLINE); /* 绘制一个同物理终端大小相同的窗口 */
noecho(); /* 关闭键入字符的回显 */
cbreak(); /* 字符一键入,直接传递给程序 */
curs_set(0); /* 隐藏光标 */
keypad(stdscr, true); /* 开启逻辑键 */
draw_initscr();
draw_map();
creat_snake();
creat_food();
display_score();
refresh();
}
/* display_snake函数:游戏的主要控制逻辑 */
void display_snake(int signo)
{
if (is_crash())
over();
if (is_eat_food()) {
add_snake();
creat_food();
display_score();
} else {
add_snake();
del_snake();
}
refresh();
}
/* over函数:游戏结束 */
void over(void)
{
draw_overscr();
endwin(); /* 结束调用curses */
exit(0);
}
下面是与蛇的移动有关的代码,也是游戏的核心部分代码
mvsnake.c
/* creat_snake函数:初始化蛇身 */
void creat_snake(void)
{
assert(head = tail = malloc(sizeof(Snake)));
head->last = head->next = NULL;
srand(clock()); /* 以当前挂钟时间作随机种子数 */
while ((head->sx = rand() % (ROW - 3) + 3) % 2 == 0)
;
while ((head->sy = rand() % (COL - 4) + 4) % 2 != 0)
;
attron(COLOR_PAIR(1));
mvaddch(head->sx, head->sy, ' ');
attroff(COLOR_PAIR(1));
}
/* creat_food函数:初始化食物 */
void creat_food(void)
{
int i, j;
srand(clock());
while ((i = rand() % (ROW - 3) + 3) % 2 == 0)
;
while ((j = rand() % (COL - 4) + 4) % 2 != 0)
;
move(i, j);
if (inch() == 288) /* 食物不能覆盖蛇身 */
creat_food();
attron(COLOR_PAIR(2));
addch(' ');
attroff(COLOR_PAIR(2));
}
/* add_snake函数:在蛇头增加2个节点 */
void add_snake(void)
{
Snake *p, *q;
assert(p = malloc(sizeof(Snake)));
assert(q = malloc(sizeof(Snake)));
p->last = head;
head->next = p;
p->next = q;
q->last = p;
q->next = NULL;
attron(COLOR_PAIR(1));
p->sx = head->sx + dx;
p->sy = head->sy + dy;
mvaddch(p->sx, p->sy, ' ');
q->sx = p->sx + dx;
q->sy = p->sy + dy;
mvaddch(q->sx, q->sy, ' ');
attroff(COLOR_PAIR(1));
head = q;
}
/* del_snake函数:在蛇尾删除2个节点 */
void del_snake(void)
{
Snake *tmp;
mvaddch(tail->sx, tail->sy, ' ');
mvaddch(tail->next->sx, tail->next->sy, ' ');
tmp = tail->next->next;
free(tail->next);
free(tail);
tail = tmp;
tail->last = NULL;
}
/* is_eat_food函数:判断是否吃到食物 */
int is_eat_food(void)
{
if (inch() == 544)
return 1;
return 0;
}
/* is_crash函数:判断是否撞到障碍物 */
int is_crash(void)
{
move(head->sx + 2 * dx, head->sy + 2 * dy);
if (head->sx <= 2 || head->sx >= ROW + 1||
head->sy <= 3 || head->sy >= COL + 1) /* 撞到边界 */
return 1;
if (inch() == 288) /* 撞到自身 */
return 1;
return 0;
}
/* get_keyboard函数:监听键盘 */
void get_keyboard(void)
{
int c;
while ((c = getch()) != 'q') { /* 按q键可以退出游戏 */
switch(c) {
case KEY_UP:
dx = -1; dy = 0;
break;
case KEY_DOWN:
dx = 1; dy = 0;
break;
case KEY_LEFT:
dx = 0; dy = -1;
break;
case KEY_RIGHT:
dx = 0; dy = 1;
break;
default:
break;
}
lock_snake();
set_ticker(DELAY);
}
}
/* lock_snake函数:防止蛇身反向移动 */
void lock_snake(void)
{
static int lx, ly;
if (dx && dx + lx == 0)
dx = lx;
if (dy && dy + ly == 0)
dy = ly;
lx = dx;
ly = dy;
}
游戏界面的绘制就是个细心活喽,大家按自己的喜好绘制就可以了,说实话,这个界面我是尝试了好多好多次,挺无聊的^o^(逃
draw.c
/* draw_initscr函数:绘制游戏开始界面 */
void draw_initscr()
{
int i;
attron(COLOR_PAIR(4));
for (i = COLS / 2 - 20; i <= COLS / 2 + 20; i += 2) {
mvaddch(LINES / 2 - 4, i, '@');
mvaddch(LINES / 2 + 6, i, '@');
}
for (i = LINES / 2 - 3; i <= LINES / 2 + 5; i++) {
mvaddch(i, COLS / 2 - 22, '@');
mvaddch(i, COLS / 2 + 22, '@');
}
mvaddstr(LINES / 2 + 8, COLS / 2 - 13, "Press any key to continue...");
attroff(COLOR_PAIR(4));
attron(COLOR_PAIR(5));
mvaddstr(LINES / 2 - 2, COLS / 2 - 8, "Welcome to snake");
mvaddstr(LINES / 2 - 1, COLS / 2 - 10, "emmmmmmmmm, however,");
mvaddstr(LINES / 2, COLS / 2 - 20, "now that you are here, you must be greedy!");
mvaddstr(LINES / 2 + 1, COLS / 2 - 11, "~~o~~o~~o~~o~~o~~o~~o~~");
mvaddstr(LINES / 2 + 2, COLS / 2 - 5, "Game Rules:");
mvaddstr(LINES / 2 + 3, COLS / 2 - 18, "1. use direciton keys to move snake");
mvaddstr(LINES / 2 + 4, COLS / 2 - 18, "2. snake is green space, food is red");
getch();
attroff(COLOR_PAIR(5));
refresh();
clear(); /* 清屏 */
}
/* draw_map函数:绘制游戏地图 */
void draw_map(void)
{
int i;
attron(COLOR_PAIR(3));
for (i = 3; i < COLS - 1; i += 2) {
mvaddch(2, i, ' ');
mvaddch(LINES - 2, i, ' ');
}
for (i = 3; i < LINES - 1; i += 2) {
mvaddch(i, 3, ' ');
mvaddch(i, COL + 1, ' ');
mvaddch(i, COLS - 3, ' ');
}
attroff(COLOR_PAIR(3));
attron(COLOR_PAIR(4));
mvaddstr(10, 132, "Your Score:");
mvaddstr(12, 130, "^0^ smiling ^0^");
attroff(COLOR_PAIR(4));
}
/* draw_overscr函数:绘制结束界面 */
void draw_overscr(void)
{
int i;
clear();
attron(COLOR_PAIR(4));
for (i = COLS / 2 - 16; i <= COLS / 2 + 16; i += 2) {
mvaddch(LINES / 2 - 4, i, '@');
mvaddch(LINES / 2 + 3, i, '@');
}
for (i = LINES / 2 - 3; i <= LINES / 2 + 2; i++) {
mvaddch(i, COLS / 2 - 18, '@');
mvaddch(i, COLS / 2 + 18, '@');
}
mvaddstr(LINES / 2 + 5, COLS / 2 - 13, "Press any key to continue...");
attroff(COLOR_PAIR(4));
attron(COLOR_PAIR(5));
mvaddstr(LINES / 2 - 1, COLS / 2 - 13, "I'm sorry, the game is over");
mvaddstr(LINES / 2, COLS / 2 - 9, "But expect you back");
attroff(COLOR_PAIR(5));
refresh();
getch();
}
/* display_score函数:显示当前得分 */
void display_score(void)
{
attron(COLOR_PAIR(5));
move(11, 136);
printw("%d", score++);
attroff(COLOR_PAIR(5));
}
其中set_ticker函数是我从网上找的,具体原理不太清楚^ _ ^
set.c
/* set_color函数:设置颜色 */
void set_color(void)
{
init_pair(1, COLOR_GREEN, COLOR_GREEN);
init_pair(2, COLOR_RED, COLOR_RED);
init_pair(3, COLOR_WHITE, COLOR_WHITE);
init_pair(4, COLOR_YELLOW, COLOR_BLACK);
init_pair(5, COLOR_RED, COLOR_BLACK);
}
/* set_ticker函数:设置间隔计时器(ms) */
int set_ticker(int n_msecs)
{
struct itimerval new_timeset;
long n_sec, n_usecs;
n_sec = n_msecs / 1000;
n_usecs = (n_msecs % 1000) * 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);
}
运行图:
最后说明一点:当你编译运行的时候需要链接至curses库哦,在编译命令后加上-lcurses就可以了
emmmm,然后呢,希望大家能多多提出建议^o^