티스토리 뷰

자바 로고

 

트럼프 카드

 

들어가기 전 설명

 

현재 구현한 코드들은 GitHub 에 있습니다. 글을 보다가 헷갈리시면 소스코드로 참고 해주시기 바랍니다.

또한, JAVA console으로 구현하였습니다.

 

 

 

게임 규칙

 

  • 게임 딜러와 플레이어 1대1로 진행합니다.
  • 딜러를 포함한 플레이어에게 카드 두 장을 나누어주고, 딜러의 카드 한 장은 게이머에게 보이지 않습니다.
  • 딜러는 카드의 합이 16 이하면 무조건 한 장을 더 받아야 하고, 17 이상의 경우에는 멈추어야 합니다.
  • 딜러의 카드와 합이 같으면 비깁니다.

 

  • 게이머는 먼저 받은 카드 두 장의 합이 21에 못 미치면 한 장씩 더 받을 수 있고, 멈출 수도 있습니다.
  • 게이머는 카드의 합이 딜러보다 먼저 21이 되거나 딜러보다 21에 가깝게 되면 이기고, 카드를 더 받았는데 21을 초과하면 집니다.

 

  • 카드 중에 에이스카드(A) 는 1이나 11으로 취급할 수 있고, 10, J, Q, K는 모두 10으로 계산합니다.
  • 카드 오픈을 했을 때 게이머의 카드 숫자합이 21미만이면 딜러와 게이머 카드 숫자합을 측정합니다.
    이 때, 딜러도 21을 넘으면 딜러가 게임에서 집니다.
  • 카드 합이 21이 되면 블랙잭(Blackjack)이 되고 즉시 승리합니다

 

 

https://ko.wikipedia.org/wiki/%EB%B8%94%EB%9E%99%EC%9E%AD

 

블랙잭 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 블랙잭 게임의 예 블랙잭(영어: Blackjack)은 트웬티원(영어: Twenty-one) 또는 벵테텅(프랑스어: Vingt-et-un)이라고도 하며, 세계의 카지노에서 가장 널리 행해지는 플

ko.wikipedia.org

 

 

 

리팩토링

 

Game.java

비즈니스 로직에 있는 초기 설정할 때 딜러와 게이머가 2장씩 받는 메서드가 있었습니다.

그냥 함수 2개씩 호출하는데 더 좋을 수 있지만,
만약에 아래와 같은 요구 사항이 발생한다면 유지보수가 편할 수 있도록 확장성있게 리팩토링 하였습니다.

 

  1. 카드 3장씩 뽑을게요.
  2. 게이머가 1명이 아니고 여러명이예요.
/* 이전 코드 */
private void initialDeal() {
    dealer.draw(cardDeck);
    dealer.draw(cardDeck);
    gamer.draw(cardDeck);
    gamer.draw(cardDeck);
}

/* 리팩토링 코드 */
private void initialDeal() {
    IntStream.range(0, 2).forEach(i -> {
        dealer.draw(cardDeck);
        gamer.draw(cardDeck);
    });
}

 

 

 

Gamer.java, Dealer.java 그리고 Player.java

player 인터페이스를 이용하여 게이머와 딜러를  구현해주었습니다.

둘이 카드를 뽑는 것은 동일 하기 때문에 Player 인터페이스 구현을 추상 클래스로 하고,

공통적인 카드 뽑는 로직을 추상 클래스에 두려고 합니다.

/* 이전 코드 */
public interface Player {
    void draw(CardDeck cardDeck);
}

public class Dealer implements Player{

    private List<Card> haveCards = new ArrayList<>();
    private String name = "딜러";
    private int score = 0;

	~ 중간 생략

    // 카드 뽑기
    @Override
    public void draw(CardDeck cardDeck) {
        Card card = cardDeck.getCard();
        haveCards.add(card);
    }
}

// 게이머도 동일..

 

그래서 아래와 같이 인터페이스를 추상 클래스에 구현한 것입니다.

그래서 공통적인 로직은 추상클래스에 두고 공통 필드도 추상클래스로 올려두었습니다.

/* 리팩토링 코드 */
public interface Player {
    void draw(CardDeck cardDeck);
    List<Card> getHaveCards();
    String getName();
}

// 추상클래스
public abstract class AbstractPlayer implements Player {
    protected List<Card> haveCards = new ArrayList<>();
    protected String name;
    protected int score = 0;

    public AbstractPlayer(String name) {
        this.name = name;
    }

    @Override
    public List<Card> getHaveCards() {
        return haveCards;
    }

    @Override
    public String getName() {
        return name;
    }

    // 카드 뽑기
    @Override
    public void draw(CardDeck cardDeck) {
        Card card = cardDeck.getCard();
        haveCards.add(card);
    }
}

// 상속1
public class Gamer extends AbstractPlayer {
    public Gamer(String name) {
        super(name);
    }
}

// 상속2
public class Dealer extends AbstractPlayer {

    public Dealer() {
        super("딜러");
    }
}

 

 

 

 

 

 

테스트 코드

 

