简单贪吃蛇模拟(C语言版本·)

简单贪吃蛇模拟(C语言版本·)

一、所需win32 API知识

1.在这儿,直接弱化概念,把在贪吃蛇中用到的API知识说一下!
 1.1用cmd命令来设置控制台窗口的长宽
1
1

  1.2.用title 指令设置控制台窗口的名字
1

#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
1

  1.5.GetConsoleCursorInfo
 *检索有关指定控制台屏幕缓冲区的游标大小和游标可见性的信息。
BOOL WINAPI GetConsoleCursorInfo(
In HANDLE hConsoleOutput,
Out PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);


1

  *参数作用: dwSize:由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条!
  bVisible:游标的可⻅性。 如果光标可⻅,则此成员为TRUE

 1.6.SetConsoleCursorInfo
BOOL WINAPI SetConsoleCursorInfo(
In HANDLE hConsoleOutput,
In const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

1
1
 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整个思路设计
1
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);

完结!!!

相关推荐

  1. C++简易贪吃

    2024-02-19 22:22:02       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-19 22:22:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-19 22:22:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-19 22:22:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-19 22:22:02       20 阅读

热门阅读

  1. 跟老吕学Python编程——目录(含全教程链接)

    2024-02-19 22:22:02       37 阅读
  2. python数据分析numpy基础之std用法和示例

    2024-02-19 22:22:02       30 阅读
  3. 【微信小程序】wxss 和 css 、wxml 和 html 区别

    2024-02-19 22:22:02       35 阅读
  4. c语言中的volatile

    2024-02-19 22:22:02       29 阅读
  5. 2024网络安全服务接接接

    2024-02-19 22:22:02       30 阅读
  6. c# B树

    2024-02-19 22:22:02       34 阅读
  7. 什么是跨端,常用的跨端技术

    2024-02-19 22:22:02       29 阅读
  8. 抵御数据攻击:有效应对.360勒索病毒的方法

    2024-02-19 22:22:02       29 阅读
  9. USACO 2024年1月铜组 MAJORITY OPINION

    2024-02-19 22:22:02       30 阅读
  10. 特殊文件夹

    2024-02-19 22:22:02       21 阅读