본문 바로가기

C언어/테트리스

[C언어] 초보자도 쉽게 구현한 테트리스 게임 만들기 ep.3 보드에 블럭 넣기

안녕하세요!!

2021.09.11 - [C언어/프로젝트] - [C언어] 초보자도 구현할 수 있는 테트리스 게임 만드는법 ep.2 커서다루기, 블럭 응용 표현하기

 

[C언어] 초보자도 구현할 수 있는 테트리스 게임 만드는법 ep.2 커서다루기, 블럭 응용 표현하기

안녕하세요^^ 두 번째 글이네요. 이번에는 테트리스를 구현하기 위해서 반드시 알아야할 지식들을 소개하겠습니다. 아래의 커서관런, 키보드관련,난수생성 에 대한 함수는 반드시 알아야 합니

dev-with-precious-dreams.tistory.com

https://github.com/SHcommit/Tetris-game-in-C-lang

(위 링크는 제가 구현한 테트리스 소스파일입니다.)

 

저번 글에

블럭 선언 및 콘솔창에 응용해서 특정한 블럭을 출력하는법과

tetris의 보드에 대해서 선언을 했었는데요.

이번글에는 

4.어떻게 tetris 보드에 블럭을 출력하는가?

그리고

tetris 보드에 블럭을 추가하는가?

를 구현하는 방법을 알려드리고자합니다.

 

혹시 아직 방법을 모르신다면,

제 블로그를 잠시 끄고

혼자서

생각해보시기 바랍니다.

 

저 또한 많은 좌절과, 고민의 연속적인, 여러 시행착오를 통해서 

결국

2차원 배열인 보드 안에

4차원 배열의 특정한 모양의 블럭을

넣는 방법을 알게 되었습니다.

 

이 방법을 알게 된다면 거의 테트리스 구현의 95퍼센트 정도의 지식을

"나 자신이 알고있다"

라고 단정지어 말할수 있습니다.

테트리스 게임을 구성하는 대부분의 함수들이

정말 도톨이 키 재는 것과 같이

위의 구현 방식

너무 비슷하기 때문이죠.

 

저의 고민은 다양했습니다.

1. 보드의 테두리를 printf로 생성한다.

2. 블럭이 ==2일 때 printf로 생성한다.

->이경우는 블럭이 ==2일때 printf로 출력만하는것입니다. 그렇기에 좌표값이 없어서 

ㅁㅁㅁㅁ와같은 출력형태가 될 것이다...등등

 

어떻게하면

보드에 4차원 배열의 특정한 블럭의 정보를 집어넣을수 있을까?

 createBoards(), printBoards() 의 구현을 모르신다면

이전 ep.2를 참고해주세요

변수값, 함수 정의 등이 있습니다.

2021.09.11 - [C언어/프로젝트] - [C언어] 초보자도 구현할 수 있는 테트리스 게임 만드는법 ep.2 커서다루기, 블럭 응용 표현하기

 

(저희 경험단을 예로..)

우선 

 

지금까지 내가 만든것은 

createBoards()메서드를 통해 boards[][]에 엣지부분 벽=1 ,나머지 빈 공간=0

의 초기화를 할 것인데,,

배열이라서 createBoards()함수를 사용하면 주소값에 값을 집어 넣어서 

새로 초기화된 값들이 블럭에 저장되어있겠지?

 

그렇다면 블록의 정보를 

출력하는 함수printBoards()를 통해 

while문 안에서

boards[][]에 저장된 값들을 계속 출력할 거야.

 

그런데 내가 GotoXY함수를 통해서 

그냥 보드 안에 빈 공간 중 특정한 좌표를 정해서

특정한 좌표.X + x*2~x*8까지,

특정한 좌표.Y + y~y+4까지 이렇게 

4차원에서의 특정한 블럭 한개를 출력하기 위해서

GotoXY함수를 사용해서 출력을 해야지~

