package jrummikub.control; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.TreeMap; import jrummikub.model.GameSettings; import jrummikub.model.Stone; import jrummikub.model.StoneColor; import jrummikub.model.StoneSet; import jrummikub.util.Pair; /** * A collection of several AI utility methods with given game settings * */ public class AIUtil { GameSettings settings; List stoneColors; /** * The utility class's methods calculate their results based on the game * settings * * @param settings * the underlying game settings */ public AIUtil(GameSettings settings) { this.settings = settings; stoneColors = new ArrayList(Arrays.asList(StoneColor.values())); stoneColors.retainAll(settings.getStoneColors()); } private static class SearchState { int pointsMissing; TreeMap, Integer> stoneCounts; int jokerCount; SearchState(int pointsMissing, TreeMap, Integer> stoneCounts, int jokerCount) { this.pointsMissing = pointsMissing; this.stoneCounts = stoneCounts; this.jokerCount = jokerCount; } } /** * Tries to find sets with a certain total of points. If only lower totals are * found, returns the sets with the highest cumulated point total. * * @param pointsMissing * the desired number of points * @param stoneCounts * the number of each stone * @param jokerCount * the total number of jokers in the game * @return the sets that have the desired point total or the highest found */ @SuppressWarnings("unchecked") public Pair, Integer> findSetsWithTotalPoints( int pointsMissing, TreeMap, Integer> stoneCounts, int jokerCount) { if (pointsMissing <= 0) { return new Pair, Integer>( Collections. emptyList(), 0); } stoneCounts = (TreeMap, Integer>) stoneCounts .clone(); Pair, Integer> result, bestResult; bestResult = new Pair, Integer>( Collections. emptyList(), 0); for (int value = settings.getHighestValue(); value >= 1; value--) { for (StoneColor color : stoneColors) { Pair stone = new Pair(value, color); if (stoneCounts.containsKey(stone)) { decrementStoneCount(stoneCounts, stone); result = findRunsWithTotalPoints(new SearchState(pointsMissing - value, stoneCounts, jokerCount), stone, 1); if (result.getSecond() >= pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; result = findGroupsWithTotalPoints(new SearchState(pointsMissing - value, stoneCounts, jokerCount), value, Collections.singletonList(color), color); if (result.getSecond() >= pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } if (jokerCount > 0) { result = findRunsWithTotalPoints(new SearchState(pointsMissing - value, stoneCounts, jokerCount - 1), stone, 1); if (result.getSecond() >= pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; result = findGroupsWithTotalPoints(new SearchState(pointsMissing - value, stoneCounts, jokerCount - 1), value, Collections.singletonList(color), color); if (result.getSecond() >= pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } } } return bestResult; } private Pair, Integer> findGroupsWithTotalPoints( SearchState searchState, int value, List chosenColors, StoneColor testedColor) { StoneColor nextColor = getNextColor(testedColor); Pair nextStone = new Pair(value, nextColor); Pair, Integer> result, bestResult; bestResult = new Pair, Integer>( Collections. emptyList(), 0); if (nextColor != null) { if (searchState.stoneCounts.containsKey(nextStone)) { decrementStoneCount(searchState.stoneCounts, nextStone); List newColors = new ArrayList(chosenColors); newColors.add(nextColor); result = findGroupsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount), value, newColors, nextColor); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; incrementStoneCount(searchState.stoneCounts, nextStone); } if (searchState.jokerCount > 0) { List newColors = new ArrayList(chosenColors); newColors.add(nextColor); result = findGroupsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount - 1), value, newColors, nextColor); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } result = findGroupsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount), value, chosenColors, nextColor); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } if (chosenColors.size() >= 3) { int groupPoints = chosenColors.size() * value; result = findSetsWithTotalPoints(searchState.pointsMissing - groupPoints, searchState.stoneCounts, searchState.jokerCount); List newStones = new ArrayList(); for (StoneColor color : chosenColors) { newStones.add(new Stone(value, color)); } List newResultList = new ArrayList(result.getFirst()); newResultList.add(new StoneSet(newStones)); result = new Pair, Integer>(newResultList, result.getSecond() + groupPoints); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } return bestResult; } private Pair, Integer> findRunsWithTotalPoints( SearchState searchState, Pair testedStone, int runLength) { Pair, Integer> result, bestResult; bestResult = new Pair, Integer>( Collections. emptyList(), 0); Pair nextStone = null; if (testedStone.getFirst() > 1) { int nextValue = testedStone.getFirst() - 1; nextStone = new Pair(nextValue, testedStone.getSecond()); if (searchState.stoneCounts.containsKey(nextStone)) { decrementStoneCount(searchState.stoneCounts, nextStone); result = findRunsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount), nextStone, runLength + 1); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; incrementStoneCount(searchState.stoneCounts, nextStone); } if (searchState.jokerCount > 0) { result = findRunsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount - 1), nextStone, runLength + 1); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } } if (runLength >= 3) { int below = testedStone.getFirst() - 1; int high = below + runLength; int runPoints = (high * (high + 1)) / 2 - (below * (below + 1)) / 2; result = findSetsWithTotalPoints(searchState.pointsMissing - runPoints, searchState.stoneCounts, searchState.jokerCount); List newStones = new ArrayList(); for (int i = 0; i < runLength; i++) { newStones.add(new Stone(i + testedStone.getFirst(), testedStone .getSecond())); } List newResultList = new ArrayList(result.getFirst()); newResultList.add(new StoneSet(newStones)); result = new Pair, Integer>(newResultList, result.getSecond() + runPoints); if (result.getSecond() >= searchState.pointsMissing) return result; if (result.getSecond() > bestResult.getSecond()) bestResult = result; } return bestResult; } static void incrementStoneCount( TreeMap, Integer> stones, Pair stone) { if (stones.containsKey(stone)) { stones.put(stone, stones.get(stone) + 1); } else { stones.put(stone, 1); } } static void decrementStoneCount( TreeMap, Integer> stones, Pair stone) { int count = stones.get(stone); count--; if (count == 0) { stones.remove(stone); } else { stones.put(stone, count); } } StoneColor getNextColor(StoneColor color) { int index = stoneColors.indexOf(color) + 1; if (index >= stoneColors.size()) { return null; } return stoneColors.get(index); } private final static Comparator> comparator = new Comparator>() { @Override public int compare(Pair o1, Pair o2) { int firstComparison = o1.getFirst().compareTo(o2.getFirst()); if (firstComparison != 0) { return -firstComparison; } else { return o1.getSecond().compareTo(o2.getSecond()); } } }; /** * Counts the numbers of stones * * @param stones * the stones to count * @return the numbers for all stones */ public static Pair, Integer>, Integer> countStones( List stones) { int jokerCount = 0; TreeMap, Integer> stoneCounts = new TreeMap, Integer>( comparator); for (Stone stone : stones) { if (stone.isJoker()) { jokerCount++; } else { Pair key = new Pair( stone.getValue(), stone.getColor()); incrementStoneCount(stoneCounts, key); } } return new Pair, Integer>, Integer>( stoneCounts, jokerCount); } }