내가 만든 테트리스 소스 Win32's API
글쓴이: 조성현 / 작성시간: 목, 2003/01/30 - 7:36오전
#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); }
코드 멋진가? ㅡ..ㅡ;;
Forums:
resource.h
----
http://linu.sarang.net, http://wbhacker.tistory.com