基础项目实战——贪吃蛇(c++)

前言

各位小伙伴们好久不见,前段时间非常的忙很多事情,其中包括各种实验报告,各种课程设计,各种考试,所以很长一段时间没更新了,今天正好是放假的第一天,我们一起来学习贪吃蛇这个非常经典的项目实战。

在这里插入图片描述

一、 游戏总体框架

#include <iostream>
using namespace std;

int main() {
    while (1) {
    }
    return 0;
}

二、地图绘制

#define H 28
#define W 60

void drawMap() {
    system("cls");
    cout << "┏";
    for (int x = 0; x < W; ++x) {
        cout << "━";
    }
    cout << "┓" << endl;

    for (int y = 0; y < H; ++y) {
        cout << "┃";
        for (int x = 0; x < W; ++x) {
            cout << " ";
        }
        cout << "┃" << endl;
    }
    cout << "┗";
    for (int x = 0; x < W; ++x) {
        cout << "━";
    }
    cout << "┛";
}

三、光标隐藏

void hideCursor() {
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO curInfo = { 1, FALSE };
    SetConsoleCursorInfo(hOutput, &curInfo);
}

四、地图定义


enum BlockType {
	EMPTY = 0,//没有食物就是空状态为0
	FOOD = 1,//有食物的状态是1
};

struct Map {
	BlockType data[H][W];//地图每个格子
	bool hasFood;//判断地图有没有食物
};

void initMap(Map* map) {//初始化地图
	for (int y = 0; y < H; y++) {
		for (int x = 0; x < W; x++) {
			map->data[y][x] = BlockType::EMPTY;//地图上每个格子都为空,也就是没有一个格子有食物为0
		}
	}
	map->hasFood = false;//地图没有食物
}

五、蛇体定义

struct Snake {
	Pos snake[H * W];
	int snakeDir;//蛇的运动方向
	int snakeLength;//蛇的长度
	int lastMoveTime;
	int moveFrequency;
};

void initSnake(Snake* snk) {//初始化蛇
	snk->snakeLength = 3;//蛇刚开始的长度为3
	snk->snakeDir = 2;//刚开始蛇的方向
	snk->snake[0] = { W / 2,H / 2 };//蛇初始位置在地图中间
	snk->snake[1] = { W / 2 - 1,H / 2 };
	snk->snake[2] = { W / 2 - 2,H / 2 };
	snk->lastMoveTime = 0;//上次移动的时间
	snk->moveFrequency = 200;//移动的延时为200ms
}

六、蛇体绘制

void drawUnit(Pos p, const char unit[]) {//函数目的就是在特定位置打印字符串
	COORD coord;//代表坐标
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	coord.X = p.x + 1;//因为方框占据一个位置,所以xy坐标加一
	coord.Y = p.y + 1;
	SetConsoleCursorPosition(hOutput, coord);
	cout << unit;
}

void drawSnake(Snake* snk) {//绘制蛇身
	for (int i = 0; i < snk->snakeLength; i++) {
		drawUnit(snk->snake[i], "■");//蛇身用方块表示
	}
}

七、蛇体移动

const int dir[4][2] = {//设运动的方向
	{-1,0}, //上
	{1,0},  //下
	{0,-1}, //左
	{0,1}   //右
};

void moveSnake(Snake* snk) {
    for (int i = snk->snakeLength - 1; i >= 1; --i) {
        snk->snake[i] = snk->snake[i - 1];
    }
    snk->snake[0].y += dir[snk->snakeDir][0];
    snk->snake[0].x += dir[snk->snakeDir][1];
}

bool doMove(Snake* snk, Map* map) {
    Pos tail = snk->snake[snk->snakeLength - 1];
    drawUnit(snk->snake[snk->snakeLength - 1], " ");
    moveSnake(snk);
    drawUnit(snk->snake[0], "■");
    return true;
}

bool checkSnakeMove(Snake* snk, Map* map) {
    doMove(snk, map);
    return true;
}


八、频率控制

bool checkSnakeMove(Snake* snk, Map* map) {
	int curTime = GetTickCount();
	if (curTime - snk->lastMoveTime > snk->moveFrequency) {//延时处理
		if (!doMove(snk, map)) return false;
		snk->lastMoveTime = curTime;
	}
	return true;//蛇在移动返回true,没有移动返回false

}

九、边界检测

bool checkOutBound(Pos p) {//检测蛇是否超出边界,如果超出返回true,否则返回false
	return (p.x <= 0 || p.x >= W + 1 || p.y <= 0 || p.y >= H + 1);

}

十、游戏失败

    while (1) {//游戏主框架
	checkChangeDir(&snk);
	if (!checkSnakeMove(&snk, &map)) {
		break;//如果蛇移动失败,终止游戏
	}
	checkFoodGenerate(&snk, &map);
}
drawUnit({ W / 2 - 4,H / 2 }, "Game Over");//结束时方框中间输出"Game Over"
while (1) {}

