본문 바로가기

뭐라도 만들어보자!!( 프로젝트 )/C, C++ 프로젝트

1-4. C/C++ Console 테트리스를 만들어보자!!(Class : TetrisBlock)

반응형

Class TetrisBlock에 대해서 설명하고자 합니다.

 

말그대로 우리가 알고있는 4 개의 1 칸 블록으로 구성된 테트리스 블록의 움직임과 충돌상태 등을 관리하는 클래스입니다. 이번 글은 이전 포스팅의 Block 클래스를 참고하시면서 보면 좋습니다.(특히 Block 클래스 소스 코드를 보면서 오시는 걸 추천드려요!!)

 

//TetrisBlock 클래스입니다.
class TetrisBlock {
private:
	Block componentBlock[4];
	int tetrisBlockType;
	int tetrisBlockRotationNum;
	ableToMove tetrisBlockNowMovableState;
	dotPos bringBackNowPos[4];
	dotPos bringBackPrevPos[4];
public:
	TetrisBlock(int posX = TETRISBLOCKGENERATEPOINT_X,
		int posY = TETRISBLOCKGENERATEPOINT_Y, int blockType = 0) {
		for (int i = 0; i < 4; i++) {
			componentBlock[i] = Block(posX + BLOCKTYPE[blockType][0][i].posX,
				posY + BLOCKTYPE[blockType][0][i].posY);
		}
		tetrisBlockType = blockType;
		tetrisBlockRotationNum = 0;
		tetrisBlockNowMovableState = { TRUE, TRUE, TRUE };
	}
	void tetrisBlockRotate(std::list<dotPos>& stackedBlock);
	void tetrisBlockPrintOut(int blockColor);
	void tetrisBlockDefaultFall(std::list<dotPos>& stackedBlock);
	void tetrisBlockPlayerControllLeft(std::list<dotPos>& stackedBlock);
	void tetrisBlockPlayerControllRight(std::list<dotPos>& stackedBlock);
	void tetrisBlockPlayerControllDown(std::list<dotPos>& stackedBlock);
	void tetrisBlockCollisionLeftCheck(std::list<dotPos>& stackedBlock);
	void tetrisBlockCollisionRightCheck(std::list<dotPos>& stackedBlock);
	void tetrisBlockCollisionDownCheck(std::list<dotPos>& stackedBlock);
	int getTetrisBlockType();
	dotPos getTetrisBlockNowState(int);
	dotPos getTetrisBlockPrevState(int);
};

 

다음은 각 메서드들에 대한 설명입니다.

 

  1. void tetrisBlockPrintOut(int blockColor) : 게임 화면상 테트리스 블록 출력
  2. void tetrisBlockDefaultFall(std::list& stackedBlock) : 테트리스 블록의 낙하
  3. void tetrisBlockRotate(std::list& stackedBlock) : 조작에 따른 테트리스 블록의 회전
  4. void tetrisBlockPlayerControllLeft(std::list& stackedBlock) : 사용자의 조작에 따른 테트리스 블록 이동가능 여부 판단 및 이동(왼쪽)
  5. void tetrisBlockPlayerControllRight(std::list& stackedBlock) : 사용자의 조작에 따른 테트리스 블록 이동가능 여부 판단 및 이동(오른쪽)
  6. void tetrisBlockPlayerControllDown(std::list& stackedBlock) : 사용자의 조작에 따른 테트리스 블록 이동가능 여부 판단 및 이동(아래쪽)
  7. void tetrisBlockCollisionLeftCheck(std::list& stackedBlock) : 테트리스 블록의 충돌(왼쪽)의 감지
  8. void tetrisBlockCollisionRightCheck(std::list& stackedBlock) : 테트리스 블록의 충돌(오른쪽)의 감지
  9. void tetrisBlockCollisionDownCheck(std::list& stackedBlock) : 테트리스 블록의 충돌(낙하)의 감지
  10. int getTetrisBlockType() : 테트리스 블록의 종류 결정
  11. dotPos getTetrisBlockNowState(int) : 이동 후 테트리스 블록의 위치 반환
  12. dotPos getTetrisBlockPrevState(int) : 이동 전 테트리스 블록의 위치 반환

