[Block 클래스의 Class Diagram]



테트리스에서 사용되는 하나의 블럭을 (다음 블럭 포함) 나타내는 클래스인 Block 에 대해서 살펴보려고 합니다. Block.h 와 Block.c 를 분석해보겠습니다.


[Block.h]


#ifndef _BLOCK_H
#define _BLOCK_H

typedef struct _point{
	int x;
	int y;
}Point;

#define POSITIONS_SIZE 4

typedef struct _block{
	Point positions[POSITIONS_SIZE][POSITIONS_SIZE];
	int current;
	int next;
	int direction;
}Block;

Block Block_Make(int isFirst, Block block);
Block Block_Move(Block block, int direction);
Point* Block_GetPositions(Block block);
void Block_PrintNext(Block block, int x, int y);

#endif


1 ~ 2, 23 라인

헤더파일의 중복 포함을 방지하는 guard 입니다.


4 ~ 7 라인

Point 구조체의 정의부입니다. 멤버로는 x 좌표, y 좌표를 가지고 있습니다.


9 라인

Block 구조체의 멤버로 사용될 positions 배열의 크기를 매크로로 설정합니다. (= 4)


11 ~ 16 라인

Block 구조체의 정의부입니다. Block 구조체는 테트리스에서 사용되는 하나의 블럭을 나타냅니다. (현재 블럭, 다음 나올 블럭 포함) 멤버로는 positions 배열이 있는데, 이는 4 X 4 크기의 2 차원 배열로, Point 형 (x, y 좌표) 으로 구성되어 있습니다. 이는, 하나의 블럭이 4 개의 정사각형 상자로 구성되어 있기 때문에, 4 개의 상자의 x, y 좌표를 담기 위해 사용됩니다. current 는 여러 타입의 블럭들 중에서 현재 이동하는 블럭의 타입을 나타냅니다. next 는 여러 타입의 블럭들 중에서 다음에 나올 블럭의 타입을 나타냅니다. direction 은 블럭의 방향을 나타내고, 위쪽, 오른쪽, 아래쪽, 왼쪽 을 나타냅니다.


18 ~ 21 라인

소스 파일에서 사용될 Block 관련 함수들의 선언부입니다.


[Block.c]


#include <time.h>
#include <windows.h>
#include <stdio.h>
#include "Block.h"
#include "Util.h"
#include "Constant.h"

#define BLOCK_EXAMPLES_SIZE 6

const static Point blockExamples[BLOCK_EXAMPLES_SIZE][POSITIONS_SIZE][POSITIONS_SIZE] = {
	//ㅁㅁㅁㅁ
	{
		{ { 0, 5 }, { 0, 6 }, { 0, 7 }, { 0, 8 } },
		{ { -2, 6 }, { -1, 6 }, { 0, 6 }, { 1, 6 } },
		{ { 0, 5 }, { 0, 6 }, { 0, 7 }, { 0, 8 } },
		{ { -2, 6 }, { -1, 6 }, { 0, 6 }, { 1, 6 } }
	},
	//    ㅁ
	//ㅁㅁㅁ
	{
		{ { 0, 8 }, { 1, 6 }, { 1, 7 }, { 1, 8 } },
		{ { -1, 7 }, { 0, 7 }, { 1, 7 }, { 1, 8 } },
		{ { 0, 6 }, { 0, 7 }, { 0, 8 }, { 1, 6 } },
		{ { -1, 6 }, { -1, 7 }, { 0, 7 }, { 1, 7 } }
	},
	//  ㅁㅁ
	//ㅁㅁ
	{
		{ { 0, 7 }, { 0, 8 }, { 1, 6 }, { 1, 7 } },
		{ { -1, 6 }, { 0, 6 }, { 0, 7 }, { 1, 7 } },
		{ { 0, 7 }, { 0, 8 }, { 1, 6 }, { 1, 7 } },
		{ { -1, 6 }, { 0, 6 }, { 0, 7 }, { 1, 7 } }
	},
	//ㅁㅁ
	//  ㅁㅁ
	{
		{ { 0, 6 }, { 0, 7 }, { 1, 7 }, { 1, 8 } },
		{ { -1, 8 }, { 0, 8 }, { 0, 7 }, { 1, 7 } },
		{ { 0, 6 }, { 0, 7 }, { 1, 7 }, { 1, 8 } },
		{ { -1, 8 }, { 0, 8 }, { 0, 7 }, { 1, 7 } }
	},
	//ㅁ
	//ㅁㅁㅁ
	{
		{ { 0, 6 }, { 1, 6 }, { 1, 7 }, { 1, 8 } },
		{ { -1, 8 }, { -1, 7 }, { 0, 7 }, { 1, 7 } },
		{ { 0, 6 }, { 0, 7 }, { 0, 8 }, { 1, 8 } },
		{ { -1, 7 }, { 0, 7 }, { 1, 7 }, { 1, 6 } }
	},
	//ㅁㅁ
	//ㅁㅁ
	{
		{ { 0, 6 }, { 0, 7 }, { 1, 6 }, { 1, 7 } },
		{ { 0, 6 }, { 0, 7 }, { 1, 6 }, { 1, 7 } },
		{ { 0, 6 }, { 0, 7 }, { 1, 6 }, { 1, 7 } },
		{ { 0, 6 }, { 0, 7 }, { 1, 6 }, { 1, 7 } }
	}
};

