【C语言】贪吃蛇

前置知识

  • 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语言中如何生成某个范围的随机数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值