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}