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