테트리스 개발 3일차

나묘쿠·2022년 1월 6일
0

테트리스

목록 보기
3/8
post-thumbnail

이번 편은 정리하는 편이 될 것이다. 개발 진행은 좌, 우로 이동과 회전할 때에 벽에 겹치지 않게 하는 부분에서 막혔다. 코드 자체는 작동하지만 완벽하게 문제를 해결하고 넘어가고 싶었기 때문에 일주일간 작성한 코드를 보고 또 보고 검토하고 또 검토한 결과 잘못 생각한 부분이 너무 많다는 결론이 나왔다. 일단 찾은 문제점들을 보자면

1.게임 화면 좌표의 애매함
2.블록의 회전된 모양 패턴의 무용
3.블록의 회전축 좌표 문제
4.블록 회전시 경계선과의 간섭 문제
5.최적화 문제

이번 문제들은 벽과의 간섭시 더 이상 진행하지 못 하게 하는 부분을 개발하다 발견한 문제이다.

먼저 1번 문제, 블록이 움직일 수 있는 공간의 시작점을 (10, 10)으로 하여 게임 벽을 (8, 9)부터 생성했는데, 이렇게 개발하다보니 8에서부터 시작되는 것이 직관적이지 않아 코딩중에 계속 실수가 발생했다. 그래서 게임의 벽은 (10, 10), 게임이 이루어지는 공간은 (12, 11)로 시작좌표를 수정하였다. 이로 인해 블록이 처음 등장하는 좌표도 x는 2만큼, y는 1만큼 증가하였다. 블록이 처음부터 모습을 전부 드러내며 나오는 것이 아니기 때문에 이는 후에 다시 수정될 것이다.

2번 문제는 블록을 회전시킬때 for문을 사용하여 좌표 전체를 옮기는 코드를 사용하였기 때문에 처음에 블록 모양을 지정해 줄 때에 모든 모양을 2차원 배열을 사용해 저장해 두었다. 하지만 회전을 계산하는 코드를 작성했으므로 기본 모양 이외의 모양은 필요가 없게 되었다. 이로 인해 블록을 저장했던 4차원 배열은 회전된 모양을 모두 삭제해 3차원 배열로 바꾸고, 활성화 된 블록에 랜덤으로 어떤 블록을 가져오는 코드도

void acran(void)
{
	int i, j;
	int num;
	
	num=rand()%7; //블록 종류가 7개이기 때문에
	
	for(i=0; i<4; i++)
	{
		for(j=0; j<4; j++)
		{
			acblock[i][j]=block[num][0][i][j];
		}
	}
}

에서

void acran(void)
{
	int i, j;
	int num;
	
	num=rand()%7; //블록 종류가 7개이기 때문에
	
	for(i=0; i<4; i++)
	{
		for(j=0; j<4; j++)
		{
			acblock[i][j]=block[num][i][j];
		}
	}
}

로 바뀌었다. 하지만 이도 뿌요뿌요 테트리스가 블록을 5개까지 미리 보여주기 때문에, 추후 게임이 개발되어 가면서 수정될 것이다.

3번 문제는 회전의 중심점 문제이다.
나는 블록이 4 * 4 크기이기 때문에 블록의 정 중앙의 가상의 점을 회전축으로 두고 코드를 짰는데 뿌요뿌요 테트리스는 (1, 2)를 중심으로 회전시킨다. 하지만 여기에 또 예외가 있는데 정사각형 블록은 회전하지 않는다. 그리고 1자형 블록은 회전이 아니라 레버를 올렸다 내렸다 하듯이 두개의 모양이 반복이 된다.
이를 고려하여 다시 코드를 짜 보았다.

void turnL(void)		//좌로 회전 
{
	if(acblock[1][2]==1&&acblock[2][1]==1&&acblock[2][2]==1&&acblock[1][1]==1)	//정사각형일 시 스킵
		return;
		
	int n=0;
	
	rvprintsq(acblock);
	
	n=acblock[2][3];
	acblock[2][3]=acblock[2][1];
	acblock[2][1]=acblock[0][1];
	acblock[0][1]=acblock[0][3];
	acblock[0][3]=n;
	
	n=acblock[1][3];
	acblock[1][3]=acblock[2][2];
	acblock[2][2]=acblock[1][1];
	acblock[1][1]=acblock[0][2];
	acblock[0][2]=n;
	
	n=acblock[1][0];	//일자모양을 위해
	acblock[1][0]=acblock[3][2];
	acblock[3][2]=n;
		
	printsq(acblock);
}

이런식으로 코드를 짜게 되었다. 일자모양을 위한 코드블록은 일자모양이 세로 0번줄과 가로 3번줄을 사용하는 유일한 블록이고, 그 중에서도 (0, 1)과 (2, 3)만 사용하기 때문이다.

