ゲームプログラミング：C++でテトリスを作ってみた

#概要

「エンジニアやっているのに、そこそこの開発規模（個人レベル）でゲーム作ったことなかったな」
そう思って、ゲームを作ろうと思いました。
「いきなりGUIとか考慮すると、完成までにモチベーションを使い果たしてしまうのではないか？」
というわけで、コンソール上で動作するゲームを作成することにしました。

以下を参考にしながらテトリスを作成しました。
館長、ありがとう！！！
　https://youtu.be/iosmmQvhyzM

とりあえずうまく動作していそうです。

![スクリーンショット (50).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/7a8f9bb9-6150-7d5c-4d0d-e3b3f31ac03e.png)

#コード

せっかくなので、コードを公開します。
ご自由にお試し下さい。
ホントはコメントとかネーミングとかもうちょっと考えて、初学者の方が分かるようにしたいと思っていますが・・・

C++的に記述できていないところも多い（vector使っていないとか）ので、ちょこちょこ修正していこうと思います。
回帰テストせなアカンで！！！

※コードレビューコメント頂けますと幸いです

```source.cpp
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <conio.h>

static const int FIELD_WIDTH = 12;
static const int FIELD_HEIGHT = 22;

static const int MINO_WIDTH = 4;
static const int MINO_HEIGHT = 4;

enum
{
    MINO_TYPE_I,
    MINO_TYPE_O,
    MINO_TYPE_S,
    MINO_TYPE_Z,
    MINO_TYPE_J,
    MINO_TYPE_L,
    MINO_TYPE_T,
    MINO_TYPE_MAX
};

enum
{
    MINO_ANGLE_0,
    MINO_ANGLE_90,
    MINO_ANGLE_180,
    MINO_ANGLE_270,
    MINO_ANGLE_MAX
};

char field[FIELD_HEIGHT][FIELD_WIDTH] = { 0 };

char displayBuffer[FIELD_HEIGHT][FIELD_WIDTH] = { 0 };

char minoShapes[MINO_TYPE_MAX][MINO_ANGLE_MAX][MINO_WIDTH][MINO_HEIGHT] =
{
    // MINO_TYPE_I
    {
        // MINO_ANGLE_0
        {
            0, 1, 0, 0,
            0, 1, 0, 0,
            0, 1, 0, 0,
            0, 1, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            0, 0, 0, 0,
            1, 1, 1, 1,
            0, 0, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 1, 0,
            0, 0, 1, 0,
            0, 0, 1, 0,
            0, 0, 1, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 0, 0,
            1, 1, 1, 1,
            0, 0, 0, 0,
            0, 0, 0, 0
        }
    },

    // MINO_TYPE_O
    {
        // MINO_ANGLE_0
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        }
    },

    // MINO_TYPE_S
    {
        // MINO_ANGLE_0
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            1, 1, 0, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 1, 0, 0,
            0, 1, 1, 0,
            0, 0, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            1, 1, 0, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 0, 0,
            0, 1, 0, 0,
            0, 1, 1, 0,
            0, 0, 1, 0
        }
    },

    // MINO_TYPE_Z
    {
        // MINO_ANGLE_0
        {
            0, 0, 0, 0,
            1, 1, 0, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            0, 0, 1, 0,
            0, 1, 1, 0,
            0, 1, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 0, 1, 1,
            0, 0, 0, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 1, 0,
            0, 1, 1, 0,
            0, 1, 0, 0,
            0, 0, 0, 0
        }
    },

    // MINO_TYPE_J
    {
        // MINO_ANGLE_0
        {
            0, 0, 1, 0,
            0, 0, 1, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            1, 1, 1, 0,
            0, 0, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 1, 0, 0,
            0, 1, 0, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 0, 0,
            0, 1, 0, 0,
            0, 1, 1, 1,
            0, 0, 0, 0
        }
    },

    // MINO_TYPE_L
    {
        // MINO_ANGLE_0
        {
            0, 1, 0, 0,
            0, 1, 0, 0,
            0, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            0, 0, 1, 0,
            1, 1, 1, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 1, 1, 0,
            0, 0, 1, 0,
            0, 0, 1, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 0, 0,
            0, 1, 1, 1,
            0, 1, 0, 0,
            0, 0, 0, 0
        }
    },

    // MINO_TYPE_T
    {
        // MINO_ANGLE_0
        {
            0, 0, 0, 0,
            1, 1, 1, 0,
            0, 1, 0, 0,
            0, 0, 0, 0
        },
        // MINO_ANGLE_90
        {
            0, 0, 0, 0,
            0, 1, 0, 0,
            0, 1, 1, 0,
            0, 1, 0, 0
        },
        // MINO_ANGLE_180
        {
            0, 0, 0, 0,
            0, 0, 1, 0,
            0, 1, 1, 1,
            0, 0, 0, 0
        },
        // MINO_ANGLE_270
        {
            0, 0, 1, 0,
            0, 1, 1, 0,
            0, 0, 1, 0,
            0, 0, 0, 0
        }
    }
};

void display();
bool isHit(int argMinoX, int argMinoY, int argMinoType, int argMinoAngle);
void resetMino();

int minoType = 0;
int minoAngle = 0;
int minoX = 0;
int minoY = 0;

int main()
{
    // create wall and bottom
    for (int i = 0; i < FIELD_HEIGHT; ++i)
    {
        field[i][0] = 1;
        field[i][FIELD_WIDTH - 1] = 1;
    }

    for (int i = 0; i < FIELD_WIDTH; ++i)
    {
        field[FIELD_HEIGHT - 1][i] = 1;
    }

    resetMino();
    time_t t = time(NULL);
    while (1)
    {
        if (_kbhit())
        {
            switch (_getch())
            {
                //case 'w':
            case 's':
                if (!isHit(minoX, minoY + 1, minoType, minoAngle))
                {
                    ++minoY;
                }
                break;
            case 'a':
                if (!isHit(minoX - 1, minoY, minoType, minoAngle))
                {
                    --minoX;
                }
                break;
            case 'd':
                if (!isHit(minoX + 1, minoY, minoType, minoAngle))
                {
                    ++minoX;
                }
                break;
            case 0x20:
                if (!isHit(minoX, minoY, minoType, (minoAngle + 1) % MINO_ANGLE_MAX))
                {
                    minoAngle = (minoAngle + 1) % MINO_ANGLE_MAX;
                }
                break;
            }
            display();
        }

        if (time(NULL) != t)
        {
            t = time(NULL);

            if (isHit(minoX, minoY + 1, minoType, minoAngle))
            {
                for (int i = 0; i < MINO_HEIGHT; ++i)
                {
                    for (int j = 0; j < MINO_WIDTH; ++j)
                    {
                        field[minoY + i][minoX + j] |= minoShapes[minoType][minoAngle][i][j];
                    }
                }

                // erase block
                for (int i = 0; i < FIELD_HEIGHT - 1; ++i)
                {
                    bool isLineFilled = true;
                    for (int j = 1; j < FIELD_WIDTH - 1; ++j)
                    {
                        if ( 1 != field[i][j] )
                        {
                            isLineFilled = false;
                        }
                    }

                    if ( true == isLineFilled )
                    {
                        for (int j = i; j > 0; --j)
                        {
                            memcpy(field[j], field[j - 1], FIELD_WIDTH);
                        }
                    }
                }

                resetMino();
            }
            else
            {
                ++minoY;
            }

            display();
        }
    }

    _getch();
    return 0;
}

void display()
{
    // write displayBuffer
    memcpy(displayBuffer, field, sizeof(field));

    for (int i = 0; i < MINO_HEIGHT; ++i)
    {
        for (int j = 0; j < MINO_WIDTH; ++j)
        {
            displayBuffer[minoY + i][minoX + j] |= minoShapes[minoType][minoAngle][i][j];
        }
    }

    // display block on console
    system("cls");

    for (int i = 0; i < FIELD_HEIGHT; ++i)
    {
        for (int j = 0; j < FIELD_WIDTH; ++j)
        {
            if (1 == displayBuffer[i][j])
            {
                std::cout << "■";
            }
            else
            {
                std::cout << "　";
            }
        }
        std::cout << std::endl;
    }
}

bool isHit(int argMinoX, int argMinoY, int argMinoType, int argMinoAngle)
{
    for (int i = 0; i < MINO_HEIGHT; ++i)
    {
        for (int j = 0; j < MINO_WIDTH; ++j)
        {
            if ( minoShapes[argMinoType][argMinoAngle][i][j] && field[argMinoY + i][argMinoX + j] )
            {
                return true;
            }
        }
    }
    return false;
}

void resetMino()
{
    minoX = 5;
    minoY = 0;
    minoType = rand() % MINO_TYPE_MAX;
    minoAngle = rand() % MINO_ANGLE_MAX;
}
```