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.ListOrderComparator;
025import ball.util.stream.Combinations;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.List;
031import java.util.function.Consumer;
032import java.util.function.Predicate;
033import java.util.stream.IntStream;
034
035/**
036 * Poker hand {@link Evaluator}.
037 *
038 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
039 */
040public class Evaluator implements Predicate<List<Card>>, Consumer<List<Card>> {
041
042    /**
043     * {@link Card} {@link Comparator}.
044     */
045    public static final Comparator<Card> CARD =
046        Comparator
047        .comparing(Card::getRank, new ListOrderComparator<>(Rank.ACE_HIGH))
048        .thenComparing(Card::getSuit, new ListOrderComparator<>(Suit.values()));
049
050    /**
051     * Hand ({@link Card} {@link List}) {@link Comparator}.
052     */
053    public static final Comparator<List<Card>> HAND =
054        (l, r) -> (IntStream.range(0, Math.min(l.size(), r.size()))
055                   .map(t -> CARD.compare(l.get(t), r.get(t)))
056                   .filter(t -> t != 0)
057                   .findFirst().orElse(Integer.compare(l.size(), r.size())));
058
059    private final List<Card> hand;
060    private final List<Ranking> orBetter;
061    private Ranking ranking = Ranking.Empty;
062    private List<Card> scoring = List.of();
063
064    /**
065     * Sole public constructor.
066     *
067     * @param   collection      The {@link Collection} of {@link Card}s to
068     *                          evaluate.
069     */
070    public Evaluator(Collection<Card> collection) {
071        this(collection, Ranking.values());
072    }
073
074    /**
075     * Protected constructor to search for specific {@link Ranking}(s).
076     *
077     * @param   collection      The {@link Collection} of {@link Card}s to
078     *                          evaluate.
079     * @param   rankings        The {@link Ranking}s to look for.
080     */
081    protected Evaluator(Collection<Card> collection, Ranking... rankings) {
082        hand = new ArrayList<>(collection);
083        hand.sort(CARD.reversed());
084
085        orBetter = new ArrayList<>(List.of(rankings));
086        Collections.reverse(orBetter);
087
088        var size = Math.min(5, hand.size());
089
090        orBetter.removeIf(t -> t.required() > size);
091
092        Combinations.of(size, size, this, hand)
093            .forEach(this);
094
095        for (int i = 0, n = scoring.size(); i < n; i += 1) {
096            Collections.swap(hand, i, hand.indexOf(scoring.get(i)));
097        }
098
099        hand.subList(scoring.size(), hand.size()).sort(CARD.reversed());
100    }
101
102    /**
103     * Method to get this hand as an unmodifiable {@link List} sorted
104     * according to its {@link Ranking}.
105     *
106     * @return  The sorted {@link List}.
107     */
108    public List<Card> getHand() { return Collections.unmodifiableList(hand); }
109
110    /**
111     * Method to get this hand's {@link Ranking}.
112     *
113     * @return  The {@link Ranking}.
114     */
115    public Ranking getRanking() { return ranking; }
116
117    /**
118     * Method to get this hand's scoring {@link Card}s as an unmodifiable
119     * {@link List}.
120     *
121     * @return  The {@link List} of scoring {@link Card}s.
122     */
123    public List<Card> getScoring() {
124        return Collections.unmodifiableList(scoring);
125    }
126
127    @Override
128    public boolean test(List<Card> prefix) {
129        return orBetter.stream().anyMatch(t -> t.possible().test(prefix));
130    }
131
132    @Override
133    public void accept(List<Card> list) {
134        var ranking =
135            orBetter.stream()
136            .filter(t -> t.test(list))
137            .findFirst().orElse(Ranking.Empty);
138
139        var scoring = list.subList(0, ranking.required());
140        var comparison = Ranking.COMPARATOR.compare(ranking, this.ranking);
141
142        if (comparison > 0) {
143            this.ranking = ranking;
144            this.scoring = scoring;
145
146            var index = orBetter.indexOf(ranking);
147
148            if (! (index < 0)) {
149                orBetter.subList(index + 1, orBetter.size()).clear();
150            }
151        } else if (comparison == 0) {
152            if (HAND.compare(scoring, this.scoring) > 0) {
153                this.scoring = scoring;
154            }
155        }
156    }
157
158    @Override
159    public String toString() {
160        return getRanking().name() + ":" + getScoring() + orBetter;
161    }
162}