假期休闲,来发贪吃蛇!(Win32控制台版)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了假期休闲,来发贪吃蛇!(Win32控制台版)相关的知识,希望对你有一定的参考价值。

技术分享

假期无事,整理电脑文件的时候发现了以前大二时做的坦克小游戏,心血来潮,决定再来一发贪吃蛇。

游戏玩法不必多说,主要是算法实现绘制过程

 

首先,利用一个二维数组 mp[][] 来存储地图信息,其中的值表示:

0:空

1:被蛇覆盖

2:食物

有了这个地图数组,生成随机食物的时候就可以避免生成到蛇身上。

 

那蛇的身体如何存储呢?也很简单,用队列(存储每一个小格的坐标信息)。

队列的头尾方向与蛇的头尾方向正好相反。

蛇每走一步,在蛇头方向的下一位置画一个小方格,同时把该位置放置到队列尾端。取出队列第一个元素,也即是蛇的尾部的坐标,把该位置的小方格清掉。

画一个小格,擦掉一个小格,再画一个,再擦一个……如此反复循环,小蛇就会爬了~

 

代码不多,写到一个文件里了:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
#include <queue>

#ifdef _MSC_VER  // M$的编译器要给予特殊照顾
#if _MSC_VER <= 1200  // VC6及以下版本
#error 你是不是还在用VC6?!
#else  // VC6以上版本
#if _MSC_VER >= 1600  // 据说VC10及以上版本有stdint.h了
#include <stdint.h>
#else  // VC10以下版本,自己定义int8_t和uint16_t
typedef signed char int8_t;
typedef unsigned short uint16_t;
#endif
#ifndef __cplusplus  // 据说VC都没有stdbool.h,不用C++编译,自己定义bool
typedef int bool;
#define true 1
#define false 0
#endif
#endif
#else  // 其他的编译器都好说
#include <stdint.h>
#ifndef __cplusplus  // 不用C++编译,需要stdbool.h里的bool
#include <stdbool.h>
#endif
#endif

using namespace std;

HANDLE g_hConsoleOutput;  // 控制台输出句柄



// 方向
enum { DIR_UP = 0, DIR_LEFT, DIR_DOWN, DIR_RIGHT };

// 绘画开始位置
const int START_ROW = 3;
const int START_COL = 3;

// 蛇的初始值
const int START_X = 5;
const int START_Y = 6;
const int START_LENGTH = 5;

// 地图大小
const int WIDTH = 32;
const int HEIGHT = 24;

// 定位到游戏池中的方格
#define gotoxyInPool(x, y) gotoxyWithFullwidth(x+START_ROW, y+START_COL)

int mp[HEIGHT][WIDTH];

typedef struct Point 
{
	int x, y;
} Point;

typedef struct Snake  // 这个结构体存储游戏相关数据
{
	Point head;
	queue<Point>body;
	int dir;
public :
	void setDir();
	Point getHead();
	int nextStep();
} Snake;

// =============================================================================
typedef struct SnakeControl  // 这个结构体存储控制相关数据
{
	// 游戏池内每格的颜色
	// 由于此版本是彩色的,仅用游戏池数据无法存储颜色信息
	// 当然,如果只实现单色版的,就没必要用这个数组了
	int8_t color[28][16];
	bool dead;  // 挂
	bool pause;  // 暂停
	unsigned score;  // 得分(这个也可以用蛇的长度表示)
} SnakeControl;


// =============================================================================
// 以全角定位到某点
void gotoxyWithFullwidth(short y, short x);

void initGame(Snake * snake, SnakeControl * control);
void printSnake(Snake * snake, SnakeControl * control);
void createFood(Snake * snake);
void GoToNextStep(Snake * snake, SnakeControl * control, int dir);
void keydownControl(Snake * snake, SnakeControl * control, int key);
void runGame(Snake * snake, SnakeControl * control);
void printPrompting();
void printPoolBorder();
void printScore(const Snake *snake, const SnakeControl *control);

