运行于linux终端的c语言编写的贪食蛇详解(游戏逻辑篇)

初版

概述

 为了熟悉linux下的c语言编程,所以尝试做的,具体功能及实现有可能会慢慢更改。
仓库地址

采用的库

 图形化将采用curses库,存储存档及得分榜采用ndbm库,利用groff编写manpage,编译及安装采用make。

功能规划

最初规划

  使用命令行启动游戏,利用命令行参数可以打印版本信息、帮助信息,初始化得分榜,开启作弊模式(碰到自身或墙壁不算游戏失败)。如果不带参数则直接启动游戏。
  开始界面右侧窗口显示得分榜,左侧显示新游戏、载入存档、删除存档、退出游戏四个选项。顶上一行显示标题,最底下一行显示临时状态。
  游戏界面左边窗口为游戏窗口,右上窗口显示操作指南,右下窗口显示当前得分及速度等级。

已完成功能
  • 参数模式打印版本、帮助信息
  • 开始界面新游戏、退出游戏
  • 游戏界面正常游戏
  • 游戏界面显示操作指南
  • 游戏界面显示当前得分及速度等级

游戏界面

  游戏将有两个界面,即主界面和游戏界面。

  1. 主界面:
        主界面将要完成开启新游戏、载入存档和删除存档的任务,及循环显示得分榜。
    在这里插入图片描述

  2. 游戏界面:
        主要分为左、右上、右下三个板块,游戏区域,操作说明区域,当前得分及速度的显示区域。
    在这里插入图片描述

  3. 流程图:
    这是最初规划的流程图,实际实现会有一定的增删。
    在这里插入图片描述

代码实现

规划中的数据结构
  1. 蛇的身体:
      将实现为一个结构体数组,数组长度为整个游戏区的格数,数组中每个结构体含有x,y两个成员,即身体每个点的坐标。
  2. 蛇的身体的当前长度:
      由一个全局变量保存
  3. 当前未占用的格子:
      维护一个全局的二维布尔数组,每次更新蛇的身体的同时,将蛇的身体所占用的格子对应的这个布尔数组的成员设为false。这个数组所有为true的成员即代表对应的格子允许生成食物。
  4. 现存的食物:
      与蛇的身体一样的结构体。
  5. 当前得分:
      与蛇的身体的总长相关
  6. 游戏者姓名:
      一个字符数组保存
  7. 当前速度:
      根据蛇身体的总长变化,每一次刷新的间隔,其区间将是1000ms~50ms。
  8. 存储文件
      存档文件,每条数据由一个编号,一个姓名,蛇身体长度,蛇的身体的数组组成。
      得分榜文件,每条数据由姓名及得分组成。
具体实现

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 %s\n",VERSION);
}
static void help(void)
{
	fprintf(stdout,"Usage: snake [options]\n");
	fprintf(stdout,"Options:\n");
	fprintf(stdout,"\t-v/--version\tdisplay the version information\n");
	fprintf(stdout,"\t-h/--help\tdisplay the help information\n");
	fprintf(stdout,"\t-i/--init\tinitialize the ranking list\n");
	fprintf(stdout,"\t-c/--cheat\tinto the cheat mode, you will not die until got full marks\n");
}
static void opt_error(char c)
{
	fprintf(stderr,"unknown option: %c\nplease use snake --help to get more information\n",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%s\n",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,'\0',sizeof(rank_entry));
	key.dptr=(void *)&index;
	key.dsize=sizeof(int);
	data=gdbm_fetch(rank_db_ptr,key);
	if(data.dptr)
		memcpy(&entry_to_return,data.dptr,data.dsize);
	return entry_to_return;
}
void add_rank_entry(rank_entry entry_add,int index)
{
	datum key,data;
	data.dptr=(void *)&entry_add;
	data.dsize=sizeof(entry_add);
	key.dptr=(void *)&index;
	key.dsize=sizeof(index);
	gdbm_store(rank_db_ptr,key,data,GDBM_INSERT);
}
void print_rank(WINDOW *win_ptr)
{
	wclear(win_ptr);
	box(win_ptr,ACS_VLINE,ACS_HLINE);
	char rank_list[MAX_RANK_RECORD][STR_LEN];
	rank_entry temp_data;
	int count;
	int index=0;
	int startrow=2;
	int startcol=8;
	gdbm_count(rank_db_ptr,(gdbm_count_t *)&count);
	for(index=1;index<=count;index++)
	{
		temp_data=get_rank_entry(index);
		sprintf(rank_list[index-1],"%d -- name:%s\tscore:%d",index,temp_data.rank_name,temp_data.rank_point);
	}
	if(count==0)
		mvwprintw(win_ptr,WINDOW_HEIGHT/2,startcol,"There are no rank list.");
	else
	{
		for(int i=0;i<count;i++)
		{
			mvwprintw(win_ptr,startrow,startcol,"%s",rank_list[i]);
			startrow+=2;
		}
	}
	wrefresh(win_ptr);
}
void save_rank(char *name,int point)
{
	int count;
	int index=0;
	datum key,data;
	rank_entry *array;
	rank_entry temp;
	strncpy(temp.rank_name,name,STR_LEN);
	temp.rank_point=point;
	gdbm_count(rank_db_ptr,(gdbm_count_t *)&count);
	if(count<MAX_RANK_RECORD)
		count+=1;
	else
		count=MAX_RANK_RECORD;
	array=malloc(sizeof(rank_entry)*count);

	key=gdbm_firstkey(rank_db_ptr);
	while(key.dptr!=NULL)
	{
		data=gdbm_fetch(rank_db_ptr,key);
		memcpy(&array[index],data.dptr,data.dsize);
		index++;
		key=gdbm_nextkey(rank_db_ptr,key);
	}
	array[count-1]=temp;
	qsort(array,count,sizeof(rank_entry),cmprank);
	rank_db_init(true);
	for(index=0;index<count;index++)
	{
		add_rank_entry(array[index],index+1);
	}

	rank_db_close();
}

 得分榜最多存储并展示9条得分信息,满9条时将覆盖最低得分。在save_rank函数中每次存入得分信息都会删除并重新创建得分榜文件(可以优化),得分榜文件按从大到小的顺序存储得分信息。

数据

 取消全局变量,调整每个窗口的尺寸

下一步

完成存档功能,重写get_key,随着speed level的提升会产生复数个食物。

完整的代码在仓库内查看


  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值