Documentation

LeanPool.MisereGames.Ruleset.Strip

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:

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:

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.

A single square of a strip: empty, or occupied by a Left/Right piece.

Instances For
    @[implicit_reducible]
    Equations

    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).

    Instances For
      theorem MisereGames.Strip.Board.ext {s t : Board} (h : ∀ (n : ), s.board n = t.board n) :
      s = t
      theorem MisereGames.Strip.Board.ext_iff {s t : Board} :
      s = t ∀ (n : ), s.board n = t.board n

      shiftDown: the shared move primitive #

      def MisereGames.Strip.Board.shiftDown (b : Piece) (start n : ) :

      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
        @[simp]
        theorem MisereGames.Strip.Board.shiftDown_lt_start (b : Piece) {start n i : } (h : i < start) :
        shiftDown b start n i = b i
        @[simp]
        theorem MisereGames.Strip.Board.shiftDown_mid (b : Piece) {start n i : } (h1 : start i) (h2 : i < n) :
        shiftDown b start n i = b (i + 1)
        @[simp]
        theorem MisereGames.Strip.Board.shiftDown_eq (b : Piece) {start n : } (h : start n) :
        shiftDown b start n n = Piece.none
        @[simp]
        theorem MisereGames.Strip.Board.shiftDown_gt (b : Piece) {start n i : } (h : n < i) :
        shiftDown b start n i = b i

        shiftDown applied to a board with finite support.

        Equations
        Instances For
          @[simp]
          theorem MisereGames.Strip.Board.shiftDownB_board (s : Board) (start n : ) :
          (s.shiftDownB start n).board = shiftDown s.board start n

          Weight measure for well-foundedness #

          noncomputable def MisereGames.Strip.Board.weight (s : Board) :

          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
            theorem MisereGames.Strip.Board.weight_eq_of_bound {s : Board} (N : ) (hN : ∀ (n : ), N ns.board n = Piece.none) :
            s.weight = iFinset.range N, if s.board i = Piece.none then 0 else i + 1
            theorem MisereGames.Strip.Board.weight_shiftDownB_lt {s : Board} {start n : } (hsn : start n) (hn : s.board n Piece.none) :
            (s.shiftDownB start n).weight < s.weight

            The rightmost occupied position #

            The position of the rightmost occupied square (or 0 if the board is empty).

            Equations
            Instances For

              The stride #

              noncomputable def MisereGames.Strip.Board.stride (s : Board) (p : Player) :

              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
              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.

                class MisereGames.Strip (R : Type u) :

                A strip ruleset with positions backed by a finite-support board.

                • toBoard : RBoard

                  The underlying board of a position.

                • ofBoard : BoardR

                  Construct a position from a board (used to realise prescribed strides).

                • toBoard_ofBoard (b : Board) : toBoard (ofBoard b) = b
                • push : RR

                  The move: push the piece at position n.

                • start : R

                  The anchor position of the shift.

                • start_le (r : R) (n : ) : start r n n
                • start_lt (r : R) {n : } (hn : 0 < n) : start r n < n
                • push_eq (r : R) (n : ) : toBoard (push r n) = (toBoard r).shiftDownB (start r n) n

                  The move is a shiftDown of the board anchored at start.

                Instances
                  def MisereGames.Strip.board {R : Type u} [Strip R] (r : R) (n : ) :

                  The piece at position n of r.

                  Equations
                  Instances For
                    @[simp]
                    theorem MisereGames.Strip.board_def {R : Type u} [Strip R] (r : R) (n : ) :
                    board r n = (toBoard r).board n
                    noncomputable def MisereGames.Strip.weight {R : Type u} [Strip R] (r : R) :

                    Weight of a position.

                    Equations
                    Instances For
                      noncomputable def MisereGames.Strip.rightmostPos {R : Type u} [Strip R] (r : R) :

                      Rightmost occupied position.

                      Equations
                      Instances For
                        noncomputable def MisereGames.Strip.stride {R : Type u} [Strip R] (r : R) (p : Player) :

                        The p-stride of a position.

                        Equations
                        Instances For
                          theorem MisereGames.Strip.stride_def {R : Type u} [Strip R] (r : R) (p : Player) :

                          Structural lemmas for push #

                          theorem MisereGames.Strip.board_push_above {R : Type u} [Strip R] (r : R) {n i : } (h : n < i) :
                          board (push r n) i = board r i
                          theorem MisereGames.Strip.board_push_eq {R : Type u} [Strip R] (r : R) (n : ) :
                          theorem MisereGames.Strip.board_push_below {R : Type u} [Strip R] (r : R) {n i : } (h : i < start r n) :
                          board (push r n) i = board r i
                          theorem MisereGames.Strip.board_push_shift {R : Type u} [Strip R] (r : R) {n : } (hn : 0 < n) :
                          board (push r n) (n - 1) = board r n
                          theorem MisereGames.Strip.weight_push_lt {R : Type u} [Strip R] (r : R) {n : } (hn : board r n Piece.none) :

                          Moves and the associated GameForm #

                          def MisereGames.Strip.moves {R : Type u} [Strip R] :
                          PlayerRSet R

                          Legal moves in a strip ruleset.

                          Equations
                          Instances For
                            theorem MisereGames.Strip.mem_moves_iff {R : Type u} [Strip R] (p : Player) (s s' : R) :
                            s' Strip.moves p s ∃ (n : ), board s n = Piece.ofPlayer p s' = push s n

                            The game graph of a strip ruleset.

                            Equations
                            Instances For
                              theorem MisereGames.Strip.weight_lt_of_mem_move {R : Type u} [Strip R] {p : Player} {s s' : R} (h_mem : s' (graph R).moves p s) :
                              noncomputable def MisereGames.Strip.toGameForm {R : Type u} [Strip R] :
                              RGameForm

                              The GameForm of a strip position.

                              Equations
                              Instances For

                                rightmostPos and stride under push #

                                theorem MisereGames.Strip.rightmostPos_push_at {R : Type u} [Strip R] {s : R} {k : } (hk : rightmostPos s = k) (hpos : 0 < k) (hne : board s k Piece.none) :
                                rightmostPos (push s k) = k - 1
                                theorem MisereGames.Strip.stride_push_below {R : Type u} [Strip R] {s : R} (p : Player) {m : } (hm : m < rightmostPos s) :
                                stride (push s m) p = stride s p
                                theorem MisereGames.Strip.stride_push_at_rightmost {R : Type u} [Strip R] {s : R} (p : Player) {k : } (hk : rightmostPos s = k) (hpos : 0 < k) (hp : board s k = Piece.ofPlayer p) :
                                stride (push s k) p = k
                                theorem MisereGames.Strip.stride_neg_push_at_rightmost {R : Type u} [Strip R] {s : R} (p : Player) {k : } (hk : rightmostPos s = k) (hp : board s k = Piece.ofPlayer p) (hpos : 0 < k) :
                                stride (push s k) (-p) = 0
                                theorem MisereGames.Strip.push_rightmost_zero_empty {R : Type u} [Strip R] {s : R} (h0 : rightmostPos s = 0) (n : ) :
                                theorem MisereGames.Strip.stride_neg_zero_of_p_move {R : Type u} [Strip R] {s : R} (p : Player) {m : } (hrm : board s (rightmostPos s) = Piece.ofPlayer p) (hm : board s m = Piece.ofPlayer p) :
                                stride (push s m) (-p) = 0
                                theorem MisereGames.Strip.stride_push_eq_rightmostPos {R : Type u} [Strip R] {s : R} (p : Player) {m : } (hm1 : board s m Piece.none) (hm2 : m = rightmostPos s) :
                                stride (push s m) p = stride s p - 1
                                theorem MisereGames.Strip.stride_push_le {R : Type u} [Strip R] {s : R} (p : Player) {m : } (hm : board s m Piece.none) :
                                stride (push s m) p stride s p
                                theorem MisereGames.Strip.stride_zero_of_push {R : Type u} [Strip R] {s : R} {p : Player} {m : } (hs : stride s p = 0) (hm : board s m Piece.none) :
                                stride (push s m) p = 0
                                theorem MisereGames.Strip.push_to_empty_is_neg {R : Type u} [Strip R] {s : R} {p : Player} {m : } (hs : stride s p = 0) (hm_piece : board s m Piece.none) (h_empty : ∀ (n : ), board (push s m) n = Piece.none) :
                                theorem MisereGames.Strip.neg_push_below_rightmost {R : Type u} [Strip R] {s : R} {p : Player} {m : } (hrm : board s (rightmostPos s) = Piece.ofPlayer p) (hm : board s m = Piece.ofPlayer (-p)) :

                                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.

                                @[implicit_reducible]
                                noncomputable instance MisereGames.Strip.ruleset (R : Type u) [Strip R] :

                                Every Strip ruleset is a Ruleset, with the shared toGameForm.

                                Equations

                                The Strided structure on the additive closure of any Strip ruleset.

                                The misère quotient of any Strip ruleset is isomorphic to .

                                Equations
                                Instances For