본문 바로가기
JAVA

블랙잭 게임 코드 피드백 #2

by mingutistory 2020. 3. 29.
728x90

 

https://minchoi0912.tistory.com/50?category=862519

 

블랙잭 게임 코드 피드백 #1

OKKY fender님의 칼럼 지적대로 지금의 나는 객체지향적 사고를 전혀 하지 못하고 있다는 인상을 계속 받고 있다. 아무래도 책을 다시 한번 읽는 것이 필요해보인다. 코드 공유하면서도 생각했지만 누구나 코드를..

minchoi0912.tistory.com

이어서 계속 피드백 해본다. 그 전에 이전에 지정해 둔 객체의 기본 동작이 변경된 부분이 존재한다. 

 

카드

- 무늬, 숫자가 존재함

  > 무늬 : 스페이드, 크로버, 다이아몬드, 하트

  > 숫자 : A(1), 2, 3, 4, 5, 6, 7, 8, 9, J(10), Q(10), K(10)

 

카드덱

- 52개의 카드로 이루어져 있음

- 딜러, 게이머에게 카드를 제공함

 

딜러

- 카드덱에서 카드를 받음

- 카드 총합이 16 이하면 17 이상이 될 때 까지 카드를 더 받음

- 카드를 오픈 함

 

게이머

- 카드덱에서 카드를 받음

- 카드를 오픈 함

 

규칙

- 딜러와 게이머의 점수를 계산한다.

- 승패를 판단한다. 

+ 버스트 여부 판단한다.

 

카드덱까지 변경해봤으니 이제 딜러, 게이머를 변경해보겠다. 

 

변경내용

1. 딜러와 게이머를 플레이어라는 하나의 인터페이스로 묶는다.

딜러와 게이머는 같은 동작을 하는 경우가 있다. 카드덱에서 카드를 받는 경우, 카드를 오픈하는 하는 경우가 그렇다. 

추상화의 과정이다. 추상화는 단어만 계속 들었지 구현을 하려고 할 때 딱 생각나는 단어가 아니었다. 배웠는데 실제로 구현을 못하고 있는 개념 중 하나.

 

Player 인터페이스를 생성하였다. 생각해보면 추상클래스 역시도 이용 할 수있지만 나중에 유지보수를 생각하면 인터페이스를 이용하는 것이 더 좋다고 한다. 시간이 지나면 부모 클래스를 손 댈 수 없을 정도로 확장 되어 있을 수 있기 때문. 동작은 같지만 구현부가 다른 경우가 있을 수 있음. 서로 다른 객체는 최대한 거리를 두는 것이 좋음. 

 

public interface Player {
	
	void getCard(Card card);
	void printCards();

}

 그래서 1. 카드덱에서 카드를 받음 2. 카드를 오픈 함 이라는 공통 동작이 존재하고 그 것을 Player 인터페이스로 묶어 메소드 구현을 강제화하였다. 

 

참고하고 있던 블로그(하단 기재)들이 구현하고자 하는 게임의 모습과 내가 생각한 블랙잭의 모습이 다르다는 것을 이 시점에서 깨닳았다. 일단 내가 구현했던 모습 그대로 다시 코드를 정리해서 보이는 것이 목표이기 때문에 그대로 따라가지 않고 내 식으로 변형해본다. 이 이후로는 코드 정리가 중구난방이 될 수 있다. 

 

크게 다른 점

- 게임을 게이머의 시점으로 만들 것임

- 딜러의 카드를 하나만 보여주고 그것을 기반으로 게이머가 hit or stand 둘 중 하나의 행동을 선택함

- 1:1의 상황만 고려할 것임

 

2. BlackJack 클래스의 메소드를 분리한다.

 

	public void play() throws IOException {
		
		System.out.println("-------------BlackJack-------------");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		Dealer dealer = new Dealer();
		Gamer gamer = new Gamer();
		Rule rule = new Rule();
		CardDeck cardDeck = new CardDeck(); 
		
		initGame(cardDeck, gamer, dealer); 
		playingGame(br, cardDeck, gamer); 
		
	}

실행 클래스에서 실행하는 play() 메소드에서는 실제로 메소드를 호출하기만 한다. 

initGame() 메소드는 gamer와 dealer에게 2개의 카드를 나눠주는 메소드.

PlayingGame은 gamer가 행동을 정할 수 있게 해주는 메소드다.

 

 

3. 카드의 총합을 어떻게 할지 고민하기

딜러의 행동 중 16점 이하이면 카드를 계속 뽑아야하는 행동이 있다. 또한 마지막의 승부는 딜러와 게이머의 총합의 비교해야한다. 그래서 나는 이 총합을 구하는 행동이 딜러, 게이머에게 모두 필요한 메소드라고 생각해서 Rule 객체에 때려 넣었었는데 카드 자체에 point 변수를 추가하는 힌트를 얻었다. 

 

처음에는 그냥 계속 입장을 고수하려고 했는데  (getSum() 메소드를 Rule 객체에 만드는 방식) printCards 매소드가 실행되면서 어차피 본인들이 가지고 있는 deck을 한번 for문으로 돌리는 것을 보고 point가 있다면 거기서 더해주기만 하면 되고, return 값이 없는 상태니까 여기서 return까지 해주면 되지 않을까 라는 생각을 했다. 그리고 심지어 계속 화면에 출력 할 때 게이머 출력 시 옆에 총합을 적어주고 싶었던 그것을 한 번에 해결 할 수 있지 않은가! 

 

