내가 만든 테트리스 소스 Win32's API

조성현의 이미지

#include <windows.h>
#include "resource.h"
#include <time.h>
#define MainOffsetX 30		// 메인화면 왼쪽 상단 표시 시작 지점
#define MainOffsetY 30
#define NextBoxOffsetX 300	// 다음벽돌 화면 왼쪽 상단 표시 시작 지점
#define NextBoxOffsetY 300
#define B_SIZE 15	// 한 벽돌의 width값
#define SCREEN_X 15	// 메인화면 해상도 지정
#define SCREEN_Y 25
#define ROT_CNT 4	// 한 도형의 최대 회전수
#define BR_CNT 4	// 한 도형의 최대 벽돌 갯수
#define MIN(a, b, c, d) (min( min(a, b), min(c, d) ))
#define MAX(a, b, c, d) (max( max(a, b), max(c, d) ))
#define M_LEFT 1
#define M_RIGHT 2
#define M_DOWN 3
#define M_UP 4
#define ID_TIMER 1
#define ID_PAUSE 101
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LPSTR lpszClass = "Tetris";
HINSTANCE g_hInst;
int s[7][ROT_CNT][BR_CNT] = {
	{ {2, 5, 6, 7}, {2, 5, 6,10}, {1, 5, 6, 9}, {1, 2, 3, 6} },	// s[0] ㅗ
	{ {1, 2, 3, 7}, {1, 2, 5, 9}, {1, 5, 6, 7}, {2, 6, 9,10} },	// s[1] ㄱ
	{ {1, 2, 3, 5}, {1, 5, 9,10}, {3, 5, 6, 7}, {1, 2, 6,10} },	// s[2] ㄱ 가로 반대 모양
	{ {2, 5, 6, 9}, {1, 2, 6, 7}, },							// s[3] ㄱㄴ
	{ {1, 5, 6,10}, {2, 3, 5, 6}, },							// s[4] ㄱㄴ 가로 반대 모양
	{ {1, 2, 3, 4}, {1, 5, 9,13}, },							// s[5] ㅡ
	{ {1, 2, 5, 6}, }											// s[6] ㅁ
};
int Scr[SCREEN_Y + 1][SCREEN_X + 2];	// 맨 아래, 왼쪽, 오른쪽에 1이라는 벽을 만듦.
int MiniScr[BR_CNT + 2][BR_CNT + 2];	// 다음화면 창 표시용
int shape, next_shape;					// 벽돌 모양별
int rot, next_rot;						// 회전 형태별
int offsetX = 8, offsetY = 0;			// x, y좌표에 더하는 기본값
int gX[4], gY[4];						// 현재 벽돌의 갯수인 4개의 좌표
BOOL SCREEN_UPDATE = TRUE, SCREEN_INIT = TRUE, START = FALSE;
HBRUSH BrickBrush, BlankBrush, OrgBrush;
HWND hPauseBtn;
HPEN BrickPen, BlankPen, OrgPen;
RECT Mrt, Nrt;

int RotCount(int *array);	// 현재 shape의 현재 rot의 갯수를 알려고 할 때 사용함
int count(int *array);		// 현재 배열에 -1값이 아닌 공간의 갯수
void draw(HWND hWnd, HDC hDC, int locX, int locY, int x, int y, BOOL stat);
							// 현재 상태(Scr[y][x])를 0(공간)과 1(벽돌)로 화면에 표현
void move_left();			// 현재 움직일 수 있는 벽돌을 왼쪽으로
void move_right();
void move_down();			// 아래로(아래키와 타이머(자동으로 내려가기)에서 사용함)
BOOL check_range(short dir);// 왼쪽과 오른쪽, 아래, 변경이 작동하는지 여부를 범위의 조사로 결정함
void GetCurXY();			// 현재 움직일 수 있는 벽돌의 위치값을 찾는다.
void SetCurXY(int v);		// 인수로 현재 벽돌의 위치값을 설정.
							// 위 두개 함수는 모든 벽돌의 갯수가 4개라는 점을 착안하여 알고리즘에 이용함
