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}