본문 바로가기

C언어/테트리스

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

728x90

안녕하세요^^

두 번째 글이네요.

이번에는 테트리스를 구현하기 위해서 반드시 알아야할 지식들을 소개하겠습니다.

아래의 커서관런, 키보드관련,난수생성 에 대한 함수는 반드시 알아야  합니다!!!!

 

2021.09.11 - [C언어] - [C언어] 초보자도 구현할 수 있는 테트리스 게임 만들기 ep.1 전반적인 개요(with tetris Algorith)

 

 

이 글의 주제는 크게 3가지 입니다. 

1. 기본적으로 알아야할 함수_ 아는 내용이면 pass

2. 블럭 구현과 응용(반드시 읽어주시면 도움됩니다)_아는 내용이면 pass

3. 테트리스 게임 판 생성



기본적으로 알아야 할 커서 관련 func

 

1. 기본적으로 알아야 할 함수

커서관련 <windows.h> GotoXY(int x, int y) CursorView(char show) COORD getCursor()
키보드관련<conio.h> _kbhit()  _getch()
난수생성<stdlib.h>,<time.h> rand()
콘솔창 전체 지우기, 지연
<windows.h>
system("cls") Sleep()
콘솔 출력 색 변경<windows.h> SetConsoleTextAttribute()

C언어로 게임 만듬에 있어 거의 대표적인 함수들입니다.

 


GotoXY(), kbhit(), getch()

 

우선 GotoXY함수를 모르신다면?

2021.09.06 - [C언어] - [C언어] GotoXY()함수로 콘솔 창의 커서를 내 마음대로 이동하기!

Cursorview및, 키보드관련 _kbhit() _getch()함수를 모르신다면?

2021.09.06 - [C언어] - [C언어] _kbhit(),getch()함수를 사용해 콘솔창에서 방향키 사용하는 방법!!!

이 글을 참고해주세요.

몇번 반복적으로 따라서 하면 금방 익히실 수 있는 함수들입니다.

사실 이정도만 알면 거의 95퍼센트 끝냈다 할 수 있습니다.

 

그 외 시작화면에서 , 게임창 진입화면으로 갈 경우

system("cls")라는 함수를 사용했고,

블럭이 내려갈때

내려가는 속도를 Sleep()함수를 이용해 사용할 수 있습니다.

 

블럭들의 색을 입히고 싶다면? 주변 배경을 꾸미고 싶다면?

SetConsoleTextAttribute() 함수를 알아야 합니다.

2021.09.05 - [C언어] - [C언어] 콘솔창에 출력되는 글자색 바꾸는 방법

 

그 외 알아두면 좋을? 함수

몰르셔도 됩니다.

하지만 배경음악까지 나오는 완벽성을 추구하시는 분들을 위해,,

배경음악 관련<mmsystem.h> PlaySound()  

PlaySound()함수는 wav음악에 한해서, 한개의 음악만 작동합니다.

추가적으로 mp3를 인식하고 여러개의 음악을 작동시키는 방법에 대해 공부중입니다.

(추후 추가하겠습니다...)

 

게임을 만들기에 앞서, 

공부하셔야 할 것은 위의 함수들입니다. 

콘솔창 내에서 좌표를 다루는 함수!!GotoXY() 거의 대부분 사용됩니다. 

이해하면 그만큼 너무 쉽지만 또 너무 많이 사용되서

귀찮은 ,,함수입니다.

 그리고 현재 좌표가 어디에 위치하는지 파악해주는 함수

COORD getCursor() 또한 중요합니다. 

언제쓰이냐면,

블럭이 한 칸내려갔을 때

다시 블럭들을 출력하기 위해서는 현재 내려간 블럭의 위치를 파악하기 위해서입니다. 

(무슨소리지?)

..

겁먹지 마세요,,

저 함수를 사용하게 될 때

알려드리겠습니다.

 


키보드 이벤트 받아주는 hbhit(), getch()

 

그리고 블럭의 회전! 블럭을 좌, 우 또는 급강하 하기위해서 

키보드의 방향키와 스페이스바를 인식하기 위해서는 

is KeyPressed()에 관한함수

뭐 입력함? _kbhit()와 그 입력한게 뭔데? _getch() 이 두 함수

