본문 바로가기
Programming/Algorithm

숫자 야구 게임 만들기

by 작은별하나 2011. 11. 20.
반응형

 

Baseball.cpp
다운로드

Making baseball game.

 

숫자로 즐길 수 있는 숫자 야구게임은 어느정도 머리를 쓰면서 하는 게임이라 중고등학교를 다니면서 한두번씩은 접해본 적이 있는 게임일겁니다. 요즘은 컴퓨터 게임이 대세니 이런 장난스러운 게임도 안 할지도 모르겠네요. 서로 숫자를 생각하고 숫자맞추기를 하는 게임입니다.

한사람이 세자리 숫자를 생각합니다. 이 세자리 숫자의 각 자릿수는 서로 달라야 합니다. 맞추어야 하는 사람은 같은 규칙의 세자리 숫자를 제시합니다. 같은 자리의 숫자가 맞은 경우에는 스트라이크를, 다른 자리의 숫자가 맞은 경우에는 볼을 셉니다. 3 strike가 될 때까지 숫자를 제시해야 하며, 서로 더 적은 횟수만에 숫자를 맞추면 이기는 게임입니다.

예를 들어서 431 이라는 숫자를 생각했고, 제시한 사람은 741 이라고 말합니다. 4라는 숫자는 서로 다른 자리에 있으므로 볼이 되며, 1이라는 숫자는 같은 자리이므로 스트라크로 선언합니다. 숫자를 생각한 사람은 판정을 1스트라이크 1볼이라고 합니다.

숫자 야구게임을 컴퓨터와 한다면 어떨까요? 컴퓨터가 생각한 숫자를 내가 맞추는 경우라면 프로그램상 스트라이크와 볼만 판정해주면 됩니다. 이 경우는 그다지 어려울 것 없는 프로그램일거라고 생각합니다. 그러나 컴퓨터가 내가 생각한 숫자를 맞추어야 한다면 어떨까요? 조금은 복잡할 것 같죠?

제가 대학을 들어가서 처음 들어갔던 서클은 컴퓨터 프로그램 동아리였습니다. 선배들이 C 언어를 가르쳤는데, 제 기억에는 아마도 4번 아니면 5번만에 C 언어 교육을 마쳤던 것 같네요. 그러면서 과제로 내 준 프로그램이 바로 이 숫자 야구게임이었습니다. 계산기 프로그램도 내주었는데, 지금사 생각해도 그 때 실력으로는 죽어다 깨어나도 짤 수 없는 프로그램이었죠. C 언어만 배운다고 프로그램을 자유자재로 짤 수는 없는 것이었죠.

그 때 재미있었던 것은 유닉스의 system() 함수를 이용해서 계산기를 만들고 숫자 야구게임 역시 만든 친구가 있었습니다. 그 친구의 숫자야구 게임의 방법은 바로 이제까지의 숫자 히스토리를 가지고 새로운 숫자가 그 히스토리의 결과에 모두 부합하는지 검사하는 것이었습니다. 사실 이 방법이 가장 좋은 방법이라고 지금도 생각하고 있습니다. 확률적 가능성을 따질 수 있는 특별한 방법이 있는 것도 아닌만큼요.

일단 첫번째는 스트라이크와 볼을 판정하는 함수입니다. 중복루프를 돌면서 같은 자리에 숫자가 맞으면 스트라이크 숫자를 증가시키고 다른 자리에 숫자가 맞으면 볼 숫자를 증가시킵니다.

 

void GetScore(int a[], int b[], int n, int *strike, int *ball)
{
	int ss = 0, bb = 0;
	for( int i = 0 ; i < n ; i++ )
	{
		for( int j = 0 ; j < n ; j++ )
		{
			if( a[i] != b[j] ) continue;
			if( i == j ) ss++; else bb++;
		}
	}
	*strike = ss;
	*ball = bb;
}

 

위 프로그램에서 3자리 숫자를 무조건 검사하지 않고 n이라는 파라미터를 받고 그 자릿수까지만 검사하도록 했습니다. 이것은 깊이우선탐색(Depth First Search)에서 가능한 빨리 잘못된 후보들을 제거하기 위해서였습니다. 이 결과가 히스토리에 있는 결과보다 더 높으면 그 후의 자릿수는 검사하나 마나이니까요.

다음 함수는 히스토리를 통해서 가장 적절한 후보를 추출하는 함수입니다. 자기호출함수를 이용해서 깊이우선탐색 것은 아니지만 불필요한 작업을 컴퓨터에 시키는 것은 왠지 미안하더라고요.

 

 

