티스토리 뷰

자바 로고

 

트럼프 카드

 

들어가기 전 설명

 

현재 구현한 코드들은 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

 

 

 

 

코드 구현하기

 

  1. Card
  2. CardDeck
  3. Dealer
  4. Gamer
  5. Rule

순서대로 구현하려고 합니다.

 

 

 

 

 

 

Card.java

 

카드 객체는 카드덱, 딜러, 게이머, 규칙에도 어디서든 의존하며 사용합니다.

왜냐하면 카드덱이 카드로 이루어져 있고 딜러와 게이머도 카드를 소지하고 있어야 하며 그 카드 점수로 룰을 따지기 때문입니다.

 

카드의 패턴과 숫자를 필드로 가지고 있으며 Enum으로 구현하고자 하였습니다.

 

근데 구현하면서 에이스 카드는 1 또는 11의 점수를 얻을 수 있다는 규칙 때문에 Denomination Enum 객체에서 Enum Value를 조회할 때 배열로 리턴을 하는 것을 볼 수 있습니다.

 

이것 때문에 뭔가 되게 짜치는(?) 로직이 탄생되어 버린 것 같습니다.

우선은 구현은 하고 개선, 보완할 점에서 개선이 된다면 보완 해보도록 하겠습니다.

public class Card {

    private Pattern pattern;
    private Denomination denomination;

    @Override
    public String toString() {
        return pattern + "이며, " + denomination + "이며 " +
        	"보이는 카드 숫자는 " + denomination.getValues()[0] + " 입니다";
    }

    public Card(Pattern pattern, Denomination denomination) {
        this.pattern = pattern;
        this.denomination = denomination;
    }

    enum Denomination {  // 카드 숫자
        TWO(2), THREE(3), FOUR(4), FIVE(5), SIX(6),
        SEVEN(7), EIGHT(8), NINE(9), TEN(10),
        JACK(10), QUEEN(10), KING(10), ACE(1, 11);

        public final int[] values;

        Denomination(int ... value) {
            this.values = value;
        }

        public int[] getValues() {
            return values;
        }
    }

    enum Pattern {   // 카드 패턴
        DIAMOND, HEART, CLUB, SPADE
    }

    public Denomination getDenomination() {
        return denomination;
    }

}

 

 

 

 

 

 

CardDeck.java

 

카드를 뽑을 때 맨 위에에서 뽑기 때문에 List 컬렉션이 아닌 Stack을 이용하였습니다.

밑장빼기 하고 싶으면 Deque를 이용하면 몰래 빼낼 수 있는...?

 

그림1 동작그만 어디서 밑장 빼기여?
그림1 동작그만 어디서 밑장 빼기여?

 

저희는 정정당당히 게임을 임하여 카드덱에서 맨 위에 부터 카드를 뽑도록 하겠습니다.

 

객체지향적으로 구현을 하다보면 해당객체를 의인화를 해야될 때가 많은 것 같습니다.

일상 생활에는 "카드를 사람이 섞는다" 라고 한다면 코드 객체지향 프로그래밍 세계해서는 "카드가 스스로 섞는다"가 되는 것이죠.

 

그래서 카드덱 객체서 스스로 셔플 될 수 있어서 private로 셔플 메서드를 넣고 카드들을 캡슐화를 진행해서 외부에서 카드 1장씩 가져가도록 구현하게 되었습니다. 

public class CardDeck {
    private Stack<Card> cardDeck;

    // Getter
    public Stack<Card> getCardDeck() {
        return cardDeck;
    }

    public CardDeck() {
        cardDeck = new Stack<>();
        init();
        shuffle();
    }

    // 트럼트 카드 세팅
    private void init(){
        // 카드 덱에 52장의 카드 추가
        for (Card.Pattern pattern : Card.Pattern.values()) {
            for (Card.Denomination denomination : Card.Denomination.values()) {
                cardDeck.add(new Card(pattern, denomination));
            }
        }
    }

    // 카드 섞기
    private void shuffle(){
        Collections.shuffle(cardDeck);
    }

    // 카드 얻기
    public Card getCard(){
        if(cardDeck.isEmpty()){
            throw new DeckEmptyException("카드를 다 뽑았습니다.");
        } else {
            return cardDeck.pop();
        }
    }
}



 

 

 

 

Dealer.java

 

카지노 딜러
그림2 카지노 딜러

 

