본문 바로가기
C언어

숫자 야구 게임 만들기 - AI 숫자 맞추기, Player 숫자 맞추기

by 호일이 2021. 4. 6.

AI가 9183 맞추는 과정

 Player가 숫자 맞추는 프로그램은 간단한 게임을 만드는 것에 가깝습니다. 랜덤으로 숫자 4개를 생성하고 플레이어가 입력한 숫자에 따라 스트라이크, 볼을 출력해주면 됩니다.

 

 

ai 숫자 맞추기 설명 이미지

AI가 숫자 맞추는 프로그램은 위 그림과 같이 특정 숫자와 모든 경우의 수를 비교하고, 스트라이크나 볼의 개수가 다른 숫자들을 제거하는 식으로 반복해서 마지막 남은 하나가 정답이 됩니다.

 

 

의사코드

1. Player 숫자 맞추기

ranNums = 겹치지 않는 (0~9)숫자 4개 랜덤 생성

LOOP
	answerNums = 4개의 숫자 입력

	FOR i = 0 to 3
		FOR j = 0 to 3
			IF ranNums[i]와 answerNums[j]의 자릿수와 숫자가 같을 때 THEN
				strike++
			ELSE IF 숫자만 같을 때 THEN
				ball++
			END IF
		END FOR
	END FOR
	strike, ball 출력

	IF strike == 4 THEN
		break
	END IF
END LOOP

 

2. AI 숫자 맞추기
-플레이어가 숫자 4개를 미리 생각해두었다고 가정

allCaseNums = 겹치지 않는 (0~9)숫자 4개의 모든 경우의 수

WHILE allCaseNums.Length != 1
	allCaseNums[0]의 숫자들 출력

	// 정해놨던 숫자 4개와 비교해서 strike, ball 입력
	strike = 입력
	ball = 입력

	allCaseNums = allCaseNums[0]의 숫자와 allCaseNums 리스트 전체를 비교해서 strike와 ball이 같지 않은 것은 전부 제거 후 저장
END WHILE

 

소스코드

1. player 숫자 맞추기

Player.c

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

int* CreateRandomNums();
void GetStrikeAndBall(int nums1[4], int nums2[4], int* strike, int* ball);

void main() {
	int* ranNums;
	int answer[4]; 
	int strike;
	int ball;

	srand((unsigned int)time(NULL));
	
	ranNums = CreateRandomNums();

	//테스트 코드
	/*printf("테스트 중, 정답 : ");
	for (int i = 0; i < 4; i++) {
		printf("%d", ranNums[i]);
	}
	printf("\n");*/

	printf("숫자를 입력하세요. ex)0 1 2 3\n");
	
	while(1) {
		strike = 0;
		ball = 0;
		
		scanf_s("%d %d %d %d", &answer[0], &answer[1], &answer[2], &answer[3]);
		
		GetStrikeAndBall(ranNums, answer, &strike, &ball);

		printf("strike : %d\n", strike);
		printf("ball : %d\n\n", ball);

		if (strike == 4) {
			printf("정답입니다.\n");
			break;
		}
	}
}

int* CreateRandomNums() {
	static int ranNums[4]; // C는 지역 변수의 반환을 지원하지 않습니다.
	int doubleCheck;

	for (int i = 0; i < 4; i++) {
		doubleCheck = 0;
		ranNums[i] = rand() % 10;
		for (int j = 0; j < i; j++) {
			if (ranNums[i] == ranNums[j]) {
				doubleCheck = 1;
				break;
			}
		}

		if (doubleCheck == 1) {
			i--;
			continue;
		}
	}
	return ranNums;
}

void GetStrikeAndBall(int nums1[4], int nums2[4], int* strike, int* ball) {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			if (i == j && nums1[i] == nums2[j]) {
				*strike = *strike + 1;
				break;
			}
			else if (nums1[i] == nums2[j]) {
				*ball = *ball + 1;
				break;
			}
		}
	}
}

 

2. AI 숫자 맞추기

 모든 경우의 수를 저장하고, 삭제하는 것을 동적 리스트로 처리하면 되겠지 하고 쉽게 생각했는데, 찾아보니 C언어는 리스트를 지원하지 않아서 직접 구현해야 했고, 결국 귀찮은 나머지 C++의 기능을 가져왔습니다.

 리스트 종류는 list와 vector 등등이 있는데 vector가 인덱스에 바로 접근이 가능해서 vector를 사용했습니다.

 

AI.cpp

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <string>

using namespace std; // vector, string 편하게 사용하려고 선언