4-1. 블럭위에 테트리스 출력하는 방법

createBoards();
printBoards();
for(int y=0;y<4;y++){
	for(int x=0;x<4;x++){
    	if(Blocks[5][2][y][x]==2){ //블럭안에있는 빈 공간 중 8,5의 특정한 좌표를 기준으로
    		GotoXY(8+x*2,5+y);
            printf("■");	//블럭을 출력할거야!
        }
    }
}

좋아 성공이다..

배열에도 특정한 위치에 '2'로 선언이 되어있겠지?

System("cls")함수는 그냥 콘솔창의 화면을 지우는 함수니까

그함수를 사용해서 

Boards 안에 정보를 

확인해볼까?

system("cls");
	for (int y = 0; y < Board_Height; y++) {//보드 높이==22
		for (int x = 0; x < Board_Width; x++) { //보드 길이==14
			printf("%d", boards[y][x]);
		}
	printf("\n");
	}
	return 0;
}

중간에 내가 출력한 블럭의 값인 2가 없는거야???!?!!

왜일까요? 

....

 

그저

배열의 특정한 위치에 특장한 좌표에 대한 블럭의 값을 저장하지 않고

GotoXY함수 만을 사용해서 블럭을 특정한 좌표에 출력했었기 때문입니다.

(아니 저기요!! 4차원 배열의 정보를 2차원 보드에 어떻게 채웁니까!!!)

 

네?

 

4차원 배열로 저장된 블럭을 동시에 여러개 출력하는 것이 아니라 

딱 1개의 모양과 특정회전상황의 블럭을 출력하는 것이기에

 

우리는 

해당 블럭의 출력하고싶은 모양과,

그모양의 특정 회전 경우 딱 한가지를 알게 되면,

그 함수를 출력할 수 있다는 것을 알고 계시죠?

(모른다면 위에서 보드에 블록 출력한 내용참고 또는 ep.2의 블록 다루기편 다시 살펴보세요 !!)

 

네 그렇습니다. 

 

<약간의 복습>

0~6개로, 총 7개의 블록 모양중에 

한개의 모양과

ex)모양= 5

그 한개의 모양에서( 0~3개), 0도,90도,180도,270도 회전 경우 4가지 중

한개의 경우를 알게 된다면!!

ex)회전= 2

Blocks[5][2][][] 

나머지 4*4 형태의 배열형태가 되는 것입니다. 즉

2차원 배열이라는 것입니다.

(ep2에서 더 자세한 설명 있습니다.)

(블럭은 4차원배열이 아니라 특히 한정적인 (모양, 회전 알고있는상태) 상태이기 때문에)

4번의 중첩된 포문(4차원)을 사용하지 않고,

2번의 중첩된 포문(2차원)을 사용해서

boards와 Blocks둘 다 건드릴수 있다는 얘기입니다.

 

4-2. tetris 보드에 블럭의 특정 위치에 대한 정보를 저장하기.

( 글쓰니 : 일단 테트리스 게임하면 블럭이 맨 위에서 내려 오니까 

보드의 x축에서 정 중앙! + 보드 맨 위의 벽(1) 한칸 아래에서 부터

블록을 출력해봐야겠어..)

그런데 블럭은 4 칸 * 4칸을 차지하고,

보드의 길이는 14칸이니까 (14-4 )/ 2 = 5!!!

다섯칸 뒤에 여섯번째칸부터가 정 중앙이구나?

좋아 정중앙 의 시작 지점은 좌표상에서 (14, 4)에 위치하는구나?

(x좌표 2칸이어야 1개의 문자가 출력됩니다. 모르신다면 ep.2 정독..)

거기서부터 4칸* 4칸이면 딱 위에서 시작해서 내려가기 좋은 자리군..

 

그런데 문제는 어떻게 블럭의 정보를 보드에 저장하지?

 여기가 가장 중요합니다!!!!

 

※중요

