C & C++/Windows API 예제

테트리스 처음부터 끝까지 [C++, Windows API]

언휴 2023. 12. 25. 13:03

1. 유튜브 동영상 강의

2. 소스 코드

안녕하세요. 언제나휴일입니다.

동영상 강의를 제작하면서 만든 코드입니다.

불필요한 코드가 남아있지만 그대로 올립니다.

2.1 Program.cpp

#include <Windows.h>
#include "Document.h"

#define BOARD_SX	70
#define BOARD_SY	50
#define MY_WIDTH	15
#define MY_HEIGHT	15

#define MY_RX(x)	(BOARD_SX+(x)*MY_WIDTH)
#define MY_RY(y)	(BOARD_SY+(y)*MY_HEIGHT)
#define NEXT_SX	270
#define MY_NRX(x)	(NEXT_SX+(x)*MY_WIDTH)
#define MY_NRY(y)	(MY_RY(y))

#define DIAGRAM_WIDTH	4
#define DIAGRAM_HEIGHT	4
#define DW	DIAGRAM_WIDTH
#define DH DIAGRAM_HEIGHT
#define TID_DROP	1032

#define SCORE_X MY_NRX(0)
#define SCORE_Y MY_NRY(5)

HBRUSH hbrushes[MAX_DIAGRAM];

