『游戏』贪吃蛇
准备一夏
如果你不想下 \(VS\) 可以去这里,但我不确保你一定找的到你要的
首先,下载俩东西:\(Visual Studio\) (大家都知道是啥,不知道的请戳(雾,我在 \(VS2019\) 里测试的,我这没问题。。),图形库
还有四个图片,放在你的 \(exe\) 文件目录下,就可以了:第一个,第二个,第三个,第四个;
等等,它们还要分别命名为:main.jpg
, main_exit.jpg
, main_start.jpg
, play.png
;
好了,上面搞完了就可以复制代码了(别嫌麻烦,我一个 xxs 弄的,其实挺好看的)
顺带个游戏说明
开始游戏后,空格暂停,再按一次开始;按箭头控制,长按加速;蛇身颜色渐变,只是别弄太长了,\(len \leq 70\) 应该就行,不然尾巴就粉色,一直粉色。。。;ESC
键退出游戏(按了就直接退出,别怪我没保存游戏进度,也没让你确认。。)。
"请谨慎食用!"
这就是丑不垃圾的代码
#include <graphics.h>
#include <conio.h>
#include <cstdio>
#include <iostream>
#include <thread>
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; i<HEIGHT; i++){
for(int j=0; j<WIDTH; j++){
Blocks[i][j] = 0;
}
}
thread in(input);
in.detach();
waitIndex = 1;
isFailure = false;
loop = false;
skip = false;
isstart = false;
isskipstart = false;
m_start.left = 320;
m_start.top = 440;
m_start.right = 490;
m_start.bottom = 480;
m_exit.left = 320;
m_exit.top = 500;
m_exit.right = 490;
m_exit.bottom = 545;
food.x = rand()%(HEIGHT-5)+2;
food.y = rand()%(WIDTH-5)+2;
Blocks[HEIGHT/2][WIDTH/2] = 1;
for(int i=1; i<=4; i++){
Blocks[HEIGHT/2][WIDTH/2-i] = i+1;
}
dir = RIGHT;
initgraph(WIDTH*BLOCK_SIZE, HEIGHT*BLOCK_SIZE);
BeginBatchDraw();
setbkmode(TRANSPARENT);
setlinecolor(RGB(36,36,36));
m_loadimage(&img,_T("main.jpg"));
loadimage(&play, _T("play.png"), 120, 120, true);
putimage(0,0,&img);
FlushBatchDraw();
while(!isstart){
if(isskipstart){
isstart = true;
skip = false;
cleardevice();
FlushBatchDraw();
break;
}
m = GetMouseMsg();
switch(m.uMsg){
case WM_MOUSEMOVE:
if(IN_START){
m_loadimage(&img,_T("main_start.jpg"));
}else if(IN_EXIT){
m_loadimage(&img,_T("main_exit.jpg"));
}else{
m_loadimage(&img,_T("main.jpg"));
}
putimage(0,0,&img);
FlushBatchDraw();
break;
case WM_LBUTTONDOWN:
if(IN_START || IN_EXIT){
isstart = true;
if(IN_EXIT){
loop = true;
}
cleardevice();
FlushBatchDraw();
}
break;
}
}
}
void move(){
int max = 0;
for(int x=0; x<HEIGHT; x++){
for(int y=0; y<WIDTH; y++){
if(Blocks[x][y]>0){
Blocks[x][y]++;
}
}
}
for(int x=0; x<HEIGHT; x++){
for(int y=0; y<WIDTH; y++){
if(max<Blocks[x][y]){
max = Blocks[x][y];
oldTail.x = x;
oldTail.y = y;
}
if(Blocks[x][y]==2){
oldHead.x = x;
oldHead.y = y;
}
}
}
newHead = oldHead;
switch(dir){
case UP: newHead.x=oldHead.x-1; break;
case DOWN: newHead.x=oldHead.x+1; break;
case LEFT: newHead.y=oldHead.y-1; break;
case RIGHT: newHead.y=oldHead.y+1; break;
default: break;
}
if(newHead.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; x<HEIGHT; x++){
for(int y=0; y<WIDTH; y++){
if(Blocks[x][y]>0){
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 <graphics.h> // 简单但啥也不是(划掉)的头文件
#include <conio.h>
#include <cstdio>
#include <iostream>
#include <thread>
#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; i<INIT_LENGTH; i++){
Blocks[HEIGHT/2][WIDTH/2-i]=i+1; // 因为1是蛇头,所以值为i+1,WIDTH/2-i恰好等于上一个身子的左边一个格子
}
dir=RIGHT; // 初始方向是右边
initgraph(WIDTH*BLOCK_SIZE,HEIGHT*BLOCK_SIZE); // 初始化窗口,传入长度和宽度,这里用格子的边长*格子数量传入
BeginBatchDraw(); // 开始批量绘画,这样每次绘制后需要调用FlushBatchDraw()函数才会在屏幕上显示。不用的话...你会发现屏幕一闪一闪的...
setbkmode(TRANSPARENT); // 设置绘制文字时可以透明,不然黑乎乎一片...
setlinecolor(RGB(36,36,36)); // 设置绘制线时的颜色,为灰色
m_loadimage(&img,_T("main.jpg")); // 加载图片
loadimage(&play,_T("play.png"),120,120,true); // 加载暂停图像,设置拉伸长度并设为自动填充
putimage(0,0,&img); // 将开始界面输出到屏幕上
FlushBatchDraw(); // 刷新画面
begin(); // 准备开始
}
// 取消初始化,好像没啥用?
// 其实你没了之后重新开始的时候会用到(小声)
void uninit(){
EndBatchDraw(); // 结束批量绘画
for(int i=0; i<HEIGHT; i++){ // 设置地图全部为0,也就是空格
for(int j=0; j<WIDTH; j++){
Blocks[i][j]=0;
}
}
}
// 准备开始游戏吧
void begin(){
while(!isstart){ // 还没开始
if(isskipstart){ // 如果按下开始按钮
isstart=true; // 整个游戏开始
skip=false; // 暂停状态设为取消
cleardevice(); // 清空屏幕
FlushBatchDraw(); // 刷新屏幕
break; // 直接走人
}
m=GetMouseMsg(); // 获取鼠标信息
switch(m.uMsg){ // 一起来switch吧
case WM_MOUSEMOVE: // 鼠标移动
if(IN_START){ // 如果鼠标在开始按钮内
m_loadimage(&img,_T("main_start.jpg")); // 加载鼠标在开始按钮内的图片
}else if(IN_EXIT){ // 如果鼠标在退出按钮内
m_loadimage(&img,_T("main_exit.jpg")); // 加载鼠标在退出按钮内的图片
}else{
m_loadimage(&img,_T("main.jpg")); // 加载正常图片
}
putimage(0,0,&img); // 将图片输出在屏幕上
FlushBatchDraw(); // 刷新屏幕
break;
case WM_LBUTTONDOWN: // 如果鼠标点击按下,注意是按下时,只要按下就判断这个,不是鼠标起来
if(IN_START || IN_EXIT){ // 如果在按钮内按下鼠标
isstart=true; // 游戏开始了!
if(IN_EXIT){ // 要是是退出的话...游戏已退出,嘀!
loop=true; // 设置游戏已退出
}
cleardevice(); // 清空画面
FlushBatchDraw(); // 刷新屏幕
}
break;
default: break; // 走人
}
}
}
// 移动
void move(){
int max=0; // 原来尾巴的位置
// 移动蛇身
// 每次移动时可以直接遍历整个矩阵,只要值不等于0就说明是蛇身,
// 加一,就等于变成了后面那个蛇身,方法是不是很巧妙?
for(int x=0; x<HEIGHT; x++){
for(int y=0; y<WIDTH; y++){
if(Blocks[x][y]>0){
Blocks[x][y]++;
}
}
}
// 处理蛇头蛇尾的删除及增加操作
for(int x=0; x<HEIGHT; x++){
for(int y=0; y<WIDTH; y++){
if(Blocks[x][y]>max){ // 如果当前值比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; x<HEIGHT; x++){ // 遍历!我要遍历!
for(int y=0; y<WIDTH; y++){
if(Blocks[x][y]>0){ // 如果是蛇身
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;
}