Martin Escardo, Paulo Oliva, 27th October 2022
Implementation of tic-tac-toe using a general definition of finite
history dependent game.
\begin{code}
{-# OPTIONS --safe --without-K #-}
module Games.TicTacToe0 where
open import Fin.ArgMinMax
open import Fin.Topology
open import Fin.Type
open import Games.TypeTrees
open import MLTT.Athenian
open import MLTT.Spartan hiding (J)
open import MonadOnTypes.J
open import MonadOnTypes.K
open import TypeTopology.CompactTypes
open import TypeTopology.SigmaDiscreteAndTotallySeparated
open import UF.DiscreteAndSeparated
\end{code}
The type of outcomes:
\begin{code}
R : Type
R = Fin 3
open import Games.FiniteHistoryDependent R
open import MonadOnTypes.JK
\end{code}
The meaning of the outcomes:
\begin{code}
X-wins draw O-wins : R
X-wins = π
draw = π
O-wins = π
\end{code}
In our conception of game, it is not necessary to specify the players,
but in this case it is convenient to do so:
\begin{code}
data Player : Type where
X O : Player
opponent : Player β Player
opponent X = O
opponent O = X
value : Player β R
value X = X-wins
value O = O-wins
\end{code}
It is also convenient to have a type of boards:
\begin{code}
Grid = Fin 3 Γ Fin 3
Matrix = Grid β Maybe Player
Board = Player Γ Matrix
boardβ : Board
boardβ = X , (Ξ» _ β Nothing)
\end{code}
Convention: in a board (p , A), the player p plays next.
\begin{code}
wins : Player β Matrix β Bool
wins p A = line || col || diag
where
_is_ : Maybe Player β Player β Bool
Nothing is _ = false
Just X is X = true
Just O is X = false
Just X is O = false
Just O is O = true
infix 30 _is_
lβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
lβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
lβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
cβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
cβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
cβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
dβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
dβ = A (π , π) is p && A (π , π) is p && A (π , π) is p
line = lβ || lβ || lβ
col = cβ || cβ || cβ
diag = dβ || dβ
\end{code}
The type of moves in a board:
\begin{code}
Move : Board β Type
Move (_ , A) = Ξ£ g κ Grid , A g οΌ Nothing
\end{code}
The type of grids has decidable equality (it is discrete) and
decidable quantification (it is compact). The type of moves in a
board is decidable (either empty or pointed) and also has decidable
quantification.
\begin{code}
Grid-is-discrete : is-discrete Grid
Grid-is-discrete = Γ-is-discrete Fin-is-discrete Fin-is-discrete
Grid-compact : is-Compact Grid {π€β}
Grid-compact = Γ-is-Compact Fin-Compact Fin-Compact
Move-decidable : (b : Board) β is-decidable (Move b)
Move-decidable (_ , A) = Grid-compact
(Ξ» g β A g οΌ Nothing)
(Ξ» g β Nothing-is-isolated' (A g))
Move-compact : (b : Board) β is-Compact (Move b)
Move-compact (x , A) = complemented-subset-of-compact-type
Grid-compact
(Ξ» g β Nothing-is-isolated' (A g))
(Ξ» g β Nothing-is-h-isolated' (A g))
\end{code}
Update a matrix by playing a move:
\begin{code}
update : (p : Player) (A : Matrix) β Move (p , A) β Matrix
update p A (g , _) g' with (Grid-is-discrete g g')
... | inl _ = Just p
... | inr _ = A g'
\end{code}
Update a board by playing a move:
\begin{code}
play : (b : Board) β Move b β Board
play (p , A) m = opponent p , update p A m
\end{code}
The game tree, with a bound on which we perform induction:
\begin{code}
tree : Board β β β π»
tree b 0 = []
tree b@(p , A) (succ k) with wins (opponent p) A | Move-decidable b
... | true | _ = []
... | false | inl _ = Move b β· (Ξ» (m : Move b) β tree (play b m) k)
... | false | inr _ = []
\end{code}
The outcome function:
\begin{code}
outcome : (b : Board) (k : β) β Path (tree b k) β R
outcome b 0 β¨β© = draw
outcome b@(p , A) (succ k) ms with wins (opponent p) A | Move-decidable b
outcome b@(p , A) (succ k) β¨β© | true | _ = value (opponent p)
outcome b@(p , A) (succ k) (m :: ms) | false | inl _ = outcome (play b m) k ms
outcome b@(p , A) (succ k) β¨β© | false | inr _ = draw
\end{code}
Selection functions for players, namely argmin for X and argmax for O:
\begin{code}
open J-definitions R
selection : (p : Player) {M : Type} β M β is-Compact M {π€β} β J M
selection X m ΞΊ p = prβ (compact-argmin p ΞΊ m)
selection O m ΞΊ p = prβ (compact-argmax p ΞΊ m)
\end{code}
And their derived quantifiers:
\begin{code}
open K-definitions R
open JK R
quantifier : Player β {M : Type} β is-Compact M β is-decidable M β K M
quantifier p ΞΊ (inl m) = overline (selection p m ΞΊ)
quantifier p ΞΊ (inr _) = Ξ» _ β draw
\end{code}
The quantifier tree for the game:
\begin{code}
quantifiers : (b : Board) (k : β) β π (tree b k)
quantifiers b 0 = β¨β©
quantifiers b@(p , A) (succ k) with wins (opponent p) A | Move-decidable b
... | true | _ = β¨β©
... | false | inl _ = quantifier p (Move-compact b) (Move-decidable b)
:: (Ξ» (m : Move b) β quantifiers (play b m) k)
... | false | inr _ = β¨β©
\end{code}
And finally the game by putting the above together:
\begin{code}
tic-tac-toe : Game
tic-tac-toe = game (tree boardβ 9) (outcome boardβ 9) (quantifiers boardβ 9)
r : R
r = optimal-outcome tic-tac-toe
\end{code}
The above computation takes too long, due to the use of brute-force
search in the definition of the game (the compactness conditions). A
more efficient one is in another file.
\begin{code}
selections : (b : Board) (k : β) β π (tree b k)
selections b 0 = β¨β©
selections b@(p , A) (succ k) with wins (opponent p) A | Move-decidable b
... | true | _ = β¨β©
... | false | inl mβ = selection p mβ (Move-compact b)
:: (Ξ» m β selections (play b m) k)
... | false | inr _ = β¨β©
p : Path (game-tree tic-tac-toe)
p = sequenceα΄Ά (selections boardβ 9) (payoff-function tic-tac-toe)
\end{code}