A Kotlin-based solver for Clockwerdle, a Wordle-style guessing game based on Blood on the Clocktower characters. The solver helps you find optimal guesses using advanced game theory algorithms.
Clockwerdle is a guessing game where players attempt to identify a Blood on the Clocktower character through a series of guesses. Each guess provides feedback across multiple attributes:
- Original Script: Which script the character is from
- Character Type: Townsfolk, Outsider, Minion, or Demon (with partial matches for same team)
- Wakes in Night: When/if the character acts at night
- Selects Player: Whether the character targets other players
- Learns Info: Whether the character gains information
- Ability Matches: Number of ability tags in common with the target
- Best Starting Character Analysis: Calculates the optimal first guess to maximize information gain
- Interactive Solver: Guides you through the game with optimal suggestions at each step
- Configurable Parameters: Customize max guesses, parallelism, and number of suggestions
The solver uses an expected cost minimization algorithm (expectimax) to find optimal guesses. For each possible guess, it calculates the expected number of guesses needed to solve the puzzle by:
- Simulating All Outcomes: For each candidate guess, simulate the feedback you'd receive for every possible target character
- Grouping by Feedback: Group remaining possibilities by the feedback pattern they'd produce
- Recursive Cost Calculation: For each feedback group, recursively calculate the expected cost to solve
- Weighted Average: Compute the weighted average cost across all possible outcomes
For a guess g with possible targets T:
ExpectedCost(g, T) = Σ P(f) × Cost(T_f)
f ∈ feedback(g, T)
where:
feedback(g, T) = set of distinct feedback patterns when guessing g against targets in T
T_f = subset of targets that produce feedback pattern f when the guess is g
P(f) = |T_f| / |T| (probability of getting feedback f)
Cost(T_f) = {
1 if f indicates correct guess
1 + min ExpectedCost(g', T_f) otherwise (recursively evaluate all available guesses)
g' ∈ available_guesses
}
- Memoization: Caches expected costs for (targets, guessed) states to avoid redundant computation
- Pruning: Eliminates guesses that fail to reduce the search space effectively
- Concurrent Computation: Evaluates multiple candidate guesses in parallel using coroutines
- Early Termination: Short-circuits when a branch exceeds the maximum guess limit
- Non-viable Guess Detection: Rejects guesses that don't eliminate enough possibilities (prevents reducing N targets to N-1)
- Guess Limit Enforcement: Returns infinite cost when max guesses would be exceeded
- Java 21 or higher
- Gradle (wrapper included)
./gradlew :jvm:installDist.\jvm\build\install\jvm\bin\jvm best-starting.\jvm\build\install\jvm\bin\jvm interactiveThe interactive mode guides you step-by-step:
- Enter your guess (character name)
- Provide the feedback from the game:
- Format:
ORIGINAL CHARACTER_TYPE WAKES SELECTS LEARNS ABILITY_MATCHES - Use
CORRECT,PARTIALLY_CORRECT, orINCORRECT(or+,~,-) - Or just
CORRECTif you guessed correctly
- Format:
- Receive optimal next guess suggestions
- Repeat until solved
Example feedback input:
CORRECT PARTIALLY_CORRECT INCORRECT CORRECT INCORRECT 2
or shorthand:
+ ~ - + - 2
All commands support:
--max-guesses N/-g N: Set maximum of guesses allowed (default: 4)--max-in-flight N/-j N: Set parallelism level (default: 16)--top-choice-limit N/-t N: Number of top choices to display (default: 5)--include-inefficient-branches/-i: Include branches where the guess cannot be optimal
Example:
.\jvm\build\install\jvm\bin\jvm best-starting -g 5 -j 2.\jvm\build\install\jvm\bin\jvm interactive
Enter your guess: DRUNK
Enter the feedback (e.g. +, ~, -, +, -, 1) or CORRECT:
> - - - - - 0
Possible targets (32):
Al-Hadikhia, Assassin, Boffin, Boomdandy, Cerenovus, Devils Advocate, Evil Twin, Fang Gu, Fearmonger, Goblin, Godfather, Harpy, Kazali, Legion, Leviathan, Lil Monsta, Lord Of Typhon, Mastermind, Mezepheles, Ojo, Pit Hag, Po, Psychopath, Riot, Shabaloth, Summoner, Vizier, Witch, Wizard, Wraith, Yaggababble, Zombuul
Best next guesses (5):
1) Kazali (2.188), 2) Lil Monsta (2.188), 3) Lord Of Typhon (2.188), 4) Mastermind (2.250), 5) Summoner (2.250)
Choose next guess by number or name: Kazali
Next guess: Kazali
Enter the feedback (e.g. +, ~, -, +, -, 1) or CORRECT:
> + ~ - - - 0
Possible targets (2):
Boffin, Mezepheles
Best next guesses (2):
1) Boffin (1.500), 2) Mezepheles (1.500)
Choose next guess by number or name: 1
Next guess: Boffin
Enter the feedback (e.g. +, ~, -, +, -, 1) or CORRECT:
> CORRECT
Well done!
The frontend is a Kotlin/JS module that lives in the web Gradle module and builds a static site.
./gradlew :web:jsBrowserDistributionThe output is written to web/build/dist/js/productionExecutable/.
Open web/build/dist/js/productionExecutable/index.html after running :web:jsBrowserDistribution.
The repo includes a GitHub Actions workflow that builds the frontend and deploys it to GitHub Pages on every push to main.