再来一次的C语言贪吃蛇小游戏(二)

5.游戏地图

贪吃蛇的游戏地图由一个个方格组成,每一格方格可以表示地面,墙,食物,蛇的身体等等,蛇只能在地面上行动,吃到食物则生长一节蛇身,撞到墙就GG。

我们可以同样可以使用枚举来表示地图上的不同物体

//game.h
typedef enum stuff_mark 
{
	wall_mark,
	ground_mark,
	food_mark,
	snake_mark
}Stuff_Mark;

为了便于表示我们创建一个物品结构体,用来保存地图上每个物理的位置,和物体的类别

//game.h
typedef struct stuff {
	int pos_x;
	int pos_y;
	Stuff_Mark mark;
}Stuff;

这样一来整个游戏的表示框架就定好了。

//game.h
//游戏当前状态
typedef enum state {
	in_menu,
	gaming,
	to_quit
}State;

//WIDTH, HEIGHT为宏定义的地图宽高,根据喜好设置
struct game 
{
	int score;
	Stuff map[HEIGHT][WIDTH];
	Stuff *food;
	State state;
};

为什么要单独保存食物在游戏结构体中呢?map中不是保存了地图上所有相关信息吗?不急,预计下下一篇揭晓哈。

关于游戏中的操作,暂时我们定义操作行为可以有:

  • 上移:move_up,对应w
  • 下移:move_down,对应s
  • 左移:move_left,对应a
  • 右移:move_right,对应d
  • 退出:esc,对应Esc
  • 无操作:no_option

为了对操作进行规范,我们也定义为枚举类型

//game.h
typedef enum option
{
	move_up = 'w',
	move_left = 'a',
	move_down = 's',
	move_right = 'd',
	esc = 27,
	no_option = 0
}Option;

6. 初始化游戏,在win32控制台显示出来

关于游戏中要展示的物体,我们都已经定义好了,那么怎么样展示出来呢?

//game.h
void start_game();
void init_map(struct game* Game);
void display_map(struct game* Game);
void display_snake(struct game *Game, Snake *snake);
void display_mark(Stuff *stuff);
void grow_food(struct game*Game);
void snake_move(struct game*Game, Snake* snake, Direction dir);
void judge_move_input(struct game*Game, Snake* snake, int* input, int* last_input);

首先解释一下这个display_mark这个方法,这个方法的唯一功能就是在一个物体所在的地方,输出表示该物体的符号,通过调用此方法,我们可以展示游戏中的所有内容,set_cursor_positonset_console_color分别实我自定义的控制台函数,用来设置控制台输出位置和颜色。相关代码在本系列(三)所提供的源码中。

//game.c
void display_mark(Stuff *stuff)
{
	set_cursor_position(stuff->pos_x, stuff->pos_y);
	switch (stuff->mark)
	{
	case wall_mark:
		set_console_color(6, 0);
		printf("##");
		break;
	case ground_mark:
		set_console_color(7, 0);
		printf("  ");
		break;
	case food_mark:
		set_console_color(4, 0);
		printf("@");
		break;
	case snake_mark:
		set_console_color(2, 0);
		printf("■");
		break;
	default:
		break;
	}
}

游戏初始化操作

//game.h
void init_map(struct game* Game)
{
	for (int i = 0; i < WIDTH; i++) 
	{
		for (int j = 0; j < HEIGHT; j++)
		{
			Game->map[j][i].pos_x = i;
			Game->map[j][i].pos_y = j;
			if (j == 0 || j == HEIGHT-1 || i==0 || i==WIDTH-1)
			{
				Game->map[j][i].mark = wall;
			}
			else Game->map[j][i].mark = ground;
		}
	 }
}

void grow_food(struct game* Game)
{
	int pos_x = rand() % WIDTH;
	int pos_y = rand() % HEIGHT;
	Stuff *stuff = &Game->map[pos_y][pos_x];
	stuff->mark = food_mark;
	Game->food = stuff;
	display_mark(stuff);
}

