본문 바로가기
자료구조and알고리즘

[알고리즘] 숫자 야구게임

by jackypark 2022. 7. 13.

숫자야구 게임을 코드로 구현해 보았다.

팀원과 함께 머리를 맞대고 짜보았다.

규칙은 아래 링크를 참고하자

https://namu.wiki/w/%EC%88%AB%EC%9E%90%EC%95%BC%EA%B5%AC

 

숫자야구 - 나무위키

간단하게 내기삼아 할 수 있는 게임이며 연필 및 종이 게임이다. 원제는 Bulls and Cows 이다. Bulls and Cows는 상업적으로 판매되는 보드 게임 마스터마인드보다 앞서 두 명 이상의 플레이어를 위한 오

namu.wiki

다음은 메인 코드와 경우의 수 파악과 맞춰야 할 수를 생성하는 메서드이다

import java.util.*;

public class Baseball {
	TreeSet<String> guess=new TreeSet<String>();
	Set<Integer> overlap=new HashSet<Integer>();
	Integer[] myArr=new Integer[] {0,1,2,3,4,5,6,7,8,9};
	int index=0;
	int strike=0;
	int ball=0;
	int cnt=0; // 몇번만에 답을 확인했는지를 위해
	int flag=0;
	String[] batter=new String[3];
	String number=null;
	boolean repeat=true;
	public static void main(String[] args) {		
			Baseball b=new Baseball();
			for(int i=0;i<1000;i++) {
				b.repeat=true;
				b.randMaker(); // 3스트라이크 잡아야 할 수
				b.perm(b.myArr, 0); //가능성 있는수 뽑기
				b.startGame();	//경기 시작
				b.overlap.clear();//한게임 끝나면 경우의 수
			}
		System.out.println("1000게임 후 평균: "+b.cnt/1000);	
		
	}
	public void perm(Integer[] arr,int depth) {//경우의 수를 뽑기 위해 10P3 경우
		if(depth==3) {//숫자 10개중 3개뽑았을때 조합가능한 수를 set에 추가
			guess.add(printNum(arr));//경우의 수들 set 리스트에 추가
			return;
		}
		for(int i=depth;i<10;i++) {//10개숫자내에서 조합가능한수 스왑
			swap(arr,depth,i);
			perm(arr,depth+1);
			swap(arr,depth,i);			
		}
		
	}
	
