C游戏开发——贪吃蛇

引言:

就是练手用的,这个真没什么好说的了。

代码:

头文件(snake.h)

#pragma once
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>

// 宏定义
#define MAP_WIDTH 60								// 地图宽度
#define MAP_HEIGHT 30								// 地图高度
#define BRICK "□"									// 四周的墙
#define GRID "■"									// 内部的网格
#define SNAKE_MAX_SIZE (MAP_WIDTH / 2 * MAP_HEIGHT)	// 蛇最大节数 
#define SNAKE_INIT_SIZE 3							// 蛇初始化节数
#define SNAKE_HEAD "★"								// 蛇头
#define SNAKE_BODY "⊙"								// 蛇身体
#define FOOD "◆"									// 食物

// 方向枚举
typedef enum _enum_direction
{
	DIR_UP,			// 上
	DIR_DOWN,		// 下
	DIR_LEFT,		// 左
	DIR_RIGHT		// 右
} enum_direction;

// 游戏状态枚举
typedef enum _enum_status
{
	RUNNING,
	PAUSE,
	GAMEOVER
} enum_status;

// 蛇身结构
typedef struct _stu_body
{
	int x;					// 蛇身X坐标
	int y;					// 蛇身Y坐标
} stu_body;

// 蛇结构体
struct _stu_snake
{
	int size;						// 实际大小
	stu_body body[SNAKE_MAX_SIZE];	// 蛇身(body[0]为蛇头)位置
	enum_direction direction;		// 运动方向
	enum_status status;				// 游戏状态
} snake;

// 食物结构体
struct _stu_food
{
	int x;
	int y;
} food;

// 公用变量
int gTopScore = 0;		// 最高分数
int gMyScore = 0;		// 我的分数
int gSpeed = 300;		// 原始速度
int gLevelSpeed = 300;	// 第一关速度(每吃10个增加一次速度)
int gCurSpeed = 300;	// 当前速度
int gMaxSpeed = 50;		// 最大速度

// 函数声明
void gameInit();							// 游戏初始化
void hideCursor();							// 隐藏光标
void welcome();								// 欢迎界面
void setCursorPosition(int x, int y);		// 设置光标所在位置(x,y)
void setFontColor(int color);				// 设置字体颜色
void gameStart();							// 游戏开始
void drawMap();								// 画地图
void drawScore();							// 画得分
void drawTip();								// 画游戏提示
void drawSnake();							// 画蛇
void drawFood();							// 画食物
void keyPress();							// 键盘事件
void moveSnake();							// 蛇移动
void pause();								// 游戏暂停
void eatFood();								// 吃食物
int collide();								// 蛇碰撞
int gameOver();								// 游戏结束
int getTopScore();							// 读取记录
void setTopScore(int score);				// 保存记录

主文件(snake.c)

#include "snake.h"

// 贪吃蛇
int main()
{
	// 欢迎界面
	welcome();

	// 游戏初始化
	gameInit();

	// 游戏开始
	gameStart();

	return 0;
}

// 欢迎界面
void welcome()
{
	// 隐藏光标
	hideCursor();

	// 设置窗口标题
	SetConsoleTitle("贪吃蛇");	// 如果用visual studio软件则需要项目属性中的字符集(使用 Unicode 字符集 -> 使用多字节字符集)

	// 设置窗口大小像素(宽100,高30)[利用dos命令]
	system("mode con cols=96 lines=30");

	// 设置窗口背景与文字颜色(可以在dos下用命令color -h查看)
	//system("color 8F");

	// 设置操作说明
	setCursorPosition(40, 8);
	printf("欢迎来到贪吃蛇的世界");
	setCursorPosition(33, 12);
	printf("提示:↑ ↓ ← → 控制贪吃蛇方向");
	setCursorPosition(30, 16);
	printf("     空格暂停或继续游戏,ESC退出游戏");
	setCursorPosition(41, 20);
	system("pause");
	system("cls");
}

// 游戏初始化
void gameInit()
{
	// 随机种子
	srand((unsigned int)time(NULL));

	// 蛇初始化
	snake.size = SNAKE_INIT_SIZE;
	snake.direction = DIR_RIGHT;
	snake.status = RUNNING;

	// 速度初始化
	gSpeed = 300;
	gLevelSpeed = 300;
	gCurSpeed = 300;
	gMaxSpeed = 50;

	// 得分初始化
	gTopScore = getTopScore();
	gMyScore = 0;
}