int main()
{
	Snake snake;
	SnakeControl control;

	CONSOLE_CURSOR_INFO cursorInfo = { 1, FALSE };  // 光标信息
	g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);  // 获取控制台输出句柄
	SetConsoleCursorInfo(g_hConsoleOutput, &cursorInfo);  // 设置光标隐藏
	SetConsoleTitleA("贪吃蛇");

	printPrompting();


	while (1)
	{
		SetConsoleTextAttribute(g_hConsoleOutput, 0x07);
		system("cls");
		initGame(&snake,&control);  // 初始化游戏
		printPrompting();  // 显示提示信息
		printPoolBorder();  // 显示游戏池边界

		runGame(&snake,&control);  // 运行游戏

		SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
	    char * str = "Game Over";
		int len = strlen(str);
		// 居中显示
		int x = HEIGHT / 2 + START_ROW ;
		int y = WIDTH / 2 + START_COL - len/4;
		gotoxyWithFullwidth(x, y);
		printf("%s",str);
		SetConsoleTextAttribute(g_hConsoleOutput, 0x07);
		system("pause > nul");
		system("cls");
	}

	gotoxyWithFullwidth(0, 0);
	CloseHandle(g_hConsoleOutput);
	system("pause > nul");

	return 0;
}




// =============================================================================
// 以全角定位到某点
void gotoxyWithFullwidth(short y, short x)
{
	static COORD cd;

	cd.X = (short)(x << 1);
	cd.Y = y;
	SetConsoleCursorPosition(g_hConsoleOutput, cd);
}


void initGame(Snake * snake, SnakeControl * control)
{
	//清空数据
	memset(mp, 0, sizeof mp);
	while (!snake->body.empty()) snake->body.pop();

	//出生点
	Point next;
	next.x = START_X ;
	next.y = START_Y ;
	snake->dir = DIR_RIGHT;
	snake->head.x = next.x;
	snake->head.y = next.y;
	for (int i = 0; i < START_LENGTH; ++i)snake->body.push(next);

	//生成食物
	createFood(snake);

	//控制信息
	control->dead = false;
	control->pause = false;
	control->score = 0;
}


//生成随机食物
void createFood(Snake * snake)
{
	int x = 0, y = 0;
	do
	{
		x = rand() % HEIGHT;
		y = rand() % WIDTH;
	} while (mp[x][y] == 1); // 不能生成到蛇的身体上面
	mp[x][y] = 2;
	gotoxyInPool(x, y);
	// 用相应颜色,显示一个实心方块
	SetConsoleTextAttribute(g_hConsoleOutput, 0xA);
	printf("■");

}

void GoToNextStep(Snake * snake, SnakeControl * control, int dir)
{
	int x = snake->head.x;
	int y = snake->head.y;
	Point next;

	switch (dir)
	{
	case DIR_UP:
		next.x = x - 1;
		next.y = y;
		break;
	case DIR_DOWN:
		next.x = x + 1;
		next.y = y;
		break;
	case DIR_LEFT:
		next.x = x;
		next.y = y - 1;
		break;
	case DIR_RIGHT:
		next.x = x;
		next.y = y + 1;
		break;
	default:
		break;
	}

	if (next.x >= 0 && next.x <  HEIGHT && next.y >= 0 && next.y < WIDTH )
	{
		if (mp[next.x][next.y] == 1)
		{
			control->dead = true;
			return;
		}


		bool isEat = mp[next.x][next.y] == 2;

		Point last = snake->body.front();
		snake->head.x = next.x;
		snake->head.y = next.y;
		snake->body.push(next);
		snake->dir = dir;

		// 画下一个头的位置
		mp[next.x][next.y] = 1;
		gotoxyInPool(next.x , next.y );
		SetConsoleTextAttribute(g_hConsoleOutput, 0xF);
		printf("■"); 

		// 如果没有吃到食物,删掉尾巴的痕迹
		if (!isEat)
		{
			snake->body.pop();
			mp[last.x][last.y] = 0;

			gotoxyInPool(last.x , last.y );
			SetConsoleTextAttribute(g_hConsoleOutput, 0xA);
			printf("%2s", "");
		}
		else
		{
			createFood(snake);
			printScore(snake, control);
		}
	}
	else
	{
		control->dead = true;
	}
}

void keydownControl(Snake * snake, SnakeControl * control, int key)
{
	if (key == 13)  // 暂停/解除暂停
	{
		control->pause = !control->pause;

		if (control->pause)  
		{
			SetConsoleTextAttribute(g_hConsoleOutput, 0xE);
			gotoxyWithFullwidth(5, 38);
			printf("      已暂停      ");
			return;
		}
		else
		{
			//清楚提示信息
			SetConsoleTextAttribute(g_hConsoleOutput, 0xE);
			gotoxyWithFullwidth(5, 38);
			printf("%18s","");

			//显示得分信息
			printScore(snake, control);
		}
	}

	if (control->pause)  // 暂停状态,不作处理
	{
		return;
	}

	int dir = 0;
	switch (key)
	{
	case ‘w‘: case ‘W‘: case ‘8‘: case 72:  // 上
		dir = DIR_UP;
		break;
	case ‘a‘: case ‘A‘: case ‘4‘: case 75:  // 左
		dir = DIR_LEFT;
		break;
	case ‘d‘: case ‘D‘: case ‘6‘: case 77:  // 右
		dir = DIR_RIGHT;
		break;
	case ‘s‘: case ‘S‘: case ‘2‘: case 80:  // 下
		dir = DIR_DOWN;
		break;
	default:
		return;
	}

	// 不允许掉头
	if (snake->dir != dir && (snake->dir + dir) % 2 == 0)
	{
		return;
	}
	snake->dir = dir;
}

