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