さて、皆さんそろそろ本当に息抜きが欲しくなった頃ではありませんか?
というわけで、今回はネタエントリーです。
テトリスを 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 件のコメント:
コメントを投稿