Misere combinatorial games.
Strips: shared board theory for Shove and Push #
Both Shove and Push are played on a horizontal strip of squares, each
square either empty or occupied by a Left or Right piece (see
Strip.Piece). In both games a move consists of choosing one of your pieces
and shifting a contiguous "block" of pieces one square to the left.
The two rulesets differ only in which block of pieces is moved:
- In
Shove, choosing the piece at positionnshifts everything at positions0 .. none square to the left, regardless of gaps. The piece at position0falls off the strip. - In
Push, only the adjacent run of pieces moves: the block stops at the first empty square to the left ofn(which gets filled). If there is no empty square the leftmost piece falls off, just likeShove.
This module factors out everything that both rulesets have in common. The
single shared primitive is Strip.shiftDown b start n, which shifts the
squares (start, n] one place to the left into [start, n). Both rulesets are
special cases:
Shove's move isshiftDown b 0 n.Push's move isshiftDown b g n, wheregis the position of the first empty square left ofn(or0if there is none).
The crucial fact about either game is its stride: the distance to the
rightmost tile of a given colour. Since the stride only ever depends on the
rightmost tile, and since pushing the rightmost tile behaves identically in
Shove and Push, the entire stride theory — culminating in the proof that
the misère quotient is isomorphic to ℤ — is developed once here, generically,
in terms of a Strip typeclass.
The piece belonging to a given player.
Equations
Instances For
A strip board: a function from positions on the strip (leftmost being 0) to
the piece at that position, with finite support (only finitely many occupied
squares).
The piece at each board position.
Instances For
The empty board.
Equations
- MisereGames.Strip.Board.zero = { board := fun (x : ℕ) => MisereGames.Strip.Piece.none, finite_support := MisereGames.Strip.Board.zero._proof_1 }
Instances For
shiftDown b start n shifts the squares in (start, n] one place to the left,
into [start, n). Concretely: squares strictly below start are unchanged;
squares start ≤ i < n receive the piece from i + 1; square n becomes
empty; squares above n are unchanged.
When start = 0 this is the Shove move (the piece at 0 falls off). When
start is the position of the first empty square below n, the empty square
is filled and this is the Push move.
Equations
Instances For
shiftDown applied to a board with finite support.
Equations
- s.shiftDownB start n = { board := MisereGames.Strip.Board.shiftDown s.board start n, finite_support := ⋯ }
Instances For
Weight measure for well-foundedness #
The weight of a board is the sum of i + 1 over each non-empty position i.
This is finite by finite support, and strictly decreases with each shiftDown
(as long as the pushed square n was occupied).
Equations
Instances For
The rightmost occupied position #
The position of the rightmost occupied square (or 0 if the board is empty).
Equations
- s.rightmostPos = Nat.findGreatest (fun (n : ℕ) => s.board n ≠ MisereGames.Strip.Piece.none) ⋯.choose
Instances For
A nonzero rightmostPos is always occupied.
The stride #
The p-stride of a board: the distance to the rightmost tile when it is of
colour p, and 0 otherwise. This is the same definition for Shove and
Push.
Equations
- s.stride p = if s.board s.rightmostPos = MisereGames.Strip.Piece.ofPlayer p then s.rightmostPos + 1 else 0
Instances For
The Strip typeclass #
A Strip is a type R of game positions whose underlying data is a Board,
equipped with a move push r n (push the piece at position n). Both Shove
and Push are instances. The move is required to be a shiftDown of the
board, anchored at a position start r n ≤ n (with start r n < n whenever `n
0`). This single description captures both rulesets.
A strip ruleset with positions backed by a finite-support board.
- toBoard : R → Board
The underlying board of a position.
- ofBoard : Board → R
Construct a position from a board (used to realise prescribed strides).
- push : R → ℕ → R
The move: push the piece at position
n. The anchor position of the shift.
Instances
The piece at position n of r.
Equations
Instances For
Weight of a position.
Equations
Instances For
Rightmost occupied position.
Equations
Instances For
The p-stride of a position.
Equations
Instances For
Moves and the associated GameForm #
Legal moves in a strip ruleset.
Equations
Instances For
The game graph of a strip ruleset.
Equations
- MisereGames.Strip.graph R = { moves := MisereGames.Strip.moves }
Instances For
The GameForm of a strip position.
Instances For
rightmostPos and stride under push #
Every strip position has a stride: HasStride p (toGameForm s) (stride s p).
This is the heart of the shared theory.
Building the Strided structure and the equivalence with ℤ #
For every player p and every n, there is a strip position with p-stride
n and (-p)-stride 0: namely a single piece of colour p at position n - 1.
Every Strip ruleset is a Ruleset, with the shared toGameForm.
Equations
- MisereGames.Strip.ruleset R = { toGameForm := MisereGames.Strip.toGameForm, moves_toGameForm := ⋯ }
The Strided structure on the additive closure of any Strip ruleset.
The misère quotient of any Strip ruleset is isomorphic to ℤ.