[windowsAPI] 10주차, 틱택톡(tic-tac-toe) 만들기

교내 전공과목인 윈도우즈 API수업을 정리합니다

 

 

1. 히트테스트를 할 수 있는 프로그램을 만들어 보자

각 범위를 누르면 서로 다른 애니메이션이 나타나도록 한다

- 실행시킬 때, 이 프로그램은 바탕화면 위에 있어야한다 ( 중간에 다른 창이 끼면 안 된다)

- 왼쪽버튼을 눌렀을 때 어느 칸을 눌렀는지 인식하도록 만들어야 한다. (hit test)

 

 

(0) 빌드 오류 고치기

플랫폼 도구 집합을 2019버전으로 맞춤.

명령줄 오류 고치기 위한 함수 수준 링크 사용 설정

 

(1) 소스코드

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	TCHAR* Mes = TEXT("마우스 왼쪽 버튼을 누르시면 애니메이션을 볼 수 있습니다");
	int i;

	switch (iMessage) {
	case WM_LBUTTONDOWN:
		i = (LOWORD(lParam) - 10) / 50;   // x(LOWORD(lParam)) = 132라고 쳐보자.
		ShowWindow(hWnd, SW_HIDE);
		switch (i) {
		case 0:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_HOR_POSITIVE);
			break;
		case 1:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_HOR_NEGATIVE);
			break;
		case 2:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_VER_POSITIVE);
			break;
		case 3:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_VER_NEGATIVE);
			break;
		case 4:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_HOR_POSITIVE | AW_VER_POSITIVE);
			break;
		case 5:
			AnimateWindow(hWnd, 500, AW_SLIDE | AW_HOR_POSITIVE | AW_VER_NEGATIVE);
			break;
		case 6:
			AnimateWindow(hWnd, 500, AW_BLEND);
			break;
		case 7:
		default:
			AnimateWindow(hWnd, 500, AW_CENTER);
			break;
		}
		InvalidateRect(hWnd, NULL, TRUE);
		SetForegroundWindow(hWnd);
		return TRUE;
	case WM_PAINT:  // 50x50사이즈의 사각형을 그린다
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 10, 10, Mes, lstrlen(Mes));
		for (i = 10; i <= 360; i += 50)
			Rectangle(hdc, i, 50, i + 50, 100); // 시작점의 Y좌표 고정(50), 끝점의 Y좌표 고정(100)
		EndPaint(hWnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
  • ShowWindow(HWND hWnd, int nCmdShow)
    - nCmdShow:지정하고자 하는 보이기 상태, SW_HIDE(숨기기), SW_MAXIMIZE(최대화)등의 값들이 있다
  • AnimateWindow (hWnd, dwTime, dwFlags)
    - dwTime: 애니메이션은 재생하는 데 걸리는 시간
    - dwFlags: 애니메이션 유형임. AW+ACTIVATE, AW_BLEND, AW_CENTER등의 값들이 있다.
  • SetForegroundWindow(hWnd): 지정한 창을 만든 스레드를 맨 앞으로 가져오며 창을 화성화함. (우선 순위로 할당)
    - 맨 앞으로 가져오도록 활상화할 창
  • PostQuitMessage(nExitCode): 스레드 메시지 큐에 WM_QUIT메시지를 붙이고 즉시 리턴한다. 이로써 이 스레드가 종료될 것이라는 것을 미리 알려줌, 다만 호출 즉시 프로세스가 종료되는 것은 아니며, 현재 메시지 큐에 들어 온 모든 메시지가 처리된 후에 WM_QuIT 메시지가 읽혀져서 종료된다.
    - nExitCode:종료코드, 보통 0으로 할당
  • DefWindowProc(hWnd, Msg, wParam, lParam): 윈도우 프로시저가 처리하지 않은 메시지의 디폴트 처리를 한다. WndProc은 원하는 메시지를 처리하고 처리되지 않은 것들은 이 함수에게 전달하여 디폴트 처리를 시키는 것이다.
    - hWnd: 메시지를 받은 윈도우의 핸들
    - Msg:메시지 구조체
    - wParam: 메시지 정보
    - lParam: 메시지 정보, 이상 4개의 인수는 WndProc함수가 전달받은 인수들과 동일함

(2)실행화면

- 나열된 박스를 클릭하면, 마치 ppt의 화면전환들이 나타나는 것 처럼 애니메이션이 실행된다

 

 

 

2. TicTacToe만들기 ver 1

빨간 바둑과 초록 바둑을 나누어진 영역에 둘 수 있도록 만들어 보자.

 

(0) 프로젝트 설정해주자

vs2013으로 바꾸는 게 나을 것 같지만 오늘도 일일이 수정하자

준수모드 > 아니오

 

(1). 소스코드

int pan[3][3]; // 3x3칸

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	HBRUSH hBrush, oldBrush;
	TCHAR* Mes = TEXT("클릭 시 애니메이션이 동작함");

	int x, y;
	static int turn = 1; // 최초의 턴을  1로 고정

	switch (iMessage) {
	case WM_LBUTTONDOWN:
		GetClientRect(hWnd, &rect);

		/*  3x3 영역 분할하기*/
		x = (LOWORD(lParam)) / (rect.right / 3);
		y = (HIWORD(lParam)) / (rect.bottom / 3);

		/* 빈곳에 대해 색상 순서 바꾸기*/
		if (pan[x][y] == 0) { 
			pan[x][y] = turn;
			turn = (turn == 1 ? 2 : 1);
			InvalidateRect(hWnd, NULL, TRUE);
		}
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		GetClientRect(hWnd, &rect);


		/* 판의 윤곽선 그리기 */
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				Rectangle(hdc, i * rect.right / 3, j * rect.bottom / 3, (i + 1) * rect.right / 3, (j + 1) * rect.bottom / 3);
			}
		}

		/* 바둑알 그리기 */
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				/* 영역 크기에 맞게 */
				if (pan[i][j] ==1) {  // 플레이어1
					hBrush = (HBRUSH)CreateSolidBrush(RGB(255, 0, 0));
					oldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					Ellipse(hdc, i * rect.right / 3, j * rect.bottom / 3, (i + 1) * rect.right / 3, (j + 1) * rect.bottom / 3);
					SelectObject(hdc, oldBrush);
					DeleteObject(hBrush);
				}
				else if (pan[i][j] == 2) { // 플레이어2
					hBrush = (HBRUSH)CreateSolidBrush(RGB(0, 255, 0));
					oldBrush = (HBRUSH)SelectObject(hdc, hBrush);
					Ellipse(hdc, i * rect.right / 3, j * rect.bottom / 3, (i + 1) * rect.right / 3, (j + 1) * rect.bottom / 3);
					SelectObject(hdc, oldBrush);
					DeleteObject(hBrush);
				}
			}
		}
		EndPaint(hWnd, &ps);
		return 0;
	
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

 

 

 

