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}