一、需求分析
1.以三维数组block[n][i][j]来表示俄罗斯方块的不同形态。0表示无方块,1代表有方块。以二维数组visit[i][j]判断游戏区域某一位置是否有方块。0代表无方块,1代表有方块。以一维数组color[n]表示不同形状方块的颜色。以二维数组markcolor[i][j]固定方块降落完成后在游戏区域的方块颜色。
2.程序通过键盘输入判断Ascll值来决定执行操作。
3.用户可在进入界面进行等级选择,再通过等级大小进行方块降落速度的计算。
4.程序要完成方块的堆积以及当行满时对该行的消除。
5.要计算好方块的大小及游戏区域的大小,要使游戏区域大小为方块大小的整数倍。
6.设置好初始界面的大小、颜色及游戏界面的大小、颜色,预览区域大小颜色及字体背景色等。
7.程序要完成对方块能否旋转、左移、右移、下移的判断及实现
二、代码实现
1、 预处理
#include <iostream>
#include <graphics.h>
#include <conio.h> // 接受键盘输入输出
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
//界面的相关参数
#define WALL_SQUARE_WIDTH 10 //围墙方块的宽度
#define xWALL_SQUARE_NUM 30 //x轴方向的方块数目
#define yWALL_SQUARE_WIDTH 46 //y轴方向的方块数目
#define GAME_WALL_WIDTH (WALL_SQUARE_WIDTH*xWALL_SQUARE_NUM) //游戏区域的宽度 300
#define GAME_WALL_HTGH (WALL_SQUARE_WIDTH*yWALL_SQUARE_WIDTH) //游戏区域的高度 460
#define Mid_x 90 //新方块出现的位置
#define Mid_y -50 //新方块出现的位置
#define WINDOW_WIDTH 480 //游戏总窗口宽度 480
#define WINDOW_HIGH 460 //游戏总窗口高度 460
//俄罗斯方块的参数
#define ROCK_SQUARE_WIDTH (2*WALL_SQUARE_WIDTH) //俄罗斯方块的大小是围墙的2倍 20
#define xROCK_SQUARE_NUM ((GAME_WALL_WIDTH-20)/ROCK_SQUARE_WIDTH) //游戏区x轴放的方块数目 14
#define yROCK_SQUARE_NUM ((GAME_WALL_HTGH-20)/ROCK_SQUARE_WIDTH) //游戏区y轴放的方块数目 22
#define ROCK_NUM 5 //五种方块
#define ROCK_CASE 5 //方块外壳:表示方块形态的壳子
//键盘数据
#define KEY_Leave 27 //Esc
#define KEY_Up 294 //方向键上
#define KEY_Right 295 //方向键右
#define KEY_Left 293 //方向键左
#define KEY_Down 296 //方向键下
#define KEY_Space 32 //空格
#define KEY_One 49 //数字键 1
#define KEY_Two 50 //数字键 2
#define KEY_Three 51 //数字键 3
#define KEY_Four 52 //数字键 4
#define KEY_Zero 48 //数字键 0
int score; //分数
int rank; //等级
int speed=500;
int MinX=10,MinY=10; //最小可以放置方块的坐标
int NextIndex=-1; //下一个方块的种类
int NowIndex=-1; //当前方块的种类
int visit[yROCK_SQUARE_NUM][xROCK_SQUARE_NUM]; //将游戏区域分为n行m列的数组用1 or 0判断游戏区是否有方块
int markcolor[yROCK_SQUARE_NUM][xROCK_SQUARE_NUM]; //将游戏区域分为n行m列的数组来表示颜色
int color[ROCK_NUM]={GREEN,CYAN,MAGENTA,YELLOW,BLUE };//方块颜色
//初始化各种方块,及其形态变化
int block[ROCK_NUM*4][ROCK_CASE][ROCK_CASE]={
//I型方块
{0,0,0,0,0,
0,0,1,0,0,
0,0,1,0,0,
0,0,1,0,0,
0,0,1,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
},
{0,0,0,0,0,
0,0,1,0,0,
0,0,1,0,0,
0,0,1,0,0,
0,0,1,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
},
//L型方块
{0,0,0,0,0,
0,1,0,0,0,
0,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,1,0,
0,1,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,0,1,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,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,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,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,1,1,0,0,
0,0,0,0,0,
0,0,0,0,0
},
//T型方块
{0,0,0,0,0,
0,0,0,0,0,
0,1,1,1,0,
0,0,1,0,0,
0,0,0,0,0
},
{0,0,0,0,0,
0,0,1,0,0,
0,1,1,0,0,
0,0,1,0,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,1,0,0,0,
0,1,1,0,0,
0,1,0,0,0,
0,0,0,0,0
},
//Z型方块
{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,1,0,0,
0,1,1,0,0,
0,1,0,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,1,0,
0,0,0,0,0
},
{0,0,0,0,0,
0,0,1,0,0,
0,1,1,0,0,
0,1,0,0,0,
0,0,0,0,0
},
};
typedef enum
{
BLOCK_UP,
BLOCK_RIGHT,
BLOCK_DOWN,
BLOCK_LEFT
}block_dir_t;
typedef enum
{
MOVE_DOWN,
MOVE_LEFT,
MOVE_RIGHT
}move_dir_t;
void Inter_Face(); //初始化界面
void Inter_GameFace(); //初始化游戏界面
void clearBlock(); //清除右上角的方块
void clearblock(int x,int y,block_dir_t blockDir);//清除游戏区域移动的方块进行更新
void drawBlock(int x,int y);//在右上角绘制下一个方块
void drawBlock(int x,int y,int blockIndex,block_dir_t blockDir);//特定位置的特定方块
void nextblock(); //随机选取下一个方块
void newblock(); //游戏区方块的连续移动
int moveAble(int x0,int y0,move_dir_t moveDir,block_dir_t blockDir);//判断方块能否移动
int rotatable(int x,int y,block_dir_t blockDir);//判断能否转向
void move(); //方块的移动
void mark(int x,int y,int blockIndex,block_dir_t blockDir); //方块的固定
void down(int x); //消行函数
void addscore(int line); //更新分数函数
void check(); //对行进行检测,并进行消行更分
void GameProcess(); //整个游戏串联
void GameEnd(); //游戏结束页面
2、子函数
//初始化界面
void Inter_Face() {
setinitmode(0,400,200);//窗口位置
initgraph(WINDOW_WIDTH,WINDOW_HIGH,0);//窗口大小
setcaption("俄罗斯方块");
setfont(40,0,"隶体");
setcolor(RED);
outtextxy(100,100,"俄 罗 斯 方 块");
setcolor(WHITE);
setfont(20,0,"隶体");
outtextxy(130,200,"请从1-4选择等级并输入");
while(1)
{
if(kbhit())
{
int key=getch();
if(key==KEY_One)
{
rank=1; break;
}
else if(key==KEY_Two)
{
rank=2; break;
}
else if(key==KEY_Three)
{
rank=3; break;
}
else if(key==KEY_Four)
{
rank=4; break;
}
}
}
setfont(20,0,"宋体");
setcolor(LIGHTGRAY);
outtextxy(150,300,"请按任意键开始游戏");
getch();
}
void Inter_GameFace()
{
char str[16];
int i,j;
score=0;//初始化分数
cleardevice();
setfillcolor(BLACK);//颜色
bar(0,0,300,460); // 游戏框
setfillcolor(BLACK); //颜色
bar(300,0,480,460); //预览区
setcolor(WHITE);
setfont(10,0,"");
//游戏界面边框
for(i=0;i<xWALL_SQUARE_NUM;i++)
for(j=0;j<yWALL_SQUARE_WIDTH;j++)
{
//outtextxy(i*WALL_SQUARE_WIDTH,0,"▉");
outtextxy(i*WALL_SQUARE_WIDTH,450,"▉");//下边界
outtextxy(0,j*WALL_SQUARE_WIDTH,"▉");//左边界
outtextxy(290,j*WALL_SQUARE_WIDTH,"▉");//右边界
}
//预览区
setfontbkcolor(BLACK);
setfont(15,0,"宋体");
setcolor(WHITE);
outtextxy(350,10,"下一个方块");
outtextxy(350,170,"你的分数");
sprintf(str,"%d",score);
outtextxy(370,185,str);
outtextxy(360,210,"等级");
sprintf(str,"%d",rank);
outtextxy(370,230,str);
outtextxy(350,275,"游戏操作");
outtextxy(350,300,"↑:旋转");
outtextxy(350,325,"↓:下降");
outtextxy(350,350,"←:左移");
outtextxy(350,375,"→:右移");
outtextxy(350,400,"0:重新开始");
outtextxy(350,425,"空格:暂停");
}
//清除预览“下一个”5*5的界面
void clearBlock()
{
setcolor(BLACK);
setfont(ROCK_SQUARE_WIDTH,0,"");
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
int x=340+ROCK_SQUARE_WIDTH*j;
int y=30+ROCK_SQUARE_WIDTH*i;
outtextxy(x,y,"▉");
}
}
}
//清除游戏区域的方块
void clearblock(int x,int y,block_dir_t blockDir)
{
int id=NowIndex*4+blockDir;
y+=Mid_y;//获得该方块距离上边界的位置
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1)
{
setcolor(BLACK);
setfont(ROCK_SQUARE_WIDTH,0,"");
outtextxy(x+j*ROCK_SQUARE_WIDTH,y+i*ROCK_SQUARE_WIDTH,"▉");
}
}
}
}
//绘制新的下一个方块
void drawBlock(int x,int y)
{
setcolor(color[NextIndex]);
setfont(ROCK_SQUARE_WIDTH,0,"");
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[NextIndex*4][i][j]==1)
{ int x2=x+ROCK_SQUARE_WIDTH*j;
int y2=y+ROCK_SQUARE_WIDTH*i;
outtextxy(x2,y2,"▉");
}
}
}
}
//绘制特定位置特定方块
void drawBlock(int x,int y,int blockIndex,block_dir_t blockDir)
{
setcolor(color[NowIndex]);
setfont(ROCK_SQUARE_WIDTH,0,"");
int id=blockIndex*4+blockDir;
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1)
{
int x2=x+ROCK_SQUARE_WIDTH*j;
int y2=y+ROCK_SQUARE_WIDTH*i;
outtextxy(x2,y2,"▉");
}
}
}
}
//随机选取一种方块
void nextblock()
{
clearBlock(); //调用清除函数
srand(time(NULL));
NextIndex=rand()%ROCK_NUM-0;//随机取一种方块形态
drawBlock(340,30);
}
//判断方块是否能移动
//在前面条件成立下y+j不能超过行所容纳的小方块数量,x+i同理,且方块所移动区域无方块
int moveAble(int x0,int y0,move_dir_t moveDir,block_dir_t blockDir)
{
int x=(y0-MinY)/ROCK_SQUARE_WIDTH;//该点到上边界有几个小方块的位置
int y=(x0-MinX)/ROCK_SQUARE_WIDTH;//该点到左边界有几个小方块的位置
int id=NowIndex*4+blockDir; //方块此时的形态
int ret=1;
if(moveDir==MOVE_DOWN) //判断能否下移
{
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1&&(x+i+1>=yROCK_SQUARE_NUM||visit[x+i+1][y+j]==1))/*上边界到方块的之间可容纳小方块的个数+该方块该列的小方块个数不能>=游戏区域所能容纳的最大值;方块下面无以固定的方块。*/
ret=0;
}
}
}
else if(moveDir==MOVE_LEFT) //判断能否左移
{
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1&&(y+j==0||visit[x+i][y+j-1]==1))/*左墙到方块的之间可容纳小方块的个数+该方块该行的小方块个数不能<=0;方块左侧无以固定的方块。*/
ret=0;
}
}
}
else if(moveDir==MOVE_RIGHT) //判断能否右移
{
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1&&(y+j+1>=xROCK_SQUARE_NUM||visit[x+i][y+j+1]==1))/*左墙到方块的之间可容纳小方块的个数+该方块该行的小方块个数不能>=游戏区域所能容纳的最大值;方块右侧无以固定的方块。*/
ret=0;
}
}
}
return ret;
}
//游戏区域方块的连续移动部分过程的衔接
void newblock()
{
NowIndex=NextIndex; // 获得使用方块的信息
drawBlock(Mid_x,Mid_y); //在游戏区制作方块
Sleep(100); //停留0.1秒反应时间
nextblock(); //右上角绘制下一个方块
move(); //降落
}
//判断方块能否旋转 Y-> return 1;N->return 0
int rotatable(int x,int y,block_dir_t blockDir)
{
int id=NowIndex*4+blockDir;
int xIndex=(y-MinY)/ROCK_SQUARE_WIDTH;
int yIndex=(x-MinX)/ROCK_SQUARE_WIDTH;
if(!moveAble(x,y,MOVE_DOWN,blockDir))//向下不能移动不能进行旋转
return 0;
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1&&(yIndex+j<0||yIndex+j>=xROCK_SQUARE_NUM||visit[xIndex+i][yIndex+j]==1))//方块紧贴左右墙或旋转后的位置有方块(被标记)都不能旋转成功
return 0;
}
}
return 1;
}
//方块降落 包括键盘的使用
void move()
{
int x=Mid_x;
int y=Mid_y;
int k=0; //偏移量
int cur_speed=speed-(rank-1)*100;//根据等级决定下降速度
block_dir_t blockDir=BLOCK_UP;//表示新方块的形状
//判断游戏是否结束
if(moveAble(Mid_x,Mid_y,MOVE_DOWN,BLOCK_UP))
{
while(1)
{
if(kbhit())
{
int key=getch();
if(key==KEY_Space)
{ getch(); //空格暂停
clearblock(x,k,blockDir);//清除上一时刻的方块,后面几个同意
}
else if(key==KEY_Up)
{ clearblock(x,k,blockDir);
block_dir_t nextDir=(block_dir_t)((blockDir+1)%4);//方块旋转后的形状
if(rotatable(x,y+k,nextDir))//判断能否旋转成功
blockDir=nextDir;
}
else if(key==KEY_Down)
{ clearblock(x,k,blockDir);
cur_speed=50;//改下降速度
}
else if(key==KEY_Left)
{ clearblock(x,k,blockDir);
if(moveAble(x,y+k+ROCK_SQUARE_WIDTH,MOVE_LEFT,blockDir))//判断能否移动
x-=ROCK_SQUARE_WIDTH;
}
else if(key==KEY_Right)
{ clearblock(x,k,blockDir);
if(moveAble(x,y+k+ROCK_SQUARE_WIDTH,MOVE_RIGHT,blockDir))//判断能否移动
x+=ROCK_SQUARE_WIDTH;
}
else if(key==KEY_Zero)//重新开始
{
Inter_Face();//初始化界面
Inter_GameFace();//初始化游戏界面
GameProcess();
}
else
clearblock(x,k,blockDir);
}
else
clearblock(x,k,blockDir);
flushkey();//清理多余的键盘输入
k+=ROCK_SQUARE_WIDTH;//无论有没有按键方块都会向下移动
drawBlock(x,y+k,NowIndex,blockDir);//绘制目前的方块
Sleep(cur_speed);
cur_speed=speed-(rank-1)*100;//恢复原有速度,针对KEY_Down
if(!moveAble(x,y+k,MOVE_DOWN,blockDir)) //如果方块不能移动便将其固定
{
mark(x,y+k,NowIndex,blockDir);
break;
}
}
}
else
{
GameEnd();
}
}
//固定方块
void mark(int x,int y,int blockIndex,block_dir_t blockDir)
{
int id=blockIndex*4+blockDir;
int x1=(y-MinY)/ROCK_SQUARE_WIDTH;//被固定的方块距顶部边框间隔小方块的个数
int y1=(x-MinX)/ROCK_SQUARE_WIDTH;//被固定的方块距离左边边框间隔小方块的个数
for(int i=0;i<ROCK_CASE;i++)
{
for(int j=0;j<ROCK_CASE;j++)
{
if(block[id][i][j]==1)//将方块固定
{
visit[x1+i][y1+j]=1;//标记该处有方块,为后续的判断条件
markcolor[x1+i][y1+j]=color[blockIndex];//标记颜色
}
}
}
}
//清除函数
void down(int x)
{
//清除i行j列
for(int i=x;i>0;i--)
{
for(int j=0;j<xROCK_SQUARE_NUM;j++)
{
if(visit[i-1][j])//如果被删除的第i行上面有方块将其原样移到下面
{
visit[i][j]=1;
markcolor[i][j]=markcolor[i-1][j];
setcolor(markcolor[i][j]);
setfont(ROCK_SQUARE_WIDTH,0,"");
outtextxy(ROCK_SQUARE_WIDTH*j+MinX,ROCK_SQUARE_WIDTH*i+MinY,"▉");
}
else//如果第i行上面没有方块只需将第i行删除即可
{
visit[i][j]=0;
setcolor(BLACK);
setfont(ROCK_SQUARE_WIDTH,0,"");
outtextxy(ROCK_SQUARE_WIDTH*j+MinX,ROCK_SQUARE_WIDTH*i+MinY,"▉");
}
}
}
if(x==0)
{
setcolor(BLACK);
setfont(ROCK_SQUARE_WIDTH,0,"");
//清除0行即最顶层行
for(int j=0;xROCK_SQUARE_NUM;j++)
{
visit[0][j]=0;
outtextxy(ROCK_SQUARE_WIDTH*j+MinX,MinY,"▉");
}
}
}
//更新分数
void addscore(int line)
{
char str[10];
setcolor(LIGHTGRAY);
score+=line*10;
setfont(20,0,"宋体");
sprintf(str,"%d",score);/*sprintf(char *a,const char *format,[argument]...);buffer是char类型的指针,指向写入的字符串指针;format为格式化字符串,即在程序中想要的格式;argument可选参数,可以为任意类型的数据*/
outtextxy(370,185,str);
}
//消行更新
void check()
{
int clearlines=0;//消除的行数
for(int i=yROCK_SQUARE_NUM-1;i>=0;i--)//从最底部的一行开始检查
{
int flag=1;
for(int j=0;j<xROCK_SQUARE_NUM;j++) //不仅要j=xROCK_SQUARE_NUM而且要保证该行都有方块
{
if(visit[i][j]==0)//判断该行是否都有方块
{
flag=0;
break;
}
}
if(flag==1)
{
down(i);//消除第i行
i++;//方块落下来有可能又是满行,再检查一遍
clearlines++;//消除行数
}
}
addscore(clearlines);//加分
}
void GameProcess()
{
nextblock();//在右上角产生第一个方块
Sleep(500);//缓冲0.5秒
memset(visit,0,sizeof(visit));//memset(char *b,int a,count);b为指针或数组,a为将初始化为的数值,count要初始化的的长度
while(1)//游戏过程
{
newblock();//方块的移动与堆积过程
check();//消行得分
}
}
void GameEnd()
{
setcolor(RED);
setfont(40,0,"宋体");
outtextxy(60,150,"GAME OVER!");
delay_ms(2000);
initgraph(WINDOW_WIDTH,WINDOW_HIGH,0);//更新窗口,用cleardevice()有时清理不了
char str[10];
setcolor(WHITE);
setfont(20,0,"宋体");
outtextxy(100,170,"你的分数是:");
sprintf(str,"%d",score);
outtextxy(220,170,str);
outtextxy(100,200,"重新开始请按'0''");
outtextxy(100,230,"退出游戏请按'Esc'");
while(1)
{
if(kbhit())
{
int key=getch();
if(key==KEY_Zero)//重新开始
{
Inter_Face();//初始化界面
Inter_GameFace();//初始化游戏界面
GameProcess();
}
else if(key==KEY_Leave)
{
closegraph();
exit(0); //正常退出游戏
}
else
outtextxy(100,290,"请重新输入");
}
}
}
3、主函数
int main(){
Inter_Face();//初始化界面
Inter_GameFace();//初始化游戏界面
GameProcess();//游戏过程
closegraph();
return 0;
}