예전에 높이 22,길이 14인 2차원 배열에 1부터 값을 증가시킨 값을 어떻게 넣었더라?

int i = 0;
	for (int y = 0; y < Board_Height; y++) {
		for (int x = 0; x < Board_Width; x++) {
			boards[y][x] = i++;
		}
	}

그렇다면 특정한 부분의 값은  해당 y, x index를 알기만 하면

그 특정한 부분의 값만 변경할 수 있었지?


만약에 예를들어 

int Array[4][7] = {
		1,1,1,1,1,1,1,
		1,0,0,0,0,8,1,
		1,0,0,0,0,0,1,
		1,1,1,1,1,1,1
	};

int Array[][] 배열이 저렇게 선언되어있고, 아래 사진처럼 

특정 위치의 index값인 8을 구하려면 

printf("%d",Array[1][5]);을 하면 

쉽게 값을 알 수 있었지.

좌표를 이용해서 구했을때는??

콘솔창의 맨 왼쪽 위에는 0,0이니까 0.0을 기준으로 

 구하면?

좀 많이 어려우실 수있습니다.

다만 한가지 확실하게 기억해야할 것은

x축 2칸이어야 1개의 문자로 표현될 수 있다!

 

 

예를들어

드래그 한칸했을때 x=1

드래그 2칸했을때 x =2

드래그 3번 했을 때x=3

드래그 4번을 해야 가, 나가 포함이 될 것입니다. 

예시가 너무 길어졌군요...

다시 본론으로.

 

위에 사진에서 노란색 에 해당하는 곳의 좌표값(14,4)으로 보드에서 (왼쪽 벽에부터 6칸, 위쪽 벽에서 1칸 아래)인 index는 좌표값으로 어떻게 구할 수 있을까??

 

그 위에 예제,, 사진에서의 8 구할때 처럼 0,0을 기준으로하면 편합니다.

boards의 시작점이 0,0에서 출력된다고 가정하면,

구하기 엄청 편한데,

만약 그렇게 가정한다면,

14,4의 좌표에서 4,3을 빼주어야 합니다.

0,0이 보드의 시작이라 가정한다면

=(10,1)이됩니다. 여기서 

x좌표는 2칸이어야 1개의 문자를 출력할수 있기에

형광칠한 네모의 왼쪽 가장 위의 index는 

10/5를 한 값,

5입니다!!!!!!

y값은 1이고, 따라서 해당 네모의 첫번째 index는 boards[1][5]가 되는 것입니다.
(** 아니 6칸이라면서요 왜 5인가요? y는 그렇다치고;;)

어라?

아니죠!!

index는 0부터시작합니다. 0,1,2,3,4,5 = 6번째 칸이되는 것입니다.

 이제 좌표값으로 배열의 특정 index값 구하는 법을 아셨나요?

이게..제일 중요합니다. 

이해 하지 못하시는분들은 죄송합니다. 제가 설명을 못해서..하지만 대충 감은 잡으셨으리라 생각합니다.

 

그렇다면 이제 1개의 좌표값, 14,4를 통해서 형광팬 칠해져있는 네모의 칸에 특정 index를 구하는것은 어떻게하면 되는 것일까요?

특정 index의 시작좌표값을 기준으로

1. (특정index)시작 좌표값.X 에서 -BoardX(4) , (특정index)시작 좌표값.Y 에서 -BoardY(3)을 해

(0,0)에서 시작한다 가정하기.

2.  (0,0)에서 특정 index의 좌표값 (((특정index)시작좌표값.X -BoardX(4))/2 , (특정index)시작좌표값-BoardY(3)).

ex) 그림에서 블록이 시작되는 (14,4)가 특정 좌표값이면,

1. 14-BoardX , 4-BoardY = (10,1)

2. (10 /2 , 1) => index ROW(세로로는) =1  , index COL(가로로는) 5에 위치한다는 것입니다.

그렇다면 boards[1][5]를 기준으로 

