Java → Как я решил задачу "Покер"

Мне самому тоже стало интересно решить задачи, которые я вам дал. Начал с задачи Покер, где нужно было сгенерировать два набора по 5 карт и определить какой набор выигрышный. 

Код, который будет здесь представлен, можно использовать только как пример. Не рекомендую его копировать или списывать.

Начало
Первая задача это генерация наборов карт. В стандартной колоде всего 52 карты, 4 масти(suit) и 13 достоинств(rank). У каждой карты есть ее достоинство и масть. Сначала я решил создать класс, который будет представлять одну карту и у которого будут два свойства: Достоинство и Масть. 

Как лучше хранить Достоинство карты, где есть 13 возможных значений: A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K и Масть карты, которая имеет 4 возможных значений: Червы(♥), Бубны(♦), Трефы(♣) и Пики(♠)? Можно было бы использовать обычный целый тип со значениями от 1 до 13 для достоинств и от 1 до 4 для мастей. Но я решил использовать Перечисления в Java. Таким образом, я никак не могу присвоить карте Достоинство и Масть, которые не существуют. Также для каждого значения перечисления я могу дополнительные свойства добавлять. см. Перечисления в Java.

Вот какой класс получился у меня:


public class Card {
    public final Suit suit;
    public final Rank rank;

    public Card(Rank rank, Suit suit){
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public String toString() {
        return String.format("%s%s", rank.name, suit.symbol);
    }

    public enum Rank {
        Two("2", 2), Three("3", 3), Four("4", 4), Five("5", 5), Six("6", 6), Seven("7", 7), Eight("8", 8),
        Nine("9", 9), Ten("10", 10), Jack("J", 11), Queen("Q", 12), King("K", 13), Ace("A", 14);

        public final String name;
        public final int value;

        Rank(String name, int value) {
            this.name = name;
            this.value = value;
        }
    }

    public enum Suit {
        Hearts('♥'), Clubs('♣'), Spades('♠'), Diamonds('♦');

        public final char symbol;

        Suit(char symbol){
            this.symbol = symbol;
        }
    }
}

Как вы заметили я добавил Констуктора для класса Card, с двумя параметрами. Также переопределил стандартный метод toString() чтобы при печати карты показывались ее достоинство и масть. 

Для Перечисления Rank(Достоинство) я добавил два свойства: знак и значение. А для Suit(Масть) я добавил знак масти. 

После этого я создал класс для хранения набора карт на руках игрока, в покере этот набор называется Рука (Hand). Размер руки 5 карт, но в руке может быть и меньше карт, если все карты не еще раздали. Поэтому у Руки должен быть метод для добавления карты.

Вот такой класс получился для Руки:

public class Hand {
    public static final int DEFAULT_SIZE = 5;

    private final List<Card> cards = new ArrayList<>();

    public void addCard(Card card){
        cards.add(card);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        for(Card card: cards){
            result.append(card).append(" ");
        }
        return result.toString();
    }
}

Добавил также метод toString(), который возвращает все карты на руке в виде текста. Константа DEFAULT_SIZE хранит размер руки по умолчанию. 

Теперь мы можем генерировать карты и наборы карт. Я сначала подумал что буду случайным образом генерировать число от 1 до 13, чтобы получить случайное Достоинство и генерировать число от 1 до 4 чтобы получать случайную Масть. Но потом понял, что таким образом я могу получить одинаковые карты. Мне нужно нужны учитывать то, что в колоде карты не повторяются и каждая сгенерированная карта должна быть уникальной.

Я мог бы при каждой генерации новой карты проверять, не повторяется ли эта карта и если повторяется, то запускать генерацию карты снова. Но я решил, что это не очень ООПшно, и создал класс для представления Колоды(Deck) в котором будет полный набор из 52 карт и из который может выдавать случайную карту из колоды, при этом удаляя карту из колоды. 

Вот такой класс получился для Колоды:

public class Deck {
    private final List<Card> deck = new ArrayList<>();
    private Random randomizer = new Random();

    public Deck(){
        initDeck();
    }

    private void initDeck() {
        deck.clear();
        for(Card.Rank rank: Card.Rank.values()){
            for(Card.Suit suit: Card.Suit.values()) {
                deck.add(new Card(rank, suit));
            }
        }
    }

    public Card dealCard() throws RuntimeException {
        if(deck.size() > 0) {
            int number = randomizer.nextInt(deck.size());
            return deck.remove(number);
        }
        throw new RuntimeException("Deck is empty!");
    }
}

Видно, что при создании новой Колоды вызывается метода initDeck() который заполняет колоду картами всех мастей и достоинств. При этом используется метода values() перечислений Rank и Suit, который возвращает все варианты этих перечислений. 

А метод dealCard() возвращает случайную карту из колоды и удаляет эту карту из колоды. Если карты закончились в колоде, метод бросает Исключение, что колода пустая. 

У нас есть Колода карт и мы можем сгенировать правильные наборы карт. Теперь нужно написать код, который будет определять Комбинацию Руки и сравнивать две Руки. 

Для оценки Руки и определения ее комбинации я создал класс HandEvaluator. И класс HandComparator, для сравнения двух Рук. 

Кода еще много.. Весь код вы можете посмотреть или загрузить себе на Github: Poker на Github.

1111 18
Alisher Alikulov