// 隐藏光标
void hideCursor()
{
	// 得到标准的输入(STD_INPUT_HANDLE)、输出(STD_OUTPUT_HANDLE)或错误(STD_ERROR_HANDLE)的设备的句柄
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

	// 光标信息变量(typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;WINBOOL bVisible;} CONSOLE_CURSOR_INFO)
	CONSOLE_CURSOR_INFO cursorInfo;

	// 获取当前光标信息
	GetConsoleCursorInfo(hOut, &cursorInfo);

	// 设置当前光标不可见
	cursorInfo.bVisible = 0;
	SetConsoleCursorInfo(hOut, &cursorInfo);
}

// 设置光标所在位置
void setCursorPosition(int x, int y)
{
	// 文字在控制台的坐标结构变量
	COORD pos = { x,y };

	// 设置控制台光标所在位置
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

// 游戏开始
void gameStart()
{
	// 画地图
	drawMap();

	// 画蛇
	drawSnake();

	// 画随机生成的食物
	drawFood();

	// 游戏运行中
	while (snake.status == RUNNING)
	{
		// 键盘事件
		keyPress();
		// 蛇移动
		moveSnake();
		// 吃食物
		eatFood();
		// 更新分数
		drawScore();
		// 休眠
		Sleep(gCurSpeed);
	}

	// 游戏结束
	if (gameOver() == 1)
	{
		system("cls");
		exit(-1);
	}
	else
	{
		gameInit();
		gameStart();
	}

}

// 画地图
void drawMap()
{
	/*== 重点:"■"与"□"都是长=2,宽=1的长方形(可以理解为一个汉字,占2个字节),而不是正方形啊。==*/

	// 清屏
	system("cls");

	setFontColor(2);	// 绿色
	// 画内部网格(30*30)
	for (int i = 0; i < MAP_WIDTH; i += 2)
	{
		for (int j = 0; j < MAP_HEIGHT; j++)
		{
			setCursorPosition(i, j);
			printf(GRID);
		}
	}

	setFontColor(6);	// 黄色
	// 画左右墙
	for (int i = 0; i < MAP_HEIGHT; i++)
	{
		setCursorPosition(0, i);
		printf(BRICK);
		setCursorPosition(MAP_WIDTH - 2, i);
		printf(BRICK);
	}

	// 画上下墙
	for (int i = 0; i < MAP_WIDTH; i += 2)
	{
		setCursorPosition(i, 0);
		printf(BRICK);
		setCursorPosition(i, MAP_HEIGHT - 1);
		printf(BRICK);
	}

	// 画得分
	drawScore();

	// 画提示
	drawTip();
}

// 画得分
void drawScore()
{
	setFontColor(15);	// 亮白色
	setCursorPosition(MAP_WIDTH + 4, 2);
	printf("最高得分:%5d", gTopScore);
	setCursorPosition(MAP_WIDTH + 4, 4);
	printf("我的得分:%5d", gMyScore);
	setCursorPosition(MAP_WIDTH + 4, 6);
	printf("我的速度:%5d米/秒", (2 * gSpeed - gCurSpeed));
}

// 画游戏提示
void drawTip()
{
	setFontColor(9);
	setCursorPosition(MAP_WIDTH + 2, 9);
	printf(" 游戏提示");
	setCursorPosition(MAP_WIDTH + 2, 10);
	printf("┌─────────────┐");
	setCursorPosition(MAP_WIDTH + 2, 11);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 12);
	printf("│每个食物得分:1 分        │");
	setCursorPosition(MAP_WIDTH + 2, 13);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 14);
	printf("│不能穿墙也不能咬到自己哦  │");
	setCursorPosition(MAP_WIDTH + 2, 15);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 16);
	printf("│用↑ ↓ ← → 控制蛇移动  │");
	setCursorPosition(MAP_WIDTH + 2, 17);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 18);
	printf("│同一方向连续点击可以加速  │");
	setCursorPosition(MAP_WIDTH + 2, 19);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 20);
	printf("│按空格可以暂停或继续游戏  │");
	setCursorPosition(MAP_WIDTH + 2, 21);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 22);
	printf("│按ESC键可以退出游戏       │");
	setCursorPosition(MAP_WIDTH + 2, 23);
	printf("│                          │");
	setCursorPosition(MAP_WIDTH + 2, 24);
	printf("└─────────────┘");
	setFontColor(6);
	setCursorPosition(MAP_WIDTH + 2, 26);
	printf(" 作者:我是小木鱼(Lag)");
	setCursorPosition(MAP_WIDTH + 2, 28);
	printf("                   2023.02.15");
}