	public void swap(Integer[] arr,int depth,int i) {
		int temp=arr[depth];
		arr[depth]=arr[i];
		arr[i]=temp;
	}
	public String printNum(Integer[] num) { //숫자를 문자열로 바꾸기 위해
		String changer=null;
		int pNum=num[0]*100+num[1]*10+num[2]*1;
		if(pNum<100) {
			changer="0"+Integer.toString(pNum);
		}else {
			changer=Integer.toString(pNum);
		}
		return changer;
	}
	public void randMaker() { //맞출 수를 랜덤 생성
		Random rand=new Random();
		for(int i=0; i<batter.length; i++) {
	         int randNum = rand.nextInt(10);
	         if(overlap.contains(randNum)) {//중복확인
	            i--;
	            continue;
	         }else {
	            batter[i] = randNum+"";//문자열로 추가
	            overlap.add(randNum);
	         }
	      }
	}

다음은 경기장? 판을 깔아주기 위한 메서드이다.

public void startGame() {
		while(repeat) {
			Iterator<String> iterator = guess.iterator();
			while(iterator.hasNext()) {				
					if(repeat==false) {
						break;
					}
					Iterator<String> clone = guess.iterator();				
					int size=0;//경우의수의 갯수를 넣기 위해
					while(clone.hasNext()) { //경우의수가 몇개 있는지 파악하기 위해서
						clone.next();
						size++;//
					}
					clone=guess.iterator();
					int ranThrow=new Random().nextInt(size);//경우의갯수를 크기로 난수던질준비
					int cloneBall=0;
					while(clone.hasNext()) {
						if(cloneBall==ranThrow) { //난수번째 때 던지는거지 공을
							number=clone.next();//던질수가 설정됨
							break;
						}
						clone.next(); //같지 않으니 넘겨
						cloneBall++;					
					}
//					while(clone.hasNext()) {  // 원래는 난수로 던지지않고 경우의 수 가운데 값만 게속 던지게 했엇음
//						if(cloneBall==(size/2)) {
//							number=clone.next();
//							break;
//						}
//						clone.next();
//						cloneBall++;					
//					}
				
				guess.remove(number); // 현재 비교할 값은 제거 이유: 어차피 다시는 이숫자를 안쓸꺼기 때문
				iterator=guess.iterator();//위에 수 제거했으니깐 다시 iterator 생성
				System.out.println(number+" 던진다 ~"); //던질수 출력
				ballCount(number); //스트라이크 볼 갯수 알기 위한 메소드
				refree(iterator);// 심판같은역할 필요없는 경우의 수들을 제거를 위한 메소드
			}
		}
	}
public void ballCount(String number) { //스트라이크 볼 전광판같은느낌
		for(int i=0;i<batter.length;i++) {
			if(number.contains(batter[i])) { // 던진수(난수)가 맞춰야 할 수의 숫자들을 가지고 있다면
				int index=number.indexOf(batter[i]);
				if(index==i) {//해당번호와 자리수가 같으면
					strike++;// 스트라이크 카운트 증가
				}else {
					ball++;//볼 카운트 증가
				}						
			}
		}
		System.out.print("스트라이크 존: ");
		for(int i=0; i<batter.length; i++) {
			System.out.print(batter[i]);// 맞춰야 할 수 출력
		}
		System.out.println();
		System.out.println(strike+" Strike~~");// 몇 스트라이크
		System.out.println(ball+" Ball~~"); // 몇 볼
		cnt++;
	}
public void refree(Iterator<String> iterator) {
		if(strike == 0 && ball == 0) { //0strike 0ball
			out(number,iterator);
		}else if(strike == 0 && ball ==1) {//0strike 1ball
			oneB(number,iterator);
		}else if(strike == 0 && ball == 2) {//0strike 2ball
			twoB(number, iterator);
		}else if(strike == 0 && ball == 3) {//0strike 3ball
			threeB(number, iterator);
		}else if(strike ==1 && ball == 0){//1strike 0ball
			oneS(number,iterator);
		}else if(strike == 1 && ball == 1) {//1strike 1ball
			oneSOneB(number, iterator);
		}else if(strike == 1 && ball ==2) {//1strike 2ball
			oneSTwoB(number,iterator);
		}else if(strike == 2 && ball == 0){//2strike
			twoS(number,iterator);
		}else {//삼진
			threeS(number,iterator);
			strike=0; //삼진 초기화
			ball=0; //삼진 초기화
			repeat=false;//게임 종료
			//System.exit(0);
		}
		strike=0;
		ball=0;
	}

1 ball 일 때 메커니즘 자기가 현재 서있는 자리는 자기가 있을 수 있는 수가 절대 아니다.

ex) 123이 1 ball일 때

      1xx  형태 인 수 전부 삭제

      x2x  형태인 수 전부 삭제

      xx3  형태인 수 전부 삭제

public void oneB(String number,Iterator<String> iterator) {// 1ball이란 말은 자기들중에 있는수가 있는데 자기 자신의 자리는 절때 아니라는거니깐	
		while (iterator.hasNext()) {
			String nextNum = iterator.next();
			if(number.charAt(0) == nextNum.charAt(0)) {
				iterator.remove();
			}else if(number.charAt(1) == nextNum.charAt(1)) {
				iterator.remove();
			}else if(number.charAt(2) == nextNum.charAt(2)) {
						iterator.remove();
					}
				}
	}

2 ball 일 때 메커니즘은 아래 예시와 같다.

123 2 ball

x12 빼고 다 삭제인데 근데 X자리에는 3이 오면 안 되니깐 X가 3오는 수 삭제
x31 빼고 다 삭제인데 X자리에 1 이오는 수 삭제
x32 
2x1
3x1
3x2
21x
31x
23x 

public void twoB(String number,Iterator<String> iterator) {
		while (iterator.hasNext()) {
			String nextNum = iterator.next();
			if(!number.substring(0, 2).equals(nextNum.substring(1))) {
				if(number.charAt(0) != nextNum.charAt(2)|| number.charAt(2) != nextNum.charAt(1)) {
					if(number.charAt(1) != nextNum.charAt(2) || number.charAt(2) != nextNum.charAt(1)) {
						if(number.charAt(1) != nextNum.charAt(0) || number.charAt(0) != nextNum.charAt(2)) {
							if(number.charAt(2) != nextNum.charAt(0) || number.charAt(0) != nextNum.charAt(2)) {
								if(number.charAt(2) != nextNum.charAt(0) || number.charAt(1) != nextNum.charAt(2)) {
									if(number.charAt(1) != nextNum.charAt(0) || number.charAt(0) != nextNum.charAt(1)) {
										if(number.charAt(2) != nextNum.charAt(0) || number.charAt(0) != nextNum.charAt(1)) {
											if(number.charAt(1) != nextNum.charAt(0) || number.charAt(2) != nextNum.charAt(1)) {
												iterator.remove();
											}else if(number.charAt(0) == nextNum.charAt(2)) {
												iterator.remove();
											}
										}else if(number.charAt(1) == nextNum.charAt(2)) {
											iterator.remove();
										}
									}else if(number.charAt(2) == nextNum.charAt(2)) {
										iterator.remove();
									}
								}else if(number.charAt(0) == nextNum.charAt(1)) {
									iterator.remove();
								}
							}else if(number.charAt(1) == nextNum.charAt(1)) {
								iterator.remove();
							}
						}else if(number.charAt(2) == nextNum.charAt(1)) {
							iterator.remove();
						}
					}else if(number.charAt(0) == nextNum.charAt(0)) {
						iterator.remove();
					}
				}else if(number.charAt(1) == nextNum.charAt(0)){
					iterator.remove();
				}
			}else if(number.charAt(2) == nextNum.charAt(0)) {
				iterator.remove();
			}
		}
	}