(2). 실행결과

한 명이 두 가지의 색상을 번 갈아 가며 3x3 바둑을 둔다. (그러나 3개를 이어도 다른 이벤트는 발생하지 않는 상태다)

 

 

 

 

2. TicTacToe만들기 ver2 (feat. 컴퓨터와 승부하기)

사용자가 바둑을 둘 때 컴퓨터가 자동으로 바둑을 배치하도록 하자

승부 조건을 따져 승패유무를 확인할 수 있게 하자

 

(1)소스코드

 

//WinMain()에 다음 소스 코드 추가
	hWndMain = hWnd;
    
    
// 전역변수 추가
int pan[3][3]; // 3x3칸
int turn = 1; // 최초의 턴을  1로 고정
int iCount; //현 게임에서 바둑을 둔 횟수 세기


//승패 판단 함수 winpoint
int winpoint(int a) {  // a는 turn
	if (pan[0][0] == a && pan[1][0] == a && pan[2][0] == a)
		return 1;
	else if (pan[0][1] == a && pan[1][1] == a && pan[2][1] == a)
		return 1;
	else if (pan[0][2] == a && pan[1][2] == a && pan[2][2] == a)
		return 1;
	else if (pan[0][0] == a && pan[0][1] == a && pan[0][2] == a)
		return 1;
	else if (pan[1][0] == a && pan[1][1] == a && pan[1][2] == a)
		return 1;
	else if (pan[2][0] == a && pan[2][1] == a && pan[2][2] == a)
		return 1;
	else if (pan[0][0] == a && pan[1][1] == a && pan[2][2] == a)
		return 1;
	else if (pan[2][0] == a && pan[1][1] == a && pan[0][2] == a)
		return 1;
	else
		return 0;
}