十一、蛇体转向

引入一个新的头文件:

#include <conio.h>

void checkChangeDir(Snake* snk) {//按键控制蛇方向
	if (_kbhit()) {//判断键盘是否被接触
		switch (_getch()) {
		case'w': //w键
			if (snk->snakeDir != 1)snk->snakeDir = 0;//方向为上方向再按反方向理论上行不通
			break;
		case'd': //d键
			if (snk->snakeDir != 2)snk->snakeDir = 3;
			break;
		case's': //s键
			if (snk->snakeDir != 0)snk->snakeDir = 1;
			break;
		case'a': //a键
			if (snk->snakeDir != 3)snk->snakeDir = 2;
			break;
		default:
			break;
		}
	}
}

十二、食物生成

void checkFoodGenerate(Snake* snk, Map* map) {//生成食物
	if (!map->hasFood) {
		while (1) {
			int x = rand() % W;//随机生成食物的x,y坐标
			int y = rand() % H;
			int i = 0;
			while (i < snk->snakeLength) {//生成食物的位置不能在蛇身上
				if (x == snk->snake[i].x && y == snk->snake[i].y) {//判断食物位置是否在蛇身上
					break;
				}
				i++;
			}
			if (i == snk->snakeLength) {
				map->data[y][x] = BlockType::FOOD;
				map->hasFood = true;
				drawUnit({ x,y }, "●");//食物用●表示
				return;
			}
		}
	}
}

十三、食物碰撞

void checkEatFood(Snake* snk, Pos tail, Map* map) {//判断蛇头有没有撞到食物,撞到蛇身就变长
	Pos head = snk->snake[0];//蛇头
	if (map->data[head.y][head.x] == BlockType::FOOD) {
		snk->snake[snk->snakeLength++] = tail;//蛇身加一,改变蛇尾位置
		map->data[head.y][head.x] = BlockType::EMPTY;//食物位置变为空
		map->hasFood = false;//地图没有食物了
		drawUnit(tail, "■");//新蛇尾画出■
	}
}

十四、整体代码

#include<iostream>
#include<Windows.h>
#include<conio.h>
using namespace std;

#define H 27 //地图长度
#define W 60 //地图宽度

const int dir[4][2] = {//设运动的方向
	{-1,0}, //上
	{1,0},  //下
	{0,-1}, //左
	{0,1}   //右
};

enum BlockType {
	EMPTY = 0,//没有食物就是空状态为0
	FOOD = 1,//有食物的状态是1
};

struct Map {
	BlockType data[H][W];//地图每个格子
	bool hasFood;//判断地图有没有食物
};

struct Pos {
	int x;
	int y;
};

struct Snake {
	Pos snake[H * W];
	int snakeDir;//蛇的运动方向
	int snakeLength;//蛇的长度
	int lastMoveTime;
	int moveFrequency;
};

void initSnake(Snake* snk) {//初始化蛇
	snk->snakeLength = 3;//蛇刚开始的长度为3
	snk->snakeDir = 2;//刚开始蛇的方向
	snk->snake[0] = { W / 2,H / 2 };//蛇初始位置在地图中间
	snk->snake[1] = { W / 2 - 1,H / 2 };
	snk->snake[2] = { W / 2 - 2,H / 2 };
	snk->lastMoveTime = 0;//上次移动的时间
	snk->moveFrequency = 200;//移动的延时为200ms
}

void hideCursor() {//隐藏光标,隐藏光标没有鸟用就是看起来舒服点
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//将输出窗口编号存起来
	CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };
	SetConsoleCursorInfo(hOutput, &curInfo);//把窗口对应的光标隐藏
}

void initMap(Map* map) {//初始化地图
	for (int y = 0; y < H; y++) {
		for (int x = 0; x < W; x++) {
			map->data[y][x] = BlockType::EMPTY;//地图上每个格子都为空,也就是没有一个格子有食物为0
		}
	}
	map->hasFood = false;//地图没有食物
}

void drawUnit(Pos p, const char unit[]) {//函数目的就是在特定位置打印字符串
	COORD coord;//代表坐标
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	coord.X = p.x + 1;//因为方框占据一个位置,所以xy坐标加一
	coord.Y = p.y + 1;
	SetConsoleCursorPosition(hOutput, coord);
	cout << unit;
}

void drawMap(Map* map) {//制作地图
	system("cls");//将控制台清空
	cout << "┏";//上框边
	for (int i = 0; i < W; i++) {
		cout << "━";
	}
	cout << "┓" << endl;
	for (int y = 0; y < H; y++) {
		cout << "┃";
		for (int x = 0; x < W; x++) {//左右框边
			if (map->data[y][x] == BlockType::EMPTY) cout << " ";
		}
		cout << "┃" << endl;
	}
	cout << "┗";//下框边
	for (int i = 0; i < W; i++) {
		cout << "━";
	}
	cout << "┛" << endl;
}

