一、贪吃蛇棋盘图
【实现步骤】
- 定义头文件,在头文件中分别定义如下内容
- 定义游戏棋盘的宽度和高度
- 定义组成蛇身体的节点结构体(结构体中有横纵坐标)
- 定义蛇的结构体(蛇的身体是由若干个节点组成,思考,蛇的身体最多有多少个节点
- 定义分数变量
- 定义记录蛇尾的位置的横纵坐标
- 定义蛇头移动的偏移量
// windows 下防止头文件多次引用的方式
// 功能类似#ifndef
#pragma once
// 游戏棋盘的列数(宽度)
#define WIDTH 60 // 横向60个格子
// 游戏棋盘的行数(高度)
#define HEIGHT 20 // 纵向20个格子
// 定义组成蛇身体的格子(节点)结构体
struct BODY {
// 每个节点都会有位置
int X;// 节点的横坐标
int Y;// 节点的纵坐标
};
// 定义蛇的结构体
struct SNAKE {
// 定义蛇当前的尺寸(当前,蛇的身体,是由几个格子组成)
int size;
// 蛇是由一个个节点构成的
// 定义节点结构体数组,用来存放所有的系欸但
// 对于蛇而言,最多铺满整个游戏棋盘
struct BODY body[WIDTH * HEIGHT];
} ;
// 偏移的坐标:
int dx = 0;// 横坐标的偏移量
int dy = 0;// 纵坐标的偏移量
// 记录蛇尾的坐标
int lx = 0;
int ly = 0;
// 食物
int food[2] = { 0 }; // food[0] 为横坐标 food[1]为纵坐标
// 分数
int score = 0;
// 声明初始化墙壁的函数
void InitWall();
// 声明初始化蛇的函数
void InitSnake();
// 声明显示界面的函数
void ShowUI();
void InitFood();//初始化食物的函数
// 声明开始游戏的函数
void PlayGame();
2. 新建 snack.c 源文件,完成游戏的业务功能
- 在源文件中定义初始化墙壁的 InitWall 函数,注意打印游戏边界,然后在头文件中声明 InitWall 函数,最后在 main 函数中调用此函数
- 在源文件中定义开始游戏的函数 PlayGame
- 设置蛇默认的移动方向
- 在 ShowUI 中更新蛇尾,将原来蛇尾的位置替换成空格
- 注意在头文件中声明 PlayGame ,并在 main 函数中调用该函数
- 如果蛇吃到了自己(蛇头的位置和蛇身体某个节点的位置重叠了),就 return,结束 PlayGame 函数,结束游戏
- 根据蛇头偏移量设置蛇头的位置
- 让蛇后一个节点替换前一个节点位置,让蛇移动起来
- 记录蛇尾的位置
- 获取键盘的按键,根据按键计算蛇头的偏移量
- 如果蛇没有碰到边界,就一直循环游戏,每循环一次刷新一下页面,重新调用 showUI 函数
2. 完成显示食物功能
在头文件中定义食物 Food,int 类型的数组,food[0] 表示横坐标,food[1] 表示纵坐标
在 PlayGame 中判断是否吃到了食物,如果吃到食物,蛇的长度增加,并重新调用 InitFood 生成食物
在 ShowUI 中将光标定位到食物的位置上,并打印食物
在源文件中定义初始化食物的函数 InitFood,随机生成食物的横纵坐标,注意函数的声明和调用
3. 显示分数
- 在 showUI 中将光标定位到需要显示分数的位置,并打印分数每次吃到食物后分数增加
【参考代码】
#include<stdio.h>
#include "snake.h"
#include <Windows.h>
#include <conio.h>
#include <stdlib.h>
#include<time.h>
// 定义蛇的结构体变量
struct SNAKE snake;
int main() {
// 隐藏光标的固定格式
CONSOLE_CURSOR_INFO cci;
// 设置光标的尺寸
cci.dwSize = sizeof(cci);
// 将光标的可见性设置为FALSE;(不展示)
cci.bVisible = FALSE;
// 设置光标的信息
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);
// 初始化棋盘的墙壁
InitWall();
// 初始化蛇
InitSnake();
// 初始化食物
InitFood();
// 操控游戏的函数
PlayGame();
// 显示页面
//ShowUI();
return 0;
}
// 食物的位置随机
void InitFood() {
// 设置随机数种子
srand(time(0));
// 在游戏棋盘的返回内随机生成食物横纵坐标
food[0] = rand() % WIDTH;// 宽度范围内随机生成横坐标
food[1] = rand() % HEIGHT;// 高度范围内随机生成纵坐标
}
// 初始化蛇
void InitSnake() {
//游戏开始时蛇的长度2
// 初始化蛇的尺寸
snake.size = 2;
// 根据蛇的尺寸,设置蛇每个节点的位置
// 数组中第一个节点,看作蛇头
// 设置第一个节点的横坐标
snake.body[0].X = WIDTH / 2;
// 设置第一个节点的纵坐标
snake.body[0].Y = HEIGHT / 2;
// 设置第二个节点的横坐标
snake.body[1].X = WIDTH / 2 - 1;
// 设置第二个节点的纵坐标
snake.body[1].Y = HEIGHT / 2;
}
// 显示界面的函数
void ShowUI() {
// 定义字符光标在控制台位置的结构体变量
COORD coord; // 结构体中是光标当前的横纵坐标
// 每次刷新页面,找到原来蛇尾的位置
coord.X = lx;
coord.Y = ly;
// 将光标定位到蛇尾的位置
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
// 将原来的蛇尾替换成空格
putchar(' ');
// 需要在哪个位置上打印,就需要将光标移动到哪个位置上
// 遍历蛇的身体节点,找到每个节点的位置,并在节点位置上打印蛇
for (int i = 0; i < snake.size; i++) {
// 设置光标当前的位置
coord.X = snake.body[i].X;
coord.Y = snake.body[i].Y;
// 将光标移动到对应的位置上
// 固定格式
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
// i= 0,第一节点表示蛇头
if (i == 0) {
putchar('O'); // 打印蛇头
}
else {
putchar('*');
}
}
// 画食物
// 找到食物的位置
coord.X = food[0];
coord.Y = food[1];
// 将光标移动到食物的位置上
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
// 画个字符代表食物
putchar('@');
// 打印分数
coord.X = 0;
coord.Y = 21;
// 跳转光标
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
printf("分数为:%d",score);
}
// 初始化墙壁的函数
// 打印棋盘边界
void InitWall() {
// i 表示棋盘的行号
for (int i = 0; i <= HEIGHT; i++) { // 此处=多一次循环是用来打印墙壁
// j表示棋盘的列号
for (int j = 0; j <= WIDTH; j++) { // 此处= 多一次循环是用来打印墙壁
// 在最后一行打印下边界
if (i == HEIGHT) {
putchar('=');
}
else if (j == WIDTH) { // 在最右侧打印右边界
putchar('|');
}
else { // 其他格子打印空字符,表示蛇能够活动的范围
putchar(' ');
}
}
putchar('\n');
}
}
// 开始游戏的函数
void PlayGame() {
// 'A'左、'D'右、'S'下、'W'上
char key = 'D'; //向右移动
// 如果蛇在棋盘内,没有撞到墙壁,让游戏持续运行(先不考虑吃到自己)
while (snake.body[0].X >= 0 && snake.body[0].X < WIDTH
&& snake.body[0].Y >= 0 && snake.body[0].Y < HEIGHT) {
// 刷新界面
ShowUI();
// _kbhit() 持续进行监听,直到获取按键结束当前监听
while (_kbhit()) {
key = _getch();
} // 获取到按键之后,就会结束循环
switch (key) {
case 'D': case 'd':
//向右移动
// 每次移动横坐标向右偏移1
dx = 1;dy = 0;break;
case 'A': case 'a':
// 向左移动
dx = -1;dy = 0;break;
case 'W':case 'w':
// 向上移动
dy = -1;dx = 0;break;
case 'S':case 's':
// 向下移动
dx = 0;dy = 1;break;
}
// 蛇头是否吃到了食物
//1. 判断蛇头和食物的位置是否重叠了,如果重叠了说明吃到了
if (snake.body[0].X == food[0] && snake.body[0].Y == food[1]) {
// 2. 如果吃到了
// 1) 重新随机食物
InitFood();
// 2) 蛇的长度增加
snake.size++;
// 3)增长分数
score++;
}
// 蛇头是否咬到了自己(除了蛇头以外的其他节点)
// 排除蛇头,此处i从1开始
for (int i = 1; i < snake.size; i++) {
if (snake.body[0].X == snake.body[i].X && snake.body[0].Y == snake.body[i].Y) {
// 进入到if语句中,说明蛇头咬到了自己
return;
}
}
// 移动之前先记录蛇尾的坐标
lx = snake.body[snake.size - 1].X;
ly = snake.body[snake.size - 1].Y;
// 蛇移动,实际上就是后面的一个节点,要顶替前面一个节点的位置
// 除去蛇头(蛇头控制方向)
// 蛇移动,实际上就是后面一个节点,要顶替前一个节点的位置
for (int i = snake.size - 1; i > 0; i--) {
snake.body[i].X = snake.body[i - 1].X;
snake.body[i].Y = snake.body[i - 1].Y;
}
// 更新蛇头的位置
snake.body[0].X += dx;
snake.body[0].Y += dy;
// CPU处理速度非常快,可能一运行代码,蛇就撞墙了
// 所以此处要减缓循环的速度
// 让程序休眠一段时间,再开启下一次循环
// 当程序执行到这行代码时会休眠一段时间,然后再运行
Sleep(200); // 单位:毫秒
}
}