![]()
A while ago, I wrote a generalized solver for Pocket Frogs’ search game as well as for Disco Zoo. At the end of a previous 2025-08-13 post, I had mentioned this.
They were fully functional, but I never got around to posting the code. I no longer play the minigames, so am not particularly motivated to write an explanatory post about how it all works, but I include a sample run solving a game in Pocket Frogs, as well as the Julia source code. The puzzle patterns may have changed since then, so the code would need a little bit of updating.
Background from the 2025-08-13 post
The spots I favor have higher expected information density (see also 3Blue1Brown Wordle information theory which is where I got the idea of using simple information theory), resulting in a simpler algorithm for playing. … However, you can also play with higher level frog species, each of which has its own pattern. In this case, the goal might not be to farm gifts, but instead to farm the frogs themselves.

I also learned that the search minigame is based on the core game mechanic of another NimbleBit Game – Disco Zoo. In Disco Zoo, you also have a 5x5 grid but there can be more or less than two animals per game board.
Eventually, I wrote some Julia code to solve for the more general scenario, where given a starting position of
ndifferent patterns on a 5x5 grid, the probabilities of each animal and the expected information content of each position would be calculated, updated after each move.Using this tool, I ground through all the Disco Zoo levels embarassingly quickly. A future post will show the Julia code…
Sample run, after loading the Julia code
julia> play_game()
Enter comma-separated list of species: losg,garc
Probability empty
5×5 Matrix{Float64}:
0.8 0.77 0.77 1.0 1.0
0.77 0.77 0.77 1.0 1.0
0.77 0.77 0.6 0.77 0.77
1.0 1.0 0.77 0.77 0.77
1.0 1.0 0.77 0.77 0.8
Probability of 1: Losgann
5×5 Matrix{Float64}:
0.1 0.11 0.11 0.0 0.0
0.11 0.11 0.11 0.0 0.0
0.11 0.11 0.2 0.11 0.11
0.0 0.0 0.11 0.11 0.11
0.0 0.0 0.11 0.11 0.1
Probability of 2: Garcius
5×5 Matrix{Float64}:
0.1 0.11 0.11 0.0 0.0
0.11 0.11 0.11 0.0 0.0
0.11 0.11 0.2 0.11 0.11
0.0 0.0 0.11 0.11 0.11
0.0 0.0 0.11 0.11 0.1
Expected information
5×5 Matrix{Float64}:
0.92 1.0 1.0 0.0 0.0
1.0 1.0 1.0 0.0 0.0
1.0 1.0 1.37 1.0 1.0
0.0 0.0 1.0 1.0 1.0
0.0 0.0 1.0 1.0 0.92
70 possibilities remaining
* <row> and <col> range from 1 to 5
* <val> is 0 for empty space or # for goal number
* <display> shows all remaining possibilities
Enter <row><col><val>, <display> to show possibilities, or <enter> to exit: 330
Probability empty
5×5 Matrix{Float64}:
1.0 0.71 0.71 1.0 1.0
0.71 0.71 0.71 1.0 1.0
0.71 0.71 1.0 0.71 0.71
1.0 1.0 0.71 0.71 0.71
1.0 1.0 0.71 0.71 1.0
Probability of 1: Losgann
5×5 Matrix{Float64}:
0.0 0.14 0.14 0.0 0.0
0.14 0.14 0.14 0.0 0.0
0.14 0.14 0.0 0.14 0.14
0.0 0.0 0.14 0.14 0.14
0.0 0.0 0.14 0.14 0.0
Probability of 2: Garcius
5×5 Matrix{Float64}:
0.0 0.14 0.14 0.0 0.0
0.14 0.14 0.14 0.0 0.0
0.14 0.14 0.0 0.14 0.14
0.0 0.0 0.14 0.14 0.14
0.0 0.0 0.14 0.14 0.0
Expected information
5×5 Matrix{Float64}:
0.0 1.15 1.15 0.0 0.0
1.15 1.15 1.15 0.0 0.0
1.15 1.15 0.0 1.15 1.15
0.0 0.0 1.15 1.15 1.15
0.0 0.0 1.15 1.15 0.0
42 possibilities remaining
Enter <row><col><val>, <display> to show possibilities, or <enter> to exit: 122
Probability empty
5×5 Matrix{Float64}:
1.0 0.0 0.83 1.0 1.0
0.83 0.83 0.83 1.0 1.0
0.83 0.83 1.0 0.0 0.83
1.0 1.0 0.83 0.83 0.83
1.0 1.0 0.83 0.83 1.0
Probability of 1: Losgann
5×5 Matrix{Float64}:
0.0 0.0 0.17 0.0 0.0
0.17 0.17 0.17 0.0 0.0
0.17 0.17 0.0 0.0 0.17
0.0 0.0 0.17 0.17 0.17
0.0 0.0 0.17 0.17 0.0
*** SOLVED ***: 2: Garcius
5×5 Matrix{Float64}:
0.0 1.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 1.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
Expected information
5×5 Matrix{Float64}:
0.0 0.0 0.65 0.0 0.0
0.65 0.65 0.65 0.0 0.0
0.65 0.65 0.0 0.0 0.65
0.0 0.0 0.65 0.65 0.65
0.0 0.0 0.65 0.65 0.0
6 possibilities remaining
Enter <row><col><val>, <display> to show possibilities, or <enter> to exit: 130
Probability empty
5×5 Matrix{Float64}:
1.0 0.0 1.0 1.0 1.0
0.8 0.8 0.8 1.0 1.0
0.8 0.8 1.0 0.0 1.0
1.0 1.0 0.8 0.8 0.8
1.0 1.0 0.8 0.8 1.0
Probability of 1: Losgann
5×5 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0
0.2 0.2 0.2 0.0 0.0
0.2 0.2 0.0 0.0 0.0
0.0 0.0 0.2 0.2 0.2
0.0 0.0 0.2 0.2 0.0
*** SOLVED ***: 2: Garcius
5×5 Matrix{Float64}:
0.0 1.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 1.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
Expected information
5×5 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0
0.72 0.72 0.72 0.0 0.0
0.72 0.72 0.0 0.0 0.0
0.0 0.0 0.72 0.72 0.72
0.0 0.0 0.72 0.72 0.0
5 possibilities remaining
Enter <row><col><val>, <display> to show possibilities, or <enter> to exit: 211
Probability empty
5×5 Matrix{Float64}:
1.0 0.0 1.0 1.0 1.0
0.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 0.0 1.0
1.0 1.0 0.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0
*** SOLVED ***: 1: Losgann
5×5 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0
1.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
*** SOLVED ***: 2: Garcius
5×5 Matrix{Float64}:
0.0 1.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 1.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
Expected information
5×5 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
1 possibilities remaining
.2...
1....
...2.
..1..
.....
1 different possibilities
Set{Matrix{Int64}} with 1 element:
[0 2 … 0 0; 1 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]
julia>
Julia code
# Pocket Frogs and Disco Zoo search minigame solver
#
# - v1.01, 2026-02-05, Joseph Chou
#
# - for Pocket Frogs (https://en.wikipedia.org/wiki/Pocket_Frogs) and Disco Zoo (https://en.wikipedia.org/wiki/Disco_Zoo)
# - analyzes all possible solutions
# - calculates probability at given location for each target
# - calculates expected information for each location for reducing possibilities
#
# Search game patterns
# - Pocket Frogs: https://docs.google.com/spreadsheets/d/1UeN9LeU5ko1fwAPwPaFIkL8yLk6wc95Lyq1c4rEeiNA/edit?gid=1783165384#gid=1783165384
# - Disco Zoo: https://discozoo.fandom.com/wiki/
#
# Information theory:
# - 3Blue1Brown Wordle information theory: https://www.youtube.com/watch?v=v68zYyaEmEA
# - I = -log2(p), where p = probability of target "present" with this test
# - expected information: summation [ p(x) • log2(1/p(x)) ] over all the possibilities = Entropy
# Global Variables -- frog_patterns and zoo_patterns dictionaries
# - key = species
# - values = 0-based pattern offsets as matrix, each row one location
frog_patterns = Dict(
"Porto" => [0 0; 0 1; 1 0; 1 1], # level 10
"Cerebrum" => [0 0; 0 2; 1 1], # level 20
"Garcius" => [0 0; 2 2], # level 36
"Anura" => [0 0; 0 3; 1 0; 1 3],
"Crustalli" => [0 0; 0 1; 0 2; 1 1],
"Nasus" => [0 0; 0 1; 0 2; 1 1],
"Velatus" => [0 3; 1 0; 1 1; 1 2],
"Bovis" => [0 3; 1 0; 1 1; 1 2],
"Clunicula" => [0 1; 0 2; 1 0; 1 3],
"Marmorea" => [0 0; 0 3; 1 1; 2 1],
"Puncti" => [0 0; 0 3; 3 0; 3 3],
"Zebrae" => [0 0; 0 1; 1 1; 1 2],
"Adamantis" => [0 1; 1 0; 1 2; 2 1],
"Stellata" => [0 1; 1 0; 1 2; 2 1],
"Africanus" => [0 2; 1 0; 2 3; 3 1],
"Partiri" => [0 0; 0 1; 0 2; 0 3],
"Roboris" => [0 0; 0 2; 1 1; 2 1],
"Mixtus" => [0 0; 1 0; 1 3; 2 1],
"Calyx" => [0 0; 1 0; 2 0; 3 0],
"Cesti" => [0 0; 1 0; 2 0; 3 0],
"Viduo" => [0 0; 0 1; 2 0; 2 1],
"Serpentis" => [0 1; 1 0; 2 1; 3 0],
"Figularis" => [0 1; 1 0; 2 1; 3 0],
"Spinae" => [0 1; 2 0; 2 1; 2 2],
"Ocularis" => [0 1; 2 0; 2 1; 2 2],
"Nimbilis" => [0 0; 0 1; 1 0; 1 1],
"Lunaris" => [0 0; 0 1; 1 0; 1 1],
"Sagitta" => [0 1; 1 0; 1 2; 3 1],
"Amfractus" => [0 1; 1 0; 2 0; 3 1],
"Ornatus" => [0 2; 1 1; 2 0; 3 0],
"Sol" => [0 3; 1 2; 2 0; 3 1],
"Lucus" => [0 1; 1 0; 1 2; 1 3],
"Ligo" => [0 3; 1 2; 2 1; 3 0],
"Corona" => [0 0; 1 1; 2 1; 3 0],
"Arbor" => [0 0; 1 1; 2 2; 3 2],
"Insero" => [0 0; 0 1; 2 0; 3 1],
"Biplex" => [0 1; 0 2; 1 0; 2 2],
"Pingo" => [0 0; 1 1; 2 0; 2 2],
"Calvaria" => [0 0; 0 3; 1 1; 1 2],
"Floresco" => [0 0; 0 1; 1 1; 2 0],
"Magus" => [0 1; 0 2; 2 0; 2 3],
"Veru" => [0 0; 1 1; 2 1; 3 1],
"Templum" => [0 1; 1 1; 2 0; 2 2],
"Papilio" => [0 0; 1 0; 1 1; 2 0],
"Pluma" => [0 0; 0 1; 1 2; 1 3],
"Cornus" => [0 0; 0 1; 1 1; 2 2],
"Volta" => [0 1; 0 3; 1 0; 1 2],
"Flecto" => [0 0; 1 1; 1 3; 2 2],
"Aceris" => [0 1; 1 0; 1 1; 1 2],
"Tabula" => [0 1; 1 0; 3 2],
"Pictoris" => [0 0; 0 3; 1 2],
"Shelbus" => [0 0; 1 0; 1 2],
"Skeletos" => [0 0; 0 3; 2 1],
"Pyramis" => [0 3; 1 0; 2 1],
"Lotus" => [0 0; 1 1; 2 2],
"Mustacium" => [0 0; 1 1; 2 2],
"Spira" => [0 2; 1 0; 2 1],
"Planeta" => [0 0; 0 1; 2 2],
"Mazeus" => [0 0; 1 1; 2 3],
"Palma" => [0 1; 2 0; 2 2],
"Coclearis" => [0 1; 3 0; 3 2],
"Axis" => [0 1; 3 0; 3 2],
"Igneous" => [0 1; 2 1; 3 0],
"Infinitas" => [0 1; 1 0; 1 2],
"Janus" => [0 1; 1 0; 1 2],
"Gemma" => [0 1; 1 0; 1 2],
"Nodare" => [0 1; 1 0; 2 0],
"Dimidius" => [0 1; 1 0; 2 1],
"Scutulata" => [0 0; 0 2; 2 2],
"Arcus" => [0 0; 2 1; 3 2],
"Botulus" => [0 0; 1 1; 2 0],
"Americano" => [0 0; 0 1; 2 0],
"Symphonia" => [0 0; 0 2; 1 0],
"Vicis" => [0 0; 2 1; 3 0],
"Levar" => [0 2; 1 0; 1 2],
"Favus" => [0 2; 1 0; 2 3],
"Tessera" => [0 2; 2 0; 3 1],
"Frondis" => [0 1; 1 2; 2 0],
"Pistrix" => [0 1; 2 0; 2 3],
"Nebula" => [0 1; 3 0],
"Fractus" => [0 1; 2 0],
"Spargo" => [0 1; 2 0],
"Hennae" => [0 1; 1 0],
"Fortuno" => [0 1; 1 0],
"Lentium" => [0 1; 1 0],
"Pulvillus" => [0 0; 2 2],
"Foramen" => [0 0; 2 2],
"Quilta" => [0 0; 1 1],
"Emblema" => [0 2; 2 0],
"Splendico" => [0 0; 0 3],
"Hexas" => [0 0; 2 3],
"Latus" => [0 0; 0 2],
"Altilium" => [0 1; 2 1],
"Procursus" => [0 0; 3 0],
"Stillas" => [0 0; 1 2],
# "Pompeius" => [0 2; 2 0; 3 1],
"Pompeius" => [0 2; 3 0],
"Inverso" => [0 2; 3 0],
"Exclamo" => [0 0; 1 0],
"Garcius" => [0 0; 2 2],
"Conexus" => [0 1; 2 0],
"Losgann" => [0 0; 2 2],
"Topologia" => [0 2; 3 0]
)
zoo_patterns = Dict(
# Farm region
"Pig" => [0 0; 0 1; 1 0; 1 1],
"Sheep" => [0 0; 0 1; 0 2; 0 3],
"Rabbit" => [0 0; 1 0; 2 0; 3 0],
"Cow" => [0 0; 0 1; 0 2],
"Horse" => [0 0; 1 0; 2 0],
"Unicorn" => [0 0; 1 1; 1 2],
"Chicken" => [0 2; 1 1; 2 0],
# Outback region
"Kangaroo" => [0 0; 1 1; 2 2; 3 3],
"Crocodile" => [0 0; 0 1; 0 2; 0 3],
"Platypus" => [0 0; 0 1; 1 1; 1 2],
"Koala" => [0 0; 0 1; 1 1],
"Cockatoo" => [0 0; 1 1; 2 1],
"Tiddaklik" => [0 1; 1 0; 1 2],
"Echidna" => [0 2; 1 0; 1 1],
# Savanna region
"Zebra" => [0 1; 1 0; 1 2; 2 1],
"Hippo" => [0 0; 0 2; 2 0; 2 2],
"Giraffe" => [0 0; 1 0; 2 0; 3 0],
"Lion" => [0 0; 0 1; 0 2],
"Elephant" => [0 0; 0 1; 1 0],
"Gryphon" => [0 0; 0 2; 1 1],
"Rhinoceros" => [0 1; 1 0; 2 1],
# Northern region
"Bear" => [0 0; 0 1; 1 1; 2 1],
"Skunk" => [0 1; 0 2; 1 0; 1 1],
"Beaver" => [0 2; 1 0; 1 1; 2 2],
"Moose" => [0 0; 0 2; 1 1],
"Fox" => [0 0; 0 1; 1 2],
"Sasquatch" => [0 0; 1 0],
"Otter" => [0 0; 1 0; 1 1],
# Polar region
"Penguin" => [0 1; 1 1; 2 0; 2 2],
"Seal" => [0 0; 1 1; 1 3; 2 2],
"Muskox" => [0 0; 0 1; 1 0; 1 2],
"Polar Bear" => [0 0; 0 2; 1 2],
"Walrus" => [0 0; 1 1; 1 2],
"Yeti" => [0 0; 2 0],
"Snowy Owl" => [0 0; 0 1; 1 1],
# Jungle region
"Toucan" => [0 1; 1 0; 2 1; 3 1],
"Monkey" => [0 0; 0 2; 1 1; 1 3],
"Gorilla" => [0 0; 0 2; 1 0; 1 2],
"Panda" => [0 2; 1 0; 2 2],
"Tiger" => [0 0; 0 2; 0 3],
"Phoenix" => [0 0; 2 2],
"Lemur" => [0 0; 1 1; 2 0],
# Jurassic region
"Diplodocus" => [0 0; 1 1; 1 2; 2 1],
"Stegosaurus" => [0 1; 0 2; 1 0; 1 3],
"Raptors" => [0 0; 0 1; 1 1; 2 2],
"T-Rex" => [0 0; 2 0; 2 1],
"Triceratops" => [0 0; 1 2; 2 0],
"Dragon" => [0 0; 1 2],
"Ankylosaurus" => [0 2; 1 0; 1 2],
# Ice Age region
"Giant Sloth" => [0 0; 1 2; 2 0; 2 2],
"Wooly Rhino" => [0 2; 1 0; 1 3; 2 1],
"Dire Wolf" => [0 1; 1 0; 1 3; 2 1],
"Saber Tooth" => [0 0; 1 2; 2 1],
"Mammoth" => [0 1; 1 0; 2 2],
"Akhlut" => [0 2; 1 0; 2 2],
"Yukon Camel" => [0 2; 1 0; 2 3],
# City region
"Raccoon" => [0 0; 0 2; 1 0; 1 3],
"Pigeon" => [0 0; 1 1; 2 1; 2 2],
"Rat" => [0 0; 0 1; 1 1; 1 3],
"Squirrel" => [0 2; 1 0; 2 1],
"Opossum" => [0 0; 1 0; 1 2],
"Sewer Turtle" => [0 0; 0 1],
"Chipmunk" => [0 1; 1 0; 1 3],
# Mountain region
"Goat" => [0 0; 1 0; 1 1; 1 2],
"Cougar" => [0 0; 1 1; 2 0; 2 2],
"Elk" => [0 0; 0 2; 1 1; 1 2],
"Eagle" => [0 0; 1 0; 2 1],
"Coyote" => [0 0; 0 1; 1 2],
"Aatxe" => [0 2; 1 0],
"Pika" => [0 0; 0 2; 1 2],
# Nocturnal region: https://discozoo.fandom.com/wiki/Nocturnal
"Badger" => [0 2; 1 0; 1 2; 2 0],
"Bat" => [0 0; 0 2; 1 1; 2 1],
"Kiwi" => [0 1; 1 0; 1 2; 2 2],
"Flying squirrel" => [0 2; 1 0; 1 3],
"Kakapo" => [0 0; 0 1; 2 1],
"Ghost" => [0 0; 1 1],
"Firefly" => [0 0; 1 0; 2 2],
# Moon region
"Moonkey" => [0 0; 1 0; 1 2; 2 2],
"Lunar tick" => [0 1; 2 1; 3 0; 3 2],
"Tribble" => [0 1; 1 0; 1 1; 1 2],
"Moonicorn" => [0 0; 1 0; 1 1],
"Luna moth" => [0 0; 0 2; 2 1],
"Jade rabbit" => [0 0; 2 1],
"Babmoon" => [0 1; 1 2; 2 0],
# Mars region
"Marsmot" => [0 1; 1 1; 2 0; 2 1],
"Marsmoset" => [0 0; 0 2; 1 2; 2 1],
"Rock" => [0 0; 0 1; 1 0; 1 1],
"Rover" => [0 1; 1 0; 1 2],
"Martian" => [0 0; 0 2; 1 1],
"Marsmallow" => [0 0; 2 0],
"Marsten" => [0 0; 0 2; 1 3],
# Constellation region
"Chamaeleon" => [0 1; 0 3; 1 0; 1 1],
"Corvus" => [0 1; 0 2; 2 0; 2 2],
"Lynx" => [0 1; 1 0; 1 1; 2 0],
"Pisces" => [0 1; 1 2; 2 0],
"Capricornus" => [0 0; 0 3; 1 2],
"Pegasus" => [0 2; 2 0],
"Horologium" => [0 0; 1 2; 2 2],
# Pets? https://discozoo.fandom.com/wiki/Pets -- incomplete or inaccurate?
"nothing" => [0 0],
"pet_rabbit" => [0 0; 0 2; 1 0; 1 2; 2 1],
"pet_bird" => [0 0; 0 2; 0 4; 1 1; 1 3],
"pet_dog" => [0 1; 1 0; 1 1; 2 0; 2 1],
"pet_cat" => [0 0; 0 1; 1 0; 2 0; 2 1],
"pet_fish" => [0 3; 1 0; 1 1; 1 2; 2 3],
"pet_turtle" => [0 0; 0 1; 0 2; 1 1; 2 1],
"pet_lizard" => [0 0; 1 0; 2 0; 2 1; 2 2],
"pet_hamster" => [0 0; 0 2; 1 1; 2 0; 2 2]
)
function match_string(input, possibilities)
# Slightly fuzzy matching function for input within possibilities
#
# Complete full match -- highest priority (in case short full name contained within other names)
matches = filter(s -> occursin(Regex("^"*input*"\$", "i"), s), possibilities) |> collect
if length(matches) == 1
return(matches[1])
end
# Any match, anchored to beginning
matches = filter(s -> occursin(Regex("^"*input, "i"), s), possibilities) |> collect
# if lowercase(input) ∈ lowercase.(possibilities)
if length(matches) == 1
match = matches[1]
return(match)
else
print("No unique match for '$input': $matches\n\n")
return ""
end
end
function get_input(possibilities, prompt = "")
# Prompt for comma-separated input of the target species and return array of matched species
# - uses match_string() function for fuzzy, case-insensitive matching
print(prompt)
inputs = [match_string(strip(s), possibilities) for s in split(readline(), ",")]
if all(s -> s != "", inputs)
return inputs
else
return String[] # length 0 if any invalid
end
end
function goal_starts(type, number, patterns)
# Create all possible pattern starts for "type", assigning value "number" in positions of a 5x5 matrix
# - cycle through all positions and place pattern if legal
# - returns array of 5x5 matrices (each containing a possible pattern location)
pattern = patterns[type]
s_valid = Matrix{Int64}[]
for i in 1:5
for j in 1:5
if (maximum(pattern[:,1]) + i > 5) | (maximum(pattern[:,2]) + j > 5)
continue # Skip if highest row and col offsets out of matrix bounds
else
m = zeros(Int, 5, 5)
for loc in eachrow(pattern)
m[i + loc[1], j + loc[2]] = number
end
push!(s_valid, copy(m))
end
end
end
return s_valid
end
function check_valid_combo(combo)
# Determine whether an interable of starting patterns locations (`combo`) can exist on grid without position conflicts
# - each element of `combo` is a 5x5 matrix containing 0's at empty locations and a # where the pattern is
# - return `true` if all elements of `combo` can co-exist on the grid without positiojn conflicts
for i in 1:5
for j in 1:5
vals = [x[i, j] for x in combo]
if length([x for x in unique(vals) if x != 0]) > 1
return false # conflict at position i,j
end
end
end
return true
end
function game_starts(goals, patterns)
# Given a list of goal species, return a set of all possible game matrices with all goals placed
# - this is basically returning a set of ALL possible starting positions
# - later, each move will filter the set to remaining possible positions
goal_starts_array = [goal_starts(type, i, patterns) for(i, type) in enumerate(goals)]
s_all = Set{Matrix{Int64}}() # Set of all possibilities
for combo in Iterators.product(goal_starts_array...) # tuples
if check_valid_combo(combo)
push!(s_all, sum(combo))
end
end
return s_all
end
function game_move(game_set, row, col, val)
# Given a set of possible game positions and a value at a given row/col coordinate, return all possible remaining sets
# - after a position is checked, position `row` and `col` will be known to contain `val` (0 for empty, # for species)
# - the function will remove from the set of remaining possible starting positions those that conflict with row/col/val
game_filter = Set{Matrix{Int64}}() # empty set
for s in game_set
if s[row, col] == val
push!(game_filter, s)
end
end
if length(game_filter) == 0
print("\n*** ERROR *** no valid positions -- ignoring move\n\n")
return(game_set) # return original input -- data entry error at some point
else
return(game_filter) # return remaining possibilities
end
end
function game_prob(game_set, value; num_digits = 2)
# For a set of matrices of all game possibilities, return matrix of probability of value at each position in matrix
# - from the remaining possibilities, return a 5x5 matrix that returns the probability of `value` appearing at each position
probs = round.(sum([m .== value for m in game_set]) ./ length(game_set); digits = num_digits)
return probs
end
function game_info(game_set, goals)
# Return expected information content of each position in remaining positions
#
# From 3Blue1Brown Wordle information theory: https://www.youtube.com/watch?v=v68zYyaEmEA
# - I = -log2(p), where p = probability of target "present" with this test
# - expected information: summation [ p(x) • log2(1/p(x)) ] over all the possibilities = Entropy
x = [ sum([m .== value for m in game_set]) ./ length(game_set) for value in 0:length(goals) ]
info_matrix = [map(x -> x == 0.0 ? 0.0 : -log2(x), matrix) for matrix in x]
prob_matrix = [game_prob(game_set, goal; num_digits = 3) for goal in 0:length(goals)] # this will be ONE-indexed though
sum([ a.* b for (a,b) in zip(info_matrix, prob_matrix)])
end
function print_grid(game_set; per_row = 10)
# display set of all possible game positions, in a grid for compactness
games = collect(game_set)
n = length(games)
for batch_start in 1:per_row:n
batch = games[batch_start:min(batch_start+per_row-1, n)]
rows = size(batch[1], 1) # assumes all game are the same size
for r in 1:rows
for m in batch
line = join(map(x -> x == 0 ? "." : x, m[r, :]), "")
print(line, " ")
end
println()
end
println() # blank line between matrix rows
end
println("$n different possibilities")
end
function game_status(game, goals)
# Given remaining game matrix possibilities and vector of goal strings:
# - display probabilities for each goal
# - display expected information content
for goal in 0:length(goals) # 0:length(goals) if want to show probability of empties
probs = game_prob(game, goal)
if goal == 0
println("Probability empty")
else
if (all(x -> x in (0.0, 1.0), probs))
println("\n*** SOLVED ***: $goal: $(goals[goal])\n")
else
println("Probability of $goal: $(goals[goal])")
end
end
display(probs)
println("\n")
end
println("Expected information")
display(round.(game_info(game, goals), digits = 2))
println("\n")
println(string(length(game)) * " possibilities remaining\n")
end
function play_game(;game_mode = "frog") # game_mode = "zoo" or "frog"
# Wrapper function for the entire game loop
# - returns an array of remaining game possibilities
# - call with `;` to silence showing return value
if game_mode == "zoo"
patterns = zoo_patterns
else
patterns = frog_patterns
end
possibilities = keys(patterns) # global variable needed for all possible goals
goals = String[]
while true
goals = get_input(possibilities, "Enter comma-separated list of species: ")
if length(goals) != 0
break # something matched, so continue to game play
end
end
game = game_starts(goals, patterns) # generate set of all possible starting positions
game_status(game, goals) # display current status
println("* <row> and <col> range from 1 to 5\n* <val> is 0 for empty space or # for goal number\n* <display> shows all remaining possibilities\n")
while length(game) != 1
print("Enter <row><col><val>, <display> to show possibilities, or <enter> to exit: ")
line = readline()
if line == ""
break
elseif line == "display"
print_grid(game; per_row = 10) # show all remaining possibilities for game positions
else
game = game_move(game, parse.(Int, collect(line))...) # parse string ### into array of 3 Int of row / col / val
game_status(game, goals) # display current status
end
end
print("\n\n")
print_grid(game)
return game
end