int InitScr();				// 화면 초기화 작업
int DrawNext(HWND, HDC);	// 다음창 그리기
int DrawMain(HWND, HDC);	// 메인창 그리기

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
	HWND hWnd;
	HACCEL hAccel;
	MSG Message;
	WNDCLASS WndClass;
	g_hInst = hInstance;

	WndClass.cbClsExtra = 0;
	WndClass.cbWndExtra = 0;
	WndClass.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
	WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndClass.hInstance = hInstance;
	WndClass.lpfnWndProc = (WNDPROC)WndProc;
	WndClass.lpszClassName = lpszClass;
	WndClass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
	WndClass.style = CS_HREDRAW | CS_VREDRAW;
	RegisterClass(&WndClass);

	hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
					CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
					NULL, (HMENU)NULL, hInstance, NULL);
	ShowWindow(hWnd, nCmdShow);

	hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
	while (GetMessage(&Message, 0, 0, 0)) {
		if (!TranslateAccelerator(hWnd, hAccel, &Message)) {
			TranslateMessage(&Message);
			DispatchMessage(&Message);
		}
	}
	return Message.wParam;
}

int RotCount(int *array)
{
	int t = 0;

	while (*array && t < BR_CNT) {
		array += BR_CNT;
		t++; // s[shape]내에서 [y]의 갯수
	}
	return t;
}

int count(int *array)
{
	int t = 0;

	while (*array != -1) {	// DeadLine[] 변수에서만 사용하므로 임의로 -1값으로 함
		array++;
		t++; // s[shape]내에서 [y]의 갯수
	}
	return t;
}

void draw(HWND hWnd, HDC hDC, int locX, int locY, int x, int y, BOOL stat)
{
	RECT rt;

	if (stat) {
		OrgBrush = (HBRUSH)SelectObject(hDC, BrickBrush);
		OrgPen = (HPEN)SelectObject(hDC, BrickPen);
	} else {
		OrgBrush = (HBRUSH)SelectObject(hDC, BlankBrush);
		OrgPen = (HPEN)SelectObject(hDC, BlankPen);
	}
	rt.left = locX + x * B_SIZE - B_SIZE / 2;
	rt.top = locY + y * B_SIZE - B_SIZE / 2;
	rt.right = rt.left + B_SIZE;
	rt.bottom = rt.top + B_SIZE;
	Rectangle(hDC, rt.left, rt.top, rt.right, rt.bottom);
	SelectObject(hDC, OrgBrush);
	SelectObject(hDC, OrgPen);
}

void move_left()
{
	for (int i = 0; i < BR_CNT; i++) {
		Scr[ gY[i] ][ gX[i]-- ] = 0;
		Scr[ gY[i] ][ gX[i] ] = 2;
	}
}

void move_right()
{
	for (int i = BR_CNT - 1; i >= 0; i--) {	// BR_CNT부터 하는 이유는 가장 먼저 움직여야 할
		Scr[ gY[i] ][ gX[i]++ ] = 0;		// 부분부터 움직이게 해서 0으로 덮어지지 않게 하기 위해서
		Scr[ gY[i] ][ gX[i] ] = 2;
	}
}

void move_down()
{
	for (int i = BR_CNT - 1; i >= 0; i--) {
		Scr[ gY[i]++ ][ gX[i] ] = 0;	// 이전 블럭에는 0을 대입해서 안보이게 한다
		Scr[ gY[i] ][ gX[i] ] = 2;		// 이동한 현재 블럭에는 2를 대입함
	}
}

