Tetris(俄罗斯方块)

一天有个淘宝网店客服问我OpenGL俄罗斯方块, 没错就是XXX作业代写。
俄罗斯方块分成两部分游戏逻辑和画面渲染.

1. 游戏逻辑

一个简单的俄罗斯方块的逻辑部分需要考虑的情况如下:

1. 方块的表示(坐标, 旋转, 上下左右移动) 
2. 格子的状态记录, 移动中的方块和边界的碰撞检测和已固定的方块的碰撞检测
3. 行满检测与消除

具体的面向对象实现如下:

class Game{
public:
    int tiles[20][10] = {0};
    void new_tile() {
        tile << 5, 0, random()%7, random()%4;
    }

    void write_tile() {
        if(!running || !started) return ;
        for(int i = 0 ; i < 4; i++) {
            tiles[tile[1]+tileMap[tile[2]][tile[3]][i][1]][tile[0]+tileMap[tile[2]][tile[3]][i][0]] = tile[2] + 1;
        }
    }

    void wipe_tile() {
        if(!running || !started) return ;
        for(int i = 0 ; i < 4; i++) {
            tiles[tile[1]+tileMap[tile[2]][tile[3]][i][1]][tile[0]+tileMap[tile[2]][tile[3]][i][0]]  = 0;
        }
    }

    void rotation() {
        if(!running || !started) return ;
        tile[3] = (tile[3]+1)%4;
        for(int i = 0 ; i < 4; i++) {
            std::pair<int,int> pos= {
                    tile[0]+tileMap[tile[2]][tile[3]][i][0],
                    tile[1]+tileMap[tile[2]][tile[3]][i][1],
            };
            if(pos.first < 0 || pos.first >= 10 || pos.second >= 20 || tiles[pos.second][pos.first]) {
                tile[3] = (tile[3]+3)%4;
                break;
            }
        }
    }

    bool move(int x, int y, bool passive = false) {
        if(!running || !started) return true;
        tile[0] += x;
        tile[1] += y;
        for(int i = 0 ; i < 4; i++) {
            std::pair<int,int> pos= {
                    tile[0]+tileMap[tile[2]][tile[3]][i][0],
                    tile[1]+tileMap[tile[2]][tile[3]][i][1],
            };
            if(pos.first < 0 || pos.first >= 10 || pos.second >= 20 || tiles[pos.second][pos.first]) {
                tile[0] -= x; tile[1] -= y;
                if(passive) {
                    write_tile();
                    if(tile[1] != 0) new_tile();
                    else {started = false; write_end(8);}
                }
                return false;
            }
        }
        check_row();
        return true;
    }

    void update() {
        if(!running || !started) return ;
        if(frame_cnt++ == 24) {
            wipe_tile();
            move(0,1,true);
            write_tile();
            frame_cnt = 0;
        }
    }

    void restart() {
        memset(tiles, 0, sizeof(int)*200);
        new_tile();
        started = true;
        running = true;
    }

    void resume_or_pause() {
        running = !running;
    }


    void write_end(int i = 8) {
        memset(tiles[std::max(i-1,0)], 0, 4*10*7);
        tiles[i+1][0] = tiles[i+3][0] = 1;
        tiles[i+0][0] = tiles[i+0][1] = tiles[i+0][2] = 1;
        tiles[i+2][0] = tiles[i+2][1] = tiles[i+2][2] = 1;
        tiles[i+4][0] = tiles[i+4][1] = tiles[i+4][2] = 1;
        tiles[i+0][3] = tiles[i+1][3] = tiles[i+2][3] = tiles[i+3][3] = tiles[i+4][3] = 2;
        tiles[i+0][6] = tiles[i+1][6] = tiles[i+2][6] = tiles[i+3][6] = tiles[i+4][6] = 2;
        tiles[i+1][4] = tiles[i+2][4] = tiles[i+2][5] = tiles[i+3][5] = 2;
        tiles[i+0][7] = tiles[i+1][7] = tiles[i+2][7] = tiles[i+3][7] = tiles[i+4][7] = 3;
        tiles[i+0][8] = tiles[i+4][8] = tiles[i+1][9] = tiles[i+2][9] = tiles[i+3][9] = 3;
    }

