001package ball.game.card.poker;
002/*-
003 * ##########################################################################
004 * Game Applications and Utilities
005 * %%
006 * Copyright (C) 2010 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.game.card.Card.Rank;
022import ball.game.card.Card.Suit;
023import ball.game.card.Card;
024import ball.util.Comparators;
025import ball.util.ListOrderComparator;
026import java.util.Collection;
027import java.util.Comparator;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.function.Predicate;
032import java.util.stream.Collectors;
033import java.util.stream.Stream;
034
035import static ball.game.card.Card.Rank.ACE;
036import static ball.game.card.Card.Rank.KING;
037import static ball.game.card.Card.Rank.SEQUENCE;
038
039/**
040 * Poker hand {@link Ranking} {@link Enum} and {@link Predicate}.
041 *
042 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
043 */
044public enum Ranking implements Predicate<List<Card>> {
045    Empty(0, null, Collection::isEmpty),
046        HighCard(1, t -> true, t -> true),
047        Pair(2, Rank.SAME, Rank.SAME),
048        TwoPair(4, holding(2, Rank.SAME), Pair.with(Pair)),
049        ThreeOfAKind(3, Rank.SAME, Rank.SAME),
050        Straight(5, SEQUENCE, SEQUENCE),
051        Flush(5, Suit.SAME, Suit.SAME),
052        FullHouse(5, holding(3, Rank.SAME), ThreeOfAKind.with(Pair)),
053        FourOfAKind(4, Rank.SAME, Rank.SAME),
054        StraightFlush(5,
055                      holding(ACE, KING).negate().and(SEQUENCE).and(Suit.SAME),
056                      holding(ACE, KING).negate().and(Straight).and(Flush)),
057        RoyalFlush(5,
058                   holding(ACE, KING).and(SEQUENCE).and(Suit.SAME),
059                   holding(ACE, KING).and(Straight).and(Flush)),
060        FiveOfAKind(5, Rank.SAME, Rank.SAME);
061
062    private final int required;
063    private final Predicate<List<Card>> possible;
064    private final Predicate<List<Card>> is;
065
066    private Ranking(int required, Predicate<List<Card>> possible, Predicate<List<Card>> is) {
067        this.required = required;
068        this.possible = possible;
069        this.is = Objects.requireNonNull(is);
070    }
071
072    /**
073     * Method to find the best possible {@link.this} {@link Ranking} hand in
074     * the {@link Collection}.
075     *
076     * @param   collection      The {@link Collection} of {@link Card}s to
077     *                          evaluate.
078     *
079     * @return  The best sorted hand as a {@link List} of {@link Card}s if
080     *          a combination matching {@link.this} {@link Ranking} is
081     *          found; the empty {@link List} otherwise.
082     */
083    public List<Card> find(Collection<Card> collection) {
084        var evaluator = new Evaluator(collection, this);
085        var hand = evaluator.getScoring().isEmpty() ? evaluator.getScoring() : evaluator.getHand();
086
087        return hand;
088    }
089
090    /**
091     * Returns the number of {@link Card} for {@link.this} {@link Ranking}.
092     *
093     * @return  The number of {@link Card}s required.
094     */
095    public int required() { return required; }
096
097    /**
098     * Method to return a {@link Predicate} to test if the {@link List} of
099     * {@link Card} is a possible {@link Ranking}.
100     *
101     * @return  A {@link Predicate} that returns {@code false} if the hand
102     *          cannot be {@link.this} {@link Ranking}; {@code true}
103     *          otherwise.
104     */
105    public Predicate<List<Card>> possible() {
106        return t -> (possible == null || possible.test(subListTo(t, required())));
107    }
108
109    @Override
110    public boolean test(List<Card> list) {
111        return (list.size() >= required() && is.test(subListTo(list, required())));
112    }
113
114    private Predicate<List<Card>> with(Predicate<List<Card>> that) {
115        return t -> test(t) && that.test(subListFrom(t, required()));
116    }
117
118    private static <T> Predicate<List<T>> holding(int count, Predicate<List<T>> predicate) {
119        return t -> (t.isEmpty() || predicate.test(subListTo(t, count)));
120    }
121
122    @SafeVarargs
123    @SuppressWarnings({ "varargs" })
124    private static <T> Predicate<List<T>> holding(Predicate<T>... array) {
125        return holding(Stream.of(array).collect(Collectors.toList()));
126    }
127
128    private static <T> Predicate<List<T>> holding(List<Predicate<T>> list) {
129        return t -> ((list.isEmpty() || t.isEmpty())
130                     || (list.get(0).test(t.get(0)) && (holding(subListFrom(list, 1)).test(subListFrom(t, 1)))));
131    }
132
133    private static <T> List<T> subListTo(List<T> list, int to) {
134        return list.subList(0, Math.min(to, list.size()));
135    }
136
137    private static <T> List<T> subListFrom(List<T> list, int from) {
138        return list.subList(from, list.size());
139    }
140
141    /**
142     * {@link Comparator} that orders {@link Ranking}s weakest to
143     * strongest.
144     */
145    public static Comparator<Ranking> COMPARATOR = new ListOrderComparator<>(values());
146}