// 画蛇
void drawSnake()
{
	// 画蛇头
	setFontColor(14);	// 淡黄色
	snake.body[0].x = (MAP_WIDTH / 2);
	if (snake.body[0].x % 2 != 0) { snake.body[0].x--; }		// x坐标必须是偶数
	snake.body[0].y = MAP_HEIGHT / 2;
	setCursorPosition(snake.body[0].x, snake.body[0].y);
	printf(SNAKE_HEAD);
	// 画蛇身
	for (int i = 1; i < SNAKE_INIT_SIZE; i++)
	{
		snake.body[i].x = snake.body[i - 1].x - 2;
		snake.body[i].y = snake.body[i - 1].y;
		setCursorPosition(snake.body[i].x, snake.body[i].y);
		printf(SNAKE_BODY);
	}
}

// 画食物
void drawFood()
{
	/*
	重点:X轴方向由于墙、网格、食物等都是长2宽1的长方体,
		 所以MAP_WIDTH=60即(0-59)可以划分为{(0-1),(2-3),(4-5)...,(56-57),(58-59)},
		 其中(0-1)是左边墙,(58-59)是右边墙,剩下{(2-3),(4-5)...,(56-57)},
		 所以食物的X坐标可以为2,4,6,8...,54,56这些数中的一个即可。
		 这些数不连续不好随机,因此改为(1,2,3,4...27.28)*2就可以了。
	*/

	// rand() % 10 + i => [0-10) + i
	food.x = (rand() % ((MAP_WIDTH - 4) / 2) + 1) * 2;	// 必须为偶数,否则蛇吃不到
	food.y = rand() % (MAP_HEIGHT - 2) + 1;
	// 判断是否与蛇重合
	while (1)
	{
		int flag = 0;
		for (int i = 0; i < snake.size; i++)
		{
			if (food.x == snake.body[i].x && food.y == snake.body[i].y)
			{
				flag = 1;
				// 重新随机食物
				food.x = (rand() % ((MAP_WIDTH - 4) / 2) + 1) * 2;
				food.y = rand() % (MAP_HEIGHT - 2) + 1;
				break;
			}
		}
		if (flag == 0) { break; }
	}
	setCursorPosition(food.x, food.y);
	setFontColor(12);
	printf(FOOD);
}

// 键盘事件
void keyPress()
{
	// 判断键盘
	if (GetAsyncKeyState(VK_LEFT))	// 左键
	{
		if (snake.direction != DIR_RIGHT)	// 禁止调头
		{
			if (snake.direction == DIR_LEFT)
			{
				if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; }	// 方向相同,临时加速
			}
			else
			{
				snake.direction = DIR_LEFT;
				if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; }		// 方向不同,恢复原来速度
			}
		}
	}
	else if (GetAsyncKeyState(VK_UP))	// 上键
	{
		if (snake.direction != DIR_DOWN)
		{
			if (snake.direction == DIR_UP)
			{
				if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; }	// 方向相同,临时加速
			}
			else
			{
				snake.direction = DIR_UP;
				if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; }		// 方向不同,恢复原来速度
			}
		}
	}
	else if (GetAsyncKeyState(VK_RIGHT))		// 右键
	{
		if (snake.direction != DIR_LEFT)
		{
			if (snake.direction == DIR_RIGHT)
			{
				if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; }	// 方向相同,临时加速
			}
			else
			{
				snake.direction = DIR_RIGHT;
				if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; }		// 方向不同,恢复原来速度
			}
		}
	}
	else if (GetAsyncKeyState(VK_DOWN))		// 下键
	{
		if (snake.direction != DIR_UP)
		{
			if (snake.direction == DIR_DOWN)
			{
				if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; }	// 方向相同,临时加速
			}
			else
			{
				snake.direction = DIR_DOWN;
				if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; }		// 方向不同,恢复原来速度
			}
		}
	}
	else if (GetAsyncKeyState(VK_SPACE))		// 空格暂停
	{
		pause();
	}
	else if (GetAsyncKeyState(VK_ESCAPE))	// 退出
	{
		snake.status = GAMEOVER;
	}
}