숙지하셔야합니다. (모르시겠다면 위에서 링크 클릭하세요 금방 아실겁니다.)

 

또한 Sleep()함수를 응용시켜서 , 블럭이 한 칸 씩 내려간 것 처럼 보이게 해주는 함수!또한 참고하셔야합니다.

Sleep함수를 안쓰면 컴퓨터의 계산처리과정은 넘나빠르기때문에 순식간에 훅하고 블럭이 내려갑니다.

다른 방법으로는 time.h에서의 clock을 이용한 것인데 ,,

그방법 또한 좋습니다 NOT BAD!

하지만 편리성을 위해 Sleep을,,

 

(개인적인 생각으로

이 함수들만 알면 테트리스는 물론 다른 게임 또한 충분히 만들 수 있다 생각합니다.

+ 메서드 안에있는 HANDLE등 어떻게 작용되는지 무엇을 return하는지 등

원리를 이해하는 것도 좋지만,

함수를 직접 선언하고

우선 사용하시면 금방 이해하실 수 있습니다.)

 

위의 함수를 다 숙지하셨다면!!!!!!

테트리스를 구현하는데 있어 모르는 함수는 완벽하게 없는 것입니다.

정말 100퍼센트 확신!!!



콘솔창에 보여지는 블록(회전, 4차원 배열 등 ) 이해하기

 

2. 블럭 이해하기

배열을 선언하고, 활용할 줄 아신다면

블럭 또한 선언하고 만들 수 있습니다.

 

 

테트리스 블럭들 중 한 모양 입니다.

이 블럭을 배열로 선언한다면 어떻게 하시겠습니까?

 

 

 

int Block[4][4]={
	 0,0,0,0,
   	 0,1,1,0,
    	 0,1,1,0,
   	 0,0,0,0
};
for(int y=0;y<4;4++){
	for(int x=0;x<4;x++){
    		if(block[y][x]==1)
        		printf("■");
   	}
}

 

이렇게 블럭이 아닌 부분은 0으로, 블럭을 표현하는 부분은 1로 표시를한 4*4의 2차원 배열을 선언할 것입니다.

그 후 아래와 같이 포문으로 1인 부분만 출력할 것입니다.

 


회전 상태의 블럭 출력 방법

 

그렇다면 이번에는

위쪽 사진처럼 90도씩 회전되어 있는 블럭들은 한개의 배열로 어떻게 선언하실 건가요?

 

 

2차원 배열을 통해서 쭉 나열하실 수 있겠지만 저는 3차원 배열을 사용할 것입니다.

왜냐? 그래야 좀 더 편리하게 특정상황에서 해당 블럭을 정확하게 사용할 수 있기 때문입니다.

int block[4][4][4]={
	 0,0,0,0
	,0,2,2,2		
	,0,2,0,0
	,0,0,0,0

	,0,0,2,0
	,0,0,2,0
	,0,0,2,2
	,0,0,0,0

	,0,0,0,2
	,0,2,2,2
	,0,0,0,0
	,0,0,0,0

	,0,2,2,0
	,0,0,2,0
	,0,0,2,0
	,0,0,0,0
};

2를 연결지어 보시면,

보이시나요? 블럭의 형태가?

 

어떻게 사용하나요?

block[0]은 초기 블럭의 상태이고

block[1]은 90도 회전한 상태의 블럭이고

block[2]는 180도 회전한 상태의 블럭,

block[3][][]은 270도 회전한 상태의 블럭을 의미하게 되는 것입니다.

 

 예를들어 270도 회전한 상태의 블럭을 출력하고 싶다면

for(int y=0;y<4;y++){
	for(int x=0;x<4;x++){
    	if(board[3][y][x]==2)
        	printf("■");
 	}
}

이렇게 내가 원하고자하는 기울기를 입력후 포문으로 출력하면 됩니다!!

180도면 block[2][y][x]..

이해 하셧나요?

위의 첫 □ 네모 블럭을 2차원 배열로 출력할 때와 출력하는 스타일이 ,

앞의 block[0 or 1 or 2 or 3]을 지정해 주기만 한다면

똑같다는 사실을.. 이해하셨나요!?!?!?!!?

좋습니다. 

2차원, 3차원배열의 전반적인 지식을 이해하셨다니 다행입니다.

 