이전의 Block 클래스와는 다르게 완전히 출력까지 담당하는 것을 알 수 있습니다. 다루어야 하는 블록이 4 개인 것을 제외하면 구조 자체는 Block 클래스와 유사하다는 것을 알 수 있습니다. 물론 몇 가지 추가된 사항들과 달라진 점들이 있습니다.  

 

우선 회전 기능을 추가하였습니다. 사실 회전이라기 보다는 블록의 이동처럼 회전 전 상태의 블록을 지우고 회전한 상태의 블록을 재출력하는 방식으로 구현하였습니다. 그러기 위해서는 회전 상태에서 충돌 영역과 겹치는 부분이 있는 지 확인을 해야 합니다. 다음은 회전에 대해 구현해놓은 메서드입니다.

 

//회전에 대한 메서드입니다.
void TetrisBlock::tetrisBlockRotate(std::list<dotPos>& stackedBlock) {
	dotPos buffer[4];
	bool ableToRotate = true;
	bool rotate = GetAsyncKeyState(VK_SPACE);
	if (rotate) {
		for (int i = 0; i < 4; i++) {
			buffer[i] = componentBlock[i].getBlockNowState();
			buffer[i].posX = buffer[i].posX - BLOCKTYPE[tetrisBlockType][tetrisBlockRotationNum][i].posX
				+ BLOCKTYPE[tetrisBlockType][(tetrisBlockRotationNum + 1) % 4][i].posX;
			buffer[i].posY = buffer[i].posY - BLOCKTYPE[tetrisBlockType][tetrisBlockRotationNum][i].posY
				+ BLOCKTYPE[tetrisBlockType][(tetrisBlockRotationNum + 1) % 4][i].posY;
		}

		for (list<dotPos>::iterator iter = stackedBlock.begin(); iter != stackedBlock.end(); iter++) {
			for (int i = 0; i < 4; i++) {
				if (buffer[i].posX == iter->posX) {
					if (buffer[i].posY == iter->posY) {
						ableToRotate = false;
						break;
					}
				}
				if (ableToRotate == false)
					break;
				ableToRotate = true;
			}
		}
	}
	if (rotate && ableToRotate) {
		tetrisBlockRotationNum = (tetrisBlockRotationNum + 1) % 4;
		for (int i = 0; i < 4; i++) {
			componentBlock[i].updateBlockPrevPos(componentBlock[i].getBlockNowState().posX,
				componentBlock[i].getBlockNowState().posY);
			componentBlock[i].updateBlockPos(buffer[i].posX, buffer[i].posY);
		}
	}
}

 

첫 번 째 for loop에서 회전한 후의 상태를 임시로 저장합니다. 아직은 회전이 가능한지를 모르기 때문입니다.

두 번째 for loop에서 buffer에 저장된 값 중 하나라도 충돌 경계와 겹치는 지 확인을 합니다. 당연히 하나의 블록이라도 겹친다면 회전은 불가능하고 그대로 함수가 끝이 납니다.

만약 모든 블록이 충돌경계와 겹치지 않는다면 ableTorRotate라는 변수에 true값이 선언되고  현재 상태에 변화된 값을, 이전 상태에 변화 전의 값을 업데이트해주는 것으로 메서드가 끝이 납니다. 

 

다음은 충돌 상태에 대한 체크에 대한 예시입니다.

 

//왼쪽 충돌에 대한 체크
void TetrisBlock::tetrisBlockCollisionLeftCheck(std::list<dotPos>& stackedBlock) {
	for (int i = 0; i < 4; i++) {
		bool buffer = componentBlock[i].blockLeftCollisionCheck(stackedBlock);
		componentBlock[i].updateNowMovableStateLeft(buffer);
	}

	for (int i = 0; i < 4; i++) {
		if (componentBlock[i].getNowMovableState().ableToMoveLeft == false) {
			tetrisBlockNowMovableState.ableToMoveLeft = false;
			break;
		}
		tetrisBlockNowMovableState.ableToMoveLeft = true;
	}

	for (int i = 0; i < 4; i++) {
		componentBlock[i].updateNowMovableStateLeft(tetrisBlockNowMovableState.ableToMoveLeft);
	}
}

 

 