void OnCreate(HWND hWnd, CREATESTRUCT* pcs)
{
	Document* doc = Document::GetSingleton();
	doc->MakeDiagram();
	SetTimer(hWnd, TID_DROP, 500, 0);
	hbrushes[0] = CreateSolidBrush(RGB(200, 20, 30));
	hbrushes[1] = CreateSolidBrush(RGB(20, 200, 30));
	hbrushes[2] = CreateSolidBrush(RGB(30, 20, 200));
	hbrushes[3] = CreateSolidBrush(RGB(127, 50, 80));
	hbrushes[4] = CreateSolidBrush(RGB(50, 80, 127));
	hbrushes[5] = CreateSolidBrush(RGB(80, 127, 50));
	hbrushes[6] = CreateSolidBrush(RGB(120, 50, 120));

}
void Ending(HWND hWnd)
{
	KillTimer(hWnd, TID_DROP);
	MessageBox(hWnd, TEXT("게임 오버"), TEXT("끝"), MB_OK);
	DestroyWindow(hWnd);
}
void OnTimer(HWND hWnd, DWORD tid)
{
	Document* doc = Document::GetSingleton();
	bool is_end = false;
	if (doc->MoveDown(&is_end) == false)
	{

	}
	InvalidateRect(hWnd, 0, true);
	if (is_end)
	{
		Ending(hWnd);
	}

}
void MoveLeftProc(HWND hWnd)
{
	Document* doc = Document::GetSingleton();
	doc->MoveLeft();
}
void MoveRightProc(HWND hWnd)
{
	Document* doc = Document::GetSingleton();
	doc->MoveRight();
}
void TurnProc(HWND hWnd)
{
	Document* doc = Document::GetSingleton();
	doc->MoveTurn();
}
void MoveDownAsFar(HWND hWnd)
{
	Document* doc = Document::GetSingleton();	
	bool is_end = false;
	while (doc->MoveDown(&is_end));
	InvalidateRect(hWnd, 0, true);
	
	if (is_end)
	{
		Ending(hWnd);
	}

}
void OnKeyDown(HWND hWnd, DWORD key, LPARAM lParam)
{
	switch (key)
	{
	case VK_UP: TurnProc(hWnd); break;
	case VK_LEFT: MoveLeftProc(hWnd); break;
	case VK_RIGHT: MoveRightProc(hWnd); break;	
	case VK_SPACE: MoveDownAsFar(hWnd); break;
	}
	InvalidateRect(hWnd, 0, true);
}
void DrawBoardGrid(HWND hWnd, HDC hdc)
{
	HPEN hPen = CreatePen(PS_SOLID, 1, RGB(40, 40, 40));
	HPEN oPen = (HPEN)SelectObject(hdc, hPen);

	for (int r = 0; r <= BOARD_ROW; r++)
	{
		MoveToEx(hdc, BOARD_SX, MY_RY(r), 0);
		LineTo(hdc, MY_RX(BOARD_COL), MY_RY(r));
	}
	for (int c = 0; c <= BOARD_COL; c++)
	{
		MoveToEx(hdc, MY_RX(c), MY_RY(0), 0);
		LineTo(hdc, MY_RX(c), MY_RY(BOARD_ROW));
	}

	SelectObject(hdc, oPen);
	DeleteObject(hPen);
}
void DrawGameBoard(HWND hWnd, HDC hdc)
{
	DrawBoardGrid(hWnd, hdc);
	Document* doc = Document::GetSingleton();
	bs_arr bs = doc->GetBoard();
	HBRUSH hBrush, oBrush;
	oBrush = (HBRUSH)SelectObject(hdc, hbrushes[0]);
	for (int y = 0; y < BOARD_ROW; y++)
	{
		for (int x = 0; x < BOARD_COL; x++)
		{
			if (bs[y][x])
			{
				int bindex = bs[y][x] - 1;
				hBrush = hbrushes[bindex];
				SelectObject(hdc, hBrush);
				Ellipse(hdc, MY_RX(x), MY_RY(y), MY_RX(x + 1) - 1, MY_RY(y + 1) - 1);
			}
		}
	}
	SelectObject(hdc, oBrush);
	
}
void DrawDiagram(HWND hWnd, HDC hdc)
{
	Document* doc = Document::GetSingleton();
	Diagram* now = doc->GetNow();
	block bl = now->GetBlock();
	int x = now->GetX();
	int y = now->GetY();
	HBRUSH hBrush = hbrushes[now->GetBNum()];
	HBRUSH oBrush = (HBRUSH)SelectObject(hdc, hBrush);

	for (int cx = 0; cx < DW; cx++)
	{
		for (int cy = 0; cy < DH; cy++)
		{
			if (bl[cy][cx] == 1)
			{
				Ellipse(hdc, 
					MY_RX(x+cx), 
					MY_RY(y+cy), 
					MY_RX(x + cx+1) - 1, 
					MY_RY(y + cy+ 1) - 1);
			}
		}
	}
	
	SelectObject(hdc, oBrush);	
}
void DrawNextBoard(HWND hWnd, HDC hdc)
{
	HPEN hPen = CreatePen(PS_SOLID, 1, RGB(40, 40, 40));
	HPEN oPen = (HPEN)SelectObject(hdc, hPen);

	for (int r = 0; r <= DH; r++)
	{
		MoveToEx(hdc, NEXT_SX, MY_NRY(r), 0);
		LineTo(hdc, MY_NRX(DH), MY_NRY(r));
	}
	for (int c = 0; c <= DW; c++)
	{
		MoveToEx(hdc, MY_NRX(c), MY_NRY(0), 0);
		LineTo(hdc, MY_NRX(c), MY_NRY(DW));
	}

	SelectObject(hdc, oPen);
	DeleteObject(hPen);
}
void DrawNextDiagram(HWND hWnd, HDC hdc)
{
	Document* doc = Document::GetSingleton();
	Diagram* next = doc->GetNext();
	block bl = next->GetBlock();
	int x = 0;
	int y = 0;
	HBRUSH hBrush = hbrushes[next->GetBNum()];
	HBRUSH oBrush = (HBRUSH)SelectObject(hdc, hBrush);

	for (int cx = 0; cx < DW; cx++)
	{
		for (int cy = 0; cy < DH; cy++)
		{
			if (bl[cy][cx] == 1)
			{
				Ellipse(hdc,
					MY_NRX(x + cx),
					MY_NRY(y + cy),
					MY_NRX(x + cx + 1) - 1,
					MY_NRY(y + cy + 1) - 1);
			}
		}
	}

	SelectObject(hdc, oBrush);	
}
void DrawScore(HWND hWnd, HDC hdc)
{
	Document* doc = Document::GetSingleton();
	int score = doc->GetScore();
	wchar_t buf[256];
	wsprintf(buf, TEXT("점수: %d"), score);
	TextOut(hdc, SCORE_X, SCORE_Y, buf, lstrlen(buf));
}
void OnDraw(HWND hWnd, HDC hdc)
{
	DrawGameBoard(hWnd, hdc);
	DrawDiagram(hWnd, hdc);
	DrawNextBoard(hWnd, hdc);
	DrawNextDiagram(hWnd, hdc);
	DrawScore(hWnd, hdc);
}
void OnPaint(HWND hWnd)
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(hWnd, &ps);
	OnDraw(hWnd, hdc);
	EndPaint(hWnd, &ps);
}
void OnDestroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	
	for (int i = 0; i < MAX_DIAGRAM; i++)
	{
		DeleteObject(hbrushes[i]);
	}
	PostQuitMessage(0);
}
LRESULT CALLBACK MainProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	switch (iMessage)
	{
	case WM_CREATE: OnCreate(hWnd, (CREATESTRUCT*)lParam); return 0;
	case WM_TIMER: OnTimer(hWnd, (DWORD)wParam); return 0;
	case WM_KEYDOWN: OnKeyDown(hWnd, (DWORD)wParam, lParam); return 0;
	case WM_PAINT: OnPaint(hWnd); return 0;
	case WM_DESTROY: OnDestroy(hWnd, wParam, lParam); return 0;

	}
	return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow)
{
	WNDCLASS wndclass = { 0 };
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.style = CS_DBLCLKS;
	wndclass.lpfnWndProc = MainProc;
	wndclass.lpszClassName = TEXT("테트리스");
	wndclass.hCursor = LoadCursor(0, IDC_ARROW);
	wndclass.hIcon = LoadIcon(0, IDI_APPLICATION);
	wndclass.hInstance = hIns;

	RegisterClass(&wndclass);
	HWND hWnd = CreateWindow(TEXT("테트리스"),
		TEXT("테트리스"), WS_OVERLAPPEDWINDOW, 100, 100, 410, 400,
		0, 0, hIns, 0);
	ShowWindow(hWnd, nShow);
	MSG Message;
	while (GetMessage(&Message, 0, 0, 0))
	{
		DispatchMessage(&Message);
	}
	return 0;
}