3 BALL일 때 메커니즘은 그 숫자들을 가지고 있지 않으면 나머지 수들을 삭제하는 것이다.

ex) 123 3 ball일 때

1,2,3을 가지고 있지 않는 모든 수들을 제거합니다..

하지만 이 메커니즘은 수정이 필요할 것 같습니다.

123일 때 스트라이크로 만들 수 있는 경우의 수는 312 231 밖에 없는데 위 메커니즘은 쓰면 원하는 답을 찾는데 시간이 더 거릴 거 같습니다.

public void threeB(String number,Iterator<String> iterator) {
		while (iterator.hasNext()) {
			String nextNum = iterator.next();
			if(!nextNum.contains(number.substring(0, 1))) {
				iterator.remove();
			}else if(!nextNum.contains(number.substring(1, 2))) {
				iterator.remove();
			}else if(!nextNum.contains(number.substring(2, 3))) {
				iterator.remove();
			}
			}
	}

1 Strike일 때 메커니즘은 세 숫자 중에 하나는 반드시 숫자도 맞고 자리도 맡는다는 말이니깐 아래 예시와 같이 삭제한다. 

Ex) 123 1 strike
1xx
x2x
xx3

위 숫자 모양을 가진 숫자들 빼고 다 삭제

public void oneS(String number,Iterator<String> iterator) {
		while (iterator.hasNext()) {
			String nextNum = iterator.next();
			if(number.charAt(0) != nextNum.charAt(0)) {
				if(number.charAt(1) != nextNum.charAt(1)) {
					if(number.charAt(2) != nextNum.charAt(2)) {
						iterator.remove();
					}else if(nextNum.substring(0,2).contains(number.substring(0, 1))||nextNum.substring(0,2).contains(number.substring(1, 2))) {
						iterator.remove();
					}
				}else if(nextNum.substring(0,1).contains(number.substring(0, 1))||nextNum.substring(0,1).contains(number.substring(2, 3))) {
					iterator.remove();
				}else if(nextNum.substring(2,3).contains(number.substring(0, 1))||nextNum.substring(2,3).contains(number.substring(2, 3))) {
					iterator.remove();
				}
			}else if(nextNum.substring(1).contains(number.substring(1, 2))||nextNum.substring(1).contains(number.substring(2, 3))) {
				iterator.remove();
			}
		}
	}

2 Strike일 때 메커니즘은 

123 2 strike

12x

x23

1x3

위 3 경우의 패턴을 가진 수가 아닐 경우 다 삭제 즉, 위 패턴인 수만 경우의 수로  살린다.

public void twoS(String number,Iterator<String> iterator) {
			while (iterator.hasNext()) {
				String nextNum = iterator.next();
				if(number.charAt(0) != nextNum.charAt(0) || number.charAt(1) != nextNum.charAt(1)) {
					if(number.charAt(1) != nextNum.charAt(1) || number.charAt(2) != nextNum.charAt(2)) {
						if(number.charAt(0) != nextNum.charAt(0) || number.charAt(2) != nextNum.charAt(2)){
							iterator.remove();
						}
					}
				}
			}
		}

1 Strike 1 Ball일 때 메커니즘은 

Ex) 123 1s 1b
1x2x자리에는 이제 더 이상 3은 절대 올 수 없게 된다
13x x자리에는 이제 더 이상 2는 절대 올 수 없게 된다
x21x자리에는 이제 더 이상 3은 절대 올 수 없게 된다
32x 
2x3
x13

위경우를 성립하지 않는 번호를 다 삭제했다.+ x자리에 남는 번호는 절대 올 수 없다.

