一、需求分析
1.该程序是完成一个简易的俄罗斯方块的任务,其要完成几个重要的功能:界面,方块下落,旋转,判断是否还能下落,左右移动,分数,速度设置,清楚满的每行,下个方块的预览等;
2.可用#include <graphics.h>的头文件来实用几个函数来控制并完成游戏的界面;
3.可用7个二维5*5数组去实现下落方块的全部类型,再用随机函数使其随机下落;
4.用#include <conio.h>的头文件去使用按键的控制,以保证用户能够合理操作;并用#include <stdlib.h>的头文件来使用rand函数来随机出示七种方块;完成正常的显示和下个方块的预览;
5.用 #include <bios.h>来运用时针去控制时间;
6.程序执行过程:然后加上几个基本的头文件来执行函数,首先设置按键操作,用:w,a,d,s控制,然后进行界面的初始化设置,启动,时针参数设置,开始新游戏,开始随机下落方块,显示下一个方块,再行旋转,移动,清除,加分,速度参数,最后判断是否已满,停止游戏,打出分数,结束的画面设置,游戏终止...
二、概要设计
该程序中的函数设置总体主要功能大致如下
1.找到合适的方块盒子(7种)使其一一下落 box[MAX_C][5][5] = { /*MAX_C(7)种预定义的盒子*/ 这样用类似的7个5*5二维数组来实现每个盒子方块的形状,自此就引荐两个作为介绍, {
{0,0,0,0,0},
{0,0,0,0,0},
{0,0,1,0,0},
{0,1,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{1,1,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
},如此中用1代表显示的格子,0代表空。
2.设置时间指针,实现旋转,移动,消去,显示加分,改变速度分别用下面函数表示
int setTimer(Timer *t, unsigned int intv, BOOL en);
/*设置时钟t,参数分别为时钟指针,时间间隔,是否活动*/
void rotateBox(int box1[5][5], int box2[5][5])
/*旋转box1输出到box2*/
int move(int dir) /*实现移动,返回成功与否*/
void clear() /*清除掉满行*/
void prscore() /*打印现在的分数*/
void spe()/*显示速度的改变*/
3.外部函数功能如下图
4.建立整个函数的流程图如下:
流程简介:玩家定义游戏开始,游戏显示开始界面,时针已经设定好,然后随机出示方块,玩家进行方块的旋转,左右移动,和下落控制,然后在旁边的表格中出示另一个表格显示下一个即将下落的方块,然后判断是否到达底部,若到,继续出示方块,若没,则可继续变换,下落到低端后在进行判断,是否可以消除该行,再进行分数的变换,速度的调整,然后判断是否已经满,若未满,则继续出示下一个方块,若满,则结束游戏,打印分数,出示结束界面,游戏终止。
三、详细设计
1.设置七种方块:这个用int box[MAX_C][5][5] = /*MAX_C(7)种预定义的盒子*/ 来实现;即七个5*5的盒子数组,方块实体用1表示,0表示空;七种如下所示,细心就会发现{
{0,0,0,0,0},
{0,0,0,0,0},
{1,1,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,0,1,0,0},
{0,1,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,1,1,0,0},
{0,0,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,0,1,1,0},
{0,1,1,0,0},
{0,0,0,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,1,1,0,0},
{0,0,1,0,0},
{0,0,1,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,0,1,1,0},
{0,0,1,0,0},
{0,0,1,0,0},
{0,0,0,0,0}
},
{
{0,0,0,0,0},
{0,0,1,1,0},
{0,0,1,1,0},
{0,0,0,0,0},
{0,0,0,0,0}
}
}; 这样七种盒子的方块就被清楚的展现在你们面前。用来保证方块不会出来
2.我们要进行按键的设置#define KEY_UP 'w' /*定义上下左右按按键*/
#define KEY_DOWN 's'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_ESC 27 /*退出*/
3.时钟的控制 ,用来控制方块的下落间隔时间及速度/*时钟结构控制*/
typedef struct { /*时钟结构*/
BOOL enabled; /*时钟是否开启*/
unsigned int intervel; /*定时间隔*/
unsigned int lasttime; /*这个属于内部使用变量*/
} Timer;
4.int GetTickCount() { /*读取BIOS时钟*/
int ret;
ret = peek(0x0,0x46e); /*实际上读取了内存0:046e处的内容*/
ret <<= 8; /*这个地方是$%#$^$%&^*/
ret += peek(0x0,0x46c); /*太多新的东西了,找点书看一看吧*/
return (ret);
}
int setTimer(Timer *t, unsigned int intv, BOOL en)
{
t -> enabled = en; /*设置一个时钟罗*/
t -> intervel = intv;
t -> lasttime = GetTickCount(); /*lasttime记录的是上一个*/
/*tickcount返回的东西*/
/*这样当再一次测试时间时新的tickcount产生了
它来减去上一次的tickcount就得出了一个时间 间隔,这个就可以和intervel比较从而得出是否
激活了
*/
return 0;
} 实现了以上的操作后,接下来要实现界面的设置一方便盒子在界面上的显示和操作;
5.void initMap(void) { /*初始化地图*/ /*
我们须要一圈卫兵呵呵,全是1...用来保证方块不会出来*/
int x, y;
for(y = 0; y < MAX_Y; y++) {
for(x = 0; x < MAX_X; x++) {
if(x < 2 || x > MAX_X - 3 || y > MAX_Y - 3)
map[y][x] = 1;
else map[y][x] = 0;
}
} /*这里初始化出这个形状*/
} /*当然是无盖的...*/
void render(void) /*这里唯一的绘图函数*/
int x, y;
static int cPage = 0; /*当前页,换页用*/
#define STARTX 50 /*定义几个常量*/
#define STARTY 0 /*数值根据自己的需要可以自己设置*/
#define LEN 18
setactivepage(cPage=(cPage == 0?1:0)); /*选择页*/
cleardevice(); /*清屏*/
setcolor(12);/*前景颜色*/
prscore(); setcolor(15);
rectangle( STARTX + LEN * 2 - 2,
STARTY + LEN * 3 - 2,
STARTX + LEN * (MAX_X - 2) + 2,
STARTY + LEN * (MAX_Y - 2) + 2);
/*用白色画一个外框*/
setfillstyle(SOLID_FILL, 5);
for(y = 3; y < MAX_Y - 2; y++) { /*画地图 */
for(x = 2; x < MAX_X - 2; x++) {
if(map[y][x]) {
rectangle( x * LEN + STARTX,
y * LEN + STARTY,
x * LEN + STARTX + LEN,
y * LEN + STARTY + LEN);
bar( x * LEN + STARTX + 1,
y * LEN + STARTY + 1,
x * LEN + STARTX + LEN - 2,
y * LEN + STARTY + LEN - 2);
}
}
}
/*绘图操作就不要作太复杂的介绍了,这只写作用*/
/*以上段,根据地图上的点阵情况将地图反映到屏幕上*/
for(y = 0; y < 5; y++) { /*画下落物*/
for(x = 0; x < 5; x++) {
if(curbox[y][x]) {
if(y + cury > 2) {
rectangle( (x + curx) * LEN + STARTX,
(y + cury) * LEN + STARTY,
(x + curx) * LEN + STARTX + LEN,
(y + cury) * LEN + STARTY + LEN);
bar( (x + curx) * LEN + STARTX +1,
(y + cury) * LEN + STARTY + 1,
(x + curx) * LEN + STARTX + LEN - 2,
(y + cury) * LEN + STARTY + LEN - 2);
}
}
}
}
/*以上将下落的盒子按昭它在地图上的坐标,画到对应的区域里*/
for(y = 0; y < 5; y++) { /*画下一个*/
for(x = 0; x < 5; x++) {
if(nextbox[y][x]) {
rectangle( x * LEN + 320,
y * LEN + 10,
x * LEN + 338,
y * LEN + 28);
bar( x * LEN + 321,
y * LEN + 11,
x * LEN + 336,
y * LEN + 26);
}
}
}
/*这个画出下一个盒子的预览*/
setvisualpage(cPage); /*确认在cPage页里画好了*/
/*将它显示出来*/
}
这样我们的初始化界面和盒子在图上的意义显示已经完成;
5.接下里我们要对即将出现的方块的形状进行预览即建立一个新的函数用来显示下一个下落的盒子;
void rebuidNext() { /*新建下一个形状并放到nextbox中*/
int i, x, y;
i = random(MAX_C); /*从几种方块里面选一种*/
for(y = 0; y < 5; y++) /*并复制过来*/
for(x = 0; x < 5; x++)
nextbox[y][x] = box[i][y][x]; /*复制*/
}
void putBox() { /*将curbox填充到地图上*/
int x, y;
for(y = 0; y < 5; y++) /*这个也简单,主要是要根*/
for(x = 0; x < 5; x++) /*据curx,cury指出位置 */
if(curbox[y][x])
map[y + cury][x + curx] = curbox[y][x];
}
int newfall() { /*创建下落元素失败返回0*/
int x, y;
curx = MAX_X / 2 - 2; /*重新指定小盒位置*/
cury = 0;
for(y = 0; y < 5; y++)
for(x = 0; x < 5; x++)
curbox[y][x] = nextbox[y][x];/*将nextBox复制过来*/
rebuidNext(); /*重建nextBox*/
return test(curx, cury, curbox);
}
这样一来,我们的预览盒子也就完成了。
4.完成以后,我们要进行下落的控制,这个在开始的控制指针已经做好的下落间隔设置,然后开始随机出示方块,玩家应开始进行旋转,移动的操作,下落的过程;
void rotateBox(int box1[5][5], int box2[5][5]) {
/*旋转box1输出到box2*/
int x, y;
for(x = 0; x < 5; x++) /*这个函数可以须要实际*/
for(y = 4; y >= 0; y--) /*编写一下才能印像深刻*/
box2[y][x] = box1[x][4 - y];
}
int rotate() /*整个旋转的操作并将盒子打印到屏幕上*/{
int x, y;
int newbox[5][5]; /*我们必须将当前盒子转动到新的盒子*/
/*再对这个新的盒子的冲突作测试*/
rotateBox(curbox, newbox); /*转动到新的盒子*/
if(test(curx, cury, newbox)) {
/*并且新的盒子能放到地图上而不冲突*/
for(y = 0; y < 5; y++)
for(x = 0; x < 5; x++)
curbox[y][x] = newbox[y][x]; /*复制进来*/
return 1;
}
else return 0;
}
int move(int dir) { /*返回成功与否*/
int newx;
if(dir) newx = curx + 1;
/*与drop一样,准备移动后的坐标*/
else newx = curx - 1;
if(test(newx, cury, curbox)) { /*测试是否冲突*/
curx = newx; /*可以的话切换curx*/
return 1;
}
return 0;
} 这个就完成了左右移动的操作;
int drop() { /*下落,返回成功与否*/
int newy; /*盒子要下落的新位置*/
newy = cury + 1; /*为当前Y位置+1*/
if(test(curx, newy, curbox)) {
cury = newy; /*测试下落盒在这个位置*/
return 1; /*上是否有冲突,没有的话*/
} /*直接设置cury*/
return 0;
}
int test(int mx, int my, int box[5][5]) {
/*测试box在map里mx,my位置上是否能着陆*/
/*这个是最关键的一个函数,它判断是否产生非空冲突*/
/*但算法还是很简单的*/
int x, y;
for(y = 0; y < 5; y++)
for(x = 0; x < 5; x++)
if(map[y + my][x + mx] && box[y][x])
return 0;
return 1;
}
这样就完成了旋转,移动,测试并下落的功能。
6.接下来我们要完成下落后的分数的改变速度的重新设定;
void clear() { /*清除掉满行*/ /*具体的算法为:
从第0行开始到最后一行,测试地图点阵是否为满,如果是的话
从当前行算起,之上的地图向下掉一行*/
int x, y;
int dx, dy;
int fullflag;
for(y = 0; y < MAX_Y - 2; y++) { /*最后两行保留行*/
fullflag = 1; /*假设为满*/
for(x = 2; x < MAX_X - 2; x++) { /*保留列~*/
if(!map[y][x]) {
fullflag = 0;
break;
}
}
if(fullflag) { /*向下移动一行*/
for(dy = y; dy > 0; dy--)
for(dx = 2; dx < MAX_X - 2; dx++)
map[dy][dx] = map[dy - 1][dx];
for(dx = 2; dx < MAX_X - 2; dx++)
map[0][dx] = 0; score+=10;/*输出新得分*/
/*并清除掉第一行*/
}
}
}
void prscore()/*输出分数*/{
char str[10];setfillstyle(SOLID_FILL,YELLOW);
rectangle(90,15,260,35);setcolor(6);
settextstyle(0,0,2);sprintf(str,"score:%d",score);
outtextxy(115,20,str);}
void spe()/*显示速度的改变*/{
if(score%50==0&&score!=0) setTimer(&tDown, speed--, 1);}
7.然后在主函数之前中定义以下的操作,这个应该会很容易理解
int map[MAX_Y+4][MAX_X+4]; /*地图\大盒子...MAX_X,Y是可见面积*/
/*我已说过需要在外面布两圈"卫兵"*/
int curbox[5][5]; /*当前下落的盒子*/
int curx, cury; /*保存着当前活动盒子在地图上的位置*/
int nextbox[5][5]; /*保存着下一个形状的盒子*/
8.接下俩用newgame函数将其串联起来,实现函数的内嵌;
void newGame() { /*新建游戏*/
int x, y;
initMap(); /*初始化地图*/
srand(GetTickCount()); /*初始化随机发生器*/
rebuidNext(); /*建立下一个*/
setTimer(&tDown, speed, 1); /*启动时钟(快慢两个)*/
setTimer(&tFast, FAST_INTV, 1);
newfall(); /*对下落的盒子操作一下*/
/*这样第一个下落的方块
就在地图顶部准备好了*/
}
9.接下来最为重要的主函数将要开始;
int main() {
char key; /*记录当前按键*/
int i;
int gd = VGA, gm = VGAMED; /*初始化的图形模式*/
Timer *ptDown; /*下落所指向的时钟(有快慢)*/
Timer trender; /*为了避免渲染给程序造成过大的负担*/
/*用一个时钟来控制渲染速度*/
/*把它设置interval = 1,*/
/*这样就是18 FPS了,当然无法达到标*/
/*准的60 FPS...毕竟这是DOS...*/
setTimer(&trender, 1, 1);
initgraph(&gd, &gm, ""); /*初始化图形*/
newGame(); /*新游戏...*/
prscore();
while(1) { /*主游戏循环*/
if(kbhit()) { /*如果键盘有按下*/
key = getch(); /*读取一个按键值到key*/
}
else key = 0;
switch(key) { /*对读到的key进行判断*/
case KEY_UP:
rotate(); /*上,旋转下落盒子*/
break;
case KEY_DOWN:
ptDown = &tFast; /*使用tFast时钟 */
break;
case KEY_LEFT:
move(0); /*左移*/
break;
case KEY_RIGHT:
move(1); /*右移*/
break;
case KEY_ESC:
closegraph(); /*结束游戏*/
exit(0);
default:
ptDown = &tDown; /*使用原来速度 */
}
if(testTimer(ptDown)) { /*在上面已设置了下落要
使用的时钟在ptDown里*/
if(!drop()) { /*下落,失败返回0*/
putBox(); /*写到地图里*/
clear();prscore();spe();setcolor(6) ;/*清除满行*/
if(!newfall()) { /*新建下落,失败则游戏结束*/
setcolor(14);
settextstyle(0,0,2);
outtextxy(100,200,"GAME OVER");/*游戏结束,在屏幕上打印字符串*/
outtextxy(100,240,"THANK YOU");
sleep(3);
exit(0);
}
}
}
if(testTimer(&trender)) /*最后...渲染...*/
render();
}
}
10.最后main函数已经介绍完毕,只需在开始加上程序所需的头文件和值的宏定义后整个程序就全部完毕
#include <stdio.h>
#include <stdlib.h>
#include <bios.h> /*这里须要读取系统运行时间来作为定时器*/
#include <graphics.h> /*很不幸,TC2的简单图形,让我放弃了用*/
#include <conio.h> /*win32+openGL来讲解.*/
#define MAX_X 14 /*可见最大X*/
#define MAX_Y 21 /*可见最大Y*/
/*我们定义了最大的可见X和Y,那么即还有不可见的部分,事实上地图(大盒子)里的左右两侧和底部各两行都被1填充,这样大大简化 出界的判断,事实上,在本例中没有这样的代码,因为旁边有一圈1阻止小盒子越出大盒子的按制范围
*/
#define MAX_C 7 /*最大种类,这个无须解释*/
#define FALSE 0
#define TRUE 1
到此为止,整个程序全部结束,详解到此完毕。
四、调试分析
1.开始第一次运行时方块不能显示出来,只能看到一个大白方框中几个空的白色的线,方块中间没东西,并且这个虚方块下落时不能被控制,这是源于在初始化界面时并没有将方块内部进行填充,再加用一个setfillstyle()函数即可填充内部的空动,方块不能被控制是因为为未判断是否有键按下在main函数中,若加上一个选择判断语句便可,
char key; /*记录当前按键*/ if(kbhit()) { /*如果键盘有按下*/
key = getch(); /*读取一个按键值到key*/ 这样便可以解决问题。
2.运行后发现方块无法旋转,并且反复块的出示有规律,并不是随机的,这个因为设定的函数有问题,重新修正后void rotateBox(int box1[5][5], int box2[5][5]) {
/*旋转box1输出到box2*/
int x, y;
for(x = 0; x < 5; x++) /*这个函数可以须要实际*/
for(y = 4; y >= 0; y--) /*编写一下才能印像深刻*/
box2[y][x] = box1[x][4 - y]; }如此一来,将整个边线都逆度针旋转90 度,便完成
要求, 然后在newgame函数和rubulid函数中中加上rand()随机数的设置,编能使整个方块随机出示。
3.分数只是会闪一下,但是并不能样一直显示,并且速度不会随这分数的增加进行改变,功能不完善,原因在于prscore函数应该在刚开始的绘图函数(即界面设置)中体现出来,然后才能将其体现到屏幕上,速度的设置在if(score%50==0&&score!=0) speed--;操作中是不能起作用的,不能单纯的进行speed--;应该将其放在时针控制中,所以应改为if(score%50==0&&score!=0) setTimer(&tDown, speed--, 1);}这样既可完成每增加50分进行速度的增加。
4.在终止程序时发生分数只能停留瞬间吗“game over”画面仅仅停留瞬间,这个只需在main函数结尾加上一个睡眠sleep()即可;
五、用户手册
由于这个程序很简单,故玩家操作也很简单,再次就做个简单介绍:游戏开始,
然后进入界面,方块自动下落,然后玩家用游戏键‘w’‘a’‘s’‘d’来控制左右移动和变换,w为方块的旋转,s为加速下落,a与d就是方块的左右移动。完成满行的消去,分数的增加,若玩家操作不当,到达顶部,则游戏结束。