테트리스 블럭 4차원으로 선언

 

그렇다면 ,

특징 보이시나요?

색칠된 부분, 출력부분만 2로 특정하게 표시해놓은 것?

 

 

이 7가지의 블럭은 어떻게 배열로 표현하면 좋을까요?

 

 

다양하게 표현을 할 수 있습니다. 

하지만 제가 표현할 방식은 4차원 배열입니다.

 

int blocks[7][4][4][4]

어떤 블럭은 총 4가지, 회전했을 경우의 블럭이 있기 때문에 블럭의 회전이 똑같은 블럭일 경우 그냥 3번 중첩해서 더 선언해줄 것이구요, 블럭의 회전이 2번인 경우는 한번더 반복해서 선언해 줄 것입니다.  

 

blocks의 첫번째 인덱스부터, 

blocks[모양][회전할경우][블럭의 표현][블럭의표현]

모양은 총 7개이고,

회전할 경우는4번, (위의 3차원 배열일때를 떠올리세요)

블럭의 표현은 4*4(위의 2차원 배열 일 때 배열의 출력을 떠올려주세요) 을 뜻합니다.

 

int Blocks[Blocks_Kinds][Blocks_SIZE][Blocks_SIZE][Blocks_SIZE] =
{ //Blocks_Kinds =7 , Blocks_SIZE = 4 #define 으로 선언.
	0,0,0,0
	,0,2,2,0
	,0,2,2,0
	,0,0,0,0
	,0,0,0,0
	,0,2,2,0
	,0,2,2,0
	,0,0,0,0
	,0,0,0,0
	,0,2,2,0
	,0,2,2,0
	,0,0,0,0
	,0,0,0,0
	,0,2,2,0
	,0,2,2,0
	,0,0,0,0
	,
	0,0,0,0
	,2,2,2,2
	,0,0,0,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,0
	,0,0,2,0
	,0,0,2,0
	,0,0,0,0
	,2,2,2,2
	,0,0,0,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,0
	,0,0,2,0
	,0,0,2,0
	,
	0,0,0,0	//ㄹ꺼꾸로
	,0,0,2,2
	,0,2,2,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,2
	,0,0,0,2
	,0,0,0,0
	,0,0,0,0
	,0,0,2,2
	,0,2,2,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,2
	,0,0,0,2
	,0,0,0,0
	,
	0,0,0,0
	,0,2,2,0	//ㄹ
	,0,0,2,2
	,0,0,0,0
	,0,0,0,2
	,0,0,2,2
	,0,0,2,0
	,0,0,0,0
	,0,0,0,0
	,0,2,2,0
	,0,0,2,2
	,0,0,0,0
	,0,0,0,2
	,0,0,2,2
	,0,0,2,0
	,0,0,0,0
	,
	0,0,0,0
	,0,2,2,2		//기역꺼꾸로
	,0,2,0,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,0
	,0,0,2,2
	,0,0,0,0
	,0,0,0,2
	,0,2,2,2
	,0,0,0,0
	,0,0,0,0
	,0,2,2,0
	,0,0,2,0
	,0,0,2,0
	,0,0,0,0
	,
	0,0,0,0
	,2,2,2,0	//ㄱ
	,0,0,2,0
	,0,0,0,0
	,0,0,2,2
	,0,0,2,0
	,0,0,2,0
	,0,0,0,0
	,0,2,0,0
	,0,2,2,2
	,0,0,0,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,0
	,0,2,2,0
	,0,0,0,0
,
	0,0,0,0
	,0,2,2,2
	,0,0,2,0
	,0,0,0,0
	,0,0,2,0
	,0,0,2,2
	,0,0,2,0
	,0,0,0,0
	,0,0,2,0
	,0,2,2,2
	,0,0,0,0
	,0,0,0,0
	,0,0,2,0
	,0,2,2,0
	,0,0,2,0
	,0,0,0,0

};

어우 길어..

 

참고로

#define Blocks_kind 7

#define Blocks_SIZE 4로 정의를 한 상태입니다.

 


블럭 관련 주의사항

 

2차원일때의 블록선언, 3차원일때의 블록회전, 4차원일때의 블록 모양 등 을 이해하셔야합니다.