void display_snake(struct game*Game, Snake *snake) 
{
	Snake_Body_Node *head = snake->head;
	Stuff *stuff;
	while (head->next_node != head)
	{
		stuff = &Game->map[head->pos_y][head->pos_x];
		stuff->mark = snake_mark;
		display_mark(stuff);
	}
	stuff = &Game->map[head->pos_y][head->pos_x];
	stuff->mark = snake_mark;
	display_mark(stuff);
}

当蛇移动的时候,我们在操作蛇的链表时并没有改变全部的节点,同时我们在显示蛇的时候,也没有必要更新所有蛇身的显示,我们只需要更新头尾的显示即可

void snake_move(struct game*Game, Snake* snake, Direction dir) 
{
	Stuff *stuff;
	int pos_x = snake->head->pos_x;
	int pos_y = snake->head->pos_y;
	pos_x = dir == left ? pos_x - 1 : dir == right ? pos_x + 1 : pos_x;
	pos_y = dir == up ? pos_y - 1 : dir == down ? pos_y + 1 : pos_y;
	switch (Game->map[pos_y][pos_x].mark)
	{
	case ground_mark:
		stuff = &Game->map[pos_y][pos_x];
		stuff->mark = snake_mark;
		display_mark(stuff);
		stuff = &Game->
			map[snake->head->previous_node->pos_y][snake->head->previous_node->pos_x];
		stuff->mark = ground_mark;
		display_mark(stuff);
		move(snake, dir, pos_x, pos_y);
		break;
	case food_mark:
		stuff = &Game->map[pos_y][pos_x];
		stuff->mark = snake_mark;
		display_mark(stuff);
		eat(snake, dir, pos_x, pos_y);
		grow_food(Game);
		Game->score++;
	case snake_mark:
		exit(1);
		break;
	case wall_mark:
		exit(1);
		break;
	default:
		break;
	}
}

最初效果如下:■■■■表示蛇,@表示食物

7. 游戏控制

关于操作的定义,我们已经在上文中说明了,但是在具体控制中,还有部分细节问题要处理

  1. 连续按同一个方向键,蛇加速
  2. 蛇不能立刻反向移动,比如蛇正在向左运动,即使你先按按下右移键,蛇也不会立刻右转
  3. 加速中的蛇方面变化后则回复正常移速
void judge_move_input(struct game*Game, Snake* snake, int* input, int* last_input)
{
	if (*last_input + *input == move_down + move_up ||
		*last_input + *input == move_left + move_right)
	{
		snake_move(Game, snake, snake->head->dir);
		return;
	}
	else if (*last_input == *input)
	{
		snake->speed = fast;
	}
	else
	{
		snake->speed = normal;
		*last_input = *input;
	}
	Direction dir = *input == move_up ? up : *input == move_down ?
		down : *input == move_left ? left : right;
	snake_move(Game, snake, dir);
}

总的游戏循环如下

void start_game() 
{
	struct game *Game = (struct game*)malloc(sizeof(struct game));
	Snake *snake = new_born_snake(5, 5);
	Game->score = 0;
	Game->state = gaming;
	init_map(Game);
	display_map(Game);
	display_snake(Game, snake);
	grow_food(Game);
	int input = no_option;
	int last_input = no_option;
	while (Game->state == gaming)
	{
		if (_kbhit())
		{
			input = _getch();
			FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));
		}
		else input = no_option;
		switch (input)
		{
		case move_up:
		case move_right:
		case move_down:
		case move_left:
			judge_move_input(Game, snake, &input, &last_input);
			break;
		case esc:
			Game->state = back_to_menu;
			break;
		case no_option:
		default:
			snake_move(Game, snake, snake->head->dir);
			break;
		}
	}
	free(Game);
}

8.游戏菜单实现

下一篇链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hack Rabbit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值