SetBlockOffset 함수로 Switch-Case 형태로 정한다.
// BlockType 에 따라 dx, dy 포인터를 결정하는 함수
void SetBlockOffsets(BlockType type, int rotation, int*& dx, int*& dy) {
	switch (type) {
	case BlockType::BlockI:
		dx = BlockI::dx[rotation];
		dy = BlockI::dy[rotation];
		break;
	case BlockType::BlockO:
		dx = BlockO::dx[rotation];
		dy = BlockO::dy[rotation];
		break;
	case BlockType::BlockT:
		dx = BlockT::dx[rotation];
		dy = BlockT::dy[rotation];
		break;
	case BlockType::BlockS:
		dx = BlockS::dx[rotation];
		dy = BlockS::dy[rotation];
		break;
	case BlockType::BlockZ:
		dx = BlockZ::dx[rotation];
		dy = BlockZ::dy[rotation];
		break;
	case BlockType::BlockJ:
		dx = BlockJ::dx[rotation];
		dy = BlockJ::dy[rotation];
		break;
	case BlockType::BlockL:
		dx = BlockL::dx[rotation];
		dy = BlockL::dy[rotation];
		break;
	}
}
위의 함수를 만들었으므로 PutBlock과 GravityBlock도 일반화가 가능하다.
void PutBlock(BlockType type, int startX, int startY, int rotation) {
	int* dx;
	int* dy;
	SetBlockOffsets(type, rotation, dx, dy);
	// 유효성 검사
	for (int i = 0; i < 4; i++) {
		int x = startX + dx[i];
		int y = startY + dy[i];
		if (!CheckBoundary(y, x)) {
			cerr << "Block이 유효범위를 벗어났음.\\n";
			exit(EXIT_FAILURE);
		}
	}
	// 블록 배치
	for (int i = 0; i < 4; i++) {
		int x = startX + dx[i];
		int y = startY + dy[i];
		grid[y][x] = 1;
	}
}
// 일반화된 GravityBlock
void GravityBlock(BlockType type, int rotation, int& startX, int& startY) {
	bool moveFlag = true;
	vector<pair<int, int>> nextBlocks;
	vector<pair<int, int>> curBlocks;
	map<pair<int, int>, bool> treeMap;
	// 포인터 = 배열
	int* dx = nullptr;
	int* dy = nullptr;
	int blockSize = 4;
	// BlockType 에 맞는 dx, dy 배열을 설정
	SetBlockOffsets(type, rotation, dx, dy);
	// 유효성 검사
	for (int i = 0; i < 4; i++) {
		int x = startX + dx[i];
		int y = startY + dy[i];
		if (!CheckBoundary(y, x)) {
			cerr << "Block이 유효범위를 벗어났음.\\n";
			exit(EXIT_FAILURE);
		}
	}
	// 먼저 현재블록들 위치를 treeMap에 true로 두고
	// 현재 블록들 위치를 vector 형태로 저장한다.
	for (int i = 0; i < blockSize; i++) {
		int curX = startX + dx[i];
		int curY = startY + dy[i];
		pair<int, int> curPosPair = make_pair(curY, curX);
		curBlocks.push_back({ curY, curX });
		treeMap[curPosPair] = true;
	}
	// 현재 블록들을 탐색하며, 다음 위치에 놓을 수 있는지를 판단한다.
	// 다음블록 판단시, 현재 블록 위치는 무시한다.
	for (auto& curBlock : curBlocks) {
		int curY = curBlock.first;
		int curX = curBlock.second;
		// y+1 한 게 nextY 이다.
		int nextY = curY + 1;
		int nextX = curX;
		// 경계검사를 한다.
		if (!CheckBoundary(nextY, nextX)) {
			moveFlag = false;
			return;
		}
		// 이미 nextY, nextX에 블록이 있는지 검사한다.
		// 자기자신인지도 체크한다.
		pair<int, int> nextPosPair = make_pair(nextY, nextX);
		if (treeMap.find(nextPosPair) == treeMap.end() && grid[nextY][nextX] == 1) {
			return;
		}
		nextBlocks.push_back({ nextY, nextX });
	}
	if (moveFlag) {
		for (auto& curBlock : curBlocks) {
			grid[curBlock.first][curBlock.second] = 0;
		}
		// 다음 블록 방문 처리
		for (auto& nextBlock : nextBlocks) {
			grid[nextBlock.first][nextBlock.second] = 1;
		}
	}
	// 이동처리를 해준다. & 이기에 변환된다.
	startY++;
}
초기 블록 놓기 + 중력적용

이제 좌우이동도 일반화가 가능하다.
- 코드가 계속 반복되므로 복사-붙여넣기 하면 된다.
- 범위유효성검사 → 이동 가능한지 검사 (자기자신블록은 판단에서 제외) → 실제이동