//연속 게임 실행시 초기화 함수
void init() {
	int i, j;
	turn = 1; //게임이 연속 될때를 대비해서
	iCount = 0;
	for (i = 0; i < 3; i++)
		for (j = 0; j < 3; j++) {
			pan[i][j] = 0;
		}
	InvalidateRect(hWndMain, NULL, TRUE);
}


//WinPRoc > WM_LBUTTONDOWN 수정
GetClientRect(hWnd, &rect);
		SetRect(&rect, rect.left, rect.top, rect.right, rect.bottom);

		/*  3x3 영역 분할하기*/
		x = (LOWORD(lParam)) / (rect.right / 3);
		y = (HIWORD(lParam)) / (rect.bottom / 3);

		/* 빈곳에 대해 색상 순서 바꾸기*/
		if (pan[x][y] == 0) { 
			pan[x][y] = turn;
			iCount++;
			// turn = (turn == 1 ? 2 : 1);  // 컴퓨터와 승부할 것이니 이제 필요없음
			InvalidateRect(hWnd, NULL, TRUE);
			UpdateWindow(hWnd);

			//메세지박스 응답선택에 따른 반응
			if (winpoint(1)) { // 컴퓨터vs플레이어 이므로, 플레이어1만 따지자.
				wsprintf(buf, TEXT("사용자 win. \n 새로 시작하겠습니까?"));
				if (MessageBox(hWnd, buf, TEXT("TicTacToe"), MB_YESNO | MB_ICONEXCLAMATION) == IDYES) 
					init();
				else
					SendMessage(hWnd, WM_CLOSE, 0L, 0L);
				return 0;
			}
			else if (iCount == 9) {
				if (MessageBox(hWnd, TEXT("Game Over. \n 새로 시작하겠습니까?"), TEXT("TicTacToe"),
					MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
					init();
				else
					SendMessage(hWnd, WM_CLOSE, 0L, 0L);
				return 0;
			}

			while (1) {
				/* (컴퓨터) 무작위 위치 찾기*/
				int tx = rand() % 3;
				int ty = rand() % 3;

				/* 무작위 위치에서 빈 곳을 찾으면*/
				if (pan[tx][ty] == 0) {
					pan[tx][ty] = 2;
					iCount++;
					InvalidateRect(hWnd, NULL, TRUE);
					UpdateWindow(hWnd);

					//메세지박스 응답선택에 따른 반응
					if (winpoint(2)) { // 컴퓨터vs플레이어 이므로, 2는 컴퓨터 고정
						wsprintf(buf, TEXT("컴퓨터 win. \n 새로 시작하겠습니까?"));
						if (MessageBox(hWnd, buf, TEXT("TicTacToe"), MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
							init();
						else
							SendMessage(hWnd, WM_CLOSE, 0L, 0L);
						return 0;
					}
					else if (iCount == 9) {
						if (MessageBox(hWnd, TEXT("Game Over. \n 새로 시작하겠습니까?"), TEXT("TicTacToe"),
							MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
							init();
						else
							SendMessage(hWnd, WM_CLOSE, 0L, 0L);
						return 0;
					}

					break; //바둑을 두었다면 제어문 탈출
				}
			}
		}
		return 0;

 

  •  UpdateWindow(hWnd) : 작업영역을 강제로 그리도록 함 (즉시)

 

 

(2) 실행결과

사용자(빨강)가 바둑을 두면 자동으로 컴퓨터(초록)도 바둑을 둔다.

또한 3개의 같은 색을 이었을시 승부처리 이벤트를 발생시킨다.

 

 

 

 

다음시간은 최적 위치를 찾아 무조건 이기는 컴퓨터와의 바둑싸움 만들기 (min-max알고리즘)