#include "stdafx.h"
#include "lambda_tetris.h"
int
APIENTRY _tWinMain(_In_
HINSTANCE
hInstance,
_In_opt_
HINSTANCE
,
_In_
LPTSTR
,
_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
) ->
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;
}