본문 바로가기
Lecture

Reversi 게임 제작하기 - 2

by 작은별하나 2019. 12. 19.
반응형

오델로 게임

 

오델로(Othello) 게임은 바둑, 장기, 오목 등과 마찬가지로 유명한 보드 게임이다. 오델로 게임은 다른 말로 리버시(Reversi)라고도 불린다. 이 게임은 1800년대에 영국에서 리버시라는 이름으로 만들어졌고, 1970년대에 일본에서 리버시라는 게임이 존재하는지 모르고 세익스피어 작품의 오델로 이름을 따서 현대적 룰을 갖춘 게임이 만들어졌다.

 

오델로 게임의 규칙

 

오델로 게임을 가로 8칸, 세로 8칸의 정방형 사각형이 있는 게임판에서 게임을 한다. 오델로에는 총 64개의 돌이 있고, 이 돌은 한쪽은 하얀색, 한쪽은 검은색으로 이루어져 있다.

Reversi game board

처음 시작하면 보드 중앙에 4개의 흑과 백돌을 번갈아 배치하게 된다. 돌을 놓을 수 있는 곳은 자신의 색 돌 사이에 적어도 1개 이상의 상대편 돌이 놓여있는 경우이다. 규칙은 번갈아가면서 돌을 놓는데, 적어도 1개 이상의 상대방 돌을 자신의 돌로 바꿀 수 있어야 한다. 그런 경우가 없으면 자신의 턴을 쉬어야 한다. 두사람 모두 놓을 수 있는 곳이 없다면 게임은 종료된다. 이 때 갯수가 많은 쪽의 색돌을 놓은 사용자가 게임에서 승리하게 된다.

 

오델로 게임 구현

 

오델로 게임을 구현하기 위해서 제일 먼저 구성해야 할 것이 게임 보드판을 모델링하는 것이다. 오델로 게임 보드판에는 각각의 경우 총 3가지 경우가 있다. 흑 돌이 놓인 경우와 백 돌이 놓인 경우, 아무 돌도 놓이지 않은 경우가 있다. 여기서는 놓을 수 있는 힌트값을 적용하기 위해서 한가지 경우를 더 추가하였다.

 

오델로 게임은 텍스트 기반으로 게임을 제작하였다. 텍스트 기반의 게임의 일반적인 것은 스낵바이트에 있는 내용을 참조하도록 하자. 다음은 완성된 형태의 오델로 게임이다.

 

구현된 Reversi game

오델로 게임판의 경우 이중 배열을 사용할 수도 있지만, 여기서는 단일 배열을 사용하였다. 단일 배열과 이중 배열은 구현 복잡도가 비슷하지만, 두개의 위치 변수를 이용하는 것보다 하나의 위치 변수를 이용하는 것이 여러모로 유리하다고 판단하였다. 그리고 현재의 점수 등을 기록하기 위해서 구조체로 보드와 관련된 변수들을 묶어두었다.

 

struct Gameboard
{
	unsigned char board[64];
	unsigned score[3];		//	0 : empty 1 : white 2 : black
	unsigned hint;
};

board 변수는 현재 게임판에 있는 돌의 색을 표시한다. 64개의 칸에 있는 돌의 색은 3가지가 존재할 수 있지만, 여기서는 표시상의 이유로 4가지(빈자리, 흰돌, 검은돌, 힌트)로 분류하였다. 점수는 현재 게임판에 놓여진 돌의 갯수를 기록한다. hint의 갯수가 0인 경우에는 현재 놓을 수 있는 돌이 없다는 것을 의미한다.

 

이 게임판의 구조체 변수를 인자로 받아서 화면에 게임판을 표시하주는 함수이다. 앞서 배운 것과 같이 system("cls")를 이용하여 화면을 지운 후, 표준 출력 함수들을 사용하여 화면에 오델로 게임판을 출력토록 하였다.

void DisplayBoard(Gameboard &board)
{
	unsigned int ui, uj;
	system("cls");
	printf("     1    2    3    4    5    6    7    8    ");
	printf(" ● : ○ = %d : %d\n", board.score[1], board.score[2]);
	for(ui = 0 ; ui < 8 ; ui++)
	{
		printf("   +----+----+----+----+----+----+----+----+\n");
		printf(" %c |", ui+'a');
		for(uj = 0 ; uj < 8 ; uj++)
		{
			unsigned char v = board.board[ui*8+uj];
			if(v == 1) printf(" ● |");
			else if(v == 2)	printf(" ○ |");
			else if(v == 0)	printf(" ※ |");
			else printf("    |");
		}
		putchar('\n');
	}
	printf("   +----+----+----+----+----+----+----+----+\n");
}

