资源描述
课程设计报告
题 目 :基于C++俄罗斯方块
学 院:
专 业:
学 号:
姓 名:
二○一三 年 十二 月
经典小游戏设计-俄罗斯方块
一、 需求分析。
1.1、游戏需求
随机给出不同的形状(长条形、Z字形、反Z形、田字形、7字形、反7形、T字型)下落填充给定的区域,若填满一条便消掉,若在游戏中各形状填满了给定区域,为输者,弹出相应提示。
1.2、游戏界面需求
良好的用户界面,有关信息显示(如操作方法、等级等)。让方块在一定的区域内运动与变形,该区域用一种颜色表明,即用一种颜色作为背景,本游戏的背景设为黑色。还需用另一种颜色把黑色围起来,宽度适中,要实现美感。而不同的方块用不同的着色表示,使游戏界面更加清晰、有条理。消层时采用一定的时间延迟,增加视觉消行的感官效果。
1.3、游戏方块需求
良好的方块形状设计,绘制七种常见的基本图形(长条形、Z字形、反Z形、田字形、L字形、反L形、T字型)以及另外本程序另外加入的点形方块,各个方块要能实现它的变形,可设为顺时针或逆 时针变形,一般为逆时针。为体现游戏的趣味性与扩展性,本游戏象征性的增加了点形方块,其他更多形状的方块可用类似方法增加。
1.4、游戏控制的需求
游戏控分为多个方面,包括画面绘制,控制命令的获取,控制命令的分配、控制命令的处理,方块的绘制,方块的移动,方块的旋转,方块下落与消层以及计分等。对各个命令的合理处理与综合控制十分重要,一旦出错可能导致整个程序的崩溃,因此需要小心设计。
二、 系统设计。
2.1、程序流程图:
2.2、游戏设计概述
从整体上而言,在该游戏可设计一个方块类,其中包括对方块的信息描述(如:ID)、方块的操作(如:旋转、下沉)。再设计一个控制类,实现各种控制(如:获取控制信号,分发控制信号)。另定义一个游戏区类,用以处理游戏区绘制等内容。框图如下:
方块类(GAME_BLOCK)
游戏区类(Window)
Private:
BLOCKINFO g_CurBlock;
(新方块)
BLOCKINFO g_NextBlock;
(下一方块)
Private:
无
Public:
Void InitWindow()
Public:
GAME_BLOCK (){}
~GAME_BLOCK (){}
void NewBlock();//生成方块
bool CheckBlock(BLOCKINFO _block);//检测方块能否放下
void DrawBlock(BLOCKINFO _block,DRAW_draw=SHOW);
void OnRotate();//旋转方块
void OnLeft();// 左移方块
void OnRight();// 右移方块
void OnDown();// 下移方块
void OnSink(CGAME &);// 沉底方块
BLOCKINFO &CurBlock();
BLOCKINFO &NextBlock();
游戏控制类(CGame)
Private:
Void DispatchControl(CTRL);
Public:
void InitGame();//初始化游戏
Void Start_Game();//开始游戏
void Game_Over();//游戏结束
void NewGame();//新游戏
void Quit_Game();//退出游戏
CTRL GetControl(bool _onlyresettimer = false);//获取控制命令
在主函数中(按照2.1中框图),先通过控制类初始化游戏,再通过随机时间函数获得一个随机数,该随机数确定一个方块,即用该随机数产生一个ID从而确定产生的为方块。然后从键盘取得各种操作信号,通过控类函数对操作信号进行分发、处理,进而控制方块的行为改变。及此同时,监控游戏区中已有方块的状态,一旦满足消行即进行消行控制,同时进行计分与等级划分,而如果游戏区已满则游戏结束,弹出提示。而对界面与方块的展现主要通过第三方软件EasyX实现,通过其画图位置的改变及时间的结合达到方块视觉移动的效果。
2.3、定义方块的数据结构
方块是本游戏的基本要素,对于方块的设计,本游戏用4*4的矩阵画出来,在相应的位置置为‘1’与‘0’以实现各种方块的形状,以“立L形”为例如下:
0
1
0
0
0
1
0
0
0
1
1
0
0
0
0
0
而在程序设计中则赋予各种基本方块一个不同的十六进制ID即可表示该方块,如“立7形”其ID为[0x4460],再及其其他变形组合与着色分配即可得到各种L形ID的集合{0x4460, 0x02E0, 0x0622, 0x0740,MAGENTA};其他各种方块类似设计。具体清单如下:
{ {0x0F00, 0x4444, 0x0F00, 0x4444, RED}, // I
{0x0660, 0x0660, 0x0660, 0x0660, BLUE}, // 口
{0x4460, 0x02E0, 0x0622, 0x0740, CYAN}, // L 蓝绿色
{0x2260, 0x0E20, 0x0644, 0x0470, GREEN}, // 反L
{0x0C60, 0x2640, 0x0C60, 0x2640, BROWN}, // Z
{0x0360, 0x4620, 0x0360, 0x4620, YELLOW}, // 反Z
{0x4E00, 0x4C40, 0x0E40, 0x4640, MAGENTA}}; // T 品红
2.4、方块的变形
方块要实现变形,其实就是通过EasyX画出不同的图形来实现的,当然乱画是不行的,而为了控制其变形的情况,必须设定相应的图形描述,本程序使用的是不同图形的不同ID码表示的方法来实在方块的描述的。能过键盘接收变形指令(即向上键),将所得信号传递给信号接收函数,再通过信号处理函数改变当前方块的ID值,最后根据新的ID值画出新的图形,此时即实在了方块的变形。
2.5、定时处理机制
为了提高游戏的易控性与自动性,定时机制是很有必要的。经过定时器的设置后,这里通过利用控制程序跳到定时器的时间的处理函数去实现,当固定时间片间隔到达后,先检测当前下坠物是否已经到达了底部,不是则进行下坠物向下移动一个单位的操作,是则到底后产生一个新的“下一个下坠物”,并代替旧的,将原先旧的“下一个下坠物”用作当前激活状态下正在使用的下坠物,并对使用后的一些状态进行检测:是否马上到达底部,使则进行销行操作;是否在到达底部的同时到达游戏区域的顶部,从而判定游戏是否因违规而结束,弹出相应对话框供用户选择是否继续重新开始。
图 3.2.2装载方块
视图类通过不同十六进制ID来记录下坠物的类型,共有七种形状,并从7种方块中随机抽取图形。而ID除了记录下坠物的类型外,还需记录其当前的变形状态。
在产生新的下一个下坠物前,需要先将当前状态物的记录与旧的下一个下坠物保存下来,然后用随机函数Random()产生一个最大值不大于指定值的随机正整数,将这个新生成的正整数用作新的“下一个下坠物”的形状值。
三、关键代码描述。
#include <easyx.h>
#include <conio.h>
#include <time.h>
/////////////////////////////////////////////
// 定义常量、枚举量、结构体、全局变量
/////////////////////////////////////////////
#define WIDTH 10 // 游戏区宽度
#define HEIGHT 22 // 游戏区高度
#define UNIT 20 // 每个游戏区单位的实际像素
// 定义操作类型
enum CMD
{
CMD_ROTATE, // 方块旋转
CMD_LEFT, CMD_RIGHT, CMD_DOWN, // 方块左、右、下移动
CMD_SINK, // 方块沉底
CMD_QUIT // 退出游戏
};
// 定义绘制方块的方法
enum DRAW
{
SHOW, // 显示方块
CLEAR, // 擦除方块
FIX // 固定方块
};
// 定义七种俄罗斯方块
struct BLOCK
{
WORD dir[4]; // 方块的四个旋转状态
COLORREF color; // 方块的颜色
} g_Blocks[7] = { {0x0F00, 0x4444, 0x0F00, 0x4444, RED}, // I
{0x0660, 0x0660, 0x0660, 0x0660, BLUE}, // 口
{0x4460, 0x02E0, 0x0622, 0x0740, CYAN}, // L 蓝绿色
{0x2260, 0x0E20, 0x0644, 0x0470, GREEN}, // 反L
{0x0C60, 0x2640, 0x0C60, 0x2640, BROWN}, // Z
{0x0360, 0x4620, 0x0360, 0x4620, YELLOW}, // 反Z
{0x4E00, 0x4C40, 0x0E40, 0x4640, MAGENTA}}; // T 品红
// 定义当前方块、下一个方块的信息
struct BLOCKINFO
{
byte id; // 方块 ID
char x, y; // 方块在游戏区中的坐标
byte dir:2; // 方向
} g_CurBlock, g_NextBlock;
// 定义游戏区
BYTE g_World[WIDTH][HEIGHT] = {0};
/////////////////////////////////////////////
// 函数声明
/////////////////////////////////////////////
void Init(); // 初始化游戏
void Quit(); // 退出游戏
void NewGame(); // 开始新游戏
void GameOver(); // 结束游戏
CMD GetCmd(); // 获取控制命令
void DispatchCmd(CMD _cmd); // 分发控制命令
void NewBlock(); // 生成新的方块
bool CheckBlock(BLOCKINFO _block); // 检测指定方块是否可以放下
void DrawUnit(int x, int y, COLORREF c, DRAW _draw);// 画单元方块
void DrawBlock(BLOCKINFO _block, DRAW _draw = SHOW);// 画方块
void OnRotate(); // 旋转方块
void OnLeft(); // 左移方块
void OnRight(); // 右移方块
void OnDown(); // 下移方块
void OnSink(); // 沉底方块
static int score=0;
/////////////////////////////////////////////
// 函数定义
/////////////////////////////////////////////
// 主函数
void main()
{
Init();
CMD c;
while(true)
{
c = GetCmd();
DispatchCmd(c);
// 按退出时,显示对话框咨询用户是否退出
if (c == CMD_QUIT)
{
HWND wnd = GetHWnd();
if (MessageBox(wnd, _T("您要退出游戏吗?"), _T("提醒"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK)
Quit();
}
}
}
// 初始化游戏
void Init()
{
initgraph(640, 480);
srand((unsigned)time(NULL));
setbkmode(TRANSPARENT); // 设置图案填充的背景色为透明
// 显示操作说明
settextstyle(14, 0, _T("宋体"));
outtextxy(20, 330, _T("操作说明:"));
outtextxy(20, 350, _T("上:旋转"));
outtextxy(20, 370, _T("下:下移"));
outtextxy(20, 390, _T("左:左移"));
outtextxy(20, 410, _T("右:右移"));
outtextxy(20, 430, _T("空格:沉底"));
outtextxy(20, 450, _T("ESC:退出"));
outtextxy(40, 150,_T(score));
// 设置坐标原点
setorigin(220, 20);
// 绘制游戏区边界
rectangle(-1, -1, WIDTH * UNIT, HEIGHT * UNIT);
rectangle((WIDTH + 1) * UNIT - 1, -1, (WIDTH + 5) * UNIT, 4 * UNIT);
// 开始新游戏
NewGame();
}
// 退出游戏
void Quit()
{
closegraph();
exit(0);
}
// 开始新游戏
void NewGame()
{
// 清空游戏区
setfillcolor(BLACK);
solidrectangle(0, 0, WIDTH * UNIT - 1, HEIGHT * UNIT - 1);
ZeroMemory(g_World, WIDTH * HEIGHT);
// 生成下一个方块
g_NextBlock.id = rand() % 7;
g_NextBlock.dir = rand() % 4;
g_NextBlock.x = WIDTH + 1;
g_NextBlock.y = HEIGHT - 1;
// 获取新方块
NewBlock();
}
// 结束游戏
void GameOver()
{
HWND wnd = GetHWnd();
if (MessageBox(wnd, _T("游戏结束。\n想重新来一局吗?"), _T("游戏结束"), MB_YESNO | MB_ICONQUESTION) == IDYES)
NewGame();
else
Quit();
}
// 获取控制命令
DWORD m_oldtime;
CMD GetCmd()
{
// 获取控制值
while(true)
{
// 如果超时,自动下落一格
DWORD newtime = GetTickCount();
if (newtime - m_oldtime >= 500)
{
m_oldtime = newtime;
return CMD_DOWN;
}
// 如果有按键,返回按键对应的功能
if (kbhit())
{
switch(getch())
{
case 'w':
case 'W': return CMD_ROTATE;
case 'a':
case 'A': return CMD_LEFT;
case 'd':
case 'D': return CMD_RIGHT;
case 's':
case 'S': return CMD_DOWN;
case 27: return CMD_QUIT;
case ' ': return CMD_SINK;
case 0:
case 0xE0:
switch(getch())
{
case 72: return CMD_ROTATE;
case 75: return CMD_LEFT;
case 77: return CMD_RIGHT;
case 80: return CMD_DOWN;
}
}
}
// 延时 (降低 CPU 占用率)
Sleep(20);
}
}
// 分发控制命令
void DispatchCmd(CMD _cmd)
{
switch(_cmd)
{
case CMD_ROTATE: OnRotate(); break;
case CMD_LEFT: OnLeft(); break;
case CMD_RIGHT: OnRight(); break;
case CMD_DOWN: OnDown(); break;
case CMD_SINK: OnSink(); break;
case CMD_QUIT: break;
}
}
// 生成新的方块
void NewBlock()
{
g_CurBlock.id = g_NextBlock.id, g_NextBlock.id = rand() % 7;
g_CurBlock.dir = g_NextBlock.dir, g_NextBlock.dir = rand() % 4;
g_CurBlock.x = (WIDTH - 4) / 2;
g_CurBlock.y = HEIGHT + 2;
// 下移新方块直到有局部显示
WORD c = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
while((c & 0xF) == 0)
{
g_CurBlock.y--;
c >>= 4;
}
// 绘制新方块
DrawBlock(g_CurBlock);
// 绘制下一个方块
setfillcolor(BLACK);
solidrectangle((WIDTH + 1) * UNIT, 0, (WIDTH + 5) * UNIT - 1, 4 * UNIT - 1);
DrawBlock(g_NextBlock);
// 设置计时器,用于判断自动下落
m_oldtime = GetTickCount();
}
// 画单元方块
void DrawUnit(int x, int y, COLORREF c, DRAW _draw)
{
// 计算单元方块对应的屏幕坐标
int left = x * UNIT;
int top = (HEIGHT - y - 1) * UNIT;
int right = (x + 1) * UNIT - 1;
int bottom = (HEIGHT - y) * UNIT - 1;
// 画单元方块
switch(_draw)
{
case SHOW:
// 画普通方块
setlinecolor(0x006060);
roundrect(left + 1, top + 1, right - 1, bottom - 1, 5, 5);
setlinecolor(0x003030);
roundrect(left, top, right, bottom, 8, 8);
setfillcolor(c);
setlinecolor(LIGHTGRAY);
fillrectangle(left + 2, top + 2, right - 2, bottom - 2);
break;
case FIX:
// 画固定的方块
setfillcolor(RGB(GetRValue(c) * 2 / 3, GetGValue(c) * 2 / 3, GetBValue(c) * 2 / 3));
setlinecolor(DARKGRAY);
fillrectangle(left + 1, top + 1, right - 1, bottom - 1);
break;
case CLEAR:
// 擦除方块
setfillcolor(BLACK);
solidrectangle(x * UNIT, (HEIGHT - y - 1) * UNIT, (x + 1) * UNIT - 1, (HEIGHT - y) * UNIT - 1);
break;
}
}
// 画方块
void DrawBlock(BLOCKINFO _block, DRAW _draw)
{
WORD b = g_Blocks[_block.id].dir[_block.dir];
int x, y;
for(int i = 0; i < 16; i++, b <<= 1)
if (b & 0x8000)
{
x = _block.x + i % 4;
y = _block.y - i / 4;
if (y < HEIGHT)
DrawUnit(x, y, g_Blocks[_block.id].color, _draw);
}
}
// 检测指定方块是否可以放下
bool CheckBlock(BLOCKINFO _block)
{
WORD b = g_Blocks[_block.id].dir[_block.dir];
int x, y;
for(int i = 0; i < 16; i++, b <<= 1)
if (b & 0x8000)
{
x = _block.x + i % 4;
y = _block.y - i / 4;
if ((x < 0) || (x >= WIDTH) || (y < 0))
return false;
if ((y < HEIGHT) && (g_World[x][y]))
return false;
}
return true;
}
// 旋转方块
void OnRotate()
{
// 获取可以旋转的 x 偏移量
int dx;
BLOCKINFO tmp = g_CurBlock;
tmp.dir++; if (CheckBlock(tmp)){ dx = 0; goto rotate; }
tmp.x = g_CurBlock.x - 1;if (CheckBlock(tmp)){ dx = -1;goto rotate; }
tmp.x = g_CurBlock.x + 1;if (CheckBlock(tmp)){ dx = 1; goto rotate; }
tmp.x = g_CurBlock.x - 2;if (CheckBlock(tmp)){ dx = -2;goto rotate; }
tmp.x = g_CurBlock.x + 2;if (CheckBlock(tmp)){ dx = 2; goto rotate; }
return;
rotate:
// 旋转
DrawBlock(g_CurBlock, CLEAR);
g_CurBlock.dir++;
g_CurBlock.x += dx;
DrawBlock(g_CurBlock);
}
// 左移方块
void OnLeft()
{
BLOCKINFO tmp = g_CurBlock;
tmp.x--;
if (CheckBlock(tmp))
{
DrawBlock(g_CurBlock, CLEAR);
g_CurBlock.x--;
DrawBlock(g_CurBlock);
}
}
// 右移方块
void OnRight()
{
BLOCKINFO tmp = g_CurBlock;
tmp.x++;
if (CheckBlock(tmp))
{
DrawBlock(g_CurBlock, CLEAR);
g_CurBlock.x++;
DrawBlock(g_CurBlock);
}
}
// 下移方块
void OnDown()
{
BLOCKINFO tmp = g_CurBlock;
tmp.y--;
if (CheckBlock(tmp))
{
DrawBlock(g_CurBlock, CLEAR);
g_CurBlock.y--;
DrawBlock(g_CurBlock);
}
else
OnSink(); // 不可下移时,执行“沉底方块”操作
}
// 沉底方块
void OnSink()
{
int i, x, y;
// 连续下移方块
DrawBlock(g_CurBlock, CLEAR);
BLOCKINFO tmp = g_CurBlock;
tmp.y--;
while (CheckBlock(tmp))
{
g_CurBlock.y--;
tmp.y--;
}
DrawBlock(g_CurBlock, FIX);
// 固定方块在游戏区
WORD b = g_Blocks[g_CurBlock.id].dir[g_CurBlock.dir];
for(i = 0; i < 16; i++, b <<= 1)
if (b & 0x8000)
{
if (g_CurBlock.y - i / 4 >= HEIGHT)
{ // 如果方块的固定位置超出高度,结束游戏
GameOver();
return;
}
else
g_World[g_CurBlock.x + i % 4][g_CurBlock.y - i / 4] = 1;
}
// 检查是否需要消掉行,并标记
BYTE remove = 0; // 低 4 位用来标记方块涉及的 4 行是否有消除行为
for(y = g_CurBlock.y; y >= max(g_CurBlock.y - 3, 0); y--)
{
i = 0;
for(x = 0; x < WIDTH; x++)
if (g_World[x][y] == 1)
i++;
if (i == WIDTH)
{
remove |= (1 << (g_CurBlock.y - y));
setfillcolor(LIGHTGREEN);
setlinecolor(LIGHTGREEN);
setfillstyle(BS_HATCHED, HS_DIAGCROSS);
fillrectangle(0, (HEIGHT - y - 1) * UNIT + UNIT / 2 - 5, WIDTH * UNIT - 1, (HEIGHT - y - 1) * UNIT + UNIT / 2 + 5);
setfillstyle(BS_SOLID);
}
}
if (remove) // 如果产生整行消除
{
// 延时 300 毫秒
Sleep(300);
// 擦掉刚才标记的行
IMAGE img;
for(i = 0; i < 4; i++, remove >>= 1)
{
if (remove & 1)
{
for(y = g_CurBlock.y - i + 1; y < HEIGHT; y++)
for(x = 0; x < WIDTH; x++)
{
g_World[x][y - 1] = g_World[x][y];
g_World[x][y] = 0;
}
getimage(&img, 0, 0, WIDTH * UNIT, (HEIGHT - (g_CurBlock.y - i + 1)) * UNIT);
putimage(0, UNIT, &img);
score++;
outtextxy(50, 150,_T(score));
}
}
}
// 产生新方块
NewBlock();
}
四、测试结果。
程序完成后运行如下,达到预期结果,运行效果良好。
成功地完成了方块的显示、旋转、消行功能,游戏可玩性强,且具有封装性、通用性、容错性。
五、实验总结。
本次C++课程设计前期准备阶段,设想一些构想与主要任务;后期则是具体功能的实现。
这次设计中我遇到了不少困难,本来设想实现双人版对战,美化界面与增加背景音乐等功能,可是由于时间的短促,最终没能实现,心里不免有些遗憾,在实现类的封装与类的继承与多态性时,也遇到了不少问题,通过大家的一起探讨与分析,问题最终一个个迎刃而解,系统开发基本完毕。
通过这次课程设计,使我懂得了不少东西。我们在日常的学习中应该注意对所学知识的实践运用,在学习时,应该深入的了解,体会知识,这对我们今后的学习与工作会有很大的帮助。通过此次的实践,我体会到学以致用的乐趣,收获了很多。
30 / 31
展开阅读全文