-- Helper theorems theorem vector_count_set {α n} {i : Fin n} {pred : α → Bool} {value : α} {xs : Vector α n} {hOld : ¬pred xs[i]} {hNew : pred value} : Vector.countP pred (Vector.set xs i value) = Vector.countP pred xs + 1 := by simp at hOld let hCountSet := Vector.countP_set (p := pred) (xs := xs) (a := value) i.2 simp [hCountSet, hNew, hOld] theorem vector_count_set_other {α n} {i : Fin n} {pred : α → Bool} {value : α} {xs : Vector α n} {hOld : ¬pred xs[i]} {hNew : ¬pred value} : Vector.countP pred (Vector.set xs i value) = Vector.countP pred xs := by simp at hOld let hCountSet := Vector.countP_set (p := pred) (xs := xs) (a := value) i.2 simp [hCountSet, hNew, hOld] theorem tupToAnd {a b c d : Bool} (h : (a, b) = (c, d)) : (a = c) ∧ b = d := by grind theorem countDisjointList {xs} (hDisjoint : ∀ a, (pred1 a → ¬pred2 a) ∧ (pred2 a → ¬pred1 a)) : List.countP pred1 xs + List.countP pred2 xs = List.countP (fun x => pred1 x || pred2 x) xs := by match xs with | .nil => simp | .cons head .nil => simp match hPred : (pred1 head, pred2 head) with | (true, true) => let hDisjoint := hDisjoint head simp [tupToAnd hPred] at hDisjoint | (true, false) => simp [tupToAnd hPred] | (false, true) => simp [tupToAnd hPred] | (false, false) => simp [tupToAnd hPred] | .cons head tail => let hRec := countDisjointList (xs := tail) hDisjoint simp [List.countP_cons] match hPred : (pred1 head, pred2 head) with | (true, true) => let hDisjoint := hDisjoint head simp [tupToAnd hPred] at hDisjoint | (true, false) => simp [tupToAnd hPred] let hAssoc := Nat.add_assoc (List.countP pred1 tail) 1 (List.countP pred2 tail) let hComm := Nat.add_comm 1 (List.countP pred2 tail) rw [hAssoc, hComm] let hAssoc' := Nat.add_assoc (List.countP pred1 tail) (List.countP pred2 tail) 1 simp [← hAssoc', hRec] | (false, true) => simp [tupToAnd hPred] let hAssoc := Nat.add_assoc (List.countP pred1 tail) (List.countP pred2 tail) 1 let hComm := Nat.add_comm 1 (List.countP pred2 tail) rw [← hAssoc, hRec] | (false, false) => simp [tupToAnd hPred, hRec] theorem countDisjoint {n} {pred1 pred2 : α → Bool} {xs : Vector α n} (hDisjoint : ∀ a, (pred1 a → ¬pred2 a) ∧ (pred2 a → ¬pred1 a)) : Vector.countP pred1 xs + Vector.countP pred2 xs = Vector.countP (fun x => pred1 x || pred2 x) xs := by simp [← Vector.countP_toList] exact countDisjointList (xs := xs.toList) hDisjoint theorem countCompleteList {pred1 pred2 : α → Bool} {xs : List α} (hDisjoint : ∀ a, pred1 a != pred2 a) (hComplete : ∀ a, pred1 a || pred2 a) : List.countP pred1 xs + List.countP pred2 xs = List.length xs := by match xs with | .nil => simp | .cons head .nil => simp match hPred : (pred1 head, pred2 head) with | (true, true) => let hDisjoint := hDisjoint head simp [tupToAnd hPred] at hDisjoint | (true, false) => simp [tupToAnd hPred] | (false, true) => simp [tupToAnd hPred] | (false, false) => let h := hComplete head simp [tupToAnd hPred] at h | .cons head tail => let hRec := countCompleteList (xs := tail) hDisjoint hComplete simp [List.countP_cons] match hPred : (pred1 head, pred2 head) with | (true, true) => let hDisjoint := hDisjoint head simp [tupToAnd hPred] at hDisjoint | (true, false) => simp [tupToAnd hPred] let hAssoc := Nat.add_assoc (List.countP pred1 tail) 1 (List.countP pred2 tail) let hComm := Nat.add_comm 1 (List.countP pred2 tail) rw [hAssoc, hComm] let hAssoc' := Nat.add_assoc (List.countP pred1 tail) (List.countP pred2 tail) 1 simp [← hAssoc', hRec] | (false, true) => simp [tupToAnd hPred] let hAssoc := Nat.add_assoc (List.countP pred1 tail) (List.countP pred2 tail) 1 let hComm := Nat.add_comm 1 (List.countP pred2 tail) rw [← hAssoc, hRec] | (false, false) => let h := hComplete head simp [tupToAnd hPred] at h theorem countComplete {pred1 pred2 : α → Bool} {xs : Vector α n} (hDisjoint : ∀ a, pred1 a != pred2 a) (hComplete : ∀ a, pred1 a || pred2 a) : Vector.countP pred1 xs + Vector.countP pred2 xs = Vector.size xs := by have h : Vector.size xs = xs.toList.length := by simp rw [h] simp only [← Vector.countP_toList] exact countCompleteList (xs := xs.toList) hDisjoint hComplete -- Tic-tac-toe game inductive Player where | X : Player | O : Player deriving Repr, DecidableEq inductive Cell where | Empty : Cell | Occupied : Player → Cell deriving Repr, DecidableEq theorem cellNeqEmptyOccupied {player} : (Cell.Empty == Cell.Occupied player) = false := by grind def Board := Vector Cell 9 deriving Repr def Position := Fin 9 instance : Membership Cell Board := by dsimp [Board] infer_instance inductive GameStatus where | InProgress : GameStatus | Won : Player → GameStatus -- `Player` indicates who won | Draw : GameStatus deriving Repr, DecidableEq structure TicTacToeState where board : Board status : GameStatus deriving Repr def emptyBoard : Board := Vector.replicate 9 Cell.Empty def initialGameState : TicTacToeState := { board := emptyBoard, status := GameStatus.InProgress } def isEmptyPosition (board : Board) (pos : Position) : Bool := board.get pos == Cell.Empty def switchPlayer (player : Player) : Player := match player with | Player.X => Player.O | Player.O => Player.X theorem switchPlayerIdentity (player : Player) : switchPlayer (switchPlayer player) = player := by simp [switchPlayer] match player with | Player.X => trivial | Player.O => trivial theorem switchPlayerNeg (player : Player) : ¬switchPlayer player = player := by simp [switchPlayer] match player with | Player.X => trivial | Player.O => trivial def isBoardFull (board : Board) : Bool := Vector.count Cell.Empty board == 0 def countMarkedCells (board : Board) (player : Player) : Nat := board.countP (· == Cell.Occupied player) def checkWin (board : Board) (player : Player) : Bool := let playerCell := Cell.Occupied player let winningLines : List (List (Fin 9)) := [ -- Rows [0, 1, 2], [3, 4, 5], [6, 7, 8], -- Columns [0, 3, 6], [1, 4, 7], [2, 5, 8], -- Diagonals [0, 4, 8], [2, 4, 6] ] -- We use ≥ here to make our proofs easier winningLines.any (fun line => line.countP (fun pos => board.get pos == playerCell) >= 3) def turnNumber (state : TicTacToeState) : Nat := 9 - state.board.count Cell.Empty def currentPlayer (state : TicTacToeState) : Player := if turnNumber state % 2 == 0 then Player.X else Player.O def wellFormedGame (state : TicTacToeState) : Bool := match state.status with | GameStatus.InProgress => !isBoardFull state.board && !checkWin state.board Player.X && !checkWin state.board Player.O && (currentPlayer state == Player.O && countMarkedCells state.board Player.O + 1 == countMarkedCells state.board Player.X || currentPlayer state == Player.X && countMarkedCells state.board Player.O == countMarkedCells state.board Player.X) | GameStatus.Won victoriousPlayer => checkWin state.board victoriousPlayer && !checkWin state.board (switchPlayer victoriousPlayer) && (victoriousPlayer == Player.O && countMarkedCells state.board Player.O == countMarkedCells state.board Player.X || victoriousPlayer == Player.X && countMarkedCells state.board Player.O + 1 == countMarkedCells state.board Player.X) | GameStatus.Draw => isBoardFull state.board && !checkWin state.board Player.X && !checkWin state.board Player.O && countMarkedCells state.board Player.O == 4 && countMarkedCells state.board Player.X == 5 theorem initialGameStateIsWellFormed (state : TicTacToeState) {h: state = initialGameState} : wellFormedGame state := by unfold initialGameState at h simp [wellFormedGame] let hStatus := congrArg (fun s => s.status) h simp at hStatus rw [hStatus] simp let hBoard := congrArg (fun s => s.board) h simp at hBoard rw [hBoard] have hCurrentPlayer : currentPlayer state = Player.X := by simp [currentPlayer, turnNumber, hBoard, emptyBoard] simp [hCurrentPlayer] have hGetFromEmptyBoard (i : Position) : Vector.get emptyBoard i = Cell.Empty := by unfold emptyBoard Vector.replicate Vector.get Vector.toArray simp have hBoardNotFull : isBoardFull emptyBoard = false := by simp [isBoardFull, emptyBoard] have hCheckWinEmpty (p : Player) : checkWin emptyBoard p = false := by simp [checkWin, hGetFromEmptyBoard] have hCheckMarkedCells (p : Player) : countMarkedCells emptyBoard p = 0 := by simp [countMarkedCells, emptyBoard] simp [ hCheckWinEmpty Player.X, hCheckWinEmpty Player.O, hBoardNotFull, hCheckMarkedCells Player.X, hCheckMarkedCells Player.O ] def makeMove (state : TicTacToeState) (pos : Position) (_ : state.status = GameStatus.InProgress) (_ : isEmptyPosition state.board pos) (_ : wellFormedGame state) : TicTacToeState := let player := currentPlayer state let newBoard := state.board.set pos.val (Cell.Occupied player) let newStatus := if checkWin newBoard player then GameStatus.Won player else if isBoardFull newBoard then GameStatus.Draw else GameStatus.InProgress { board := newBoard, status := newStatus } theorem noWinWithoutOwnMove (board : Board) (player : Player) (pos : Position) (hIsEmpty : isEmptyPosition board pos) (hNoWin : checkWin board player = false) : checkWin (Vector.set board pos.val (Cell.Occupied (switchPlayer player))) player = false := by -- `newBoard` is the board after the other player has made a move let newBoard := Vector.set board pos.val (Cell.Occupied (switchPlayer player)) -- The cell at pos is now occupied by them have cellAtPosIsOccupiedByOtherPlayer : Vector.get newBoard pos = Cell.Occupied (switchPlayer player) := by simp [isEmptyPosition] at hIsEmpty match hMatch : Vector.get board pos with | Cell.Occupied a => simp [hMatch] at hIsEmpty | Cell.Empty => simp [newBoard, Vector.set, Vector.get] -- Other cells were not modified have getFromNewBoardRespectsPreviouslyOccupied {i} {hIsNotPos : ¬pos = i} : Vector.get newBoard i = Vector.get board i := by have hDistinct : pos.val ≠ i.val := by intro h apply hIsNotPos apply Fin.ext simp [h] let hGetFromBoardUnchanged := Vector.getElem_set_ne (xs := board) (x := Cell.Occupied (switchPlayer player)) pos.2 i.2 (h := hDistinct) simp [newBoard] simp only [Vector.get, Fin.cast, Vector.getElem_toArray] simp [hGetFromBoardUnchanged] simp [checkWin] at hNoWin simp [checkWin] have setAnywhereLeavesSelectionUntouched {i} : (Vector.get board i == Cell.Occupied player) = (Vector.get newBoard i == Cell.Occupied player) := by by_cases hPos : pos = i . simp [isEmptyPosition] at hIsEmpty simp [← hPos, cellAtPosIsOccupiedByOtherPlayer, hIsEmpty, cellNeqEmptyOccupied, switchPlayerNeg] . let hGetFromBoardUnchanged := getFromNewBoardRespectsPreviouslyOccupied (hIsNotPos := hPos) exact Eq.symm (congrArg (· == Cell.Occupied player) hGetFromBoardUnchanged) -- For each count, we know that it doesn't change after the Vector.set have noCountChange {x y z : Position} (hCountBeforeMove : List.countP (fun i => Vector.get board i == Cell.Occupied player) [x, y, z] < 3) : List.countP (fun i => Vector.get newBoard i == Cell.Occupied player) [x, y, z] < 3 := by -- Unfold simp [List.countP, List.countP.go, List.countP.go, List.countP.go] -- Rewrite simp [ (setAnywhereLeavesSelectionUntouched (i := x)).symm, (setAnywhereLeavesSelectionUntouched (i := y)).symm, (setAnywhereLeavesSelectionUntouched (i := z)).symm, ] -- Fold back change List.countP (fun i => Vector.get board i == Cell.Occupied player) [x, y, z] < 3 exact hCountBeforeMove simp [ newBoard, noCountChange hNoWin.left, noCountChange hNoWin.right.left, noCountChange hNoWin.right.right.left, noCountChange hNoWin.right.right.right.left, noCountChange hNoWin.right.right.right.right.left, noCountChange hNoWin.right.right.right.right.right.left, noCountChange hNoWin.right.right.right.right.right.right.left, noCountChange hNoWin.right.right.right.right.right.right.right, ] theorem moveIncrementsTurnNumber (state : TicTacToeState) (newState : TicTacToeState) (pos : Position) (hIsEmpty : isEmptyPosition state.board pos) (hNewBoard : newState.board = Vector.set state.board pos.val (Cell.Occupied (currentPlayer state))) : turnNumber newState = turnNumber state + 1 := by simp [turnNumber] have hAtLeastOneNonEmpty : Cell.Empty ∈ state.board := by simp [isEmptyPosition, Vector.get] at hIsEmpty let hIsMember := Vector.getElem_mem (xs := state.board) (i := pos.val) pos.2 simp [hIsEmpty] at hIsMember exact hIsMember have hCount := Vector.count_pos_iff (xs := state.board) (a := Cell.Empty) have hCountSet := Vector.count_set (xs := state.board) (b := Cell.Empty) (a := Cell.Occupied (currentPlayer state)) (i := pos.val) pos.2 simp [isEmptyPosition, Vector.get] at hIsEmpty simp [hIsEmpty] at hCountSet have hLemma : Vector.count Cell.Empty newState.board + 1 = Vector.count Cell.Empty state.board := by simp [hNewBoard] simp [hCountSet] exact Nat.sub_add_cancel (hCount.mpr hAtLeastOneNonEmpty) simp [← hLemma] have hNewStateCellEmptyLemma : Vector.count Cell.Empty newState.board ≤ 8 := by simp [hNewBoard, hCountSet, Vector.count_le_size] let hNatSub := Nat.sub_add_comm hNewStateCellEmptyLemma (n := 8) (m := 1) (k := Vector.count Cell.Empty newState.board) simp at hNatSub exact hNatSub theorem hBoardNotFullIfCellEmpty (state : TicTacToeState) (pos : Position) (hIsEmpty : isEmptyPosition state.board pos) : ¬isBoardFull state.board := by simp [isBoardFull] simp [isEmptyPosition, Vector.get] at hIsEmpty by_cases h : state.board.count Cell.Empty = 0 . let hNoEmpty := state.board.not_mem_of_count_eq_zero h let hPosEmpty := state.board.getElem_mem pos.2 simp [hIsEmpty] at hPosEmpty contradiction . trivial theorem cellEmptyCountOfIsEmpty (hIsEmpty : isEmptyPosition board pos) : board.count Cell.Empty ≥ 1 := by simp [isEmptyPosition, Vector.get] at hIsEmpty let hPosEmpty := board.getElem_mem pos.2 simp [hIsEmpty] at hPosEmpty simp [hPosEmpty] theorem moveDecrementsEmptyCellCount (state : TicTacToeState) (newState : TicTacToeState) (pos : Position) (hIsEmpty : isEmptyPosition state.board pos) (hNewBoard : newState.board = Vector.set state.board pos.val (Cell.Occupied (currentPlayer state))) : newState.board.count Cell.Empty + 1 = state.board.count Cell.Empty := by simp [hNewBoard] let hCountSet := Vector.count_set (xs := state.board) (a := Cell.Occupied (currentPlayer state)) (b := Cell.Empty) pos.2 simp [hCountSet] let hIsEmpty' := hIsEmpty simp [isEmptyPosition, Vector.get] at hIsEmpty' simp [hIsEmpty'] let hCount := cellEmptyCountOfIsEmpty hIsEmpty simp [Nat.sub_add_cancel hCount] theorem newBoardIsFullAfterTurn8 (state : TicTacToeState) (newState : TicTacToeState) (pos : Position) (_ : state.status = GameStatus.InProgress) (hIsEmpty : isEmptyPosition state.board pos) (_ : wellFormedGame state) (hNewBoard : newState.board = Vector.set state.board pos.val (Cell.Occupied (currentPlayer state))) : isBoardFull newState.board = (turnNumber state == 8) := by let hAtLeastOneEmpty := hBoardNotFullIfCellEmpty state pos hIsEmpty simp [isBoardFull] at hAtLeastOneEmpty let hAtLeastOneEmpty := Nat.pos_of_ne_zero hAtLeastOneEmpty simp [isBoardFull, turnNumber] by_cases h : Vector.count Cell.Empty newState.board == 0 . simp [h] let hMoveDecrementsEmptyCellCount := moveDecrementsEmptyCellCount state newState pos hIsEmpty hNewBoard let hMoveDecrementsEmptyCellCount := congrArg (· - 1) hMoveDecrementsEmptyCellCount simp at hMoveDecrementsEmptyCellCount simp [hMoveDecrementsEmptyCellCount] at h let h := congrArg (· + 1) h simp only at h simp [Nat.sub_add_cancel hAtLeastOneEmpty] at h simp [h] . simp [h] simp at h let h' := Nat.pos_of_ne_zero h let hMoveDecrementsEmptyCellCount := moveDecrementsEmptyCellCount state newState pos hIsEmpty hNewBoard let hMoveDecrementsEmptyCellCount := congrArg (· - 1) hMoveDecrementsEmptyCellCount simp at hMoveDecrementsEmptyCellCount simp [hMoveDecrementsEmptyCellCount] at h' let h' := Nat.add_lt_add_right h' 9 simp only [Nat.zero_add] at h' have hTrivial : state.board.count Cell.Empty ≤ 9 := Vector.count_le_size let h' := Nat.sub_lt_sub_right hTrivial h' have hSimplify : Vector.count Cell.Empty state.board - 1 + 9 - Vector.count Cell.Empty state.board = 8 := by -- Swap (· - 1 + 9) to (· + 9 - 1) so it simplifies let hCount := cellEmptyCountOfIsEmpty hIsEmpty simp [← Nat.sub_add_comm hCount (m := 9)] simp [hSimplify] at h' let hNotEquals := (Iff.mp Nat.lt_iff_le_and_ne h').right simp at hNotEquals exact hNotEquals theorem noWinnersWhileInProgress (state : TicTacToeState) (hInProgress : state.status = GameStatus.InProgress) (hWellFormed : wellFormedGame state) : checkWin state.board Player.X = false ∧ checkWin state.board Player.O = false := by simp [wellFormedGame, hInProgress] at hWellFormed simp [hWellFormed] theorem singleWinner (state : TicTacToeState) (hWon : state.status = GameStatus.Won winner) (hWellFormed : wellFormedGame state) : checkWin state.board (switchPlayer winner) = false := by simp [wellFormedGame, hWon] at hWellFormed simp [hWellFormed] theorem playerXCellCount (state : TicTacToeState) (hWellFormed : wellFormedGame state) : countMarkedCells state.board Player.X = (turnNumber state + 1 ) / 2 := by let hWellFormed' := hWellFormed simp [countMarkedCells, turnNumber] have playerCellCount : countMarkedCells state.board Player.X + countMarkedCells state.board Player.O = 9 - Vector.count Cell.Empty state.board := by simp [countMarkedCells, Vector.count_eq_countP] have hDisjoint : ∀ a, (a == Cell.Occupied Player.X -> ¬(a == Cell.Occupied Player.O)) ∧ (a == Cell.Occupied Player.O -> ¬(a == Cell.Occupied Player.X)) := by intro a match a with | Cell.Empty => trivial | Cell.Occupied Player.X => trivial | Cell.Occupied Player.O => trivial have hDisjoint' : ∀ a, (a == Cell.Occupied Player.X || a == Cell.Occupied Player.O) != (a == Cell.Empty) := by intro a match a with | Cell.Empty => trivial | Cell.Occupied Player.X => trivial | Cell.Occupied Player.O => trivial have hComplete : ∀ a, a == Cell.Occupied Player.X || a == Cell.Occupied Player.O || a == Cell.Empty := by intro a match a with | Cell.Empty => trivial | Cell.Occupied Player.X => trivial | Cell.Occupied Player.O => trivial simp [countDisjoint (xs := state.board) (pred1 := (fun x => x == Cell.Occupied Player.X)) (pred2 := (fun x => x == Cell.Occupied Player.O)) hDisjoint] let hCountComplete := countComplete (xs := state.board) (pred1 := fun x => x == Cell.Occupied Player.X || x == Cell.Occupied Player.O) (pred2 := fun x => x == Cell.Empty) hDisjoint' hComplete simp [Vector.size] at hCountComplete simp [← hCountComplete] simp [← playerCellCount] match hStatus : state.status with | GameStatus.Draw => simp [wellFormedGame, hStatus] at hWellFormed simp [hWellFormed] rw [← countMarkedCells] simp [hWellFormed] | GameStatus.InProgress => simp [wellFormedGame, hStatus] at hWellFormed match hCurrentPlayer : currentPlayer state with | Player.X => simp [hCurrentPlayer] at hWellFormed simp [hWellFormed, ← Nat.two_mul, Nat.add_div (a := 2 * countMarkedCells state.board Player.X) (b := 1) (c := 2) (by trivial)] simp [countMarkedCells] | Player.O => simp [hCurrentPlayer] at hWellFormed simp [ Nat.add_assoc, hWellFormed, ← Nat.two_mul, ] simp [countMarkedCells] | GameStatus.Won winner => simp [wellFormedGame, hStatus] at hWellFormed let hSingleWinner := singleWinner state hStatus hWellFormed' simp [hSingleWinner] at hWellFormed match winner with | Player.X => simp at hWellFormed simp [Nat.add_assoc, hWellFormed, ← Nat.two_mul] simp [countMarkedCells] | Player.O => simp at hWellFormed simp [ ← hWellFormed, ← Nat.two_mul, Nat.add_div (a := 2 * countMarkedCells state.board Player.O) (b := 1) (c := 2) (by trivial) ] simp [hWellFormed] simp [countMarkedCells] theorem makeMovePreservesWellFormedness {state : TicTacToeState} {pos : Position} {hInProgress : state.status = GameStatus.InProgress} {hIsEmpty : isEmptyPosition state.board pos} {hWellFormed : wellFormedGame state} : wellFormedGame (makeMove state pos hInProgress hIsEmpty hWellFormed) := by let player := currentPlayer state let hPlayer : player = currentPlayer state := by trivial let newState := (makeMove state pos hInProgress hIsEmpty hWellFormed) let newBoard := newState.board have hNewBoard : newBoard = Vector.set state.board pos.val (Cell.Occupied player) := by trivial have hNewBoard' : newBoard = newState.board := by trivial let hWellFormed' := hWellFormed simp [wellFormedGame, hInProgress] at hWellFormed have hNoWinnersYet : checkWin state.board player = false ∧ checkWin state.board (switchPlayer player) = false := by match player with | Player.X => simp [switchPlayer, hWellFormed] | Player.O => simp [switchPlayer, hWellFormed] let pMarkedO := fun x => x == Cell.Occupied Player.O let pMarkedX := fun x => x == Cell.Occupied Player.X let hIsEmpty' := hIsEmpty simp [isEmptyPosition, Vector.get] at hIsEmpty' let hNotMarkedO : ¬pMarkedO (state.board.get pos) := by simp [pMarkedO, Vector.get, hIsEmpty'] let hNotMarkedX : ¬pMarkedX (state.board.get pos) := by simp [pMarkedX, Vector.get, hIsEmpty'] let xMarksUntouched := vector_count_set_other (i := pos) (xs := state.board) (pred := pMarkedO) (value := Cell.Occupied Player.X) (hOld := (by trivial)) (hNew := (by trivial)) let oMarksUntouched := vector_count_set_other (i := pos) (xs := state.board) (pred := pMarkedX) (value := Cell.Occupied Player.O) (hOld := (by trivial)) (hNew := (by trivial)) let xMarksIncreasedByOne := vector_count_set (i := pos) (xs := state.board) (pred := pMarkedX) (value := Cell.Occupied Player.X) (hOld := (by trivial)) (hNew := (by trivial)) let oMarksIncreasedByOne := vector_count_set (i := pos) (xs := state.board) (pred := pMarkedO) (value := Cell.Occupied Player.O) (hOld := (by trivial)) (hNew := (by trivial)) simp [pMarkedO] at xMarksUntouched simp [pMarkedX] at xMarksIncreasedByOne simp [pMarkedX] at oMarksUntouched simp [pMarkedO] at oMarksIncreasedByOne rw [makeMove, wellFormedGame, ← hNewBoard] simp let hPreviousCellCount := hWellFormed.right -- State-specific properties match hMatch : (checkWin newBoard player, checkWin newBoard (switchPlayer player)) with | (true, false) => -- We have a winner! let hWonLeft := congrArg (fun x => x.1) hMatch simp at hWonLeft let hWonRight := congrArg (fun x => x.2) hMatch simp at hWonRight simp [← hPlayer, hWonLeft, hWonRight] -- The cell distribution is well formed simp [hNewBoard] match hCurrentPlayer : player with | Player.X => simp [← hPlayer, hCurrentPlayer, countMarkedCells] at hPreviousCellCount simp [ countMarkedCells, xMarksUntouched, congrArg (· + 1) hPreviousCellCount, xMarksIncreasedByOne ] | Player.O => simp [← hPlayer, hCurrentPlayer, countMarkedCells] at hPreviousCellCount simp [ countMarkedCells, oMarksUntouched, ← hPreviousCellCount, oMarksIncreasedByOne ] | (false, false) => let hWonLeft := congrArg (fun x => x.1) hMatch simp at hWonLeft let hWonRight := congrArg (fun x => x.2) hMatch simp at hWonRight simp [← hPlayer, hWonLeft] by_cases hDraw : isBoardFull newBoard . -- Draw simp [hDraw] -- We know player = Player.X, since they will always have the last turn let hDrawAfterEigthTurn := newBoardIsFullAfterTurn8 state newState pos hInProgress hIsEmpty hWellFormed' hNewBoard conv at hDrawAfterEigthTurn => pattern newState.board change newBoard simp [hDraw] at hDrawAfterEigthTurn simp [← hPlayer] at hPreviousCellCount simp [currentPlayer, hDrawAfterEigthTurn] at hPlayer simp [hPlayer, countMarkedCells] at hPreviousCellCount simp [hPlayer] at hWonLeft simp [switchPlayer, hPlayer] at hWonRight simp [hWonLeft, hWonRight] -- Well-formed cell distribution simp [ countMarkedCells, hNewBoard, hPlayer, xMarksUntouched, hPreviousCellCount, xMarksIncreasedByOne ] -- We know Player.X has 4 cells at the beginning of move 8 let hPlayerXHas4Cells := playerXCellCount state hWellFormed' simp [hDrawAfterEigthTurn] at hPlayerXHas4Cells rw [← countMarkedCells, hPlayerXHas4Cells] . -- The game goes on simp [hDraw] have hNoWinners : checkWin newBoard Player.X = false ∧ checkWin newBoard Player.O = false := by match hPlayerToCheck : player with | Player.X => simp [hPlayerToCheck] at hWonLeft simp [hPlayerToCheck, switchPlayer] at hWonRight simp [hWonLeft, hWonRight] | Player.O => simp [hPlayerToCheck] at hWonLeft simp [hPlayerToCheck, switchPlayer] at hWonRight simp [hWonLeft, hWonRight] have modLemma {x : Nat} : (x % 2 = 0) = ((x + 1) % 2 = 1) := by simp by_cases hCase : x % 2 = 0 . simp at hCase simp [hCase, Nat.add_mod] . simp at hCase simp [hCase] simp [← hCase, ← Nat.two_mul] have hNextPlayer : currentPlayer newState = switchPlayer (currentPlayer state) := by simp [currentPlayer, switchPlayer] let hTurnNumber := moveIncrementsTurnNumber state newState pos hIsEmpty hNewBoard let hModLemma := modLemma (x := turnNumber state) by_cases hIf : turnNumber newState % 2 = 0 . simp [hIf] simp [hModLemma, ← hTurnNumber, hIf] . simp at hIf simp [hIf] simp [hModLemma, ← hTurnNumber, hIf] simp [hNoWinners] match hCurrentPlayer : player with | Player.X => rw [hPlayer] at hCurrentPlayer conv => pattern currentPlayer { board := newBoard, status := GameStatus.InProgress } change currentPlayer newState conv => pattern currentPlayer { board := newBoard, status := GameStatus.InProgress } change currentPlayer newState simp [hNextPlayer, hCurrentPlayer, switchPlayer] simp [noWinnersWhileInProgress state hInProgress hWellFormed', hCurrentPlayer] at hWellFormed simp [hNewBoard, countMarkedCells, xMarksUntouched] rw [← countMarkedCells] simp [hWellFormed] simp [countMarkedCells, xMarksIncreasedByOne] | Player.O => rw [hPlayer] at hCurrentPlayer conv => pattern currentPlayer { board := newBoard, status := GameStatus.InProgress } change currentPlayer newState conv => pattern currentPlayer { board := newBoard, status := GameStatus.InProgress } change currentPlayer newState simp [hNextPlayer, hCurrentPlayer, switchPlayer] simp [noWinnersWhileInProgress state hInProgress hWellFormed', hCurrentPlayer] at hWellFormed simp [hNewBoard, countMarkedCells, oMarksUntouched] rw [← countMarkedCells, ← countMarkedCells] simp [← hWellFormed] simp [countMarkedCells, oMarksIncreasedByOne] | (_, true) => -- This branch is unreachable, so the known facts at this point must lead to a contradiction. let hWonLeft := congrArg (fun x => x.1) hMatch simp at hWonLeft let hWonRight := congrArg (fun x => x.2) hMatch simp at hWonRight -- If the other player already won, it must have won before the last move, which -- is impossible (it would mean the game state was not well formed at the last move) let hNoPreviousWinner := noWinWithoutOwnMove state.board (switchPlayer player) pos hIsEmpty hNoWinnersYet.right simp [switchPlayerIdentity, ← hNewBoard] at hNoPreviousWinner simp [hNoPreviousWinner] at hWonRight -- Proof: players alternate after each move theorem playersAlternateAfterMove {state : TicTacToeState} {pos : Position} {hInProgress : state.status = GameStatus.InProgress} {hIsEmpty : isEmptyPosition state.board pos} {hWellFormed : wellFormedGame state} {newState : TicTacToeState} (hNewState: newState = makeMove state pos hInProgress hIsEmpty hWellFormed) : currentPlayer state = switchPlayer (currentPlayer newState) := by let hNewBoard := congrArg (fun s => s.board) hNewState simp at hNewBoard let hMoveIncrementsTurnNumber := moveIncrementsTurnNumber state newState pos hIsEmpty hNewBoard simp [currentPlayer, hMoveIncrementsTurnNumber] by_cases hCase : turnNumber state % 2 = 0 . simp [hCase] have hNextTurnNumber : (turnNumber state + 1) % 2 = 1 := by simp [Nat.add_mod, hCase] simp [hNextTurnNumber, switchPlayer] . simp at hCase simp [hCase] have hNextTurnNumber : (turnNumber state + 1) % 2 = 0 := by simp [Nat.add_mod, hCase] simp [hNextTurnNumber, switchPlayer] -- Proof: cell gets marked after move with the player's mark theorem cellMarkedAfterMove {state : TicTacToeState} {pos : Position} {hInProgress : state.status = GameStatus.InProgress} {hIsEmpty : isEmptyPosition state.board pos} {hWellFormed : wellFormedGame state} {newState : TicTacToeState} (h: makeMove state pos hInProgress hIsEmpty hWellFormed = newState) : newState.board.get pos = Cell.Occupied (currentPlayer state) := by unfold makeMove at h let h' := congrArg (fun x : TicTacToeState => x.board) h simp at h' rw [Eq.symm h'] unfold Vector.set Vector.get simp