직접해보는 것도 추천합니다.

선언 후

포문을 사용해 4차원에서 특정한 경우의 블럭 출력을 연습하셔야합니다.

※주의사항1

블록을 출력하면 그냥 일자로 출력이 되기 때문에

GotoXY함수를 사용하셔야합니다. 

 사용법을 모르시겠다면

2021.09.06 - [C언어] - [C언어] GotoXY()함수로 콘솔 창의 커서를 내 마음대로 이동하는 방법

에서 몇번 씩 색다른 좌표를 출력하면 금방 이해하십니다.

다룰수 있는 경우 ,

위에서 말한대로 

포문을 사용해 4차원에서 특정한 경우의 블럭 출력을 연습하셔야합니다.

정말 중요합니다. 이 사용법이 거의 모든 테트리스

구현기능의 거의 모든것을 응용해서 구현가능합니다.

※주의사항2

(콘솔창에서의 x좌표는 '두개의 칸이 있어야 한개의 문자가 출력 가능하다)

y축일 경우 상관없지만

x축일 경우 2칸을 한 칸으로 생각해야 합니다. 

즉 GotoXY(0,0)에 a를 출력하고

GotoXY(1,0)에 b를 출력하면 a와b가 겹쳐져서 출력됩니다. 

따라서 GotoXY(2,0)에 b를 출력해야 우리가

일반적으로 출력하는  ab출력과 같아집니다.

이것또한 연습해보기 바랍니다.

 

 그리고 콘솔창에서 드래그 한번 하면,

오호라,, 한칸 드래그 했을때 작구나?

두칸  드래그 해야 한개의 문자가 출력될 크기가 되는구나?

이해하실수있습니다.

 

0,0과 1,0에 각각 다른 문자를 출력했을때,

0,0과 2,0에 각각 다른 문자를 출력했을때말입니다.

※주의사항3

블럭을 나타내고자 포문을 활용할 때는 y, x를 사용해 포문을 이용하기입니다.

i, j를통해 포문을 구현할수있지만 뭐가 배열의 col인지 row인지

햇갈릴 가능성 대단히 큽니다.

 

포문을 이용해 블럭을 출력할 때

겉의 포문이 왜 y축인지 ,

안 포문이 왜 x축인지 "반드시"

이해하셔야합니다.

(할수있어요 화이팅+_+)

 

사실 여기까지 이해하셨다면 정말로 테트리스를 구현함에있어

추가적인 함수만 정의해주면 끝입니다.

 

(저는 함수, 변수선언 줄이 좀 길지만,,

위에것을응용한것이라 정말쉽고 빠르게 쭉쭉구현됬습니다.

 

물론 처음 테트리스를 만들면서

느낀점과같이

2021.09.11 - [C언어] - [C언어] 초보자도 구현할 수 있는 테트리스 게임 만들기 ep.1 전반적인 개요(with tetris Algorith)

쉽지는 않고 고민 엄청 하긴했습니다.)

그래도 위의 커서를 다룰줄만 안다면 생각보다 쉽습니다. 나 머지 다..(저만 그렇게 생각하는 것일수도)

진짜입니다.

 


테트리스 게임 보드(판) 생성,  선언하기

 

3. 테트리스 게임판 생성하기

 

저는 테트리스 판을 가로 = 14

세로 = 22로 정의했습니다

#define Board_Width 14

#define Board_Height 22

한번 정의를 해두면 다시사용할때 편리합니다.

반드시

자주 사용되는 변수들은 정의하세요!!

 

보드는 자주 사용하기 위해

int boards[Board_Height][Board_Width];

를 정적 배열로 선언했습니다.

(#define선언하는곳한칸아래)

참고로 board로했어야했는데 ..

이미 boards로 정의 후 사용된 곳이 너무 많아서 

일단은 수정하지 않고 있습니다.

여러분은 변수 이름 선언에 좀 더많은 신경을 써주세요...

지금 생각해보니..

(물론 배열 call by reference라 어디에 선언하든 상관이 없겟군요?(물론 잠깐쓰고 사라지는 함수에 선언하는 경우 빼고)  하지만 여러 함수를 통해 

많은  reference가 이루어져서 정적인 지역에 선언을 해주었습니다.)

 

그 후 바깥부분을 벽으로 표현하기위해 1, 안쪽에있는 부분은 0으로 표현했습니다.

 

void createBoards() {
	for (int y= 0; y < Board_Height; y++) { // #define Board_Height 22
		boards[y][0] = 1;
		boards[y][Board_Width-1] = 1;
		for (int x = 0; x < Board_Width; x++) {  //#define Board_Weight 14
			if (y == 0) 
				boards[y][x] = 0;
			boards[Board_Height-1][x] = 1;
			if (y > 0 && y < Board_Height-1)
				if (x > 0 && x < Board_Width-1)
					boards[i][j] = 0;
		}
	}
}

저는 보드의 높이

#define Board_Height 22

보드의 길이 

#define Board_Weright 14로 

선언했습니다.

보드 또한 2차원 배열로 노가다를통해

{1,1,1,1,1,1,1,...

1,0,0,,,,0,1

1,0,0,,0,1

,,,

1,1,1,1,1,1,1,

선언할수있지만 

우리에게는 포문이 있잖아요~ 

for문을 활용합시당!

 

그럼 출력은?

따로 또 함수를 만들었습니다.

boards[y][x]가 1일 때는 printf("▨");

boards[y][x]가 0 일때는 printf("  ");//빈칸 두개! 

왜?

라고 생각한다면

2번째에 블럭 다루는 부분 아래쪽에 커서 부분 참고!!

void printBoards() {
	for (int x = 1; x < 13; x++) {		//BoardX =4; BoardY=3; 을 #define으로 정의
		GotoXY(BoardX + x * 2, BoardY+1);
		printf("_");
	}
	
	for (int y = 0; y < Board_Height; y++) {
		GotoXY(BoardX, BoardY + y);
		if (boards[y][0] == 1) {
			textcolor(3);	printf("▩");
		}
		if (boards[y][Board_Width - 1] == 1) {
			GotoXY(BoardX + Board_Width * 2 - 2, BoardY + y);
			textcolor(3);
			printf("▩");
		}
		textcolor(WHITE);
	}
	for (int x = 0; x < Board_Width; x++) {
		GotoXY(BoardX+x*2, BoardY + Board_Height-1);
		if (boards[Board_Height-1][x] == 1) {
			textcolor(3);	printf("▩");
		}
		textcolor(WHITE);
	}
	
}

저는 보드의 출력 시작 부분을

#define BoardX 4
#define BoardY 3

으로 선언 후 4,3부터 Boards를 출력했습니다.

 

앗 참고로 저는 색 변경 부분까지 신경썼습니다. WHITE의 경우 15로 바꿔야합니다. 

ep1에서 마지막 부분내용 읽어보시면 enum으로 선언했습니다.

나중에 알려드리겠습니다.

 

 

오늘, 게임에 사용되는 함수, 블럭, 보드를 다뤄봤는데요.

 

정말 중요한 것은 코드 복붙 끝이 아니라

여러분이 스스로 생각해서 변수 이름은 뭐로하지?

함수 이름은?

이사람은 이렇게 코드를 짰구나?

나는 어떻게 짜면 좋을까?

개선방안은? 등등

여러분만의 창의적인 코드를 구현하시면 좋을것 같습니다.

긴 글 읽어주셔서 감사합니다.

ep1에 사용된 함수, 변수

 

#define Blocks_kind 7

#define Blocks_SIZE 4

#define Board_Width 14
#define Board_Height 22

#define BoardX 4
#define BoardY 3

createBoards(); //보드판 생성

printBoards(); //보드판 출력

Blocks[Blocks_kind][Blocks_SIZE][Blocks_SIZE][Blocks_SIZE]; //4차원 배열에 블럭의 모든 형태 저장.

 

다음 주제는 " ep1 전반적 개요"에 따라,

보드에 블럭을 어떻게 표현하는가?와

그외의 방법을 알려드리도록 하겠습니다.

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

 

아래 글은 제가 테트리스를 구현한 소스코드입니다.

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

 

GitHub - SHcommit/Tetris-game-in-C-lang: Creating a Tetris game in c language.

Creating a Tetris game in c language. Contribute to SHcommit/Tetris-game-in-C-lang development by creating an account on GitHub.

github.com

 

728x90