첫 번 째 for loop는 각 구성 블록의 충돌 여부를 판단합니다.

두 번 째 for loop에서는 블록들 중 하나라도 충돌이 되는 경우 테트리스 블록은 움직이지 못한다는 것을 tetrisBlockMovableState에 업데이트 시켜 줍니다. 

마지막으로 각 componentBlock의 movableState.ableToMoveleft를 업데이트 해줍니다. 

 

이런 과정을 거치는 이유는 기존의 한 칸 블록을 다루었던 것과는 다르게 4개의 블록을 하나의 블록처럼 움직이게 하기 위해서 입니다. 오른쪽 방향 충돌과 아랫쪽 방향 충돌에 대한 로직도 이와 같습니다. 다만 아랫쪽 방향 충돌 시에는 블록의 상태가 완전히 확정되고, 다른 방향으로도 움직일 수 없다는 점을 고려하여 코드가 조금 다릅니다.

 

//아래 방향 충돌 체크
void TetrisBlock::tetrisBlockCollisionDownCheck(std::list<dotPos>& stackedBlock) {
	for (int i = 0; i < 4; i++) {
		bool buffer = componentBlock[i].blockDownCollisionCheck(stackedBlock);
		componentBlock[i].updateNowMovableStateDown(buffer);
	}

	for (int i = 0; i < 4; i++) {
		if (componentBlock[i].getNowMovableState().ableToFall == false) {
			tetrisBlockNowMovableState.ableToFall = false;
			tetrisBlockNowMovableState.ableToMoveLeft = false;
			tetrisBlockNowMovableState.ableToMoveRight = false;
			break;
		}
		tetrisBlockNowMovableState.ableToFall = true;
	}

	if (tetrisBlockNowMovableState.ableToFall == false) {
		for (int i = 0; i < 4; i++) {
			componentBlock[i].updateNowMovableStateDown(tetrisBlockNowMovableState.ableToFall);
			componentBlock[i].updateNowMovableStateLeft(tetrisBlockNowMovableState.ableToMoveLeft);
			componentBlock[i].updateNowMovableStateRight(tetrisBlockNowMovableState.ableToMoveRight);
			componentBlock[i].updateWhetherStacked(true);
		}
	}
	else {
		for (int i = 0; i < 4; i++) {
			componentBlock[i].updateNowMovableStateRight(tetrisBlockNowMovableState.ableToMoveRight);
		}
	}
}

 

테트리스 블록 이동도 그렇기 다르지 않습니다. 우선 적으로 아랫방향과 왼쪽 방향으로의 충돌여부를 판단한 후 이동이 가능하다면 전 블록에 대한 위치 정보를 현재의 블록 위치 정보로 업데이트하고 왼쪽으로 이동한 블록의 위치 정보를 업데이트합니다.

 

//왼쪽 이동 관련
void TetrisBlock::tetrisBlockPlayerControllLeft(std::list<dotPos>& stackedBlock) {
	tetrisBlockCollisionDownCheck(stackedBlock);
	if (tetrisBlockNowMovableState.ableToFall == true)
		tetrisBlockCollisionLeftCheck(stackedBlock);
	bool whetherLeft = GetAsyncKeyState(VK_LEFT);
	for (int i = 0; i < 4; i++)
		componentBlock[i].blockPlayerControllLeft(whetherLeft);
}

 

그 후 실제 블록을 화면에 출력합니다.

 

//출력(printOutBlock, printOutBlank는 정의되어 있습니다!!)
void TetrisBlock::tetrisBlockPrintOut(int blockColor) {
	for (int i = 0; i < 4; i++) {
		printOutBlank(getTetrisBlockPrevState(i).posX, getTetrisBlockPrevState(i).posY);
	}
	for (int i = 0; i < 4; i++) {
		printOutBlock(getTetrisBlockNowState(i).posX, getTetrisBlockNowState(i).posY, blockColor);
	}
}

 

TetrisBlock 클래스에 대한 설명을 마쳤습니다. 다음에는 StackedBlockState 클래스에 대해서 설명하겠습니다.

반응형