Tip) Card.java 안에 있는 Enum은 default 접근제한자를 가지고 있어서 테스트 코드 패키지하고 맞춰줘야 사용할 수 있다.

 

  1. 카드가 52장으로 만들어 졌는가?
  2. 카드를 52장 뽑은 후 또 카드를 뽑을라면 내가 만들어둔 DeckEmptyException이 발생되는가?
  3. 서로 카드 숫자의 합이 21(블랙잭 점수) 일 때 승리자는 게이머인가?
  4. 서로 카드 숫자의 합이 블랙잭 점수보다 낮으면서 같으면 무승부인가?
  5. 내가 카드숫자의 합이 11인데 A(에이스) 카드를 뽑게 되면 11이 아닌 1로 카드 숫자가 증가 되는가?

 

@SpringBootTest
class BlackjackApplicationTests {

	private CardDeck cardDeck;

	@BeforeEach
	public void setUp() {
		cardDeck = new CardDeck();
	}

	@Test
	@DisplayName("카드가 52장 생성 되는가?")
	public void testCardDeckInitialization() {
		Assertions.assertThat(cardDeck.getCardDeck().size()).isEqualTo(52);
	}

	@Test
	@DisplayName("카드를 52장 뽑은 후 또 카드를 뽑을라면 내가 만들어둔 DeckEmptyException이 발생되는가?")
	public void testGetCardFromEmptyDeck() {
		for (int i = 0; i < 52; i++) {
			cardDeck.getCard();
		}
		Assertions.assertThatThrownBy(() -> cardDeck.getCard())
				.isInstanceOf(DeckEmptyException.class)
				.hasMessage("카드를 다 뽑았습니다.");
	}

	@Test
	@DisplayName("서로 카드 숫자의 합이 21(블랙잭 점수) 일 때 승리자는 게이머인가?")
	public void testWinnerCheck1(){
		Rule rule = new Rule();
		Dealer dealer = new Dealer();
		Gamer gamer = new Gamer("Gamer");

		gamer.getHaveCards().addAll(createBlackjackHand());
		dealer.getHaveCards().addAll(createBlackjackHand());
		String winner = rule.getWinner(dealer, gamer);
		Assertions.assertThat(winner).isEqualTo(gamer.getName());

	}

	@Test
	@DisplayName("서로 카드 숫자의 합이 블랙잭 점수보다 낮으면서 같으면 무승부인가?")
	public void testWinnerCheck2(){
		Rule rule = new Rule();
		Dealer dealer = new Dealer();
		Gamer gamer = new Gamer("Gamer");

		gamer.getHaveCards().addAll(createNonBlackjackHand());
		dealer.getHaveCards().addAll(createNonBlackjackHand());
		String winner = rule.getWinner(dealer, gamer);
		Assertions.assertThat(winner).isEqualTo("무승부");

	}

	@Test
	@DisplayName("내가 카드숫자의 합이 11인데 A(에이스) 카드를 뽑게 되면 11이 아닌 1로 카드 숫자가 증가 되는가?")
	public void testCardScore(){
		Rule rule = new Rule();

		List<Card> cards = new ArrayList<>();
		cards.add(new Card(Card.Pattern.DIAMOND, Card.Denomination.THREE));
		cards.add(new Card(Card.Pattern.CLUB, Card.Denomination.EIGHT));

		int beforeScore = rule.calculateScore(cards);

		Assertions.assertThat(beforeScore).isEqualTo(11);

		cards.add(new Card(Card.Pattern.SPADE, Card.Denomination.ACE));

		// 에이스 카드를 추가한 후 점수 다시 계산
		int afterScore = rule.calculateScore(cards);

		Assertions.assertThat(afterScore).isEqualTo(12);

	}

	// 블랙잭 점수 카드
	private List<Card> createBlackjackHand() {
		List<Card> cards = new ArrayList<>();
		cards.add(new Card(Card.Pattern.SPADE, Card.Denomination.ACE));
		cards.add(new Card(Card.Pattern.HEART, Card.Denomination.TEN));
		return cards;
	}

	// 블랙잭이 아닌 점수 카드
	private List<Card> createNonBlackjackHand() {
		List<Card> cards = new ArrayList<>();
		cards.add(new Card(Card.Pattern.DIAMOND, Card.Denomination.THREE));
		cards.add(new Card(Card.Pattern.CLUB, Card.Denomination.EIGHT));
		return cards;
	}
}

 

테스트코드 실행결과
그림1 실행결과

 

 

 

 

후기

 

블랙잭 게임은 실제로는 이해하고 있지만 코드로 녹여내는데 시간이 많이 걸리게 되었습니다.

우선 도메인 로직과 비즈니스 로직을 분리하여 개발하게 되었고 이거 덕분에 도메인 개발이 무엇인지 조금은 깨달은 것 같습니다.

또한, "오브젝트" 책을 읽고 어떻게 하면 더 재사용 하여 객체지향적으로 설계할지를 고민하게 된 것 같습니다.

많이 부족하지만 지금 가진 능력에 최대한으로 구현 해보고자 였습니다.

 

다음에는 더욱더 성장한 글로 돌아 오도록 하겠습니다.

 

 

감사합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함