如果你对其他的C++案例或知识感兴趣,请考虑阅读我的专栏:
须知
本文仅作学习笔记使用,仅在CSDN网站发布,如果在其他网站发现,均为侵权行为,请举报。作者:小王在努力。
俄罗斯方块
说明
本案例使用的是Easyx实现的,如果没有安装Easyx的朋友可能不能运行这个案例的源码(但是下方的exe文件还是可以运行的),Easyx的官网地址如下:
Easyx官网,官网中有详细的下载教程、安装过程、以及Easyx的帮助文档等。
一、游戏展示
1.游戏文件
百度网盘地址:俄罗斯方块.exe(无BGM)。 提取码:elsf
百度网盘地址:俄罗斯方块.zip(有BGM) 。 提取码:elsf
强烈推荐大家下载带BGM版本的。如果大家对百度云的网速不信任的话,请点赞,然后再评论区留下你的邮箱,我会将游戏源码以及游戏文件发送到你的邮箱中。
2.游戏画面
3.游戏挑战
目前游戏难度最高为等级4。由于前几天属实闲的没事,便玩了一会,破了自己的记录,大家有兴趣的可以挑战一下这个分数:
二、案例实现
1.案例分析
我将整个游戏的主界面划分为三个部分:背景部分、界面部分、方块部分。按照这个思想就能实现这个游戏,接下来详细介绍这几个部分如何实现。
1.1 背景部分
这里说的背景部分指的是下面这张图片:
这个东西想必大家都可以看出来,也就是方块的活动区域,这个部分我们只需要让他显示在方块后面即可,他需要做的只有一件事:
1.输出背景:Back类 void showBack();
1.2 界面部分
这里说的界面部分指的是:
界面部分是我们显示游戏信息、操作按钮的区域。这部分我们要完成的操作也很简单:
1.在指定位置输出当前的分数和等级信息:Surface类.void showSurface();
2.在指定位置输出下一个方块的信息:Surface类.void showSurface();
1.3 方块部分
这里说的方块部分指的是俄罗斯方块中的七种方块(图片来自网络,侵删):
这些方块我是通过一个个方格组合起来的,方格:
那么是如何组合起来的呢?我们以第一个正方形方块为例:
1.首先定义一个大小为18的一维数组,同时将正方形的方块信息存储在这个数组中:
int d[18]={0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,1,0,0};
2.将这个一维数组转换为二维数组(自定义函数set()实现)
this->bl[4].set(d);//此处的set函数的功能就是一维转二维
3.根据这个二维数组的元素值输出方格,如果是0就不输出图片,如果是1就输出方格。这样讲整个二维数组遍历输出一边就能得到一个方块。
//输出当前下落的方块
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bl[this->i].s[i][j]==1)//判断方块信息数组
putimage(this->bl_xy[this->i][1]+(j-1)*30,this->bl_xy[this->i][2]+(i-1)*30,&this->img);//输出当前下落的方块
在了解了一个二维数组代表了一个方块的信息之后,我们就应该明确方块要做哪些事情:
1.输出方块:Block类void showBlock();
2.方块自动下落:Block类void move();
3.方块变换(左/右移、逆时针旋转):Block类void transform();
4.判断游戏是否结束:Block类void isVectory();
1.4 总结
我们只需要将每一部分要完成的任务完成,这个游戏就能做出来了。至于每一部分的任务如何实现,后面都有对应类对应的函数来实现它,大家可以在下面类的设计中寻找相应的函数,关于具体功能如何实现的,函数中有较为明确的说明。
2.类的设计
根据上述的三个部分,我们决定设计三个类:背景部分(Back类)、界面部分(Surface类)、方块类(Block类)。
2.1 Back类
头文件:
#pragma once
#include<graphics.h>
class Back
{
private:
IMAGE img;//存储图片的对象
public:
Back(void);
~Back(void);
void showBack();//输出背景
};
源文件:
#include "Back.h"
Back::Back(void)
{
loadimage(&this->img,_T("IMAGE"),_T("背景.jpg"));//将背景图片加载到 img中
}
Back::~Back(void)
{
}
void Back::showBack()
{
putimage(0,0,&this->img);//将img中的图片输出到指定位置
}
2.2 Surface类
头文件:
#pragma once
#include<graphics.h>
#include"Block.h"
class Surface
{
private:
IMAGE img;//存储图片的对象
public:
Surface(void);
~Surface(void);
void showSurface(Block b);//输出界面函数
};
源文件:
#include "Surface.h"
Surface::Surface(void)
{
loadimage(&this->img,_T("IMAGE"),_T("界面.jpg"));//将界面.jpg图片加载到img对象中
}
Surface::~Surface(void)
{
}
//输出界面函数
//界面函数主要包括几部分:1、分数和等级
// 2、下一个方块
void Surface::showSurface(Block b)
{
putimage(300,0,&this->img);//在指定位置输出img对象中保存的图片
int temp_x=343;
int temp_y=63;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(b.bl[b.nexti].s[i][j]==1)//根据下一块方块信息,输出下一块方块
putimage(temp_x+(j-1)*30,temp_y+(i-1)*30,&b.img);//在指定位置输出下一块方块
setbkmode(TRANSPARENT);
settextstyle(24,16,_T("楷体"));//设置字体的格式
settextcolor(LIGHTGRAY);//设置字体颜色
TCHAR grade[10];
_stprintf_s(grade, _T("%d"), b.grade);
outtextxy(390, 205, grade);//在指定位置输出分数
TCHAR level[10];
_stprintf_s(level, _T("%d"), b.level);
outtextxy(390, 241, level);//在指定位置输出游戏等级
}
2.1 Block类
头文件:
#pragma once
#include"Shape.h"
#include<graphics.h>
#include<windows.h>
#include<cstdlib>
#include<ctime>
#include"Back.h"
class Block
{
public:
IMAGE img;//存储图片的对象
Shape bl[8];//用于方块信息的对象数组,其中Shape为二维数组(由于不会用三维数组)
int bc[21][11];//背景数组(将整个游戏区域划分为10×20的数组)
int i;//当前方块的序号
int nexti;//下一个方块的序号
int bl_xy[8][3];//输出图片的左上角坐标
int b_flag;//标志
int a[3];//存放方块序号的数组
int grade;//游戏分数
int level;//游戏等级
public:
Block(void);
~Block(void);
void showBlock();//输出方块
void move(Back b,int &flag);//方块移动函数
void transform(int x,Back b,int &flag);//方块变换函数
void newBlock();//随机产生下一个方块的函数
void isVectory(Back b,int &flag);//判断游戏结束函数
void rotate();//方块旋转函数
};
源文件:
#include "Block.h"
Block::Block(void)
{
int a[18]={0,
0,0,0,0,
0,0,0,0,
0,1,0,0,
1,1,1,0};
this->bl[1].set(a);//第一个方块的数组值赋值
int b[18]={0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
0,1,1,0};
this->bl[2].set(b);//第二个方块的数组值赋值
int c[18]={0,
0,0,0,0,
0,0,0,0,
0,1,1,0,
1,1,0,0};
this->bl[3].set(c);//第三个方块的数组值赋值
int d[18]={0,
0,0,0,0,
0,0,0,0,
1,1,0,0,
1,1,0,0};
this->bl[4].set(d);//第四个方块的数组值赋值
int e[18]={0,
0,0,0,0,
1,0,0,0,
1,0,0,0,
1,1,0,0};
this->bl[5].set(e);//第五个方块的数组值赋值
int f[18]={0,
0,0,0,0,
0,1,0,0,
0,1,0,0,
1,1,0,0};
this->bl[6].set(f);//第六个方块的数组值赋值
int g[18]={0,
0,1,0,0,
0,1,0,0,
0,1,0,0,
0,1,0,0};
this->bl[7].set(g);//第七个方块的数组值赋值
loadimage(&this->img,_T("IMAGE"),_T("方块.jpg"));//加载图片到img对象中,方便后续输出
for(int i=1;i<=20;i++)//初始化背景数组
for(int j=1;j<=10;j++)
this->bc[i][j]=0;
this->i=0;
srand((int(time(0))));//设置随机数种子
this->bl_xy[this->i][1]=90;//设置每个方块的起始x坐标
this->bl_xy[this->i][2]=-90;//设置每个方块的起始y坐标
this->b_flag=1;
this->grade=0;//设置游戏初始分数为0
this->level=1;//设置游戏初始难度等级为1
}
Block::~Block(void)
{
}
//输出方块函数
//输出当前下落的方块以及已经下落的方块
void Block::showBlock()
{
//输出当前下落的方块
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bl[this->i].s[i][j]==1)//判断方块信息数组
putimage(this->bl_xy[this->i][1]+(j-1)*30,this->bl_xy[this->i][2]+(i-1)*30,&this->img);//输出当前下落的方块
//输出以及下落的方块
for(int i=1;i<=20;i++)
for(int j=1;j<=10;j++)
if(this->bc[i][j]==1)
putimage((j-1)*30,(i-1)*30,&this->img);
}
//方块移动函数
//实现方法:1、先找到当前方块的最低点坐标
// 2、判断是否达到下界
// 3、达到下界就将当前方块加入到已经下落方块中,输出背景,输出方块
// 4、没有达到下界就继续移动,判断是否与已经下落的方块重合
// 5、重合的话就停止下降,并将此方块加入到已经下落方块中,输出背景,输出方块
// 6、如果没有重合,继续下降
void Block::move(Back b,int &flag)
{
int a= 0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[j][i]==1)//找到当前方块最下面的方格
{
if(this->bl_xy[this->i][2]+(j+1)*30<=600)//下一步移动 没有到达下界
{
for(int j=1;j<=4;j++)
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[j][i]==1)//判断最下面一行的方块
if(this->bc[(this->bl_xy[this->i][2])/30+j+1][(this->bl_xy[this->i][1])/30+i]==1)//判断与背景的方块是否有重合
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j]==0)
this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j] = this->bl[this->i].s[i][j];//修改背景方块信息
b.showBack();//输出方块
showBlock();//输出背景
flag=1;//设置返回信息
break;
}
if(!flag)//如果与背景不重合
{
this->bl_xy[this->i][2]+=30;//方块继续下降
b.showBack();//输出方块
showBlock();//输出背景
}
}else//下一步移动达到下界
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j]==0)
this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j] = this->bl[this->i].s[i][j];//修改背景方块信息
b.showBack();
showBlock();
flag=1;//设置返回信息
break;
}
a=1;
break;
}
if(a) break;
}
}
//方块变换函数
//实现方块的:左/右移动、旋转、加速下降
void Block::transform(int x,Back b,int &flag)
{
int temp=0;
for(int j=4;j>=1;j--)
for(int i=1;i<=4;i++)
{
if(this->bl[this->i].s[j][i]==1)
{
temp=j;
break;
}
}
if(this->bl_xy[this->i][2]+(temp+1)*30<=600)//当没有下降到最低点的时候
{
int flag=0;
switch(x)
{
case 's':
{
int a= 0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[j][i]==1)//判断最下面一行的方块
{
if(this->bl_xy[this->i][2]+(j+1)*30<=600)
{
for(int j=1;j<=4;j++)
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[j][i]==1)//判断最下面一行的方块
if(this->bc[(this->bl_xy[this->i][2])/30+j+1][(this->bl_xy[this->i][1])/30+i]==1)//判断与背景的方块是否有重合
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j]==0)
this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j] = this->bl[this->i].s[i][j];//修改背景方块信息
b.showBack();
showBlock();
flag=1;//设置返回信息
break;
}
if(!flag)//如果与背景不重合
{
this->bl_xy[this->i][2]+=30;
b.showBack();
showBlock();
}
}
a=1;
break;
}
if(a) break;
}
};break;
case 'a':
{
int a=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最左边的方块
{
if(this->bl_xy[this->i][1]+(j-2)*30>=0)//判断是否在左边界处
{
for(int j=1;j<=4;j++)
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//判断最左边一列的方块
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j-1]==1)//判断与背景的方块是否有重合
{
flag=1;//设置返回信息
break;
}
if(!flag)
{
this->bl_xy[this->i][1]-=30;
b.showBack();
showBlock();
}
}
a=1;
break;
}
if(a) break;
}
};break;
case 'd':
{
int a=0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最右边的方块
{
if(this->bl_xy[this->i][1]+j*30<300)//判断是否在边界处
{
for(int j=1;j<=4;j++)
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//判断最左边一列的方块
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j+1]==1)//判断与背景的方块是否有重合
{
flag=1;//设置返回信息
break;
}
if(!flag)
{
this->bl_xy[this->i][1]+=30;
b.showBack();
showBlock();
}
}
a=1;
break;
}
if(a) break;
}
};break;
case 'w':
{
rotate();
b.showBack();
showBlock();
};break;
}
}
else
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j]==0)
this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j] = this->bl[this->i].s[i][j];
b.showBack();
showBlock();
//Sleep(1000-(speed-1)*200);
}
}
//随机产生方块函数
//随机产生两个新的方块的序号
//一个是当前的方块的序号。另一个是下一个方块的序号
//实现方法:在一开始,产生两个随机数,第一个为当前方块的序号
// 第二个为下一个方块的序号,然后每次只用产生一个方
// 块的序号即可
void Block::newBlock()
{
if(this->b_flag)//一开始,产生两个随机数,代表随机产生两个方块
{
this->a[1] = (rand()%7)+1;
this->a[2] = (rand()%7)+1;
this->b_flag=0;
}
else//后面就只需要产生第二个方块的序号即可
this->a[2] = (rand()%7)+1;
this->i=a[1];//第一个方块的序号赋值
a[1]=a[2];//数组元素前移
this->nexti=a[2];//第二个方块的序号赋值
this->bl_xy[this->i][1]=90;//
this->bl_xy[this->i][2]=-90;//
}
//判断游戏结束函数
//实现功能:消除和判断游戏结束
void Block::isVectory(Back b,int &flag)
{
for(int i=1;i<=20;i++)//逐行判断 有一行满了就将该行全部清0 输出
{
int sum=0;
for(int j =1;j<=10;j++)
{
if(this->bc[i][j]==1)
sum++;
}
if(sum==10)
{
//for(int j =1;j<=10;j++)//将被消除的一行数组元素值设置为0代表被消除
// this->bc[i][j]=0;
for(int p=i;p>=2;p--)
for(int q=1;q<=10;q++)
this->bc[p][q]=this->bc[p-1][q];
for(int q=1;q<=10;q++)
this->bc[1][q]=0;
this->grade+=(10*this->level);
switch(this->grade)
{
case 100:this->level+=1;;break;
case 300:this->level+=1;break;
case 900:this->level+=1;break;
}
b.showBack();
showBlock();
}
}
int sum=0;
for(int i=1;i<=20;i++)//逐列判断 如果有一列全满,就设置结束标志 游戏结束
{
for(int j = 1;j<=10;j++)
if(this->bc[i][j]==1)
{
sum++;
break;
}
}
if(sum==20)
{
flag = 2;
setbkmode(TRANSPARENT);
settextcolor(DARKGRAY);
settextstyle(50,32,_T("黑体"));
outtextxy(10, 250, _T("游戏结束!"));
}
}
//方块旋转函数
//实现方块的逆时针旋转
//大体思路也就是将存储方块信息的二维数组逆时针旋转90度
//举个简单的例子:
//将原数组a[3][3]:{1,2,3,
// 4,5,6,
// 7,8,9}逆时针旋转90度
//
//旋转后的数组a`[3][3]:{1,4,7,
// 2,5,8,
// 3,6,9}
//只要找到他们之间的对应关系,然后对原数组赋值就可以实现旋转了(用temp保存原数组值,因为过程中原数组值被修改了)
//他们之间对应的关系为:a[i][j]=temp[j][i];
void Block::rotate()
{
if(this->i!=4)//正方形方块
if(this->i==7)//长条方块
{
if(this->bl[this->i].s[1][2]==1)
{
int g[18]={0,
0,0,0,0,
0,0,0,0,
1,1,1,1,
0,0,0,0};
this->bl[7].set(g);//第七个方块的数组值赋值
//右边界判断
int flag_r=0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最右边的方块
if(this->bl_xy[this->i][1]+j*30>300)//判断是否在边界处
{
flag_r=1;
break;
}
if(flag_r) break;
}
//左边界判断
int flag_l=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最左边的方块
if(this->bl_xy[this->i][1]+(j-1)*30<0)//判断是否在左边界处
{
flag_l=1;
break;
}
if(flag_l) break;
}
int flag=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j-1]==1)//判断与背景的方块是否有重合
{
flag=1;
break;
}
if(flag) break;
}
if(flag||flag_r||flag_l)
{
int g[18]={0,
0,1,0,0,
0,1,0,0,
0,1,0,0,
0,1,0,0};
this->bl[7].set(g);//第七个方块的数组值赋值
}
}
else
{
int g[18]={0,
0,1,0,0,
0,1,0,0,
0,1,0,0,
0,1,0,0};
this->bl[7].set(g);//第七个方块的数组值赋值
//右边界判断
int flag_r=0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最右边的方块
if(this->bl_xy[this->i][1]+j*30>300)//判断是否在边界处
{
flag_r=1;
break;
}
if(flag_r) break;
}
//左边界判断
int flag_l=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最左边的方块
if(this->bl_xy[this->i][1]+(j-1)*30<0)//判断是否在左边界处
{
flag_l=1;
break;
}
if(flag_l) break;
}
int flag=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j-1]==1)//判断与背景的方块是否有重合
{
flag=1;
break;
}
if(flag) break;
}
if(flag||flag_r||flag_l)
{
int g[18]={0,
0,0,0,0,
0,0,0,0,
1,1,1,1,
0,0,0,0};
this->bl[7].set(g);//第七个方块的数组值赋值
}
}
}
else
{
int temp[4][4];
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
temp[i][j]=this->bl[this->i].s[i+1][j];
for(int i=2;i<=4;i++)
for(int j=1;j<=3;j++)
this->bl[this->i].s[i][j]=temp[j][5-i];
//右边界判断
int flag_r=0;
for(int j=4;j>=1;j--)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最右边的方块
if(this->bl_xy[this->i][1]+j*30>300)//判断是否在边界处
{
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
this->bl[this->i].s[i+1][j]=temp[i][j];
flag_r=1;
break;
}
if(flag_r) break;
}
//左边界判断
int flag_l=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[i][j]==1)//找到最左边的方块
if(this->bl_xy[this->i][1]+(j-1)*30<0)//判断是否在左边界处
{
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
this->bl[this->i].s[i+1][j]=temp[i][j];
flag_l=1;
break;
}
if(flag_l) break;
}
//背景判断
int flag_b=0;
for(int j=1;j<=4;j++)
{
for(int i=1;i<=4;i++)
if(this->bl[this->i].s[j][i]==1)//判断最下面一行的方块
if(this->bc[(this->bl_xy[this->i][2])/30+i][(this->bl_xy[this->i][1])/30+j-1]==1)//判断与背景的方块是否有重合
{
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
this->bl[this->i].s[i+1][j]=temp[i][j];
flag_b=1;
break;
}
if(flag_b) break;
}
}
}
三、完整源码
如果大家对完整源码感兴趣,请点赞博文,然后再评论区留下邮箱,我会发送到大家的邮箱中的。(只留邮箱不点赞的,恕不发送,请尊重每个人的劳动成果)
后话
- 首先给大家说一下,博主经常在线,如果有什么问题或者想法,可以在下方评论,我会积极反馈的。
- 其次还是要请大家能够多多指出问题,我也会在评论区等候大家!
.