vector<int*> SetAllCaseNums();
bool CheckOverlap(int* nums);
vector<int*> RemoveElements(vector<int*> list, int nums[4], int strike, int ball);
void GetStrikeAndBall(int nums1[4], int nums2[4], int* strike, int* ball);

int main() {
	vector<int*> allCaseNums = SetAllCaseNums();
	int strike, ball;

	while (allCaseNums.size() != 1) {
		printf("AI : ");
		for (int i = 0; i < 4; i++)
			printf("%d", allCaseNums[0][i]);
		printf("\n");

		strike = 0;
		ball = 0;

		printf("strike : ");
		scanf_s("%d", &strike);

		printf("ball : ");
		scanf_s("%d", &ball);
		printf("\n");

		allCaseNums = RemoveElements(allCaseNums, allCaseNums[0], strike, ball);
	}

	printf("AI : 정답은 ");
	for (int i = 0; i < 4; i++)
		printf("%d", allCaseNums[0][i]);
	printf("입니다.\n");

	return 0;
}

vector<int*> SetAllCaseNums() {
	vector<int*> allCaseNums;
	string strNums = "0123";
	int nums = 0;
	int* pNums = new int[4] {0, 1, 2, 3};
	allCaseNums.push_back(pNums);

	while(nums < 9876) {
		// 숫자 증가
		nums = stoi(strNums);	 // string to int
		strNums = to_string(++nums); // int to string

		// 3자리면 앞자리에 0 추가
		if (strNums.length() == 3) {
			strNums.insert(0, "0");
		}

		// 배열화
		pNums = new int[4];
		for (int i = 0; i < 4; i++) {
			pNums[i] = strNums[i] - '0'; // char to int
		}

		// 중복 체크
		if (CheckOverlap(pNums)) {
			continue;
		}

		// 리스트에 저장
		allCaseNums.push_back(pNums); 
	}

	return allCaseNums;
}

bool CheckOverlap(int* nums) {
	for (int i = 0; i < 4; i++) {
		for (int j = i + 1; j < 4; j++) {
			if (nums[i] == nums[j]) {
				return true;
			}
		}
	}
	return false;
}

vector<int*> RemoveElements(vector<int*> list, int nums[4], int strike, int ball) {
	for (int i = 0; i < list.size(); i++) {
		int tempStrike = 0, tempBall = 0;
		GetStrikeAndBall(list[i], nums, &tempStrike, &tempBall);

		if (strike != tempStrike || ball != tempBall) {
			list.erase(list.begin() + i);
			i--; // 한 칸 삭제해서 한 칸 밂
		}
	}
	return list;
}

void GetStrikeAndBall(int nums1[4], int nums2[4], int* strike, int* ball) {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			if (i == j && nums1[i] == nums2[j]) {
				*strike = *strike + 1;
				break;
			}
			else if (nums1[i] == nums2[j]) {
				*ball = *ball + 1;
				break;
			}
		}
	}
}

 

함수 간단 설명

(Player 숫자 맞추기는 생략하겠습니다.)

 

함수로 만들어 사용한 것은 SetAllCaseNums, CheckOverlap, RemoveElements, GetStrikeAndBall 총 4개입니다.

 

SetAllCaseNums는 모든 경우의 수를 저장하고 리스트로 반환하는 함수입니다.

  • 숫자를 string에 저장해서 앞자리가 0인 것도 출력되게 했습니다.
  • 증가는 string에서 못하기 때문에 int로 변환해서 증가하고 다시 string으로 변환하는 과정을 거쳤습니다.
  • string을 리스트에 저장하기 위해 int[4] 배열에 저장하고, 중복체크를 거쳐 리스트에 저장합니다.

과정이 귀찮아서 string을 쓰지 않고, 다른 방법으로 처리하는 게 나을 수도 있을 것 같습니다. 

 

CheckOverlap은 배열에 중복이 있는지 검사하는 함수입니다. 

 

RemoveElements는 모든 경우의 수와 특정 숫자들을 비교해서 스트라이크나 볼의 개수가 다를 때 요소를 삭제하는 AI 숫자 맞추기의 핵심 함수입니다.

 

GetStrikeAndBall은 숫자들끼리 비교해서 스트라이크와 볼의 개수를 구하는 함수입니다.

 

 

 

 

NumersBaseball_Player.exe
0.01MB

Player 숫자 맞추기 실행 파일

 

NumersBaseball_AI.exe
0.01MB

AI 숫자 맞추기 실행 파일

 

 모든 경우의 수를 비교하므로, 사람보다 훨씬 빨리 맞추는 것 같습니다. 조금 난이도를 낮춰서 사람 vs AI를 만들어보면 재밌을 것 같네요.

반응형

댓글