每日一言
结局每隔一段时间就会到来,我经历它,就像飞机穿过云层。
跟着课程敲了一下午,终于写好了~
用到了一些没用过的函数,总结介绍一下
⭐首先是设置光标位置,它可以让我们指哪打哪,非常好用awa,以下是一个封装好的函数
//设置光标位置
void SetPos(int y, int x) {
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标的位置
COORD pos = { x,y };//先行后列更符合习惯
SetConsoleCursorPosition(handle, pos);
}
⭐接着是隐藏光标,光标总是一闪一闪的实在是有点烦(bushi
以下是让光标消失的魔法
//以下为隐藏光标代码
//控制台光标信息
CONSOLE_CURSOR_INFO cursor_info = { 0 };
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo(handle, &cursor_info);
//隐藏光标
cursor_info.bVisible = false;
//将更改应用到对应的控制台
SetConsoleCursorInfo(handle, &cursor_info);
⭐接下来是修改命令框大小与设置命令框标题
//设置命令框大小
system("mode con cols=100 lines=50");
//设置命令框标题
system("title 贪吃蛇");
⭐然后是设置本地环境,让你可以打印出宽字符,需要包含#include<locale.h>
//设置本地环境,设置后支持宽字符的使用
setlocale(LC_ALL, "");
⭐还有最重要的如何检测一个键是否被按过,以下是一个被定义的宏,通过它来检查按键
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)) & 1 ? 1 : 0)
VK代表虚拟键值
以上只是这些函数的简单介绍,如果想详细了解的可以点击🌳虚拟键码
⭐接下来是要写完整个贪吃蛇用到的知识
- C语言(重要)
- 数据结构中的单链表(重要)
- WIN32 API (会用一点点就行)
1. 代码:
1.1 Snake.h
#pragma once
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>
//检测一个键是否被按过
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)) & 1 ? 1 : 0)
//方向
enum DIRECTION {
UP = 1,
DOWN,
LEFT,
RIGHT
};
//游戏状态
enum GAME_STATUS
{
OK = 1,
KILL_BY_WALL,
KILL_BY_SELF,
GAMEOVER
};
//蛇身
typedef struct SnakeNode {
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//整条蛇
typedef struct Snake{
int Score;//分数
int FoodWight;//一个食物的分数
int SleepTime;//每走一步需要的时间
pSnakeNode pSnake;//维护整条蛇的指针
pSnakeNode pFood;//维护食物的指针
enum DIRECTION Dir;//方向
enum GAME_STATUS Status;//游戏状态
}Snake, * pSnake;
//设置光标位置
void SetPos(int x, int y);
//游戏开始前的准备工作
void GameStart(pSnake snake);
//游戏运行时的维护工作
void GameRun(pSnake snake);
//游戏结束时的维护工作
void GameEnd();
1.2 Snake.c
#include "Snake.h"
//设置光标位置
void SetPos(int y, int x) {
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//根据句柄设置光标的位置
COORD pos = { x,y };//先行后列更符合习惯
SetConsoleCursorPosition(handle, pos);
}
//打印地图
void PrintMap() {
int i = 0;
//上
SetPos(0, 0);
for (i = 0; i < 58; i+=2) {
wprintf(L"%c",L'■');
}
//下
SetPos(26, 0);
for (i = 0; i < 58; i+=2) {
wprintf(L"%c", L'■');
}
//左
for (i = 1; i < 26; i++) {
SetPos(i, 0);
wprintf(L"%c", L'■');
}
//右
for (i = 1; i < 26; i++) {
SetPos(i, 56);
wprintf(L"%c", L'■');
}
}
//游戏介绍
void WelcomeToGame() {
SetPos(13, 40);
printf("欢迎来到贪吃蛇小游戏");
SetPos(18, 41);
system("pause");//暂停一下
system("cls");//清空屏幕
SetPos(12, 25);
printf("用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速");
SetPos(13, 25);
printf("加速将能得到更高的分数。");
SetPos(18, 40);
system("pause");//暂停一下
system("cls");//清空屏幕
}
//初始化蛇
void InitSnake(pSnake snake) {
//初始化游戏数据
snake->FoodWight = 10;
snake->Dir = RIGHT;
snake->Score = 0;
snake->SleepTime = 200;
snake->Status = OK;
//创建蛇身(单链表)
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 2; i++) {
//创建蛇身节点
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (!cur) {
perror("InitSnake() cur malloc fail");
return;
}
//设置蛇的出生地
cur->x = 5;
cur->y = 24 + i * 2;
//头插法
cur->next = NULL;
if (snake->pSnake == NULL) {
snake->pSnake = cur;
}
else {
cur->next = snake->pSnake;
snake->pSnake = cur;
}
}
//打印蛇的身体
cur = snake->pSnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"●");
cur = cur->next;
}
}
//创建食物
void CreateFood(pSnake snake) {
int x = 0;
int y = 1;
roll:
//生成食物坐标
//列应为2的倍数
while (y % 2 != 0) {
x = rand() % 25 + 1;
y = rand() % 53 + 2;
}
//食物不能生成在蛇身上
pSnakeNode cur = snake->pSnake;
while (cur) {
//如果与蛇身重复,则重新生成食物坐标
if (cur->x == x && cur->y == y)
{
goto roll;
}
cur = cur->next;
}
//创建食物
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (!pFood) {
perror("CreateFood() pFood malloc fail");
return;
}
else {
//设置食物坐标
pFood->x = x;
pFood->y = y;
//printf("x = %d y = %d", x, y);
//打印食物
SetPos(pFood->x, pFood->y);
wprintf(L"☆");
//将食物信息给到游戏
snake->pFood = pFood;
}
//SetPos(27, 0);
}
//游戏开始前的准备工作
void GameStart(pSnake snake) {
//以下为隐藏光标代码
//控制台光标信息
CONSOLE_CURSOR_INFO cursor_info = { 0 };
//获得设备句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo(handle, &cursor_info);
//隐藏光标
cursor_info.bVisible = false;
//将更改应用到对应的控制台
SetConsoleCursorInfo(handle, &cursor_info);
//设置命令框大小
system("mode con cols=100 lines=30");
//设置命令框题目
system("title 贪吃蛇");
//游戏介绍
WelcomeToGame();
//打印地图
PrintMap(snake);
//初始化蛇
InitSnake(snake);
//创建食物
CreateFood(snake);
}
//打印游戏说明
void PrintHelpInfo() {
SetPos(15, 64);
printf("不能碰墙,不能咬到自己");
SetPos(16, 64);
printf("用↑.↓.←.→ 分别控制蛇的移动");
SetPos(17, 64);
printf("F3为加速,F4为减速");
SetPos(18, 64);
printf("ESC :退出游戏.space:暂停游戏.");
}
//判断下一个位置是不是食物
bool IsFood(pSnake snake, pSnakeNode pNextNode) {
if (snake->pFood->x == pNextNode->x && snake->pFood->y == pNextNode->y) {
return true;
}
return false;
}
//吃食物
void EatFood(pSnake snake,pSnakeNode pNextNode) {
//头插
pNextNode->next = snake->pSnake;
snake->pSnake = pNextNode;
//打印蛇
pSnakeNode cur = snake->pSnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"●");
cur = cur->next;
}
//加分数
snake->Score += snake->FoodWight;
//释放掉食物节点占用的内存
free(snake->pFood);
snake->pFood = NULL;
//创建一个新的食物
CreateFood(snake);
}
//下个位置不是食物
void NotEatFood(pSnake snake, pSnakeNode pNextNode) {
//头插
pNextNode->next = snake->pSnake;
snake->pSnake = pNextNode;
//找到尾节点的前一个节点,顺便打印蛇身
pSnakeNode cur = snake->pSnake;
while (cur->next->next != NULL) {
SetPos(cur->x, cur->y);
wprintf(L"●");
cur = cur->next;
}
//在蛇尾打印空格
SetPos(cur->next->x, cur->next->y);
printf(" ");
//释放尾节点
free(cur->next);
cur->next = NULL;
}
void SnakeMove(pSnake snake) {
//创建下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (!pNextNode) {
perror("SnakeMove() pNextNode malloc fail");
return;
}
//确定下一个节点的位置
switch (snake->Dir) {
case UP:
{
pNextNode->x = snake->pSnake->x - 1;
pNextNode->y = snake->pSnake->y;
break;
}
case DOWN:
{
pNextNode->x = snake->pSnake->x + 1;
pNextNode->y = snake->pSnake->y;
break;
}
case LEFT:
{
pNextNode->x = snake->pSnake->x;
pNextNode->y = snake->pSnake->y - 2;
break;
}
case RIGHT:
{
pNextNode->x = snake->pSnake->x;
pNextNode->y = snake->pSnake->y + 2;
break;
}
}
//判断下一个节点是不是食物
if (IsFood(snake,pNextNode))
{
//吃食物
EatFood(snake, pNextNode);
}
else {
//不是食物就正常走
NotEatFood(snake, pNextNode);
}
//判断是否撞墙
if (snake->pSnake->x == 26 ||
snake->pSnake->x == 0 ||
snake->pSnake->y == 0 ||
snake->pSnake->y == 56)
{
snake->Status = KILL_BY_WALL;
return;
}
//判断是否撞到自己
pSnakeNode cur = snake->pSnake->next;
while (cur) {
if (pNextNode->x == cur->x && pNextNode->y == cur->y) {
snake->Status = KILL_BY_SELF;
return;
}
cur = cur->next;
}
}
void pause() {
while (1) {
Sleep(300);
//按空格退出暂停
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
void GameRun(pSnake snake) {
//打印游戏说明
PrintHelpInfo();
//蛇的移动
do {
//打印得分与食物分值
SetPos(10, 64);
printf("得分: %5d ", snake->Score);
printf("每个食物分值:%2d分", snake->FoodWight);
//判断玩家是否要改变蛇的方向
//按上不能往下
if (KEY_PRESS(VK_UP) && snake->Dir != DOWN) {
snake->Dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && snake->Dir != UP) {
snake->Dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && snake->Dir != RIGHT) {
snake->Dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && snake->Dir != LEFT) {
snake->Dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE)) {
//暂停
pause();
}
else if (KEY_PRESS(VK_F3)) {
//加速
if (snake->SleepTime >= 50)
{
snake->SleepTime -= 30;
snake->FoodWight += 2;
}
}
else if (KEY_PRESS(VK_F4)) {
//减速
if (snake->SleepTime <= 320)
{
snake->SleepTime += 30;
snake->FoodWight -= 2;
}
}
else if (KEY_PRESS(VK_ESCAPE)) {
snake->Status = GAMEOVER;
return;
}
//休眠一下
Sleep(snake->SleepTime);
//蛇走一步
SnakeMove(snake);
} while (snake->Status == OK);
}
//游戏结束时的维护工作
void GameEnd(pSnake snake) {
//判断玩家是怎么输的
if (snake->Status == KILL_BY_SELF) {
SetPos(10, 18);
printf("很可惜,你咬到自己了");
}
else if (snake->Status == KILL_BY_WALL) {
SetPos(10, 18);
printf("很可惜,你被墙撞死了");
}
else if (snake->Status == GAMEOVER) {
SetPos(10, 18);
printf("你选择了退出游戏");
}
//释放蛇身的节点
pSnakeNode cur = snake->pSnake;
while (cur) {
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
1.3 test.c
#include "Snake.h"
//int main() {
// //设置命令框大小
// system("mode con cols=100 lines=50");
// //设置命令框标题
// system("title 贪吃蛇");
//
// //COORD pos = { 40,10 };
//
// //控制台光标信息 结构体
// CONSOLE_CURSOR_INFO cursor_info = { 0 };
//
// //获得设备句柄
// HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//
// GetConsoleCursorInfo(handle, &cursor_info);
//
// //设置光标大小
// //cursor_info.dwSize = 50;
//
// //隐藏光标
// cursor_info.bVisible = false;
//
// //设置控制台
// SetConsoleCursorInfo(handle, &cursor_info);
//
// //根据句柄设置光标的位置
// COORD pos = { 20,5 };
// SetConsoleCursorPosition(handle, pos);
//
//
// printf("12345");
//
//
// getchar();
// return 0;
//}
//int main() {
// //设置本地环境,设置后支持宽字符的使用
// setlocale(LC_ALL, "");
// int i = 0;
// for (i = 0; i < 60; i++)
// {
// SetPos(i, 0);
// wprintf(L"%c", L'■');
// }
// for (i = 0; i < 30; i++)
// {
// SetPos(0 , i);
// wprintf(L"%c", L'■');
// }
//
// return 0;
//}
int main() {
//设置本地环境,设置后支持宽字符的使用
setlocale(LC_ALL, "");
//设置随机数种子
srand((unsigned int)time(NULL));
int op = 0;
do {
//设置一条蛇
Snake snake = { 0 };
//游戏开始前的准备工作
GameStart(&snake);
//游戏运行时的维护工作
GameRun(&snake);
//游戏结束时的维护工作
GameEnd(&snake);
SetPos(13, 18);
printf("重开一局?(Y/N):");
op = getchar();//接收选项
//拿走缓冲区内的换行
getchar();
} while (op == 'Y' || op == 'y');
SetPos(15, 24);
printf("退出游戏");
SetPos(27, 0);
return 0;
}
2. 可以增加游戏趣味性的想法
- 在场上随机投放道具,吃到了可以获得2次穿墙机会(传送门)
- 在场上随机投放道具,吃到了令玩家的按键功能相反(按上蛇往下走)
- 可以吃道具,扩大地图,或者随着蛇的增大地图随之变大
- 给贪吃蛇染上新颜色
- 可以增加贪吃蛇选皮肤界面
- 可以吃道具,吃到后蛇分成两半,一边用wasd键操控,一边用上下左右键操控(该道具整局只在一条蛇时概率出现)
- 增加音效
结语
请给自己些耐心,一口吃不成胖子。
山外青山楼外楼,莫把百尺当尽头。
保持空杯心态加油努力吧!
都看到这里啦!真棒(*^▽^*)
可以给作者一个免费的赞赞吗,这将会鼓励我继续创作,谢谢大家
编程小白写作,如有纰漏或错误,欢迎指正