2.2Document.h

#pragma once
#include "Diagram.h"
#include "Board.h"

#define SX	3
#define SY	0
class Document
{
	static Document* singleton;
	Diagram* now;
	Diagram* next;
	Board* board;
	int score;
public:
	static Document *GetSingleton();	
	void MakeDiagram();
	bool MoveDown(bool* is_end);
	bool MoveLeft();
	bool MoveRight();
	Diagram* GetNow();
	bool MoveTurn();
	Diagram *GetNext();	
	bs_arr GetBoard();
	int GetScore()const;
private:
	void ChangeDiagram();
	Document();
	bool MoveEnable(Diagram *now,int cx,int cy,bool is_turn=false);
};

2.3 Document.cpp

#include "Document.h"
Document* Document::singleton;
Document* Document::GetSingleton()
{
	if (singleton == 0)
	{
		singleton = new Document();
	}
	return singleton;
}
Document::Document()
{
	now = new Diagram();
	next = new Diagram();
	board = new Board();
	score = 0;
	MakeDiagram();
}
int Document::GetScore()const
{
	return score;
}
void Document::MakeDiagram()
{
	next->SetPosition(SX, SY);
	now->SetPosition(SX, SY);
}
void Document::ChangeDiagram()
{
	now->SetPosition(next);
	next->SetPosition(SX, SY);
}
bool Document::MoveDown(bool *is_end)
{
	*is_end = false;
	if (MoveEnable(now, 0, 1))
	{
		now->Move(0, 1);
		return true;
	}
	else
	{
		int bnum = now->GetBNum();
		block bl = now->GetBlock();
		int x = now->GetX();
		int y = now->GetY();
		score +=board->AddBlock(bnum, bl, x, y);
	}
	ChangeDiagram();	
	block bl = now->GetBlock();
	int x = now->GetX();
	int y = now->GetY();
	if (board->IsCrash(bl, x, y))
	{
		*is_end = true;
	}
	return false;
}
bool Document::MoveLeft()
{
	if (MoveEnable(now, -1, 0))
	{
		now->Move(-1, 0);
		return true;
	}
	return false;
}
bool Document::MoveRight()
{
	if (MoveEnable(now, 1, 0))
	{
		now->Move(1, 0);
		return true;
	}
	return false;
}
bool Document::MoveTurn()
{
	if (MoveEnable(now, 0, 0,true))
	{
		now->Turn();
		return true;
	}
	return false;
}
Diagram* Document::GetNow()
{
	return now;
}
Diagram* Document::GetNext()
{
	return next;
}
bool Document::MoveEnable(Diagram* now, int cx, int cy , bool is_turn)
{
	block bl = now->GetBlock(is_turn);

	int x = now->GetX() + cx;
	int y = now->GetY() + cy;
	return board->IsCrash(bl, x, y) == false;
}
bs_arr Document::GetBoard()
{
	return board->GetBoardSpace();
}