4*4의 배열인 특정 모양의 Blocks[5][2][][]의 정보를 

boards[1][5]의 index부터 가로 4번, 세로 4번의 포문을 반복한

 

boards[1+y][5+x]

for (int y = 0; y < 4; y++) {
	for (int x = 0; x < 4; x++) {
		boards[1+y][5+x] = Blocks[5][2][y][x];
	}
}

위와 같은 연산을 

거치게 되면 

보드에 블럭의 정보를 저장하는 것이 됩니다!!!!!!!!!!!!!!!!!!!!!!!!!!!


(얼마나 열시밓 고된 노력으로 알아낸 노하우인데.ㅠ)

 

그럼 이제 다시 boards의 정보를 출력하면 되겠죠?

createBoards();
	for (int y = 0; y < 4; y++) {
		for (int x = 0; x < 4; x++) {
			boards[1 + y][5 + x] = Blocks[5][2][y][x];
		}
	}
	for (int y = 0; y < Board_Height; y++) {
		for (int x = 0; x < Board_Width; x++) {
			if (boards[y][x] == 1)
				printf("■");
			else if (boards[y][x] == 2) // 배열에 저장된 블럭일 경우!
				printf("□");
			else //0인 경우
				printf("  "); //2칸을 띄어야 한다 
		}
		printf("\n");
	}

여기서 0일 경우 2칸을 띄워야하는 이유가 우리가 출력하는 문자는 x축 2개당 1개의 문자를 나타내므로 

만약 printf(" "); 한개를 출력하도록 한다면?

 

그림이 망가지게 됩니다.

.그래서 문자를 출력하기 위해 2칸을 띄우는 것입니다.

 

하지만 이것을 매번 해당 좌표 - boardsX , 좌표.Y -boardsY를 하고 /2 해서 for문으로 보드의 배열을 출력하면 

좀 오래걸릴 것 입니다. 

따라서 

 

저는 boards에 배열을 저장하기는 하지만 boards를 통해 출력을 하지 않습니다.

저는 또한 

for (int y = 0; y < 4; y++) {
		for (int x = 0; x < 4; x++) {
			boards[1 + y][5 + x] = Blocks[5][2][y][x];
		}
	}

4차원배열의 블럭을 2차원 배열의 보드에 저장할 때도 

위와 같은 방식을 사용하지 않습니다. 

여러분의 편의를 위해 전부 저장했던 것이죠.

 

절약하기 위해서

for (int y = 0; y < 4; y++) {
		for (int x = 0; x < 4; x++) {
        if(Blocks[5][2][y][x] ==2)
			boards[1 + y][5 + x] = 2;
		}
	}

이렇게 만약 블록에 0이 아니라 2의 index가 된다면? 그때의 boards에 값을 대입하겠다!

이렇게하게된다면 

위의 연산은 최소 16번인데,

아래의 연산은 최소 4번만 하게되는 것입니다.

(컴퓨터야 따랑해..)

 

또한 

boards를 출력하는 것 보다 blocks의 정보를 boards에 대입할 때

의 해당 위치의 첫 index는 이미 (14,4)로 알고 있으니 

이를 이용해서 

GotoXY(14+x*2,4+y);

printf("□"); 를 해줄 것입니다.

for (int y = 0; y < 4; y++) {
     for (int x = 0; x < 4; x++) {
        if(Blocks[5][2][y][x] ==2){
			boards[1 + y][5 + x] = 2;
            GotoXY(14+x*2,4+y);
            printf("□");
         }
     }
}

 

 

이렇게 오늘 보드에 블럭의 정보를 좌표에따라 저장을 했는데요.

이 방법을 사용하여,

블럭이 1칸 내려가면

y좌표값이 1 증가할 때 블록의 현재 위치가 (14, 4 +1)이 될 때의 

 

블록을 구현하는 방법과 올바르게 구현하지 않을 시 생기는 오류에 대해서

다음 글에서 알려드리겠습니다.