BOOL check_range(short dir)
{
	int i;

	switch (dir) {
	case M_LEFT:
		for (i = 0; i < BR_CNT; i++)
			if (Scr[ gY[i] ][ gX[i] - 1 ] == 1) return FALSE;
		break;
	case M_RIGHT:
		for (i = 0; i < BR_CNT; i++)
			if (Scr[ gY[i] ][ gX[i] + 1 ] == 1) return FALSE;
		break;
	case M_DOWN:
		for (i = 0; i < BR_CNT; i++) {
			if (Scr[ gY[i] + 1 ][ gX[i] ] == 1) return FALSE;
		}
		break;
	case M_UP:
		rot = (rot + 1) % RotCount(&s[shape][0][0]);
		offsetX = MIN(gX[0], gX[1], gX[2], gX[3]);
		offsetY = MIN(gY[0], gY[1], gY[2], gY[3]);
		SetCurXY(0);				// 현재 gX, gY에 0값을 주기
		GetCurXY();					// 바뀐 rot로 gX, gY 값 불러오기
		for (i = offsetY; i <= MAX(gY[0], gY[1], gY[2], gY[3]); i++) {
			for (int j = offsetX; j <= MAX(gX[0], gX[1], gX[2], gX[3]); j++) {
				if (Scr[i][j] == 1) {
					rot = (rot == 0) ? RotCount(&s[shape][0][0]) - 1 : rot - 1;
					GetCurXY();		// rot값에 맞는 gX, gY 값 불러오기
					SetCurXY(2);	// 현재 gX, gY에 1값을 주기
					return FALSE;
				}
			}
		}
		break;
	default:
		return FALSE;
	}

	return TRUE;
}

void GetCurXY()
{
	for (int x = 0; x < BR_CNT; x++) {
		gX[x] = offsetX + (s[shape][rot][x] - 1) % BR_CNT;
		gY[x] = offsetY + (int)((s[shape][rot][x] - 1) / BR_CNT);
	}
}

void SetCurXY(int v)
{
	for (int x = 0; x < BR_CNT; x++) {
		Scr[ gY[x] ][ gX[x] ] = v;
	}
}

int InitScr()
{
	int x, y;

	if (SCREEN_UPDATE || SCREEN_INIT) {
		if (SCREEN_INIT) {
			for (x = 0; x < SCREEN_X + 1; x++) {	// 아래쪽 벽돌을 1로 지정
				Scr[SCREEN_Y][x] = 1;
			}
			for (y = 0; y < SCREEN_Y + 1; y++) {	// 왼쪽, 오른쪽 벽돌을 1로 지정
				Scr[y][0] = 1;
				Scr[y][SCREEN_X + 1] = 1;
			}
			if (!START) {	// 맨 처음 벽돌은 다음창에만 나타나고 메인창에는 안나타나게 함
				srand((unsigned)time(NULL));
				next_shape = rand() % 7;
				next_rot = rand() % RotCount(&s[next_shape][0][0]);
			} else {
				shape = next_shape;
				rot = next_rot;
				offsetX = SCREEN_X / 2; offsetY = 0;
				srand((unsigned)time(NULL));
				next_shape = rand() % 7;
				next_rot = rand() % RotCount(&s[next_shape][0][0]);
				GetCurXY();
				SetCurXY(2);
			}
			SCREEN_INIT = FALSE;
		} else {
			// 현재 위치를 0으로 하여 이동후 이전블럭 안보이게 하는 역할
			SetCurXY(0);
			GetCurXY();
			SetCurXY(2);	// Scr[y값][x값] 현재 모양 칸에 2값을 줌.
		}
		SCREEN_UPDATE = FALSE;
	}

	return TRUE;
}

