C语言小游戏详解—俄罗斯方块
版权声明:本文为博主原创文章,转载请注明CSDN博客源地址!
https://blog.csdn.net/Mou_Yang/article/details/82424643
一、游戏组成
俄罗斯方块总共有19种方块,每一种方块都是由4个小格子组成。但其实这19种方块是由7种基本方块通过变形得到的。
下面是这7种基本方块:
第0种基本方块:有1种形态
第1种基本方块:有2种形态
第2种基本方块:有2种形态
第3种基本方块:有4种形态
第4种基本方块:有4种形态
第5种基本方块:有2种形态
第6种基本方块:有4种形态
二、游戏规则
1、在一个行高为20,列高为14的矩形中,随机方块从上方以一定速率下落;
2、其下落过程中可通过按键使它左右移动、变形;
3、方块下落至矩阵底部或者下部与其他方块接触,则固定该方块的位置,并生成新的方块重复步骤2;
4、如果矩阵中的某一行都是方块,则消去该行的方块,并使上面的方块下沉一行;
5、如果方块的堆积高度超过20,则游戏结束。
三、总体设计
我的想法是创建一个25*14的整型数组array,数组元素用0表示空位,1表示堆积的方块,2表示在运动的方块,用另外的数组graph[4][2]记录在运行的方块各个点的坐标。红色位置用作方块运动到底后生成新方块的位置,橙色区域用作玩家可视区域,在该区域方块运动并接收玩家指令变形。
四、定义变量
设置矩阵的行高:
#define H 25
设置列高14:
#define L 14
设置7种方块在红色区域生成时的各点坐标位置:
int block[7][4][2]={{{2,6},{2,7},{3,6},{3,7}},//第0种基本方块
{{1,7},{2,7},{2,6},{3,6}},//第1种基本方块
{{1,6},{2,6},{2,7},{3,7}},//第2种基本方块
{{2,6},{3,6},{3,7},{3,8}},//第3种基本方块
{{2,8},{3,8},{3,7},{3,6}},//第4种基本方块
{{3,5},{3,8},{3,7},{3,6}},//第5种基本方块
{{2,7},{3,6},{3,7},{3,8}}};//第6种基本方块
假设每种方块都有4种变形(如果它只有一种变形,则它的另外三种变形,跟原来一样),每次变形后各点坐标都会变化,创建数组记录坐标变化的差值:
int to_change[7][4][4][2]={{{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},
//第0种基本方块,每次变化后的坐标都不变,故x,y坐标变化的差值都为0
{{{2,1},{1,0},{0,1},{-1,0}},{{-2,-1},{-1,0},{0,-1},{1,0}},{{2,1},{1,0},{0,1},{-1,0}},{{-2,-1},{-1,0},{0,-1},{1,0}}},
//第1种基本方块,它有2种变形,故第一次变化的坐标差值跟第三次一样,第二次变化跟第四次一样
{{{1,2},{0,1},{1,0},{0,-1}},{{-1,-2},{0,-1},{-1,0},{0,1}},{{1,2},{0,1},{1,0},{0,-1}},{{-1,-2},{0,-1},{-1,0},{0,1}}},
//...
{{{-1,2},{-2,1},{-1,0},{0,-1}},{{2,0},{1,1},{0,0},{-1,-1}},{{0,-1},{1,0},{0,1},{-1,2}},{{-1,-1},{0,-2},{1,-1},{2,0}}},
//...
{{{1,0},{0,-1},{-1,0},{-2,1}},{{0,-2},{-1,-1},{0,0},{1,1}},{{-2,1},{-1,2},{0,1},{1,0}},{{1,1},{2,0},{1,-1},{0,-2}}},
//...
{{{-3,2},{-2,-1},{-1,0},{0,1}},{{3,-2},{2,1},{1,0},{0,-1}},{{-3,2},{-2,-1},{-1,0},{0,1}},{{3,-2},{2,1},{1,0},{0,-1}}},
//...
{{{0,1},{-2,1},{-1,0},{0,-1}},{{1,-1},{1,1},{0,0},{-1,-1}},{{-1,0},{1,0},{0,1},{-1,2}},{{0,0},{0,-2},{1,-1},{2,0}}}};
//...
设置生成的方块的序号:
int picture;//picture=rand()%7;
记录当前运动方块的变形:
int now; //0<=now<=3
记录运动方块4个点的x和y坐标
int graph[4][2];//Xi=graph[i][0] Yi=graph[i][1]
设置记录键盘输入的字符变量
char control;//'w'表示变形 'a'表示左移 'd'表示右移
设置计时:
int start;//每500毫秒方块下降一格
五、主要函数
方块变形:调用数组to_change对方块坐标graph[][]进行修改,实现方块变形。注意:判断方块变形后是否会超出屏幕左右边界,如果超出,就要挪一下方块的位置。
void Change();
左右移动:将玩家输入的字符赋值给control,然后作参数传给M,如果为A/a,则左移,为D/d,则右移。注意:当方块触碰左右边界,或者左右有其他已经固定的方块,是无法在移动的。
void Move(char M);
方块到底部:判断方块是否已经到达底部,或者下部跟其他方块接触,是的话返回1,不是返回0。
int See();
生成方块:重置数组graph[][]的坐标,生成新的方块。
void New();
方块前进:实现方块向下走一格
void Go();
游戏开始:开始游戏,初始化picture,nowgraph[][]等数据。
void Begin();
消去一行:遍历数组array,如果某一行全部为1(该行全是方块),则消去该行,上面的方块下沉一格,并在最上面生成空白的一行。同时判断死亡,如果遍历时发现方块堆积高度大于20,返回1代表游戏结束。
int Elimination();
可视界面:输出矩阵array可视的部分,即游戏界面。
void Cout();
以上是需要自己实现的函数,除此之外还要另外调用4个库函数:
1.实现非显示输入:让用户输入的字符不用在控制台显示出来然后回车赋值给变量,而是直接在输入时就赋值给变量。
#include <conio.h>
char getch();
2.判断是否有键盘信息:当检测到有键盘输入 时,该函数返回值>0,否则返回0。
#include <conio.h>
int kbhit();
3.记录时间:该函数返回设备的启动时间(单位:毫秒),可多次调用,并用后者减去前者,用以计算两者的时间间隔。
#include <ctime>
int GetTickCount();
4.卡住屏幕:在等待到用户的下一步指令之前,卡住程序的运行。
#include <windows.h>
system("cls");
六、完整代码
#include<iostream>
#include<conio.h>
#include<windows.h>
#include<stdlib.h>
#include<ctime>
#define H 25
#define L 14
//这个里要判断它下一步是否会重叠,如果会则表示已经到底
using namespace std;
int array[H][L]; //进行方块组合的地方
//28中变形
int to_change[7][4][4][2]={{{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},
{{{2,1},{1,0},{0,1},{-1,0}},{{-2,-1},{-1,0},{0,-1},{1,0}},{{2,1},{1,0},{0,1},{-1,0}},{{-2,-1},{-1,0},{0,-1},{1,0}}},
{{{1,2},{0,1},{1,0},{0,-1}},{{-1,-2},{0,-1},{-1,0},{0,1}},{{1,2},{0,1},{1,0},{0,-1}},{{-1,-2},{0,-1},{-1,0},{0,1}}},
{{{-1,2},{-2,1},{-1,0},{0,-1}},{{2,0},{1,1},{0,0},{-1,-1}},{{0,-1},{1,0},{0,1},{-1,2}},{{-1,-1},{0,-2},{1,-1},{2,0}}},
{{{1,0},{0,-1},{-1,0},{-2,1}},{{0,-2},{-1,-1},{0,0},{1,1}},{{-2,1},{-1,2},{0,1},{1,0}},{{1,1},{2,0},{1,-1},{0,-2}}},
{{{-3,2},{-2,-1},{-1,0},{0,1}},{{3,-2},{2,1},{1,0},{0,-1}},{{-3,2},{-2,-1},{-1,0},{0,1}},{{3,-2},{2,1},{1,0},{0,-1}}},
{{{0,1},{-2,1},{-1,0},{0,-1}},{{1,-1},{1,1},{0,0},{-1,-1}},{{-1,0},{1,0},{0,1},{-1,2}},{{0,0},{0,-2},{1,-1},{2,0}}}};
//7个图形
int block[7][4][2]={{{2,6},{2,7},{3,6},{3,7}},
{{1,7},{2,7},{2,6},{3,6}},
{{1,6},{2,6},{2,7},{3,7}},
{{2,6},{3,6},{3,7},{3,8}},
{{2,8},{3,8},{3,7},{3,6}},
{{3,5},{3,8},{3,7},{3,6}},
{{2,7},{3,6},{3,7},{3,8}}};
int picture;//图形的序号
int now; //当前图形的变化号
int graph[4][2];//图形的坐标
char control;
int start;
void Change();
void Move(char M); //根据输入的AD判断左右移动
int See(); //判断是否到底
void New(); //开新
void Go(); //向下走一格
void Begin(); //一切值初始化
int Elimination(); //把完整行的消去
void Cout(); //输出矩阵
int main()
{
Begin();
Cout();
while(1){
if(GetTickCount()-start>=500){
start=GetTickCount();
if(!See())Go();
else {
if(Elimination())break;
New();
}
system("cls");
Cout();
}
if(kbhit()){
control=getch();
if(control=='W'||control=='w')Change();
else Move(control);
system("cls");
Cout();
}
}
//虽然背景板的高度为25,但显示时只显示后20格
}
void Change()
{
for(int i=0;i<4;i++)
array[graph[i][0]][graph[i][1]]=0;
now=(now+1)%4;
for(int i=0;i<4;i++){
graph[i][0]+=to_change[picture][now][i][0];
graph[i][1]+=to_change[picture][now][i][1];
}
for(int i=0;i<4;i++){
if(graph[i][1]<0){
int temp=0-graph[i][1];
for(int j=0;j<4;j++)graph[j][1]+=temp;
}
if(graph[i][1]>L-1){
int temp=graph[i][1]-(L-1);
for(int j=0;j<4;j++)graph[j][1]-=temp;
}
}
for(int i=0;i<4;i++)
array[graph[i][0]][graph[i][1]]=2;
}
void Move(char M)
{
for(int i=0;i<4;i++)
array[graph[i][0]][graph[i][1]]=0;
if((M=='a'||M=='A')&&graph[0][1]&&graph[1][1]&&graph[2][1]&&graph[3][1])
if(array[graph[0][0]][graph[0][1]-1]!=1&&array[graph[1][0]][graph[1][1]-1]!=1&&array[graph[2][0]][graph[2][1]-1]!=1&&array[graph[3][0]][graph[3][1]-1]!=1){
graph[0][1]--;
graph[1][1]--;
graph[2][1]--;
graph[3][1]--;
}
if((M=='d'||M=='D')&&graph[0][1]<L-1&&graph[1][1]<L-1&&graph[2][1]<L-1&&graph[3][1]<L-1)
if(array[graph[0][0]][graph[0][1]+1]!=1&&array[graph[1][0]][graph[1][1]+1]!=1&&array[graph[2][0]][graph[2][1]+1]!=1&&array[graph[3][0]][graph[3][1]+1]!=1){
graph[0][1]++;
graph[1][1]++;
graph[2][1]++;
graph[3][1]++;
}
for(int i=0;i<4;i++)
array[graph[i][0]][graph[i][1]]=2;
}
int See()
{
for(int i=0;i<4;i++)
if(graph[i][0]+1==H)return 1;
for(int i=0;i<4;i++)
if(array[graph[i][0]+1][graph[i][1]]==1)return 1;
return 0;
}
void New()
{
picture=rand()%7;
now=-1;
for(int i=0;i<4;i++){
graph[i][0]=block[picture][i][0];
graph[i][1]=block[picture][i][1];
array[graph[i][0]][graph[i][1]]=2;
}
}
void Go()
{
for(int i=0;i<4;i++){
array[graph[i][0]][graph[i][1]]=0;
graph[i][0]++;
}
for(int i=0;i<4;i++)
array[graph[i][0]][graph[i][1]]=2;
}
void Begin()
{
srand(time(NULL));
start=GetTickCount();//开始的时间
for(int i=0;i<H;i++)
for(int j=0;j<L;j++)
array[i][j]=0;
picture=rand()%7;
now=-1;
for(int i=0;i<4;i++){
graph[i][0]=block[picture][i][0];
graph[i][1]=block[picture][i][1];
array[graph[i][0]][graph[i][1]]=2;
}
}
int Elimination()
{
for(int i=0;i<4;i++){
array[graph[i][0]][graph[i][1]]=1;
if(graph[i][0]<4)return 1;
}
for(int i=H-1;i>=0; ){
int j;
for(j=0;j<L;j++)
if(array[i][j]!=1)break;
if(j==L){
for(int k=i;k>0;k--)
for(int l=0;l<L;l++)
array[k][l]=array[k-1][l];
}else i--;
}
return 0;
}
void Cout()
{
for(int i=5;i<H;i++){
for(int j=0;j<L;j++){
if(array[i][j]==0)printf(".");
else if(array[i][j]==1){
printf("#");
}else printf("o");
}
printf("\n");
}
}
七、效果界面
超简易版的俄罗斯方块,没有积分,没有开始菜单。(但其实这些自己去加就可以了,开个变量记一下)
八、总结
作为一个已经学了两年编程的菜鸟来说,写这个东西还花了一天,确实有点丢人。不过因为刚开的博客想写点东西上来,就水了个俄罗斯方块。整个过程我已经很详细地介绍了,但如果还有什么疏漏或者错误的地方,欢迎在下方评论指出,一起探讨。