static Block _Block_MoveToDown(Block block);
static Block _Block_MoveToLeft(Block block);
static Block _Block_MoveToRight(Block block);
static Block _Block_RotateRight(Block block);

Block Block_Make(int isFirst, Block block){
	int i;
	int j;
	srand((unsigned int)time(NULL));
	if (isFirst){
		block.current = rand() % BLOCK_EXAMPLES_SIZE;
	}
	else{
		block.current = block.next;
	}
	for (i = 0; i < POSITIONS_SIZE; i++){
		for (j = 0; j < POSITIONS_SIZE; j++){
			block.positions[i][j] = blockExamples[block.current][i][j];
		}
	}
	block.next = rand() % BLOCK_EXAMPLES_SIZE;
	block.direction = UP;
	return block;
}

Block Block_Move(Block block, int direction){
	switch (direction){
	case LEFT:
		return _Block_MoveToLeft(block);
	case RIGHT:
		return _Block_MoveToRight(block);
	case DOWN:
		return _Block_MoveToDown(block);
	case UP:
		return _Block_RotateRight(block);
	}
	return _Block_MoveToDown(block);
}

Point* Block_GetPositions(Block block){
	return block.positions[block.direction];
}

void Block_PrintNext(Block block, int x, int y){
	CursorUtil_GotoXY(x, y);
	printf("Next block : ");
	x += 3;
	y += 2;
	CursorUtil_GotoXY(x, y++);
	switch (block.next){
	case 0:
		printf("■■■■");
		CursorUtil_GotoXY(x, y++);
		printf("        ");
		break;
	case 1:
		printf("      ■");
		CursorUtil_GotoXY(x, y++);
		printf("  ■■■");
		break;
	case 2:
		printf("    ■■");
		CursorUtil_GotoXY(x, y++);
		printf("  ■■  ");
		break;
	case 3:
		printf("  ■■  ");
		CursorUtil_GotoXY(x, y++);
		printf("    ■■");
		break;
	case 4:
		printf("■      ");
		CursorUtil_GotoXY(x, y++);
		printf("■■■  ");
		break;
	case 5:
		printf("  ■■  ");
		CursorUtil_GotoXY(x, y++);
		printf("  ■■  ");
		break;
	}
}

static Block _Block_MoveToDown(Block block){
	int i;
	int j;
	for (i = 0; i < POSITIONS_SIZE; i++){
		for (j = 0; j < POSITIONS_SIZE; j++){
			block.positions[i][j].x++;
		}
	}
	return block;
}

static Block _Block_MoveToLeft(Block block){
	int i;
	int j;
	for (i = 0; i < POSITIONS_SIZE; i++){
		for (j = 0; j < POSITIONS_SIZE; j++){
			block.positions[i][j].y--;
		}
	}
	return block;
}

