├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .docker └── gitpod │ └── Dockerfile ├── .github └── workflows │ ├── build.yml │ └── build_non_main.yml ├── .gitignore ├── .gitpod.yml ├── .i18n ├── Game.pot ├── config.json └── en │ └── Game.pot ├── .vscode ├── settings.json └── tasks.json ├── Game.lean ├── Game ├── Doc │ ├── Definitions.lean │ ├── Lemmas.lean │ └── Tactics.lean ├── Game4gpt.zip ├── Levels │ ├── AndIntro.lean │ ├── AndIntro │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ └── L08.lean │ ├── AndTactic.lean │ ├── AndTactic │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ └── L08.lean │ ├── ExSyl.lean │ ├── IffIntro.lean │ ├── IffIntro │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ └── L07.lean │ ├── IffTactic.lean │ ├── IffTactic │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ └── L07.lean │ ├── ImpIntro.lean │ ├── ImpIntro │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ ├── L08.lean │ │ └── L09.lean │ ├── ImpTactic.lean │ ├── ImpTactic │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ ├── L08.lean │ │ └── L09.lean │ ├── L01-template.lean │ ├── NotIntro.lean │ ├── NotIntro │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ ├── L08.lean │ │ ├── L09.lean │ │ ├── L10.lean │ │ ├── L11.lean │ │ └── L12.lean │ ├── NotTactic.lean │ ├── NotTactic │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ ├── L08.lean │ │ ├── L09.lean │ │ ├── L10.lean │ │ ├── L11.lean │ │ └── L12.lean │ ├── OrIntro.lean │ ├── OrIntro │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ └── L08.lean │ ├── OrTactic.lean │ └── OrTactic │ │ ├── L01.lean │ │ ├── L02.lean │ │ ├── L03.lean │ │ ├── L04.lean │ │ ├── L05.lean │ │ ├── L06.lean │ │ ├── L07.lean │ │ └── L08.lean └── Metadata.lean ├── LICENSE ├── LOCAL-SETUP.md ├── README.md ├── images ├── logic0101.png ├── logic0101_v2.png ├── logic0101_v3.png └── logic0101_v4.png ├── lake-manifest.json ├── lakefile.lean └── lean-toolchain /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | WORKDIR / 4 | 5 | COPY ./lean-toolchain /lean-toolchain 6 | 7 | USER node 8 | 9 | WORKDIR /home/node 10 | 11 | RUN export LEAN_VERSION="$(cat /lean-toolchain | grep -oE '[^:]+$')" && git clone --depth 1 --branch $LEAN_VERSION https://github.com/leanprover-community/lean4game.git 12 | 13 | WORKDIR / 14 | 15 | USER root 16 | 17 | ENV ELAN_HOME=/usr/local/elan \ 18 | PATH=/usr/local/elan/bin:$PATH 19 | 20 | SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] 21 | 22 | RUN export LEAN_VERSION="$(cat /lean-toolchain)" && \ 23 | curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --no-modify-path --default-toolchain $LEAN_VERSION; \ 24 | chmod -R a+w $ELAN_HOME; \ 25 | elan --version; \ 26 | lean --version; \ 27 | leanc --version; \ 28 | lake --version; 29 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerComposeFile": "./docker-compose.yml", 3 | "service": "game", 4 | "workspaceFolder": "/home/node/game", 5 | "forwardPorts": [3000], 6 | // These settings make sure that the created files (lake-packages etc.) are owned 7 | // by the user and not `root`. 8 | // see also https://containers.dev/implementors/json_reference/ 9 | // and https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user 10 | "remoteUser": "node", 11 | "updateRemoteUserUID": true, 12 | // I don't know why I need this, but I did... 13 | "overrideCommand": true, 14 | "onCreateCommand": { 15 | "npm_install": "(cd ~/lean4game && npm install)", 16 | // BUG: Apparently `&& lake exe cache get` was needed here because the update hook was broken. 17 | // should been fixed in https://github.com/leanprover-community/mathlib4/pull/8755 18 | // "lake_build": "(cd ~/game && lake update -R && lake exe cache get && lake build)" 19 | }, 20 | "postStartCommand": "cd ~/lean4game && export VITE_LEAN4GAME_SINGLE=true && npm start", 21 | "customizations": { 22 | "vscode": { 23 | "settings": { 24 | "remote.autoForwardPorts": false 25 | }, 26 | "extensions": [ 27 | "leanprover.lean4" 28 | ] 29 | }, 30 | "codespaces": { 31 | "openFiles": ["Game.lean"] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | game: 5 | build: 6 | context: .. 7 | dockerfile: .devcontainer/Dockerfile 8 | volumes: 9 | - ..:/home/node/game 10 | ports: 11 | - "3000:3000" 12 | -------------------------------------------------------------------------------- /.docker/gitpod/Dockerfile: -------------------------------------------------------------------------------- 1 | # This is the Dockerfile for `leanprovercommunity/mathlib:gitpod`. 2 | 3 | # gitpod doesn't support multiple FROM statements, (or rather, you can't copy from one to another) 4 | # so we just install everything in one go 5 | FROM ubuntu:22.04 6 | 7 | USER root 8 | 9 | RUN apt-get update && apt-get --assume-yes install sudo git curl git bash-completion python3 -y && apt-get clean 10 | 11 | RUN useradd -l -u 33333 -G sudo -md /home/gitpod -s /bin/bash -p gitpod gitpod \ 12 | # passwordless sudo for users in the 'sudo' group 13 | && sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers 14 | USER gitpod 15 | WORKDIR /home/gitpod 16 | 17 | SHELL ["/bin/bash", "-c"] 18 | 19 | # gitpod bash prompt 20 | RUN { echo && echo "PS1='\[\033[01;32m\]\u\[\033[00m\] \[\033[01;34m\]\w\[\033[00m\]\$(__git_ps1 \" (%s)\") $ '" ; } >> .bashrc 21 | 22 | # install elan 23 | RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none 24 | 25 | # install whichever toolchain mathlib is currently using 26 | RUN . ~/.profile && elan toolchain install $(curl https://raw.githubusercontent.com/leanprover-community/mathlib4/master/lean-toolchain) 27 | 28 | ENV PATH="/home/gitpod/.local/bin:/home/gitpod/.elan/bin:${PATH}" 29 | 30 | # fix the infoview when the container is used on gitpod: 31 | ENV VSCODE_API_VERSION="1.50.0" 32 | 33 | # ssh to github once to bypass the unknown fingerprint warning 34 | RUN ssh -o StrictHostKeyChecking=no github.com || true 35 | 36 | # run sudo once to suppress usage info 37 | RUN sudo echo finished 38 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | run-name: Build the game and save artifact 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "main" ] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: install elan 13 | run: | 14 | set -o pipefail 15 | curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz 16 | ./elan-init -y --default-toolchain none 17 | echo "$HOME/.elan/bin" >> $GITHUB_PATH 18 | 19 | - uses: actions/checkout@v4 20 | 21 | - name: print lean and lake versions 22 | run: | 23 | lean --version 24 | lake --version 25 | 26 | # Note: this would also happen in the lake post-update-hook 27 | - name: get mathlib cache 28 | continue-on-error: true 29 | run: | 30 | lake exe cache clean 31 | # We've been seeing many failures at this step recently because of network errors. 32 | # As a band-aid, we try twice. 33 | # The 'sleep 1' is small pause to let the network recover. 34 | lake exe cache get || (sleep 1; lake exe cache get) 35 | 36 | - name: create timestamp file 37 | run: touch tmp_timestamp 38 | 39 | # Note: this would also happen in the lake post-update-hook 40 | - name: build gameserver executable 41 | run: env LEAN_ABORT_ON_PANIC=1 lake build gameserver 42 | 43 | - name: building game 44 | run: env LEAN_ABORT_ON_PANIC=1 lake build 45 | 46 | - name: delete unused mathlib cache 47 | continue-on-error: true 48 | run: find . -type d \( -name "*/.git" \) -delete -print && find ./.lake/ -type f \( -name "*.c" -o -name "*.hash" -o -name "*.trace" \) -delete -print && find ./.lake/ -type f \( -name "*.olean" \) \! -neweraa ./tmp_timestamp -delete -print 49 | 50 | - name: delete timestamp file 51 | run: rm ./tmp_timestamp 52 | 53 | - name: compress built game 54 | #run: tar -czvf ../game.tar.gz . 55 | run: zip game.zip * .lake/ -r 56 | 57 | - name: upload compressed game folder 58 | uses: actions/upload-artifact@v3 59 | with: 60 | name: build-for-server-import 61 | path: | 62 | game.zip 63 | 64 | - name: What next? 65 | run: echo "To export the game to the Game Server, open https://adam.math.hhu.de/import/trigger/${GITHUB_REPOSITORY,,} \n Afterwards, you can play the game at https://adam.math.hhu.de/#/g/${GITHUB_REPOSITORY,,}" 66 | -------------------------------------------------------------------------------- /.github/workflows/build_non_main.yml: -------------------------------------------------------------------------------- 1 | name: Build non-main branch 2 | run-name: Build non-main branch 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches-ignore: 7 | - 'main' 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: install elan 14 | run: | 15 | set -o pipefail 16 | curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz 17 | ./elan-init -y --default-toolchain none 18 | echo "$HOME/.elan/bin" >> $GITHUB_PATH 19 | 20 | - uses: actions/checkout@v4 21 | 22 | - name: print lean and lake versions 23 | run: | 24 | lean --version 25 | lake --version 26 | 27 | - name: get mathlib cache 28 | continue-on-error: true 29 | run: | 30 | lake exe cache clean 31 | # We've been seeing many failures at this step recently because of network errors. 32 | # As a band-aid, we try twice. 33 | # The 'sleep 1' is small pause to let the network recover. 34 | lake exe cache get || (sleep 1; lake exe cache get) 35 | 36 | - name: building game 37 | run: env LEAN_ABORT_ON_PANIC=1 lake build 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | lake-packages/ 3 | .lake/ 4 | **/.DS_Store 5 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .docker/gitpod/Dockerfile 3 | 4 | vscode: 5 | extensions: 6 | - leanprover.lean4 7 | 8 | tasks: 9 | - init: | 10 | lake exe cache get 11 | - command: | 12 | sudo apt-get --assume-yes install gcc 13 | -------------------------------------------------------------------------------- /.i18n/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLang": "en", 3 | "translationContactEmail": "" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": true, 3 | "editor.tabSize": 2, 4 | "editor.rulers" : [100], 5 | "files.encoding": "utf8", 6 | "files.eol": "\n", 7 | "files.insertFinalNewline": true, 8 | "files.trimFinalNewlines": true, 9 | "files.trimTrailingWhitespace": true 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "lake: build", 6 | "detail": "build lean files", 7 | "type": "shell", 8 | "command": "lake build", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | }, 14 | { 15 | "label": "game: open browser", 16 | "command": "${input:openSimpleBrowser}", 17 | "problemMatcher": [], 18 | // "runOptions": { 19 | // "runOn": "folderOpen" 20 | // } 21 | }, 22 | ], 23 | "inputs": [ 24 | { 25 | "id": "openSimpleBrowser", 26 | "type": "command", 27 | "command": "simpleBrowser.show", 28 | "args": ["http://localhost:3000"] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /Game.lean: -------------------------------------------------------------------------------- 1 | -- Here's the import to make Lean know about things called `Game`s 2 | import GameServer.Commands 3 | 4 | -- Here are the imports defining many worlds for the game `Game` (the natural number game, 5 | -- in this case). Each world consists of a finite number of levels, and levels 6 | -- are numbered 1,2,3,4... inside the level files. 7 | import Game.Levels.AndIntro 8 | import Game.Levels.AndTactic 9 | import Game.Levels.ImpIntro 10 | import Game.Levels.ImpTactic 11 | import Game.Levels.NotIntro 12 | import Game.Levels.NotTactic 13 | import Game.Levels.OrIntro 14 | import Game.Levels.OrTactic 15 | import Game.Levels.IffIntro 16 | import Game.Levels.IffTactic 17 | 18 | -- Here's where we show how to glue the worlds together 19 | -- Intro World Dependencies 20 | Dependency AndIntro → ImpIntro 21 | Dependency ImpIntro → NotIntro 22 | Dependency ImpIntro → OrIntro 23 | Dependency ImpIntro → IffIntro 24 | -- Intro—Tactic Dependencies 25 | Dependency AndIntro → AndTactic 26 | Dependency ImpIntro → ImpTactic 27 | Dependency OrIntro → OrTactic 28 | Dependency NotIntro → NotTactic 29 | Dependency IffIntro → IffTactic 30 | -- Tactic World Dependencies 31 | Dependency AndTactic → ImpTactic 32 | Dependency ImpTactic → OrTactic 33 | Dependency ImpTactic → NotTactic 34 | Dependency ImpTactic → IffTactic 35 | 36 | -- Here's what we'll put on the title screen 37 | Title "A Lean Intro to Logic" 38 | Introduction 39 | " 40 | # An Introduction to Constructive Logic 41 | This is a game about evidence [¹] and propositions. 42 | 43 | To play this puzzler you'll need to learn some notation. Unlike learning how to do a crossword or solve a Sudoku, the notation is a bit more involved. As the levels progress, you will — in effect — be learning a small part of a programming language. Just enough to prove (or construct evidence for) propositions and predicates. 44 | 45 | Below, you're provided with a whirlwind tour of the notation at play as well as a bit of motivation for why the notation looks the way it does. This is all done through a single example. Many of the details will seem lacking; The concepts covered here will be addressed with more detail during the tutorial worlds of this game. 46 | 47 | # Redux 48 | 49 | Each tutorial world is accompanied by a **Redux World** with the same subtitle. These worlds are optional, don't have any flavor text, and challenge you to solve the exact same set of levels in a different way. These worlds probably do not belong in a puzzle game, but I found the exercise helpful in garnering a computer-sciencey intuition about the interplay between proof terms and tactics in Lean 4. 50 | 51 | # Building some notation 52 | Consider the following argument stated in natural language: 53 | 54 | “Either cat fur or dog fur was found at the scene of the crime. If dog fur was found at the scene of the crime, Officer Thompson had an allergy attack. If cat fur was found at the scene of the crime, then Macavity is responsible for the crime. But Officer Thompson didn’t have an allergy attack, and so therefore Macavity must be responsible for the crime.” [[ˢʳᶜ](https://iep.utm.edu/propositional-logic-sentential-logic/#H5)] 55 | 56 | Does this argument convince you? The validity of this argument can be made more obvious by representing the chain of reasoning leading from the premises to the conclusion: 57 | 1. Either cat fur was found at the scene of the crime, or dog fur was found at the scene of the crime. (Premise) 58 | 2. If dog fur was found at the scene of the crime, then Officer Thompson had an allergy attack. (Premise) 59 | 3. If cat fur was found at the scene of the crime, then Macavity is responsible for the crime. (Premise) 60 | 4. Officer Thompson did not have an allergy attack. (Premise) 61 | 5. Dog fur was not found at the scene of the crime. (Follows from 2 and 4) 62 | 6. Cat fur was found at the scene of the crime. (Follows from 1 and 5) 63 | 7. Therefore Macavity is responsible for the crime. (Follows from 3 and 6) 64 | 65 | If you take a moment to re-read them again, lines 5, 6, & 7 are all slightly different styles of logical deductions. 66 | 67 | - Line 5 is deducing the negation of the left-hand side of an \"if ... then ...\" statement. Just for references' sake, we'll give this style of reasoning a name: [**modus tollens**](https://en.wikipedia.org/wiki/Modus_tollens) 68 | - Line 6 is using the process of elimination on two options. This is the style of reasoning responsible for Sherlock Holmes' most famous quote — \"When you have eliminated the impossible, whatever remains, however improbable, must be the truth\". We'll give this a name too: [**modus tollendo ponens**](https://en.wikipedia.org/wiki/Modus_ponendo_tollens) 69 | - Line 7 is the conclusion and is applying the \"if ... then ...\" statement on line 3. We'll call this one [**modus ponens**](https://en.wikipedia.org/wiki/Modus_ponens). 70 | 71 | We won't always be denoting these with Latin names, but the general process of being able to give some generically useful deductive reasoning a name is nice. It makes them easier to reference. During the course of this game some of your proofs will be given names and correspondingly unlocked in your inventory. Thus names are a way to avoid proving the same thing over and over again. 72 | 73 | # Propositions 74 | If we separate out the 4 true/false statements required for our line of reasoning and introduce some connectives, we can see the exact same argument in a more concise form. The numbers 1 - 7 here are meant to match exactly with the natural language above. 75 | 76 | We're going to give our English connectives some symbols: 77 | - \"and\" — \"∧\" 78 | - \"implies\" — \"→\" 79 | - \"not\" — \"¬\" 80 | - \"or\" — \"∨\" 81 | 82 | and we'll give our propositions some symbols: 83 | - C — Cat fur was found at the scene 84 | - D — Dog fur was found at the scene 85 | - M — Macavity is responsible for the crime 86 | - T — Officer Thompson had an allergy attack. 87 | 88 | These symbols let us write out the argument from above as follows: 89 | 1. C ∨ D (Premise) 90 | 2. D → T (Premise) 91 | 3. C → M (Premise) 92 | 4. ¬T (Premise) 93 | 5. ¬D (Modus tollens on 2 and 4) 94 | 6. C (Modus tollendo ponens on 1 and 5) 95 | 7. M (Modus ponens on 3 and 6) 96 | 8. Therefore M (The Conclusion) 97 | 98 | Take a moment to see if you can match up the propositions and their meanings with the natural language versions above. If it feels unnatural right now, don't worry too much. This will become more natural as you progress. 99 | 100 | # Evidence 101 | The argument above is pretty similar to a full formalization of the chain of reasoning. This game doesn't reference line numbers and doesn't allow free-floating hypothesis. Instead everything is given a name. The justification for introducing a new name will be an expression. 102 | 103 | Here's how this example might be expressed in the language of this game. It's a little different, but see if you can match this up with the argument as expressed above: 104 | 105 | ```lean 106 | -- Objects 107 | C, D, M, T : Prop 108 | -- Assumptions 109 | h₁ : C ∨ D 110 | h₂ : D → T 111 | h₃ : C → M 112 | h₄ : ¬T 113 | ``` 114 | ---- 115 | ```lean 116 | -- Chain of reasoning 117 | have h₅ : ¬D := modus_tollens h₂ h₄ 118 | have h₆ : C := modus_tollendo_ponens h₁ h₅ 119 | have h₇ : M := modus_ponens h₃ h₆ 120 | -- Goal (Conclusion) 121 | exact h₇ 122 | ``` 123 | 124 | You'll notice I have given each assumption and each step in the chain of reasoning names like `h♯` where `♯` matches up with the line numbers from before. That's just to put the example here in easy correspondence with the examples above. 125 | 126 | `h₁ : C ∨ D` is read out in english as `h₁` is evidence for the proposition `C ∨ D`. In this case, `h₁` is evidence given as part of the premise, so there's no `:= ...` expression afterwards. 127 | 128 | To introduce new evidence — such as `h₅` — you need to write out an expression that the game can evaluate as evidence of the correct proposition. We'll introduce how to write these expressions throughout the tutorial worlds. 129 | 130 | `h₅` is evidence of `¬D` (that \"Dog fur was not found at the scene of the crime.\") and we know this by the expression `(modus_tollens h₂ h₄)`. 131 | 132 | # To start 133 | 134 | In this game, each level will ask you to provide evidence of some proposition. This will often involve using the evidence you already have in some way. 135 | 136 | Click on the first world — **Party Invites** — to get started. 137 | 138 | ---- 139 | 140 | [¹] This game says “evidence” where other learning material may say “term”, “proof”, or neither (relying on context to differentate a proposition from its proof). I use *evidence* for this game in an effort to make the flavor text seem less at odds with the formalization.\\ 141 | \\ 142 | [ˢʳᶜ]Logic example taken from [IEP](https://iep.utm.edu/propositional-logic-sentential-logic/#H5) entry: *Propositional Logic*. 143 | " 144 | 145 | Info " 146 | *Game version: 0.1.0* 147 | 148 | # Discussion 149 | 150 | This game is currently in its initial development phase, designed to be largely self-contained and accessible without requiring a programming or math background to navigate. Feedback about meeting that goal is welcome! 151 | 152 | While self-contained; in many ways, this game is targeted more at programmers than mathematicians. It doesn't use classical reasoning, sticking instead to constructive logic. The emphasis for most of the theorem proving is on writing proof terms — rather than using tactics. 153 | 154 | Please feel encouraged to visit the game's GitHub repository and initiate a discussion on any topic regarding this game. Just open a new issue or comment on an existing one. 155 | Github: [A Lean Intro to Logic](https://github.com/Trequetrum/lean4game-logic) 156 | 157 | ## Progress saving 158 | 159 | The game stores your progress in your local browser storage. 160 | If you delete it, your progress will be lost! 161 | 162 | Warning: In most browsers, deleting cookies will also clear the local storage 163 | (or \"local site data\"). Make sure to download your game progress first! 164 | 165 | ## Credits 166 | 167 | * **Game Author:** Mark Fischer 168 | * **Game Engine:** Alexander Bentkamp, Jon Eugster, Patrick Massot 169 | 170 | ## Resources 171 | 172 | * The [Lean Zulip chat](https://leanprover.zulipchat.com/) forum 173 | * Github: [A Lean Intro to Logic](https://github.com/Trequetrum/lean4game-logic) 174 | " 175 | 176 | /-! Information to be displayed on the servers landing page. -/ 177 | Languages "English" 178 | CaptionShort "Lean intro to Logic" 179 | CaptionLong "A mostly self-contained introduction to constructive logic using Lean. Learn how each propositional connective can be introduced and eliminated using both terms and tactics." 180 | CoverImage "images/logic0101_v4.png" 181 | 182 | /-! Build the game. Shows warnings if it found a problem with your game. -/ 183 | MakeGame 184 | -------------------------------------------------------------------------------- /Game/Doc/Definitions.lean: -------------------------------------------------------------------------------- 1 | import GameServer.Commands 2 | 3 | namespace GameLogic 4 | 5 | /-- 6 | # Conjunction 7 | ## Introduction 8 | An “And” can be introduced with the `and_intro` theorem. Conjunctions and biconditionals can both be constructed using the angle bracket notation as well. 9 | ### Examples 10 | ``` 11 | -- Assumptions 12 | p : P 13 | q : Q 14 | -- Each new term is evidence for P ∧ Q 15 | -- Explicit Constructer, no annotations needed 16 | have h₁ := and_intro p q 17 | have h₂ := and_intro (left := p) (right := q) 18 | -- Implicit Constructuer, annotations based on context 19 | -- Type these angle brackets with “\<” and “\>” 20 | have h₆ : P ∧ Q := ⟨p,q⟩ 21 | have h₇ := (⟨p,q⟩ : P ∧ Q) 22 | ``` 23 | ## Elimination 24 | An “And” like `h : P ∧ Q` can be reduced in two ways: 25 | 1. Aquire evidence for `P` using `and_left h` or `h.left` 26 | 2. Aquire evidence for `Q` using `and_right` or `h.right` 27 | -/ 28 | DefinitionDoc GameLogic.and_def as "∧" 29 | 30 | /-- 31 | # Disjunction 32 | ## Introduction 33 | An “Or” like `h : P ∨ Q` can be introduced in two ways: 34 | 1. If you have `p : P`, you can use `or_inl p` 35 | 2. If you have `q : Q`, you can use `or_inr q` 36 | 37 | In either case, remember that the other type in your disjunction must be inferable in context or supplied as part of the expression. For example: `(or_inl p : P ∨ Q)` 38 | ## Elimination 39 | An “Or” like `h : P ∨ Q` can be be eliminated if both P and Q imply the same proposition. In this example, P or Q implies R: 40 | ``` 41 | -- Assumptions 42 | pvq: P ∨ Q 43 | pr : P → R 44 | qr : Q → R 45 | -- Goal: R 46 | exact or_elim pvq pr qr 47 | ``` 48 | -/ 49 | DefinitionDoc GameLogic.or_def as "∨" 50 | 51 | /-- 52 | # False 53 | This is a proposition for which there can never be any evidence. If your assumptions lead you to evidence for `False`, then your assumptions are inconsitent and you can use `false_elim` to deduce any proposition you like. 54 | -/ 55 | DefinitionDoc GameLogic.false_def as "False" 56 | 57 | /-- 58 | # Biconditional 59 | ## Introduction 60 | An “If and only if” can be introduced with the `iff_intro` theorem. Biconditionals and conjunctions can both be constructed using the angle bracket notation as well. 61 | ### Examples 62 | ``` 63 | -- Assumptions 64 | h₁ : P → Q 65 | h₂ : Q → P 66 | -- Each new term is evidence for P ↔ Q 67 | -- Explicit Constructer, no annotations needed 68 | have h₁ := iff_intro h₁ h₂ 69 | have h₂ := iff_intro (mp := h₁) (mpr := h₂) 70 | -- Implicit Constructuer, annotations based on context 71 | -- Type these angle brackets with “\<” and “\>” 72 | have h₆ : P ↔ Q := ⟨h₁, h₂⟩ 73 | have h₇ := (⟨h₁, h₂⟩ : P ↔ Q) 74 | ``` 75 | ## Elimination 76 | An “If and only if” like `h : P ↔ Q` can be reduced in two ways: 77 | 1. Aquire evidence for `P → Q` using `iff_mp h` or `h.mp` 78 | 2. Aquire evidence for `Q → P` using `iff_mpr h` or `h.mpr` 79 | ## Rewrite 80 | Biconditionals let you use the `rewrite` tactic to change goals or assumptions. 81 | -/ 82 | DefinitionDoc GameLogic.iff_def as "↔" 83 | 84 | /-- 85 | # Function Application/Implication Elimination 86 | `P → Q` is propostion given to functions from evidence of `P` to evidence of `Q`. 87 | # Juxtaposition 88 | Juxtaposition just means “to place next to each other,” which is what we'll do to give a parameter to a function. 89 | ### Example 90 | ``` 91 | -- Assumptions 92 | e₁ : P 93 | e₂ : Q 94 | Goal: 95 | P ∧ Q 96 | ``` 97 | ---- 98 | ``` 99 | exact (and_intro e₁ e₂) 100 | ``` 101 | ### Example 102 | ``` 103 | -- Assumptions 104 | a : A 105 | h₁ : A → B 106 | -- Goal 107 | B 108 | ``` 109 | --- 110 | ``` 111 | exact (h₁ a) 112 | ``` 113 | Takes `h₁` and applies `a` to it. 114 | -/ 115 | DefinitionDoc GameLogic.FunElim as "→ elim" 116 | 117 | /-- 118 | # `fun _ => _` 119 | You can create evidence for an implication by defining the appropriate function. 120 | - `have h₁ : P → P := fun p : P => p` 121 | - `have h₂ : P ∧ Q → P := fun h : P ∧ Q => h.left` 122 | 123 | Generally, you don't need to repeat the types when they're obvious from the context. 124 | - `have h₁ : P → P := fun p => p` 125 | - `have h₂ : P ∧ Q → P := fun h => h.left` 126 | 127 | # Unicode: 128 | - `fun` can be written as `λ` 129 | - `=>` can be written as `↦` 130 | ---- 131 | - `have h₁ : P → P := λp ↦ p` 132 | - `have h₂ : P ∧ Q → P := λh ↦ h.left` 133 | -/ 134 | DefinitionDoc GameLogic.FunIntro as "→ intro" 135 | 136 | /-- 137 | ### **Logic Constants & Operators** 138 | | $Name~~~$ | $Ascii~~~$ | $Unicode$ | $Unicode Cmd$ | 139 | | --- | :---: | :---: | --- | 140 | | True | `True` | | | 141 | | False | `False` | | | 142 | | Not | `Not` | ¬ | `\n` `\not` `\neg` `\lnot` | 143 | | And | `/\` | ∧ | `\and` `\an` `\wedge` | 144 | | Or | `\/` | ∨ | `\v` `\or` `\vee` | 145 | | Implies | `->` | → | `\r` `\imp` `\->` `\to` `\r-` `\rightarrow` | 146 | | Iff | `<->` | ↔ | `\iff` `\lr-` `\lr` `\<->` `\leftrightarrow` | 147 | | For All | `foral` | ∀ | `\all` `\forall` | 148 | | Exists | `exists` | ∃ | `\ex` `\exists` | 149 | 150 | ### **Anonymous Function** 151 | Example: 152 | An anonymous function that swaps a conjunction 153 | ``` 154 | -- Ascii 155 | fun h : P ∧ Q => and_intro (and_right h) (and_left h) 156 | -- Unicode 157 | λh : P ∧ Q ↦ ⟨h.right, h.left⟩ 158 | ``` 159 | | $Ascii~~~$ | $Unicode~~~$ | $Unicode Cmd$ | 160 | | --- | :---: | --- | 161 | | `fun` | λ | `\fun` `\la` `\lambda` `\lamda` `\lam` `\Gl` | 162 | | `=>` | ↦ | `\map` `\mapsto` | 163 | 164 | ### **Other Unicode** 165 | | $Name$ | $Unicode~~~$ | $Unicode Cmd$ | 166 | | --- | :---: | --- | 167 | | Angle brackets | ⟨ ⟩ | `\<` `\>` `\langle` `\rangle` | 168 | | Subscript Numbers | ₁ ₂ ₃ ... | `\1` `\2` `\3` ... | 169 | | Left Arrow | ← | `\l` `\leftarrow` `\gets` `\<-` | 170 | | Turnstyle | ⊢ | `\│-` `\entails` `\vdash` `\goal` | 171 | -/ 172 | DefinitionDoc GameLogic.AsciiTable as "Unicode Table" 173 | 174 | /-- 175 | # Remembering Algebra 176 | In math class, you may have learned an acronym like BEDMAS or PEMDAS to remember the precedence of operators in your math expressions: 177 | 1. Brackets (or Parentheses) 178 | 2. Exponents 179 | 3. Division or Multiplication 180 | 4. Addition or Subtraction 181 | 182 | These rules exist for the logical operators as well. 183 | # Brackets 184 | Brackets group or disambiguate expressions. You can think of precedence rules as deciding where brackets belong. If an operator is an infix operator, then it has an associativity as well. 185 | - right-associative: `P ∧ Q ∧ R` ≡ `P ∧ (Q ∧ R)` 186 | - left-associative: `1 + 2 + 3` ≡ `(1 + 2) + 3` 187 | - non-associative: `P ↔ Q ↔ R` is an error 188 | # High to low Precedence 189 | Function application doesn't have an operator, it's just `function argument`. It has max precedence and is left associative (meaning `and_intro p q` ≡ `(and_intro p) q`). 190 | ### Propositional Operators 191 | | $Operator$ | $~~~Precedence$ | | 192 | | :---: | :---: | --- | 193 | | ¬ | max | | 194 | | ∧ | 35 | right-associative | 195 | | ∨ | 30 | right-associative | 196 | | → | 25 | right-associative | 197 | | ↔ | 20 | non-associative | 198 | | ∃ | __ | | 199 | | ∀ | __ | | 200 | ### Expression Operators 201 | | $Operator$ | $~~~Precedence$ | | 202 | | :---: | :---: | --- | 203 | | ≫ | 85 | left-associative | 204 | | |> | min + 1 | right-associative | 205 | | <| | min | left-associative | 206 | ### Example: 207 | ``` 208 | ¬P ∨ Q ∧ P → Q ↔ Q ∨ R ∨ ¬S 209 | -- ¬ binds the tightest: 210 | (¬P) ∨ Q ∧ P → Q ↔ Q ∨ R ∨ (¬S) 211 | -- Next is ∧ 212 | (¬P) ∨ (Q ∧ P) → Q ↔ Q ∨ R ∨ (¬S) 213 | -- Next is ∨, associated right 214 | (¬P) ∨ (Q ∧ P) → Q ↔ Q ∨ (R ∨ (¬S)) 215 | -- The rest of ∨ 216 | ((¬P) ∨ (Q ∧ P)) → Q ↔ (Q ∨ (R ∨ (¬S))) 217 | -- Next is → 218 | (((¬P) ∨ (Q ∧ P)) → Q) ↔ (Q ∨ (R ∨ (¬S))) 219 | -- No more steps as this is fully disambiguated 220 | ``` 221 | Here's a version where you can see it aligned 222 | ``` 223 | ¬P ∨ Q ∧ P → Q ↔ Q ∨ R ∨ ¬S 224 | (¬P) ∨ Q ∧ P → Q ↔ Q ∨ R ∨ (¬S) 225 | (¬P) ∨ (Q ∧ P) → Q ↔ Q ∨ R ∨ (¬S) 226 | (¬P) ∨ (Q ∧ P) → Q ↔ Q ∨ (R ∨ (¬S)) 227 | ((¬P) ∨ (Q ∧ P)) → Q ↔ (Q ∨ (R ∨ (¬S))) 228 | (((¬P) ∨ (Q ∧ P)) → Q) ↔ (Q ∨ (R ∨ (¬S))) 229 | ``` 230 | -/ 231 | DefinitionDoc GameLogic.Precedence as "Precedence" 232 | -------------------------------------------------------------------------------- /Game/Doc/Lemmas.lean: -------------------------------------------------------------------------------- 1 | import GameServer.Commands 2 | 3 | namespace GameLogic 4 | 5 | def and_left {P Q : Prop} (h : P ∧ Q) : P := And.left h 6 | 7 | /-- 8 | # ∧ Elimination Left 9 | ### `and_left : P ∧ Q -> P` 10 | 11 | If `h` is a term with a type like `P ∧ Q` 12 | 13 | `and_left h`, `h.left` or `h.1` are all expressions for denoting the left-hand side of the given evidence. In this case, the left side has a type of `P`. 14 | -/ 15 | TheoremDoc GameLogic.and_left as "and_left" in "∧" 16 | 17 | def and_right {P Q : Prop} (h : P ∧ Q) : Q := And.right h 18 | 19 | /-- 20 | # ∧ Elimination Right 21 | ### `and_right : P ∧ Q -> Q` 22 | 23 | If `h` is a term with a type like `P ∧ Q` 24 | 25 | `and_right h`, `h.right` or `h.2` are all expressions for denoting the right-hand side of the given evidence. In this case, the left side has a type of `Q`. 26 | -/ 27 | TheoremDoc GameLogic.and_right as "and_right" in "∧" 28 | 29 | def and_intro {P Q : Prop} (left : P) (right : Q) : P ∧ Q := And.intro left right 30 | 31 | /-- 32 | # and_intro 33 | ### `and_intro : P -> Q -> P ∧ Q` 34 | `and_intro` is a function with two parameters. It takes two disparate pieces of evidence and combines them into a single piece of evidence. If `(e₁ : P)` and `(e₂ : Q)` are evidence, then 35 | ``` 36 | have h : P ∧ Q := and_intro e₁ e₂ 37 | ``` 38 | -/ 39 | TheoremDoc GameLogic.and_intro as "and_intro" in "∧" 40 | 41 | def false_elim {P : Prop} (h : False) : P := h.rec 42 | 43 | /-- 44 | If 45 | ``` 46 | -- Assumptions 47 | h : False 48 | ``` 49 | then 50 | ``` 51 | have t : T := false_elim h 52 | ``` 53 | will allow you to write any well formed proposition in place of `T`. This makes `false_elim` the \"From `False`, anything goes\" function. **Ex falso quodlibet**. 54 | -/ 55 | TheoremDoc GameLogic.false_elim as "false_elim" in "¬" 56 | 57 | def or_inl {P Q : Prop} (p : P) : Or P Q := Or.inl p 58 | 59 | /-- 60 | # Or Introduction Left 61 | Turns evidence for the lefthand of an `∨` proposition into a disjunction. The context must supply what the righthand side of the disjunction is. 62 | ``` 63 | -- Objects 64 | P Q : Prop 65 | -- Assumptions 66 | p : P 67 | ``` 68 | allows: 69 | ``` 70 | have h : P ∨ Q := or_inl p 71 | have h := (or_inl p : P ∨ Q) 72 | have h := show P ∨ Q from or_inl p 73 | ``` 74 | -/ 75 | TheoremDoc GameLogic.or_inl as "or_inl" in "∨" 76 | 77 | def or_inr {P Q : Prop} (q : Q) : Or P Q := Or.inr q 78 | 79 | /-- 80 | # Or Introduction Right 81 | Turns evidence for the righthand of an `∨` proposition into a disjunction. The context must supply what the lefthand side of the disjunction is. 82 | ``` 83 | -- Objects 84 | P Q : Prop 85 | -- Assumptions 86 | q : Q 87 | ``` 88 | allows: 89 | ``` 90 | have h : P ∨ Q := or_inr q 91 | have h := (or_inl q : P ∨ Q) 92 | have h := show P ∨ Q from or_inl q 93 | ``` 94 | " 95 | -/ 96 | TheoremDoc GameLogic.or_inr as "or_inr" in "∨" 97 | 98 | def or_elim 99 | {P Q R : Prop} 100 | (h : P ∨ Q) 101 | (left : P → R) 102 | (right : Q → R) : R := 103 | Or.elim h left right 104 | 105 | /-- 106 | # Or Elimination 107 | If you can conclude something from `A` and you can conclude the same thing from `B`, then if you know `A ∨ B` it won't matter which of the two happens as you can still guarantee something. 108 | 109 | or_elim is also evidence: 110 | ``` 111 | or_elim : (P ∨ Q) → (P → R) → (Q → R) → R` 112 | ``` 113 | # Parameters 114 | `or_elim` has three parameters: 115 | 1. takes evidence for a disjunction, 116 | 2. evidence an implication on the left, 117 | 3. evidence for an implication on the right. 118 | # Example 119 | `or_elim` is your first 3-parameter function. 120 | ``` 121 | pvq: P ∨ Q 122 | pr : P → R 123 | qr : Q → R 124 | have r : R := or_elim pvq pr qr 125 | ``` 126 | -/ 127 | TheoremDoc GameLogic.or_elim as "or_elim" in "∨" 128 | 129 | def iff_intro 130 | {P Q : Prop} 131 | (mp: P → Q) 132 | (mpr: Q → P) : P ↔ Q := 133 | Iff.intro mp mpr 134 | 135 | example {P Q : Prop} (h : P ∧ Q ∨ ¬P ∧ ¬Q) : P ↔ Q := 136 | or_elim h 137 | (λh₁ ↦ ⟨λ_ ↦ h₁.right, λ_ ↦ h₁.left⟩) 138 | (λh₁ ↦ ⟨false_elim ∘ h₁.left, false_elim ∘ h₁.right⟩) 139 | 140 | /-- 141 | # Propositional Equivalence 142 | `P ↔ Q` means that `P` and `Q` must have the same truth value. This is often said as “`P` if and only iff `Q`” or “`P` is logically equivalent to `Q`”. 143 | 144 | `iff_intro` is the way to prove a biconditional like `P ↔ Q`. It requires you to show evidence for both `P → Q` and `Q → P`. 145 | -/ 146 | TheoremDoc GameLogic.iff_intro as "iff_intro" in "↔" 147 | 148 | def iff_mp {P Q : Prop} (h : P ↔ Q) : P → Q := h.mp 149 | 150 | /-- 151 | If you have an assumption like `h : P ↔ Q`, then 152 | ``` 153 | iff_mp h -- or 154 | h.mp -- is evidence for P → Q 155 | 156 | iff_mpr h -- or 157 | h.mpr -- is evidence for Q → P 158 | ``` 159 | -/ 160 | TheoremDoc GameLogic.iff_mp as "iff_mp" in "↔" 161 | 162 | def iff_mpr {P Q : Prop} (h : P ↔ Q) : Q → P := h.mpr 163 | 164 | /-- 165 | If you have an assumption like `h : P ↔ Q`, then 166 | ``` 167 | iff_mp h -- or 168 | h.mp -- is evidence for P → Q 169 | 170 | iff_mpr h -- or 171 | h.mpr -- is evidence for Q → P 172 | ``` 173 | -/ 174 | TheoremDoc GameLogic.iff_mpr as "iff_mpr" in "↔" 175 | 176 | def modus_ponens {P Q : Prop} (hpq: P → Q) (p: P) : Q := hpq p 177 | 178 | /-- 179 | In this game, the deductive rule *modus_ponens* is just function application. 180 | ``` 181 | intro h : A ∧ B 182 | have a : A := and_left h 183 | -- could be written as 184 | have a : A := modus_ponens and_left h 185 | ``` 186 | and 187 | ``` 188 | intro a : A 189 | intro b : B 190 | have h : A ∧ B := and_intro a b 191 | -- could be written as 192 | have h : A ∧ B := modus_ponens (modus_ponens and_intro a) b 193 | ``` 194 | 195 | You should never use this style of prefix `modus_ponens` and just use function application instead as that will generally be clearer. 196 | 197 | ---- 198 | # Infix Modus Ponens 199 | There is are infix operators for function application; they look like `|>` and `<|`. `f <| x`, and `x |> f` means the same as the same as `f x`. 200 | 201 | `<|` parses `x` with lower precedence, which means that `f <| g $ <|` is interpreted as `(f (g x))` rather than `((f g) x)`. 202 | 203 | It's twin, `|>` chains such that `x |> f |> g` is interpreted as `g (f x)`. 204 | 205 | What makes the infix operators useful is that they can often replace a pair of brackets `(...)` making expressions easier to read. 206 | 207 | ---- 208 | # Computer Science 209 | If you've done some programming before, you might recognise `Modus Ponens` as the identity function for implications. So `(modus_ponens and_left)` is extensionally equal to `and_left`. There's a conspiracy at work here! 210 | -/ 211 | TheoremDoc GameLogic.modus_ponens as "modus_ponens" in "→" 212 | 213 | def and_comm {P Q : Prop}: P ∧ Q ↔ Q ∧ P := 214 | ⟨ λ⟨l,r⟩ ↦ ⟨r,l⟩, 215 | λ⟨l,r⟩ ↦ ⟨r,l⟩ 216 | ⟩ 217 | 218 | /-- 219 | # ∧ is commutative 220 | 221 | `and_comm` is evidence that `P ∧ Q ↔ Q ∧ P` 222 | -/ 223 | TheoremDoc GameLogic.and_comm as "and_comm" in "∧" 224 | 225 | def and_assoc {P Q R : Prop}: (P ∧ Q) ∧ R ↔ P ∧ Q ∧ R := 226 | ⟨ λ⟨⟨p,q⟩,r⟩ ↦ ⟨p,q,r⟩, 227 | λ⟨p,q,r⟩ ↦ ⟨⟨p,q⟩,r⟩ 228 | ⟩ 229 | 230 | /-- 231 | # ∧ is Associative 232 | 233 | `and_assoc` is evidence that `(P ∨ Q) ∨ R ↔ P ∨ Q ∨ R` 234 | -/ 235 | TheoremDoc GameLogic.and_assoc as "and_assoc" in "∧" 236 | 237 | def or_comm {P Q : Prop}: P ∨ Q ↔ Q ∨ P := 238 | ⟨ (Or.elim · Or.inr Or.inl), 239 | (Or.elim · Or.inr Or.inl) 240 | ⟩ 241 | 242 | /-- 243 | # ∨ is commutative 244 | 245 | `or_comm` is evidence that `P ∨ Q ↔ Q ∨ P` 246 | -/ 247 | TheoremDoc GameLogic.or_comm as "or_comm" in "∨" 248 | 249 | -- or_assoc 250 | def or_assoc {P Q R : Prop}: (P ∨ Q) ∨ R ↔ P ∨ Q ∨ R := 251 | have mp := (Or.elim · 252 | (Or.elim · Or.inl (Or.inr ∘ Or.inl)) 253 | (Or.inr ∘ Or.inr)) 254 | have mpr := (Or.elim · 255 | (Or.inl ∘ Or.inl) 256 | (Or.elim · (Or.inl ∘ Or.inr) Or.inr)) 257 | ⟨mp,mpr⟩ 258 | 259 | -- or_assoc expanded 260 | example {P Q R : Prop}: P ∨ Q ∨ R ↔ (P ∨ Q) ∨ R := 261 | have mp := (λh ↦ Or.elim h 262 | (λp ↦ Or.inl (Or.inl p)) 263 | (λh₂ ↦ Or.elim h₂ 264 | (λq ↦ Or.inl (Or.inr q)) 265 | (λr ↦ Or.inr r))) 266 | have mpr := (λh ↦ Or.elim h 267 | (λh₂ ↦ Or.elim h₂ 268 | (λp ↦ Or.inl p) 269 | (λq ↦ Or.inr (Or.inl q))) 270 | (λr ↦ Or.inr (Or.inr r))) 271 | Iff.intro mp mpr 272 | 273 | example {P Q R : Prop}: P ∨ Q ∨ R ↔ (P ∨ Q) ∨ R := by 274 | have mp : P ∨ Q ∨ R → (P ∨ Q) ∨ R := (Or.elim · 275 | (Or.inl ∘ Or.inl) 276 | (Or.elim · (Or.inl ∘ Or.inr) Or.inr)) 277 | have mpr : (P ∨ Q) ∨ R → P ∨ Q ∨ R := (Or.elim · 278 | (Or.elim · Or.inl (Or.inr ∘ Or.inl)) 279 | (Or.inr ∘ Or.inr)) 280 | exact ⟨mp,mpr⟩ 281 | 282 | /-- 283 | # ∨ is Associative 284 | 285 | `or_assoc` is evidence that `P ∨ Q ∨ R ↔ (P ∨ Q) ∨ R` 286 | -/ 287 | TheoremDoc GameLogic.or_assoc as "or_assoc" in "∨" 288 | 289 | def imp_trans {P Q R : Prop} (hpq : P → Q) (hqr : Q → R) (p : P): R := hqr (hpq p) 290 | 291 | /-- 292 | # → is transitive 293 | `P → Q` and `Q → R` implies `P → R` 294 | ``` 295 | imp_trans : (P → Q) → (Q → R) → P → R 296 | ``` 297 | 298 | Of course, because of `and_comm`, you know you can flip this around too. 299 | `Q → R` and `P → Q` implies `P → R` has a near-identical proof. 300 | 301 | ### Infix Operator: 302 | `imp_trans` has an infix operator. This looks like `≫` (which is written as “`\gg`”). 303 | 304 | For the math-inclined, because the expression for an implication is a function, you can also use function composition for the same purpose (`∘` is written as “`\o`”). Just remember that `∘` has the parameters swapped from the way `imp_trans` is defined. 305 | -/ 306 | TheoremDoc GameLogic.imp_trans as "imp_trans" in "→" 307 | 308 | infixl:85 " ≫ " => λa b ↦ Function.comp b a -- type as \gg 309 | 310 | def not_not_not {P : Prop}: ¬¬¬P ↔ ¬P := ⟨ 311 | (λh p ↦ h (λnp ↦ np p)), 312 | (λnp nnp ↦ nnp $ np) 313 | ⟩ 314 | 315 | /-- 316 | # Negation is stable 317 | A nice result of this theorem is that any more than 2 negations can be simplified down to 1 or 2 negations. 318 | ``` 319 | not_not_not : ¬¬¬P ↔ ¬P 320 | ``` 321 | -/ 322 | TheoremDoc GameLogic.not_not_not as "not_not_not" in "↔ extra" 323 | 324 | def modus_tollens {P Q : Prop} (h: P → Q) (nq: ¬Q) : ¬P := h ≫ nq 325 | 326 | /-- 327 | # Modus Tollens 328 | Denying the consequent. 329 | 330 | If P, then Q.\ 331 | Not Q.\ 332 | Therefore, not P. 333 | ``` 334 | mt : (P → Q) → ¬Q → ¬P 335 | ``` 336 | 337 | ### Infix Operator: 338 | `modus_tollens` is a specialized version of `imp_trans`, which makes it possible to use `≫` (which is written as “`\gg`”) as an infix operator for `modus_tollens`. 339 | -/ 340 | TheoremDoc GameLogic.modus_tollens as "modus_tollens" in "→" 341 | 342 | def identity {P : Prop}(p : P) : P := p 343 | 344 | /-- 345 | # Propositional Identity 346 | This is the \"I think therefore I am\" of propositional logic. Like `True` it is a simple tautology whose truth requires no premises or assumptions — only reason alone. 347 | ``` 348 | identity : P → P 349 | ``` 350 | -/ 351 | TheoremDoc GameLogic.identity as "identity" in "→" 352 | -------------------------------------------------------------------------------- /Game/Doc/Tactics.lean: -------------------------------------------------------------------------------- 1 | import GameServer.Commands 2 | 3 | /-- 4 | # Summary 5 | The `exact` tactic is a means through which you give the game your answer. Many levels can be done in multiple steps. You'll use the `exact` tactic when you're ready to create the final expression. It will be evaluated to see whether it matches the goal. 6 | 7 | `exact` will work with any expression and attempt to unify it with the current goal. The simplest such expression is just a name that — `:` — “is evidence for” the goal. More complicated expressions often make use of unlocked definitions and theorems as well as function abstraction and application. 8 | 9 | # Errors 10 | Because most of the starting levels use only the `exact` tactic and an expression, it's common to forget to specify the tactic. Sometimes this raises the error: 11 | ``` 12 | unknown tactic 13 | ``` 14 | and other times the much more ambiguous message: 15 | ``` 16 | no goals left 17 | This probably means you solved the level with warnings or Lean encountered a parsing error. 18 | ``` 19 | Hopefully we'll have better error messages in the future ☺ 20 | 21 | ### Example 22 | ``` 23 | Objects: 24 | P : Prop 25 | Assumptions: 26 | h : P 27 | Goal: 28 | P 29 | ``` 30 | ---- 31 | ``` 32 | exact h 33 | ``` 34 | 35 | ### Example 36 | ``` 37 | Objects: 38 | P Q: Prop 39 | Assumptions: 40 | h : (P → Q) ∧ ¬Q 41 | Goal: 42 | ¬P 43 | ``` 44 | ---- 45 | ``` 46 | exact λp ↦ and_right h (and_left h p) 47 | ``` 48 | -/ 49 | TacticDoc exact 50 | 51 | /-- 52 | ## Summary 53 | `have` is used to add new assumptions to your proof state. 54 | 55 | `have h : P := e` adds the assumption `h : P` to the current proof state if `e` is an expression that evaluates to evidence for `P`. 56 | 57 | If `P` is omitted, the game will attempt to infer the proposition. Most tutorial worlds will introduce alternative expressions as a shorthand where you can omit parts of the expression if the proposition being introduced can be inferred. 58 | 59 | `and_intro e₁ e₂`, and `iff_intro e₁ e₂` can both be written as `⟨e₁, e₂⟩` as long as the context makes the proposition being constructed clear. This will often mean using the long hand or including the `P` when using the `have` tactic. 60 | ``` 61 | -- Should h be inferred as P ∧ Q or P ↔ Q? 62 | -- To be unambiguous 63 | exact h := ⟨e₁, e₂⟩ 64 | -- must become 65 | exact h : P ∧ Q := ⟨e₁, e₂⟩ 66 | -- or perhaps 67 | exact h := (⟨e₁, e₂⟩ : P ∧ Q) 68 | ``` 69 | 70 | ### Example 71 | ``` 72 | Objects: 73 | P Q: Prop 74 | Assumptions: 75 | h : (P → Q) ∧ ¬Q 76 | Goal: 77 | ¬P 78 | ``` 79 | --- 80 | ``` 81 | have hpq := h.left 82 | ``` 83 | creates the new proof state where hpq is an assumption 84 | ``` 85 | Objects: 86 | P Q: Prop 87 | Assumptions: 88 | h : (P → Q) ∧ ¬Q 89 | hpq : P → Q 90 | Goal: 91 | ¬P 92 | ``` 93 | -/ 94 | TacticDoc «have» 95 | 96 | /-- 97 | ## Summary 98 | 99 | If `h₁` is a proof of an equivalence `P ↔ Q`, then `rw [h₁]` will change 100 | all `P`s in the goal to `Q`s. It's the way to “substitute in”. 101 | 102 | ## Variants 103 | 104 | * `rw [← h₁]` — changes `Q`s to `P`s; get the back arrow by typing `\left ` or `\l`. 105 | 106 | * `rw [h₁, h₂, h₃, h₄]` — a sequence of rewrites 107 | 108 | * `rw [h₁] at h₂` — changes `P`s to `Q`s in hypothesis `h₂` 109 | 110 | * `rw [h₁] at h₂ h₃ ⊢` — changes `X`s to `Y`s in two hypotheses and the goal; 111 | get the `⊢` symbol with `\|-`. 112 | 113 | * `repeat rw [h₁]` — keep attempting to `rw` until there are no more matches. For example, if the goal is `¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬P` you can use `rw [not_not_not]` 9 times or just use `repeat rw [not_not_not]` once to get `¬P` 114 | 115 | * `nth_rewrite 2 [h₁]` — will change only the second `P` in the goal to `Q`. 116 | -/ 117 | TacticDoc rw 118 | 119 | /-- 120 | ## Summary 121 | `repeat t` repeatedly applies the tactic `t` to the goal. You don't need to use this tactic, it just speeds things up sometimes. 122 | -/ 123 | TacticDoc «repeat» 124 | 125 | /-- 126 | ## Summary 127 | 128 | If `h : X = Y` and there are several `X`s in the goal, then 129 | `nth_rewrite 3 [h]` will just change the third `X` to a `Y`. 130 | 131 | ## Example 132 | 133 | If the goal is `2 + 2 = 4` then `nth_rewrite 2 [two_eq_succ_one]` 134 | will change the goal to `2 + succ 1 = 4`. In contrast, `rw [two_eq_succ_one]` 135 | will change the goal to `succ 1 + succ 1 = 4`. 136 | -/ 137 | TacticDoc nth_rewrite 138 | 139 | /-- 140 | assumption tries to solve the main goal by searching your the assumptions in your proof state for a hypothesis with a compatible proposition 141 | -/ 142 | TacticDoc assumption 143 | 144 | /-- 145 | # Constructor 146 | Whenever there's a clear way to create new evidence **that matches the goal**, the constructor tactic will pick that for you. This replaces the current goal with one or more goals that together complete the construction. 147 | 148 | For example, if your goal is `P ∧ Q` then the `constructor` tactic will replace that goal with two separate subgoals. First you'll need to show evidence for `P`, then you'll need to show evidence for `Q`. 149 | -/ 150 | TacticDoc constructor 151 | 152 | /-- 153 | # Conjunction/Biconditional 154 | `cases` will deconstruct an `∧` or an `↔` into it's parts, removing the assumption and replacing it with two new assumptions. 155 | # Disjunction 156 | Used with an `∨` cases will split the main goal, replacing it with a goal for each of the two possibilities. 157 | -/ 158 | TacticDoc cases 159 | 160 | /-- 161 | # It suffices to show 162 | To prove `Q` by `P → Q`, it suffices to show `P`. 163 | ### More Generally 164 | To prove `Q` by `P₁ → P₂ → P₃ → ... → Q`, it suffices to show `P₁, P₂, P₃, ...`. One way to convince yourself this is true is to prove that `(P₁ → P₂ → Q) → (P₁ ∧ P₂ → Q)` and convince yourself there exists a procedure for any `(P₁ → P₂ → ... → Q) → (P₁ ∧ P₂ ∧ ... → Q)` 165 | ### In Practice 166 | The `apply` tactic returns as many subgoals as the number of premises that have not been fixed by the Goal. 167 | ### Example: 168 | If you have: 169 | ``` 170 | Assumptions: 171 | h : P → Q 172 | Goal : Q 173 | ``` 174 | then `apply h` will change your proof state to: 175 | ``` 176 | Assumptions: 177 | h : P → Q 178 | Goal : P 179 | ``` 180 | -/ 181 | TacticDoc apply 182 | 183 | /-- 184 | # Sub-Proof 185 | If your Goal is an implication, this tactic introduces one or more hypotheses, optionally naming and/or pattern-matching them. 186 | 187 | The effect on a goal like `P → Q` is to add `P` as an assumption and change the Goal to `Q`. If the implication is already a part of a sub-proof, then once you show evidence for `Q`, the assumption `P` is discharged and can not be used for the rest of the proof. 188 | 189 | This is is the interactive way of defining a function using tactics. You can think of discharging an assumption as the same as parameters being limited in scope to the function's body/definition. 190 | -/ 191 | TacticDoc intro 192 | 193 | /-- 194 | contradiction closes the current goal there are assumptions which are "trivially contradictory". 195 | 196 | ### Example 197 | ``` 198 | Assumptions: 199 | h : False 200 | ``` 201 | ### Example 202 | ``` 203 | Assumptions: 204 | h₁ : P 205 | h₂ : ¬P 206 | ``` 207 | -/ 208 | TacticDoc contradiction 209 | 210 | /-- 211 | Change the goal to `False`. This is only helpful when there are assumptions which are in some way contradictory. 212 | ### Example 213 | ``` 214 | Assumptions 215 | h : P ∧ ¬P 216 | Goal: Q 217 | ``` 218 | I cannot show evidence for `Q` directly, but because `False → Q` is trivially true (False implies anything), I can use the tactic `exfalso` which changes the Goal: 219 | ``` 220 | Assumptions 221 | h : P ∧ ¬P 222 | Goal: Q 223 | ``` 224 | After which `exact h.right r.left` meets the current goal. 225 | ### Apply 226 | `exfalso` is the same as `apply false_elim`. 227 | ∴ to show `Q` by `False → Q`, it suffices to show `False`. 228 | -/ 229 | TacticDoc exfalso 230 | 231 | /-- 232 | # Show a Disjunction 233 | Evidence for `P ∨ Q` can be created in two ways. `left` changes the goal to `P` while `right` changes the goal to `Q`. 234 | -/ 235 | TacticDoc left 236 | 237 | /-- 238 | # Show a Disjunction 239 | Evidence for `P ∨ Q` can be created in two ways. `left` changes the goal to `P` while `right` changes the goal to `Q`. 240 | -/ 241 | TacticDoc right 242 | -------------------------------------------------------------------------------- /Game/Game4gpt.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trequetrum/lean4game-logic/40ceec5f3ca5dce6cec2800b8f5e4927631ca2da/Game/Game4gpt.zip -------------------------------------------------------------------------------- /Game/Levels/AndIntro.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.AndIntro.L01 2 | import Game.Levels.AndIntro.L02 3 | import Game.Levels.AndIntro.L03 4 | import Game.Levels.AndIntro.L04 5 | import Game.Levels.AndIntro.L05 6 | import Game.Levels.AndIntro.L06 7 | import Game.Levels.AndIntro.L07 8 | import Game.Levels.AndIntro.L08 9 | 10 | World "AndIntro" 11 | Title "∧ Tutorial: Party Invites" 12 | 13 | Introduction 14 | " 15 | # Let the festivities commence! 16 | You're hosting your yearly soirée, and it's time to start planning! Last year your planning went so poorly that nobody showed up. Not for lack of trying though, they just wound up at a number of bewildering addresses. The silver lining was that all your friends were safe from the fire when, accidentally, you burned down your entire apartment building.\\ 17 | \\ 18 | This year will be different‼ This year, if you want to be sure that there will be fancy cheeses, you had better have evidence that somebody is bringing the cheese platter. 19 | 20 | World 1: **Party Invites** is a tutorial world that is meant to introduce you to conjunction — the logical “and”. The symbol used to denote an “and” looks like “`∧`”. You'll learn to how to use evidence to create an `∧` and also how to get evidence out when it's been `∧`ed together.\\ 21 | \\ 22 | The real-world analogues for evidence of `A ∧ B` might be a box with evidence for `A` and evidence for `B`, an audio recording with both pieces of evidence, or a tree with evidence in its branches.\\ 23 | \\ 24 | While real-world analogues can be anything, the abstract machinery used in this game will always be the same. In the case of the `∧` operator, the game stores the associated evidence in a tuple data structure.\\ 25 | \\ 26 | The details aren't important. Each level will be encoded for you into the symbols of a proof state. The puzzle, at its core, will be about symbol manipulation. Much of the text is there for added fun and flair. 27 | 28 | # **Aside**: Expressions 29 | If you're coming at this as a puzzle, part of the goal of the tutotial worlds is to teach you how to form expressions and to think about what they evaluate to. Consider how how these expressions all evaluate to the same number: 30 | ``` 31 | 4 + 6 32 | (4) + 6 33 | (4) + (6) 34 | 3 + 1 + 6 35 | 3 + (1 + 6) 36 | 4 + 4 + 2 37 | (4 * 2) + 2 38 | ``` 39 | and how some things which may look like expressions really are not: 40 | ``` 41 | 4 6 42 | 4 + 43 | 4 (++) 6 44 | (4 +) 6 45 | ``` 46 | The expressions that this game is asking you to form are mostly in prefix form. In context, this means the operation is given a textual name instead of a symbol and the parameters are separated by spaces **after** the name. For example; the above expressions may look like: 47 | ``` 48 | add 4 6 49 | add (4) 6 50 | add (4) (6) 51 | add (add 3 1) 6 52 | add 3 (add 1 6) 53 | add (add 4 4) 2 54 | add (mul 4 2) 2 55 | ``` 56 | We're not using expressions to express numbers, but many of the concepts do carry over. Instead of numbers, we're working with logical inferences. 57 | " 58 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 1 7 | Title "Exactly! It's in the premise" 8 | 9 | NewTactic exact 10 | NewDefinition GameLogic.AsciiTable 11 | OnlyTactic exact 12 | TheoremTab "∧" 13 | 14 | Introduction " 15 | # Introduction 16 | You've made a todo list, so you've begun to plan your party. 17 | ## Proposition Key: 18 | `P` — You're **P**lanning a party 19 | ## Assumptions 20 | `todo_list : P` — Can be read as “The `todo_list` is evidence that you're `P`lanning a party” 21 | # The Exact Tactic 22 | The Exact tactic is — for now — the means through which you give the game your answer. It's your way of declaring that you're done. In this level, you're just going to be using one of the assumptions directly, but as you learn how to form expressions the `exact` tactic will accept those too.\\ 23 | \\ 24 | The input will look like `exact e` where `e` is an expression the game will accept for the current Goal.\\ 25 | \\ 26 | ⋆Spoilers!⋆ If you enter “`exact todo_list`,” you will have completed this level. 27 | 28 | # Become Familiar with the User Interface! 29 | 30 | # Proof State 31 | Found in the middle bottom of the screen, the proof state tells you what objects exist, what assumptions are available, and what goal proposition you're trying to exhibit evidence for. Find the area of the screen with **Objects**, **Assumptions**, and **Goal**. I'll describe each shortly here. 32 | ## 1. Objects: 33 | In this level, you'll notice that there is only one proposition. `P : Prop` is the game's way of telling you that it knows that `P` is a proposition. You can check out the **Proposition Key** above to learn what it's denoting in this level if you're interested. 34 | ## 2. Assumptions: 35 | Most levels will give you some starting assumptions that take the form of evidence for some propositions. The shorthand for a proposition traditionally starts with a capital letter and the shorthand for evidence traditionally starts with a lowercase letter. 36 | ## 3. Goal: 37 | The goal is always a proposition that you want to exhibit some evidence for. In this level, one of your assumptions already contains evidence for the goal. That will certainly not always be the case. 38 | # Inventory 39 | On the right of the screen is your inventory of tactics, definitions, and theorems. Once unlocked, you can click them to read about what they do. 40 | " 41 | 42 | /-- Exhibit evidence that you're planning a party. -/ 43 | Statement (P : Prop)(todo_list : P) : P := by 44 | exact todo_list 45 | 46 | Conclusion " 47 | Congratulations, not only have you started your todo list, you've learned how to exhibit the list as evidence that you've started planning the party. 48 | " 49 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 2 7 | Title "And Introduction" 8 | 9 | NewTheorem GameLogic.and_intro 10 | NewDefinition GameLogic.and_def 11 | OnlyTactic exact 12 | TheoremTab "∧" 13 | 14 | Introduction " 15 | # `∧` 16 | The hat symbol “ ∧ ” is how logicians denote a conjunction — a logical “and”. `A ∧ B` means “A and B”. It works the way you would intuitively expect. Like a lot of math operators (`+,-,÷,×`,and others), the `∧` symbol is an infix operator. This means it has a left side and a right side. Looking at `A ∧ B`, you can see that `A` is on the left and `B` is on the right. 17 | 18 | # Sending Invitations in a Single Package 19 | You have two letters, one extending an invitation to Pippin and the other to Sybeth. Since they share a residence, you'd like to consolidate their invites into a single package for shipping. The box you're using has space for two items, one on the left and one on the right.\\ 20 | \\ 21 | You've labelled the box explicitly, specifying that Pippin's invitation is on the left and Sybeth's invitation is on the right. This ensures there's no confusion about the contents of the box. Upon opening it, they will easily locate their respective invites without the need to search the entire package. 22 | # Proposition Key: 23 | - P — “**P**ippin is invited to the party” 24 | - S — “**S**ybeth is invited to the party” 25 | 26 | Like the box described in the intro, any evidence for a conjunction like `A ∧ B` will have a left part and a right part. 27 | # Assumptions 28 | - `p : P` — Your invitation for Pippin is evidence that Pippin is invited to the party 29 | - `s : S` — Your invitation for Sybeth is evidence that Sybeth is invited to the party 30 | # Goal 31 | Use `p` and `s` to produce evidence that `P ∧ S`. Remember that you use evidence (generally lowercase letters), to deduce new propositions (generally uppercase letters) 32 | 33 | # Using the `∧` Construtor 34 | This level has unlocked “`∧`” under definitions. This has made the `and_intro` theorem available. You can use `and_intro` by giving it the two relevant pieces of evidence. The expression looks like: `and_intro e₁ e₂` where `e₁` and `e₂` are evidence.\\ 35 | \\ 36 | The help page has even more detail about creating conjunctions like this (There's a common shorthand using angle-brackets `⟨` `,` `⟩` ). 37 | 38 | # A reminder 39 | Use the `exact` tactic to exhibit evidence for a goal 40 | " 41 | 42 | /-- Fill a box, label correctly -/ 43 | Statement (P S : Prop)(p: P)(s : S) : P ∧ S := by 44 | Hint (hidden := true) "exact and_intro p s" 45 | exact and_intro (left := p) (right := s) 46 | 47 | 48 | Conclusion " 49 | You've got evidence that Pippin and Sybeth are invited to the party.\\ 50 | \\ 51 | Here are some answers the game would have accepted: 52 | ``` 53 | exact and_intro p s 54 | exact ⟨p,s⟩ 55 | ``` 56 | " 57 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 3 7 | Title "The Have Tactic" 8 | 9 | NewDefinition GameLogic.Precedence 10 | NewTactic «have» 11 | OnlyTactic 12 | exact 13 | «have» 14 | TheoremTab "∧" 15 | 16 | Introduction " 17 | # Too Many Invites 18 | You have invites for Alarfil, Ilyn, Orin, and Uriel who all live together. Unfortunately, boxes only have space for two items, but you've thought up a clever solution! 19 | 1. You'll put Alarfil's and Ilyn's invites in a box, 20 | 2. You'll put Orin's and Uriel's invites in another box. 21 | 3. You'll put both boxes in a final box. 22 | ### Nested Boxes! 23 | Nesting boxes like this is a way to get around the “two items per box” rule. Ensure that everything is correctly labelled to guarantee each invite reaches the correct recipient. 24 | # Proposition Key: 25 | - A — **A**larfil is invited to the party 26 | - I — **I**lyn is invited to the party 27 | - O — **O**rin is invited to the party 28 | - U — **U**riel is invited to the party 29 | 30 | # The `have` Tactic 31 | You can complete this level with your knowledge from the previous level without using this new tactic. For example, either of these would work: 32 | ``` 33 | exact and_intro (and_intro a i) (and_intro o u) 34 | exact ⟨⟨a,i⟩,⟨o,u⟩⟩ 35 | ``` 36 | Instead of nesting this way, you can break the process down into steps using the `have` tactic. Enter “`have ai := and_intro a i`” to create your first box. After it's entered, it will appear under assumptions in the proof state. Now enter “`have ou := and_intro o u`” to create the second box.\\ 37 | \\ 38 | If you followed this suggestion, your proof state should now have the following assumptions: 39 | ``` 40 | Assumptions: 41 | a: A 42 | i: I 43 | o: O 44 | u: U 45 | ai: A ∧ I 46 | ou: O ∧ U 47 | ``` 48 | \\ 49 | Finally, now you can place these two boxes — `ai` and `ou` — into a third box and submit your answer using the `exact` tactic. 50 | " 51 | 52 | /-- Three × and_intro. -/ 53 | Statement (A I O U : Prop)(a : A)(i : I)(o : O)(u : U) : (A ∧ I) ∧ O ∧ U := by 54 | have ai := and_intro a i 55 | have ou := and_intro o u 56 | Hint (hidden := true) "exact and_intro {ai} {ou}" 57 | exact and_intro ai ou 58 | 59 | example (A I O U : Prop)(a : A)(i : I)(o : O)(u : U) : (A ∧ I) ∧ O ∧ U := by 60 | exact ⟨⟨a,i⟩,⟨o,u⟩⟩ 61 | 62 | Conclusion " 63 | Great! Another 4 invites sent out. You're getting the hang of this. 64 | " 65 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 4 7 | Title "And Elimination" 8 | 9 | NewTheorem GameLogic.and_left 10 | OnlyTactic 11 | exact 12 | «have» 13 | TheoremTab "∧" 14 | 15 | Introduction " 16 | # Using Only What Is Needed 17 | Sybeth has left a voicemail on your answering machine. In it she says “Hello, it's Sybeth, I'm calling to confirm that Pippin is coming to the party and I am also coming to the party! See you then!”\\ 18 | \\ 19 | You'd like to convince Cyna to join the party. You know that Cyna is good friends with Pippin. Constructing evidence that Pippin is attending the party might just be the key to convincing Cyna to join as well. 20 | # Proposition Key: 21 | - P — \"**P**ippin is coming to the party\" 22 | - S — \"**S**ybeth is coming to the party\" 23 | # Assumptions: 24 | - `vm : P ∧ S` — The voicemail (`vm`) is evidence that (`P ∧ S`) Pippin and Sybeth are coming to the party. 25 | # Convincing Cyna 26 | Cyna is close with Pippin, but you don't know much about his friendship with Sybeth. You want only a relevant part of the voicemail. Fortunately, you know that any evidence with an `∧` has a left part and a right part. You can use this knowledge to pull evidence out of `vm`.\\ 27 | \\ 28 | This can be done with either of these two methods: 29 | ``` 30 | have p := and_left vm 31 | have p := vm.left 32 | ``` 33 | Once you've done this, you're very close to level 1 again where the Goal is directly in your assumptions. 34 | " 35 | 36 | /-- Exhibit evidence that Pippin is coming to the party. -/ 37 | Statement (P S : Prop)(vm: P ∧ S) : P := by 38 | have p := and_left vm 39 | exact p 40 | 41 | example (P S : Prop)(vm: P ∧ S) : P := by 42 | have p := vm.left 43 | exact p 44 | 45 | Conclusion " 46 | You've got a proof that Pippin is coming to the party! Lets see if Cyna will attend as well. 47 | 48 | ---- 49 | A reminder that expressions work with the `have` and `exact` tactics in much the same way. You can also solve this level without `have`. 50 | ```lean 51 | exact vm.left 52 | ``` 53 | " 54 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 5 7 | Title "And Elimination 2" 8 | 9 | NewTheorem GameLogic.and_right 10 | OnlyTactic 11 | exact 12 | «have» 13 | TheoremTab "∧" 14 | 15 | Introduction " 16 | # Another Unlock 17 | Can you figure this one out? 18 | " 19 | 20 | /-- Both P and Q entails just Q as well! -/ 21 | Statement (P Q : Prop)(h: P ∧ Q) : Q := by 22 | Hint (hidden := true) "`exact h.right`" 23 | exact and_right h 24 | 25 | Conclusion " 26 | Nice. Onward! 27 | 28 | ---- 29 | ``` 30 | exact h.right 31 | ``` 32 | ---- 33 | ``` 34 | exact and_right h 35 | ``` 36 | " 37 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 6 7 | Title "Mix and Match" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | TheoremTab "∧" 13 | 14 | Introduction " 15 | # Mixed Up Conjunctions 16 | Recall when you placed the invites for Alarfil, Ilyn, Orin, and Uriel in separate boxes. There was a mix-up in the arrangement. Can you fix it so that Alarfil and Uriel's invites are together? 17 | # Proposition Key: 18 | - A — **A**larfil is invited to the party 19 | - I — **I**lyn is invited to the party 20 | - O — **O**rin is invited to the party 21 | - U — **U**riel is invited to the party 22 | " 23 | 24 | /-- and_intro, and_left, and_right -/ 25 | Statement (A I O U : Prop)(h1 : A ∧ I)(h2 : O ∧ U) : A ∧ U := by 26 | have a := h1.left 27 | have u := h2.right 28 | exact and_intro a u 29 | 30 | example (A I O U : Prop)(h1 : A ∧ I)(h2 : O ∧ U) : A ∧ U := by 31 | exact and_intro h1.left h2.right 32 | 33 | example (A I O U : Prop)(h1 : A ∧ I)(h2 : O ∧ U) : A ∧ U := by 34 | exact and_intro (and_left h1) (and_right h2) 35 | 36 | example (A I O U : Prop)(h1 : A ∧ I)(h2 : O ∧ U) : A ∧ U := by 37 | exact ⟨h1.left, h2.right⟩ 38 | 39 | Conclusion " 40 | That's better, but you'd better send out those invites so you can get some responses! 41 | 42 | ---- 43 | ``` 44 | have a := h1.left 45 | have u := h2.right 46 | exact and_intro a u 47 | ``` 48 | --- 49 | ``` 50 | exact and_intro h1.left h2.right 51 | ``` 52 | ---- 53 | ``` 54 | exact and_intro (and_left h1) (and_right h2) 55 | ``` 56 | --- 57 | ``` 58 | exact ⟨h1.left, h2.right⟩ 59 | ``` 60 | " 61 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 7 7 | Title "More Elimination" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | TheoremTab "∧" 13 | 14 | Introduction " 15 | # An Emergency! 16 | Stop with the invites! Coco, your cat is stuck up a tree.\\ 17 | \\ 18 | The firefighters are here but they need your help figuring if they can get to the branch with Coco. They can see the entire tree and they know how to follow the branches by going left or right at each intersection. If they can do that, then they must be able to get to Coco. 19 | # Proposition Key: 20 | - C — The firefighters can get to Coco 21 | - L — The firefighters can get to some leaves 22 | # Goal 23 | Show evidence that the firefighters can get to Coco 24 | 25 | # `∧` associates to the right 26 | Check out the definition page for \"Precedence\" to learn a bit more. 27 | # Hint # 1 28 | If you hover your mouse over an operator in the proof state it will highlight the part of the expression that it operates **on**. Find the one that highlights the entire expression to see where the trunk of the tree is.\\ 29 | \\ 30 | Another approach is trial and error. Enter `have h₁ := h.right` to see `h₁: (L ∧ L) ∧ L` appear in your assumptions, which doesn't have `C` anywhere, indicating that this isn't the correct part of the tree. You can hit retry, then change that line to have `h₁ := h.left`. Then your assumptions will have `h₁: L ∧ ((L ∧ C) ∧ L) ∧ L ∧ L ∧ L`, which has the `C` somewhere. 31 | # Hint # 2 32 | The |**Show more help!**| button below will display the expression for you. Beware, the boss level will not come with this option. 33 | " 34 | 35 | /-- Navigate the tree -/ 36 | Statement (C L : Prop)(h: (L ∧ (((L ∧ C) ∧ L) ∧ L ∧ L ∧ L)) ∧ (L ∧ L) ∧ L) : C := by 37 | Hint (hidden := true) "h.left.right.left.left.right" 38 | exact h.left.right.left.left.right 39 | 40 | Conclusion " 41 | Amazing! You've helped save your cat! 42 | 43 | ---- 44 | Here are three solutions, are you able to follow each of them? 45 | ``` 46 | have h₁ := and_left h 47 | have h₂ := and_right h₁ 48 | have h₃ := and_left h₂ 49 | have h₄ := and_left h₃ 50 | have h₅ := and_right h₄ 51 | exact h₅ 52 | ``` 53 | or 54 | ``` 55 | exact and_right (and_left (and_left (and_right (and_left h)))) 56 | ``` 57 | or 58 | ``` 59 | exact h.left.right.left.left.right 60 | ``` 61 | " 62 | -------------------------------------------------------------------------------- /Game/Levels/AndIntro/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndIntro" 6 | Level 8 7 | Title "Rearranging Boxes" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | TheoremTab "∧" 13 | 14 | Introduction " 15 | # BOSS LEVEL 16 | If you can finish this level, you've certainly mastered the `∧`. There's no deep logical tricks in this boss level, you've just got to know how to work at properly unnesting and then building the right Proposition.\\ 17 | \\ 18 | Using the `have` tactic, you can break this task down into digestible chunks. The top right of the screen has a button that toggles between editor mode and typewriter mode. Editor mode is often a bit easier to work with as it allows you to enter multi-line expressions or edit earlier lines seamlessly. While in editor mode, the proof state will change depending on which line your caret is on. 19 | # Rearranging Boxes 20 | Finally, a bunch of your invites have returned with RSVPs. The mailman has delivered them in a big box. Make a list of the expected attendees so far. 21 | # Proposition Key: 22 | - A — **A**larfil is coming to the party 23 | - C — **C**yna** is coming to the party 24 | - I — **I**lyn** is coming to the party 25 | - O — **O**rin** is coming to the party 26 | - P — **P**ippin** is coming to the party 27 | - S — **S**ybeth** is coming to the party 28 | - U — **U**riel** is coming to the party 29 | " 30 | 31 | /-- Take apart and build evidence -/ 32 | Statement (A C I O P S U : Prop)(h: ((P ∧ S) ∧ A) ∧ ¬I ∧ (C ∧ ¬O) ∧ ¬U) : A ∧ C ∧ P ∧ S := by 33 | have psa := h.left 34 | have c := h.right.right.left.left 35 | have cps := and_intro c psa.left 36 | exact and_intro psa.right cps 37 | 38 | Conclusion " 39 | Amazing! You've mastered \"AND\". 40 | 41 | --- 42 | ``` 43 | -- 3/4 of the things you need are one step away 44 | have psa := h.left 45 | 46 | -- Evidence for C takes some digging 47 | have c := h.right.right.left.left 48 | 49 | -- build C ∧ P ∧ S 50 | have cps := and_intro c psa.left 51 | 52 | -- exibit A ∧ C ∧ P ∧ S 53 | exact and_intro psa.right cps 54 | ``` 55 | " 56 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.AndTactic.L01 2 | import Game.Levels.AndTactic.L02 3 | import Game.Levels.AndTactic.L03 4 | import Game.Levels.AndTactic.L04 5 | import Game.Levels.AndTactic.L05 6 | import Game.Levels.AndTactic.L06 7 | import Game.Levels.AndTactic.L07 8 | import Game.Levels.AndTactic.L08 9 | 10 | World "AndTactic" 11 | Title "Redux: ∧ World Tactics" 12 | 13 | Introduction " 14 | # Redux: ∧ World Tactics 15 | Welcome to the redux of the **∧ Tutorial World**. Every level is the same, but instead of solving each level with `have` and `exact`, this world opens up a bunch of custom tactics.\\ 16 | \\ 17 | This world introduces tactics that you can use in lieu of the expressions you've learned so far. 18 | " 19 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 1 7 | Title "Assumption" 8 | 9 | NewTactic assumption 10 | 11 | OnlyTactic assumption 12 | 13 | Introduction " 14 | # `assumption` 15 | If the evidence you want is in your list of **Assumptions**, the `assumption` tactic will finish the level for you. 16 | " 17 | 18 | /-- Use the assumption tactic -/ 19 | Statement (P : Prop)(h'₁ : P) : P := by 20 | assumption 21 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 2 7 | Title "Constructor" 8 | 9 | NewTactic constructor 10 | 11 | OnlyTactic 12 | assumption 13 | constructor 14 | 15 | Introduction " 16 | # Constructor 17 | Whenever there's a clear way to create new evidence **that matches the goal**, the constructor tactic will pick that for you. This replaces the current goal with one or more goals that together complete the construction.\\ 18 | \\ 19 | For this level, your goal is `P ∧ Q`. The `constructor` tactic will replace that goal with two separate subgoals. This is likely the first time you've seen two goals in your proof state. First you'll need to show evidence for `P`, then you'll need to show evidence for `Q`. 20 | " 21 | 22 | /-- Use the constructor tactic -/ 23 | Statement (P Q : Prop)(h'₁ : P)(h'₂ : Q) : P ∧ Q := by 24 | constructor 25 | assumption 26 | assumption 27 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 3 7 | Title "Practise Makes Perfect" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | 13 | Introduction " 14 | # Practise Makes Perfect 15 | You only have `assumption` and `constructor` available to you. Figure out the order you need to enter them in. 16 | " 17 | 18 | /-- Repeat constructor/assumption until you're done -/ 19 | Statement (P Q R S : Prop)(h'₁ : P)(h'₂ : Q)(h'₃ : R)(h'₄ : S) : (P ∧ Q) ∧ R ∧ S := by 20 | constructor 21 | constructor 22 | assumption 23 | assumption 24 | constructor 25 | assumption 26 | assumption 27 | 28 | 29 | /-- Tactic Proof -/ 30 | example (P Q R S : Prop)(h'₁ : P)(h'₂ : Q)(h'₃ : R)(h'₄ : S) : (P ∧ Q) ∧ R ∧ S := by 31 | repeat (first | constructor | assumption) 32 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 4 7 | Title "Cases for a Conjunction" 8 | 9 | NewTactic cases 10 | 11 | OnlyTactic 12 | assumption 13 | cases 14 | 15 | Introduction " 16 | # `cases` for a Conjunction 17 | Here, we introduce the `cases` tactic in an unstructured context. `cases` takes a name from the local context and either splits it into multiple goals, or deconstructs it into its parts depending on the Proposition.\\ 18 | \\ 19 | In this level, `cases h` will replace `h` with its `left` and `right` parts. Try it out. 20 | " 21 | 22 | /-- P and Q implies P -/ 23 | Statement (P Q : Prop)(h: P ∧ Q) : P := by 24 | cases h 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 5 7 | Title "Rinse and Repeat" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | 13 | Introduction " 14 | # Rinse and Repeat 15 | When you were writing expressions before, level 4 needed `and_left` while level 5 needed `and_right`. Tactics can incorporate an arbitrary amount of automation. In this case, because `assumption` will do a search through your assumptions for you, your proof for this level can be 100% identical to the one you used in the last level. 16 | " 17 | 18 | /-- Both P and Q entails just Q as well! -/ 19 | Statement (P Q : Prop)(h: P ∧ Q) : Q := by 20 | cases h 21 | assumption 22 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 6 7 | Title "Nothing New" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | 14 | Introduction " 15 | # Nothing New 16 | Just use what you've been taught. 17 | " 18 | 19 | /-- Combining your new tactics -/ 20 | Statement (P Q R S : Prop)(h1 : P ∧ Q)(h2 : R ∧ S) : P ∧ S := by 21 | cases h1 22 | cases h2 23 | constructor 24 | assumption 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 7 7 | Title "More Cases" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | 13 | Introduction " 14 | # So Many Cases 15 | Just keep picking the right hypothesis. 16 | " 17 | 18 | /-- Navigate the tree -/ 19 | Statement (P Q : Prop)(h: (Q ∧ (((Q ∧ P) ∧ Q) ∧ Q ∧ Q ∧ Q)) ∧ (Q ∧ Q) ∧ Q) : P := by 20 | cases h 21 | cases left 22 | cases right_1 23 | cases left 24 | cases left_2 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/AndTactic/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "AndTactic" 6 | Level 8 7 | 8 | Title "And Tactic Boss" 9 | 10 | OnlyTactic 11 | assumption 12 | constructor 13 | cases 14 | 15 | Introduction " 16 | # BOSS LEVEL 17 | The trick for this level is to pull out the relevant info from the assumption `h` before moving on, otherwise you'll need to re-do that work for each sub-goal. 18 | 19 | I use the `cases` tactic 5 times and for me, they're the first five tactics for this level. 20 | " 21 | 22 | /-- Take apart and build evidence -/ 23 | Statement (A C I O P S U : Prop)(h: ((P ∧ S) ∧ A) ∧ ¬I ∧ (C ∧ ¬O) ∧ ¬U) : A ∧ C ∧ P ∧ S := by 24 | cases h 25 | cases left 26 | cases right 27 | cases right_2 28 | cases left_2 29 | constructor 30 | assumption 31 | constructor 32 | assumption 33 | assumption 34 | 35 | Conclusion " 36 | Amazing! You've beaten the `∧` world a second time and you've learned some extra tactics in the process. 37 | " 38 | -------------------------------------------------------------------------------- /Game/Levels/ExSyl.lean: -------------------------------------------------------------------------------- 1 | -- import Game.Metadata 2 | -- import GameServer.Commands 3 | 4 | -- import Game.Doc.Lemmas 5 | -- import Game.Doc.Definitions 6 | -- import Game.Doc.Tactics 7 | 8 | -- Paul ↦ Pippin 9 | -- Sarah ↦ Sybeth 10 | -- Jason ↦ Alarfil 11 | -- Jaime ↦ Ilyn 12 | -- Jordan ↦ Orin 13 | -- Justin ↦ Uriel 14 | -- Alan ↦ Fredu ↦ Cyna 15 | -- Robbie ↦ Riffin 16 | -- Bella ↦ Bella 17 | -- Bert ↦ Lippa 18 | 19 | theorem imp_imp_imp {a b c d : Prop} (h₀ : c → a) (h₁ : b → d) : (a → b) → (c → d) := (h₁ ∘ · ∘ h₀) 20 | 21 | theorem imp.swap : (a → b → c) ↔ (b → a → c) := ⟨flip, flip⟩ 22 | 23 | theorem imp_not_comm : (a → ¬b) ↔ (b → ¬a) := imp.swap 24 | 25 | theorem And.imp (f : a → c) (g : b → d) (h : a ∧ b) : c ∧ d := ⟨f h.1, g h.2⟩ 26 | 27 | theorem and_congr_right (h : a → (b ↔ c)) : a ∧ b ↔ a ∧ c := 28 | ⟨fun ⟨ha, hb⟩ => ⟨ha, (h ha).1 hb⟩, fun ⟨ha, hb⟩ => ⟨ha, (h ha).2 hb⟩⟩ 29 | 30 | 31 | theorem and_left_comm : a ∧ (b ∧ c) ↔ b ∧ (a ∧ c) := by 32 | -- rw [← and_assoc, ← and_assoc, @and_comm a b] 33 | sorry 34 | 35 | theorem and_right_comm : (a ∧ b) ∧ c ↔ (a ∧ c) ∧ b := by 36 | simp only [and_left_comm, and_comm] 37 | 38 | theorem and_rotate : a ∧ b ∧ c ↔ b ∧ c ∧ a := by 39 | simp only [and_left_comm, and_comm] 40 | 41 | theorem and_not_self : ¬(a ∧ ¬a) | ⟨ha, hn⟩ => hn ha 42 | 43 | 44 | theorem not_not_em (a : Prop) : ¬¬(a ∨ ¬a) := fun h => h (.inr (h ∘ .inl)) 45 | 46 | theorem Or.symm : a ∨ b → b ∨ a := .rec .inr .inl 47 | 48 | theorem Or.imp (f : a → c) (g : b → d) (h : a ∨ b) : c ∨ d := h.elim (inl ∘ f) (inr ∘ g) 49 | 50 | theorem Or.imp_left (f : a → b) : a ∨ c → b ∨ c := .imp f id 51 | 52 | theorem Or.imp_right (f : b → c) : a ∨ b → a ∨ c := .imp id f 53 | 54 | theorem or_congr (h₁ : a ↔ c) (h₂ : b ↔ d) : (a ∨ b) ↔ (c ∨ d) := ⟨.imp h₁.1 h₂.1, .imp h₁.2 h₂.2⟩ 55 | 56 | theorem or_congr_left (h : a ↔ b) : a ∨ c ↔ b ∨ c := or_congr h .rfl 57 | 58 | theorem or_congr_right (h : b ↔ c) : a ∨ b ↔ a ∨ c := or_congr .rfl h 59 | 60 | theorem Or.comm : a ∨ b ↔ b ∨ a := ⟨Or.symm, Or.symm⟩ 61 | 62 | theorem or_comm : a ∨ b ↔ b ∨ a := Or.comm 63 | 64 | theorem or_assoc : (a ∨ b) ∨ c ↔ a ∨ (b ∨ c) := 65 | ⟨.rec (.imp_right .inl) (.inr ∘ .inr), .rec (.inl ∘ .inl) (.imp_left .inr)⟩ 66 | 67 | -- OR 68 | 69 | theorem Or.resolve_left {a b : Prop} (h: a ∨ b) (na : ¬a) : b := h.elim (absurd · na) id 70 | 71 | theorem Or.neg_resolve_left (h : ¬a ∨ b) (ha : a) : b := h.elim (absurd ha) id 72 | 73 | theorem Or.resolve_right {a b : Prop} (h: a ∨ b) (nb : ¬b) : a := h.elim id (absurd · nb) 74 | 75 | theorem Or.neg_resolve_right (h : a ∨ ¬b) (nb : b) : a := h.elim id (absurd nb) 76 | 77 | theorem or_left_comm : a ∨ (b ∨ c) ↔ b ∨ (a ∨ c) := by rw [← or_assoc, ← or_assoc, @or_comm a b] 78 | 79 | theorem or_right_comm : (a ∨ b) ∨ c ↔ (a ∨ c) ∨ b := by rw [or_assoc, or_assoc, @or_comm b] 80 | 81 | theorem or_iff_right_of_imp (ha : a → b) : (a ∨ b) ↔ b := ⟨Or.rec ha id, .inr⟩ 82 | 83 | theorem not_or_intro {a b : Prop} (ha : ¬a) (hb : ¬b) : ¬(a ∨ b) := (·.elim ha hb) 84 | 85 | theorem or_iff_left_iff_imp : (a ∨ b ↔ a) ↔ (b → a) := 86 | ⟨fun h hb => h.1 (Or.inr hb), or_iff_left_of_imp⟩ 87 | 88 | -- distributivity 89 | 90 | theorem and_imp : (a ∧ b → c) ↔ (a → b → c) := 91 | ⟨fun h ha hb => h ⟨ha, hb⟩, fun h ⟨ha, hb⟩ => h ha hb⟩ 92 | 93 | @[simp] theorem not_and : ¬(a ∧ b) ↔ (a → ¬b) := and_imp 94 | 95 | namespace NatB 96 | 97 | inductive ℕb where 98 | | One: ℕb 99 | | T2: ℕb → ℕb 100 | | P1: ℕb → ℕb 101 | 102 | inductive ℕ where 103 | | Zero: ℕ 104 | | Binary: ℕb → ℕ 105 | 106 | open ℕb 107 | 108 | def mkString : ℕb → String 109 | | One => "1" 110 | | T2 n => mkString n ++ "0" 111 | | P1 n => mkString n ++ "1" 112 | 113 | instance : ToString ℕb where 114 | toString: ℕb → String := mkString 115 | 116 | instance : ToString ℕ where 117 | toString: ℕ → String 118 | | ℕ.Zero => "0" 119 | | ℕ.Binary n => s!"{n}" 120 | 121 | def mkNat' : ℕb → Nat 122 | | One => 1 123 | | T2 n => mkNat' n * 2 124 | | P1 n => mkNat' n * 2 + 1 125 | 126 | def mkNat : ℕ → Nat 127 | | ℕ.Zero => 0 128 | | ℕ.Binary n => mkNat' n 129 | 130 | #eval mkNat $ ℕ.Binary $ ℕb.T2 $ ℕb.P1 $ ℕb.P1 ℕb.One 131 | 132 | def plusℕb: ℕb → ℕb → ℕb 133 | | One, One => T2 One 134 | | One, T2 y => sorry 135 | | One, P1 y => sorry 136 | | T2 x, y => sorry 137 | | P1 x, y => sorry 138 | 139 | -- 1 + (2 * y) 140 | -- = 141 | def plus: ℕ → ℕ → ℕ 142 | | ℕ.Zero, x => x 143 | | x, ℕ.Zero => x 144 | | ℕ.Binary x, ℕ.Binary y => match x, y with 145 | | a, b => sorry 146 | 147 | def succ : ℕb → ℕb 148 | | One => T2 One 149 | | T2 n => P1 n 150 | | P1 n => succ n 151 | 152 | end NatB 153 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.IffIntro.L01 2 | import Game.Levels.IffIntro.L02 3 | import Game.Levels.IffIntro.L03 4 | import Game.Levels.IffIntro.L04 5 | import Game.Levels.IffIntro.L05 6 | import Game.Levels.IffIntro.L06 7 | import Game.Levels.IffIntro.L07 8 | 9 | World "IffIntro" 10 | Title "↔ Tutorial: Party Games" 11 | 12 | Introduction " 13 | # Party Games 14 | A soirée requires a diligent and clear-headed host if the guests are to have fun. It's your job to ensure everybody is having a good time. Let the games begin! 15 | 16 | # Propositional Equivalence 17 | ## Biconditional: If and only if 18 | The `↔` operator doesn't introduce anything new. `P ↔ Q` can be constructed whenever evidence for both `P → Q` and `Q → P` is available. In general, if `h₁` and `h₂` are evidence as follows: 19 | ``` 20 | h₁ : P ↔ Q 21 | h₂ : (P → Q) ∧ (Q → P) 22 | ``` 23 | Then `h₁` and `h₂` have roughly the same capabilities. Also, proof of either P or Q or ¬P or ¬Q, immediately translates to the same proof for the logically equivalent proposition. The following levels will explain the details.\\ 24 | \\ 25 | The main new idea introduced in this tutorial world is the `rw` tactic. The *rewrite* tactic offers a principled way to alter the current goal or available hypotheses of your current proof state. 26 | " 27 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 1 7 | Title "Iff_Intro" 8 | 9 | NewTheorem GameLogic.iff_intro 10 | NewDefinition GameLogic.iff_def 11 | OnlyTactic 12 | exact 13 | «have» 14 | 15 | Introduction " 16 | # Coupled Conditionals 17 | Sybeth and Alarfil are a couple. In effect, this means that if Sybeth is playing a party game, then Alarfil is playing too and vice versa. Therefore Sybeth is playing Charades if and only if Alarfil playing. 18 | # Proposition Key: 19 | - J — Alarfil is playing Charades 20 | - S — Sybeth is playing Charades 21 | # Unlocked `↔ intro` 22 | Assuming `e₁ : P → Q` and `e₂ : Q → P`, you can introduce a biconditional with `have h := iff_intro e₁ e₂`, though the anonymous constructor syntax works just like it does for conjunctions: `have h : P ↔ Q := ⟨e₁, e₂⟩` 23 | " 24 | 25 | /-- Statement -/ 26 | Statement (J S : Prop) (hsj: S → J) (hjs: J → S) : S ↔ J := by 27 | exact iff_intro hsj hjs 28 | 29 | example (J S : Prop) (hsj: S → J) (hjs: J → S) : S ↔ J := by 30 | exact ⟨hsj, hjs⟩ 31 | 32 | Conclusion " 33 | Onward and upward 34 | 35 | ---- 36 | ``` 37 | exact iff_intro hsj hjs 38 | ``` 39 | --- 40 | ``` 41 | exact ⟨hsj, hjs⟩ 42 | ``` 43 | " 44 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 2 7 | Title "Conjunctive Iff" 8 | 9 | NewTheorem 10 | GameLogic.iff_mp 11 | GameLogic.iff_mpr 12 | OnlyTactic 13 | exact 14 | «have» 15 | 16 | Introduction " 17 | # Two sides to every coin 18 | You're flipping a coin to decide which team gets to guess first in *Salad Bowl*. Heads means blue team and tails means purple team. Even though you're on the purple team, you're secretly hoping it comes up heads. 19 | # Proposition Key: 20 | - B — Blue Team goes first 21 | - P — Purple Team goes First 22 | # Unlocked `iff_mp` and `iff_mpr` 23 | For a biconditional like `h : P ↔ Q`, 24 | 1. You can extract `P → Q` using `iff_mp h` or `h.mp`. `mp` here is short of modus ponens. 25 | 2. You can extra `Q → P` using `iff_mpr h` or `h.mpr`. `mpr` here is short of modus ponens reversed. 26 | " 27 | 28 | /-- Statement -/ 29 | Statement (B P : Prop) (h : B ↔ ¬P) : (B → ¬P) ∧ (¬P → B) := by 30 | exact and_intro (iff_mp h) (iff_mpr h) 31 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 3 7 | Title "Iff_mp" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Right-Hand Swap 15 | You'll need h1.mp in order to turn evidence for Q into evidence for R 16 | " 17 | 18 | /-- Statement -/ 19 | Statement (P Q R : Prop) (h1 : Q ↔ R)(h2 : P → Q) : P → R := by 20 | exact h2 ≫ h1.mp 21 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 4 7 | Title "Iff_Intro" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Left-Hand Swap 15 | You'll need `h1.mpr` in order to turn evidence for R into evidence for P 16 | " 17 | 18 | /-- Statement -/ 19 | Statement (P Q R : Prop) (h1 : P ↔ R)(h2 : P → Q) : R → Q := by 20 | exact h1.mpr ≫ h2 21 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 5 7 | Title "Rewriting" 8 | 9 | NewTactic rw 10 | NewHiddenTactic nth_rewrite 11 | OnlyTactic 12 | exact 13 | «have» 14 | rw 15 | «repeat» 16 | nth_rewrite 17 | 18 | Introduction " 19 | # Rewrite the goal 20 | ## To an extreme 21 | You're playing a round of *The Resistance*. You've been diligently figuring out who is a member of the resistance and who is a spy. It's a bit complicated, but you've reasoned that the current state of affairs is possible if and only if Alarfil and Cyna are members of the resistance and Pippin is a spy. You also know from an earlier reveal that Pippin and Lippa must be on the same team. Therefore, the same argument applies to Lippa as well. 22 | # Proposition Key: 23 | - A — Alarfil is a member of the resistance 24 | - C — Cyna is a member of the resistance 25 | - L — Lippa is a member of the resistance 26 | - P — Pippin is a member of the resistance 27 | # The `rw` tactic 28 | Depending on the order you've chosen to do the tutorial worlds, you may not yet be familiar with all the symbols in the current proof state. In fact, you may not even have unlocked the tools in your inventory necessary to complete this level. Don't worry, the rewrite tactic makes this level a breeze.\\ 29 | \\ 30 | First, take a look at the proof state and notice that `h₂` and the Goal are extremely similar. In fact, if you replace every occurrence of `L` with `P`, then the two would be identical. That's where `rw` comes in. 31 | \\ 32 | If you have `hpq : P ↔ Q` then `rw [hpq]` will change all `P`s in the goal to `Q`s. It's the way to “substitute in”.\\ 33 | \\ 34 | The `rw` tactic is an automation tool. It does the rote work of taking apart an expression, looking for a matching sub-expression, swapping in the equivalent sub-expression and then constructing the new equivalent term from there.\\ 35 | \\ 36 | Try it out! 37 | " 38 | 39 | /-- Who is loyal, who is a spy? -/ 40 | Statement 41 | (A C L P : Prop) 42 | (h1 : L ↔ P) 43 | (h2 : ¬((A → C ∨ ¬P) ∧ (P ∨ A → ¬C) → (P → C)) ↔ A ∧ C ∧ ¬P) 44 | : ¬((A → C ∨ ¬L) ∧ (L ∨ A → ¬C) → (L → C)) ↔ A ∧ C ∧ ¬L := by 45 | rw [h1] 46 | exact h2 47 | 48 | example 49 | (A C L P : Prop) 50 | (h1 : L ↔ P) 51 | (h2 : ¬((A → C ∨ ¬P) ∧ (P ∨ A → ¬C) → (P → C)) ↔ A ∧ C ∧ ¬P) 52 | : ¬((A → C ∨ ¬L) ∧ (L ∨ A → ¬C) → (L → C)) ↔ A ∧ C ∧ ¬L := by 53 | rw [← h1] at h2 54 | exact h2 55 | 56 | example 57 | (A C L P : Prop) (h1 : L ↔ P) 58 | (h2 : ¬((A → C ∨ ¬P) ∧ (P ∨ A → ¬C) → (P → C)) ↔ A ∧ C ∧ ¬P) 59 | : ¬((A → C ∨ ¬L) ∧ (L ∨ A → ¬C) → (L → C)) ↔ A ∧ C ∧ ¬L := 60 | ⟨ 61 | λh3 ↦ have ⟨a,c,np⟩ := h2.mp ( 62 | λh₄ ↦ h3 (λ⟨hl₅,hr₅⟩ l ↦ h₄ ⟨ 63 | λa ↦ or_elim 64 | (hl₅ a) 65 | or_inl 66 | (imp_trans h1.mpr ≫ or_inr) 67 | , 68 | λ_ ↦ hr₅ (or_inl l) 69 | ⟩ (h1.mp l)) 70 | ) 71 | ⟨a, c, h1.mp ≫ np⟩ 72 | , 73 | λ⟨a,c,nl⟩ _ ↦ false_elim ( 74 | h2.mpr 75 | ⟨a, c, h1.mpr ≫ nl⟩ 76 | λ_ _ ↦ c 77 | ) 78 | ⟩ 79 | 80 | Conclusion " 81 | Oh, how nice! A fast solution to a complex problem. 82 | 83 | ---- 84 | If you've read the inventory page for `rw`, you may have seen another solution too. You can use the backwards arrow “`←`” to change `P`s to `L`s instead of `L`s to `P`s. Also, you can change a hypothesis instead of the goal. 85 | ``` 86 | rw [← h₁] at h₂ 87 | exact h₂ 88 | ``` 89 | 90 | Here's an example of what this looks like without the `rw` tactic — if you want to try this solution, copy & paste the following text to the editor input mode. 91 | ``` 92 | exact ⟨ 93 | λh₃ ↦ have ⟨a,c,np⟩ := h₂.mp ( 94 | λh₄ ↦ h₃ (λ⟨hl₅,hr₅⟩ l ↦ h₄ ⟨ 95 | λa ↦ or_elim 96 | (hl₅ a) 97 | or_inl 98 | (imp_trans h₁.mpr ≫ or_inr) 99 | , 100 | λ_ ↦ hr₅ (or_inl l) 101 | ⟩ (h₁.mp l)) 102 | ) 103 | ⟨a, c, h₁.mp ≫ np⟩ 104 | , 105 | λ⟨a,c,nl⟩ _ ↦ false_elim ( 106 | h₂.mpr 107 | ⟨a, c, h₁.mpr ≫ nl⟩ 108 | λ_ _ ↦ c 109 | ) 110 | ⟩ 111 | ``` 112 | The thing to notice here is that this long-form solution needs both `h₁.mp` and `h₁.mpr`. Keep in mind that though it’s often tempting to try to use conditionals (`→`), rewrite **requires** a biconditional (`↔`) to work. 113 | " 114 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | namespace GameLogic 4 | 5 | World "IffIntro" 6 | Level 6 7 | Title "Rewriting" 8 | 9 | NewTheorem 10 | GameLogic.and_assoc 11 | GameLogic.or_assoc 12 | OnlyTactic 13 | exact 14 | «have» 15 | rw 16 | «repeat» 17 | nth_rewrite 18 | 19 | Introduction " 20 | # Keep rewriting! 21 | It's another round of the resistance! This time you know that if you could prove that Alarfil, Cyna, or Lippa are part of the resistance, then that would be good enough to show that least one of the other two are spies.\\ 22 | \\ 23 | It doesn't really matter how your friends are paired up, the same truth will hold. 24 | # Proposition Key: 25 | - A — Alarfil is a member of the resistance 26 | - C — Cyna is a member of the resistance 27 | - L — Lippa is a member of the resistance 28 | ## New Theorems 29 | Two new theorems have been unlocked for this level. We'll make you prove them later — though I imagine they make a certain amount of intuitive sense regardless. 30 | 1. `or_assoc : P ∨ Q ∨ R ↔ (P ∨ Q) ∨ R` 31 | 2. `and_assoc : P ∧ Q ∧ R ↔ (P ∧ Q) ∧ R` 32 | ## A Challenge 33 | You'll likely see the way this can be solved using the `rw` tactic. In this case, there's a nice and short proof that doesn't use `rw`. If you want the extra challenge, see if you can see it. 34 | " 35 | 36 | /-- Exactly 2 rewrites -/ 37 | Statement (P Q R : Prop) (h : P ∨ Q ∨ R → ¬(P ∧ Q ∧ R)) : (P ∨ Q) ∨ R → ¬((P ∧ Q) ∧ R) := by 38 | rw [or_assoc, and_assoc] 39 | exact h 40 | 41 | example (P Q R : Prop) (h : P ∨ Q ∨ R → ¬(P ∧ Q ∧ R)) : (P ∨ Q) ∨ R → ¬((P ∧ Q) ∧ R) := by 42 | exact or_assoc.mp ≫ h ≫ imp_trans and_assoc.mp 43 | 44 | Conclusion " 45 | ``` 46 | rw [or_assoc] 47 | rw [and_assoc] 48 | exact h 49 | ``` 50 | ---- 51 | ``` 52 | rw [or_assoc, and_assoc] 53 | exact h 54 | ``` 55 | ---- 56 | Without the `rw` tactic 57 | ``` 58 | exact or_assoc.mp ≫ h ≫ imp_trans and_assoc.mp 59 | ``` 60 | " 61 | -------------------------------------------------------------------------------- /Game/Levels/IffIntro/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | namespace GameLogic 4 | 5 | World "IffIntro" 6 | Level 7 7 | Title "IffBoss" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # BOSS LEVEL 15 | This is an involved level. It doesn't require you to do anything tricky, but there are a lot of moving parts and it is easy to lose track of what you're doing.\\ 16 | \\ 17 | I recommend editor mode. Think about what this is asking you to prove and use `have` to give yourself any auxiliary facts that don't exist under your theorems. 18 | " 19 | 20 | Statement (P Q R : Prop): (P ∧ Q ↔ R ∧ Q) ↔ Q → (P ↔ R) := by 21 | exact ⟨ 22 | λ⟨pr,rp⟩ q ↦ ⟨λp ↦ (pr ⟨p,q⟩).left, λr ↦ (rp ⟨r,q⟩).left⟩, 23 | λqpr ↦ ⟨λ⟨p,q⟩ ↦ ⟨(qpr q).mp p, q⟩, λ⟨r,q⟩ ↦ ⟨(qpr q).mpr r, q⟩⟩ 24 | ⟩ 25 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.IffTactic.L01 2 | import Game.Levels.IffTactic.L02 3 | import Game.Levels.IffTactic.L03 4 | import Game.Levels.IffTactic.L04 5 | import Game.Levels.IffTactic.L05 6 | import Game.Levels.IffTactic.L06 7 | import Game.Levels.IffTactic.L07 8 | 9 | World "IffTactic" 10 | Title "Redux: ↔ World Tactics" 11 | 12 | Introduction " 13 | # Redux: ↔ World Tactics 14 | Welcome to the redux of the **↔ Tutorial World**. Every level is the same, but instead of solving each level with `have` and `exact`, this world opens up a bunch of custom tactics.\\ 15 | \\ 16 | This world introduces tactics that you can use in lieu of the expressions you've learned so far. 17 | " 18 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffTactic" 6 | Level 1 7 | Title "Iff_Intro" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | «repeat» 13 | 14 | Introduction " 15 | # Using Tactics 16 | The tactics available to you should provide a hint for what to do. You'll be using a tactic in a context you haven't tried before, but the result should make sense once you try it and see what happens. 17 | " 18 | 19 | Statement (P Q : Prop) (hsj: Q → P) (hjs: P → Q) : Q ↔ P := by 20 | constructor 21 | repeat assumption 22 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffTactic" 6 | Level 2 7 | Title "Conjunctive Iff" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | constructor 13 | «repeat» 14 | 15 | Introduction " 16 | # Using Tactics 17 | Last level you learned that `constructor` works for biconditionals in the the same way it works for conjunction. This level demonstrates that `cases` works the way you may expect as well. 18 | " 19 | 20 | Statement (P Q : Prop) (h : P ↔ ¬Q) : (P → ¬Q) ∧ (¬Q → P) := by 21 | cases h 22 | constructor 23 | repeat assumption 24 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffTactic" 6 | Level 3 7 | Title "Iff_mp" 8 | 9 | OnlyTactic 10 | intro 11 | apply 12 | assumption 13 | 14 | Introduction " 15 | # Right-Hand Swap 16 | " 17 | 18 | Statement (P Q R : Prop) (h1 : Q ↔ R)(h2 : P → Q) : P → R := by 19 | intro p 20 | apply h1.mp 21 | apply h2 22 | assumption 23 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffTactic" 6 | Level 4 7 | Title "Iff_Intro" 8 | 9 | OnlyTactic 10 | intro 11 | apply 12 | assumption 13 | 14 | Introduction " 15 | # Left-Hand Swap 16 | " 17 | 18 | Statement (P Q R : Prop) (h1 : P ↔ R)(h2 : P → Q) : R → Q := by 19 | intro 20 | apply h2 21 | apply h1.mpr 22 | assumption 23 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffTactic" 6 | Level 5 7 | Title "Rewriting" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | «repeat» 13 | constructor 14 | intro 15 | apply 16 | left 17 | right 18 | exfalso 19 | 20 | Introduction " 21 | # A Longer Level 22 | The `rw` tactic makes this level a cakewalk. For the redux version we're going to make this outright tedious, but this will show without a doubt that you've mastered simple propositional proofs.\\ 23 | \\ 24 | I suggest you finish the other redux worlds before doing this level.\\ 25 | \\ 26 | We're not allowing `have`, `exact`, or `rw` for this level. It's certainly doable this way... good luck. 27 | " 28 | 29 | /-- This one takes a while -/ 30 | Statement 31 | (P Q R S : Prop) 32 | (h1 : R ↔ S) 33 | (h2 : ¬((P → Q ∨ ¬S) ∧ (S ∨ P → ¬Q) → (S → Q)) ↔ P ∧ Q ∧ ¬S) 34 | : ¬((P → Q ∨ ¬R) ∧ (R ∨ P → ¬Q) → (R → Q)) ↔ P ∧ Q ∧ ¬R := by 35 | cases h1 36 | cases h2 37 | constructor 38 | intro h3 39 | constructor 40 | apply and_left 41 | apply mp_1 42 | intro x 43 | apply h3 44 | intro ⟨pqnr,rpnq⟩ r 45 | apply x 46 | constructor 47 | intro p 48 | cases pqnr p 49 | left 50 | assumption 51 | right 52 | intro s 53 | apply h 54 | apply mpr 55 | assumption 56 | intro 57 | intro q 58 | apply rpnq 59 | left 60 | repeat assumption 61 | apply mp 62 | assumption 63 | constructor 64 | apply and_left 65 | apply and_right 66 | apply mp_1 67 | intro x 68 | apply h3 69 | intro ⟨pqnr,rpnq⟩ r 70 | apply x 71 | constructor 72 | intro p 73 | cases pqnr p 74 | left 75 | assumption 76 | right 77 | intro s 78 | apply h 79 | apply mpr 80 | assumption 81 | intro sp 82 | apply rpnq 83 | cases sp 84 | left 85 | apply mpr 86 | assumption 87 | right 88 | assumption 89 | apply mp 90 | assumption 91 | apply modus_tollens 92 | assumption 93 | apply and_right 94 | apply and_right 95 | apply mp_1 96 | intro x 97 | apply h3 98 | intro ⟨pqnr, rpnq⟩ r 99 | apply x 100 | constructor 101 | intro p 102 | cases pqnr p 103 | left 104 | assumption 105 | right 106 | intro 107 | apply h 108 | assumption 109 | intro 110 | apply rpnq 111 | left 112 | assumption 113 | apply mp 114 | assumption 115 | intro ⟨p, q, nr⟩ 116 | intro 117 | apply mpr_1 118 | constructor 119 | assumption 120 | constructor 121 | assumption 122 | apply modus_tollens 123 | repeat assumption 124 | intro 125 | intro 126 | assumption 127 | 128 | example 129 | (P Q R S : Prop) (h1 : R ↔ S) 130 | (h2 : ¬((P → Q ∨ ¬S) ∧ (S ∨ P → ¬Q) → (S → Q)) ↔ P ∧ Q ∧ ¬S) 131 | : ¬((P → Q ∨ ¬R) ∧ (R ∨ P → ¬Q) → (R → Q)) ↔ P ∧ Q ∧ ¬R := by 132 | apply iff_intro ( 133 | λh3 ↦ have ⟨p,q,ns⟩ := h2.mp ( 134 | λh₄ ↦ h3 (λ⟨hl₅,hr₅⟩ r ↦ h₄ ⟨ 135 | λp ↦ or_elim 136 | (hl₅ p) 137 | or_inl 138 | (imp_trans h1.mpr ≫ or_inr) 139 | , 140 | λ_ ↦ hr₅ (or_inl r) 141 | ⟩ (h1.mp r)) 142 | ) 143 | ⟨p, q, h1.mp ≫ ns⟩ 144 | ) ( 145 | λ⟨p,q,nr⟩ _ ↦ false_elim ( 146 | h2.mpr 147 | ⟨p, q, h1.mpr ≫ nr⟩ 148 | λ_ _ ↦ q 149 | ) 150 | ) 151 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | namespace GameLogic 4 | 5 | World "IffTactic" 6 | Level 6 7 | Title "Rewriting" 8 | 9 | OnlyTactic 10 | assumption 11 | intro 12 | apply 13 | 14 | Introduction " 15 | # Remember to use `or_assoc` and `and_assoc` 16 | Even without the `rw` tactic, this can be solved in a few lines. 17 | " 18 | 19 | Statement (P Q R : Prop) (h : P ∨ Q ∨ R → ¬(P ∧ Q ∧ R)) : (P ∨ Q) ∨ R → ¬((P ∧ Q) ∧ R) := by 20 | intro _ _ 21 | apply h 22 | apply or_assoc.mp 23 | assumption 24 | apply and_assoc.mp 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/IffTactic/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | namespace GameLogic 4 | 5 | World "IffTactic" 6 | Level 7 7 | Title "IffBoss" 8 | 9 | Introduction " 10 | # BOSS LEVEL 11 | This is an involved level. Tactics can be especially helpful in that much of the bookkeeping is done on your behalf. Good luck. 12 | 13 | There are no restrictions this level. Use everything you've learned. 14 | " 15 | 16 | Statement (P Q R : Prop): (P ∧ Q ↔ R ∧ Q) ↔ Q → (P ↔ R) := by 17 | constructor 18 | intro ⟨pr, rp⟩ 19 | intro 20 | constructor 21 | intro 22 | apply and_left 23 | apply pr 24 | constructor 25 | repeat assumption 26 | intro 27 | apply and_left 28 | apply rp 29 | constructor 30 | repeat assumption 31 | intro qpr 32 | constructor 33 | intro ⟨_,q⟩ 34 | rw [← qpr q] 35 | constructor 36 | repeat assumption 37 | intro ⟨_,q⟩ 38 | rw [qpr q] 39 | constructor 40 | repeat assumption 41 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.ImpIntro.L01 2 | import Game.Levels.ImpIntro.L02 3 | import Game.Levels.ImpIntro.L03 4 | import Game.Levels.ImpIntro.L04 5 | import Game.Levels.ImpIntro.L05 6 | import Game.Levels.ImpIntro.L06 7 | import Game.Levels.ImpIntro.L07 8 | import Game.Levels.ImpIntro.L08 9 | import Game.Levels.ImpIntro.L09 10 | 11 | World "ImpIntro" 12 | Title "→ Tutorial: Party Snacks" 13 | 14 | Introduction 15 | " 16 | # Snacks! 17 | What's a birthday party without chips and cake? 18 | # About Conditionals 19 | This world teaches you about conditional statements. In every day sentences, you'll see such statements expressed as “if [...], then [...]” logic — “If the sky is clear at night, then we will be able to see the stars.”.\\ 20 | \\ 21 | This game uses the implication arrow “ → ”. “If A, then B” is the same as “A implies B” or “`A → B`”.\\ 22 | \\ 23 | So far, we've been giving our evidence and propositions real-world analogues. Doing this is a bit less desirable with conditionals as real-world evidence is often based on our understanding and not some tangible object. For example; the evidence that “If it rained, then the driveway is wet” generally doesn't come in the form of peer-reviewed physics papers. You should be thinking of the flavour text for each level as an unrefined embellishment of the symbols you're actually concerned with.\\ 24 | \\ 25 | That said, at the level the flavour text works on, what would constitute evidence for a conditional statement? Well, for example; “If we have gnocchi, butter, parmesan, and black pepper, then we can cook *gnocchi cacio e pepe*”. An example of evidence for such a conditional could be a recipe with cooking instructions. You can think of the recipe as an argument explaining that the given ingredients are sufficient to be able to cook the dish.\\ 26 | \\ 27 | Another example might be a safe's combination, as evidence that “I can find the hidden safe” → “I can steal the contents of the safe”. Perhaps a certification is evidence that “I won the contract” → “I will renovate the client's yard”.\\ 28 | \\ 29 | The concept behind an implication, such as `A → B`, is that there exists an explicit path starting from the truth of `A`, from which you can demonstrate the truth of `B`. \\ 30 | \\ 31 | Where this game abstracted the idea of `A ∧ B` into a tuple holding evidence for `A` and evidence for `B`. The way we store evidence like `A → B` is with a function that takes evidence for `A` and produces evidence for `B`.\\ 32 | \\ 33 | `h : A → B` reads `h` is evidence for `A → B`. While real-world analogues can be anything, the formalization used in this game will always be a function. 34 | " 35 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 1 7 | Title "Cake Delivery Service" 8 | 9 | NewDefinition GameLogic.FunElim 10 | NewTheorem GameLogic.modus_ponens 11 | OnlyTactic 12 | exact 13 | «have» 14 | 15 | Introduction " 16 | # Let there be cake! 17 | You've found an online bakery service. Their website details how the cake delivery service works.\\ 18 | \\ 19 | If you send them evidence of payment, then you'll receive an email detailing when the cake will be delivered. The system is automated for you, so you can send it evidence of payment as often as you like and it'll always instantly return the same evidence that the cake will be delivered. 20 | # Proposition Key: 21 | - `P` — You've **P**aid for the cake 22 | - `C` — The **C**ake will be delivered 23 | # Implication \" → \" 24 | You use an implication the same way you've been using `and_intro`, `and_left`, and `and_right`. You write out the name of the implication and then write the name of the evidence required for the left side of the \" → \" next to it. Juxtaposition just means “to place next to each other,” which is what this is style of function application is called. 25 | - assumption `a : A` 26 | - assumption `h₁ : A → B` 27 | - have `b : B := h₁ a` 28 | 29 | You can read `h₁ a` as modus ponens. In fact, you've unlocked a theorem called modus_ponens that you could use here. Since modus ponens is implemented as function application, you can — and should — always just Juxtapose instead. 30 | # A note 31 | You'll often see assumptions given one or two letter names (`p`, `r`, `q`, `h₁`, `h₂`, `h₃`, etc). Assumptions are generally not long-lived. They are part of some expression, exhibit some implication, and then are discarded. Their names in this context don't need to be particularly memorable.\\ 32 | \\ 33 | Theorems — like those on the right side of the game screen — can be thought of as assumptions that are always available for every level (and therefore do not need to be listed under assumptions for any given level). They tend to have longer names because they will be available for all future levels.\\ 34 | \\ 35 | Name-length is not a hard and fast rule, just a common idiom. For a counter-example; this level gives the assumption `bakery_service` a longer name. 36 | 37 | # Reminder 38 | Exhibit evidence for the goal using the `exact` tactic. 39 | " 40 | 41 | /-- Exhibit evidence that cake will be delivered to the party -/ 42 | Statement (P C: Prop)(p: P)(bakery_service : P → C) : C := by 43 | have c := bakery_service p 44 | exact c 45 | 46 | example (P C: Prop)(p: P)(bakery_service : P → C) : C := by 47 | exact bakery_service p 48 | 49 | example (P C: Prop)(p: P)(bakery_service : P → C) : C := by 50 | exact modus_ponens bakery_service p 51 | 52 | Conclusion " 53 | Congratulations. You have evidence that your party will have cake! 54 | 55 | ---- 56 | 1: 57 | ``` 58 | have c := bakery_service p 59 | exact c 60 | ``` 61 | 2: 62 | ``` 63 | exact bakery_service p 64 | ``` 65 | 3: 66 | ``` 67 | exact modus_ponens bakery_service p 68 | ``` 69 | " 70 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 2 7 | Title "Identity" 8 | 9 | NewDefinition GameLogic.FunIntro 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Is it Cake!? 16 | There's a show on TV about cake. It asks bakers to distinguish between every-day objects and cakes. This seems silly, because a cake is a cake. 17 | # Proposition Key: 18 | - `C` — This object is a Cake 19 | # Implication “ → ” 20 | You'll notice that this time you don't have any assumptions. Fortunately, `C → C` is a tautology which means you can create an expression for the goal without any assumptions.\\ 21 | \\ 22 | In this game, when you need to show an implication, you write a function. Creating a function requires an assumption and an expression. The assumption is introduced with two parts: `(n : P)` where `n` is any name of your choosing and the `P` is a well formed proposition.\\ 23 | \\ 24 | It looks like this: 25 | ``` 26 | fun : => 27 | λ : 28 | ``` 29 | Often, when you're writting a function, the game will already know what proposition it's expecting. In such cases, the `` becomes an optional part of the function. 30 | 31 | # A hint 32 | Just fill in the `` below: 33 | ``` 34 | -- Assuming h is evidence for C, 35 | -- write an expression for evidence of C 36 | exact λ h : C ↦ 37 | ``` 38 | " 39 | 40 | /-- Common! Cake is Cake -/ 41 | Statement (C: Prop) : C → C := by 42 | exact λ(h: C) ↦ h 43 | 44 | example (C: Prop) : C → C := by 45 | exact λh ↦ h 46 | 47 | 48 | Conclusion " 49 | Once you've gathered evidence confirming that the object is a cake, the game becomes straightforward. The show's appeal lies not in the simple truism that a cake is a cake but rather in the contestants' skill to compile credible evidence. Typically, the most compelling evidence is presented by the host when he cuts into the object, unveiling its contents. 50 | 51 | ---- 52 | # A Tip 53 | Moreover, since the goal already specifies the expected type of evidence, you can streamline your function without explicitly writing out the assumed proposition. 54 | ``` 55 | exact λ(h : C) ↦ h 56 | -- can be written 57 | exact λh ↦ h 58 | ``` 59 | " 60 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 3 7 | Title "Cake Form Swap" 8 | 9 | NewTheorem GameLogic.identity 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Trouble with the cake 16 | The baker from the bakery called, expressing confusion about your cake order. While he can bake a cake with icing and sprinkles, you've requested sprinkles and icing. You attempt to convey that every cake with sprinkles and icing is **also** at the same time a cake with icing and sprinkles. The baker doesn't believe you.\\ 17 | \\ 18 | If you assume an arbitrary cake that has icing and that has sprinkles, show that you also have a cake that has sprinkles and has icing! 19 | # Proposition Key: 20 | - `I` — The cake has **I**cing 21 | - `S` — The cake has **S**prinkles 22 | " 23 | 24 | /-- Show that ∧ is commutative -/ 25 | Statement (I S: Prop) : I ∧ S → S ∧ I := by 26 | Hint (hidden := true) "`λ h : I ∧ S ↦ and_intro (and_right h) h.left`" 27 | exact λ(h : I ∧ S) ↦ and_intro (and_right h) h.left 28 | 29 | Conclusion " 30 | The bakery guy, upon reviewing your evidence, exclaims, \"Amazing! I never knew this. With this added knowledge, I'll be able to bake your cake!\" 31 | 32 | ---- 33 | In this level, as your goal is `I ∧ S → S ∧ I`, the game automatically recognizes the evidence you're assuming. You don't need to explicitly write it out. Therefore, you can use the following alternatives: 34 | ``` 35 | fun h => and_intro (and_right h) (and_left h) 36 | fun h => and_intro h.right h.left 37 | -- or with Unicode 38 | λh ↦ ⟨h.right, h.left⟩ 39 | ``` 40 | " 41 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 4 7 | Title "Chain Reasoning" 8 | 9 | NewTheorem GameLogic.and_comm 10 | OnlyTactic 11 | exact 12 | «have» 13 | TheoremTab "→" 14 | 15 | Introduction " 16 | # A Chain of Reasoning 17 | You know Alarfil will be excited about a cake with sprinkles. Since Sybeth has just started dating Alarfil and enjoys seeing them happy, it follows that Sybeth will be excited about a cake with sprinkles. 18 | # Proposition Key: 19 | - `C` — The **C**ake has sprinkles 20 | - `A` — **A**larfil is happy 21 | - `S` — **S**ybeth is happy 22 | 23 | # Transitivity Aside 24 | With numbers, if `a` is less than `b` and `b` is less than `c`, then you can deduce that `a` is less than `c`. 25 | $$ 26 | \\cfrac{a < b\\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace b < c}{a < c} 27 | $$ 28 | This is the transitive property of `<`. You should be able to show that this same property holds for conditionals — \"`→`\". Solving this level shows the following: 29 | $$ 30 | \\cfrac{C → A\\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace \\nobreakspace A → S}{C → S} 31 | $$ 32 | This property is so commonly useful that there exists special notation for it. **Once unlocked**, `imp_trans` has an infix operator. This looks like ≫ (which is written as “\\gg”). Example: `h1 ≫ h2` 33 | " 34 | 35 | /-- Show that → is transitive -/ 36 | Statement (C A S: Prop) (h1 : C → A) (h2 : A → S) : C → S := by 37 | exact λ(c: C) ↦ h2 (h1 c) 38 | 39 | -- Example using infix application to drop a pair of brackets. 40 | example (C A S: Prop) (h₁ : C → A) (h₂ : A → S) : C → S := by 41 | exact λc ↦ h₂ <| h₁ c 42 | 43 | example (C A S: Prop) (h₁ : C → A) (h₂ : A → S) : C → S := by 44 | exact h₁ ≫ h₂ 45 | 46 | Conclusion " 47 | AH ha! Well done. 48 | 49 | ---- 50 | For the math-inclined, because the expression for an implication is a function, you can also use function composition. 51 | ``` 52 | exact h₂ ∘ h₁ 53 | ``` 54 | " 55 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 5 7 | Title "Riffin Snacks" 8 | 9 | NewTheorem GameLogic.imp_trans 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Is Riffin bringing something? 16 | Riffin is an artsy, but rather eccentric friend of yours.\\ 17 | \\ 18 | Ever since you asked Riffin to bring something to your party, he's been sending you rather cryptic emails. Initially, these emails seemed to have nothing to do with the party. However, after receiving the 5th email, you believe you might be able to use them to create evidence that Riffin is bringing something to the party.\\ 19 | \\ 20 | These are the emails you received: 21 | 1. If you're planning a party then the quest has begun 22 | 2. If the quest has begin then the road to victory is long and winding 23 | 3. If the quest has begun then it's time to get serious 24 | 4. If the starting gun has fired then it's time to get serious 25 | 5. If it's time to get serious then I'll bring a unicorn snack to the party 26 | 27 | You still have your todo list as evidence that you're planning a party. Will it be enough? 28 | # Proposition Key: 29 | - `P` — You're **P**lanning a party 30 | - `Q` — The **Q**uest has begun 31 | - `R` — The **R**oad to victory is long and winding 32 | - `S` — The **S**tarting gun has fired 33 | - `T` — It's **T**ime to get serious 34 | - `U` — Riffin is bringing a **U**nicorn snack 35 | # Evidence 36 | Sometimes visuals can make a logical argument much easier to digest. Here is a diagram you've drawn depicting Riffin's notes so far. 37 | $$ 38 | \\begin{CD} 39 | P @>{h₁}>> Q @>{h₂}>> R \\\\ 40 | @. @VV{h₃}V \\\\ 41 | S @>>{h₄}> T @>>{h₅}> U 42 | \\end{CD} 43 | $$ 44 | # Reminder 45 | The Precedence definition page explains that function application is left-associative. So these 2 are the same: 46 | - `h₁ h₂ h₃ h₄ h₅` 47 | - `((((h₁ h₂) h₃) h₄) h₅)` 48 | " 49 | 50 | /-- Riffin is bringing a unicorn snack -/ 51 | Statement (P Q R S T U: Prop) (p : P) (h1 : P → Q) (h2 : Q → R) (h3 : Q → T) (h4 : S → T) (h5 : T → U) : U := by 52 | exact (h1 ≫ h3 ≫ h5) p 53 | 54 | -- One application at a time 55 | example (P Q R S T U: Prop) (p : P) (h₁ : P → Q) (h₂ : Q → R) (h₃ : Q → T) (h₄ : S → T) (h₅ : T → U) : U := by 56 | have q := h₁ p 57 | have t := h₃ q 58 | have u := h₅ t 59 | exact u 60 | 61 | -- Standard nested function application 62 | example (P Q R S T U: Prop) (p : P) (h₁ : P → Q) (h₂ : Q → R) (h₃ : Q → T) (h₄ : S → T) (h₅ : T → U) : U := by 63 | exact h₅ (h₃ (h₁ p)) 64 | 65 | -- Use the fact that implication is transitive 66 | example (P Q R S T U: Prop) (p : P) (h₁ : P → Q) (h₂ : Q → R) (h₃ : Q → T) (h₄ : S → T) (h₅ : T → U) : U := by 67 | have hpt := imp_trans h₁ h₃ 68 | have hpu := imp_trans hpt h₅ 69 | exact hpu p 70 | 71 | -- Use the infix operator for implication transitivity 72 | example (P Q R S T U: Prop) (p : P) (h₁ : P → Q) (h₂ : Q → R) (h₃ : Q → T) (h₄ : S → T) (h₅ : T → U) : U := by 73 | exact (h₁ ≫ h₃ ≫ h₅) p 74 | 75 | -- Use swapped function application to create a linux-style pipe 76 | example (P Q R S T U: Prop) (p : P) (h₁ : P → Q) (h₂ : Q → R) (h₃ : Q → T) (h₄ : S → T) (h₅ : T → U) : U := by 77 | exact p |> h₁ |> h₃ |> h₅ 78 | 79 | Conclusion " 80 | Amazing! He is bringing a snack and you have evidence to prove it too! 81 | 82 | ---- 83 | Here are two solutions to Riffin's puzzle. Sometimes it's helpful to see the same puzzles solved in more than one way. 84 | ``` 85 | have q := h₁ p 86 | have t := h₃ q 87 | have u := h₅ t 88 | exact u 89 | ``` 90 | or nest them together: 91 | ``` 92 | exact h₅ (h₃ (h₁ p)) 93 | ``` 94 | or use the `imp_trans` theorem from the previous world: 95 | ``` 96 | have hpt := imp_trans h₁ h₃ 97 | have hpu := imp_trans hpt h₅ 98 | exact hpu p 99 | ``` 100 | or if you've seen the infix operator for `imp_trans`: 101 | ``` 102 | exact (h₁ ≫ h₃ ≫ h₅) p 103 | ``` 104 | " 105 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 6 7 | Title "and_imp" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Curry 15 | If you've got chips and dip, then you've got a popular party snack! This is undeniable.\\ 16 | \\ 17 | Therefore if you've got chips, then if you've got dip, then you've got a popular party snack. 18 | # Proposition Key: 19 | - `C` — You've got chips 20 | - `D` — You've got Dip 21 | - `S` — You've got a popular party snack 22 | " 23 | 24 | /-- Conjunction interacting with implication -/ 25 | Statement (C D S: Prop) (h : C ∧ D → S) : C → D → S := by 26 | exact λc ↦ and_intro c ≫ h 27 | 28 | example (C D S: Prop) (h : C ∧ D → S) : C → D → S := by 29 | exact λc d ↦ h (and_intro c d) 30 | 31 | Conclusion " 32 | Cool. Chips and Dip! 33 | 34 | ---- 35 | # A Tip! 36 | If you're writing a function with more than one parameter, you can just list the parameters. That's a shorthand for nested function declarations. 37 | ``` 38 | λ(p : P) ↦ λ(q : Q) ↦ h (and_intro p q) 39 | -- can be written as: 40 | λ(p : P)(q : Q) ↦ h (and_intro p q) 41 | -- Or because the propositions of p and 42 | -- q are clear from the goal: 43 | λp q ↦ h (and_intro p q) 44 | ``` 45 | " 46 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 7 7 | Title "and_imp 2" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Un-Curry 15 | If you've got chips, then if you've got dip, then you've got a popular party snack.\\ 16 | \\ 17 | Therefore, if you've got chips and dip, then you've got a popular party snack! 18 | # Proposition Key: 19 | - `C` — You've got chips 20 | - `D` — You've got Dip 21 | - `S` — You've got a popular party snack 22 | " 23 | 24 | /-- Conjunction interacting with implication -/ 25 | Statement (C D S: Prop) (h : C → D → S) : C ∧ D → S := by 26 | exact λ(cd: C ∧ D) ↦ h cd.left cd.right 27 | 28 | example (C D S: Prop) (h : C → D → S) : C ∧ D → S := by 29 | exact λ⟨c, d⟩ ↦ h c d 30 | 31 | Conclusion " 32 | Cool. Chips and Dip for sure! 33 | " 34 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 8 7 | Title "Distribute" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Go buy chips and dip! 15 | - If you go shopping, then you'll buy chips. 16 | - If you go shopping, then you'll buy dip. 17 | - ∴ If you go shopping, you'll buy chips and dip 18 | # Proposition Key: 19 | - `C` — You buy chips 20 | - `D` — You buy dip 21 | - `S` — You go shopping 22 | " 23 | 24 | /-- → distributes over ∧ -/ 25 | Statement (C D S : Prop) (h : (S → C) ∧ (S → D)) : S → C ∧ D := by 26 | exact λ(s : S) ↦ and_intro (h.left s) (h.right s) 27 | 28 | -- Should this game try to teach destructuring? 29 | example (C D S : Prop) (h : (S → C) ∧ (S → D)) : S → C ∧ D := by 30 | have ⟨l,r⟩ := h 31 | exact λs ↦ ⟨l s, r s⟩ 32 | 33 | Conclusion " 34 | You definitely have a knack for providing conditional arguments! 35 | " 36 | -------------------------------------------------------------------------------- /Game/Levels/ImpIntro/L09.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpIntro" 6 | Level 9 7 | Title "Uncertain Snacks" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # BOSS LEVEL!!! 15 | # Uncertain Snacks 16 | Sybeth wants to know whether Riffin will still bring a snack, regardless of whether she brings one herself or not.\\ 17 | \\ 18 | She's asked you for evidence that: 19 | - **If** Riffin is bringing a snack **then** 20 | 1. Her bringing a snack **implies** Riffin is bringing a snack 21 | 2. Her not bringing a snack **implies** Riffin is bringing a snack 22 | 23 | That's a bit convoluted, but you should be able to produce some evidence of this! 24 | # Proposition Key: 25 | - `R` — Riffin is bringing a snack 26 | - `S` — Sybeth is bringing a snack 27 | " 28 | 29 | /-- Write the necessary nested function(s)! -/ 30 | Statement (R S : Prop) : R → (S → R) ∧ (¬S → R) := by 31 | exact λ(r : R) ↦ and_intro (λ(_ : S) ↦ r) (λ(_ : ¬S) ↦ r) 32 | 33 | example (R S : Prop) : R → (S → R) ∧ (¬S → R) := by 34 | exact λr ↦ and_intro (λ_ ↦ r) λ_ ↦ r 35 | 36 | example (R S : Prop) : R → (S → R) ∧ (¬S → R) := by 37 | have sr := λ(r: R)(_ : S) ↦ r 38 | have nsr := λ(r: R)(_ : ¬S) ↦ r 39 | exact λr ↦ ⟨sr r, nsr r⟩ 40 | 41 | example (R S : Prop) : R → (S → R) ∧ (¬S → R) := by 42 | exact λr ↦ ⟨λ_ ↦ r, λ_ ↦ r⟩ 43 | 44 | Conclusion " 45 | You're very convincing, and now Sybeth can see that if Riffin is bringing a snack, he'll be bringing it regardless of what she does.\\ 46 | \\ 47 | On to the next world! 48 | 49 | ---- 50 | # Hint 51 | If you're not going to use some evidence, then you don't need to name it. You can write an underscore as a placeholder. For example, my solution looked like this: 52 | ``` 53 | exact λ r : R ↦ ⟨λ _ : S ↦ r, λ _ : ¬S ↦ r⟩ 54 | -- which can be abbreviated 55 | exact λr ↦ ⟨λ_ ↦ r, λ_ ↦ r⟩ 56 | ``` 57 | " 58 | 59 | /-- Tactic Proof -/ 60 | example (R S : Prop) : R → (S → R) ∧ (¬S → R) := by 61 | intro r 62 | constructor 63 | intro; assumption 64 | intro; assumption 65 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.ImpTactic.L01 2 | import Game.Levels.ImpTactic.L02 3 | import Game.Levels.ImpTactic.L03 4 | import Game.Levels.ImpTactic.L04 5 | import Game.Levels.ImpTactic.L05 6 | import Game.Levels.ImpTactic.L06 7 | import Game.Levels.ImpTactic.L07 8 | import Game.Levels.ImpTactic.L08 9 | import Game.Levels.ImpTactic.L09 10 | 11 | World "ImpTactic" 12 | Title "Redux: → World Tactics" 13 | 14 | Introduction " 15 | # Redux: → World Tactics 16 | Welcome to the redux of the **→ Tutorial World**. Every level is the same, but instead of solving each level with `have` and `exact`, this world opens up a bunch of custom tactics.\\ 17 | \\ 18 | This world introduces tactics that you can use in lieu of the expressions you've learned so far. 19 | " 20 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 1 7 | Title "Apply" 8 | 9 | NewTactic apply 10 | 11 | OnlyTactic 12 | assumption 13 | apply 14 | 15 | Introduction " 16 | # The `apply` Tactic 17 | This tactic can be a bit confusing as it allows you to reason backwards. In this level, `h` gives us a means to get from P to Q. We can reason backwards and say, that because you have the means to turn evidence for P into evidence for Q — it suffices to show evidence for P.\\ 18 | \\ 19 | If you write `apply h`, you'll see that this changes your goal from `Q` to `P`. 20 | " 21 | 22 | /-- `P → (P → Q) → Q` -/ 23 | Statement (P Q: Prop)(h'₁: P)(h : P → Q) : Q := by 24 | apply h 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 2 7 | Title "Identity" 8 | 9 | NewTactic intro 10 | 11 | OnlyTactic 12 | assumption 13 | intro 14 | 15 | Introduction " 16 | # The `intro` Tactic 17 | The `intro` tactic lets you define a function interactively. It introduces one or more hypotheses, optionally naming them.\\ 18 | \\ 19 | In this level, `intro h` does two things. First, it adds an assumption `h : P` and second, it changes your goal from `P → P` to just `P`. In this sense, `intro h` is a bit like `λh ↦ `. 20 | " 21 | 22 | /-- Idenity via tactics -/ 23 | Statement (P: Prop) : P → P := by 24 | intro; assumption 25 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 3 7 | Title "Swapping" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | intro 14 | 15 | Introduction " 16 | # Nothing New 17 | Just use what you know. Start with `intro` to give yourself an assumption to work with 18 | " 19 | 20 | /-- Show that ∧ is commutative -/ 21 | Statement (P Q: Prop) : P ∧ Q → Q ∧ P := by 22 | intro h 23 | cases h 24 | constructor 25 | assumption 26 | assumption 27 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 4 7 | Title "Apply Chain Reasoning" 8 | 9 | OnlyTactic 10 | assumption 11 | apply 12 | intro 13 | 14 | Introduction " 15 | # More practise 16 | This level will require `apply` again. Remember that it lets you reason backwards on the goal. 17 | " 18 | 19 | /-- Show that → is transitive -/ 20 | Statement (C A S: Prop) (h1 : C → A) (h2 : A → S) : C → S := by 21 | intro 22 | apply h2 23 | apply h1 24 | assumption 25 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 5 7 | Title "Apply Backwards" 8 | 9 | OnlyTactic 10 | assumption 11 | apply 12 | intro 13 | 14 | Introduction " 15 | # Follow the Graph Backwards 16 | $$ 17 | \\begin{CD} 18 | P @>{h₁}>> Q @>{h₂}>> R \\\\ 19 | @. @VV{h₃}V \\\\ 20 | S @>>{h₄}> T @>>{h₅}> U 21 | \\end{CD} 22 | $$ 23 | " 24 | 25 | /-- Think before you act :) -/ 26 | Statement (P Q R S T U: Prop) (h1 : P → Q) (h2 : Q → R) (h3 : Q → T) (h4 : S → T) (h5 : T → U) : P → U := by 27 | intro 28 | apply h5 29 | apply h3 30 | apply h1 31 | assumption 32 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 6 7 | Title "repeat combinator" 8 | 9 | NewTactic «repeat» 10 | 11 | OnlyTactic 12 | assumption 13 | constructor 14 | apply 15 | intro 16 | «repeat» 17 | 18 | Introduction " 19 | # The `repeat` tactic combinator 20 | Sometimes you'll find you need to use the same tactic a few times in a row. Any tactic can be repeated (until it fails) using the `repeat`.\\ 21 | \\ 22 | In this level, it's very likely that you'll be building a conjunction from two assumptions already available in your proof state. Once you make it that far, instead of writing 23 | ``` 24 | constructor 25 | assumption 26 | assumption 27 | ``` 28 | try this instead 29 | ``` 30 | constructor 31 | repeat assumption 32 | ``` 33 | " 34 | 35 | /-- repeat assumption -/ 36 | Statement (P Q R: Prop) (h : P ∧ Q → R) : P → Q → R := by 37 | intro p 38 | intro q 39 | apply h 40 | constructor 41 | repeat assumption 42 | 43 | /-- Tactic Proofs -/ 44 | example (P Q R: Prop) (h : P ∧ Q → R) : P → Q → R := by 45 | intro p 46 | exact and_intro p ≫ h 47 | 48 | example (P Q R: Prop) (h : P ∧ Q → R) : P → Q → R := by 49 | intro p 50 | have h2 : Q → P ∧ Q := and_intro p 51 | have h3 : Q → R := h2 ≫ h 52 | assumption 53 | 54 | example (P Q R: Prop) (h : P ∧ Q → R) : P → Q → R := by 55 | intro _ _ 56 | apply h 57 | constructor <;> assumption 58 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 7 7 | Title "Another One" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | apply 13 | intro 14 | «repeat» 15 | 16 | Introduction " 17 | # And Another One 18 | Reverse the implication you proved last level. 19 | # More about `apply` 20 | What this level is showing is that you can think about `h` as a function with 2 parameters instead of a function that returns a function. The `apply` tactic implicitly understands this point of view.\\ 21 | \\ 22 | Once your goal is `R`, you can `apply h`. This is like saying “in order to prove R, it suffices to prove `P` and prove `Q`”. You'll notice that `apply` automatically creates the two goals for you in such cases.\\ 23 | \\ 24 | **Aside:** When you've been using `constructor` to build conjunctions, this has been the same thing as if you had used `apply and_intro`. You can try that out next level if you're so inclined. 25 | " 26 | 27 | /-- `(P → Q → R) → P ∧ Q → R` -/ 28 | Statement (P Q R: Prop) (h : P → Q → R) : P ∧ Q → R := by 29 | intro pq 30 | cases pq 31 | apply h 32 | repeat assumption 33 | 34 | example (P Q R: Prop) (h : P → Q → R) : P ∧ Q → R := by 35 | intro ⟨_,_⟩ 36 | apply h 37 | repeat assumption 38 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 8 7 | Title "Distribute" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | apply 14 | intro 15 | 16 | Introduction " 17 | # Keep it up 18 | " 19 | 20 | /-- `(P → Q) ∧ (P → R) → P → Q ∧ R` -/ 21 | Statement (P Q R : Prop) (h : (P → Q) ∧ (P → R)) : P → Q ∧ R := by 22 | intro 23 | cases h 24 | apply and_intro 25 | apply left 26 | assumption 27 | apply right 28 | assumption 29 | 30 | example (P Q R : Prop) (h : (P → Q) ∧ (P → R)) : P → Q ∧ R := by 31 | intro 32 | cases h 33 | constructor 34 | apply left 35 | assumption 36 | apply right 37 | assumption 38 | -------------------------------------------------------------------------------- /Game/Levels/ImpTactic/L09.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "ImpTactic" 6 | Level 9 7 | Title "Implication Tactic Boss" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | intro 13 | «repeat» 14 | 15 | Introduction " 16 | # World's End 17 | The home stretch, you can do it! 18 | 19 | # More tactic combinations 20 | You can combine two tactics into a single tactic using `{tac1; tac2}`. This may be helpful when combined with a tactic like `repeat`. As an example, there may come a time in this level where the following can save you a bit of typing: 21 | ``` 22 | repeat {intro; assumption} 23 | ``` 24 | " 25 | 26 | /-- `Q → (P → Q) ∧ (¬P → Q)` -/ 27 | Statement (P Q : Prop) : Q → (P → Q) ∧ (¬P → Q) := by 28 | intro 29 | constructor 30 | repeat {intro; assumption} 31 | 32 | Conclusion " 33 | This set of tactic-based levels is complete! 34 | 35 | ------ 36 | ``` 37 | intro 38 | constructor 39 | repeat {intro; assumption} 40 | ``` 41 | " 42 | -------------------------------------------------------------------------------- /Game/Levels/L01-template.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "IffIntro" 6 | Level 1 7 | Title "Title" 8 | 9 | Introduction " 10 | # Flavor Title 11 | Flavor Text 12 | # Proposition Key: 13 | - P — Pig 14 | - Q — Quack 15 | " 16 | 17 | /-- Statement -/ 18 | Statement: ¬False := by 19 | exact identity 20 | 21 | Conclusion " 22 | Onward and upward 23 | " 24 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.NotIntro.L01 2 | import Game.Levels.NotIntro.L02 3 | import Game.Levels.NotIntro.L03 4 | import Game.Levels.NotIntro.L04 5 | import Game.Levels.NotIntro.L05 6 | import Game.Levels.NotIntro.L06 7 | import Game.Levels.NotIntro.L07 8 | import Game.Levels.NotIntro.L08 9 | import Game.Levels.NotIntro.L09 10 | import Game.Levels.NotIntro.L10 11 | import Game.Levels.NotIntro.L11 12 | import Game.Levels.NotIntro.L12 13 | 14 | World "NotIntro" 15 | Title "¬ Tutorial: Falsification" 16 | 17 | Introduction " 18 | # In this world 19 | In this world, you'll be introduced to negation — which is written with the “`¬`” symbol.\\ 20 | \\ 21 | This operator is really just syntactic sugar. `¬P` means `P → False`. It seamlessly integrates into all the scenarios where implications are used. It's also constructed using functions (`λ...↦...`) just like any other implication.\\ 22 | \\ 23 | The new interesting element for this world is `False`. What is `False`? It's a proposition — part of the set of statements that can be either true or false. Importantly, however it's defined as a proposition which always happens to be false. By sheer force of definition — there can never exist any evidence supporting the veracity of `False`.\\ 24 | \\ 25 | Consider a real-world analogue like “Tom is an experienced beginner” or “Tom is a married bachelor”, neither can ever be true. For there to exist evidence of either, you need to throw out definitions of the words themselves.\\ 26 | \\ 27 | An interesting corollary arises: from the premise of `False`, any proposition becomes permissible. If you're allowed to speak in gobbledygook, then you can say anything! 28 | ### Garbage In, Garbage out 29 | Imagine you're signing a contract of utmost importance. The terms stipulate: “Once per day, you will be given a whole number greater than 0. If the number falls below 100, you must gracefully wave your left hand; if it exceeds 90, your right hand should elegantly sway. Lastly, if the number plunges below 0, you must transform into a cucumber.”\\ 30 | \\ 31 | On casual scrutiny, one might naively conclude that adhering to this contract may involve turning into a cucumber. While that may seem impossible, a subtle loophole exists. By astutely arguing that the contract will never demand the impossible act of becoming a cucumber, you can effectively assure your compliance.\\ 32 | \\ 33 | By signing the contract, you're agreeing that “If there appears a number that is both greater than 0 and less 0, then I will transform into a cucumber.” Your grandiose claims remain secure as they hinge on an eventuality that defies logical possibility. 34 | " 35 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 1 7 | Title "Not False" 8 | 9 | NewDefinition GameLogic.false_def 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Proof State 16 | The proof state in the level is as short as you've seen so far. There are no **Objects** or **Assumptions** listed.\\ 17 | \\ 18 | In other levels, you get a proposition key and in the proof state — under **Objects** — you'd see something like `P Q : Prop`. When you see `P` in a level, it's a variable standing in for whatever proposition is in the proposition key. The game freely re-uses these letters in other levels as they can stand in for anything.\\ 19 | \\ 20 | You won't see `False` listed under objects, just as you won't see **Theorems** or **Definitions** listed under assumptions. This just means that `False` never changes from level to level. It's never a stand-in for anything else. It's a fully defined and always available proposition. 21 | ## Not False 22 | Inuitively, it should be very simple to provide evidence for \"not false\". Since `¬P` is shorthand for `P → False`, you should think of `¬False` as shorthand for `False → False`. To drive home the fact that `False` is a proposition, this has the same proof as `P → P` (Which you solved in \"**→ Tutorial, level 2**\"). 23 | " 24 | 25 | /-- `identity` -/ 26 | Statement: ¬False := by 27 | exact identity 28 | 29 | example: ¬False := by 30 | exact λ(f : False) ↦ f 31 | 32 | Conclusion " 33 | You'll never **actually** find evidence for `False`, but evidence for `¬False` is a very simple tautology, as you would expect. 34 | 35 | ---- 36 | Which Proof did you use? 37 | ``` 38 | exact identity 39 | exact λ(f : False) ↦ f 40 | exact λf ↦ f 41 | ``` 42 | " 43 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 2 7 | Title "False implies anything" 8 | 9 | NewTheorem GameLogic.false_elim 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Sybeth's Punctuality 16 | Sybeth is never on time. Despite her assurances that she'll grace the party with her timely presence, past experiences have proven otherwise. It's almost become a running joke, so much so that you playfully quip, \"Yeah, if you arrive on time, then I'll eat my boots.\" 17 | # Proposition Key: 18 | - `B` — You eat your boots 19 | - `S` — Sybeth is on time 20 | # `false_elim` 21 | You've unlocked the \"false implies anything\" function. `false_elim` will take evidence for `False` and produce evidence for **anything**. 22 | # A Tip 23 | Remember you can think of `h : ¬S` as `h : S → False`.\\ 24 | \\ 25 | Once you've started with `λ(s : S) ↦ `, you'll then see that the expression `h s` evaluates to evidence for `False`. If ever you have evidence for `False`, you should always immediately consider using `false_elim` to create evidence for anything — which in this case will be `B`.\\ 26 | \\ 27 | There is no proof that \"keeps going\" once you've created evidence for `False`. Some proofs have multiple parts, so you may close off one line of reasoning and move on to another, but there can be no meaningful logic in any context where evidence for `False` is present. 28 | " 29 | 30 | /-- ¬S is enough to show S → B -/ 31 | Statement (B S : Prop)(h : ¬S) : S → B := by 32 | Hint (hidden := true) "λs ↦ false_elim (h s)" 33 | exact imp_trans h false_elim 34 | 35 | Conclusion " 36 | You've made use of the concept that \"false implies anything\". 37 | 38 | ---- 39 | ``` 40 | h : S → False 41 | false_elim : False → B 42 | ``` 43 | Because the righthand side of `h` and the lefthand side of `false_elim` match, you can use `imp_trans` to combine these: 44 | ``` 45 | imp_trans h false_elim 46 | ``` 47 | " 48 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 3 7 | Title "Double False!" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # The Ambiguous Celebration Response 15 | Your somewhat bothersome cousin just called and is asking if you're throwing your annual soirée this year. You don't want to outright lie, so you say \"I'm not not throwing the party.\" 16 | # Proposition Key: 17 | - `P` — You're throwing a party' 18 | " 19 | 20 | /-- not not introduction. -/ 21 | Statement (P : Prop)(p : P) : ¬¬P := by 22 | exact λ(np : ¬P) ↦ np p 23 | 24 | Conclusion " 25 | You've made use of the concept that \"false implies anything\". 26 | " 27 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 4 7 | Title "Self Contradictory" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Self Contradictory 15 | Alarfil claims Lippa is coming and Cyna claims Lippa is not coming. They can't both be right. 16 | # Proposition Key: 17 | - `L` — **L**ippa is attending the party 18 | " 19 | 20 | /-- The law of non-self-contradiction -/ 21 | Statement (L : Prop) : ¬(L ∧ ¬L) := by 22 | exact λ(h : L ∧ ¬L) ↦ h.right h.left 23 | 24 | Conclusion " 25 | Well Concluded! 26 | " 27 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 5 7 | Title "Modus Tollens" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Modus Tollens 15 | If Bella comes to the party, she is certain to perform a bawdy song. You've assured Sybeth that there will be no bawdy songs at the event. Sybeth is disappointed to discover that Bella won't be joining. 16 | # Proposition Key: 17 | - `B` — Bella is attending the party 18 | - `S` — A bawdy song will be sung 19 | " 20 | 21 | /-- Modus Tollens. -/ 22 | Statement (B S : Prop)(h1 : B → S)(h2 : ¬S) : ¬B := by 23 | exact imp_trans h1 h2 24 | 25 | Conclusion " 26 | Congratulations. Did you recognise this proof? It's actually a slightly less general version of the proof you used in the \"**→ Tutotial world, level 4**\" to show that implication is transitive. 27 | 28 | --- 29 | Thinking of `h₂` as `Q → False`, you can actually use your imp_trans theorem here. 30 | ``` 31 | exact λp ↦ h₂ (h₁ p) 32 | ``` 33 | ``` 34 | exact imp_trans h₁ h₂ 35 | ``` 36 | For the math-inclined, because the expression for an implication is a function, you can also use function composition. 37 | ``` 38 | exact h₂ ∘ h₁ 39 | ``` 40 | " 41 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 6 7 | Title "Alarfil" 8 | 9 | NewTheorem GameLogic.modus_tollens 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # The Alarfil Effect 16 | You're delighted that Alarfil will be there.\\ 17 | \\ 18 | Remarkably, even in moments when Alarfil lacks humor, he manages to be amusing! His comedic charm persists, regardless of circumstances. 19 | # Proposition Key: 20 | - `A` — Alarfil is humorless 21 | " 22 | 23 | /-- Remember `h : A → A → False` -/ 24 | Statement (A : Prop) (h: A → ¬A) : ¬A := by 25 | exact λ(a : A) ↦ h a a 26 | 27 | Conclusion " 28 | This joke is a reach, I know, but my answer in this level kinda spells `ahaa` — `λa ↦ h a a`. \\ 29 | \\ 30 | Okay, okay. Let's proceed. 31 | " 32 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 7 7 | Title "Negation" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # The Power of negation 15 | \"Is it possible that if this is the cake you bought, then it's gonna taste horrible?\"\\ 16 | \"I'm certain that's not possible.\"\\ 17 | \"Oh, so what you're saying is that you have evidence that the cake is delicious!\" 18 | # Proposition Key: 19 | - `B` — You bought this cake 20 | - `C` — The cake tastes horrible 21 | " 22 | 23 | /-- Nested `λ↦`s. -/ 24 | Statement (B C : Prop) (h: ¬(B → C)) : ¬C := by 25 | exact λ(c : C) ↦ h (λ(_ : B) ↦ c) 26 | 27 | Conclusion " 28 | Phew, that makes perfect sense now. 29 | " 30 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 8 7 | Title "Negated Conjunction" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Definitely Not 15 | Your cake order definitely has sprinkles, which means it's **not** missing sprinkles and loaded with chocolate chips 16 | # Proposition Key: 17 | - `C` — The cake is loaded with chocolate chips 18 | - `S` — The cake is topped with sprinkles 19 | " 20 | 21 | /-- Negation into conjunction. -/ 22 | Statement (C S : Prop) (s: S) : ¬(¬S ∧ C) := by 23 | exact λ(nsc : ¬S ∧ C) ↦ nsc.left s 24 | 25 | Conclusion " 26 | Might it possibly still be filled with chocolate chips? That sounds absolutely delightful! 27 | " 28 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L09.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 9 7 | Title "Implies a Negation" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Allergy #1 15 | Owing to his allergy, if Pippin is present, there must be no avocado at the party. Which means that we cannot have both Pippin and avocado at the party 16 | # Proposition Key: 17 | - `A` — There's avocado at the party 18 | - `P` — Pippin is attending the party 19 | " 20 | 21 | /-- Show ¬(P ∧ A) -/ 22 | Statement (A P : Prop) (h : P → ¬A) : ¬(P ∧ A) := by 23 | exact λ(pa : P ∧ A) ↦ h pa.left pa.right 24 | 25 | Conclusion " 26 | That's settled 27 | " 28 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L10.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 10 7 | Title "Conjunction Implication" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Allergy #2 15 | We cannot have both Pippin and avocado at the party. Which means that if Pippin is attending, then there will not be any avocado. 16 | # Proposition Key: 17 | - `A` — There's avocado at the party 18 | - `P` — Pippin is attending the party 19 | " 20 | 21 | /-- Show P → ¬A. -/ 22 | Statement (A P : Prop) (h: ¬(P ∧ A)) : P → ¬A := by 23 | exact λ(p: P)(a : A) ↦ h (and_intro p a) 24 | 25 | Conclusion " 26 | That's settled... again! 27 | 28 | ---- 29 | Reminder that these are the same: 30 | ``` 31 | λp ↦ λa ↦ h ⟨p,a⟩ 32 | -- and 33 | λp a ↦ h ⟨p,a⟩ 34 | ``` 35 | " 36 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L11.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 11 7 | Title "not_not_not" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Allergy: Triple Confusion 15 | Pippin is allergic to avocado. You tell him you're not *not* **not** bringing avocado!!! Pippin gives you a confused look, but after a moment of contemplation, he responds with, \"Ok, good to know.\" 16 | # Proposition Key: 17 | - `A` — You're bringing avocado 18 | " 19 | 20 | /-- ¬A is stable. -/ 21 | Statement (A : Prop)(h : ¬¬¬A) : ¬A := by 22 | exact λa ↦ h λna ↦ na a 23 | 24 | Conclusion " 25 | Well reasoned 26 | " 27 | -------------------------------------------------------------------------------- /Game/Levels/NotIntro/L12.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotIntro" 6 | Level 12 7 | Title "¬Intro Boss" 8 | 9 | NewTheorem GameLogic.not_not_not 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # BOSS Level 16 | \"Is it possible that if this is the cake you bought, then it's gonna taste horrible?\"\\ 17 | \"I'm certain that's not possible!\"\\ 18 | \"Oh, so what you're saying is that you have evidence that this is not not the cake you bought.\" 19 | # Proposition Key: 20 | - `B` — You bought this cake 21 | - `C` — The cake tastes horrible 22 | " 23 | 24 | /-- ¬¬\"You bought this cake" -/ 25 | Statement (B C : Prop) (h : ¬(B → C)) : ¬¬B := by 26 | exact λnb ↦ h (λb ↦ false_elim (nb b)) 27 | 28 | example (B C : Prop) (h : ¬(B → C)) : ¬¬B := by 29 | exact λnb ↦ h (nb ≫ false_elim) 30 | 31 | Conclusion " 32 | These unintuitive statements highlight the inherent challenge in contemplating the *potential* existence (or definite non-existence) of implications. 33 | 34 | That's a twist of logic, to be sure! 35 | " 36 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.NotTactic.L01 2 | import Game.Levels.NotTactic.L02 3 | import Game.Levels.NotTactic.L03 4 | import Game.Levels.NotTactic.L04 5 | import Game.Levels.NotTactic.L05 6 | import Game.Levels.NotTactic.L06 7 | import Game.Levels.NotTactic.L07 8 | import Game.Levels.NotTactic.L08 9 | import Game.Levels.NotTactic.L09 10 | import Game.Levels.NotTactic.L10 11 | import Game.Levels.NotTactic.L11 12 | import Game.Levels.NotTactic.L12 13 | 14 | World "NotTactic" 15 | Title "Redux: ¬ World Tactics" 16 | 17 | Introduction " 18 | # Redux: ¬ World Tactics 19 | Welcome to the redux of the **¬ Tutorial World**. Every level is the same, but instead of solving each level with `have` and `exact`, this world opens up a bunch of custom tactics.\\ 20 | \\ 21 | This world introduces tactics that you can use in lieu of the expressions you've learned so far. 22 | " 23 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 1 7 | Title "Not False" 8 | 9 | OnlyTactic 10 | assumption 11 | intro 12 | 13 | Introduction " 14 | Just like before, you need to keep in mind that `¬P` is defined as `P → False`.\\ 15 | \\ 16 | ∴ `¬False` is actually asking for `False → False` 17 | " 18 | 19 | /-- `identity` -/ 20 | Statement: ¬False := by 21 | intro 22 | assumption 23 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 2 7 | Title "Contradiction" 8 | 9 | NewTactic contradiction 10 | OnlyTactic 11 | intro 12 | contradiction 13 | 14 | Introduction " 15 | # Contradiction 16 | This level introduces a relatively powerful tactic. Contradiction is a finishing tactic that will look through your assumptions and discharge some of the more obvious contradictions in order to solve any goal.\\ 17 | \\ 18 | examples: 19 | 1. `h : False` says `h` is evidence for `False` which is a contradiction 20 | 2. `h₁ : P` and `h₂ : ¬P` says that P is true and not true at the same time. This is also a contradiction. 21 | " 22 | 23 | /-- ¬S is enough to show S → B -/ 24 | Statement (P Q : Prop)(h'₁ : ¬P) : P → Q := by 25 | intro 26 | contradiction 27 | 28 | example (P Q : Prop)(h'₁ : ¬P) : P → Q := by 29 | intro 30 | exfalso 31 | apply h'₁ 32 | assumption 33 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 3 7 | Title "Double False" 8 | 9 | NewTactic exfalso 10 | OnlyTactic 11 | assumption 12 | apply 13 | intro 14 | exfalso 15 | 16 | Introduction " 17 | # exfalso 18 | `exfalso` is **Contradiction**'s younger brother. Using `exfalso` turns your goal to `False`. This is the same thing as using `apply false_elim`. Using the principle that “from nonsense, follows nonsense,” if you spot a contradiction in your assumptions, you can make progress by changing your goal to `False.`\\ 19 | \\ 20 | Remember that because `¬P` is the same as `P → False`, you can use the `apply` tactic on evidence for `¬P` 21 | " 22 | 23 | /-- not not introduction. -/ 24 | Statement (P : Prop)(h'₁ : P) : ¬¬P := by 25 | intro np 26 | exfalso 27 | apply np 28 | assumption 29 | 30 | example (P : Prop)(h'₁ : P) : ¬¬P := by 31 | intro 32 | contradiction 33 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 4 7 | Title "Self Contradictory" 8 | 9 | OnlyTactic 10 | intro 11 | cases 12 | apply 13 | assumption 14 | 15 | Introduction " 16 | # Self Contradictory 17 | You start this level with the `intro` tactic. This will add an assumption to your proof state and change your goal to `False`. 18 | " 19 | 20 | /-- The law of non-self-contradiction -/ 21 | Statement (P : Prop) : ¬(P ∧ ¬P) := by 22 | intro pnp 23 | cases pnp 24 | apply right 25 | assumption 26 | 27 | example (P : Prop) : ¬(P ∧ ¬P) := by 28 | intro ⟨p, np⟩ 29 | contradiction 30 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 5 7 | Title "Modus Tollens" 8 | 9 | OnlyTactic 10 | intro 11 | apply 12 | assumption 13 | 14 | Introduction " 15 | # Modus Tollens 16 | By now you're getting used to using `intro` and `apply` on negated propositions. Keep it up! 17 | " 18 | 19 | /-- Modus Tollens. -/ 20 | Statement (P Q : Prop)(h1 : P → Q)(h2 : ¬Q) : ¬P := by 21 | intro 22 | apply h2 23 | apply h1 24 | assumption 25 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 6 7 | Title "Self Contradictory 2" 8 | 9 | NewTheorem GameLogic.modus_tollens 10 | OnlyTactic 11 | intro 12 | apply 13 | «repeat» 14 | assumption 15 | 16 | Introduction " 17 | # Another self-contradiction 18 | If you try to `apply h` right away, you'll be stuck. You'll have to try something else first.\\ 19 | \\ 20 | Once your goal is to show `False`, then `apply h` seems to do something funny. It asks you to show evidence for the same goal twice. If you try to apply `P → Q → R → S` to a Goal of `S`, then apply will make you prove each parameter separately — resulting is the three sub-goals of showing `P`, showing `Q`, and showing `R`.\\ 21 | \\ 22 | In this level, `apply h` is applying `P → P → False` to a goal of `False`, so it follows that you'll need to show evidence for `P` twice. 23 | " 24 | 25 | /-- Remember `h: P → ¬P` is actually `h : P → P → False` -/ 26 | Statement (P : Prop) (h: P → ¬P) : ¬P := by 27 | intro 28 | apply h 29 | repeat assumption 30 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 7 7 | Title "Negation" 8 | 9 | OnlyTactic 10 | intro 11 | apply 12 | assumption 13 | 14 | Introduction " 15 | # Anonymous intro 16 | You've likely tried this already, but you can use `intro` without following it with any identifiers. 17 | " 18 | 19 | Statement (P Q : Prop) (h: ¬(P → Q)) : ¬Q := by 20 | intro 21 | apply h 22 | intro 23 | assumption 24 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 8 7 | Title "Negated Conjunction" 8 | 9 | OnlyTactic 10 | cases 11 | intro 12 | apply 13 | assumption 14 | 15 | Introduction " 16 | # Pattern matching tip 17 | You can solve this level using the knowledge you've aquired so far. If you've used a language with destructuring or pattern matching before, then you can introduce and pattern-match in one step with `intro ⟨nq, p⟩`. Doing so means you won't need the `cases` tactic for this level. 18 | " 19 | 20 | /-- Negation into conjunction. -/ 21 | Statement (P Q : Prop) (h: Q) : ¬(¬Q ∧ P) := by 22 | intro nqp 23 | cases nqp 24 | apply left 25 | assumption 26 | 27 | example (P Q : Prop) (h: Q) : ¬(¬Q ∧ P) := by 28 | intro ⟨nq, _⟩ 29 | apply nq 30 | assumption 31 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L09.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 9 7 | Title "Implies a Negation" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | intro 13 | apply 14 | «repeat» 15 | 16 | Introduction " 17 | # Enjoy ⌣ 18 | " 19 | 20 | /-- Show ¬(P ∧ A) -/ 21 | Statement (P Q : Prop) (h : Q → ¬P) : ¬(Q ∧ P) := by 22 | intro qp 23 | cases qp 24 | apply h 25 | repeat assumption 26 | 27 | example (P Q : Prop) (h : Q → ¬P) : ¬(Q ∧ P) := by 28 | intro ⟨_, _⟩ 29 | apply h 30 | repeat assumption 31 | 32 | Conclusion " 33 | Did you use `intro ⟨p, a⟩` this time? 34 | " 35 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L10.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 10 7 | Title "Conjunction Implication" 8 | 9 | OnlyTactic 10 | assumption 11 | intro 12 | apply 13 | constructor 14 | «repeat» 15 | 16 | Introduction " 17 | # Intro 18 | Remember how `λx y ↦ ...` is just shorthand for `λx ↦ λy ↦ ...`? Well, the `intro` tactic admits the same shorthand: 19 | ``` 20 | intro p q 21 | ``` 22 | is just shorthand for 23 | ``` 24 | intro p 25 | intro q 26 | ``` 27 | " 28 | 29 | /-- Show P → ¬A. -/ 30 | Statement (P Q : Prop) (h: ¬(Q ∧ P)) : Q → ¬P := by 31 | intro p q 32 | apply h 33 | constructor 34 | repeat assumption 35 | 36 | example (P Q : Prop) (h: ¬(Q ∧ P)) : Q → ¬P := by 37 | intro _ _ 38 | apply h 39 | constructor <;> assumption 40 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L11.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 11 7 | Title "not_not_not" 8 | 9 | OnlyTactic 10 | assumption 11 | intro 12 | apply 13 | 14 | Introduction " 15 | # False 16 | Once your goal is false, then you can `apply h` 17 | " 18 | 19 | /-- ¬A is stable. -/ 20 | Statement (P : Prop)(h : ¬¬¬P) : ¬P := by 21 | intro 22 | apply h 23 | intro np 24 | apply np 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/NotTactic/L12.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "NotTactic" 6 | Level 12 7 | Title "Not Tactics Boss" 8 | 9 | OnlyTactic 10 | assumption 11 | intro 12 | apply 13 | exfalso 14 | contradiction 15 | 16 | Introduction " 17 | # The Final “Redux: ¬ World Tactics” level 18 | 19 | `contradiction` is available this level, you can use it to shorten your answer a little bit. 20 | " 21 | 22 | Statement (B C : Prop) (h : ¬(B → C)) : ¬¬B := by 23 | intro nb 24 | apply h 25 | intro b 26 | exfalso 27 | apply nb 28 | assumption 29 | 30 | example (B C : Prop) (h : ¬(B → C)) : ¬¬B := by 31 | intro nb 32 | apply h 33 | intro b 34 | contradiction 35 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.OrIntro.L01 2 | import Game.Levels.OrIntro.L02 3 | import Game.Levels.OrIntro.L03 4 | import Game.Levels.OrIntro.L04 5 | import Game.Levels.OrIntro.L05 6 | import Game.Levels.OrIntro.L06 7 | import Game.Levels.OrIntro.L07 8 | import Game.Levels.OrIntro.L08 9 | 10 | World "OrIntro" 11 | Title "∨ Tutorial: The Kraken" 12 | 13 | Introduction " 14 | # The ∨ Tutorial World! 15 | This world introduces disjunction. In sentences, the connective “or” is commonly employed, while in logical formulas, the symbol “`∨`” takes the stage. By now, you’re likely developing an understanding of the dynamics of the relationship between evidence and propositions. 16 | 1. Evidence for a conjunction `P ∧ Q` involves presenting a pair or tuple of evidence — one for `P` and another for `Q`. 17 | 2. Evidence for an implication like `P → Q` is a function from evidence of `P` to evidence of `Q`. 18 | 3. Evidence for a negation like `¬P` is a function from evidence of `P` to evidence of `False`. 19 | ### 4. Evidence for a disjunction 20 | Evidence for `P ∨ Q` requires only evidence for either `P` or for `Q`. While simple at heart, this can lead to difficulties. Consider some expression that uses evidence for `P` to exhibit a proposition like `P ∨ Q` — because that expression requires only evidence for `P`, it could also be used for `P ∨ R`, or `P ∨ S`.\\ 21 | \\ 22 | While the correct proposition can often be inferred, there are times when you may see an error like: “failed to infer declaration type” or “don't know how to synthesize implicit argument”. Generally, this means that the proposition you're trying to exhibit needs to be made clear from the context or needs to be supplied directly as part of the expression. 23 | ``` 24 | Objects: 25 | P Q : prop 26 | Assumptions: 27 | p : P 28 | Goal: P ∨ Q 29 | ``` 30 | Because the goal knows what is required, the following will work. 31 | ``` 32 | exact or_inl p 33 | ``` 34 | When using the `have` tactic you can annotate the evidence you're introducing with the `:` operator. Then the correct proposition for the expression can be inferred. For example: 35 | ``` 36 | have pvq : P ∨ Q := or_inl p 37 | exact pvq 38 | ``` 39 | Another option is to provide a proposition directly inside an expression. This becomes helpful in large expressions that have many parts. Even if it's often redundant, any expression can be annotated in this way 40 | 1. You can use a `show ... from ...` expression 41 | ``` 42 | have pvq := show P ∨ Q from or_inl p 43 | exact pvq 44 | -- Nested show statements work too... 45 | have pvq := show P ∨ Q from or_inl (show P from p) 46 | exact pvq 47 | ``` 48 | 2. You can use the `:` “is evidence for” operator if you bracket appropriately. `:` has a low precedence, so you’ll need to bracket the expression in most cases. 49 | ``` 50 | have pvq := (or_inl p : P ∨ Q) 51 | exact pvq 52 | -- Nested `:` work as well... 53 | have pvq := (or_inl (p : P) : P ∨ Q) 54 | exact pvq 55 | ``` 56 | " 57 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 1 7 | Title "Left Evidence" 8 | 9 | NewTheorem GameLogic.or_inl 10 | NewDefinition GameLogic.or_def 11 | OnlyTactic 12 | exact 13 | «have» 14 | 15 | Introduction " 16 | # Or Introduction Left 17 | You know that skittles are super colourful. Which means that either skittles are super colourful or oranges are a vegetable. 18 | # Proposition Key: 19 | - `O` — Oranges are a vegetable 20 | - `S` — skittles are super colourful 21 | # New unlock 22 | You've just unlocked `or_inl`. It turns evidence for the lefthand of an `∨` proposition into a disjunction. 23 | " 24 | 25 | /-- Or Introduction Left -/ 26 | Statement (O S : Prop)(s : S) : S ∨ O := by 27 | exact or_inl s 28 | 29 | Conclusion " 30 | This says nothing about whether or not oranges are a vegetable. All we know is that at least one of `O` or `S` must be true. 31 | " 32 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 2 7 | Title "Right Evidence" 8 | 9 | NewTheorem GameLogic.or_inr 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Or Introduction Right 16 | You know that skittles are super colourful. Which means that either sprinkles are super colourful or skittles are super colourful. 17 | # Proposition Key: 18 | - `K` — sprinkles are super colourful 19 | - `S` — skittles are super colourful 20 | # New unlock 21 | You've just unlocked `or_inr`. It turns evidence for the righthand of an `∨` proposition into a disjunction. 22 | " 23 | 24 | /-- Or Introduction Right -/ 25 | Statement (O S : Prop)(s : S) : K ∨ S := by 26 | exact or_inr s 27 | 28 | Conclusion " 29 | Almost a repeat of level 1. That was fast. 30 | 31 | You'll notice that last time you showed evidence for a proposition involving “Oranges are a vegetable” even though you probably know that oranges are a fruit and not a vegetable. This time you did the same for “sprinkles are super colourful,” which you probably know is true. 32 | 33 | What this demonstrates is that `∨` is an inclusive “or”, which means at least one of the propositions is true. It could be both or just one. 34 | " 35 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 3 7 | Title "Or Elimination" 8 | 9 | NewTheorem GameLogic.or_elim 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Party Games 16 | Here's the deal. Ilyn and Cyna both said they're bringing board games and you're sure at least one of them is going to make it. So there's definitely board games at the party! 17 | # Proposition Key: 18 | - `B` — There will be boardgames at the party 19 | - `C` — Cyna is coming to the party 20 | - `I` — Ilyn is coming to the party 21 | # Or Elimination 22 | If you can conclude something from `A` and you can conclude the same thing from `B`, then if you know `A ∨ B` it won't matter which of the two happens as you can still guarantee something. 23 | # You've unlocked `or_elim` 24 | `or_elim` has three parameters: 25 | 1. takes evidence for a disjunction, 26 | 2. evidence an implication on the left, 27 | 3. evidence for an implication on the right. 28 | 29 | `or_elim` is your first 3-parameter function. The associated proposition is `or_elim : (P ∨ Q) → (P → R) → (Q → R) → R`. 30 | ``` 31 | pvq: P ∨ Q 32 | pr : P → R 33 | qr : Q → R 34 | have r : R := or_elim pvq pr qr 35 | ``` 36 | " 37 | 38 | /-- `or_elim h₃ ... ...` -/ 39 | Statement (B C I : Prop)(h1 : C → B)(h2 : I → B)(h3 : C ∨ I) : B := by 40 | exact or_elim h3 h1 h2 41 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 4 7 | Title "Or is Commutative" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # Either way. 15 | Chocolate chip oatmeal cookies, which ingredient goes first? 16 | 1. Oatmeal or chocolate chips? 17 | 2. Chocolate chips or oatmeal? 18 | # Proposition Key: 19 | - C — Chocolate chips 20 | - O — Oatmeal 21 | 22 | This time they're not under assumptions, but you have the evidence in your inventory. 23 | - `or_inr : C → O ∨ C` 24 | - `or_inl : O → O ∨ C` 25 | " 26 | 27 | /-- Commutativity of "`∨`" -/ 28 | Statement (C O : Prop)(h : C ∨ O) : O ∨ C := by 29 | exact or_elim h or_inr or_inl 30 | 31 | Conclusion " 32 | Well, done. 33 | 34 | --- 35 | ``` 36 | exact or_elim h or_inr or_inl 37 | ``` 38 | " 39 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 5 7 | Title "Carry On Effects" 8 | 9 | NewTheorem GameLogic.or_comm 10 | OnlyTactic 11 | exact 12 | «have» 13 | 14 | Introduction " 15 | # Carry On Effects 16 | If the cake arrives, then everybody will rejoice. Either the cake arrives or you get a refund. Therefore, either everybody will rejoice or you get a refund! That's a win-win situation. 17 | # Proposition Key: 18 | - C — The cake arrives 19 | - J — Everybody is joyful 20 | - R — You get a refund 21 | " 22 | 23 | /-- Implication across ∨ -/ 24 | Statement (C J R : Prop)(h1 : C → J)(h2 : C ∨ R) : J ∨ R := by 25 | exact or_elim h2 (h1 ≫ or_inl) or_inr 26 | 27 | example (C J R : Prop)(h1 : C → J)(h2 : C ∨ R) : J ∨ R := by 28 | have h3 : C → J ∨ R := imp_trans h1 or_inl 29 | exact or_elim h2 h3 or_inr 30 | 31 | example (C J R : Prop)(h1 : C → J)(h2 : C ∨ R) : J ∨ R := by 32 | have h3 : C → J ∨ R := λc ↦ or_inl (h1 c) 33 | exact or_elim h2 h3 or_inr 34 | 35 | Conclusion " 36 | Well done, you're getting good at this! 37 | " 38 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 6 7 | Title "or distributes over and" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # A distribution of cookies 15 | You can tell the cookies are either gingersnap cookies or they're a mix of shortbread cookies and sugar cookies. 16 | # Proposition Key: 17 | - G — They are gingersnap cookies 18 | - H — They are shortbread cookies 19 | - U — They are sugar cookies 20 | " 21 | 22 | /-- ∨ over ∧ -/ 23 | Statement (G H U : Prop)(h : G ∨ H ∧ U) : (G ∨ H) ∧ (G ∨ U) := by 24 | have fg : G → (G ∨ H) ∧ (G ∨ U) := 25 | λg ↦ ⟨or_inl g, or_inl g⟩ 26 | have fhu : H ∧ U → (G ∨ H) ∧ (G ∨ U) := 27 | λhu ↦ ⟨or_inr hu.left, or_inr hu.right⟩ 28 | exact or_elim h fg fhu 29 | 30 | example (G H U : Prop)(h : G ∨ H ∧ U) : (G ∨ H) ∧ (G ∨ U) := by 31 | exact or_elim h 32 | (λg ↦ ⟨or_inl g, or_inl g⟩) 33 | λhu ↦ ⟨or_inr hu.left, or_inr hu.right⟩ 34 | 35 | Conclusion " 36 | As proof terms start getting bigger, it makes more sense to use Editor mode instead of typewritter mode. 37 | 38 | ---- 39 | ``` 40 | exact or_elim h 41 | (λg ↦ ⟨or_inl g, or_inl g⟩) 42 | (λhu ↦ ⟨or_inr hu.left, or_inr hu.right⟩) 43 | ``` 44 | You can split this up a bit, but you'll need to write out a lot of propositions in full. 45 | ``` 46 | -- the case for g 47 | have fg : G → (G ∨ H) ∧ (G ∨ U) := 48 | λg ↦ ⟨or_inl g, or_inl g⟩ 49 | 50 | -- the case for H ∧ U 51 | have fhu : H ∧ U → (G ∨ H) ∧ (G ∨ U) := 52 | λhu ↦ ⟨or_inr hu.left, or_inr hu.right⟩ 53 | 54 | -- Finish it out with or_elim 55 | exact or_elim h fg fhu 56 | ``` 57 | " 58 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 7 7 | Title "and distributes over or" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # A distribution of cookies 15 | You can tell from the aroma that there are are gingersnap cookies. There's another smell there too. Could be shortbread cookies or sugar cookies. 16 | # Proposition Key: 17 | - G — They are gingersnap cookies 18 | - H — They are shortbread cookies 19 | - U — They are sugar cookies 20 | " 21 | 22 | /-- ∧ over ∨ -/ 23 | Statement (G H U : Prop)(h : G ∧ (H ∨ U)) : (G ∧ H) ∨ (G ∧ U) := by 24 | have hvu := h.right 25 | have ffh : H → (G ∧ H) ∨ (G ∧ U) := 26 | λx ↦ or_inl ⟨h.left, x⟩ 27 | have ffu : U → (G ∧ H) ∨ (G ∧ U) := 28 | λx ↦ or_inr ⟨h.left, x⟩ 29 | exact or_elim hvu ffh ffu 30 | 31 | example (G H U : Prop)(h : G ∧ (H ∨ U)) : (G ∧ H) ∨ (G ∧ U) := by 32 | have g := h.left 33 | exact or_elim h.right 34 | (and_intro g ≫ or_inl) 35 | (and_intro g ≫ or_inr) 36 | 37 | Conclusion " 38 | This is a great example of how the infix `imp_trans` operator can streamline a level: 39 | 40 | --- 41 | ```lean 42 | have g := h.left 43 | exact or_elim h.right 44 | (and_intro g ≫ or_inl) 45 | (and_intro g ≫ or_inr) 46 | ``` 47 | " 48 | -------------------------------------------------------------------------------- /Game/Levels/OrIntro/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrIntro" 6 | Level 8 7 | Title "The Implication" 8 | 9 | OnlyTactic 10 | exact 11 | «have» 12 | 13 | Introduction " 14 | # BOSS LEVEL 15 | If we summon the Kraken, then we shall assuredly perish. While that might sound ominous, consider this: if we summon the Kraken or **have icecream**, then we shall **have icecream!** or we shall perish. 16 | # Proposition Key: 17 | - I — **We have have icecream!** 18 | - K — We summon the Kraken 19 | - P — We shall assuredly perish 20 | " 21 | 22 | /-- Implication of ∨ -/ 23 | Statement (I K P : Prop)(h : K → P) : K ∨ I → I ∨ P := by 24 | exact (or_elim · (h ≫ or_inr) or_inl) 25 | 26 | example (I K P : Prop)(h : K → P) : K ∨ I → I ∨ P := by 27 | have h₁ : K → I ∨ P := λk ↦ or_inr (h k) 28 | exact λki ↦ or_elim ki h₁ or_inl 29 | 30 | Conclusion " 31 | Okay, that was a bit easy for a boss level. Don't sweat it, just enjoy the icecream! 32 | " 33 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.OrTactic.L01 2 | import Game.Levels.OrTactic.L02 3 | import Game.Levels.OrTactic.L03 4 | import Game.Levels.OrTactic.L04 5 | import Game.Levels.OrTactic.L05 6 | import Game.Levels.OrTactic.L06 7 | import Game.Levels.OrTactic.L07 8 | import Game.Levels.OrTactic.L08 9 | 10 | World "OrTactic" 11 | Title "Redux: ∨ World Tactics" 12 | 13 | Introduction " 14 | # Redux: ∨ World Tactics 15 | Welcome to the redux of the **∨ Tutorial World**. Every level is the same, but instead of solving each level with `have` and `exact`, this world opens up a bunch of custom tactics.\\ 16 | \\ 17 | This world introduces tactics that you can use in lieu of the expressions you've learned so far. 18 | " 19 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L01.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 1 7 | Title "Left Evidence" 8 | 9 | NewTactic left 10 | OnlyTactic 11 | assumption 12 | left 13 | 14 | Introduction " 15 | # `left` tactic 16 | If your goal is a disjunction, `left` will change your goal to the left of that disjunction, ignoring the right. 17 | " 18 | 19 | /-- Go Left -/ 20 | Statement (P Q : Prop)(h'₁ : P) : P ∨ Q := by 21 | left 22 | assumption 23 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L02.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 2 7 | Title "Right Evidence" 8 | 9 | NewTactic right 10 | OnlyTactic 11 | assumption 12 | right 13 | 14 | Introduction " 15 | # `right` tactic 16 | Can you guess how it works? 17 | " 18 | 19 | /-- Go Right -/ 20 | Statement (P Q : Prop)(h'₁ : Q) : P ∨ Q := by 21 | right 22 | assumption 23 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L03.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 3 7 | Title "Or Elimination" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | apply 13 | 14 | Introduction " 15 | # A new way to use the `cases` tactic 16 | You've used cases so far as a means to take apart conjunctions like `P ∧ Q`. The same tactic works a bit differently on disjunctions. When you use `cases h3`, you'll need to show `P` assuming Q and show `P` assuming `R`. Then the game will conclude that `P` is the case regardless of which of the disjunctions are true. 17 | " 18 | 19 | /-- `cases h3` -/ 20 | Statement (P Q R : Prop)(h1 : Q → P)(h2 : R → P)(h3 : Q ∨ R) : P := by 21 | cases h3 22 | apply h1 23 | assumption 24 | apply h2 25 | assumption 26 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L04.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 4 7 | Title "Or is Commutative" 8 | 9 | OnlyTactic 10 | assumption 11 | cases 12 | right 13 | left 14 | 15 | Introduction " 16 | # Cases again 17 | Similar to last level. 18 | " 19 | 20 | /-- Commutativity of “`∨`” -/ 21 | Statement (P Q : Prop)(h : P ∨ Q) : Q ∨ P := by 22 | cases h 23 | right 24 | assumption 25 | left 26 | assumption 27 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L05.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 5 7 | Title "Old Hat" 8 | 9 | NewTheorem GameLogic.or_comm 10 | OnlyTactic 11 | cases 12 | assumption 13 | apply 14 | left 15 | right 16 | 17 | Introduction " 18 | # Old Hat 19 | You've got this. 20 | " 21 | 22 | /-- Implication across ∨ -/ 23 | Statement (P Q R : Prop)(h1 : P → Q)(h2 : P ∨ R) : Q ∨ R := by 24 | cases h2 25 | left 26 | apply h1 27 | assumption 28 | right 29 | assumption 30 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L06.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 6 7 | Title "or distributes over and" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | left 14 | right 15 | 16 | Introduction " 17 | # Tactics All Day Long 18 | " 19 | 20 | /-- ∨ over ∧ -/ 21 | Statement (P Q R : Prop)(h : P ∨ Q ∧ R) : (P ∨ Q) ∧ (P ∨ R) := by 22 | cases h 23 | constructor 24 | left 25 | assumption 26 | left 27 | assumption 28 | cases h_1 29 | constructor 30 | right 31 | assumption 32 | right 33 | assumption 34 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L07.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 7 7 | Title "and distributes over or" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | left 14 | right 15 | «repeat» 16 | 17 | Introduction " 18 | # Tactics, Tactics, Tactics 19 | " 20 | 21 | /-- ∧ over ∨ -/ 22 | Statement (P Q R : Prop)(h : P ∧ (Q ∨ R)) : (P ∧ Q) ∨ (P ∧ R) := by 23 | cases h 24 | cases right 25 | left 26 | constructor 27 | repeat assumption 28 | right 29 | constructor 30 | repeat assumption 31 | -------------------------------------------------------------------------------- /Game/Levels/OrTactic/L08.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | open GameLogic 4 | 5 | World "OrTactic" 6 | Level 8 7 | Title "The Implication" 8 | 9 | OnlyTactic 10 | assumption 11 | constructor 12 | cases 13 | left 14 | right 15 | apply 16 | intro 17 | 18 | Introduction " 19 | # Final `∨` Tactic Level 20 | This level uses most of the tactics you've learned so far. 21 | " 22 | 23 | /-- Implication of ∨ -/ 24 | Statement (P Q R : Prop)(h : Q → R) : Q ∨ P → P ∨ R := by 25 | intro qp 26 | cases qp 27 | right 28 | apply h 29 | assumption 30 | left 31 | assumption 32 | -------------------------------------------------------------------------------- /Game/Metadata.lean: -------------------------------------------------------------------------------- 1 | import GameServer.Commands 2 | 3 | import Game.Doc.Definitions 4 | import Game.Doc.Tactics 5 | import Game.Doc.Lemmas 6 | 7 | import Mathlib.Tactic 8 | 9 | -- import Game.Tactic.Induction 10 | -- import Game.Tactic.Cases 11 | -- import Game.Tactic.Rfl 12 | -- import Game.Tactic.Rw 13 | -- import Game.Tactic.Apply 14 | -- import Game.Tactic.Use 15 | -- import Game.Tactic.Ne 16 | -- import Game.Tactic.Xyzzy 17 | -- import Std.Tactic.RCases 18 | -- import Game.Tactic.Have 19 | -- import Game.Tactic.LeftRight 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Jon Eugster 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /LOCAL-SETUP.md: -------------------------------------------------------------------------------- 1 | # Local setup 2 | 3 | The game is accessible [localhost:3000](http://localhost:3000). 4 | 5 | # Game Skeleton 6 | 7 | This is a template for creating a game with [lean4game](https://github.com/leanprover-community/lean4game/). It contains a single Level plus the files needed for a local development setup. 8 | 9 | The documentation about how to use this template are at the [lean4game repository](https://github.com/leanprover-community/lean4game/): 10 | 11 | * [Creating a new game](https://github.com/leanprover-community/lean4game/blob/main/doc/create_game.md) 12 | * [Updating an existing game](https://github.com/leanprover-community/lean4game/blob/main/doc/update_game.md) 13 | * [Running a game locally](https://github.com/leanprover-community/lean4game/blob/main/doc/running_locally.md) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lean 4 Logic Game 2 | 3 | This is a game about propositional logic as expressed in the Lean Theorem Prover. It uses the [Lean4 Game Engine](https://github.com/leanprover-community/lean4game) and is running live at [adam.math.hhu.de](https://adam.math.hhu.de/#/g/Trequetrum/lean4game-logic) 4 | 5 | # A Lean Intro to Logic 6 | 7 | Highschool math and zero programming background? Mostly not a problem, the idea of this game is to be extremely approachable. That said, this is a work in progress, so there's some rough edges that need rounding off. Feel free to open an issue to leave feedback of any sort. 8 | 9 | Here looking for answers to any of the levels? You can look under `Game/Levels/`. Each statement has a proof that uses only the tools the game has taught so far. 10 | 11 | # Discussion 12 | 13 | This game is currently in its initial development phase, designed to be largely self-contained and accessible without requiring a programming or math background to navigate. Feedback about meeting that goal is welcome! 14 | 15 | While self-contained; in many ways, this game is targeted more at programmers than mathematicians. It doesn't use classical reasoning, sticking instead to constructive logic. The emphasis for most of the theorem proving is on writting proof terms — rather than using tactics. In fact, logic proof automation is such that the tactic **`tauto`** can solve any propositional logic theorem (Though possible, that's an NP-Complete problem). 16 | 17 | The main thrust of this game is to create puzzles that are fun to think through on your own. 18 | -------------------------------------------------------------------------------- /images/logic0101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trequetrum/lean4game-logic/40ceec5f3ca5dce6cec2800b8f5e4927631ca2da/images/logic0101.png -------------------------------------------------------------------------------- /images/logic0101_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trequetrum/lean4game-logic/40ceec5f3ca5dce6cec2800b8f5e4927631ca2da/images/logic0101_v2.png -------------------------------------------------------------------------------- /images/logic0101_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trequetrum/lean4game-logic/40ceec5f3ca5dce6cec2800b8f5e4927631ca2da/images/logic0101_v3.png -------------------------------------------------------------------------------- /images/logic0101_v4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trequetrum/lean4game-logic/40ceec5f3ca5dce6cec2800b8f5e4927631ca2da/images/logic0101_v4.png -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover/std4.git", 5 | "type": "git", 6 | "subDir": null, 7 | "rev": "32983874c1b897d78f20d620fe92fc8fd3f06c3a", 8 | "name": "std", 9 | "manifestFile": "lake-manifest.json", 10 | "inputRev": "v4.7.0", 11 | "inherited": true, 12 | "configFile": "lakefile.lean"}, 13 | {"url": "https://github.com/mhuisi/lean4-cli", 14 | "type": "git", 15 | "subDir": null, 16 | "rev": "39229f3630d734af7d9cfb5937ddc6b41d3aa6aa", 17 | "name": "Cli", 18 | "manifestFile": "lake-manifest.json", 19 | "inputRev": "nightly", 20 | "inherited": true, 21 | "configFile": "lakefile.lean"}, 22 | {"url": "https://github.com/hhu-adam/lean-i18n.git", 23 | "type": "git", 24 | "subDir": null, 25 | "rev": "7550f08140c59c9a604bbcc23ab7830c103a3e39", 26 | "name": "i18n", 27 | "manifestFile": "lake-manifest.json", 28 | "inputRev": "v4.7.0", 29 | "inherited": true, 30 | "configFile": "lakefile.lean"}, 31 | {"url": "https://github.com/leanprover-community/import-graph", 32 | "type": "git", 33 | "subDir": null, 34 | "rev": "ac07367cbdd57440e6fe78e5be13b41f9cb0f896", 35 | "name": "importGraph", 36 | "manifestFile": "lake-manifest.json", 37 | "inputRev": "v4.7.0", 38 | "inherited": true, 39 | "configFile": "lakefile.lean"}, 40 | {"url": "https://github.com/leanprover-community/lean4game.git", 41 | "type": "git", 42 | "subDir": "server", 43 | "rev": "66aa8e688ec6d684bc2ad37c7eee46627a0481b2", 44 | "name": "GameServer", 45 | "manifestFile": "lake-manifest.json", 46 | "inputRev": "v4.7.0", 47 | "inherited": false, 48 | "configFile": "lakefile.lean"}, 49 | {"url": "https://github.com/leanprover-community/quote4", 50 | "type": "git", 51 | "subDir": null, 52 | "rev": "64365c656d5e1bffa127d2a1795f471529ee0178", 53 | "name": "Qq", 54 | "manifestFile": "lake-manifest.json", 55 | "inputRev": "master", 56 | "inherited": true, 57 | "configFile": "lakefile.lean"}, 58 | {"url": "https://github.com/leanprover-community/aesop", 59 | "type": "git", 60 | "subDir": null, 61 | "rev": "5fefb40a7c9038a7150e7edd92e43b1b94c49e79", 62 | "name": "aesop", 63 | "manifestFile": "lake-manifest.json", 64 | "inputRev": "master", 65 | "inherited": true, 66 | "configFile": "lakefile.lean"}, 67 | {"url": "https://github.com/leanprover-community/ProofWidgets4", 68 | "type": "git", 69 | "subDir": null, 70 | "rev": "fb65c476595a453a9b8ffc4a1cea2db3a89b9cd8", 71 | "name": "proofwidgets", 72 | "manifestFile": "lake-manifest.json", 73 | "inputRev": "v0.0.30", 74 | "inherited": true, 75 | "configFile": "lakefile.lean"}, 76 | {"url": "https://github.com/leanprover-community/mathlib4.git", 77 | "type": "git", 78 | "subDir": null, 79 | "rev": "a45ae63747140c1b2cbad9d46f518015c047047a", 80 | "name": "mathlib", 81 | "manifestFile": "lake-manifest.json", 82 | "inputRev": "v4.7.0", 83 | "inherited": false, 84 | "configFile": "lakefile.lean"}], 85 | "name": "Game", 86 | "lakeDir": ".lake"} 87 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | -- Using this assumes that each dependency has a tag of the form `v4.X.0`. 5 | def leanVersion : String := s!"v{Lean.versionString}" 6 | 7 | def LocalGameServer : Dependency := { 8 | name := `GameServer 9 | src := Source.path "../lean4game/server" 10 | } 11 | 12 | def RemoteGameServer : Dependency := { 13 | name := `GameServer 14 | src := Source.git "https://github.com/leanprover-community/lean4game.git" leanVersion "server" 15 | } 16 | 17 | /- Choose GameServer dependency depending on the environment variable `LEAN4GAME`. -/ 18 | open Lean in 19 | #eval (do 20 | let gameServerName := if get_config? lean4game.local |>.isSome then 21 | ``LocalGameServer else ``RemoteGameServer 22 | modifyEnv (fun env => Lake.packageDepAttr.ext.addEntry env gameServerName) 23 | : Elab.Command.CommandElabM Unit) 24 | 25 | /-! # USER SECTION 26 | 27 | Below are all the dependencies the game needs. Add or remove packages here as you need them. 28 | 29 | Note: If your package (like `mathlib` or `Std`) has tags of the form `v4.X.0` then 30 | you can use `require mathlib from git "[URL]" @ leanVersion` 31 | -/ 32 | 33 | 34 | 35 | require mathlib from git "https://github.com/leanprover-community/mathlib4.git" @ leanVersion 36 | 37 | 38 | 39 | /-! # END USER SECTION -/ 40 | 41 | -- NOTE: We abuse the `trace.debug` option to toggle messages in VSCode on and 42 | -- off when calling `lake build`. Ideally there would be a better way using `logInfo` and 43 | -- an option like `lean4game.verbose`. 44 | package Game where 45 | moreLeanArgs := #[ 46 | "-Dtactic.hygienic=false", 47 | "-Dlinter.unusedVariables.funArgs=false", 48 | "-Dtrace.debug=false"] 49 | moreServerOptions := #[ 50 | ⟨`tactic.hygienic, false⟩, 51 | ⟨`linter.unusedVariables.funArgs, true⟩, 52 | ⟨`trace.debug, true⟩] 53 | weakLeanArgs := #[] 54 | 55 | @[default_target] 56 | lean_lib Game 57 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.7.0 2 | --------------------------------------------------------------------------------