欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言小游戏详解—俄罗斯方块

程序员文章站 2024-03-18 20:08:10
...

版权声明:本文为博主原创文章,转载请注明CSDN博客源地址!
https://blog.csdn.net/Mou_Yang/article/details/82424643

一、游戏组成

俄罗斯方块总共有19种方块,每一种方块都是由4个小格子组成。但其实这19种方块是由7种基本方块通过变形得到的。
下面是这7种基本方块:
C语言小游戏详解—俄罗斯方块 第0种基本方块:有1种形态
C语言小游戏详解—俄罗斯方块 第1种基本方块:有2种形态
C语言小游戏详解—俄罗斯方块 第2种基本方块:有2种形态
C语言小游戏详解—俄罗斯方块第3种基本方块:有4种形态
C语言小游戏详解—俄罗斯方块第4种基本方块:有4种形态
C语言小游戏详解—俄罗斯方块第5种基本方块:有2种形态
C语言小游戏详解—俄罗斯方块第6种基本方块:有4种形态

二、游戏规则

1、在一个行高为20,列高为14的矩形中,随机方块从上方以一定速率下落;
2、其下落过程中可通过按键使它左右移动、变形;
3、方块下落至矩阵底部或者下部与其他方块接触,则固定该方块的位置,并生成新的方块重复步骤2;
4、如果矩阵中的某一行都是方块,则消去该行的方块,并使上面的方块下沉一行;
5、如果方块的堆积高度超过20,则游戏结束。

三、总体设计

C语言小游戏详解—俄罗斯方块
我的想法是创建一个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");
    }
}

七、效果界面

超简易版的俄罗斯方块,没有积分,没有开始菜单。(但其实这些自己去加就可以了,开个变量记一下)
C语言小游戏详解—俄罗斯方块C语言小游戏详解—俄罗斯方块

八、总结

作为一个已经学了两年编程的菜鸟来说,写这个东西还花了一天,确实有点丢人。不过因为刚开的博客想写点东西上来,就水了个俄罗斯方块。整个过程我已经很详细地介绍了,但如果还有什么疏漏或者错误的地方,欢迎在下方评论指出,一起探讨。