static Block _Block_MoveToRight(Block block){
	int i;
	int j;
	for (i = 0; i < POSITIONS_SIZE; i++){
		for (j = 0; j < POSITIONS_SIZE; j++){
			block.positions[i][j].y++;
		}
	}
	return block;
}

static Block _Block_RotateRight(Block block){
	block.direction = (block.direction + 1) % POSITIONS_SIZE;
	return block;
}


1 ~ 6 라인

필요한 헤더파일들을 include 합니다.


8 라인

생성될 수 있는 여러 블럭 타입들의 사이즈를 매크로로 정의합니다.


10 ~ 58 라인

생성될 수 있는 블럭 타입들을 미리 상수 배열로 정의해 놓습니다. const 를 붙인 이유는 상수화하기 위함이고, static 은 현재 파일 내에서만 내부적으로 사용하기 위함입니다. 총 6 개의 타입의 블럭들을 위쪽, 오른쪽, 아래쪽, 왼쪽 방향에 대한 상대좌표로 저장해 놓습니다. 


예를 들어 ㅁㅁㅁㅁ 와 같은 모양의 블럭은 위쪽 방향일 때 (0,5), (0, 6), (0, 7), (0, 8) 의 위치를 가집니다. 만약 한번 회전하여 오른쪽 방향일 때는 (-2, 6), (-1, 6), (0, 6), (1, 6) 의 위치를 가집니다. 한번 더 회전하여 아래쪽 방향일 때는 위쪽 방향과 마찬가지로 (0,5), (0, 6), (0, 7), (0, 8) 를 가지게 되고, 마지막으로 한번 더 회전하여 왼쪽 방향일 때는 오른쪽 방향과 같은 (-2, 6), (-1, 6), (0, 6), (1, 6) 의 위치를 가집니다. 이처럼 x 좌표는 0 을 기준으로 하고, y 좌표는 테트리스의 중간 지점을 기준으로 하여, 각각 6 개의 모양 별로, 위쪽, 오른쪽, 아래쪽, 왼쪽으로 회전했을 때 좌표 위치를 배열에 미리 저장해 놓습니다.


60 ~ 63 라인

Block.c 소스 파일 내부에서만 사용될 내부 함수의 선언부입니다.


65 라인

Block_Make 함수의 정의부입니다. 매개변수로 isFirst (블록이 테트리스 시작하고 난 후에 가장 처음에 만들어지는지 아닌지 여부) 와 block (실제 만들어서 내용물을 저장하여 반환할 블럭) 을 받고, 만들어진 block 을 반환합니다.


68 ~ 74 라인

srand 와 time 함수를 통해 난수 생성 전 시드를 섞어줍니다. 그리고 처음 블럭을 생성하는 경우면 (isFirst 가 True 이면) block.current 에 난수를 생성해서 저장하고 (0 ~ 5 사이의 임의의 정수), 처음이 아니면, block.current 에는 이전에 생성해 두었던, block.next 를 가져와서 저장합니다. 


즉, 쉽게 말하면, 처음 블럭을 생성하는 경우에는, current 를 난수로 생성해야 하고, 두번 째 부터는, current 를 다시 난수로 생성하지 말고, 이전에 생성해 두었던, next 에서 가져오면 된다는 의미입니다. 

(의사 난수 (랜덤 숫자) 생성하기 참고)

http://kkikkodev.tistory.com/52


75 ~ 59 라인

block.positions 배열에 위에서 생성 혹은 next 에서 가져온 block.current 를 기반으로 blockExamples (6 개 모양의 블록들을 4 방향 모두 상대좌표 위치를 저장해 놓은 배열) 에서 블럭을 가져와서 4 개의 상자의 위치를 저장합니다.


80 라인

block.next 에 난수를 생성하여 저장합니다. (다음 블럭을 생성합니다.)


81 라인

block.direction 을 UP (= 0) 으로 초기화합니다. (위쪽 방향을 시작 방향으로 설정)