void drawSnake(Snake* snk) {//绘制蛇身
	for (int i = 0; i < snk->snakeLength; i++) {
		drawUnit(snk->snake[i], "■");//蛇身用方块表示
	}
}

bool checkOutBound(Pos p) {//检测蛇是否超出边界,如果超出返回true,否则返回false
	return (p.x <= 0 || p.x >= W + 1 || p.y <= 0 || p.y >= H + 1);

}

void checkEatFood(Snake* snk, Pos tail, Map* map) {//判断蛇头有没有撞到食物,撞到蛇身就变长
	Pos head = snk->snake[0];//蛇头
	if (map->data[head.y][head.x] == BlockType::FOOD) {
		snk->snake[snk->snakeLength++] = tail;//蛇身加一,改变蛇尾位置
		map->data[head.y][head.x] = BlockType::EMPTY;//食物位置变为空
		map->hasFood = false;//地图没有食物了
		drawUnit(tail, "■");//新蛇尾画出■
	}
}

void moveSnake(Snake* snk) {
	for (int i = snk->snakeLength - 1; i >= 1; i--) {
		snk->snake[i] = snk->snake[i - 1];//贪吃蛇的核心    
	}    //因为蛇身是往前走的,每个蛇身现在的位置变成它前一个蛇身的位置
	snk->snake[0].y += dir[snk->snakeDir][0];//改变蛇头的位置
	snk->snake[0].x += dir[snk->snakeDir][1];
}

bool doMove(Snake* snk, Map* map) {//蛇体移动
	Pos tail = snk->snake[snk->snakeLength - 1];//拿到蛇的尾部
	drawUnit(tail, " "); //因为蛇再往前走,所以蛇尾原来的位置就为空的
	moveSnake(snk);
	if (checkOutBound(snk->snake[0])) {//蛇移动完以后判断是否超出边界,如果超出停止运动
		return false;
	}
	checkEatFood(snk, tail, map);
	drawUnit(snk->snake[0], "■");//蛇往前走,蛇头的前一个位置要画一个方块
	return true; //如果移动到边界的时候返回false,也就是会移动失败
}

bool checkSnakeMove(Snake* snk, Map* map) {
	int curTime = GetTickCount();
	if (curTime - snk->lastMoveTime > snk->moveFrequency) {//延时处理
		if (!doMove(snk, map)) return false;
		snk->lastMoveTime = curTime;
	}
	return true;//蛇在移动返回true,没有移动返回false

}

void checkChangeDir(Snake* snk) {//按键控制蛇方向
	if (_kbhit()) {//判断键盘是否被接触
		switch (_getch()) {
		case'w': //w键
			if (snk->snakeDir != 1)snk->snakeDir = 0;//方向为上方向再按反方向理论上行不通
			break;
		case'd': //d键
			if (snk->snakeDir != 2)snk->snakeDir = 3;
			break;
		case's': //s键
			if (snk->snakeDir != 0)snk->snakeDir = 1;
			break;
		case'a': //a键
			if (snk->snakeDir != 3)snk->snakeDir = 2;
			break;
		default:
			break;
		}
	}
}

void checkFoodGenerate(Snake* snk, Map* map) {//生成食物
	if (!map->hasFood) {
		while (1) {
			int x = rand() % W;//随机生成食物的x,y坐标
			int y = rand() % H;
			int i = 0;
			while (i < snk->snakeLength) {//生成食物的位置不能在蛇身上
				if (x == snk->snake[i].x && y == snk->snake[i].y) {//判断食物位置是否在蛇身上
					break;
				}
				i++;
			}
			if (i == snk->snakeLength) {
				map->data[y][x] = BlockType::FOOD;
				map->hasFood = true;
				drawUnit({ x,y }, "●");//食物用●表示
				return;
			}
		}
	}
}

void initGame(Snake* snk, Map* map) {//游戏调用的函数
	hideCursor();//去除光标
	initMap(map);//初始化地图
	initSnake(snk);//初始化蛇
	drawMap(map);//画图
	drawSnake(snk);//画蛇
}

int main() {
	Map map;
	Snake snk;
	initGame(&snk, &map);
	while (1) {//游戏主框架
		checkChangeDir(&snk);
		if (!checkSnakeMove(&snk, &map)) {
			break;//如果蛇移动失败,终止游戏
		}
		checkFoodGenerate(&snk, &map);
	}
	drawUnit({ W / 2 - 4,H / 2 }, "Game Over");//结束时方框中间输出"Game Over"
	while (1) {}
	return 0;
}

十五、结语

建议大家自己手敲一遍,还有就是我写了这么多注释是为了方便大家理解,你们在写代码的时候记得不要写那么多注释,使得代码不美观,而且增加许多与项目无关的文字很有可能导致项目调试的时候出现bug。

在这里插入图片描述

(在这里我祝大家2025年新年快乐,非常感谢大家的观看,希望大家点赞加关注支持一下,谢谢大家!!!)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值