    Eigen::Vector4i tile; // (x, y, type, rotation)

private:
    // 一个二维数组表示所有可能出现的方块和方向。
    static int tileMap[7][4][4][2];

    void check_row() {
        for(int i = 19, ii = 19; i >= 0; i--) {
            int tile_cnt = 0;
            for(int j = 0; j < 10; j++) tile_cnt += tiles[i][j] > 0;
            for(int j = 0; j < 10; j++) tiles[ii][j] = tiles[i][j] ;
            if(tile_cnt != 10) ii--;
        }
    }

    int frame_cnt = 0;
    bool running = false, started = false;
} game;


int Game::tileMap[7][4][4][2] = {
{   0, 0, -1, 0,  1,  0,  -1, -1,    // "L"
    0, 1,  0,  0,  0,  -1, 1, -1,
    1, 1, -1, 0,  0,  0,  1,  0,
    -1, 1,  0,  1,  0,  0,  0,  -1},
{   0, 0, -1, -1, -1, 0,  0,  -1,    //"O"
    0, 0,  -1, -1, -1, 0,  0, -1,
    0, 0, -1, -1, -1, 0,  0,  -1,
    0,  0,  -1, -1, -1, 0,  0,  -1},
{   0, 0, 1,  0,  -1, 0,  -2, 0,    //"I"
    0, 0,  0,  -2, 0,  -1, 0, 1,
    0, 0, 1,  0,  -1, 0,  -2, 0,
    0,  0,  0,  -2, 0,  -1, 0,  1},
{   0, 0, 1,  0,  -1, -1, 0,  -1,    //"S"
    0, 0,  0,  1,  1,  0,  1, -1,
    0, 0, 1,  0,  -1, -1, 0,  -1,
    0,  0,  0,  1,  1,  0,  1,  -1},
{   0, 0, -1, 0,  1,  0,  1,  -1,    //"J"
    0, 0,  0,  1,  0,  -1, 1, 1,
    0, 0, 1,  0,  -1, 0,  -1, 1,
    0,  0,  0,  1,  0,  -1, -1, -1},
{   0, 0, -1, 0,  0,  -1, 1,  -1,    //"Z"
    0, -1, 0,  0,  1,  0,  1, 1,
    0, 0, -1, 0,  0,  -1, 1,  -1,
    0,  -1, 0,  0,  1,  0,  1,  1},
{   0, 0, 1,  0,  -1, 0,  0,  -1,    //"T"
    0, 0,  0,  1,  0,  -1, 1, 0,
    0, 0, 0,  1,  -1, 0,  1,  0,
    -1, 0,  0,  1,  0,  -1, 0,  0}
};

2. 渲染实现

对于渲染的要求,只使用一组着色器实现,即通过Uniform传所有格子的状态,具体如下:

// vertex shader
#version 330 core
in vec2 position;
out vec2 pos;
void main()
{
    pos = vec2(position.x*1.14,position.y*-1.09);
    gl_Position = vec4(position, 0.0, 1.0);
} ;
 
// fragment shader
#version 330 core
out vec4 outColor;
in vec2 pos;
uniform int tile[200];
uniform sampler2D tileTex;
void main()
{
    vec2 border = smoothstep(-0.1, 0.0, -abs(sin(3.1415926*pos*vec2(5.0,10.0))));
    if(max(abs(pos.x),abs(pos.y))>1.002) {outColor.xyz = texture(tileTex,vec2(pos.x/2.28 + 0.5, 360.0/393.0*(pos.y/2.18 + 0.5 )) ).xyz;return;}int tile_id = int(floor(10.0*pos.y+10.0)/*xiconxi.github.io*/*10)+int(floor(5.0*pos.x+5.0));
    tile_id     = tile[tile_id<200&&tile_id>=0?tile_id:0];
    vec3 tile_color = texture(tileTex,vec2( (tile_id-1+mod(abs(pos.x*5),1.0))/7.0,360.0/393.0+33.0/393.0*mod(abs(pos.y*10.0), 1.0))).xyz;
    tile_color = tile_id == 0 ? vec3(0.55)*length(tile_color): tile_color;
    outColor.xyz = mix(tile_color ,vec3(0.0),max(border.x,border.y));
} ;  

总体渲染效果如下:

具体代码在Github::Tetris