F.R.I.D.A.Y.

지뢰찾기 - 윈도우 구성 본문

DEV/Direct2D

지뢰찾기 - 윈도우 구성

F.R.I.D.A.Y. 2021. 7. 7. 17:07
반응형

 실제 기능 구현


윈도우 생성

 이전 두 개의 포스트를 통해 그리기 작업과, 맵데이터 관리/처리는 해결했다. 이제, 그래픽 작업과 맵데이터를 작동시키기 위한 메인 윈도우를 구성하자.

 

멤버

 윈도우에서 관리할 멤버들은 다음과 같다.

  • DrawTool*
  • TextTool*
  • IMap*
  • D2D1_POINT_2F MovePoint
  • size_t window_padding, window_gap

 여기에서 MovePoint는 <OnMouseMove>메서드 처리 후, 그리기 처리를 할 때 필요다. 그리고 window_padding과 window_gap은 각 블럭 사이 간격과 window border와 블럭간의 거리를 상수로 처리한 것이다.

 

메서드

 이벤트 처리를 위한 메서드는 총 6개로, 다음과 같다.

  • OnLButtonDown
  • OnRButtonDown
  • OnMouseMove
  • OnSize
  • OnCreate
  • OnPaint

 각 메서드에서 담당할 작업은 다음과 같다. 주요 내용만 설명한다.

 

OnLButtonDown

 블럭을 클릭했을 때, 해당 블럭이 열리지 않은 상태(BLOCK_STATE::CLOSED)라면 연 상태(BLOCK_STATE::OPENED)로 변경한다. 다만, 체크를 했을 때 블럭 상태가 BLOCK_STATE::PICKED라면 확장하지 않는다.

 또한, 클릭한 블럭 팔방에 폭탄의 개수가 0인 경우에는 해당 영역도 추가로 확장시킨다.

 

OnRButtonDown

 블럭의 상태를 CLOSED ↔ PICKED 로 상호 전환한다. 

 


알고리즘 구현

 기본적인 설명이 끝났으므로 이제 알고리즘 구현을 진행해야 한다.

 

OnLButtonDown

조건

 해당 메서드에서 처리할 내용은 총 n가지로 다음과 같다.

  1. 클릭한 지역이 CLOSED인 경우
     클릭한 지역을 CLOSED → OPENED로 변경
  2. 클릭한 지역이 PICKED인 경우
     아무 효과 적용하지 않음

 (1)안에서 추가로 판단해야하는 조건은 다음과 같다.

 

OPENED로 수정된 지역이 지뢰로 설정되어 있는가?

 

 

 만일 지뢰로 설정되어 있다면 그 즉시 게임을 중단시킨다. 반대로 지뢰로 설정되지 않은 지역이라면 추가로 아래 조건을 판단해야한다.

 

클릭한 지역의 인접 지역에 폭탄이 몇 개나 존재하는가?

 

  • 인접 지역에 지뢰가 없는 경우
     해당 지역의 팔방에 대해 조건 판단을 그대로 수행한다.
  • 인접 지역에 지뢰가 있는 경우
     해당 지역만 CLOSED → OPENED로 수정한다.

 

