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_positon
,set_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. 游戏控制
关于操作的定义,我们已经在上文中说明了,但是在具体控制中,还有部分细节问题要处理
- 连续按同一个方向键,蛇加速
- 蛇不能立刻反向移动,比如蛇正在向左运动,即使你先按按下右移键,蛇也不会立刻右转
- 加速中的蛇方面变化后则回复正常移速
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.游戏菜单实现