2013年12月13日金曜日

[C++][クソコード] main関数に全部書くってこうですか?

こちらは C++ (fork) Advent Calendar 2013 13日目の記事になります。

さて、皆さんそろそろ本当に息抜きが欲しくなった頃ではありませんか?
というわけで、今回はネタエントリーです。

テトリスを main(WinMain)関数の中に全部書いてみましたー

#include "stdafx.h"
#include "lambda_tetris.h"

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE /*hPrevInstance*/,
                     _In_ LPTSTR    /*lpCmdLine*/,
                     _In_ int       nCmdShow)
{
    enum kConfig {
          MAX_AREA_W = 10
        , MAX_AREA_H = 25
        , PADDING_X  = 10
        , PADDING_Y  = 20
        , BLOCK_SIZE = 20
        , NEXT_AREA_SIZE=BLOCK_SIZE*6
        , WINDOW_W = 400
        , WINDOW_H = 600
    };

    struct Position {
        LONG x, y;
        Position& operator += (const Position& rhs) {
            x += rhs.x;
            y += rhs.y;
            return *this;
        }
        Position operator + (const Position& rhs) const {
            Position pos = *this;
            pos += rhs;
            return pos;
        }
    };
    struct Matrix {
        POINT x;
        POINT y;
    };
    struct Block {
        Position pos;
        COLORREF color;
    };
    class Mino {
    public:
        ::std::vector<Block> blocks;
        Position pos;
    public:
        Mino(::std::initializer_list<Block> list) : blocks(list), pos(Position{ MAX_AREA_W / 2, -4 }) {};

        int left() const {
            int x= ::std::numeric_limits<int>::max();
            ::std::for_each(blocks.begin(), blocks.end(), [&](const Block& blk){ if( x > blk.pos.x ) x = blk.pos.x; });
            return x + pos.x;
        }
        int right() const {
            int x= ::std::numeric_limits<int>::min();
            ::std::for_each(blocks.begin(), blocks.end(), [&](const Block& blk){ if( x < blk.pos.x ) x = blk.pos.x; });
            return x + pos.x;
        }
        int bottom() const {
            int y= ::std::numeric_limits<int>::min();
            ::std::for_each(blocks.begin(), blocks.end(), [&](const Block& blk){ if( y < blk.pos.y ) y = blk.pos.y; });
            return y + pos.y;
        }
        void rotate(Matrix mtx) {
            for( auto& blk : blocks ) {
                blk.pos = { mtx.x.x * blk.pos.x + mtx.x.y * blk.pos.y, mtx.y.x * blk.pos.x + mtx.y.y * blk.pos.y };
            }
        }
    };
    struct FieldBlock : public Block {
        FieldBlock(const ::std::shared_ptr<Mino> mino, const Block& blk) {
            pos = blk.pos + mino->pos;
            color = RGB(GetRValue(blk.color) / 2, GetGValue(blk.color) / 2, GetBValue(blk.color) / 2);
        }
    };
    struct Field {
        ::std::vector<FieldBlock> line[MAX_AREA_H];
    public:
        bool find(Position pos) {
            if( pos.y < 0 || pos.y >= MAX_AREA_H ) return false;
            const auto it = ::std::find_if(line[pos.y].begin(), line[pos.y].end()
                , [=](const FieldBlock& b){ return b.pos.x <= pos.x && b.pos.x >= pos.x; });
            return (it != ::std::end(line[pos.y]));
        }
        bool find(::std::shared_ptr<Mino> mino) {
            for( const auto& blk : mino->blocks ) {
                if( find(blk.pos+mino->pos) ) return true;
            }
            return false;
        }
        bool fix(::std::shared_ptr<Mino> mino) {
            bool over = false;
            for( const auto& blk : mino->blocks ) {
                if( mino->pos.y + blk.pos.y < 0 ) {
                    over = true;
                    continue;
                }
                assert(mino->pos.y + blk.pos.y < MAX_AREA_H);
                line[mino->pos.y + blk.pos.y].emplace_back(mino, blk);
            }
            return over;
        }
        int checkLine() {
            int count=0;
            for( int i=0; i < MAX_AREA_H; ++i ) {
                if( line[i].size() == MAX_AREA_W ) {
                    auto tmp(::std::move(line[i]));
                    for( int j=i; j > 0; --j ) {
                        line[j] = ::std::move(line[j-1]);
                        ::std::for_each(line[j].begin(), line[j].end(), [j](FieldBlock& blk){ blk.pos.y = j; });
                    }
                    line[0] = ::std::move(tmp);
                    line[0].clear();
                    ++count;
                }
            }
            return count;
        }
    };

    struct Game {
        ::std::shared_ptr<Mino> m_curr;
        ::std::shared_ptr<Mino> m_next;
        Field m_field;
        ::std::mt19937 m_rnd;
        float m_speed;
        float m_y;
        unsigned int m_counter;
        unsigned int m_score;
        struct KeyState {
            DWORD hold;
            DWORD trigger;
        } m_keyState;

        Game() : m_curr(nullptr), m_next(nullptr), m_speed(0.1f), m_y(0.0f), m_score(0)
        {
            ::std::random_device rd;
            m_rnd.seed(rd());

            m_curr = MakeMino();
            m_next = MakeMino();
        }

        enum {
              BUTTON_UP     = 0x00
            , BUTTON_DOWN   = 0x01
            , BUTTON_LEFT   = 0x02
            , BUTTON_RIGHT  = 0x04
            , BUTTON_SPACE  = 0x08
        };
        DWORD UpdateKeyState()
        {
            DWORD key = 0;
            if( GetKeyState(VK_UP) < 0 ) key|=BUTTON_UP;
            if( GetKeyState(VK_DOWN) < 0 ) key|=BUTTON_DOWN;
            if( GetKeyState(VK_LEFT) < 0 ) key|=BUTTON_LEFT;
            if( GetKeyState(VK_RIGHT) < 0 ) key|=BUTTON_RIGHT;
            if( GetKeyState(VK_SPACE) < 0 ) key|=BUTTON_SPACE;

            m_keyState.trigger = (m_keyState.hold ^ key) & key;
            m_keyState.hold = key;
            return key;
        }
        bool IsFloor(::std::shared_ptr<Mino> mino)
        {
            const int bottom = mino->bottom();
            if( bottom >= MAX_AREA_H-1 ) return true;

            for( const auto& blk : mino->blocks ) {
                const auto y = mino->pos.y + blk.pos.y + 1;
                const auto x = mino->pos.x + blk.pos.x;
                if( y < 0 || y >= MAX_AREA_H ) continue;
                const auto& line = m_field.line[y];
                const auto it = ::std::find_if(line.begin(), line.end(), [=](const FieldBlock& b){ return b.pos.x == x; });
                if( it != ::std::end(line) ) return true;
            }
            return false;
        }
        void MoveX(::std::shared_ptr<Mino> mino, const Position& move)
        {
            if( move.x < 0 && ( mino->left() + move.x < 0 ) ) {
                return;
            }
            if( move.x > 0 && ( mino->right() + move.x >= MAX_AREA_W ) ) {
                return;
            }
            if( move.x ) {
                for( const auto& blk : mino->blocks ) {
                    if( m_field.find({ mino->pos.x + blk.pos.x + move.x, mino->pos.y + blk.pos.y }) ) {
                        return;
                    }
                }
            }
            mino->pos.x += move.x;
        }
        void Update()
        {
            UpdateKeyState();
            if( m_curr != nullptr )
            {
                ++m_counter;
                if( m_counter >= 60*60 ) {
                    m_counter = 0;
                    if( m_speed < 2.0f ) {
                        m_speed += 0.1f;
                    }
                }
                m_y += m_speed;
                Position move{ 0, 0 };
                if( m_y > 1.0f ) {
                    move.y = 1;
                    m_y -= 1.0f;
                }

                if( m_keyState.trigger & BUTTON_LEFT ) {
                    move.x -= 1;
                }
                if( m_keyState.trigger & BUTTON_RIGHT ) {
                    move.x += 1;
                }
                if( m_keyState.hold & BUTTON_DOWN ) {
                    move.y += 1;
                }
                if( m_keyState.trigger & BUTTON_SPACE ) {
                    m_curr->rotate({ 0, 1, -1, 0 });
                    if( m_curr->left() < 0 || m_curr->right() >= MAX_AREA_W || m_field.find(m_curr) ) {
                        m_curr->rotate({ 0, -1, 1, 0 });
                    }
                }

                MoveX(m_curr, move);
                for( int i=0; i < move.y; ++i ) {
                    if( IsFloor(m_curr) ) {
                        if( m_field.fix(m_curr) ) {
                            m_curr = nullptr;
                        } else {
                            m_curr = m_next;
                            m_next = MakeMino();
                        }
                        break;
                    }
                    m_curr->pos.y += 1;
                }
            }

            const auto n = m_field.checkLine();
            m_score += n*n * 10;
        }
        ::std::shared_ptr<Mino> MakeMino()
        {
            ::std::uniform_int_distribution<int> dist(0, 6);
            switch( dist(m_rnd) )
            {
            case 0:
                {
                    COLORREF c=RGB(0, 255, 255);
                    return ::std::make_shared<Mino>(Mino{ { 0, -1, c }, { 0, 0, c }, { 0, 1, c }, { 0, 2, c } });
                }
                break;
            case 1:
                {
                    COLORREF c=RGB(255, 255, 0);
                    return ::std::make_shared<Mino>(Mino{ { 0, 0, c }, { 0, 1, c }, { 1, 0, c }, { 1, 1, c } });
                }
                break;
            case 2:
                {
                    COLORREF c=RGB(0, 255, 0);
                    return ::std::make_shared<Mino>(Mino{ { -1, 0, c }, { 0, 0, c }, { 0, 1, c }, { 1, 1, c } });
                }
                break;
            case 3:
                {
                    COLORREF c=RGB(255, 0, 0);
                    return ::std::make_shared<Mino>(Mino{ { -1, 1, c }, { 0, 1, c }, { 0, 0, c }, { 1, 0, c } });
                }
                break;
            case 4:
                {
                    COLORREF c=RGB(255, 128, 0);
                    return ::std::make_shared<Mino>(Mino{ { 0, -1, c }, { 0, 0, c }, { 0, 1, c }, { 1, 1, c } });
                }
                break;
            case 5:
                {
                    COLORREF c=RGB(0, 0, 255);
                    return ::std::make_shared<Mino>(Mino{ { 1, -1, c }, { 1, 0, c }, { 1, 1, c }, { 0, 1, c } });
                }
                break;
            case 6:
                {
                    COLORREF c=RGB(255, 0, 255);
                    return ::std::make_shared<Mino>(Mino{ { -1, 1, c }, { 0, 0, c }, { 0, 1, c }, { 1, 1, c } });
                }
                break;
            default:
                break;
            }
            return nullptr;
        }
    };

    struct Global {
        Game game;
        static Global& GetInstance() { static Global g; return g; }
    };

    auto WndProc =[](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT {
        DLGPROC About =static_cast<DLGPROC>([](HWND hDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/) -> INT_PTR {
            switch( message )
            {
            case WM_INITDIALOG:
                return (INT_PTR)TRUE;
            case WM_COMMAND:
                if( LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL )
                {
                    EndDialog(hDlg, LOWORD(wParam));
                    return (INT_PTR)TRUE;
                }
                break;
            }
            return (INT_PTR)FALSE;
        });

        static HDC hMemDC = nullptr;
        static HBITMAP hBitmap = nullptr;

        switch( message )
        {
        case WM_CREATE:
            {
                HDC hDC     = GetDC(hWnd);
                hMemDC      = CreateCompatibleDC(hDC);
                hBitmap     = CreateCompatibleBitmap(hDC, WINDOW_W, WINDOW_H);
                SelectObject(hMemDC, hBitmap);
                for( auto i : { DEFAULT_GUI_FONT, DC_PEN, DC_BRUSH } ) {
                    SelectObject(hMemDC, GetStockObject(i));
                }
                ReleaseDC(hWnd, hDC);
            }
            break;
        case WM_COMMAND:
            {
                switch( LOWORD(wParam) )
                {
                case IDM_ABOUT:
                    DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
                }
            }
            break;
        case WM_PAINT:
            {
                Game game = Global::GetInstance().game;

                auto hFrame = CreateSolidBrush(RGB(0, 0, 0));
                auto DrawBlock = [=](HDC hdc, Position pos, COLORREF color) {
                    RECT rc ={ pos.x, pos.y, pos.x + BLOCK_SIZE, pos.y + BLOCK_SIZE };
                    auto hBrush = CreateSolidBrush(color);
                    FillRect(hdc, &rc, hBrush);
                    FrameRect(hdc, &rc, hFrame);
                    DeleteObject(hBrush);
                };
                auto DrawMino = [=](::std::shared_ptr<Mino> mino, Position base) {
                    if( mino == nullptr ) return;
                    for( const Block& blk : mino->blocks ) {
                        if( base.y + blk.pos.y < 0 ) continue;
                        DrawBlock(hMemDC, { base.x + blk.pos.x*BLOCK_SIZE, base.y + blk.pos.y*BLOCK_SIZE }
                        , blk.color);
                    }
                };
                auto DrawField =[=]() {
                    for( const auto& line : game.m_field.line ) {
                        for( const FieldBlock& blk : line ) {
                            DrawBlock(hMemDC, { blk.pos.x*BLOCK_SIZE + PADDING_X, blk.pos.y*BLOCK_SIZE + PADDING_Y }, blk.color);
                        }
                    }
                };
                auto DrawFrame = [=](RECT rc) {
                    FrameRect(hMemDC, &rc, hFrame);
                };

                {
                    RECT rc ={ 0, 0, WINDOW_W, WINDOW_H };
                    FillRect(hMemDC, &rc, NULL);
                }

                DrawFrame({ PADDING_X, PADDING_Y, PADDING_X + BLOCK_SIZE*MAX_AREA_W, PADDING_Y + BLOCK_SIZE*MAX_AREA_H });

                {
                    const LONG left = PADDING_X + BLOCK_SIZE*MAX_AREA_W + 40;
                    const LONG top = PADDING_Y;
                    DrawFrame({ left, top, left + NEXT_AREA_SIZE, top + NEXT_AREA_SIZE });
                    Position base{ left + NEXT_AREA_SIZE/2 - BLOCK_SIZE/4, top + NEXT_AREA_SIZE/2 - BLOCK_SIZE };
                    DrawMino(game.m_next, base);

                    SetTextColor(hMemDC, 0);
                    TCHAR buf[64];
                    wsprintf(buf, _T("%08d"), game.m_score);
                    TextOut(hMemDC, left, top + NEXT_AREA_SIZE + BLOCK_SIZE, buf, _tcslen(buf));
                }

                if( game.m_curr != nullptr ) {
                    Position base{ game.m_curr->pos.x*BLOCK_SIZE + PADDING_X, game.m_curr->pos.y*BLOCK_SIZE + PADDING_Y };
                    DrawMino(game.m_curr, base);
                }
                DrawField();

                if( game.m_curr == nullptr ) {
                    SetTextColor(hMemDC, RGB(255, 0, 0));
                    TextOut(hMemDC, 80, WINDOW_H/2, _T("GAME OVER"), 9);
                }

                DeleteObject(hFrame);

                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps);

                BitBlt(hdc, 0, 0, WINDOW_W, WINDOW_H, hMemDC, 0, 0, SRCCOPY);
                EndPaint(hWnd, &ps);
            }
            break;
        case WM_ERASEBKGND:
            return TRUE;
        case WM_DESTROY:
            DeleteObject(hBitmap);
            DeleteDC(hMemDC);
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    };

    // アプリケーションの初期化を実行します:
    const int MAX_LOADSTRING = 100;
    TCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
    TCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_TETRIS, szWindowClass, MAX_LOADSTRING);

    [&](){
        WNDCLASSEX wcex;
        wcex.cbSize = sizeof(WNDCLASSEX);

        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = 0;
        wcex.hInstance      = hInstance;
        wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TETRIS));
        wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_TETRIS);
        wcex.lpszClassName  = szWindowClass;
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

        return RegisterClassEx(&wcex);
    }();

    HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, WINDOW_W, WINDOW_H, NULL, NULL, hInstance, NULL);

    if( !hWnd ) {
        return FALSE;
    }

    auto TimerProc =[](HWND hWnd, UINT nMsg, UINT_PTR nIDEvent, DWORD dwTime) {
        Global::GetInstance().game.Update();
        InvalidateRect(hWnd, nullptr, TRUE);
    };
    SetTimer(hWnd, NULL, 1000 / 60, TimerProc);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TETRIS));

    // メイン メッセージ ループ:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

実行画面


ソースコード一式はこちらから取得できます。
https://github.com/srz-zumix/lambda_tetris

特に解説したりとかは無いです。
それでは!

0 件のコメント:

コメントを投稿