코드 구현

 위 조건에 맞춰 코드를 구현하면 다음과 같다.

	D2D1_SIZE_U mapSize = map->GetMapSize();
	D2D1_SIZE_F blockSize = map->GetBlockSize();
	Mine::Block* bTemp;
	for (size_t y = 0; y < mapSize.height; ++y) {
		for (size_t x = 0; x < mapSize.width; ++x) {
			if (m_window_padding + (m_window_gap + blockSize.width) * x < pos.x && pos.x < m_window_padding + (m_window_gap + blockSize.width) * x + blockSize.width &&
				m_window_padding + (m_window_gap + blockSize.height) * y < pos.y && pos.y < m_window_padding + (m_window_gap + blockSize.height) * y + blockSize.height) {
				bTemp = map->GetBlock(x, y);

				// PICKED는 클릭할 수 없음
				if (bTemp->GetState() == Mine::Block::BLOCK_STATE::PICKED) {
					y = mapSize.height;
					break;
				}

				bTemp->SetState(Mine::Block::BLOCK_STATE::OPENED);

				if (bTemp->isBomb() == true) {
					InvalidateRect(Window(), nullptr, false);
					MessageBoxW(Window(), L"지뢰 밟았어요", L"지뢰찾기", 0 | MB_ICONEXCLAMATION);
					SendMessage(Window(), WM_CLOSE, 0, 0);
					return 0;
				}
				else {
					if (!bTemp->GetAroundBomb()) {
						std::stack<POINT> visitList;
						POINT curPos{x,y}, tempPos;
						
						bool** visit = new bool*[mapSize.height];
						for (size_t i = 0; i < mapSize.height; ++i) {
							visit[i] = new bool[mapSize.width]{};
						}
						visitList.push(curPos);
						static POINT offset[8]{
							{-1,-1},{-1,0},{-1,1},
							{0,-1},{0,1},
							{1,-1},{1,0},{1,1}
						};

						do {
							curPos = visitList.top();
							visitList.pop();

							if (visit[curPos.y][curPos.x]) continue;	// 이미 지나간 길은 패스
							bTemp = map->GetBlock(curPos.x, curPos.y);

							visit[curPos.y][curPos.x] = true;
							if(bTemp->GetState() != Mine::Block::BLOCK_STATE::PICKED)
								bTemp->SetState(Mine::Block::BLOCK_STATE::OPENED);

							if (bTemp->GetAroundBomb()) continue;	// 주변에 폭탄이 있으면 패스

							for (int i = 0; i < 8; ++i) {
								tempPos.x = curPos.x + offset[i].x;
								tempPos.y = curPos.y + offset[i].y;
								if (map->isAvailable(tempPos.x, tempPos.y) && bTemp->GetState() != Mine::Block::BLOCK_STATE::PICKED) { // 범위 안인 경우에만
									visitList.push(tempPos);
								}
							}


						} while (!visitList.empty());

						for (size_t i = 0; i < mapSize.height; ++i) {
							delete[] visit;
						}
						delete[] visit;

					}

					y = mapSize.height;
					break;
				}


			}
		}
	}


	InvalidateRect(Window(), nullptr, false);

	if (map->isClear()) {
		MessageBoxW(Window(), L"지뢰찾기 성공!", L"지뢰찾기", 0 | MB_ICONINFORMATION);
		SendMessage(Window(), WM_CLOSE, 0, 0);
	}

	return 0;

 

 

 여기에서 중요한 부분이라 생각하는 곳은 다음이다.

						do {
							curPos = visitList.top();
							visitList.pop();

							if (visit[curPos.y][curPos.x]) continue;	// 이미 지나간 길은 패스
							bTemp = map->GetBlock(curPos.x, curPos.y);

							visit[curPos.y][curPos.x] = true;
							if(bTemp->GetState() != Mine::Block::BLOCK_STATE::PICKED)
								bTemp->SetState(Mine::Block::BLOCK_STATE::OPENED);

							if (bTemp->GetAroundBomb()) continue;	// 주변에 폭탄이 있으면 패스

							for (int i = 0; i < 8; ++i) {
								tempPos.x = curPos.x + offset[i].x;
								tempPos.y = curPos.y + offset[i].y;
								if (map->isAvailable(tempPos.x, tempPos.y) && bTemp->GetState() != Mine::Block::BLOCK_STATE::PICKED) { // 범위 안인 경우에만
									visitList.push(tempPos);
								}
							}


						} while (!visitList.empty());

 인접한 지역중 aroundBombCount가 0인 지역을 모두 확장시키는 코드인데 이 알고리즘을 BFS라 해야할지 백트래킹이라 해야할지 잘 모르겠다.

 

 

OnRButtonDown

 WM_RBUTTONDOWN 이벤트가 발생했을 때, 클릭한 블럭의 상태가 CLOSED라면 PICKED로 PICKED라면 CLOSED로 수정만 하면 된다.

 

 또한, 상태가 변경되므로 지뢰를 모두 찾았는지에 대한 판별도 진행해야한다.

 

 

OnPaint

 그리기 정보를 위한 구조는 Draw.h/Draw.cpp에 포함되어 있다.

  • BLOCK_STATE::CLOSED
  • BLOCK_STATE::OPENED
  • BLOCK_STATE::PICKED

 총 세 가지의 경우가 존재하며, OPENED외에 CLOSED와 PICKED는 마우스 hover시 안내를 위해 추가로 분기를 내주어야 한다.

 상태가 OPENED인 경우에는 주변 지뢰의 수에 맞추어 색상 조절도 해주면 좋을 것이다.

선택 난이도에 따라 색상이 달라진다.
코드 일부분

 


 

 전체 소스코드의 경우, 리팩토링을 거치면서 난이도 선택을 할 수 있는 윈도우를 추가 생성했고 이에 따라 게임용 윈도우도 난이도 선택을 할 수 있는 윈도우에 영향을 받도록 구성이 되어있기 때문에 지금 당장은 올리지 않는다.

 

# index

728x90
반응형

'DEV > Direct2D' 카테고리의 다른 글

지뢰찾기 - 맵 데이터  (0) 2021.07.03
지뢰찾기 - 그리기 툴  (0) 2021.07.03
지뢰찾기  (0) 2021.06.28
물리엔진 - 벽 충돌 part2. 좌우 벽 구현  (0) 2021.05.14
물리엔진 - 좌우 이동  (0) 2021.05.14
Comments