『游戏』贪吃蛇
准备一夏
如果你不想下 \(VS\) 可以去这里,但我不确保你一定找的到你要的
首先,下载俩东西:\(Visual Studio\) (大家都知道是啥,不知道的请戳(雾,我在 \(VS2019\) 里测试的,我这没问题。。),图形库
还有四个图片,放在你的 \(exe\) 文件目录下,就可以了:第一个,第二个,第三个,第四个;
等等,它们还要分别命名为:main.jpg , main_exit.jpg , main_start.jpg , play.png;
好了,上面搞完了就可以复制代码了(别嫌麻烦,我一个 xxs 弄的,其实挺好看的)
顺带个游戏说明
开始游戏后,空格暂停,再按一次开始;按箭头控制,长按加速;蛇身颜色渐变,只是别弄太长了,\(len \leq 70\) 应该就行,不然尾巴就粉色,一直粉色。。。;ESC键退出游戏(按了就直接退出,别怪我没保存游戏进度,也没让你确认。。)。
"请谨慎食用!"
这就是丑不垃圾的代码
#include
#include
#include
#include
#include
using namespace std;
#pragma warning(disable : 4996)
#define BLOCK_SIZE 20 // 每个小格子的长宽大小
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40 // 宽度上一共40个小格子
#define UP 72 // 上箭头ACLL码 72
#define DOWN 80 // 下箭头ACLL码 80
#define LEFT 75 // 左箭头ACLL码 75
#define RIGHT 77 // 右箭头ACLL码 77
#define ESC 27 // ESC键ACLL码
#define SPACE 32 // 空格ACLL码
#define IN_START m.x>=m_start.left && m.x<=m_start.right && m.y>=m_start.top && m.y<=m_start.bottom
#define IN_EXIT m.x>=m_exit.left && m.x<=m_exit.right && m.y>=m_exit.top && m.y<=m_exit.bottom
struct Locat{
int x,y;
};
// 全局变量定义
int Blocks[HEIGHT][WIDTH]; // 二维数组,用于记录所有的游戏数据
int waitIndex; // 等待移动
char dir; // 小蛇移动方向
Locat food; // 食物的位置
IMAGE img; // 保存图像
IMAGE play; // 暂停图像
MOUSEMSG m; // 鼠标
bool isFailure; // 是否游戏失败
bool loop; // 是否游戏退出
bool skip; // 是否暂停
bool isstart;
bool isskipstart;
RECT m_start;
RECT m_exit; // 按钮坐标
Locat oldTail,oldHead,newHead;
void text(int x, int y, int height,LPCTSTR str,COLORREF color){
settextstyle(height,0,_T("Consolas"));
settextcolor(color);
outtextxy(x,y,str);
FlushBatchDraw();
}
void uninit(){
EndBatchDraw();
}
void m_loadimage(IMAGE *img, LPCTSTR str){
loadimage(img, str, BLOCK_SIZE*WIDTH, BLOCK_SIZE*HEIGHT, true);
}
void input(){
void init();
void move();
while(!loop){
if(kbhit()){
char ch = getch();
switch(ch){
case UP: if(dir==DOWN){break;} dir=ch; move(); break;
case DOWN: if(dir==UP){break;} dir=ch; move(); break;
case LEFT: if(dir==RIGHT){break;} dir=ch; move(); break;
case RIGHT: if(dir==LEFT) {break;} dir=ch; move(); break;
case ESC: loop=true; isstart=true; break;
case SPACE:
if(isFailure){
uninit();
init();
skip = false;
cleardevice();
}else if(!isskipstart){
skip=!skip;
}else{
isskipstart = true;
}
isstart = true;
break;
default: break;
}
}
}
}
void init(){
srand(time(NULL));
for(int i=0; i0){
Blocks[x][y]++;
}
}
}
for(int x=0; x=HEIGHT || newHead.x<0 || newHead.y>=WIDTH || newHead.y<0 || Blocks[newHead.x][newHead.y]>0){
isFailure = true;
return;
}
Blocks[newHead.x][newHead.y] = 1;
if(newHead.x==food.x && newHead.y==food.y){
do{
food.x = rand()%(HEIGHT-5)+2;
food.y = rand()%(WIDTH-5)+2;
}while(Blocks[food.x][food.y]>1);
}else{
Blocks[oldTail.x][oldTail.y] = 0;
}
}
void show(){
cleardevice();
for(int x=0; x0){
setfillcolor(HSVtoRGB(Blocks[x][y],0.9,1)); // HSVtoRGB(Blocks[x][y]*10,0.9,1)
}else{
setfillcolor(RGB(28,28,28));
}
fillrectangle(y*BLOCK_SIZE,x*BLOCK_SIZE,(y+1)*BLOCK_SIZE,(x+1)*BLOCK_SIZE);
}
}
setfillcolor(RGB(0,255,0));
fillrectangle(food.y*BLOCK_SIZE,food.x*BLOCK_SIZE,(food.y+1)*BLOCK_SIZE,(food.x+1)*BLOCK_SIZE);
if(isFailure){
skip = true;
}
FlushBatchDraw();
}
int main(){
init();
while(!loop){
if(!skip && !isskipstart){
show();
waitIndex++;
if(waitIndex==15){
move();
waitIndex = 1;
}
}else{
if(isskipstart){
continue;
}else if(isFailure){
text(250, 240, 80, _T("游戏失败"), RGB(255,0,0));
FlushBatchDraw();
isstart = false;
skip = true;
}else{
if(isstart){
putimage(340, 240, &play, SRCPAINT);
FlushBatchDraw();
}
}
}
}
uninit();
return 0;
}
咋样,还行吧。。。
\[\Huge update\ 2021.10 \]
突然感觉以前代码空格好多(
改了下码风,稍微修改,并增加了亿些注释(累555)。
// 作者:LYqwq
// 博客:https://www.luogu.com.cn/blog/ericiscool/tanchishe
// 洛谷uid:399116
#include // 简单但啥也不是(划掉)的头文件
#include
#include
#include
#include
#pragma warning(disable : 4996) // VS会整一些莫名奇妙的警告导致运行不了,用这个禁用qwq
using namespace std;
// 定义宏养成习惯:加括号
// 这些可以自己瞎改,嘿嘿
// 注意这个窗口大小改了,按钮位置还在原来的,这样你就算按了你看到的“按钮”,也无效qwq
#define BLOCK_SIZE (20) // 每个小格子的长宽大小
#define HEIGHT (30) // 高度上小格子数量
#define WIDTH (40) // 宽度上小格子数量
#define SPEED (15) // 速度
#define INIT_LENGTH (5) // 初始时蛇身长度
#define COLOR_SPEED (1) // 颜色渐变速度
#define BLOCK_COLOR (RGB(28,28,28)) // 小格子颜色
#define FOOD_COLOR (RGB(0,255,0)) // 食物颜色
// 这里别改
#define UP (72) // 上箭头ACLL码,可直接表示方向
#define DOWN (80) // 下箭头ACLL码,同上
#define LEFT (75) // 左箭头ACLL码,同上
#define RIGHT (77) // 右箭头ACLL码,同上
#define ESC (27) // ESC键ACLL码
#define SPACE (32) // 空格ACLL码
#define IN_START (m.x>=m_start.left && m.x<=m_start.right && m.y>=m_start.top && m.y<=m_start.bottom) // 判断鼠标是否在开始界面的 开始游戏 按钮内
#define IN_EXIT (m.x>=m_exit.left && m.x<=m_exit.right && m.y>=m_exit.top && m.y<=m_exit.bottom) // 同上,判断按钮为 退出
struct Locat{ // 为了偷懒(划掉)定义一个表示坐标的结构体
int x,y;
};
// 全局变量定义
int Blocks[HEIGHT][WIDTH]; // 二维数组,用于记录游戏中的地图
int waitIndex; // 等待移动,每次循环加一,如果大于等于SPEED就移动,并设为0,可以理解为记录移动时间的变量
char dir; // 小蛇移动方向
Locat food; // 食物的坐标
IMAGE img; // 保存图像
IMAGE play; // 暂停图像
MOUSEMSG m; // 鼠标
bool isFailure; // 是否游戏失败
bool loop; // 是否游戏退出
bool skip; // 是否暂停
bool isstart; // 游戏是否已开始
bool isskipstart; // 是否按下开始按钮,游戏开始前按下空格也算
RECT m_start; // 按钮坐标
RECT m_exit; // 同上
Locat oldTail,oldHead,newHead; // 蛇头蛇尾坐标和新的蛇头坐标,newHead用于每次移动
int dx[100]; // 用于UP,DOWN,LEFT,RIGHT移动转换坐标
int dy[100]; // 同上
// 函数声明
int main();
void text(int x,int y,int height,LPCTSTR str,COLORREF color);
void m_loadimage(IMAGE *img,LPCTSTR str);
void input();
void init();
void uninit();
void begin();
void move();
void show();
// 为了偷懒(划掉)定义一个在屏幕上显示文字的函数
// x,y:文字坐标
// height:文字高度
// str:显示的字符串
// color:颜色嘛,没什么好解释的
void text(int x,int y,int height,LPCTSTR str,COLORREF color=RGB(0,0,0)){
settextstyle(height,0,_T("Consolas")); // 设置高度,宽度,字体
settextcolor(color); // 颜色
outtextxy(x,y,str); // 坐标和字符串
FlushBatchDraw(); // 刷新屏幕
}
// 也是为了偷懒(划掉)定义一个用于加载图片的函数
// *imp:要加载的图片
void m_loadimage(IMAGE *img,LPCTSTR str){
loadimage(img,str,BLOCK_SIZE*WIDTH,BLOCK_SIZE*HEIGHT,true);
}
// 用于读入,另建一个线程读入,可以省不少时间,而且操作很灵
void input(){
while(!loop){ // 游戏没有退出才执行
if(kbhit()){ // 如果有输入
char ch=getch();
switch(ch){
// 读入的字符是上、下、左、右箭头,如果原方向相反则break。否则改变方向,并移动
// 这里只要读入就移动顺便实现了长按加速功能
case UP: if(dir==DOWN){break;} dir=ch; move(); break; // 别告诉我if不用加大括号,这样更容易分清楚if和外面的语句
case DOWN: if(dir==UP){break;} dir=ch; move(); break;
case LEFT: if(dir==RIGHT){break;} dir=ch; move(); break;
case RIGHT: if(dir==LEFT) {break;} dir=ch; move(); break;
// 如果检测到ESC就退出程序,并准备下一次游戏开始
case ESC: loop=true; isstart=true; break;
// 空格
case SPACE:
if(isFailure){ // 如果死亡,就重新初始化一遍,暂停为否,清空屏幕
// 重新初始化
uninit();
init();
// 取消暂停状态
skip=false;
cleardevice(); // 清空屏幕
}else if(!isskipstart){ // 如果没按开始键
skip=!skip; // 暂停状态改变
}else{
isskipstart=true; // 按开始按钮
}
isstart=true; // 只要按了空格就将开始设为true
break;
default: break; // 其他情况:break!我什么都不管,就这么break!
}
}
}
}
// 初始化函数
void init(){
// 前置初始化
srand((unsigned)time(NULL)); // 初始化随机种子
thread in(input); // 分离线程,读入,见上input()
in.detach(); // 让这个线程自己执行,我们依旧干自己的事(
// 记录游戏变量及移动坐标便宜
waitIndex=0; // 初始设置移动计数变量为0
isFailure=false; // 是否死亡,咋可能还没开始就没了(
loop=false; // 是否退出游戏,刚开始呢(
skip=false; // 是否暂停,并没有
isstart=false; // 是否点击了开始按钮
isskipstart=false; // 没按开始按钮
dx[UP]=-1,dx[DOWN]=+1; // 移动时的坐标偏移设置,x。如果是LEFT或RIGHT的话x坐标没有变化,不管他,自动初始化为0了
dy[LEFT]=-1,dy[RIGHT]=+1; // 同上,只不过是y,同上,将LEFT和RIGHT改为UP和DOWN
// 按钮坐标
m_start.left=320; // 设置开始按钮坐标,m_start是一个存储矩阵的结构体类型,此处是左上脚x坐标
m_start.top=440; // 左上角y坐标
m_start.right=490; // 右下角x坐标
m_start.bottom=480; // 右下角y坐标,这里设置开始按钮坐标结束
m_exit.left=320; // 同上,左上脚x坐标,只是设置的按钮坐标是结束游戏按钮坐标
m_exit.top=500; // 左上角y坐标
m_exit.right=490; // 右下角x坐标
m_exit.bottom=545; // 右下角y坐标,结束
// 食物
food.x=rand()%(HEIGHT-5)+2;
food.y=rand()%(WIDTH-5)+2; // 生成食物坐标,使用随机数,范围在2,2 ~ HEIGHT-2,WIDTH-2
// 处理地图
Blocks[HEIGHT/2][WIDTH/2]=1; // 设置蛇头坐标
/*
* 蛇身颜色渐变,所以采用这种方法:
* 蛇头设为1,后面依次递增,便于设置颜色。
*/
for(int i=1; i0){
Blocks[x][y]++;
}
}
}
// 处理蛇头蛇尾的删除及增加操作
for(int x=0; xmax){ // 如果当前值比max还要大,就说明当前这个值更可能是蛇尾
max=Blocks[x][y]; // 更新max
oldTail.x=x; // 设置旧蛇尾左边
oldTail.y=y;
}
if(Blocks[x][y]==2){ // 如果是上一个蛇头所在地,更新旧蛇头坐标
oldHead.x=x;
oldHead.y=y;
}
}
}
// 将蛇头移动到应有的位置
// 直接使用坐标偏移,简单多了
newHead.x=oldHead.x+dx[dir],newHead.y=oldHead.y+dy[dir];
// 判断死亡条件
if(newHead.x>=HEIGHT || newHead.x<0 || newHead.y>=WIDTH || newHead.y<0 || Blocks[newHead.x][newHead.y]>0){
isFailure=true; // 个屁了qwq
return; // 再见
}
Blocks[newHead.x][newHead.y]=1; // 转移蛇头位置
if(newHead.x==food.x && newHead.y==food.y){ // 如果吃到了食物
do{
food.x=rand()%(HEIGHT-5)+2;
food.y=rand()%(WIDTH-5)+2; // 随机出一个,范围不能太靠边
}while(Blocks[food.x][food.y]>0); // 如果随机到的位置在蛇身上,继续随机,直到不在为止
// 不用将旧尾巴设为0了,吃到了长身子嘛
}else{
Blocks[oldTail.x][oldTail.y]=0; // 没吃到,就把原来的尾巴设为0,等于移动了1下
}
}
// 绘画
void show(){
cleardevice(); // 清空屏幕,屏幕上看不到,因为没刷新屏幕
for(int x=0; x0){ // 如果是蛇身
setfillcolor(HSVtoRGB(Blocks[x][y]*COLOR_SPEED,0.9,1)); // 设置填充颜色为渐变颜色
}else{
setfillcolor(BLOCK_COLOR); // 否则设置颜色为普通小格子的颜色
}
fillrectangle(y*BLOCK_SIZE,x*BLOCK_SIZE,(y+1)*BLOCK_SIZE,(x+1)*BLOCK_SIZE); // 画在屏幕上
}
}
setfillcolor(FOOD_COLOR); // 将填充颜色设为食物颜色
fillrectangle(food.y*BLOCK_SIZE,food.x*BLOCK_SIZE,(food.y+1)*BLOCK_SIZE,(food.x+1)*BLOCK_SIZE); // 画食物
if(isFailure){ // 如果没了
skip=true; // 暂停
}
FlushBatchDraw(); // 刷新屏幕,酱紫就能看见我们刚画的东西了
}
// 一切从此开始
int main(){
init(); // 首先初始化
while(!loop){ // 只要游戏没退出
if(!skip && !isskipstart){ // 游戏未暂停,也未按暂停键
show(); // 绘制画面
waitIndex++; // 移动时间++
if(waitIndex>=SPEED){ // 如果到了移动的时间
move(); // 那么移动
waitIndex=0; // 并将时间计数器设为0
}
}else{
if(isskipstart){ // 如果按了开始键
continue; // 什么都不干,就是玩儿
}else if(isFailure){ // 个屁了
text(250,240,80,_T("游戏失败"),RGB(255,0,0)); // 在屏幕上绘制"游戏失败",颜色为红色
FlushBatchDraw(); // 刷新屏幕
isstart=false; // 死亡后开始状态设为false
skip=true; // 死亡后暂停
}else{
if(isstart){ // 如果刚打开游戏
putimage(340,240,&play,SRCPAINT); // 绘制开始时的图片
FlushBatchDraw(); // 刷新画面
}
}
}
}
uninit(); // 最后uninit()
return 0;
}