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.