public void oneSOneB(String number,Iterator<String> iterator) {
			while (iterator.hasNext()) {
				String nextNum = iterator.next();
				if(number.charAt(0) != nextNum.charAt(0) || number.charAt(1) != nextNum.charAt(2)) {
					if(number.charAt(0) != nextNum.charAt(0) || number.charAt(2) != nextNum.charAt(1)) {
						if(number.charAt(1) != nextNum.charAt(1) || number.charAt(0) != nextNum.charAt(2)) {
							if(number.charAt(1) != nextNum.charAt(1) || number.charAt(2) != nextNum.charAt(0)) {
								if(number.charAt(2) != nextNum.charAt(2) || number.charAt(1) != nextNum.charAt(0)) {
									if(number.charAt(2) != nextNum.charAt(2) || number.charAt(0) != nextNum.charAt(1)) {
										iterator.remove();
									}else if(number.charAt(1) == nextNum.charAt(0)) {
										iterator.remove();
									}
								}else if(number.charAt(0) == nextNum.charAt(1)) {
									iterator.remove();
								}
							}else if(number.charAt(0) == nextNum.charAt(2)) {
								iterator.remove();
							}
						}else if(number.charAt(2) == nextNum.charAt(0)) {
							iterator.remove();
						}
					}else if(number.charAt(1) == nextNum.charAt(2)) {
						iterator.remove();
					}
				}else if(number.charAt(2) == nextNum.charAt(1)) {
					iterator.remove();
				}
				
			}
		}

1 Strike 2 Ball 일 때 메커니즘은 3 ball이랑 비슷하다 그 숫자들을 안 가지고 있으면 삭제이다.

ex) 123이면 1,2,3을 가지지 않는 숫자들은 다 삭제이다

근데 사실 이 메커니즘도 3 ball와 마찬가지로 수정이 필요할 것 같다 만약 123이 1 s2 b 이면 3 strike 경 우의는 132 321 213밖에 경우의 수밖에 안 남는다. 그게 사실 내 코드처럼 삭제하면 저 3개만 남는 거 같지 않아서 수정이 필요할 것 같다는 생각이 들었다.

public void oneSTwoB(String number,Iterator<String> iterator) {
			while (iterator.hasNext()) {
				String nextNum = iterator.next();
				if(!nextNum.contains(number.substring(0, 1))) {
					iterator.remove();
				}else if(!nextNum.contains(number.substring(1, 2))) {
					iterator.remove();
				}else if(!nextNum.contains(number.substring(2, 3))) {
					iterator.remove();
				}
			}
			
		}

0 Strike 0 Ball일 경우는 out이라고 메서드명을 지정했다 출루했다 라는 느낌이다. 

메커니즘은 3 ball과 반대로 그 수를 가지고 있는 숫자는 다 삭제다. 

ex) 123이 0 strike 0 ball 이면 1,2,3을 가진수 전부 삭제.

public void out(String number,Iterator<String> iterator) {
			while (iterator.hasNext()) {
				String nextNum = iterator.next();
				if(nextNum.contains(number.substring(0, 1))) {
					iterator.remove();
				}else if(nextNum.contains(number.substring(1, 2))) {
					iterator.remove();
				}else if(nextNum.contains(number.substring(2, 3))) {
					iterator.remove();
				}
			}
		}

3 Strike 일 때는 삼진이니깐 그냥 삼진 됐다고 출력시키고 경기 종료시키면 된다

public void threeS(String number,Iterator<String> iterator) {
		System.out.println(number+" : !!!삼진!!!");
		System.out.println("count : "+ cnt); //몇번만에 맞췄는지 확인하기 위해서
		ystem.out.println("-----------------------------------------------------------");
		System.out.println();

		}

코드를 실행시키면 아래와 같은 결과처럼 나온다.

출력결과

그리고 반복문을 통해서 1000번의 게임을 실행했을 때 한게임을 이기는데 걸리는 횟수는 평균이 5가 나왔다.

 

비록 알고리즘을 교수님께서 힌트를 주셔서 힌트를 바탕으로 짜긴 했지만 시행착오가 많았다. 처음에 코드 돌렸을 때 한 게임당 숫자를 찾는 횟수가 10 이상 나왔는데 팀원들의 머리를 한대 모아 열심히 짠 결과 9-> 8-> 7 ->6->5 수정할 때마다 횟수가 하나씩 줄어나갔다. 비록 코드가 길어서 비효율적으로 보일 수도 있지만 인터넷을 안 보고 메모장에 경우의 수를 일일이 확인해 가며 알고리즘을 짤 수 있다는 게 좋았다. 혼자 했으면 빠르게 포기했을 것 같은 코드들이지만 서로 멘탈 케어하며 열심히 한 거 같다.

 

 

댓글