int DrawMain(HWND hWnd, HDC hDC)
{
	int x, y;

	/* 메인 박스 화면 */
	for (y = 0; y < SCREEN_Y; y++)
		for (x = 1; x < SCREEN_X + 1; x++)
			draw(hWnd, hDC, MainOffsetX, MainOffsetY, x, y, Scr[y][x] ? 1 : 0);
			// Scr[y][x] 값에는 1(굳혀진 벽돌), 2(현재 움직이는 벽돌), 0(비어 있음)이란 값이 올 수가 있다.
	Mrt.left = MainOffsetX + B_SIZE / 2;
	Mrt.top = MainOffsetY - B_SIZE / 2 - 1;
	Mrt.right = MainOffsetX + (SCREEN_X + 1) * B_SIZE - B_SIZE / 2 + 1;
	Mrt.bottom = MainOffsetY + SCREEN_Y * B_SIZE - B_SIZE / 2 + 1;
	MoveToEx(hDC, Mrt.left, Mrt.top, NULL);
	LineTo(hDC, Mrt.left, Mrt.bottom);
	LineTo(hDC, Mrt.right, Mrt.bottom);
	MoveToEx(hDC, Mrt.left, Mrt.top, NULL);
	LineTo(hDC, Mrt.right, Mrt.top);
	LineTo(hDC, Mrt.right, Mrt.bottom);

	return TRUE;
}

