package com.github.donkirkby.vograbulary.russian; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import com.github.donkirkby.vograbulary.ultraghost.WordList; public class Puzzle { public static final int NO_SELECTION = Integer.MIN_VALUE; private String clue; private String[] targets; private int targetWord; private int targetCharacter; private boolean isSolved; private float delay = 0; private BigDecimal totalScore; public static class NoSolutionException extends RuntimeException { private static final long serialVersionUID = -4948543814705260615L; public NoSolutionException(String message) { super(message); } } public Puzzle(String clue) { this(clue, BigDecimal.ZERO); } public Puzzle(String clue, Puzzle previous) { this(clue, previous.getTotalScore()); } private Puzzle(String clue, BigDecimal totalScore) { this.clue = clue; this.totalScore = totalScore; clearTargets(); int targetPosition = 0; String[] words = clue.split("\\s+"); for (int i = 0; i < words.length; i++) { String word = words[i]; if (word.charAt(0) == '*') { // remove * and any other punctuation words[targetPosition++] = word.replaceAll("\\W*", ""); } } targets = new String[] { words[0].toUpperCase(), words[1].toUpperCase() }; if (words.length == 2) { this.clue = ""; } } /** * Clear the target word and character so no solution is selected. */ public void clearTargets() { targetWord = targetCharacter = NO_SELECTION; } /** * The index of the word that will have the other word inserted into it. * For example, if the words are "unable"(0) and "comfortable"(1), then the * target word is 0, because "comfortable" is inserted into to "unable" to * make "uncomfortable". * @param targetWord */ public void setTargetWord(int targetWord) { if (targetWord < 0 || 1 < targetWord) { throw new ArrayIndexOutOfBoundsException( "Target word index " + targetWord + " is invalid."); } this.targetWord = targetWord; } public int getTargetWord() { return targetWord; } /** * The index of the character in the target word that will have the other * word inserted before it. For example, if the words are "unable" and * "comfortable", then the target character is 2, because "comfortable" is * inserted before the "a" to make "uncomfortable". * @param targetCharacter */ public void setTargetCharacter(int targetCharacter) { if (targetWord < 0) { throw new IllegalStateException( "Target character set before target word."); } if (! isValidTargetCharacter(targetCharacter)) { throw new ArrayIndexOutOfBoundsException( "Target character index " + targetCharacter + " is invalid."); } this.targetCharacter = targetCharacter; } public boolean isValidTargetCharacter(int targetCharacter) { return 1 <= targetCharacter && targetCharacter <= getTarget(targetWord).length() - 1; } public int getTargetCharacter() { return targetCharacter; } public String getClue() { return clue; } public String getTarget(int wordIndex) { return targets[wordIndex]; } public boolean isTargetSet() { return targetCharacter != NO_SELECTION; } public boolean isSolved() { return isSolved; } public void setSolved(boolean isSolved) { this.isSolved = isSolved; if (isSolved) { this.totalScore = totalScore.add(getScore()); } } public String getCombination() { if ( ! isTargetSet()) { throw new IllegalStateException( "Target word and character are not set."); } return targets[targetWord].substring(0, targetCharacter) + targets[(targetWord+1)%2] + targets[targetWord].substring(targetCharacter); } public BigDecimal getScore() { float rawScore = Math.max(0.000101f, 100 * (float)Math.exp(-delay*Math.log(2)/10)); return new BigDecimal( rawScore, rawScore >= 100 ? MathContext.DECIMAL32 : new MathContext(2, RoundingMode.FLOOR));//2 significant digits } public String adjustScore(float seconds) { if ( ! isSolved) { delay += seconds; } return getScoreDisplay(); } public String getScoreDisplay() { return getScoreDisplay(getScore()); } public String getTotalScoreDisplay() { return getScoreDisplay(getTotalScore()); } private String getScoreDisplay(BigDecimal score) { // TODO: Put the formatting back in a way that works under GWT. // int formatPrecision = Math.max(0, score.scale()); return score.toString();//String.format("%." + formatPrecision + "f", score); } public BigDecimal getTotalScore() { return totalScore; } public String findSolution(WordList wordList) { for (int wordIndex = 0; wordIndex < 2; wordIndex++) { setTargetWord(wordIndex); final int targetLength = targets[wordIndex].length(); for (int charIndex = 1; charIndex < targetLength; charIndex++) { setTargetCharacter(charIndex); if (wordList.contains(getCombination())) { return getCombination(); } } } throw new NoSolutionException("No solution for clue: " + clue); } }