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 { private GameSettings settings; private 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; } } @SuppressWarnings("serial") private static class EnoughPoints extends Throwable { private Pair, Integer> result; public EnoughPoints(Pair, Integer> result) { this.result = result; } public Pair, Integer> getResult() { return result; } } /** * 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) { Pair, Integer> emptyResult = new Pair, Integer>( Collections. emptyList(), 0); if (pointsMissing <= 0) return emptyResult; stoneCounts = (TreeMap, Integer>) stoneCounts .clone(); SearchHelper searchHelper = new SearchHelper( pointsMissing, new Pair, Integer>(Collections. emptyList(), 0)); try { for (int value = settings.getHighestValue(); value >= 1; value--) { for (StoneColor color : stoneColors) { Pair stone = new Pair( value, color); if (stoneCounts.containsKey(stone)) { SearchState searchState = new SearchState(pointsMissing - value, stoneCounts, jokerCount); decrementStoneCount(stoneCounts, stone); searchHelper.checkResult(findRunsWithTotalPoints(searchState, stone, 1)); searchHelper.checkResult(findGroupsWithTotalPoints(searchState, value, Collections.singletonList(color), color)); } if (jokerCount > 0) { SearchState searchState = new SearchState(pointsMissing - value, stoneCounts, jokerCount - 1); searchHelper.checkResult(findRunsWithTotalPoints(searchState, stone, 1)); searchHelper.checkResult(findGroupsWithTotalPoints(searchState, value, Collections.singletonList(color), color)); } } } } catch (EnoughPoints enoughPoints) { return enoughPoints.getResult(); } return searchHelper.getBestResult(); } private static class SearchHelper { Pair, Integer> bestResult; int pointsMissing; public SearchHelper(int pointsMissing, Pair, Integer> bestResult) { this.pointsMissing = pointsMissing; this.bestResult = bestResult; } public void checkResult(Pair, Integer> result) throws EnoughPoints { if (result.getSecond() >= pointsMissing) throw new EnoughPoints(result); if (result.getSecond() > bestResult.getSecond()) bestResult = result; } public Pair, Integer> getBestResult() { return bestResult; } } private Pair, Integer> findGroupsWithTotalPoints( SearchState searchState, int value, List chosenColors, StoneColor testedColor) { StoneColor nextColor = getNextColor(testedColor); Pair nextStone = new Pair(value, nextColor); SearchHelper searchHelper = new SearchHelper( searchState.pointsMissing, new Pair, Integer>(Collections. emptyList(), 0)); try { if (nextColor != null) { List newColors = new ArrayList(chosenColors); newColors.add(nextColor); if (searchState.stoneCounts.containsKey(nextStone)) { decrementStoneCount(searchState.stoneCounts, nextStone); searchHelper.checkResult(findGroupsWithTotalPoints(searchState, value, newColors, nextColor)); incrementStoneCount(searchState.stoneCounts, nextStone); } if (searchState.jokerCount > 0) { searchHelper.checkResult(findGroupsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount - 1), value, newColors, nextColor)); } searchHelper.checkResult(findGroupsWithTotalPoints(searchState, value, chosenColors, nextColor)); } if (chosenColors.size() >= 3) { searchHelper.checkResult(handleFoundGroup(searchState, value, chosenColors)); } } catch (EnoughPoints enoughPoints) { return enoughPoints.getResult(); } return searchHelper.getBestResult(); } private Pair, Integer> handleFoundGroup( SearchState searchState, int value, List chosenColors) { int groupPoints = chosenColors.size() * value; Pair, Integer> 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); return result; } private Pair, Integer> findRunsWithTotalPoints( SearchState searchState, Pair testedStone, int runLength) { SearchHelper searchHelper = new SearchHelper( searchState.pointsMissing, new Pair, Integer>(Collections. emptyList(), 0)); try { 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); searchHelper.checkResult(findRunsWithTotalPoints(searchState, nextStone, runLength + 1)); incrementStoneCount(searchState.stoneCounts, nextStone); } if (searchState.jokerCount > 0) { searchHelper.checkResult(findRunsWithTotalPoints(new SearchState( searchState.pointsMissing, searchState.stoneCounts, searchState.jokerCount - 1), nextStone, runLength + 1)); } } if (runLength >= 3) { searchHelper.checkResult(handleFoundRun(searchState, testedStone, runLength)); } } catch (EnoughPoints enoughPoints) { return enoughPoints.getResult(); } return searchHelper.getBestResult(); } private Pair, Integer> handleFoundRun(SearchState searchState, Pair testedStone, int runLength) { int below = testedStone.getFirst() - 1; int high = below + runLength; int runPoints = (high * (high + 1)) / 2 - (below * (below + 1)) / 2; Pair, Integer> 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); return result; } private static void incrementStoneCount( TreeMap, Integer> stones, Pair stone) { if (stones.containsKey(stone)) { stones.put(stone, stones.get(stone) + 1); } else { stones.put(stone, 1); } } private 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); } } private 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); } }