概述
文章目录
- 初版
- 概述
- 采用的库
- 功能规划
- 最初规划
- 已完成功能
- 游戏界面
- 代码实现
- 规划中的数据结构
- 具体实现
- 二版
- 更改的地方
- 参数
- 得分榜
- 数据
- 下一步
初版
概述
为了熟悉linux下的c语言编程,所以尝试做的,具体功能及实现有可能会慢慢更改。
仓库地址
采用的库
图形化将采用curses库,存储存档及得分榜采用ndbm库,利用groff编写manpage,编译及安装采用make。
功能规划
最初规划
使用命令行启动游戏,利用命令行参数可以打印版本信息、帮助信息,初始化得分榜,开启作弊模式(碰到自身或墙壁不算游戏失败)。如果不带参数则直接启动游戏。
开始界面右侧窗口显示得分榜,左侧显示新游戏、载入存档、删除存档、退出游戏四个选项。顶上一行显示标题,最底下一行显示临时状态。
游戏界面左边窗口为游戏窗口,右上窗口显示操作指南,右下窗口显示当前得分及速度等级。
已完成功能
- 参数模式打印版本、帮助信息
- 开始界面新游戏、退出游戏
- 游戏界面正常游戏
- 游戏界面显示操作指南
- 游戏界面显示当前得分及速度等级
游戏界面
游戏将有两个界面,即主界面和游戏界面。
-
主界面:
主界面将要完成开启新游戏、载入存档和删除存档的任务,及循环显示得分榜。
-
游戏界面:
主要分为左、右上、右下三个板块,游戏区域,操作说明区域,当前得分及速度的显示区域。
-
流程图:
这是最初规划的流程图,实际实现会有一定的增删。
代码实现
规划中的数据结构
- 蛇的身体:
将实现为一个结构体数组,数组长度为整个游戏区的格数,数组中每个结构体含有x,y两个成员,即身体每个点的坐标。 - 蛇的身体的当前长度:
由一个全局变量保存 - 当前未占用的格子:
维护一个全局的二维布尔数组,每次更新蛇的身体的同时,将蛇的身体所占用的格子对应的这个布尔数组的成员设为false。这个数组所有为true的成员即代表对应的格子允许生成食物。 - 现存的食物:
与蛇的身体一样的结构体。 - 当前得分:
与蛇的身体的总长相关 - 游戏者姓名:
一个字符数组保存 - 当前速度:
根据蛇身体的总长变化,每一次刷新的间隔,其区间将是1000ms~50ms。 - 存储文件
存档文件,每条数据由一个编号,一个姓名,蛇身体长度,蛇的身体的数组组成。
得分榜文件,每条数据由姓名及得分组成。
具体实现
data.h
#ifndef _DATA_H
#define _DATA_H
#include <unistd.h>
#include <curses.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ndbm.h>
#include <getopt.h>
#include <stdio.h>
#define WINDOW_WIDTH 40
#define WINDOW_HEIGHT 20
#define TOTLE_POINT ((WINDOW_WIDTH-2)*(WINDOW_HEIGHT-2))
#define SPEED_MAX 1000
#define STR_LEN 38
#define VERSION ("1.00")
typedef struct
{
int x;
int y;
}node;
typedef node food;
typedef node direct;
typedef node *snake;
//存档数据和得分榜数据处理函数
//游戏逻辑函数
void init_status(WINDOW *win_ptr,direct *d_ptr,food *f_ptr,snake greedy,char *name);
void destory_status(snake greedy);
void end_game(WINDOW *win_ptr,char *string);
//用于参数模式的函数
int command_mode(int argc,char *argv[]);
//用于开始界面的函数
void draw_base_window(void);
void draw_select_window(WINDOW *win_ptr,char *options[],int current_highlight,int start_row,int start_col);
void clear_start_screen(void);
int getchoice(WINDOW *win_ptr,char *choices[]);
//用于游戏界面的函数
void Checkmap(snake greedy);
void draw_snake_window(WINDOW *win_ptr,snake greedy,food f1);
void draw_status_window(WINDOW *win_ptr,char *name);
void update_snake(snake greedy,direct d,bool *eated);
void init_keyboard(WINDOW *w_ptr);
void get_key(direct *d);
void close_keyboard(WINDOW *w_ptr);
bool Eatfood(snake greedy,food f1);
bool Isover(snake greedy);
bool Iswin(void);
void Createfood(food *fd);
#endif
frontend.c
#define _GNU_SOURCE
#include "data.h"
//用于参数模式的函数声明
static void version(void);
static void help(void);
static void opt_error(char c);
//用于开始界面的函数声明
//用于参数模式的函数定义
static void version(void)
{
fprintf(stdout,"greedy snake version %sn",VERSION);
}
static void help(void)
{
fprintf(stdout,"Usage: snake [options]n");
fprintf(stdout,"Options:n");
fprintf(stdout,"t-v/--versiontdisplay the version informationn");
fprintf(stdout,"t-h/--helptdisplay the help informationn");
fprintf(stdout,"t-i/--inittinitialize the ranking listn");
fprintf(stdout,"t-c/--cheattinto the cheat mode, you will not die until got full marksn");
}
static void opt_error(char c)
{
fprintf(stderr,"unknown option: %cnplease use snake --help to get more informationn",c);
}
static void cheat(void)
{
extern bool Cheat;
Cheat=true;
}
int command_mode(int argc,char *argv[])
{
extern bool Cheat;
int result=0,opt;
struct option longopts[]=
{
{"version",0,NULL,'v'},
{"help",0,NULL,'h'},
{"init",0,NULL,'i'},
{"cheat",0,NULL,'c'},
{0,0,0,0}
};
while((opt=getopt_long(argc,argv,":vhic",longopts,NULL))!=-1)
{
switch(opt)
{
case 'v':
version();
result=1;
break;
case 'h':
help();
result=1;
break;
case 'i':
/*删除旧的得分榜数据库文件,创建新的*/
result=0;
break;
case 'c':
cheat();
result=0;
break;
case '?':
opt_error(optopt);
result=2;
break;
}
}
return result;
}
//用于游戏逻辑的函数定义
void init_status(WINDOW *win_ptr,direct *d_ptr,food *f_ptr,snake greedy,char *name)
{
char *prompt[]=
{
"enter your name: ",
0
};
extern int Current_len;
int seed;
seed=rand()%4;
switch(seed)//随机产生初始方向
{
case 0:
d_ptr->x=0;
d_ptr->y=-1;
break;
case 1:
d_ptr->x=0;
d_ptr->y=1;
break;
case 2:
d_ptr->x=-1;
d_ptr->y=0;
break;
case 3:
d_ptr->x=1;
d_ptr->y=0;
break;
}
Current_len=0;
Checkmap(greedy);
while(true)//产生一个不靠边框的蛇头
{
Createfood(&greedy[0]);/*其实用greedy是一样的*/
if(greedy[0].x>1&&greedy[0].x<COLS-2&&greedy[0].y>1&&greedy[0].y<LINES-2)
break;
}
greedy[1].x=greedy[0].x-d_ptr->x;/*向方向向量的相反方向生成一个蛇尾巴*/
greedy[1].y=greedy[0].y-d_ptr->y;
Current_len=2;
Checkmap(greedy);
Createfood(f_ptr);
move(LINES-2,1);
clrtoeol();
mvprintw(LINES-2,1,"touch enter to save your name.");
refresh();
draw_select_window(win_ptr,prompt,-1,WINDOW_HEIGHT/2-1,WINDOW_WIDTH/2-10);
wgetnstr(win_ptr,name,STR_LEN-1);
}
void destory_status(snake greedy)
{
free(greedy);
}
void end_game(WINDOW *win_ptr,char *string)
{/*不管是得到最高分还是挂掉,都是打印信息然后存入得分榜*/
wclear(win_ptr);
box(win_ptr,ACS_VLINE,ACS_HLINE);
mvwprintw(win_ptr,WINDOW_HEIGHT/2-1,WINDOW_WIDTH/2-10,"%s",string);
wrefresh(win_ptr);
/*存储得分榜*/
sleep(2);
}
//用于开始界面的函数定义
void draw_base_window(void)
{/*画个边框,输出题目*/
clear();
box(stdscr,ACS_VLINE,ACS_HLINE);
mvprintw(1,COLS/2-7,"%s","Greedy Snake");
refresh();
}
void draw_select_window(WINDOW *win_ptr,char *options[],int current_highlight,int start_row,int start_col)
{/*不想有高亮显示时将current_highlight设为-1*/
wclear(win_ptr);
box(win_ptr,ACS_VLINE,ACS_HLINE);
int current_row=0;
char **option_ptr;
char *txt_ptr;
option_ptr=options;
while(*option_ptr)
{
if(current_row==current_highlight)
wattron(win_ptr,A_STANDOUT);
txt_ptr=options[current_row];
mvwprintw(win_ptr,start_row+current_row*2,start_col,"%s",txt_ptr);
if(current_row==current_highlight)
wattroff(win_ptr,A_STANDOUT);
current_row++;
option_ptr++;
}
wrefresh(win_ptr);
}
void clear_start_screen(void)
{
clear();
mvprintw(1,COLS/2-7,"%s","Greedy Snake");
refresh();
}
int getchoice(WINDOW *win_ptr,char *choices[])
{
static int selected_row=0;
int max_row=0;
int start_screenrow=WINDOW_HEIGHT/2-5,start_screencol=WINDOW_WIDTH/2-6;
char **options;
int selected;
int key=0;
options=choices;
mvprintw(LINES-2,1,"Move highlight then press enter");
refresh();
while(*options)
{
max_row++;
options++;
}
keypad(stdscr,true);
cbreak();
noecho();
while(key!=KEY_ENTER&&key!='n')
{
if(key==KEY_UP)
{
if(selected_row==0)
selected_row=max_row-1;
else
selected_row--;
}
if(key==KEY_DOWN)
{
if(selected_row==max_row-1)
selected_row=0;
else
selected_row++;
}
selected=*choices[selected_row];
draw_select_window(win_ptr,choices,selected_row,start_screenrow,start_screencol);
key=getch();
}
keypad(stdscr,false);
nocbreak();
echo();
return selected;
}
//用于游戏界面的函数定义
void draw_snake_window(WINDOW *win_ptr,snake greedy,food f1)
{
extern int Current_len;
wclear(win_ptr);
box(win_ptr,ACS_VLINE,ACS_HLINE);
mvwaddch(win_ptr,f1.y,f1.x,'@');
for(int i=Current_len-1;i>=0;i--)/*从蛇尾打印到蛇头,这样在开启作弊模式的时候,蛇头与身体重合的时候依然可以看到蛇头*/
{
if(i==0)
mvwaddch(win_ptr,greedy[i].y,greedy[i].x,'#');
else
mvwaddch(win_ptr,greedy[i].y,greedy[i].x,'*');
}
wrefresh(win_ptr);
}
void draw_status_window(WINDOW *win_ptr,char *name)
{
extern int Current_len;
int score=Current_len;
char speed_string[STR_LEN];
char score_string[STR_LEN];
sprintf(score_string,"Current Score = %d",score);
sprintf(speed_string,"Current Speed level = %d",(Current_len/35));
wclear(win_ptr);
box(win_ptr,ACS_VLINE,ACS_HLINE);
mvwprintw(win_ptr,WINDOW_HEIGHT/9,WINDOW_WIDTH/4,"Hello %s",name);
mvwprintw(win_ptr,WINDOW_HEIGHT/5,WINDOW_WIDTH/4,"%s",score_string);
mvwprintw(win_ptr,WINDOW_HEIGHT/3,WINDOW_WIDTH/4,"%s",speed_string);
wrefresh(win_ptr);
}
void Checkmap(snake greedy)
{/*将蛇的数组映射到代表剩余格子的布尔数组,用于标识可生成食物的位置*/
extern bool Map[WINDOW_HEIGHT-2][WINDOW_WIDTH-2];
extern int Current_len;
int index_x,index_y,i;
for(index_y=0;index_y<WINDOW_HEIGHT-2;index_y++)
for(index_x=0;index_x<WINDOW_WIDTH-2;index_x++)
Map[index_y][index_x]=true;/*其实并不用每次都初始化这个数组,因为蛇的数组每次改变的只有头尾两个位置,后面再优化*/
for(i=0;i<Current_len;i++)
{
Map[greedy[i].y-1][greedy[i].x-1]=false;
}
}
void update_snake(snake greedy,direct d,bool *eated)
{
extern bool Map[WINDOW_HEIGHT-2][WINDOW_WIDTH-2];
extern int Current_len;
int i;
if(*eated)
{
Current_len++;
*eated=false;
}
node temp;
temp=greedy[0];
temp.x+=d.x;
temp.y+=d.y;
for(i=Current_len-1;i>0;i--)
{
greedy[i]=greedy[i-1];
}
greedy[0]=temp;
Checkmap(greedy);
}
void init_keyboard(WINDOW *w_ptr)
{
keypad(stdscr,true);
noecho();
cbreak();
leaveok(w_ptr,true);/*让光标不可见*/
timeout(SPEED_MAX);/*等待这么多时间,表现出来就是蛇的移动的间隔*/
}
void get_key(direct *d)
{
int key;
if((key=getch())!=ERR)
{
switch(key)
{
case 'A':
case 'a':
case KEY_LEFT:
if(d->x!=1)/*防止方向变成当前方向完全相反的方向*/
{
d->x=-1;
d->y=0;
}
break;
case 'D':
case 'd':
case KEY_RIGHT:
if(d->x!=-1)
{
d->x=1;
d->y=0;
}
break;
case 'W':
case 'w':
case KEY_UP:
if(d->y!=1)
{
d->x=0;
d->y=-1;
}
break;
case 'S':
case 's':
case KEY_DOWN:
if(d->y!=-1)
{
d->x=0;
d->y=1;
}
break;
}
}
}
void close_keyboard(WINDOW *w_ptr)
{
keypad(stdscr,false);
echo();
timeout(-1);/*恢复成getch()会一直等待*/
nocbreak();
leaveok(w_ptr,false);/*光标可见*/
}
bool Eatfood(snake greedy,food f1)
{
if(greedy[0].x==f1.x&&greedy[0].y==f1.y)
return true;
else
return false;
}
bool Isover(snake greedy)
{
extern int Current_len;
bool flag=false;
if(greedy[0].x==0||greedy[0].x==(WINDOW_WIDTH-1)||greedy[0].y==0||greedy[0].y==(WINDOW_HEIGHT-1))/*碰到墙的话*/
flag=true;
for(int i=1;i<Current_len;i++)
{
if(greedy[0].x==greedy[i].x&&greedy[0].y==greedy[i].y)/*碰到自己的话*/
flag=true;
}
return flag;
}
bool Iswin(void)
{
extern int Current_len;
if(Current_len==TOTLE_POINT)/*蛇占满了每一个格子,不开作弊模式不可能吧*/
return true;
else
return false;
}
void Createfood(food *fd)
{
int index_x=0,index_y=0;
extern bool Map[WINDOW_HEIGHT-2][WINDOW_WIDTH-2];
extern int Current_len;
int residue=TOTLE_POINT-Current_len;
int count=0;
count=rand()%residue+1;
while(count!=0)
{
if(Map[index_y][index_x])
count--;
if(index_x==WINDOW_WIDTH-3)
{
index_y++;
index_x=0;
}
else
index_x++;
}
fd->x=index_x+1;/*因布尔数组的下标从0、0开始,而蛇的坐标从1、1开始*/
fd->y=index_y+1;
}
main.c
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include "data.h"
bool Cheat=false;
int Current_len;
bool Map[WINDOW_HEIGHT-2][WINDOW_WIDTH-2];
int main(int argc,char *argv[])
{
char *start_menu[]=
{
"new game",
"load game",
"delete data",
"quit",
0
};
char *instructions[]=
{
"use up/down/left/right",
"or w/s/a/d",
"to control the snake",
"Esc to save/end the game",
0
};
srand(time(0));
snake greedy;
greedy=malloc(sizeof(food)*TOTLE_POINT);
bool eatedfood=false;
int key;
food f;
direct d;
char name[STR_LEN];
//参数处理
int command_result;
if(argc>1)
{
command_result=command_mode(argc,argv);
if(command_result)
exit(command_result);
}
//开始界面
initscr();
draw_base_window();
WINDOW *start_rank_win=newwin(WINDOW_HEIGHT,WINDOW_WIDTH,(LINES-WINDOW_HEIGHT)/2,COLS/2+3);
WINDOW *select_win=newwin(WINDOW_HEIGHT,WINDOW_WIDTH,2,6);
draw_select_window(start_rank_win,start_menu,-1,1,1);
do
{
command_result=getchoice(select_win,start_menu);
switch(command_result)
{
case 'n':
/*设置新的初始状态*/
init_status(select_win,&d,&f,greedy,name);
command_result='g';
break;
case 'l':
/*载入存档中的状态*/
command_result='g';
break;
case 'd':
/*删除存档*/
break;
case 'q':
exit(EXIT_SUCCESS);
}
}while(command_result!='g');
delwin(start_rank_win);
draw_base_window();
//游戏界面
//现在select_win作为游戏窗口
WINDOW *instructions_win=newwin(WINDOW_HEIGHT/2,WINDOW_WIDTH,(LINES-WINDOW_HEIGHT)/2,COLS/2+3);
WINDOW *status_win=newwin(WINDOW_HEIGHT/2,WINDOW_WIDTH,LINES/2,COLS/2+3);
draw_select_window(instructions_win,instructions,-1,1,WINDOW_WIDTH/4);
init_keyboard(select_win);
while(true)
{
timeout(SPEED_MAX-(Current_len/35)*50);//改变速度
draw_status_window(status_win,name);
draw_snake_window(select_win,greedy,f);
if(Isover(greedy))
{
end_game(select_win,"Game Over!");
break;
}
if(Iswin())
{
end_game(select_win,"You Win!");
break;
}
if(Eatfood(greedy,f))
{
Createfood(&f);
eatedfood=true;
}
get_key(&d);
update_snake(greedy,d,&eatedfood);
}
close_keyboard(select_win);
delwin(select_win);
delwin(instructions_win);
delwin(status_win);
endwin();
destory_status(greedy);
exit(EXIT_SUCCESS);
}
注:现在的游戏逻辑已基本完善,接下来会更新数据存储,更新的代码在仓库内查看
二版
更改的地方
参数
-i/–init参数可以重置得分榜
-c/–cheat参数可以不判断是否碰到自身或墙壁
得分榜
存储存档及得分榜采用gdbm库,完成得分榜,得分榜将在开始界面展示,并且在游戏结束(失败或得到最高分)时存入得分榜。
#include "data.h"
static GDBM_FILE rank_db_ptr=NULL;
static GDBM_FILE savedata_db_ptr=NULL;
static int cmprank(const void *p1,const void *p2);
static int cmprank(const void *p1,const void *p2)
{
return (*(rank_entry *)p2).rank_point-(*(rank_entry *)p1).rank_point;
}
//得分榜数据处理函数
int rank_db_init(bool new_database)
{
if(rank_db_ptr)
gdbm_close(rank_db_ptr);
if(new_database)
unlink(RANK_FILE);
rank_db_ptr=gdbm_open(RANK_FILE,0,GDBM_WRCREAT|GDBM_SYNC,00640,NULL);
if(rank_db_ptr==NULL)
{
fprintf(stderr,"Can not open rank database,n%sn",gdbm_strerror(gdbm_errno));
return 0;
}
return 1;
}
void rank_db_close(void)
{
if(rank_db_ptr)
gdbm_close(rank_db_ptr);
rank_db_ptr=NULL;
}
rank_entry get_rank_entry(int index)
{
rank_entry entry_to_return;
datum key,data;
memset(&entry_to_return,'