2.4 Board.h

#pragma once
#define BOARD_COL	10
#define BOARD_ROW	18
#include "Diagram.h"
typedef const int(*bs_arr)[BOARD_COL];
class Board
{
	int bs[BOARD_ROW][BOARD_COL];
public:
	Board();
	bool IsCrash(block bl, int x, int y);
	int AddBlock(int bnum, block bl, int x, int y);
	bs_arr GetBoardSpace();
private:
	int LineCheck(int cy);
	bool IsFull(int cy);
	void MoveLine(int cy);
};

2.5 Board.cpp

#include "Board.h"
#include <string.h>
Board::Board()
{
	for (int y = 0; y < BOARD_ROW; y++)
	{
		for (int x = 0; x < BOARD_COL; x++)
		{
			bs[y][x] = 0;
		}
	}
}
bool Board::IsCrash(block bl, int x, int y)
{
	for (int cx = 0; cx < DW; cx++)
	{
		for (int cy = 0; cy < DH; cy++)
		{
			if (bl[cy][cx])
			{
				if ((y + cy) >= BOARD_ROW || (x + cx < 0) || (x + cx) >= BOARD_COL)
				{
					return true;
				}
				if (bs[y + cy][x + cx])
				{
					return true;
				}
			}
		}
	}
	return false;
}
int Board::AddBlock(int bnum, block bl, int x, int y)
{
	for (int cy = 0; cy < DH; cy++)
	{
		for (int cx = 0; cx < DW; cx++)
		{
			if (bl[cy][cx])
			{
				bs[y + cy][x + cx] = bnum + 1;
			}			
		}
	}
	return LineCheck(y+3);
}
bs_arr Board::GetBoardSpace()
{
	return bs;
}
int Board::LineCheck(int cy)
{
	int clc = 0;
	for (int i = 0; i < DH; i++)
	{
		if (IsFull(cy))
		{
			MoveLine(cy);
			clc++;
		}
		else
		{
			cy--;
		}
	}
	return clc;
}
bool Board::IsFull(int cy)
{
	for (int x = 0; x < BOARD_COL; x++)
	{
		if (bs[cy][x] == 0)
		{
			return false;
		}
	}
	return true;
}
void Board::MoveLine(int cy)
{
	memcpy(bs + 1, bs, sizeof(int) * BOARD_COL * cy);
}

2.6 Diagram.h

