implement a monotonicity scoring heuristic
add an alpha-beta pruned minimax implementation
rearrange code a bit
fix cache validation criteria in SearcherCachingMinimax

SearcherCachingMinimax now gives the same result as SearcherNaiveMinimax
remove unused methods from BoardCache
rearrange code a bit to delineate which code can use global state
extract the rendering code out of the can-use-globals code section
change order of fields in SearcherCachingMinimax::Info

I don't know why, I just prefer lookup to be first. Maybe because it's
conceptually part of the key rather than the cached value.
verify alpha-beta and caching-minimax against naive-minimax
add utility function imin()
be more explicit about which search algo each printed stat comes from
implement a caching version of alpha-beta pruned minimax

This is currently slightly incorrect (it does not always precisely match
the outcome from plain naive minimax)
move imin up above SearcherAlphaBeta so it can be used in that
restructure SearcherAlphaBeta to hopefully improve clarity
rename SearcherAlphaBeta::{pruned => num_pruned}

For consistency and clarity.
don't use Board::count_free() to iterate over possible new tile positions

Instead, just iterate directly over the cells, since that's what count_free
does anyway, and non-free tiles can be trivially skipped.
extract method SearcherCachingMinimax::tally_cache_hit

For clarity.
initialise with a predefined board configuration and RNG state

For testing the searchers.
tally evaluated moves in SearcherAlphaBeta
print board and RNG state before each AI move

So that when the AI fails, I can jump back to the failing board state.