int DrawNext(HWND hWnd, HDC hDC)
{
	int x, y, Right, Bottom;

	/* 다음벽돌 박스 화면 */
	for (y = 0; y < BR_CNT + 2; y++)
		for (x = 0; x < BR_CNT + 2; x++)
			MiniScr[ y ][ x ] = 0;	// 미니창 초기화
	Right = MAX( (s[next_shape][next_rot][0] - 1) % BR_CNT,
			(s[next_shape][next_rot][1] - 1) % BR_CNT,
			(s[next_shape][next_rot][2] - 1) % BR_CNT,
			(s[next_shape][next_rot][3] - 1) % BR_CNT );
	Bottom = MAX( (s[next_shape][next_rot][0] - 1) / BR_CNT,
			(s[next_shape][next_rot][1] - 1) / BR_CNT,
			(s[next_shape][next_rot][2] - 1) / BR_CNT,
			(s[next_shape][next_rot][3] - 1) / BR_CNT );
	MiniScr[ ((BR_CNT + 2) - Bottom) / 2 + (s[next_shape][next_rot][0] - 1) / BR_CNT ] \
		[ ((BR_CNT + 2) - Right) / 2 + (s[next_shape][next_rot][0] - 1) % BR_CNT ] = 1;
	MiniScr[ ((BR_CNT + 2) - Bottom) / 2 + (s[next_shape][next_rot][1] - 1) / BR_CNT ] \
		[ ((BR_CNT + 2) - Right) / 2 + (s[next_shape][next_rot][1] - 1) % BR_CNT ] = 1;
	MiniScr[ ((BR_CNT + 2) - Bottom) / 2 + (s[next_shape][next_rot][2] - 1) / BR_CNT ] \
		[ ((BR_CNT + 2) - Right) / 2 + (s[next_shape][next_rot][2] - 1) % BR_CNT ] = 1;
	MiniScr[ ((BR_CNT + 2) - Bottom) / 2 + (s[next_shape][next_rot][3] - 1) / BR_CNT ] \
		[ ((BR_CNT + 2) - Right) / 2 + (s[next_shape][next_rot][3] - 1) % BR_CNT ] = 1;
	for (y = 0; y < BR_CNT + 2; y++)
		for (x = 0; x < BR_CNT + 2; x++)
			draw(hWnd, hDC, NextBoxOffsetX, NextBoxOffsetY, x, y, MiniScr[y][x] ? 1 : 0);
	Nrt.left = NextBoxOffsetX - B_SIZE / 2 - 1;
	Nrt.top = NextBoxOffsetY - B_SIZE / 2 - 1;
	Nrt.right = NextBoxOffsetX + (BR_CNT + 2) * B_SIZE - B_SIZE / 2 + 1;
	Nrt.bottom = NextBoxOffsetY + (BR_CNT + 2) * B_SIZE - B_SIZE / 2 + 1;
	MoveToEx(hDC, Nrt.left, Nrt.top, NULL);
	LineTo(hDC, Nrt.left, Nrt.bottom);
	LineTo(hDC, Nrt.right, Nrt.bottom);
	MoveToEx(hDC, Nrt.left, Nrt.top, NULL);
	LineTo(hDC, Nrt.right, Nrt.top);
	LineTo(hDC, Nrt.right, Nrt.bottom);

	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;
	HFONT MyFont, OldFont;
	HMENU hMenu = GetMenu(hWnd);
	PAINTSTRUCT ps;
	RECT rt = {Mrt.left, Mrt.top, Nrt.right, max(Mrt.bottom, Nrt.bottom)};
	int x, y, ix;
	char buf[20];
	int DeadLine[SCREEN_Y + 1];

	switch (iMessage) {
	case WM_CREATE:
		hPauseBtn = CreateWindow("button", "Start", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
							NextBoxOffsetX - 5, MainOffsetX, 70, 25, hWnd, (HMENU)ID_PAUSE, g_hInst, NULL);
		hDC = GetDC(hWnd);
		MyFont = CreateFont(10, 0, 0, 0, 0, 0, 0, 0, HANGEUL_CHARSET, 0, 0, 0, 0, "바탕체");
		OldFont = (HFONT)SelectObject(hDC, MyFont);
		CreateWindow("static", "Next Brick", WS_CHILD | WS_VISIBLE | SS_SIMPLE,
				NextBoxOffsetX - 5, NextBoxOffsetY - 25, 100, 20, hWnd, (HMENU)-1, g_hInst, NULL);
		SelectObject(hDC, OldFont);
		ReleaseDC(hWnd, hDC);
		BrickBrush = CreateSolidBrush(0x88);
		BlankBrush = CreateSolidBrush(0xffffff);
		BrickPen = CreatePen(PS_SOLID, 1, 0xffffff);
		BlankPen = CreatePen(PS_SOLID, 1, 0xffffff);
		EnableMenuItem(hMenu, ID_MENU_START, MF_ENABLED);
		EnableMenuItem(hMenu, ID_MENU_PAUSE, MF_GRAYED);
		return 0;
	case WM_TIMER:
		switch (wParam) {
		case ID_TIMER:
			if (check_range(M_DOWN)) move_down();
			else {
				memset(&DeadLine[0], -1, sizeof(int) * (SCREEN_Y + 1));
				SCREEN_INIT = TRUE;
				SetCurXY(1);	// 현재상태를 굳혀버림(1값을 주는 것은 벽으로 취급하라는 의미)
				
				for (y = 0; y < SCREEN_Y; y++) {
					for (x = 1; (x < SCREEN_X + 1) && (Scr[y][x] == 1); x++);
					if (x == SCREEN_X + 1) DeadLine[count(&DeadLine[0])] = y;	// 완성된 줄이라면 현재 위치를 저장
				}
				//wsprintf(buf, "%d갯수가 처리되어야 합니다.", count(&DeadLine[0]));
				//SetWindowText(hWnd, buf);
				for (x = 0; x < count(&DeadLine[0]); x++) {		// 완성된 줄 갯수만큼 반복
					for (ix = 1; ix < SCREEN_X + 1; ix++) Scr[0][ix] = 0;
					for (y = DeadLine[x]; y > 0; y--)			// 완성된 아래줄부터 처리하는 이유는
						for (ix = 1; ix < SCREEN_X + 1; ix++)	// 효율적으로 이동하기 위해서
							if (Scr[y][ix] != Scr[y - 1][ix])
								Scr[y][ix] = Scr[y - 1][ix];	// 현재줄에 윗줄을 복사하기
				}
			}
			InvalidateRect(hWnd, &rt, TRUE);
		}
		return 0;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ID_MENU_START:
			START = TRUE;
			SetTimer(hWnd, ID_TIMER, 800, NULL);
			EnableMenuItem(hMenu, ID_MENU_START, MF_GRAYED);
			EnableMenuItem(hMenu, ID_MENU_PAUSE, MF_ENABLED);
			CheckMenuItem(hMenu, ID_MENU_PAUSE, MF_UNCHECKED);
			SetWindowText(hPauseBtn, "Pause");
			break;
		case ID_MENU_PAUSE:
			if (GetMenuState(hMenu, ID_MENU_PAUSE, NULL) == MF_CHECKED) {
				START = TRUE;
				SetWindowText(hPauseBtn, "Pause");
				SetTimer(hWnd, ID_TIMER, 800, NULL);
				CheckMenuItem(hMenu, ID_MENU_PAUSE, MF_UNCHECKED);
			} else {
				START = FALSE;
				SetWindowText(hPauseBtn, "Start");
				KillTimer(hWnd, ID_TIMER);
				CheckMenuItem(hMenu, ID_MENU_PAUSE, MF_CHECKED);
				InvalidateRect(hWnd, &rt, TRUE);
			}
			break;
		case ID_MENU_RESET:
			START = FALSE;
			KillTimer(hWnd, ID_TIMER);
			SCREEN_INIT = TRUE;
			for (y = 0; y < SCREEN_Y; y++)
				for (x = 1; x < SCREEN_X + 1; x++)
					Scr[y][x] = 0;
			EnableMenuItem(hMenu, ID_MENU_START, MF_ENABLED);
			EnableMenuItem(hMenu, ID_MENU_PAUSE, MF_GRAYED);
			CheckMenuItem(hMenu, ID_MENU_PAUSE, MF_UNCHECKED);
			SetWindowText(hPauseBtn, "Start");
			InvalidateRect(hWnd, &rt, TRUE);
			break;
		case ID_MENU_EXIT:
			SendMessage(hWnd, WM_DESTROY, 0, 0);
			break;
		case ID_PAUSE:
			GetWindowText(hPauseBtn, buf, 20);
			if (!strcmp(buf, "Start")) {
				SetWindowText(hPauseBtn, "Pause");
				SendMessage(hWnd, WM_COMMAND, (WPARAM)ID_MENU_START, 0);
				SetFocus(hWnd);
			} else {
				SetWindowText(hPauseBtn, "Start");
				SendMessage(hWnd, WM_COMMAND, (WPARAM)ID_MENU_PAUSE, 0);
				SetFocus(hWnd);
			}
			break;
		}
		return 0;
	case WM_KEYDOWN:
		switch ((int)wParam) {
		case VK_LEFT:
			if (check_range(M_LEFT) && START) {
				move_left();
				InvalidateRect(hWnd, &rt, TRUE);
			}
			break;
		case VK_RIGHT:
			if (check_range(M_RIGHT) && START) {
				move_right();
				InvalidateRect(hWnd, &rt, TRUE);
			}
			break;
		case VK_DOWN:
			if (check_range(M_DOWN) && START) {
				move_down();
				InvalidateRect(hWnd, &rt, TRUE);
			}
			break;
		case VK_UP:
			if (check_range(M_UP) && START) {
				SCREEN_UPDATE = TRUE;
				InvalidateRect(hWnd, &rt, TRUE);
			}
			break;
		case VK_SPACE:
			if (START) {
				while (check_range(M_DOWN)) {
					move_down();
					InvalidateRect(hWnd, &rt, TRUE);
				}
				SendMessage(hWnd, WM_TIMER, ID_TIMER, 0);
			}
			break;
		}
		return 0;
	case WM_PAINT:
		hDC = BeginPaint(hWnd, &ps);
		InitScr();
		DrawNext(hWnd, hDC);
		DrawMain(hWnd, hDC);
		EndPaint(hWnd, &ps);
		return 0;
	case WM_DESTROY:
		DeleteObject(BrickBrush);
		DeleteObject(BlankBrush);
		DeleteObject(BrickPen);
		DeleteObject(BlankPen);
		KillTimer(hWnd, ID_TIMER);
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hWnd, iMessage, wParam, lParam);
}

코드 멋진가? ㅡ..ㅡ;;

조성현의 이미지

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Tetris.rc
//
#define IDR_MENU1                       101
#define IDR_MENU                        101
#define IDR_ACCELERATOR                 102
#define ID_MENU_START                   40001
#define ID_MENU_EXIT                    40002
#define ID_MENU_RESET                   40003
#define ID_MENU_PAUSE                   40004

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40005
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif