基础项目实战——贪吃蛇(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年新年快乐,非常感谢大家的观看,希望大家点赞加关注支持一下,谢谢大家!!!)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951868.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

排序:插入、选择、交换、归并排序

排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&#xff0c;…

Windows service运行Django项目

系统&#xff1a;Windows Service 软件&#xff1a;nssm&#xff0c;nginx 配置Django项目 1、把Django项目的静态文件整理到staticfiles文件夹中 注&#xff1a;settings中的设置 STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles/) STATICFILES_DI…

comfyui精准作图之gligen

简介 在 Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;GLIGEN 是一种用于增强文本到图像生成模型可控性的技术。它通过在现有的预训练扩散模型&#xff08;如 Stable Diffusion&#xff09;基础上&#xff0c;引入额外的定位输入&#xff08;如边界框、关键点或参…

【学习资源】MBSE和工业软件

工业软件从业者&#xff0c;需要学习与应用MBSE方法论&#xff0c;解决复杂问题的有效手段。笔者做一个简单介绍。 1 什么是MBSE&#xff1f; MBSE&#xff08;Model-Based Systems Engineering&#xff0c;基于模型的系统工程&#xff09;是一种系统工程方法论&#xff0c;其…

ue5 蒙太奇,即上半身动画和下半身组合在一起,并使用。学习b站库得科技

本文核心 正常跑步动画端枪动画跑起来也端枪 正常跑步动画 端枪动画的上半身 跑起来也端枪 三步走&#xff1a; 第一步制作动画蒙太奇和插槽 第二步动画蓝图选择使用上半身动画还是全身动画&#xff0c;将上半身端枪和下半身走路结合 第三步使用动画蒙太奇 1.开始把&a…

【Docker】docker compose 安装 Redis Stack

注&#xff1a;整理不易&#xff0c;请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是&#xff1f; 简单来说&#xff0c;Redis Stack 是增强版的 Redis &#xff0c;它在传统的 Redis 数据库基础上增加了一些高级功能和模块&#xff0c;以支持更多的使用场景和需求。Redis…

视频转码对画质有影响吗?视频融合平台EasyCVR支持哪些转码格式?

视频转码过程是将视频文件从一种编码格式转换为另一种格式的过程&#xff0c;这一过程在现代数字媒体中扮演着至关重要的角色。众所周知&#xff0c;视频转码不仅仅是简单的格式转换&#xff0c;它涉及多个关键参数的改变&#xff0c;例如视频编码格式、比特率、分辨率以及帧率…

vscode开启调试模式,结合Delve调试器调试golang项目详细步骤

1.前期准备 (1).在vs code中的扩展程序中搜索并安装Go扩展程序 (2).安装 Delve 调试器 go install github.com/go-delve/delve/cmd/dlvlatest (3).打开vs code的命令面板&#xff0c;输入Go: Install/Update Tools&#xff0c;并单击该命令执行&#xff0c;安装或更新Go语…

springboot和vue配置https请求

项目场景&#xff1a; 代码发布到线上使用https请求需要配置ssl证书&#xff0c;前后端都需要修改。 问题描述 如图&#xff0c;我们在调用接口时报如下错误&#xff0c;这就是未配置ssl但是用https请求产生的问题。 解决方案&#xff1a; 前端&#xff1a;在vite.config.js文…

每日学习30分轻松掌握CursorAI:Cursor基础设置与配置

Cursor基础设置与配置 一、基础设置概览 1. 设置项分类表 设置类别主要功能重要程度语言设置界面及AI交互语言配置★★★★★快捷键配置自定义操作快捷键★★★★☆外观设置主题、字体、颜色方案★★★☆☆编辑器设置缩进、换行、代码风格★★★★☆AI功能设置AI响应灵敏度、…

设计模式(观察者模式)

设计模式&#xff08;观察者模式&#xff09; 第三章 设计模式之观察者模式 观察者模式介绍 观察者模式&#xff08;Observer Design Pattern&#xff09; 也被称为发布订阅模式 。模式定义&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变的时候…

QT 下拉菜单设置参数 起始端口/结束端口/线程数量 端口扫描4

上篇文章QT实现 端口扫描暂停和继续功能 3-CSDN博客 双击 添加对话框类 界面设计 由于主体代码已经写完&#xff0c;只需要更改参数的获取即可 获取起始端口结束端口的输入 槽函数 给主界面类添加调用对话框类的功能 实现功能&#xff1a;点击菜单项可以弹出对话框窗体 增加槽…

Unity自定义编辑器:基于枚举类型动态显示属性

1.参考链接 2.应用 target并设置多选编辑 添加[CanEditMultipleObjects] using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor;[CustomEditor(typeof(LightsState))] [CanEditMultipleObjects] public class TestInspector :…

《代码随想录》Day31打卡!

《代码随想录》贪心算法&#xff1a;合并区间 本题的完整题目如下所示&#xff1a; 本题的完整思路如下所示&#xff1a; 1.本题依然是先对数组的左边界进行排序。将数组的第一个元素赋值给current。 2.遍历数组&#xff0c;判断current中的右边界和当前元素的左边界是否有重叠…

灵活运用事务回滚,快捷处理多张数据表格

各位编程宝子们&#xff08;尤其是对MySQL了解不多的宝子们&#xff09;在使用关系表处理时&#xff0c;有时候会希望简单一次性解决多张表的数据处理&#xff0c;但又有时候无从下手。其实有时候掌握数据的事务和回滚便可以简单解决这些事情&#xff0c;接下来我将以一个学生信…

Github提交Pull Request教程 Git基础扫盲(零基础易懂)

1 PR是什么&#xff1f; PR&#xff0c;全称Pull Request&#xff08;拉取请求&#xff09;&#xff0c;是一种非常重要的协作机制&#xff0c;它是 Git 和 GitHub 等代码托管平台中常见的功能&#xff0c;被广泛用于参与社区贡献&#xff0c;从而促进项目的发展。 PR的整个过…

kvm 解决 安装windows 虚拟机cpu 核数问题

通过lscpu命令查到我本机的cpu信息如下 CPU(s): 12 —— 系统的总逻辑处理单元数量&#xff08;包括所有核心和逻辑处理器&#xff09;。Thread(s) per core: 2 —— 每个物理核心支持 2 个线程&#xff08;表示启用了超线程技术&#xff09;。Core(s) per socket: 6 —— 每个…

面向对象分析与设计Python版 面向对象分析方法

文章目录 前言一、名词法二、名词法-案例三、CRC卡片法四、分析模型法&#xff08;了解&#xff09; 前言 面向对象分析的目标&#xff1a;发现对象、定义对象之间的关系和属性。常用的面向对象分析方法有三种&#xff1a; 名词法CRC卡片法分析模型法 一、名词法 大型复杂系…

python基础和redis

1. Map函数 2. filter函数 numbers generate_numbers() filtered_numbers filter(lambda x: x % 2 0, numbers) for _ in range(5):print(next(filtered_numbers)) # 输出: 0 2 4 6 83. filter map 和 reduce 4. picking and unpicking 5. python 没有函数的重载&#xff0…

Vue2:el-table中的文字根据内容改变颜色

想要实现的效果如图,【级别】和【P】列的颜色根据文字内容变化 1、正常创建表格 <template><el-table:data="tableData"style="width: 100%"><el-table-column prop="id" label="ID"/> <el-table-column …