4번 문제는 기존에 회전할 때의 코드로 작동시키면 벽과 간섭한다는 것이다. 이는 원래 개발일지 3편에 들어갈 예정이었지만 이 작업이 제일 오래걸려서 이렇게 작성하게 되었다. 모든 시행착오를 이곳에 적어두고 싶지만 자잘한 수정이 너무 많아 최종본과 이에 도달하기까지의 생각의 과정만 적으려 한다.

처음엔 좌, 우로 이동과 회전할 때의 간섭을 모두 한 번에 처리하려고 했다. 하지만 두 과정의 알고리즘이 달랐다.
좌, 우로의 이동은
입력 -> 잔상 제거 -> 간섭 검사 -> 간섭하지 않을시 이동 -> 출력 이고
회전은
입력 -> 잔상 제거 -> 회전 -> 간섭시 닿지 않도록 좌표이동 -> 출력
이다.

비슷해보이지만 순서의 차이 때문에 따로 작성을 해야 했다.

좌우이동의 간섭처리는

int blockcheckmove(void)
{
	int i;
	
	if(acblock[1][0]==1)	//일자모양일 때
	{
		if(x==12)
			return 1;
	}
				
	if(x==10)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][1]==1)
				return 1;
		}
	}
	
	else if(x<10)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][2]==1)
				return 1;
		}
	}
	
	else if(x==24)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][3]==1)
				return 2;	
		}
	}
	
	else if(x>24)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][2]==1)
				return 2;	
		}
	}
	
	else
		return 0;
}

를 작성하여

void moveL(void)		//좌로 이동 
{
	if(blockcheckmove()!=1)
	{
		rvprintsq(acblock);
		x-=2;
		printsq(acblock);
	}
}

처럼 사용하였다. 이때 최적화 문제도 같이 다루기 위해 밑의 두 코드를 비교해보겠다.

void moveR(void)	
{
	rvprintsq(acblock); //잔상제거
	
	if(blockcheckmove()!=2) //간섭x
		x+=2;
		
	printsq(acblock); //출력
}
void moveR(void)	
{
	if(blockcheckmove()!=2)
	{
		rvprintsq(acblock);
		x+=2;
		printsq(acblock);
	}
}

코드1과 코드2는 같은 역할을 한다. 간섭이 되지 않는다고 가정할 때 코드 1은 잔상제거->if->연산->출력, 코드2는 if->잔상제거->연산->출력을 거쳐 다른 점이 없어보인다. 하지만 이때, 간섭이 된다고 가정할 때에 코드1은 잔상제거 -> if -> 출력 이렇게 3단계를 거치지만, 코드2는 if 하고 끝으로, 1단계만 거친다. 이를 통해 코드2가 훨씬 효율적인 코드라고 할 수 있다.

회전할 때의 간섭은

int blockcheckturn(void)		//회전시 간섭체크 
{
	int i;
	
	if(x==10)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][0]==1)
				return 1;
		}
	}
	
	else if(x==8)
	{
		if(acblock[1][0]==1)
			return 3;
	
		for(i=0; i<4; i++)
		{
			if(acblock[i][1]==1)
				return 1;	
		}
	}

	
	else if(x>24)
	{
		for(i=0; i<4; i++)
		{
			if(acblock[i][2]==1)
				return 2;	
		}
	}
	
	else
		return 0;
}

로 작성하여

void turnL(void)		//좌로 회전 
{
	if(acblock[1][2]==1&&acblock[2][1]==1&&acblock[2][2]==1&&acblock[1][1]==1)
		return;
		
	int n=0;
	
	rvprintsq(acblock);
	
	n=acblock[2][3];
	acblock[2][3]=acblock[2][1];
	acblock[2][1]=acblock[0][1];
	acblock[0][1]=acblock[0][3];
	acblock[0][3]=n;
	
	n=acblock[1][3];
	acblock[1][3]=acblock[2][2];
	acblock[2][2]=acblock[1][1];
	acblock[1][1]=acblock[0][2];
	acblock[0][2]=n;
	
	n=acblock[1][0];
	acblock[1][0]=acblock[3][2];
	acblock[3][2]=n;
	
	if(blockcheckturn()==1)
		x+=2;
		
	else if(blockcheckturn()==2)
		x-=2;
		
	else if(blockcheckturn()==3)
		x+=4;
		
	printsq(acblock);
}

로 사용하였다.

이로써 좌측과 우측 벽과의 간섭은 완벽하게 구현되었다.
코드를 깔끔하게 만들고 싶어 일부러 신경을 써 시간을 조금 더 들였다. 하지만 아직도 부족한 것 같아 주석을 달아줄 때 같이 정리해야겠다. 다음엔 블록이 쌓이는 부분을 구현할 생각이다.

profile
사고의 흐름을 따라가 보자.

0개의 댓글