Documentation

CombinatorialGames.Ruleset.Strip

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.

inductive Strip.Piece :

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

Instances For
    @[instance_reducible]
    Equations
    structure Strip.Board :

    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 Strip.Board.ext {s t : Board} (h : ∀ (n : ℕ), s.board n = t.board n) :
      s = t
      theorem Strip.Board.ext_iff {s t : Board} :
      s = t ↔ ∀ (n : ℕ), s.board n = t.board n

      The empty board.

      Equations
      Instances For

        shiftDown: the shared move primitive #

        def 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 Strip.Board.shiftDown_lt_start (b : ℕ → Piece) {start n i : ℕ} (h : i < start) :
          shiftDown b start n i = b i
          @[simp]
          theorem 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 Strip.Board.shiftDown_eq (b : ℕ → Piece) {start n : ℕ} (h : start ≤ n) :
          shiftDown b start n n = Piece.none
          @[simp]
          theorem Strip.Board.shiftDown_gt (b : ℕ → Piece) {start n i : ℕ} (h : n < i) :
          shiftDown b start n i = b i
          def Strip.Board.shiftDownB (s : Board) (start n : ℕ) :

          shiftDown applied to a board with finite support.

          Equations
          Instances For
            @[simp]
            theorem 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 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 Strip.Board.weight_eq_of_bound {s : Board} (N : ℕ) (hN : ∀ (n : ℕ), N ≤ n → s.board n = Piece.none) :
              s.weight = ∑ i ∈ Finset.range N, if s.board i = Piece.none then 0 else i + 1
              theorem 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 #

              noncomputable def Strip.Board.rightmostPos (s : Board) :

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

              Equations
              Instances For

                The stride #

                noncomputable def 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 Strip (R : Type u) :
                  • toBoard : R → Board

                    The underlying board of a position.

                  • ofBoard : Board → R

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

                  • toBoard_ofBoard (b : Board) : toBoard (ofBoard b) = b
                  • push : R → ℕ → R

                    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 Strip.board {R : Type u} [Strip R] (r : R) (n : ℕ) :

                    The piece at position n of r.

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

                      Weight of a position.

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

                        Rightmost occupied position.

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

                          The p-stride of a position.

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

                            Structural lemmas for push #

                            theorem Strip.board_push_above {R : Type u} [Strip R] (r : R) {n i : ℕ} (h : n < i) :
                            board (push r n) i = board r i
                            @[simp]
                            theorem Strip.board_push_eq {R : Type u} [Strip R] (r : R) (n : ℕ) :
                            theorem 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 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 Strip.weight_push_lt {R : Type u} [Strip R] (r : R) {n : ℕ} (hn : board r n ≠ Piece.none) :

                            Moves and the associated GameForm #

                            def Strip.moves {R : Type u} [Strip R] :
                            Player → R → Set R
                            Equations
                            Instances For
                              theorem 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
                              def Strip.graph (R : Type u) [Strip R] :

                              The game graph of a strip ruleset.

                              Equations
                              Instances For
                                theorem 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 Strip.toGameForm {R : Type u} [Strip R] :
                                R → GameForm

                                The GameForm of a strip position.

                                Equations
                                Instances For
                                  theorem Strip.toGameForm_zero_iff {R : Type u} [Strip R] (g : R) :
                                  theorem Strip.mem_moves_toGameForm_iff {R : Type u} [Strip R] (s : R) (p : Player) (g' : GameForm) :

                                  rightmostPos and stride under push #

                                  theorem Strip.rightmostPos_push_below {R : Type u} [Strip R] {s : R} {m : ℕ} (hm : m < rightmostPos s) :
                                  theorem 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 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 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 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 Strip.push_rightmost_zero_empty {R : Type u} [Strip R] {s : R} (h0 : rightmostPos s = 0) (n : ℕ) :
                                  theorem 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 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 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 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 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 Strip.isSolved_of_stride_zero {R : Type u} [Strip R] {p : Player} {s : R} (hs : stride s p = 0) :
                                  theorem Strip.rightmostPos_greatest {R : Type u} [Strip R] {s : R} {k : ℕ} (hk : board s k ≠ Piece.none) :
                                  theorem Strip.rightmost_of_stride_pos {R : Type u} [Strip R] {p : Player} {s : R} (hs : stride s p ≠ 0) :
                                  theorem 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.

                                  @[instance_reducible]
                                  noncomputable instance 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