简单贪吃蛇模拟(C语言版本·)
一、所需win32 API知识
1.在这儿,直接弱化概念,把在贪吃蛇中用到的API知识说一下!
1.1用cmd命令来设置控制台窗口的长宽
1.2.用title 指令设置控制台窗口的名字
#include <stdio.h>
#include <windows.h>
int main()
{
//设置控制台尺寸
system("mode con cols=100 cols=30");
//设置控制台名字
system("title 贪吃蛇");
system("pause");
return 0;
}
1.3.表示一个字符在控制台屏幕上的坐标COORD
可以设置一个坐标COORD pos = {10,20};
1.4.GetStdHandle函数介绍!
*此函数检索指定设备的句柄!(标准输入,标准输出,标准错误)
原型是:HANDLE WINAPI GetStdHandle(
In DWORD nStdHandle
);
1.5.GetConsoleCursorInfo
*检索有关指定控制台屏幕缓冲区的游标大小和游标可见性的信息。
BOOL WINAPI GetConsoleCursorInfo(
In HANDLE hConsoleOutput,
Out PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
*参数作用: dwSize:由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条!
bVisible:游标的可⻅性。 如果光标可⻅,则此成员为TRUE
1.6.SetConsoleCursorInfo
BOOL WINAPI SetConsoleCursorInfo(
In HANDLE hConsoleOutput,
In const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
1.7.SetConsoleCursorPosition
BOOL WINAPI SetConsoleCursorPosition(
In HANDLE hConsoleOutput,
In COORD dwCursorPosition
);
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置
//设置一个函数,用于定位位置
void SetPos(short x, short y)
{
COORD pos = { x,y };
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//设置位置
SetConsoleCursorPosition(handle, pos);
}
1.8.GetAsyncKeyState
SHORT GetAsyncKeyState(
[in] int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
要判断一个键盘上的键是否按过,要看GetAsyncKeyState返回值的最低位是否是1,如果最低位是1,则表示按键按过,做出相应的操作,如果最低位是0,则表示按键没按过,不执行任何操作!
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1)? 1:0)
int main()
{
//按键控制模块
while (1)
{
if (KEY_PRESS(VK_NUMPAD0))
{
printf("0\n");
}
else if (KEY_PRESS(VK_NUMPAD1))
{
printf("1");
}
}
return 0;
}
1.9.打印宽字符
宽字符x轴占两格子,Y轴占一个格子
#include <locale.h>
int main() {
setlocale(LC_ALL, "");
wchar_t ch1 = L'●';
wchar_t ch2 = L'★';
printf("%c%c\n", 'a', 'b');
wprintf(L"%c\n", ch1);
wprintf(L"%c\n", ch2);
return 0;
}
二、游戏逻辑实现
首先,对于现在的我来说,写这样一个贪吃蛇还是很难的,写这个东西,让我感觉最深刻的是枚举类型的运用,这是一个好东西,还有结构体的运用,能够学到大概一个简答的东西,怎么组织,怎么来写,这是很重要的一点,看整个代码,不是很难,但是如果你自己要组织出来,要写出来还是很难得,特别是枚举的应用!
1.数据存储结构的设
首先蛇的每一个节点都要存储,选用链表结构来存储蛇的每一个节点的数据!蛇吃的食物也是一个节点,所以可以直接定义!
typedef struct SnakeNode
{
//存储坐标信息
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
蛇的方向和游戏状态可以用枚举类型一一列举,我认为这说的这个游戏设计最好的地方,这个方面跟能让你感受枚举的作用!!!
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
LEFT
};
enum GAME_STATE
{
OK = 1,//正常运行
ESC,//按ESC退出游戏
KILL_BY_WALL,//撞墙死亡退出游戏
KILL_BY_SELF //撞自己死亡退出游戏
};
这儿再说一下,枚举这样写,也看不出来,有什么作用,但是到后面设计的时候有奇效!!!
下面来定义一个结构体存放整个贪吃蛇游戏中所用到的所有信息!!!
struct Snake
{
//定义蛇头
pSnakeNode pSnake;
//定义食物
pSnakeNode pFood;
//方向
DIRECTION Direction;
//状态
GAME_STATE GameState;
//获得的总分
int Score;
//食物的分数
int FoodWeight;
//睡眠时间
//决定贪吃蛇速度的快慢
int SleepTime;
};
2整个思路设计
3.游戏逻辑实现
//先写测试代码,Test.c
void test()
{
//创建贪吃蛇
int ch = 0;
do
{
Snake snake = { 0 };
GameStart(&snake);
GameRun(&snake);
GameEnd(&snake);
SetPos(20, 15);
printf("再来一局吗?(Y/N):");
ch = getchar();
getchar();//清理\n
} while (ch == 'Y'||ch == 'y');
}
int main()
{
//修改适配本地中文环境
srand((unsigned int)time(NULL));
setlocale(LC_ALL, "");
//测试贪吃蛇
test();
SetPos(0, 28);
return 0;
}
//写Snake.c文件,各个逻辑的写法,时间不够,详细不说了
#include "Snake.h"
//设置光标位置的函数
void SetPos(short x, short y)
{
COORD pos = {x,y};
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(houtput, pos);
}
void WelcomeToGame()
{
//欢迎信息
SetPos(40, 12);
printf("欢迎来到贪吃蛇游戏!\n");
SetPos(41, 18);
system("pause");
system("cls");
//功能介绍信息
SetPos(29, 12);
printf("用↑.↓.←.→分别控制蛇的移动,F3为加速,F4为减速\n");
SetPos(29, 13);
printf("加速将能得到更高分数!");
SetPos(29,20);
system("pause");
system("cls");
}
void CreatMap()
{
//上
SetPos(0, 0);
for (int i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//SetPos(57, 0);
//printf("---->X");
//wprintf(L"%lc", L'┉');
//wprintf(L"%lc", L'▶');
//下
SetPos(0, 26);
for (int i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
SetPos(0, 1);
for (int i = 1; i <= 25; i++)
{
wprintf(L"%lc\n", WALL);
}
//SetPos(0, 27);
//wprintf(L"%lc\n", L'┋');
//wprintf(L"%lc", L'▼');
//printf("Y");
//右
SetPos(56, 1);
for (int i = 1; i <= 25; i++)
{
wprintf(L"%lc\n", WALL);
SetPos(56, 1 + i);
}
}
void InitSnake(pSnake ps)
{
//创建5个蛇身节点
pSnakeNode cur = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake malloc fail!");
return;
}
//申请成功
cur->x = POS_X + 2 * i;
cur->y = POS_Y;
cur->next = NULL;
//头插
if (ps->pSnake == NULL)
{
ps->pSnake = cur;
}
else
{
cur->next = ps->pSnake;
ps->pSnake = cur;
}
}
//打印蛇身
cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BOOY);
cur = cur->next;
}
//贪吃蛇其它信息初始化
ps->dir = RIGHT;
ps->FoodWeight = 10;
ps->pFood = NULL;
ps->Score = 0;
ps->SleepTime = 400; //ms
ps->status = OK;
}
void CreatFood(pSnake ps)
{
int x = 0;
int y = 0;
//处理随机生成的坐标,不能在墙外面,x必须是2的倍数
again:
do {
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2!=0);
//食物不能是蛇的节点
pSnakeNode cur = ps->pSnake;
while (cur)
{
if ((x == cur->x) && (y == cur->y))
{
goto again;
}
cur = cur->next;
}
//创建食物
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreatFood malloc fail!");
return;
}
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
ps->pFood = pFood;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
void PrintHelpInfo()
{
SetPos(62, 15);
printf("1.不能穿墙,不能咬到自己");
SetPos(62, 16);
printf("2.用↑.↓.←.→分别控制蛇的移动");
SetPos(62, 17);
printf(" F3为加速,F4为减速");
SetPos(62, 18);
}
void pause()
{
//
while (1)
{
Sleep(200);
//如果按键按下,跳出循环
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
if ((ps->pFood->x == pnext->x) && (ps->pFood->y == pnext->y))
{
return 1;
}
return 0;
}
void EatFood(pSnake ps, pSnakeNode pnext)
{
//头插
pnext->next = ps->pSnake;
ps->pSnake = pnext;
//打印蛇身
pSnakeNode cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BOOY);
cur = cur->next;
}
ps->Score += ps->FoodWeight;
//释放旧的食物
free(ps->pFood);
//创造新的食物
CreatFood(ps);
}
void NotEatFood(pSnake ps, pSnakeNode pnext)
{
//头插
pnext->next = ps->pSnake;
ps->pSnake = pnext;
//找到倒数第二个节点
pSnakeNode cur = ps->pSnake;
while (cur->next->next)
{
//打印感觉有问题
SetPos(cur->x, cur->y);
wprintf(L"%lc", BOOY);
cur = cur->next;
}
//打印倒数第二个节点
SetPos(cur->x, cur->y);
wprintf(L"%lc", BOOY);
//最后一个节点
pSnakeNode tail = cur->next;
cur->next = NULL;
//将尾结点的地方打印成空格
SetPos(tail->x, tail->y);
printf(" ");
free(tail);
tail = NULL;
//打印蛇身
}
//检测撞墙
KillByWall(pSnake ps)
{
if ((ps->pSnake->x == 0) || (ps->pSnake->x == 56) ||
(ps->pSnake->y == 0) || (ps->pSnake->y == 26))
{
ps->status = KILL_BY_WALL;
}
}
//检测撞到自己
KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->pSnake->next; //从第二个开始遍历
while (cur)
{
if ((cur->x == ps->pSnake->x) &&(cur->y == ps->pSnake->y))
{
ps->status = KILL_BY_SELF;
//结束
break;
}
cur = cur->next;
}
}
void SnakeMove(pSnake ps)
{
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("SnakeMove malloc fail");
return;
}
pnext->next = NULL;
switch (ps->dir)
{
case UP:
pnext->x = ps->pSnake->x;
pnext->y = ps->pSnake->y - 1;
break;
case DOWN:
pnext->x = ps->pSnake->x;
pnext->y = ps->pSnake->y + 1;
break;
case LEFT:
pnext->x = ps->pSnake->x-2;
pnext->y = ps->pSnake->y;
break;
case RIGHT:
pnext->x = ps->pSnake->x+2;
pnext->y = ps->pSnake->y;
break;
}
//下一个坐标是否是食物
if (NextIsFood(ps, pnext))
{
//是食物就吃掉
EatFood(ps,pnext);
}
else
{
//不是食物正常走
NotEatFood(ps,pnext);
}
//检测撞墙
KillByWall(ps);
//检测撞到自己
KillBySelf(ps);
}
void GameStart(pSnake ps)
{
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorinfo;
//获取控制台光标信息
GetConsoleCursorInfo(handle, &cursorinfo);
cursorinfo.bVisible = false;
SetConsoleCursorInfo(handle, &cursorinfo);
//打印欢迎信息
WelcomeToGame();
//绘制地图
CreatMap();
//初始化蛇
InitSnake(ps);
//创建食物
CreatFood(ps);
}
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
do
{
//do...while循环先进去,打印分数
//分数
SetPos(62, 10);
printf("总分:%5d\n", ps->Score);
SetPos(62, 11);
printf("食物的分值:%02d\n", ps->FoodWeight);
//检测按键
//上 下 左 右 ESC 空格 F3 F4
//下面怎么控制按下按键,做出对应的响应的操作
//它是通过枚举来控制按键对应的操作
if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
{
//按下上键,则方向变为上
ps->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
{
ps->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
{
ps->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
{
ps->dir = RIGHT;
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = ESC;
//按下ESC做出的响应是跳出这个游戏的循环
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
pause(); //暂停和恢复暂停
//第一次按下空格键,检测到则调用了pause函数
//程序死循环睡眠
//再次按下则在pause函数里面响应,跳出死循环,执行其它操作
}
else if (KEY_PRESS(VK_F3))
{
//还是通过枚举来控制
if (ps->SleepTime >= 80)
{
ps->SleepTime -= 30;
ps->FoodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
//F4按下对应的响应
if (ps->FoodWeight > 2)
{
ps->SleepTime += 30;
ps->FoodWeight -= 2;
}
}
//睡眠一下
//停留一下,执行一下,停留时间的长短由Sleep控制
Sleep(ps->SleepTime);
// 走一步
SnakeMove(ps);
} while (ps->status == OK);
}
void GameEnd(pSnake ps)
{
SetPos(25, 12);
switch (ps->status)
{
case ESC:
printf("主动退出游戏!");
break;
case KILL_BY_WALL:
printf("撞墙而死!");
break;
case KILL_BY_SELF:
printf("撞自身而死!");
break;
}
//释放所有申请的节点
pSnakeNode cur = ps->pSnake;
while (cur)
{
pSnakeNode next = cur->next;
free(cur);
cur = next;
}
ps->pSnake = NULL;
free(ps->pFood);
ps->pFood = NULL;
}
//写Snake.h文件
#pragma once
#include <stdio.h>
#include <locale.h>
#include <windows.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1)? 1:0)
#define WALL L'□'
#define BOOY L'●'
#define FOOD L'★'
//蛇起始位置
#define POS_X 24
#define POS_Y 5
//控制游戏状态
enum GAME_STATUS
{
OK = 1,
ESC, //退出
KILL_BY_WALL, //撞墙
KILL_BY_SELF //撞自身
};
//控制方向来用
enum DIRECTION
{
UP=1,
DOWN,
LEFT,
RIGHT
};
//蛇身节点
typedef struct Snakenode
{
int x;
int y;
struct Snakenode* next;
}SnakeNode,*pSnakeNode;
//维护整个贪吃蛇游戏
typedef struct Snake
{
pSnakeNode pSnake; //蛇头
pSnakeNode pFood; //食物指针
int Score; //累计分数
int FoodWeight; //一个食物分数
int SleepTime; //蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢
enum GAME_STATUS status; //游戏当前状态
enum DIRECTION dir; //方向
}Snake,*pSnake;
void GameStart(pSnake ps);
void SetPos(short x, short y);
void WelcomeToGame();
void CreatMap();
void InitSnake(pSnake ps);
void CreatFood(pSnake ps);
void GameRun(pSnake ps);
void PrintHelpInfo();
void SnakeMove(pSnake ps);
int NextIsFood(pSnake ps, pSnakeNode pnext);
void EatFood(pSnake ps, pSnakeNode pnext);
void NotEatFood(pSnake ps, pSnakeNode pnext);
//检测撞墙
KillByWall(pSnake ps);
//检测撞到自己
KillBySelf(pSnake ps);
void GameEnd(pSnake ps);
void SetPos(short x, short y);
完结!!!