이제 이렇게 카드를 나눠주는 사람인 것이고 딜러도 2장의 카드가 필요하므로 Gamer와 역할 차이가 별로 안납니다.

인터페이스에 카드 드로우 메서드를 추상화하고 딜러에게 구현하였습니다.

 

근데 왜 딜러가 멋있어 보이져?

public class Dealer implements Player{

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

    public String getName() {
        return name;
    }

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

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

}



 

 

 

 

Gamer.java

 

이 게임은 카지노 게임이므로 도박의 중독이 있으므로 주의해주시기 바랍니다.

도박 중독 안되게 그냥 게임은 게임일 뿐 돈걸고 하지 맙시다!!!!!!!

잘 못하다간 아래 오렌지 아저씨처럼 호구 아저씨 되는겁니다.

카지노 장면 중 하나
그림3 도박 게이머

 

게이머는 딜러와 같은 역할을 하며 사용자 닉네임을 설정할 수 있게 하였습니다.

인터페이스에 카드 드로우 메서드를 추상화하고 게이머에게 구현하였습니다.

public class Gamer implements Player {

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

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

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

    public String getName() {
        return name;
    }

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

}



 

 

 

 

Rule.java

 

심판 같은 객체라고 생각하시면 됩니다.

카드를 통해서 점수를 계산하고, 그 점수에 따라서 누가 승리자 인지 판별해 줍니다.

 

인생에서 퇴장안당하고 싶으면 도박중복은 절대 안됩니다.!!

 

심판
그림4 심판

 

승리자를 판별 할 때 여러번의 분기처리를 하기 위해 if-else 문을 많이 사용한 것을 볼 수 있습니다.

만약 블랙잭 규칙이 자주 바뀌거나 좀 세세한 승리 판별 규칙이 달라지면 오류가 많이 발생할 확률이 큰 로직인 것 같습니다.

 

개선 및 보완할 때 이것을 보완 해보도록 하겠습니다.

public class Rule {

    private int VICTORY_DEALER = 0;
    private int VICTORY_GAMER = 0;

    private final int BLACKJACK_SCORE = 21;

    public int getVICTORY_DEALER() {
        return VICTORY_DEALER;
    }

    public int getVICTORY_GAMER() {
        return VICTORY_GAMER;
    }

    public int getBLACKJACK_SCORE() {
        return BLACKJACK_SCORE;
    }

    // 점수 계산
    public int calculateScore(List<Card> cards){
        int sum = 0;
        int aceCount = 0;

        // 첫 번째 합계 계산 및 에이스 카운트
        for (Card card : cards) {
            int value = card.getDenomination().getValues()[0];
            if (value == 1) {  // 에이스 카드인 경우
                value = 11;    // 먼저 11로 계산
                aceCount++;
            }
            sum += value;
        }

        // 점수가 21을 초과하면, 에이스를 1로 변환
        while (sum > 21 && aceCount > 0) {
            sum -= 10;  // 에이스 하나를 11에서 1로 변경 (11 - 10 = 1)
            aceCount--;
        }

        return sum;
    }

    // 승리자 판별
    public String getWinner(Dealer dealer, Gamer gamer) {
        int dealerScore = calculateScore(dealer.getHaveCards());    // 딜러 점수
        int gamerScore = calculateScore(gamer.getHaveCards());      // 게이머 점수

        String winner = null;

        if (gamerScore == BLACKJACK_SCORE) {  // 게이머가 블랙잭일 경우
            winner = gamer.getName();
            VICTORY_GAMER++;
        } else if (gamerScore < BLACKJACK_SCORE && dealerScore < gamerScore) { // 블랙잭 점수보다 낮으면서 딜러보다 점수 높을 떄
            winner = gamer.getName();
            VICTORY_GAMER++;
        } else if (BLACKJACK_SCORE < dealerScore && gamerScore < BLACKJACK_SCORE) { // 딜러도 블랙잭 점수 넘었을 떄
            winner = gamer.getName();
            VICTORY_GAMER++;
        } else if (gamerScore < BLACKJACK_SCORE && (gamerScore == dealerScore)){  // 무승부
            winner = "무승부";
        } else {
            winner = dealer.getName();
            VICTORY_DEALER++;
        }

        return winner;
    }
}

 

 

다음 이야기

 

다음 글에서는 이 도메인 로직을 가지고 서비스를 구현한다면 어떻게 할지를 구현해보도록 하겠습니다.

 

 

감사합니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함