생각했으면 빨리 실행해본다. 

 

	@Override
	public int printCards() {
		StringBuilder sb = new StringBuilder();
		sb.append("Gamer's Card : ");
		
		int sum = 0; 
		
		for(Card card : deck) {
			sb.append(card.toString());
			sb.append(" ");
			sum += card.getPoint();
		}
		
		System.out.println(sb.toString());
		System.out.println(" 총합 : " + sum);
		
		return sum; 
	}

그렇게 탄생한 코드가 이것이었는데 나는 출력을 위해서 이 메소드를 사용하고 싶었던 건데 총합을 여기서 리턴한다는게 또 두개의 일을 하나로 묶었구나 했다. 나는 이런게 한번에 해결 할 수 있는 좋은 방법이라고 계속 생각해 왔었는데 메소드의 역할을 분명히 하는 것이 뒤에 유지보수에 유리하다고 요즘 계속 생각하던 중.

 

대신 Player 인터페이스에 int getSum() 메소드를 추가하여 구현을 강제화했다. 

 

@Override
	public int getSum() {
		int sum = 0; 
		for(Card card : deck) {
			sum += card.getPoint();
		}
		return sum; 
	}

 

4. 16 이하 일 때 카드 뽑는 동작 메소드 나누기

	// 합 16 이하인지 확인
	private boolean isLessThan() {
		return this.getSum() <= 16;
	}
	
	// 합이 16 이하인 경우 이상으로 만들기 
	public void checkDealerCards(CardDeck cardDeck) {
		while(isLessThan()) {
			Card card = cardDeck.splitCard();
			this.getCard(card);
		}
	}	

Dealer 클래스에 두 메소드를 추가하였다. 합이 16 이하인지 확인하고 이상으로 만드는 동작이다. 이 동작은 게이머에게는 필요하지 않는 동작이기 때문에 player 인터페이스에 들어가지 않는다. 

 

5. Rule 클래스 코드 변경하기

 

이것 저것 정말 많은 메소드를 가지고 있던 Rule 클래스의 메소드를 2개로 줄였다. 상단에서 딜러와 게이머의 합을 계산하는 것을 Rule 클래스의 동작으로 지정했는데 합을 계산하는 것은 Rule 객체가 할 필요가 없다고 판단했다. 

그래서 위 3번과 같이 변경해주었고 대신 버스트 여부를 판단하는 메소드를 추가했다.

 

ublic class Rule { 
	
	private static final int BLACKJACK_NUM = 21;
	
	// 승패 확인
	public void getWinner(int dealerSum, int gamerSum) {
		
		int dealerMinus = BLACKJACK_NUM - dealerSum;
		int gamerMinus = BLACKJACK_NUM - gamerSum;
		
		if(dealerMinus > gamerMinus) {
			System.out.println("게이머 승리");
		}else if(dealerMinus == gamerMinus) {
			System.out.println("무승부");
		}else {
			System.out.println("딜러 승리");
		}
	}
	
	// 버스트 확인
	public boolean isBust(Player player, int sum) {
		String name = player.getName();
		if(sum > 21) {
			System.out.println(name + " Bust");
			return true;
		}else {
			return false; 
		}
	}
}

원래 인터페이스로 Gamer와 Dealer을 합쳐 놓지 않았던 이전 코드에서는 String who 변수로 딜러 혹은 게이머의 버스트를 확인하는 것인지 확인했는데 현재는 Player 인터페이스로 매개변수를 선언 할 수 있어서 확실히 편하다. 

 

그리고 승자의 출력을 위해서 Gamer, Dealer에게 각각 정적 필드 name을 지정해주었따. 사실 이 부분은 다른 블로그(하단 기재)를 참고하여 만든 부분인데 이 부분을 getWinner에서 나는 지금 활용하지 못하고 있다. 이 부분은 조금 더 생각해봐야 하는 부분. 

 

사실 코드 내가 생각해서 다시 완료 했는데 마음에 안드는 부분들이 몇가지 있어서 조금 더 생각 필요 할 것 같다. 

계속 써나가면서 변경해야지.

전체 코드는 깃허브에서 확인 가능. 

 

그리고 내가 지금까지 커밋을 진짜 제대로 생각 못하고 있었구나 라고 느꼈다. 커밋 치는거 제대로 습관화하고 메세지도 더 자세하게 잘 작성하는 걸로. 

 

https://github.com/minchoi9509/blackjack

 

minchoi9509/blackjack

블랙잭 게임 수정 중. Contribute to minchoi9509/blackjack development by creating an account on GitHub.

github.com

 

이 분들의 글이 없었으면 절대 이 만큼 생각하지 못했을 것이다. 

https://jojoldu.tistory.com/62

 

객체지향 좀 더 이해하기 - 블랙잭 게임 구현 (1)

순수 Java로 이루어진 프로젝트 객체지향을 이해하는데 있어 게시판은 좋은 예제가 아니라는 자바지기(박재성)님과 OKKY fender님의 이야기로 시작한 프로젝트 Java로 웹을 한다고 하면서 실제로 Java와 객체지향..

jojoldu.tistory.com

https://blog.naver.com/bell2017/221240394331

 

블랙잭3 인터페이스 사용+ 클래스 구조 만들기

요번 글은 인터페이스를 사용 해야 하는 이유에 대해 알아보겠습니다. <인터페이스를 사용해야 하는 경...

blog.naver.com

 

300x250

댓글