前置知识
- C 语言进阶(结构体,指针)、链表。
具体实现
函数声明及全局变量
#include <stdio.h> // 控制台输入输出
#include <stdlib.h> // malloc 动态分配内存
#include <conio.h> // 检测键盘按下
#include <windows.h> // 设置控制台的相关参数
#include <time.h> // 控制主循环运行速度和产生随机数种子
#define length 60 // 长(列数)
#define wide 30 // 宽(行数)
typedef struct Node {
int x;
int y;
struct Node *next;
} Node;
void pos(int x, int y); // 改变控制台光标位置
void windows(); // 设置游戏窗口
void welcome(); // 欢迎界面
void get_random_position(int *x, int *y); // 获取随机坐标
int inSnake(int x, int y); // 判断坐标是否在蛇身上
void create_food(); // 生成食物
void init_snake(); // 初始化蛇节点
void move_snake(); // 蛇的移动
void create_wall(); // 生成墙体
Node *head = NULL; // 蛇的头节点指针
Node food;
int dx[4] = {0, 0, -1, 1}; // 通过两个分别对应 x 轴和 y 轴的数组,可以使用 rection 变量方便的进行移动时的运算
int dy[4] = {-1, 1, 0, 0}; // 例:rection = 0, 则 (dx[0], dy[0]) -> x + 0, y - 1 -> 向上移动一格
int direction = -1; // 表示前进方向(0上、1下、2左、3右)
int fail = 0; // 判断游戏是否结束
int score = 0, food_garde = 10; // 当前分数和单个食物的分数
int re_create_food = 1; // 判断是否生成食物
控制光标位置
void pos(int x, int y) { // 改变控制台光标位置
COORD pos; // 位置坐标
pos.X = x;
pos.Y = y;
HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
SetConsoleCursorPosition(handleOutput, pos); // 设置控制台光标位置
/*
句柄:是一种数据结构或对象,用于标识或引用其他对象或资源。
它通常是一个整数或指针,是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控件、位图、GDI对象、资源、文件等 [4]。
获取标准输出句柄,用来控制 控制台的相关参数
*/
}
设置游戏窗口
void windows() { // 设置游戏窗口
system("title 贪吃蛇"); // 设置窗口标题
char cmd[40];
sprintf(cmd,"mode con cols=%d lines=%d", length + 10, wide + 10); // 设置窗口的长度与宽度
system(cmd);
HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handleOutput, &CursorInfo); // 获取控制台光标
CursorInfo.bVisible=0; // 隐藏控制台光标
SetConsoleCursorInfo(handleOutput, &CursorInfo); // 设置控制台光标
}
欢迎界面
void welcome(){ // 欢迎界面
printf("\n\n\twelcome come to SnakeGame\n\n");
printf("\t↑ ↓ ← → control direction\n\n");
printf("\tESC For Exit\n\n");
printf("\tEnter For Begin\n\n");
getchar(); //读取Enter字符进入游戏界面
system("cls"); //清除控制台屏幕
}
获取随机坐标
void get_random_position(int *x, int *y) { // 获取随机坐标
srand((unsigned int)time(NULL)); //随着时间变化产生不同种子
//保证坐标在边界内
*x = rand() % (length - 1) + 1; // 获得 1 ~ length - 1 之间的随机数
*y = rand() % (wide - 1) + 1;
}
判断坐标是否在蛇身上
int inSnake(int x, int y) { // 判断坐标是否在蛇身上
Node *tp = head->next;
while(tp){ //从头遍历蛇身
if(x == tp -> x && y == tp -> y) {
return 1;
}
tp = tp -> next;
}
return 0;
}
生成食物
void create_food() { // 生成食物
int x, y;
do {
get_random_position(&x, &y);
} while (inSnake(x, y) == 1); // 循环判断获取到的随机坐标是否在蛇身上
food.x = x;
food.y = y;
pos(x, y);
printf("@");
}
初始化蛇节点
void init_snake() { // 初始化蛇节点
Node *new_node = (Node*)malloc(sizeof(Node));
new_node -> next = NULL;
new_node -> x = length / 2;
new_node -> y = wide / 2;
head = new_node;
pos(head -> x, head -> y);
printf("*");
}
蛇的移动
void move_snake() { // 蛇的移动
Node *new_node = (Node*)malloc(sizeof(Node));
new_node -> x = head -> x + dx[direction];
new_node -> y = head -> y + dy[direction];
new_node -> next = head;
head = new_node;
if (inSnake(head -> x, head -> y) || head -> x == 0 || head -> x == length || head -> y == 0 || head -> y == wide) { // 判断是否撞到墙和撞到蛇身
fail = 1;
} else {
pos(head -> x, head -> y);
printf("*");
}
if (head -> x == food.x && head -> y == food.y && re_create_food == 0) { // 判断吃到食物
score = score + food_garde;
re_create_food = 1;
} else { // 当没吃到食物时,每次移动要把蛇尾覆盖掉
Node *tp = head;
while(tp -> next -> next != NULL) { // 定位到蛇尾的上一个节点
tp = tp -> next;
}
pos(tp -> next -> x, tp -> next -> y);
printf(" "); // 覆盖蛇尾
free(tp -> next);
tp -> next = NULL;
}
/*
移动的实现思路:每次移动时,都以新位置的坐标创建节点并让它的下一项指向头结点,然后头结点指针指向新节点,
释放并覆盖尾节点。可以看成每次移动时,都把尾巴移到了头部。
这样子移动可以不用每次都重新完整的绘制一遍蛇身,并且不用挨个计算蛇节点的位置。
*/
}
生成墙体
void create_wall() { // 生成墙体
pos(0, 0);
printf("+");
pos(0, wide);
printf("+");
pos(length, 0);
printf("+");
pos(length, wide);
printf("+");
for (int i = 1; i < length; i++) {
pos(i, 0);
printf("-");
pos(i, wide);
printf("-");
}
for (int j = 1; j < wide; j++) {
pos(0, j);
printf("|");
pos(length, j);
printf("|");
}
}
程序展示
完整代码(Gluttonous Snake.c)
#include <stdio.h> // 控制台输入输出
#include <stdlib.h> // malloc 动态分配内存
#include <conio.h> // 检测键盘按下
#include <windows.h> // 设置控制台的相关参数
#include <time.h> // 控制主循环运行速度和产生随机数种子
#define length 60 // 长(列数)
#define wide 30 // 宽(行数)
typedef struct Node {
int x;
int y;
struct Node *next;
} Node;
void pos(int x, int y); // 改变控制台光标位置
void windows(); // 设置游戏窗口
void welcome(); // 欢迎界面
void get_random_position(int *x, int *y); // 获取随机坐标
int inSnake(int x, int y); // 判断坐标是否在蛇身上
void create_food(); // 生成食物
void init_snake(); // 初始化蛇节点
void move_snake(); // 蛇的移动
void create_wall(); // 生成墙体
Node *head = NULL; // 蛇的头节点指针
Node food;
int dx[4] = {0, 0, -1, 1}; // 通过两个分别对应 x 轴和 y 轴的数组,可以使用 rection 变量方便的进行移动时的运算
int dy[4] = {-1, 1, 0, 0}; // 例:rection = 0, 则 (dx[0], dy[0]) -> x + 0, y - 1 -> 向上移动一格
int direction = -1; // 表示前进方向(0上、1下、2左、3右)
int fail = 0; // 判断游戏是否结束
int score = 0, food_garde = 10; // 当前分数和单个食物的分数
int re_create_food = 1; // 判断是否生成食物
int main() {
windows();
welcome();
create_wall();
init_snake();
while (fail == 0) { // 整个游戏都处在一个大循环中,持续的进行按键检测,直到游戏结束
if (re_create_food == 1) {
create_food();
re_create_food = 0;
}
if (_kbhit()) { // 判断是否按键按下
int key = (int)_getch();
// 改变移动方向,并且不能反方向移动
if (key == 72 && direction != 1) { // 上
direction = 0;
} else if (key == 80 && direction != 0) { // 下
direction = 1;
} else if (key == 75 && direction != 3) { // 左
direction = 2;
} else if (key == 77 && direction != 2) { // 右
direction = 3;
} else if (key == 27) { // Esc
fail = 1;
}
}
if (direction != -1) {
move_snake();
}
pos(0, wide + 1);
printf("得分:%d", score);
Sleep(250); // 主动控制循环速度,防止蛇的移动过快
}
pos(0, wide + 1);
printf(" ");
return 0;
}
void pos(int x, int y) { // 改变控制台光标位置
COORD pos; // 位置坐标
pos.X = x;
pos.Y = y;
HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
SetConsoleCursorPosition(handleOutput, pos); // 设置控制台光标位置
/*
句柄:是一种数据结构或对象,用于标识或引用其他对象或资源。
它通常是一个整数或指针,是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控件、位图、GDI对象、资源、文件等 [4]。
获取标准输出句柄,用来控制 控制台的相关参数
*/
}
void windows() { // 设置游戏窗口
system("title 贪吃蛇"); // 设置窗口标题
char cmd[40];
sprintf(cmd,"mode con cols=%d lines=%d", length + 10, wide + 10); // 设置窗口的长度与宽度
system(cmd);
HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handleOutput, &CursorInfo); // 获取控制台光标
CursorInfo.bVisible=0; // 隐藏控制台光标
SetConsoleCursorInfo(handleOutput, &CursorInfo); // 设置控制台光标
}
void welcome(){ // 欢迎界面
printf("\n\n\twelcome come to SnakeGame\n\n");
printf("\t↑ ↓ ← → control direction\n\n");
printf("\tESC For Exit\n\n");
printf("\tEnter For Begin\n\n");
getchar(); //读取Enter字符进入游戏界面
system("cls"); //清除控制台屏幕
}
void get_random_position(int *x, int *y) { // 获取随机坐标
srand((unsigned int)time(NULL)); //随着时间变化产生不同种子
//保证坐标在边界内
*x = rand() % (length - 1) + 1; // 获得 1 ~ length - 1 之间的随机数
*y = rand() % (wide - 1) + 1;
}
int inSnake(int x, int y) { // 判断坐标是否在蛇身上
Node *tp = head->next;
while(tp){ //从头遍历蛇身
if(x == tp -> x && y == tp -> y) {
return 1;
}
tp = tp -> next;
}
return 0;
}
void create_food() { // 生成食物
int x, y;
do {
get_random_position(&x, &y);
} while (inSnake(x, y) == 1); // 循环判断获取到的随机坐标是否在蛇身上
food.x = x;
food.y = y;
pos(x, y);
printf("@");
}
void init_snake() { // 初始化蛇节点
Node *new_node = (Node*)malloc(sizeof(Node));
new_node -> next = NULL;
new_node -> x = length / 2;
new_node -> y = wide / 2;
head = new_node;
pos(head -> x, head -> y);
printf("*");
}
void move_snake() { // 蛇的移动
Node *new_node = (Node*)malloc(sizeof(Node));
new_node -> x = head -> x + dx[direction];
new_node -> y = head -> y + dy[direction];
new_node -> next = head;
head = new_node;
if (inSnake(head -> x, head -> y) || head -> x == 0 || head -> x == length || head -> y == 0 || head -> y == wide) { // 判断是否撞到墙和撞到蛇身
fail = 1;
} else {
pos(head -> x, head -> y);
printf("*");
}
if (head -> x == food.x && head -> y == food.y && re_create_food == 0) { // 判断吃到食物
score = score + food_garde;
re_create_food = 1;
} else { // 当没吃到食物时,每次移动要把蛇尾覆盖掉
Node *tp = head;
while(tp -> next -> next != NULL) { // 定位到蛇尾的上一个节点
tp = tp -> next;
}
pos(tp -> next -> x, tp -> next -> y);
printf(" "); // 覆盖蛇尾
free(tp -> next);
tp -> next = NULL;
}
/*
移动的实现思路:每次移动时,都以新位置的坐标创建节点并让它的下一项指向头结点,然后头结点指针指向新节点,
释放并覆盖尾节点。可以看成每次移动时,都把尾巴移到了头部。
这样子移动可以不用每次都重新完整的绘制一遍蛇身,并且不用挨个计算蛇节点的位置。
*/
}
void create_wall() { // 生成墙体
pos(0, 0);
printf("+");
pos(0, wide);
printf("+");
pos(length, 0);
printf("+");
pos(length, wide);
printf("+");
for (int i = 1; i < length; i++) {
pos(i, 0);
printf("-");
pos(i, wide);
printf("-");
}
for (int j = 1; j < wide; j++) {
pos(0, j);
printf("|");
pos(length, j);
printf("|");
}
}
参考资料
C语言实现贪吃蛇(详细版)
c语言windows环境下隐藏光标
设置c语言控制台大小问题
[c语言]在程序中检测键盘按键
C语言中如何生成某个范围的随机数