// 蛇移动
void moveSnake()
{
	// 判断该方向是否可以移动(要发生碰撞)
	if (collide() == 0) { return; }

	// 擦除最后一节
	setCursorPosition(snake.body[snake.size - 1].x, snake.body[snake.size - 1].y);
	setFontColor(2);
	printf(GRID);

	// 循环替换除蛇头外的其他节
	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;
		setCursorPosition(snake.body[i].x, snake.body[i].y);
		setFontColor(14);
		printf(SNAKE_BODY);
	}

	// 处理蛇头
	switch (snake.direction)
	{
		case DIR_UP:
			snake.body[0].y--;
			break;
		case DIR_DOWN:
			snake.body[0].y++;
			break;
		case DIR_LEFT:
			snake.body[0].x -= 2;
			break;
		case DIR_RIGHT:
			snake.body[0].x += 2;
			break;
	}
	setCursorPosition(snake.body[0].x, snake.body[0].y);
	setFontColor(14);
	printf(SNAKE_HEAD);
}

// 游戏暂停
void pause()
{
	while (1)
	{
		Sleep(300);
		if (GetAsyncKeyState(VK_SPACE)) { break; }		// 再按一次空格继续游戏
	}
}

// 吃食物
void eatFood()
{
	if (snake.body[0].x == food.x && snake.body[0].y == food.y)
	{
		// 蛇长一节
		if (snake.size < SNAKE_MAX_SIZE)
		{
			snake.body[snake.size].x = snake.body[snake.size - 1].x;
			snake.body[snake.size].y = snake.body[snake.size - 1].y;
			setCursorPosition(snake.body[snake.size].x, snake.body[snake.size].y);
			setFontColor(14);
			printf(SNAKE_BODY);
			snake.size++;
		}

		// 记录得分
		gMyScore++;

		// 判断速度是否加快
		if (gMyScore % 10 == 0)		// 10个为一关
		{
			if (gLevelSpeed > gMaxSpeed)
			{
				gLevelSpeed -= 50;
				gCurSpeed = gLevelSpeed;
			}
		}

		// 重新生成食物
		drawFood();
	}
}

// 蛇碰撞
int collide()
{
	int newX = snake.body[0].x;
	int newY = snake.body[0].y;

	switch (snake.direction)
	{
		case DIR_UP:
			newY = snake.body[0].y - 1;
			break;
		case DIR_DOWN:
			newY = snake.body[0].y + 1;
			break;
		case DIR_LEFT:
			newX = snake.body[0].x - 2;
			break;
		case DIR_RIGHT:
			newX = snake.body[0].x + 2;
			break;
	}

	// 判断是否撞墙
	if (newX < 2 || newX > MAP_WIDTH - 4 || newY < 1 || newY > MAP_HEIGHT - 2)
	{
		// 游戏失败
		snake.status = GAMEOVER;
		return 0;
	}

	// 判断是否撞自身
	for (int i = 1; i < snake.size; i++)
	{
		if (newX == snake.body[i].x && newY == snake.body[i].y)
		{
			// 游戏失败
			snake.status = GAMEOVER;
			return 0;
		}
	}

	return 1;
}

// 游戏结束
int gameOver()
{
	if (snake.status == RUNNING) { return 0; }

	// 记录得分
	if (gMyScore > gTopScore) { setTopScore(gMyScore); }

	// 是否重新开始游戏
	HANDLE curHandle = FindWindow(NULL, "贪吃蛇");	// 得到当前窗口句柄
	int rtn = MessageBox(curHandle, "胜败乃兵家常事,大虾要重新来过吗?", "提示", MB_YESNO | MB_ICONQUESTION);
	if (rtn == IDYES)
	{
		return 0;
	}
	else
	{
		return 1;
	}

}

// 读取记录
int getTopScore()
{
	int score = 0;

	// 打开文件
	FILE* fp = fopen("./score.dat", "rb");
	if (!fp){return 0;}

	// 读取得分
	fread(&score, sizeof(int), 1, fp);

	// 关闭文件
	fclose(fp);

	return score;
}

// 保存记录
void setTopScore(int score)
{
	// 打开文件
	FILE* fp = fopen("./score.dat", "wb");

	// 写入得分
	fwrite(&score, sizeof(int), 1, fp);

	// 关闭文件
	fclose(fp);
}

// 设置字体颜色
void setFontColor(int color)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}

下载:

这个就不需要了吧,代码就这么几行而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值