#pragma once
#define MAX_DIAGRAM		7
#define MAX_TURN		4
#define DIAGRAM_WIDTH	4
#define DIAGRAM_HEIGHT	4
#define DW DIAGRAM_WIDTH
#define DH DIAGRAM_HEIGHT
typedef const int(*block)[4];
class Diagram
{
	int dindex;
	int turn;
	int x;
	int y;
public:
	Diagram();
	int GetX()const;
	int GetY()const;
	int GetBNum()const;
	void SetPosition(int sx, int sy);
	void SetPosition(Diagram* next);
	void Move(int cx, int cy);
	void Turn();
	block GetBlock(bool is_turn=false);
	
	
};

2.7 Diagram.cpp

#include "Diagram.h"
#include <stdlib.h>
#include <time.h>
const int block_vals[MAX_DIAGRAM][MAX_TURN][DH][DW] =
{
	{
		{
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{1, 1, 1, 1},
			{0, 0, 0, 0}
		},
			{
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{1, 1, 1, 1},
			{0, 0, 0, 0}
		}

	},//0
	{
		{
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 1, 1, 0},
			{0, 0, 0, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 1, 1, 0},
			{0, 0, 0, 0}
		},
			{
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 1, 1, 0},
			{0, 0, 0, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 1, 1, 0},
			{0, 0, 0, 0}
		}

	},//1
	{
		{
			{0, 0, 0, 0},
			{0, 0, 1, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 1, 0},
			{0, 1, 1, 1},
			{0, 0, 0, 0}
		},
			{
			{0, 0, 0, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 1},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 1, 1, 1},
			{0, 0, 1, 0}
		}

	},//2
	{
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 1}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 1},
			{0, 0, 1, 1},
			{0, 0, 1, 0}
		},
			{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 1}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 1},
			{0, 0, 1, 1},
			{0, 0, 1, 0}
		}

	},//3
	{
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 1, 1},
			{0, 1, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 1, 0, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 0}
		},
			{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 1, 1},
			{0, 1, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 1, 0, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 0}
		}

	},//4
	{
		{
			{0, 0, 0, 0},
			{0, 1, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 1},
			{0, 1, 1, 1},
			{0, 0, 0, 0}
		},
			{
			{0, 0, 0, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 1}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 1, 1, 1},
			{0, 1, 0, 0}
		}

	},//5
	{
		{
			{0, 0, 0, 0},
			{0, 0, 1, 1},
			{0, 0, 1, 0},
			{0, 0, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 1, 1, 1},
			{0, 0, 0, 1}
		},
			{
			{0, 0, 0, 0},
			{0, 0, 1, 0},
			{0, 0, 1, 0},
			{0, 1, 1, 0}
		},
		{
			{0, 0, 0, 0},
			{0, 1, 0, 0},
			{0, 1, 1, 1},
			{0, 0, 0, 0}
		}

	}//6
};
Diagram::Diagram()
{
	dindex = 0;
	turn = 0;
	x = 0;
	y = 0;
}
int Diagram::GetX()const
{
	return x;
}
int Diagram::GetY()const
{
	return y;
}
int Diagram::GetBNum()const
{
	return dindex;
}
void Diagram::SetPosition(int sx, int sy)
{
	dindex = rand() % MAX_DIAGRAM;
	turn = 0;
	x = sx;
	y = sy;
}
void Diagram::SetPosition(Diagram* next)
{
	dindex = next->dindex;
	turn = next->turn;
	x = next->x;
	y = next->y;
}
void Diagram::Move(int cx, int cy)
{
	x += cx;
	y += cy;
}
void Diagram::Turn()
{
	turn = (turn + 1) % MAX_TURN;
}
block Diagram::GetBlock(bool is_turn)
{
	if (is_turn)
	{
		int nturn = (turn + 1) % MAX_TURN;
		return block_vals[dindex][nturn];
	}
	return block_vals[dindex][turn];
}