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

etc-image-0

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

 

 

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

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

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

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

 

 

(0) 빌드 오류 고치기

etc-image-1

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

etc-image-2

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

 

(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)실행화면

etc-image-3

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

 

 

 

2. TicTacToe만들기 ver 1

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

 

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

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

etc-image-4

준수모드 > 아니오

 

(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). 실행결과

etc-image-5

한 명이 두 가지의 색상을 번 갈아 가며 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) 실행결과

etc-image-6

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

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

 

 

 

 

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