void runGame(Snake * snake, SnakeControl * control)
{
	int ch;
	clock_t clockLast, clockNow;

	clockLast = clock();  // 计时
	printScore(snake, control);  // 显示游戏池

	while (!control->dead)  // 没挂
	{
		while (_kbhit())  // 有键按下
		{
			ch = _getch();
			if (ch == 27)  // Esc键
			{
				return;
			}
			keydownControl(snake, control, ch);  // 处理按键
		}

		if (!control->pause)  // 未暂停
		{
			clockNow = clock();  // 计时
								 // 两次记时的间隔超过0.45秒
			if (clockNow - clockLast > 0.15F * CLOCKS_PER_SEC)
			{
				clockLast = clockNow;
				GoToNextStep(snake, control, snake->dir);;  // 蛇自动往前走
			}
		}
	}
}



void printPrompting()
{
	// 边框
	SetConsoleTextAttribute(g_hConsoleOutput, 0xF);
	gotoxyWithFullwidth(3, 37);
	printf("┏━━━━━━━━━┓");
	gotoxyWithFullwidth(4, 37);
	printf("┃%18s┃", "", "");
	gotoxyWithFullwidth(5, 37);
	printf("┃%18s┃", "", "");
	gotoxyWithFullwidth(6, 37);
	printf("┃%18s┃", "", "");
	gotoxyWithFullwidth(7, 37);
	printf("┗━━━━━━━━━┛");

	SetConsoleTextAttribute(g_hConsoleOutput, 0xB);
	gotoxyWithFullwidth(10, 37);
	printf("■控制说明:");
	gotoxyWithFullwidth(12, 38);
	printf("□向左移动:← A 4");
	gotoxyWithFullwidth(13, 38);
	printf("□向右移动:→ D 6");
	gotoxyWithFullwidth(14, 38);
	printf("□向下移动:↓ S 2");
	gotoxyWithFullwidth(15, 38);
	printf("□向上移动:↑ W 8");

	gotoxyWithFullwidth(26, 37);
	printf("■By: woffee 17.01.20");

}

// =============================================================================
// 显示得分信息
void printScore(const Snake *snake, const SnakeControl *control)
{
	SetConsoleTextAttribute(g_hConsoleOutput, 0xE);
	gotoxyWithFullwidth(5, 41);
	printf("得分:%d", snake->body.size());
}

// =============================================================================
// 显示游戏边界
void printPoolBorder()
{
	int x, y;
	SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);

	x = START_ROW-1, y = START_COL-1;
	for (int i = 0; i <= WIDTH ; ++i) { gotoxyWithFullwidth(x, y); printf("%2s", ""); y++; }

	x = START_ROW + HEIGHT, y = START_COL;
	for (int i = 0; i <= WIDTH ; ++i) { gotoxyWithFullwidth(x, y); printf("%2s", ""); y++; }

	x = START_ROW, y = START_COL-1;
	for (int i = 0; i <= HEIGHT; ++i) { gotoxyWithFullwidth(x, y); printf("%2s", ""); x++; }

	x = START_ROW-1, y = START_COL + WIDTH;
	for (int i = 0; i <= HEIGHT; ++i) { gotoxyWithFullwidth(x, y); printf("%2s", ""); x++; }
}

  

 

以上是关于假期休闲,来发贪吃蛇!(Win32控制台版)的主要内容,如果未能解决你的问题,请参考以下文章

一条贪吃蛇的使命——零基础入门贪吃蛇游戏

一条贪吃蛇的使命——零基础入门贪吃蛇游戏(附演示地址)

重做贪吃蛇 万向移动型 蛇的移动和吃食部分C# wpf版

c语言 贪吃蛇 程序

c语言 贪吃蛇 程序

JS学习——贪吃蛇代码(简易版)