int Guess(int nHistoryNumber[][5], int nHistoryCount, int *pnGuessNumber)
{
	int GuessInt(int nGuessNumber[][5], int nGuessCount, int *pnGuessNumber, int nLast, int nDepth);
	return GuessInt(nHistoryNumber, nHistoryCount, pnGuessNumber, 0, 0);
}

int GuessInt(int nHistoryNumber[][5], int nHistoryCount, int *pnGuessNumber, int nLast, int nDepth)
{
	if( nDepth == 3 )
	{
		for( int j = 0 ; j < nHistoryCount ; j++ )
		{
			int nStrike, nBall;
			GetScore(nHistoryNumber[j], pnGuessNumber, nDepth, &nStrike, &nBall);
			if( nStrike != nHistoryNumber[j][3] || nBall != nHistoryNumber[j][4] ) return 0;
		}
		return 1;
	}
	else if( nDepth )
	{
		for( int j = 0 ; j < nHistoryCount ; j++ )
		{
			int nStrike, nBall;
			GetScore(nHistoryNumber[j], pnGuessNumber, nDepth, &nStrike, &nBall);
			if( nStrike > nHistoryNumber[j][3] || nBall > nHistoryNumber[j][4] ) return 0;
		}
	}
	int nBase = rand()%9;
	int count = 0;
	for( int i = 0 ; i < 9 ; i++ )
	{
		int k = (nBase+i)%9;
		if( nLast & (1<<k) ) continue;
		pnGuessNumber[nDepth] = k+1;
		nLast |= 1<<k;
		if( GuessInt(nHistoryNumber, nHistoryCount, pnGuessNumber, nLast, nDepth+1) ) return 1;
		nLast &= ~(1<<k);
	}
	return 0;
}

 

사실 후보들을 모두 검사해보고 싶었지만, 히스토리에 쌓인 개수가 적다면 후보들이 많이 나오므로 다 담을 수도 없죠. 유일성검사(즉 두번째 후보가 나타나지 않는다면)를 한다면 답을 확신할 경우도 표시할 수 있겠죠.

 

 

기타 나머지 프로그램 소스는 다음과 같습니다.

 

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>

void GetScore(int a[], int b[], int n, int *strike, int *ball);
void DoComputerGuess();

void main()
{
	srand(time(0));
	while( 1 )
	{
		DoComputerGuess();
		printf("Do you want to continue? (y/n) : ");
		int c = getch();
		printf("%c\n", c);
		if( c == 'n' ) break;
	}
}

void DoComputerGuess()
{
	int Guess(int nHistoryNumber[][5], int nHistoryCount, int *pnGuessNumber);
	printf("Think three digit number.  Each digit numbers are different.\n");
	printf("When you are ready, press key 'r'");
	while( getch() != 'r' ) ;
	putchar('\n');

	int nHistoryCount = 0;
	int nHistoryNumber[20][5];
	int nGuessNumber[3];
	int nStrike, nBall;
	while( 1 )
	{
		int s = Guess(nHistoryNumber, nHistoryCount, nGuessNumber);
		if( s == 0 )
		{
			printf("You cheated!!\n");
			break;
		}
		printf("I guess %d%d%d\n", nGuessNumber[0], nGuessNumber[1], nGuessNumber[2]);
		do
		{
			printf("Enter strike number :");
			int c = getch();
			printf("%c\n", c);
			if( c >= '0' && c <= '3' ) { nStrike = c - '0'; break; }
		} while( 1 );
		do
		{
			printf("Enter Ball number :");
			int c = getch();
			printf("%c\n", c);
			if( c >= '0' && c <= '3' ) { nBall = c - '0'; break; }
		} while( 1 );

		nHistoryNumber[nHistoryCount][0] = nGuessNumber[0];
		nHistoryNumber[nHistoryCount][1] = nGuessNumber[1];
		nHistoryNumber[nHistoryCount][2] = nGuessNumber[2];
		nHistoryNumber[nHistoryCount][3] = nStrike;
		nHistoryNumber[nHistoryCount][4] = nBall;
		nHistoryCount++;
		
		if( nStrike == 3 ) break;
	}
}

 

답이 없는 경우에는 당신이 속였다고 표시합니다. 그리고 게임의 편의성을 위해서 콘솔 입출력 함수인 getch()를 이용하였습니다.

728x90

'Programming > Algorithm' 카테고리의 다른 글

소수 판별하기  (0) 2014.10.07
네이버 개발자 센터에 알고리즘 프로젝트 개설  (0) 2014.09.19
Eight queens problem  (0) 2011.11.14
자료구조의 보석 힙(Heap)  (0) 2011.09.27
Prim algorithm with heap  (0) 2011.09.27

댓글