概要
利用C++面向对象,逐步完善逻辑完成代码架构
游戏类的定义、地图类的定义
整体架构流程
定义游戏类:
游戏类的属性有:分数、时间、地图尺寸
行为:画地图,地图右侧信息,行满消除行,碰撞测试等
定义图形类:
属性有:横纵坐标,图形颜色,图形样式,图形类型,存储所有图形
行为:设置图形初始坐标,对所有图形初始化,显示图形样式,绘制图形,移动图形等
技术细节
不仅各类有属性和行为,还需要利用单例模式来针对单一对象。利用单例模式,那就需要添加接口来访问类的私有域
游戏类做单例模式,在私有域中先定义一个构造函数并设置一个接口,用以访问私有域的数据,并且在public中设置一个静态指针函数用来接受接口数据,最后在类外初始化静态变态,开空间,存储地址名。
先写入项目所需要的头文件以及你所需要的宏定义
#include <iostream>
#include <easyx.h>
#include <windows.h>
#include <WinUser.h>
#include <ctime>
#include <cstdlib>
using namespace std;
//宏定义地图方块尺寸
#define MAP_WIDTH 10
#define MAP_HEIGH 20
#define BLOK_WIDTH 20
//所有颜色的数组
int colorAll[] = {BLUE,GREEN,CYAN,RED,MAGENTA,BROWN,LIGHTCYAN};
图形类
class block
{
private:
int x;//横坐标
int y;//纵坐标
COLORREF color;//图形颜色
bool blocks[4][4] = {{0}}; //图形样式
int type;//图形类型
static bool Allblock[7][4][4];//左右所有图形
public:
//设置图形初始坐标
block(const int &x = MAP_WIDTH/2 - 4/2,const int &y=0 ):x(x),y(y)
{
randtype();
}
//对所有图形的初始化存储
static void intallblock(void);
//随机选取图形操作
void randtype(void);
//显示图形样式
void show();
//绘制图形 x,y BLOK_WIDTH 游戏区域(20*20)
void drewblock();
//清除图形
void clear();
//移动函数
bool move (int direction = 0);
//检测碰撞函数
bool checkCollision();
//设置位移的接口函数
void setPos(const int &x = MAP_WIDTH/2 - 4/2,const int &y=0 );
//添加到地图
void addmap();
//设置按键
void key(int x);
//图形旋转
void rotate(void);
};
图形类的行为,先声明放在类内,再将定义写在类外,规范写法
//静态成员初始化
bool block::Allblock[7][4][4] = {{{0}}};
//随机产生图形的类型
void block::randtype(void)
{
//产生随机数 0 ~ 6
type = rand()%7;
//选取颜色
color = colorAll[type];
//将Allblock[type]数据赋值给block
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
blocks[i][j] = Allblock[type][i][j];
}
}
}
//存储所有图形的类型
void block::intallblock(void)
{
//长条
Allblock[0][0][1] = Allblock[0][1][1] = Allblock[0][2][1] = Allblock[0][3][1] = true;
//正七字
Allblock[1][0][0] = Allblock[1][1][0] = Allblock[1][1][2] = Allblock[1][1][1] = true;
//反七字
Allblock[2][1][0] = Allblock[2][1][1] = Allblock[2][1][2] = Allblock[2][2][0] = true;
//T字
Allblock[3][0][1] = Allblock[3][1][0] = Allblock[3][1][1] = Allblock[3][1][2] = true;
//田字
Allblock[4][0][0] = Allblock[4][0][1] = Allblock[4][1][0] = Allblock[4][1][1] = true;
//正Z
Allblock[5][0][0] = Allblock[5][0][1] = Allblock[5][1][1] = Allblock[5][1][2] = true;
//竖Z
Allblock[6][0][1] = Allblock[6][1][0] = Allblock[6][1][1] = Allblock[6][2][0] = true;
}
//用控制台验证代码可行性
void block::show()
{
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
cout << blocks[i][j] << " ";
}
cout <<endl;
}
}
//画出方块的图形
void block::drewblock()
{
setfillcolor(color);
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
//判断block是否有值
if(blocks[i][j])
{
//计算像素点
int left = 20 + (x+j)*BLOK_WIDTH;
int top = 20 + (y+i)*BLOK_WIDTH;
fillrectangle(left,top,left+BLOK_WIDTH,top+BLOK_WIDTH);
}
}
}
}
void block::clear()
{
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
//判断block是否有值
if(blocks[i][j])
{
//计算像素点
int left = 20 + (x+j)*BLOK_WIDTH;
int top = 20 + (y+i)*BLOK_WIDTH;
clearrectangle(left,top,left+BLOK_WIDTH,top+BLOK_WIDTH);
}
}
}
}
//图形移动
bool block::move(int direction)
{
if(direction == 0)
{
y++;
//进行碰撞检测
if(checkCollision())
{
y--;//重复方块,得会退一个格子
return true;
}
else
{
return false;
}
}
else if(direction ==1)//向左移动
{
x--;
if(checkCollision())
{
x++;
}
else
{
return false;
}
}
else if(direction ==2)//向右移动
{
x++;
if(checkCollision())
{
x--;
}
else
{
return false;
}
}
}
//图形旋转
void block::rotate(void)
{
if(type==0||type==1||type==2||type==3||type==5||type==6)
{
//四角
int temp1 = 0;
temp1 =blocks[0][0];
blocks[0][0] = blocks [0][2];
blocks[0][2] = blocks [2][2];
blocks[2][2] = blocks [2][0];
blocks[2][0] = temp1;
//十字
int temp2 = 0;
temp2 = blocks[0][1];
blocks[0][1] = blocks[1][2];
blocks[1][2] = blocks[2][1];
blocks[2][1] = blocks[1][0];
blocks[1][0] = temp2;
if(type == 0)
{
int temp = blocks[1][3];
blocks[1][3] = blocks[3][1];
blocks[3][1] = temp;
}
if(checkCollision())
{
int temp = blocks[0][0];
blocks[0][0] = blocks[2][0];
blocks[2][0] = blocks[2][2];
blocks[2][2] = blocks[0][2];
blocks[0][2] = temp;
temp = blocks[0][1];
blocks[0][1] = blocks[1][0];
blocks[1][0] = blocks[2][1];
blocks[2][1] = blocks[1][2];
blocks[1][2] = temp;
if(type == 0)
{
int temp = blocks[1][3];
blocks[1][3] = blocks[3][1];
blocks[3][1] = temp;
}
}
}
}
//设置x,y
void block::setPos(const int &x,const int &y)
{
this->x = x;
this->y = y;
}
游戏类
class Game
{
private:
int score =0;//分数
int game_time = 0;//时间
bool map[MAP_HEIGH][MAP_WIDTH] = {{0}};//地图尺寸
//单例模式,构造函数私有
Game(){}
Game(const Game &p){}
// 设置一个接口
static Game *p;
//设置析构函数
~Game()
{
delete p;
p = NULL;
}
public:
void play();//开始游戏
static Game *getInstance();//设置一个静态指针函数,接收获得接口的数据
//画地图
void drewmap();
//画右侧信息
void drewprompt();
//设置一个接口,给碰撞测试使用
bool getmap(const int &x = 0,const int &y = 0);
//设置map里面的方块数据函数 -----给block里面的addmap使用
void setmap(const int &x = 0,const int &y = 0,const int &value=0);
//消除行
void clearline();
};
类外定义游戏类的行为
//返回p的地址
Game* Game::getInstance()
{
return p;
}
//画地图尺寸
void Game::drewmap()
{
//绘制外圈圆角矩形---- 10,10,340,430,10,10
roundrect(10,10,340,430,10,10);
//绘制内层直角矩形---- 20,20,220,420
rectangle(20,20,220,420);
//设置一个填充色
setfillcolor(YELLOW);
//根据 map[MAP_WIDTH][MAP_HEIGH] 绘制地图 如果为1则填充矩形
int i,j;
for(i=0;i<MAP_HEIGH;i++)
{
for (j=0;j<MAP_WIDTH;j++)
{
if(map[i][j] == true)
{
int left = 20 + j*20;
int top = 20 + i*20;
//画格子
fillrectangle(left,top,left+BLOK_WIDTH,top+BLOK_WIDTH);
}
}
}
}
//画右侧信息
void Game::drewprompt()
{
//easyX的字体
//创建一个字体对象
LOGFONT f;
settextcolor(BLACK);
//可以让字体在默认的样式上进行修改----- 先获取一下默认值
gettextstyle(&f);
//可以设置自定义样式 ------235,20,_T("下一方块");
f.lfHeight = 30;
f.lfQuality = PROOF_QUALITY;
settextstyle(&f);
outtextxy(235,20,_T("下一方块"));
//显示分数----- 235,180,str
f.lfHeight = 15;
settextstyle(&f);
//将其他基本数据类型的数据一某种方式转成字符串---
char str[20] = {0};
sprintf(str,_T("分数:%d"),score);
outtextxy(235,180,str);
//显示时间 ---- 235,220,str
char str1[20] = {0};
sprintf(str1,_T("时间:%d秒"),game_time/1000);
outtextxy(235,220,str1);
}
//接口函数
bool Game::getmap(const int &x,const int &y)
{
return map[x][y];
}
//碰撞检测
bool block::checkCollision()
{
//获取Game对象
Game *game = Game::getInstance();
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
//计算相对位移 x+i y+j
if(blocks[i][j])
{
int left = 20 + (x+j)*BLOK_WIDTH;
int top = 20 + (y+i)*BLOK_WIDTH;
//map[x+j],[y+i]是私有的---需要用到一个接口来调用game
if(game->getmap(y+i,x+j) == 1 || left < 20 || left+BLOK_WIDTH>220 ||top+BLOK_WIDTH>420)
{
return true;
}
}
}
}
return false;
}
//设置map里面的方块数据函数 -----给block里面的addmap使用
void Game::setmap(const int &x,const int &y,const int &value)
{
map[x][y] = value;
}
//将block[4][4] 放入map
void block::addmap()
{
Game *game = Game::getInstance();
int i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
if(blocks[i][j])
{
//找坐标 y+i x+j
game->setmap(y+i,x+j,blocks[i][j]);
}
}
}
}
//消除行
void Game::clearline()
{
bool flag;
//每次消除一行即可
for(int i=0;i<20;i++)
{
flag = false;
//扫行
for(int j=0;j<10;j++)
{
if(map[i][j] == 0)
{
flag = true;
break;
}
}
if(flag == false)
{
//满行 将行从i-1开始依次向下移动
for(int line =i;line>0;line--)
{
for(int x=0;x<10;x++)
{
map[line][x] = map[line-1][x];
}
}
score+=10;
break;//每次清除一行
}
}
}
//在静态区开空间,将地址名存入p
Game* Game::p = new Game;
将所有的行为定义完成,需要用game写一个执行程序
//执行游戏 void Game::play() { cout << "开始游戏" << endl; //创建画布返回一个窗口句柄 HWND a = initgraph(350,440,EX_SHOWCONSOLE); // windos opi SetWindowText--设置窗口标题 //_T(字符串) 可以将字符串转换成 LPCTSTR类型 SetWindowText(a,_T("俄罗斯方块")); //设置背景色 setbkcolor(WHITE); cleardevice(); //设置线条颜色 setlinecolor(BLACK); Game::drewmap(); // 调用绘制地图 Game::drewprompt();// 调用绘制右侧信息 //初始化所有图形 block::intallblock(); //刷新种子 srand(time(NULL)); block b; b.show(); //画出图形初始位置 b.drewblock(); //画出下一个方块 block nextblock(11,2); nextblock.drewblock(); //定义时间用于控制下降的速度 clock_t start = 0; clock_t end; BeginBatchDraw(); ExMessage mes; while(1) { //4*4擦除 b.clear(); //清空地图 --- 20,20 220,420 clearrectangle(20,20,220,420); //重新绘制 drewmap(); //按键操作 bool res1 = peekmessage(&mes,EX_KEY); if(res1&&mes.message == WM_KEYDOWN) { if(mes.vkcode == VK_DOWN||mes.vkcode =='s'||mes.vkcode =='S') { bool res = b.move(0);//检测本次移动是否结束 if(res) { b.addmap(); b = nextblock; b.setPos(); nextblock.randtype(); clearrectangle(240,60,320,140); nextblock.drewblock(); if(b.checkCollision()) { b.drewblock(); MessageBox(GetHWnd(),_T("游戏结束"),_T("haha"),MB_OKCANCEL); goto exit; } } } else if(mes.vkcode == VK_UP) { b.rotate(); } else if(mes.vkcode == VK_LEFT||mes.vkcode =='A'||mes.vkcode =='a') { b.move (1); } else if(mes.vkcode == VK_RIGHT||mes.vkcode =='D'||mes.vkcode =='d') { b.move (2); } else if(mes.vkcode == VK_ESCAPE) { goto exit; } } end = clock();//获取系统时间 if(end-start >=500) { bool res = b.move();//检测本次移动是否结束 if(res) { b.addmap(); //b = nextblock b = nextblock; //b修改x,y ---- 私有的 ----block b.setPos(); //生成新的nextblock nextblock.randtype(); clearrectangle(240,60,320,140);//清除已经画出来的xext方块 //绘制新的nextblock nextblock.drewblock(); if(b.checkCollision()) { // 检测到碰撞绘制一下 b.drewblock(); //提示用户游戏结束--弹窗口-- windows MessageBox MessageBox(GetHWnd(),_T("游戏结束"),_T("haha"),MB_OKCANCEL); goto exit; } } start = clock(); game_time +=500; drewprompt(); } b.drewblock(); //清除行 clearline(); drewprompt(); FlushBatchDraw(); Sleep(50); } exit: EndBatchDraw(); }
主函数
int main(int argc, char** argv) { Game *p = Game::getInstance(); p->play(); return 0; }
小结
以上代码完整放入devc++,即可做出俄罗斯方块,但是需要下载easyx(链接为EasyX Graphics Library for C++),下载好后将devc++这个文件打开
easyx文档解压完是这样,将easyx下载后的文件名一一对应放入:
不要直接拖文件夹,要将easyx中的文件逐一放入
devc++软件(链接:https://pan.baidu.com/s/1qL_m4MJOijTvbT2LrvOl-w
提取码:4pb0)
这样就可以调用easyx的库
最后在devc++中点击 项目 --- 项目属性 --- 参数 ----加入以上参数就可以完成俄罗斯方块项目