오델로 게임을 처음 시작하면, 중앙에 4개의 돌이 엇갈려서 배치되어야 한다. 이 초기값을 다음과 같이 구조체 변수 초기화를 이용하여 작성하였다.

 

static Gameboard Initboard =
{
	{
		3, 3, 3, 3, 3, 3, 3, 3,		3, 3, 3, 3, 3, 3, 3, 3,
		3, 3, 3, 3, 0, 3, 3, 3,		3, 3, 3, 1, 2, 0, 3, 3,
		3, 3, 0, 2, 1, 3, 3, 3,		3, 3, 3, 0, 3, 3, 3, 3,
		3, 3, 3, 3, 3, 3, 3, 3,		3, 3, 3, 3, 3, 3, 3, 3
	},
	60, 2, 2, 4
};

다음의 PutBoard 함수는 사용자의 입력 또는 컴퓨터가 게임트리를 이용하여 계산할 때 사용한다. 주어진 위치에 자신의 턴에 맞는 색의 돌을 놓는다. 이 부분의 로직은 다소 복잡하기 때문에 <파트 1>, <파트 2> 부분으로 나누어서 설명토록 한다. index 인자는 돌을 놓을 위치, turn 인자는 돌의 색이다.

 

void PutBoard(Gameboard &board, unsigned int index, unsigned int turn)
{
	unsigned int ui;
	unsigned int r;
	unsigned int anti = (turn ^ 0x3);
	static int dxy[8] = { -8, -7, 1, 9, 8, 7, -1, -9 };
	<파트 1 : 돌을 놓는 로직>
	<파트 2 : 놓을 수 있는 곳을 찾는 로직>
}

<파트 1>은 돌을 놓는 로직으로 PutBoard 함수에서 주어진 위치에 돌을 놓고, 그 돌의 영향으로 인하여 주변의 돌의 색을 바꾸는 부분이다.

 

	if(index != 64)
	{
		board.board[index] = turn;
		board.score[0]--;
		board.score[turn]++;
		for(ui = 0 ; ui < 8 ; ui++)
		{
			r = index + dxy[ui];
			int k;
			for( k = 1 ; k < limit[index][ui] ; ++k)
			{
				if(board.board[r] != anti) break;
				r += dxy[ui];
			}
			if(board.board[r] == turn)
			{
				while(--k)
				{
					r -= dxy[ui];
					board.board[r] = turn;
					board.score[turn]++;
					board.score[anti]--;
				}
			}
		}
	}

<파트 2>는 돌을 놓을 수 있는 곳을 찾는 것으로 모든 빈 칸을 검사하여 해당 칸을 힌트로 표시해주는 역할을 한다.

 

	//	clear hint slots
	board.hint = 0;
	for(index = 0 ; index < 64 ; index++ )
	{
		if(board.board[index] != 3 && board.board[index] != 0)
			continue;
		board.board[index] = 3;
		for(ui = 0 ; ui < 8 ; ui++)
		{
			r = index + dxy[ui];
			int k;
			for( k = 1 ; k < limit[index][ui] ; k++)
			{
				if(board.board[r] != turn)
					break;
				r += dxy[ui];
			}
			if(k != 1 && board.board[r] == anti)
			{
				board.board[index] = 0;
				board.hint++;
				break;
			}
		}
	}

이 소스에서 사용한 limit 배열은 계산의 편이성을 위해서 배열로 작성하였고 이 배열은 함수를 통해서 작성하였다. 이렇게 작성함으로써 속도를 향상시킬 수가 있다.

 

void MakeLimits()
{
	FILE *fp = fopen("limittbl.h", "w");
	unsigned limit[8];
	unsigned index, x, y;

	fprintf(fp, "unsigned limit[64][8] = \n{\n");
	for(index = 0 ; index < 64 ; index++ )
	{
		//	Get limit values of 8 axis
		x = index%8;
		y = index/8;
		limit[0] = y;
		limit[1] = (y > 7-x)?7-x:y;
		limit[2] = 7-x;
		limit[3] = (7-y > 7-x)?7-x:7-y;
		limit[4] = 7-y;
		limit[5] = (7-y > x)?x:7-y;
		limit[6] = x;
		limit[7] = (y > x)?x:y;
		fprintf(fp, "\t{ %d, %d, %d, %d, %d, %d, %d, %d }", 
			limit[0], limit[1], limit[2], limit[3], 
			limit[4], limit[5], limit[6], limit[7]);
		if(index != 63)
			fprintf(fp, ",\n");
	}
	fprintf(fp, "};");

	fclose(fp);
}

 

728x90

댓글