├── .gitignore ├── LICENSE ├── Maze.lean ├── README.md ├── lake-manifest.json ├── lakefile.lean └── lean-toolchain /.gitignore: -------------------------------------------------------------------------------- 1 | .lake/ 2 | lakefile.olean -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Maze.lean: -------------------------------------------------------------------------------- 1 | import Lean 2 | 3 | -- Coordinates in a two dimensional grid. ⟨0,0⟩ is the upper left. 4 | structure Coords where 5 | x : Nat -- column number 6 | y : Nat -- row number 7 | deriving BEq 8 | 9 | structure GameState where 10 | size : Coords -- coordinates of bottom-right cell 11 | position : Coords -- row and column of the player 12 | walls : List Coords -- maze cells that are not traversible 13 | 14 | -- We define custom syntax for GameState. 15 | 16 | declare_syntax_cat game_cell 17 | declare_syntax_cat game_cell_sequence 18 | declare_syntax_cat game_row 19 | declare_syntax_cat horizontal_border 20 | declare_syntax_cat game_top_row 21 | declare_syntax_cat game_bottom_row 22 | 23 | syntax "─" : horizontal_border 24 | 25 | syntax "\n┌" horizontal_border* "┐\n" : game_top_row 26 | 27 | syntax "└" horizontal_border* "┘\n" : game_bottom_row 28 | 29 | syntax "░" : game_cell -- empty 30 | syntax "▓" : game_cell -- wall 31 | syntax "@" : game_cell -- player 32 | 33 | syntax "│" game_cell* "│\n" : game_row 34 | 35 | syntax:max game_top_row game_row* game_bottom_row : term 36 | 37 | inductive CellContents where 38 | | empty : CellContents 39 | | wall : CellContents 40 | | player : CellContents 41 | 42 | def update_state_with_row_aux : Nat → Nat → List CellContents → GameState → GameState 43 | | _, _, [], oldState => oldState 44 | | currentRowNum, currentColNum, cell::contents, oldState => 45 | let oldState' := update_state_with_row_aux currentRowNum (currentColNum+1) contents oldState 46 | match cell with 47 | | CellContents.empty => oldState' 48 | | CellContents.wall => {oldState' .. with 49 | walls := ⟨currentColNum,currentRowNum⟩::oldState'.walls} 50 | | CellContents.player => {oldState' .. with 51 | position := ⟨currentColNum,currentRowNum⟩} 52 | 53 | def update_state_with_row : Nat → List CellContents → GameState → GameState 54 | | currentRowNum, rowContents, oldState => update_state_with_row_aux currentRowNum 0 rowContents oldState 55 | 56 | -- size, current row, remaining cells -> gamestate 57 | def game_state_from_cells_aux : Coords → Nat → List (List CellContents) → GameState 58 | | size, _, [] => ⟨size, ⟨0,0⟩, []⟩ 59 | | size, currentRow, row::rows => 60 | let prevState := game_state_from_cells_aux size (currentRow + 1) rows 61 | update_state_with_row currentRow row prevState 62 | 63 | -- size, remaining cells -> gamestate 64 | def game_state_from_cells : Coords → List (List CellContents) → GameState 65 | | size, cells => game_state_from_cells_aux size 0 cells 66 | 67 | def termOfCell : Lean.TSyntax `game_cell → Lean.MacroM (Lean.TSyntax `term) 68 | | `(game_cell| ░) => `(CellContents.empty) 69 | | `(game_cell| ▓) => `(CellContents.wall) 70 | | `(game_cell| @) => `(CellContents.player) 71 | | _ => Lean.Macro.throwError "unknown game cell" 72 | 73 | def termOfGameRow : Nat → Lean.TSyntax `game_row → Lean.MacroM (Lean.TSyntax `term) 74 | | expectedRowSize, `(game_row| │$cells:game_cell*│) => 75 | do if cells.size != expectedRowSize 76 | then Lean.Macro.throwError "row has wrong size" 77 | let cells' ← Array.mapM termOfCell cells 78 | `([$cells',*]) 79 | | _, _ => Lean.Macro.throwError "unknown game row" 80 | 81 | macro_rules 82 | | `(┌ $tb:horizontal_border* ┐ 83 | $rows:game_row* 84 | └ $bb:horizontal_border* ┘) => 85 | do let rsize := Lean.Syntax.mkNumLit (toString rows.size) 86 | let csize := Lean.Syntax.mkNumLit (toString tb.size) 87 | if tb.size != bb.size then Lean.Macro.throwError "top/bottom border mismatch" 88 | let rows' ← Array.mapM (termOfGameRow tb.size) rows 89 | `(game_state_from_cells ⟨$csize,$rsize⟩ [$rows',*]) 90 | 91 | --------------------------- 92 | -- Now we define a delaborator that will cause GameState to be rendered as a maze. 93 | 94 | def extractXY : Lean.Expr → Lean.MetaM Coords 95 | | e => do 96 | let e':Lean.Expr ← Lean.Meta.whnf e 97 | let sizeArgs := Lean.Expr.getAppArgs e' 98 | let x ← Lean.Meta.whnf sizeArgs[0]! 99 | let y ← Lean.Meta.whnf sizeArgs[1]! 100 | let numCols := (Lean.Expr.rawNatLit? x).get! 101 | let numRows := (Lean.Expr.rawNatLit? y).get! 102 | return Coords.mk numCols numRows 103 | 104 | partial def extractWallList : Lean.Expr → Lean.MetaM (List Coords) 105 | | exp => do 106 | let exp':Lean.Expr ← Lean.Meta.whnf exp 107 | let f := Lean.Expr.getAppFn exp' 108 | if f.constName!.toString == "List.cons" 109 | then let consArgs := Lean.Expr.getAppArgs exp' 110 | let rest ← extractWallList consArgs[2]! 111 | let ⟨wallCol, wallRow⟩ ← extractXY consArgs[1]! 112 | return (Coords.mk wallCol wallRow) :: rest 113 | else return [] -- "List.nil" 114 | 115 | partial def extractGameState : Lean.Expr → Lean.MetaM GameState 116 | | exp => do 117 | let exp': Lean.Expr ← Lean.Meta.whnf exp 118 | let gameStateArgs := Lean.Expr.getAppArgs exp' 119 | let size ← extractXY gameStateArgs[0]! 120 | let playerCoords ← extractXY gameStateArgs[1]! 121 | let walls ← extractWallList gameStateArgs[2]! 122 | pure ⟨size, playerCoords, walls⟩ 123 | 124 | def update2dArray {α : Type} : Array (Array α) → Coords → α → Array (Array α) 125 | | a, ⟨x,y⟩, v => 126 | Array.set! a y $ Array.set! a[y]! x v 127 | 128 | def update2dArrayMulti {α : Type} : Array (Array α) → List Coords → α → Array (Array α) 129 | | a, [], _ => a 130 | | a, c::cs, v => 131 | let a' := update2dArrayMulti a cs v 132 | update2dArray a' c v 133 | 134 | def delabGameRow : Array (Lean.TSyntax `game_cell) → Lean.PrettyPrinter.Delaborator.DelabM (Lean.TSyntax `game_row) 135 | | a => `(game_row| │ $a:game_cell* │) 136 | 137 | def delabGameState : Lean.Expr → Lean.PrettyPrinter.Delaborator.Delab 138 | | e => 139 | do guard $ e.getAppNumArgs == 3 140 | let ⟨⟨numCols, numRows⟩, playerCoords, walls⟩ ← 141 | try extractGameState e 142 | catch _ => failure -- can happen if game state has variables in it 143 | 144 | let topBar := Array.replicate numCols $ ← `(horizontal_border| ─) 145 | let emptyCell ← `(game_cell| ░) 146 | 147 | let a0 := Array.replicate numRows $ Array.replicate numCols emptyCell 148 | let a1 := update2dArray a0 playerCoords $ ← `(game_cell| @) 149 | let a2 := update2dArrayMulti a1 walls $ ← `(game_cell| ▓) 150 | let aa ← Array.mapM delabGameRow a2 151 | 152 | `(┌$topBar:horizontal_border*┐ 153 | $aa:game_row* 154 | └$topBar:horizontal_border*┘) 155 | 156 | -- The attribute [delab] registers this function as a delaborator for the GameState.mk constructor. 157 | @[delab app.GameState.mk] def delabGameStateMk : Lean.PrettyPrinter.Delaborator.Delab := do 158 | let e ← Lean.PrettyPrinter.Delaborator.SubExpr.getExpr 159 | delabGameState e 160 | 161 | -- We register the same elaborator for applications of the game_state_from_cells function. 162 | @[delab app.game_state_from_cells] def delabGameState' : Lean.PrettyPrinter.Delaborator.Delab := 163 | do let e ← Lean.PrettyPrinter.Delaborator.SubExpr.getExpr 164 | let e' ← Lean.Meta.whnf e 165 | delabGameState e' 166 | 167 | -------------------------- 168 | 169 | inductive Move where 170 | | east : Move 171 | | west : Move 172 | | north : Move 173 | | south : Move 174 | 175 | @[simp] 176 | def make_move : GameState → Move → GameState 177 | | ⟨s, ⟨x,y⟩, w⟩, Move.east => 178 | if ! w.elem ⟨x+1, y⟩ ∧ x + 1 ≤ s.x 179 | then ⟨s, ⟨x+1, y⟩, w⟩ 180 | else ⟨s, ⟨x,y⟩, w⟩ 181 | | ⟨s, ⟨x,y⟩, w⟩, Move.west => 182 | if ! w.elem ⟨x-1, y⟩ 183 | then ⟨s, ⟨x-1, y⟩, w⟩ 184 | else ⟨s, ⟨x,y⟩, w⟩ 185 | | ⟨s, ⟨x,y⟩, w⟩, Move.north => 186 | if ! w.elem ⟨x, y-1⟩ 187 | then ⟨s, ⟨x, y-1⟩, w⟩ 188 | else ⟨s, ⟨x,y⟩, w⟩ 189 | | ⟨s, ⟨x,y⟩, w⟩, Move.south => 190 | if ! w.elem ⟨x, y + 1⟩ ∧ y + 1 ≤ s.y 191 | then ⟨s, ⟨x, y+1⟩, w⟩ 192 | else ⟨s, ⟨x,y⟩, w⟩ 193 | 194 | def IsWin : GameState → Prop 195 | | ⟨⟨sx, sy⟩, ⟨x,y⟩, _⟩ => x = 0 ∨ y = 0 ∨ x + 1 = sx ∨ y + 1 = sy 196 | 197 | inductive Escapable : GameState → Prop where 198 | | Done (s : GameState) : IsWin s → Escapable s 199 | | Step (s : GameState) (m : Move) : Escapable (make_move s m) → Escapable s 200 | 201 | theorem step_west 202 | {s: Coords} 203 | {x y : Nat} 204 | {w: List Coords} 205 | (hclear' : ! w.elem ⟨x,y⟩) 206 | (W : Escapable ⟨s,⟨x,y⟩,w⟩) : 207 | Escapable ⟨s,⟨x+1,y⟩,w⟩ := 208 | by have hmm : GameState.mk s ⟨x,y⟩ w = make_move ⟨s,⟨x+1, y⟩,w⟩ Move.west := 209 | by have h' : x + 1 - 1 = x := rfl 210 | simp [h', hclear'] 211 | rw [hmm] at W 212 | exact .Step ⟨s,⟨x+1,y⟩,w⟩ Move.west W 213 | 214 | theorem step_east 215 | {s: Coords} 216 | {x y : Nat} 217 | {w: List Coords} 218 | (hclear' : ! w.elem ⟨x+1,y⟩) 219 | (hinbounds : x + 1 ≤ s.x) 220 | (E : Escapable ⟨s,⟨x+1,y⟩,w⟩) : 221 | Escapable ⟨s,⟨x, y⟩,w⟩ := 222 | by have hmm : GameState.mk s ⟨x+1,y⟩ w = make_move ⟨s, ⟨x,y⟩,w⟩ Move.east := 223 | by simp [hclear', hinbounds] 224 | rw [hmm] at E 225 | exact .Step ⟨s, ⟨x,y⟩, w⟩ Move.east E 226 | 227 | theorem step_north 228 | {s: Coords} 229 | {x y : Nat} 230 | {w: List Coords} 231 | (hclear' : ! w.elem ⟨x,y⟩) 232 | (N : Escapable ⟨s,⟨x,y⟩,w⟩) : 233 | Escapable ⟨s,⟨x, y+1⟩,w⟩ := 234 | by have hmm : GameState.mk s ⟨x,y⟩ w = make_move ⟨s,⟨x, y+1⟩,w⟩ Move.north := 235 | by have h' : y + 1 - 1 = y := rfl 236 | simp [h', hclear'] 237 | rw [hmm] at N 238 | exact .Step ⟨s,⟨x,y+1⟩,w⟩ Move.north N 239 | 240 | theorem step_south 241 | {s: Coords} 242 | {x y : Nat} 243 | {w: List Coords} 244 | (hclear' : ! w.elem ⟨x,y+1⟩) 245 | (hinbounds : y + 1 ≤ s.y) 246 | (S : Escapable ⟨s,⟨x,y+1⟩,w⟩) : 247 | Escapable ⟨s,⟨x, y⟩,w⟩ := 248 | by have hmm : GameState.mk s ⟨x,y+1⟩ w = make_move ⟨s,⟨x, y⟩,w⟩ Move.south := 249 | by simp [hclear', hinbounds] 250 | rw [hmm] at S 251 | exact .Step ⟨s,⟨x,y⟩,w⟩ Move.south S 252 | 253 | def escape_west {sx sy : Nat} {y : Nat} {w : List Coords} : Escapable ⟨⟨sx, sy⟩,⟨0, y⟩,w⟩ := 254 | .Done _ (Or.inl rfl) 255 | 256 | def escape_east {sy x y : Nat} {w : List Coords} : Escapable ⟨⟨x+1, sy⟩,⟨x, y⟩,w⟩ := 257 | .Done _ (Or.inr <| Or.inr <| Or.inl rfl) 258 | 259 | def escape_north {sx sy : Nat} {x : Nat} {w : List Coords} : Escapable ⟨⟨sx, sy⟩,⟨x, 0⟩,w⟩ := 260 | .Done _ (Or.inr <| Or.inl rfl) 261 | 262 | def escape_south {sx x y : Nat} {w: List Coords} : Escapable ⟨⟨sx, y+1⟩,⟨x, y⟩,w⟩ := 263 | .Done _ (Or.inr <| Or.inr <| Or.inr rfl) 264 | 265 | elab "fail" m:term : tactic => throwError m 266 | 267 | -- `first | t | u` is the Lean 4 equivalent of `t <|> u` in Lean 3. 268 | 269 | -- the `decides`s are to discharge the `hclear` and `hinbounds` side-goals 270 | macro "west" : tactic => 271 | `(tactic| first | apply step_west; (· decide; done) | fail "cannot step west") 272 | macro "east" : tactic => 273 | `(tactic| first | apply step_east; (· decide; done); (· decide; done) | fail "cannot step east") 274 | macro "north" : tactic => 275 | `(tactic| first | apply step_north; (· decide; done) | fail "cannot step north") 276 | macro "south" : tactic => 277 | `(tactic| first | apply step_south; (· decide; done); (· decide; done) | fail "cannot step south") 278 | 279 | macro "out" : tactic => `(tactic| first | apply escape_north | apply escape_south | 280 | apply escape_east | apply escape_west | 281 | fail "not currently at maze boundary") 282 | 283 | -- Can escape the trivial maze in any direction. 284 | example : Escapable ┌─┐ 285 | │@│ 286 | └─┘ := by out 287 | 288 | 289 | -- some other mazes with immediate escapes 290 | example : Escapable ┌──┐ 291 | │░░│ 292 | │@░│ 293 | │░░│ 294 | └──┘ := by out 295 | example : Escapable ┌──┐ 296 | │░░│ 297 | │░@│ 298 | │░░│ 299 | └──┘ := by out 300 | example : Escapable ┌───┐ 301 | │░@░│ 302 | │░░░│ 303 | │░░░│ 304 | └───┘ := by out 305 | example : Escapable ┌───┐ 306 | │░░░│ 307 | │░░░│ 308 | │░@░│ 309 | └───┘ := by out 310 | 311 | 312 | -- Now for some more interesting mazes. 313 | 314 | def maze1 := ┌──────┐ 315 | │▓▓▓▓▓▓│ 316 | │▓░░@░▓│ 317 | │▓░░░░▓│ 318 | │▓░░░░▓│ 319 | │▓▓▓▓░▓│ 320 | └──────┘ 321 | 322 | example : Escapable maze1 := by 323 | west 324 | west 325 | east 326 | south 327 | south 328 | east 329 | east 330 | south 331 | out 332 | 333 | def maze2 := ┌────────┐ 334 | │▓▓▓▓▓▓▓▓│ 335 | │▓░▓@▓░▓▓│ 336 | │▓░▓░░░▓▓│ 337 | │▓░░▓░▓▓▓│ 338 | │▓▓░▓░▓░░│ 339 | │▓░░░░▓░▓│ 340 | │▓░▓▓▓▓░▓│ 341 | │▓░░░░░░▓│ 342 | │▓▓▓▓▓▓▓▓│ 343 | └────────┘ 344 | 345 | example : Escapable maze2 := 346 | by south 347 | east 348 | south 349 | south 350 | south 351 | west 352 | west 353 | west 354 | south 355 | south 356 | east 357 | east 358 | east 359 | east 360 | east 361 | north 362 | north 363 | north 364 | east 365 | out 366 | 367 | def maze3 := ┌────────────────────────────┐ 368 | │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ 369 | │▓░░░░░░░░░░░░░░░░░░░░▓░░░@░▓│ 370 | │▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓░▓▓▓▓▓│ 371 | │▓░▓░░░▓░░░░▓░░░░░░░░░▓░▓░░░▓│ 372 | │▓░▓░▓░▓░▓▓▓▓░▓▓▓▓▓▓▓▓▓░▓░▓░▓│ 373 | │▓░▓░▓░▓░▓░░░░▓░░░░░░░░░░░▓░▓│ 374 | │▓░▓░▓░▓░▓░▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▓░▓│ 375 | │▓░▓░▓░▓░░░▓░░░░░░░░░░▓░░░▓░▓│ 376 | │▓░▓░▓░▓▓▓░▓░▓▓▓▓▓▓▓▓▓▓░▓░▓░▓│ 377 | │▓░▓░▓░░░░░▓░░░░░░░░░░░░▓░▓░▓│ 378 | │▓░▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓│ 379 | │░░▓░░░░░░░░░░░░░░░░░░░░░░░░▓│ 380 | │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│ 381 | └────────────────────────────┘ 382 | 383 | example : Escapable maze3 := 384 | by west 385 | west 386 | west 387 | south 388 | sorry -- can you finish the proof? 389 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lean4-maze 2 | 3 | This repo shows how maze solving 4 | can be encoded as theorem proving 5 | using the Lean 4 programming language. 6 | 7 | It draws inspiration from https://github.com/kbuzzard/maze-game. 8 | 9 | 10 | ## Setup 11 | 12 | ### On The Web 13 | 14 | [Try it in your browser on the Lean 4 Playground.](https://live.lean-lang.org/#url=https%3A%2F%2Fraw.githubusercontent.com%2Fdwrensha%2Flean4-maze%2Fmain%2FMaze.lean) 15 | 16 | ### Locally 17 | 18 | First, install Lean 4 on your computer: https://leanprover.github.io/lean4/doc/setup.html 19 | 20 | Then open `Maze.lean` in emacs or VSCode. 21 | 22 | ## Playing 23 | 24 | You can define a maze like this: 25 | 26 | ```lean 27 | def maze := ┌────────┐ 28 | │▓▓▓▓▓▓▓▓│ 29 | │▓░▓@▓░▓▓│ 30 | │▓░▓░░░▓▓│ 31 | │▓░░▓░▓▓▓│ 32 | │▓▓░▓░▓░░│ 33 | │▓░░░░▓░▓│ 34 | │▓░▓▓▓▓░▓│ 35 | │▓░░░░░░▓│ 36 | │▓▓▓▓▓▓▓▓│ 37 | └────────┘ 38 | ``` 39 | 40 | The `@` symbol denotes your current location. 41 | You are free to move within the `░` cells. 42 | The `▓` cells are walls. 43 | 44 | Your goal is to escape the maze at any of its borders. 45 | 46 | You can interactively solve a maze like this: 47 | 48 | 49 | ```lean 50 | example : Escapable maze := 51 | by south 52 | east 53 | south 54 | south 55 | ``` 56 | 57 | As you make progress, Lean's goal view will display your current state. 58 | For example, after the moves made above, the state is shown as: 59 | 60 | ```lean 61 | ⊢ Escapable 62 | ( 63 | ┌────────┐ 64 | │▓▓▓▓▓▓▓▓│ 65 | │▓░▓░▓░▓▓│ 66 | │▓░▓░░░▓▓│ 67 | │▓░░▓░▓▓▓│ 68 | │▓▓░▓@▓░░│ 69 | │▓░░░░▓░▓│ 70 | │▓░▓▓▓▓░▓│ 71 | │▓░░░░░░▓│ 72 | │▓▓▓▓▓▓▓▓│ 73 | └────────┘ 74 | ) 75 | ``` 76 | 77 | The main moves available to you at any point are `north`, `south`, `east`, and `west`. 78 | 79 | When you reach the boundary, you can finish your proof with `out`. 80 | 81 | ## how does it work? 82 | 83 | As you traverse a maze, you are constructing a proof 84 | that the maze satisfies an `Escapable` predicate, defined as 85 | 86 | ```lean 87 | inductive Escapable : GameState → Prop where 88 | | Done (s : GameState) : IsWin s → Escapable s 89 | | Step (s : GameState) (m : Move) : Escapable (make_move s m) → Escapable s 90 | ``` 91 | 92 | The mazes as drawn above are actual valid Lean 4 syntax! 93 | 94 | We define new syntax categories and some `macro_rules` for elaborating 95 | them into valid values. 96 | 97 | To get Lean to render the values back in the above format, 98 | we define a delaboration function and register it with the pretty printer. 99 | 100 | Lean 4 lets us do all of this in-line, in ordinary Lean 4 code. 101 | 102 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": "1.1.0", 2 | "packagesDir": ".lake/packages", 3 | "packages": [], 4 | "name": "maze", 5 | "lakeDir": ".lake"} 6 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | 3 | open Lake DSL 4 | 5 | package maze 6 | 7 | @[default_target] 8 | lean_lib Maze 9 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.19.0-rc2 2 | --------------------------------------------------------------------------------