文章目录
- 每日一言
- 代码:
- Snake.h
- Snake.c
- test.c
- 可以增加游戏趣味性的想法
- 结语
每日一言
结局每隔一段时间就会到来,我经历它,就像飞机穿过云层。
跟着课程敲了一下午,终于写好了~
用到了一些没用过的函数,总结介绍一下
⭐首先是设置光标位置,它可以让我们指哪打哪,非常好用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 (会用一点点就行)
代码:
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();
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);}
}
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次穿墙机会(传送门)
- 在场上随机投放道具,吃到了令玩家的按键功能相反(按上蛇往下走)
- 可以吃道具,扩大地图,或者随着蛇的增大地图随之变大
- 给贪吃蛇染上新颜色
- 可以增加贪吃蛇选皮肤界面
- 可以吃道具,吃到后蛇分成两半,一边用wasd键操控,一边用上下左右键操控(该道具整局只在一条蛇时概率出现)
- 增加音效
结语
请给自己些耐心,一口吃不成胖子。
山外青山楼外楼,莫把百尺当尽头。
保持空杯心态加油努力吧!
都看到这里啦!真棒(*^▽^*)
可以给作者一个免费的赞赞吗,这将会鼓励我继续创作,谢谢大家
编程小白写作,如有纰漏或错误,欢迎指正