简单贪吃蛇模拟(C语言版本·)
- 一、所需win32 API知识
- 二、游戏逻辑实现
一、所需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; //msps->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);
完结!!!