82 라인

생성한 block 을 반환합니다.


85 라인

Block_Move 함수의 정의부입니다. 매개변수로, block 과 direction (사용자가 입력한 방향키 값) 을 받아서, 4 가지 방향키에 해당하는 처리를 한 block 을 반환합니다.


86 ~ 96 라인

direction 을 살펴봐서, LEFT (= 3) 이면 Block_MoveToLeft 함수를 호출하여 얻은 반환 값을 retrun 하고, RIGHT (= 1) 이면 Block_MoveToRight 함수를 호출하여 얻은 반환 값을 return 하고, 마찬가지로, DOWN (= 2) 인 경우는 Block_MoveToDown 함수를, UP (= 0) 인 경우는, Block_RotateRight 함수릂 호출하여 얻은 반환 값을 return 합니다. 그리고 마지막 줄에서는, 혹시나 이 4 가지 방향 말고 다른 값이 들어왔을 경우를 대비해서 Block_MoveToDown 함수를 호출하도록 처리하였습니다. 여기서 주목할 점은, direction 이 UP 일 때는, Move 가 아닌 Rotate 를 해야 한다는 것입니다. 오른쪽으로 90 도씩 회전하게 됩니다.


99 라인

Block_GetPositions 함수의 정의부입니다. 매개변수로 block 을 받아서 positions 에서 현재 방향에 해당하는 요소를 반환합니다.


100 라인

매개변수로 받은 block 의 positions 의 block.direction 번째의 요소를 반환합니다. 반환형이 Point* 인 이유는 positions 가 2 차원 배열이기 때문에 X 번째의 요소는 1 차원 배열이 되기 때문입니다.


103 라인

Block_PrintNext 함수의 정의부입니다. 매개변수로 block 과 x, y 를 받습니다. 해당 좌표 (x, y) 에 다음 블럭을 출력하는 역할을 합니다.


104 ~ 140 라인

CursorUtil_GotoXY 함수를 통해 x, y 좌표로 출력 커서를 이동시킵니다. 

(콘솔 커서 좌표 이동하기 참고)

http://kkikkodev.tistory.com/26


block.next 를 확인하여 0 부터 5 까지에 해당하는 타입의 블럭 모양들을 출력합니다. (총 6 가지 모양)


143 라인

_Block_MoveToDown 함수의 정의부입니다. 이 함수는 내부에서만 쓰이는 함수로 static 으로 정의되어 있습니다. 매개변수로 block 을 받아서 밑으로 한칸 이동시키고 반환하는 역할을 합니다.


144 ~ 151 라인

block.positions (블럭의 4 개 상자의 좌표들) 들의 x 좌표를 증가시킵니다. (아래로 한 칸 이동)


154 라인

_Block_MoveToLeft 함수의 정의부입니다. 이 함수는 매개변수로 block 을 받아서 왼쪽으로 한칸 이동시키고 반환하는 역할을 합니다.


155 ~ 162 라인

block.positions (블럭의 4 개 상자의 좌표들) 들의 y 좌표를 감소시킵니다. (왼쪽으로 한 칸 이동)


165 라인

_Block_MoveToRight 함수의 정의부입니다. 이 함수는 매개변수로 block 을 받아서 오른쪽으로 한칸 이동시키고 반환하는 역할을 합니다.


166 ~ 173 라인

block.positions (블럭의 4 개 상자의 좌표들) 들의 y 좌표를 증가시킵니다. (오른쪽으로 한 칸 이동)


176 라인

_Block_RotateRight 함수의 정의부입니다. 이 함수는 매개변수로 block 을 받아서 오른쪽 90 도 방향 (시계 방향) 으로 블럭을 돌리고, 반환하는 역할을 합니다.


177 ~ 178 라인

block.direction 을 1 증가시킵니다. (다음 방향으로 전환합니다.) % 처리는 0 -> 1 -> 2-> 3-> 0 으로 순환하게 만들게 하기 위해서 사용한 기법입니다.


by kkikkodev 2015. 6. 12. 11:24