diff --git a/.gitignore b/.gitignore index 4126250..cb87a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules client/dist -server/build \ No newline at end of file +server/build +**/lean_packages/ + diff --git a/server/leanserver/GameServer/Commands.lean b/server/leanserver/GameServer/Commands.lean index 893cd76..db9628b 100644 --- a/server/leanserver/GameServer/Commands.lean +++ b/server/leanserver/GameServer/Commands.lean @@ -48,11 +48,12 @@ elab "Introduction" t:str : command => do | .Game => modifyCurGame fun game => pure {game with introduction := t.getString} /-- Define the statement of the current level. -/ -elab "Statement" sig:declSig val:declVal : command => do +elab "Statement" statementName:ident ? descr:str ? sig:declSig val:declVal : command => do let lvlIdx ← getCurLevelIdx let declName : Name := (← getCurGame).name ++ (← getCurWorld).name ++ ("level" ++ toString lvlIdx : String) elabCommand (← `(theorem $(mkIdent declName) $sig $val)) modifyCurLevel fun level => pure {level with goal := sig} +-- TODO: Do something with the lemma name. /-- Define the conclusion of the current game or current level if some building a level. -/ @@ -131,6 +132,10 @@ local elab "Message'" decls:mydecl* ":" goal:term "=>" msg:str : command => do macro "Message" decls:mydecl* ":" goal:term "=>" msg:str : command => do `(set_option linter.unusedVariables false in Message' $decls* : $goal => $msg) +/-- Declare a hint in reaction to a given tactic state in the current level. -/ +macro "Hint" decls:mydecl* ":" goal:term "=>" msg:str : command => do + `(set_option linter.unusedVariables false in Message' $decls* : $goal => $msg) + -- TODO: implement me? /-! ## Tactics -/ diff --git a/server/testgame/.vscode/settings.json b/server/testgame/.vscode/settings.json new file mode 100644 index 0000000..23325f1 --- /dev/null +++ b/server/testgame/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.rulers" : [100], + "files.encoding": "utf8", + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true +} diff --git a/server/testgame/TestGame.lean b/server/testgame/TestGame.lean index ff92164..e6cd5fe 100644 --- a/server/testgame/TestGame.lean +++ b/server/testgame/TestGame.lean @@ -1,6 +1,16 @@ import TestGame.Metadata -import TestGame.Levels.Level1 -import TestGame.Levels.Level2 -import TestGame.Levels.Level3 -import TestGame.Levels.Level4 -import TestGame.Levels.Level5 \ No newline at end of file +import TestGame.Levels.Logic.L01_Rfl +import TestGame.Levels.Logic.L02_Rfl +import TestGame.Levels.Logic.L03_Assumption +import TestGame.Levels.Logic.L03b_Assumption +import TestGame.Levels.Logic.L04_Rewrite +import TestGame.Levels.Logic.L05_Apply +import TestGame.Levels.Logic.L05b_Apply +import TestGame.Levels.Logic.L05c_Apply +import TestGame.Levels.Logic.L06_Iff +import TestGame.Levels.Logic.L06b_Iff +import TestGame.Levels.Logic.L06c_Iff +import TestGame.Levels.Logic.L06d_Iff +import TestGame.Levels.Logic.L07_And +import TestGame.Levels.Logic.L08_Or +import TestGame.Levels.Logic.L08b_Or diff --git a/server/testgame/TestGame/LemmaDocs.lean b/server/testgame/TestGame/LemmaDocs.lean index e49db37..b12744f 100644 --- a/server/testgame/TestGame/LemmaDocs.lean +++ b/server/testgame/TestGame/LemmaDocs.lean @@ -1,14 +1,14 @@ import GameServer.Commands -import TestGame.MyNat +-- import TestGame.MyNat -LemmaDoc zero_add as zero_add in "Addition" -"This lemma says `∀ a : ℕ, 0 + a = a`." +-- LemmaDoc zero_add as zero_add in "Addition" +-- "This lemma says `∀ a : ℕ, 0 + a = a`." -LemmaDoc add_zero as add_zero in "Addition" -"This lemma says `∀ a : ℕ, a + 0 = a`." +-- LemmaDoc add_zero as add_zero in "Addition" +-- "This lemma says `∀ a : ℕ, a + 0 = a`." -LemmaDoc add_succ as add_succ in "Addition" -"This lemma says `∀ a b : ℕ, a + succ b = succ (a + b)`." +-- LemmaDoc add_succ as add_succ in "Addition" +-- "This lemma says `∀ a b : ℕ, a + succ b = succ (a + b)`." -LemmaSet addition : "Addition lemmas" := -zero_add add_zero \ No newline at end of file +-- LemmaSet addition : "Addition lemmas" := +-- zero_add add_zero diff --git a/server/testgame/TestGame/Levels/Logic/L01_Rfl.lean b/server/testgame/TestGame/Levels/Logic/L01_Rfl.lean new file mode 100644 index 0000000..7894fa6 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L01_Rfl.lean @@ -0,0 +1,35 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 1 + +Title "Aller Anfang ist... ein Einzeiler?" + +Introduction +" +Willkommen zum Lean-Crashkurs wo du lernst wie man mathematische Beweise vom Computer +unterstützt und verifiziert schreiben kann. + +Ein Beweis besteht in Lean aus verschiedenen **Taktiken**, welche ungefähr einem +logischen Schritt entsprechen, den man auf Papier aufschreiben würde. + +Rechts im **Infoview** siehst den Status des aktuellen Beweis. +Du siehst ein oder mehrere offene **Goals** (mit einem `⊢` davor), die du noch zeigen musst. +Wenn du eine Taktik hinschreibst, dann versucht Lean diesen Schritt beim +ersten offenen Goal zu machen. +Wenn der Beweis komplett ist, erscheint \"goals accomplished\". +" + +Statement "Zeige `42 = 42`." : 42 = 42 := by + rfl + +Message : 42 = 42 => +"Die erste Taktik ist `rfl`, die ein Goal von der Form `A = A` beweist." + +Hint : 42 = 42 => +"Man schreibt eine Taktik pro Zeile, also gib 'rfl' ein gefolgt von ENTER." + +Conclusion "Bravo!" + +Tactics rfl diff --git a/server/testgame/TestGame/Levels/Logic/L02_Rfl.lean b/server/testgame/TestGame/Levels/Logic/L02_Rfl.lean new file mode 100644 index 0000000..8b53a51 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L02_Rfl.lean @@ -0,0 +1,28 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 2 + +Title "Definitionally equal" + +Introduction +" +Achtung: `rfl` kann auch Gleichungen beweisen, wenn die beiden Terme Lean-intern gleich +definiert sind, auch wenn diese unterschiedlich dargestellt werden. +So sind `1 + 1` und `2` per Definition das Gleiche, da sie beide von Lean als `0.succ.succ` +gelesen werden. + +Das kann anfänglich verwirrend sein und das Verhalten hängt von der Lean-Implementation ab. +" + +Statement "Zeige dass eins plus eins zwei ist." : 1 + 1 = 2 := by + rfl + +Conclusion +" +Im weiteren führen die meisten anderen Taktiken `refl` automatisch am Ende aus, +deshalb musst du dieses häufig gar nicht mehr schreiben. +" + +Tactics rfl diff --git a/server/testgame/TestGame/Levels/Logic/L03_Assumption.lean b/server/testgame/TestGame/Levels/Logic/L03_Assumption.lean new file mode 100644 index 0000000..98acf4c --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L03_Assumption.lean @@ -0,0 +1,29 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 3 + +Title "Annahmen" + +Introduction +" +Mathematische Aussagen haben Annahmen. Das sind zum einen Objekte, wie \"sei `n` eine +natürliche Zahl\", oder auch wahre Aussagen über diese Objekte, wie zum Beispiel +\"und angenommen, dass `n` strikt grösser als `1` ist\". + +In Lean schreibt man beides mit dem gleichen Syntax: `(n : ℕ) (h : 1 < n)` definiert +zuerst `n` als natürliche Zahl und kreeirt eien Annahme, dass `1 < n`. Dieser Annahme geben wir +den Namen `h`. + +Wenn das Goal genau einer Annahme entspricht, kann man diese mit `assumption` beweisen. +" + +Statement triviale_angelegenheit + "Angenommen `1 < n`. dann ist `1 < n`." + (n : ℕ) (h : 1 < n) : 1 < n := by + assumption + +Conclusion "" + +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L03b_Assumption.lean b/server/testgame/TestGame/Levels/Logic/L03b_Assumption.lean new file mode 100644 index 0000000..33afcde --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L03b_Assumption.lean @@ -0,0 +1,28 @@ +import TestGame.Metadata +import Mathlib.Data.Nat.Basic -- TODO + +Game "TestGame" +World "TestWorld" +Level 4 + +Title "Logische Aussagen: `Prop`" + +Introduction +" +Eine allgemeine logische Aussage definiert man mit `(A : Prop)`. Damit sagt man noch nicht, +ob die Aussage `A` wahr oder falsch ist. Mit einer Annahme `(hA : A)` nimmt man an, dass +`A` wahr ist: `hA` ist ein Beweis der Aussage `A`. +" + +-- TODO: Macht es Sinn mehrere Aufgaben auf einer Seite zu haben? +Statement mehr_triviales + " + Sei `A` eine logische Aussage und angenommen man hat einen Beweis für `A`. + Zeige, dass `A` wahr ist. + " + (A : Prop) (hA : A) : A := by + assumption + +Conclusion "" + +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L04_Rewrite.lean b/server/testgame/TestGame/Levels/Logic/L04_Rewrite.lean new file mode 100644 index 0000000..6b1329f --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L04_Rewrite.lean @@ -0,0 +1,53 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 5 + +Title "Rewrite" + +Introduction +" +Oft sind aber die Annahmen nicht genau das, was man zeigen will, sondern man braucht +mehrere Schritte im Beweis. + +Wenn man eine Annahme `(h : X = Y)` hat die sagt, dass `X` und `Y` gleich sind, +kann man die Taktik `rw` (steht für 'rewrite') brauchen um im Goal +das eine durch das andere zu ersetzen. +" + +Statement umschreiben + " + Angenommen man hat die Gleichheiten + `a = b`, `a = d`, `c = d`. + Zeige dass `b = c`. + " + (a b c d : ℕ) (h₁ : c = d) (h₂ : a = b) (h₃ : a = d) : b = c := by + rw [h₁] + rw [←h₂] + assumption + +-- Gleich am Anfang anzeigen. +Message (a : ℕ) (b : ℕ) (c : ℕ) (d : ℕ) (h₁ : c = d) (h₂ : a = b) (h₃ : a = d) : b = c => +"Wenn man eine Annahme `(h₁ : c = d)` hat, kann man mit `rw [h₁]` (oder `rewrite [h₁]`) das erste +`c` im Goal mit `d` ersetzen." + +Hint (a : ℕ) (b : ℕ) (c : ℕ) (d : ℕ) (h₁ : c = d) (h₂ : a = b) (h₃ : a = d) : b = c => +"Die kleinen Zahlen `h₁ h₂ h₃` werden in Lean oft verwendet und man schreibt diese mit +`\\1`, `\\2`, `\\3`, …" + +Message (a : ℕ) (b : ℕ) (c : ℕ) (d : ℕ) (h₁ : c = d) (h₂ : a = b) (h₃ : a = d) : b = d => +"Mit `rw [← h₂]` (`\\l`, also klein L wie \"left\") kann man eine Hypotheses +`(h₂ : a = b)` rückwärts anwenden und `b` durch `a` ersetzen." + +-- TODO: Muss ich das wirklich mehrmals auflisten? +Message (x : ℕ) : x = x => +"Der Hauptunterschied zwischen `rw` und `rewrite` ist, dass das erste automatisch versucht, +anschliessend `rfl` anzuwenden. Bei `rewrite` musst du `rfl` explizit noch aufrufen." + +Conclusion "Übrigens, mit `rw [h₁] at h₂` kann man auch eine andere Annahme umschreiben +anstatt dem Goal." +-- TODO: Das macht es doch unmöglich mit den Messages... + +Tactics assumption +Tactics rw diff --git a/server/testgame/TestGame/Levels/Logic/L05_Apply.lean b/server/testgame/TestGame/Levels/Logic/L05_Apply.lean new file mode 100644 index 0000000..835190e --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L05_Apply.lean @@ -0,0 +1,38 @@ +import TestGame.Metadata +import Mathlib + +Game "TestGame" +World "TestWorld" +Level 6 + +Title "Implikation" + +Introduction +" +Wie wir schon gesehen haben, wir eine logische Aussage als `(A : Prop)` geschrieben, und +die Annahme, dass `A` wahr ist als `(hA : A)`, also `hA` ist sozusagens ein Beweis der +Aussage `A`. + +Logische Aussagen können einander implizieren. Wir kennen hauptsächlich zwei Zeichen dafür: +`A ↔ B` (`\\iff`) bedeutet \"Genau dann wenn\" und `A → B` (`\\to`) bedeutet \"`A` impliziert `B`\". + +Wenn man Aussage `B` beweisen will und eine Implikationsannahme `(h : A → B)` hat, dann kann man +diese mit `apply h` anwenden. +Auf Papier würde man schreiben, \"es genügt zu zeigen, dass `A` stimmt, denn `A` impliziert `B`\". +" + +Statement + " + Seien `A`, `B` logische Aussagen, wobei `A` wahr ist und `A` impliziert `B`. + Zeige, dass `B` wahr ist. + " + (A B : Prop) (hA : A) (g : A → B) : B := by + apply g + assumption + +Message (A : Prop) (B : Prop) (hA : A) (g : A → B) : A => +"Nachdem du die Implikation `A → B` angewendet hast, musst du nur noch `A` zeigen, +dafür hast du bereits einen Beweis in den Annahmen." + +Tactics apply +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L05b_Apply.lean b/server/testgame/TestGame/Levels/Logic/L05b_Apply.lean new file mode 100644 index 0000000..e8e634c --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L05b_Apply.lean @@ -0,0 +1,43 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 7 + +Title "Implikation" + +Introduction +" +Angenommen man hat folgende Implikationen und weiss dass Aussage `A` wahr ist. +``` +A → B ← C + ↓ ↓ +D → E → F +``` +Beweise Aussage `F`. +" + +Statement + " + Seien `A`, `B` logische Aussagen, wobei `A` wahr ist und `A` impliziert `B`. + Zeige, dass `B` wahr ist. + " + (A B C D E F : Prop) (hA : A) (f : A → B) (g : C → B) (h : B → E) + (i : D → E) (k : E → F) (m : C → F) : F := by + apply k + apply h + apply f + assumption + +Message (A : Prop) (B : Prop) (C : Prop) (D : Prop) (E : Prop) (F : Prop) + (hA : A) (f : A → B) (g : C → B) (h : B → E) + (i : D → E) (k : E → F) (m : C → F) : C => +"Sackgasse. Probier doch einen anderen Weg." + +Message (A : Prop) (B : Prop) (C : Prop) (D : Prop) (E : Prop) (F : Prop) + (hA : A) (f : A → B) (g : C → B) (h : B → E) + (i : D → E) (k : E → F) (m : C → F) : D => +"Sackgasse. Probier doch einen anderen Weg." + +Tactics apply +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L05c_Apply.lean b/server/testgame/TestGame/Levels/Logic/L05c_Apply.lean new file mode 100644 index 0000000..f10e1c7 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L05c_Apply.lean @@ -0,0 +1,22 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 8 + +Title "Implikation" + +Introduction +" +Wenn das Goal von der Form `A → B` ist, kann man mit `intro` annehmen, dass `A` wahr ist +und das Goal wird zu `B`. +" + +Statement + (A B C : Prop) (f : A → B) (g : B → C) : A → C := by + intro hA + apply g + apply f + assumption + +Tactics intro apply assumption diff --git a/server/testgame/TestGame/Levels/Logic/L06_Iff.lean b/server/testgame/TestGame/Levels/Logic/L06_Iff.lean new file mode 100644 index 0000000..506d103 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L06_Iff.lean @@ -0,0 +1,30 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 9 + +Title "Genau dann wenn" + +Introduction +" +Genau-dann-wenn `A ↔ B` (`\\iff`) besteht aus zwei Implikationen `A → B` und `B → A`. + +Als erstes kann man mit `rw` Annahmen der Form `(h : A ↔ B)` genau gleich wie Gleichungen +`(h : a = b)` benützen, um das Goal umzuschreiben. + +Hier also nochmals die Gleiche Aufgabe, aber diesmal mit Iff-Statements von Aussagen anstatt +Gleichungen von natürlichen Zahlen. +" + +Statement + " + Zeige dass `B ↔ C`. + " + (A B C D : Prop) (h₁ : C ↔ D) (h₂ : A ↔ B) (h₃ : A ↔ D) : B ↔ C := by + rw [h₁] + rw [←h₂] + assumption + +Tactics rw +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L06b_Iff.lean b/server/testgame/TestGame/Levels/Logic/L06b_Iff.lean new file mode 100644 index 0000000..1940f8e --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L06b_Iff.lean @@ -0,0 +1,39 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 10 + +Title "Genau dann wenn" + +Introduction +" +Als nächstes will man oft ein Iff-Statement `A ↔ B` wie zwei einzelne Implikationen +`A → B` und `B → A` behandeln. + +Wenn das Goal `A ↔ B` ist, kann man mit der `constructor` Taktik, dieses in die Einzelteile +`A → B` und `B → A` zerlegen. + +" + +Statement + " + Zeige dass `B ↔ C`. + " + (A B : Prop) (mp : A → B) (mpr : B → A) : A ↔ B := by + constructor + assumption + assumption + + +Conclusion +" +Die Taktik `constructor` heisst so, weil `↔` als \"Struktur\" definiert ist, die +aus mehreren Einzelteilen besteht: `⟨A → B, B → A⟩`. Man sagt also Lean, es soll versuchen, +ob das Goal aus solchen Einzelteilen \"konstruiert\" werden kann. +" + +Tactics constructor +Tactics assumption + +-- TODO : `case mpr =>` ist mathematisch noch sinnvoll. diff --git a/server/testgame/TestGame/Levels/Logic/L06c_Iff.lean b/server/testgame/TestGame/Levels/Logic/L06c_Iff.lean new file mode 100644 index 0000000..30d1b2a --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L06c_Iff.lean @@ -0,0 +1,62 @@ +import TestGame.Metadata +import Std.Tactic.RCases +import Mathlib.Tactic.Cases + +set_option tactic.hygienic false + +Game "TestGame" +World "TestWorld" +Level 11 + +Title "Genau dann wenn" + +Introduction +" +Umgekehrt, wenn man eine Annahme `(h : A ↔ B)` hat, kann man auf verschiedene +Arten die Einzelteile `A → B` und `B → A` extrahieren. + +- mit `rcases h` oder `rcases h with ⟨h₁, h₂⟩` teilt man die Annahme `h` auf. (Im zweiten Fall gibt +man explizit an, wie die neuen Annahmen heissen sollen, die Klammern sind `\\<` und `\\>`). + +" +Statement + (A B : Prop) : (A ↔ B) → (A → B) := by + intro h + rcases h + exact mp + +Message (A : Prop) (B : Prop) : (A ↔ B) → A → B => +"Angefangen mit `intro h` kannst du annehmen, dass `(h : A ↔ B)` wahr ist." + +Conclusion +" +Anstatt +``` +intro h +rcases h with ⟨h₁, h₂⟩ +``` +kann man direkt `intro ⟨h₁, h₂⟩` schreiben. +Wie du schon gesehen hast, sind diese Klammern `⟨⟩` Lean's Syntax für eine Struktur aus +mehreren Teilen. + +" + +Tactics intro apply rcases assumption + + + + + + + +-- TODO: The new `cases` works differntly. There is also `cases'` +example (A B : Prop) : (A ↔ B) → (A → B) := by + intro h + cases h with + | intro a b => + assumption + +example (A B : Prop) : (A ↔ B) → (A → B) := by + intro h + cases' h with a b + assumption diff --git a/server/testgame/TestGame/Levels/Logic/L06d_Iff.lean b/server/testgame/TestGame/Levels/Logic/L06d_Iff.lean new file mode 100644 index 0000000..693c6f2 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L06d_Iff.lean @@ -0,0 +1,33 @@ +import TestGame.Metadata + +Game "TestGame" +World "TestWorld" +Level 12 + +Title "Genau dann wenn" + +Introduction +" +Man kann auch die einzelnen Richtungen benützen, ohne `h` selber zu verändern: + +- `h.1` und `h.2` sind direkt die einzelnen Richtungen. Man kann also z.B. mit `apply h.1` die +Implikation `A → B` auf ein Goal `B` anwenden. +- `h.mp` und `h.mpr` sind die bevorzugten Namen anstatt `.1` und `.2`. \"mp\" kommt von +\"Modus Ponens\", aber das ist hier irrelevant. +" + +Statement + " + Benütze nur `apply` und `assumption` um das gleiche Resultat zu zeigen. + " + (A B C : Prop) (h : A ↔ B) (g : B → C) : A → C := by + intro hA + apply g + apply h.mp + assumption + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ↔ B) (g : B → C) (hA : A) : B => +"Mit `apply h.mp` kannst du nun die Implikation `A → B` anwenden." + +Tactics apply +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L07_And.lean b/server/testgame/TestGame/Levels/Logic/L07_And.lean new file mode 100644 index 0000000..b321755 --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L07_And.lean @@ -0,0 +1,71 @@ +import TestGame.Metadata +import Std.Tactic.RCases + +set_option tactic.hygienic false + +Game "TestGame" +World "TestWorld" +Level 13 + +Title "Und" + +Introduction +" +Das logische UND `A ∧ B` (`\\and`) funktioniert sehr ähnlich zum Iff (`↔`). +Grund dafür ist, dass `A ∧ B` auch eine Struktur aus zwei Teilen `⟨A, B⟩` ist. + +Man can also genau gleich `constructor` und `rcases` anwenden, ebenso kann man +`.1` und `.2` für die Einzelteile brauchen, diese heissen lediglich +`h.left` und `h.right` anstatt `.mp` und `.mpr`. +" + +Statement + (A B : Prop) : (A ∧ (A → B)) ↔ (A ∧ B) := by + constructor + intro h + rcases h with ⟨h₁, h₂⟩ + constructor + assumption + apply h₂ + assumption + intro h + rcases h with ⟨h₁, h₂⟩ + constructor + assumption + intro + assumption + +Message (A : Prop) (B : Prop) : A ∧ (A → B) ↔ A ∧ B => +"`↔` oder `∧` im Goal kann man mit `constructor` aufteilen." + +-- if they don't use `intro ⟨_, _⟩`. +Message (A : Prop) (B : Prop) (h : A ∧ (A → B)) : A ∧ B => +"Jetzt erst mal noch schnell die Annahme `A ∧ (A → B)` mit `rcases` aufteilen." + +-- if they don't use `intro ⟨_, _⟩`. +Message (A : Prop) (B : Prop) (h : A ∧ B) : A ∧ (A → B) => +"Jetzt erst mal noch schnell die Annahme `A ∧ B` mit `rcases` aufteilen." + +Message (A : Prop) (B : Prop) (hA : A) (h : A → B) : A ∧ B => +"Wieder in Einzelteile aufteilen..." + +Message (A : Prop) (B : Prop) : A ∧ (A → B) => +"Immer das gleiche ... noch mehr aufteilen." + +Message (A : Prop) (B : Prop) (h₁: A) (h₂: B) : A → B => +"Das ist jetzt vielleicht etwas verwirrend: Wir wollen die Implikation `A → B` zeigen, +wissen aber, dass `B` immer wahr ist (habe eine Annahme der Form `(hB : B)`). + +Mit intro können wir einfach nochmal annehmen, dass `A` wahr ist. Es stört uns nicht, +dass wir das schon wissen und auch gar nicht brauchen. Damit müssen wir nur noch zeigen, +dass `B` wahr ist." + +Hint (A : Prop) (B : Prop) (hA : A) (h : A → B) : B => +"Sieht nach einem Fall für `apply` aus." + + +-- TODO + + +Tactics apply rcases +Tactics assumption diff --git a/server/testgame/TestGame/Levels/Logic/L08_Or.lean b/server/testgame/TestGame/Levels/Logic/L08_Or.lean new file mode 100644 index 0000000..01c272f --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L08_Or.lean @@ -0,0 +1,25 @@ +import TestGame.Metadata +import Std.Tactic.RCases +import Mathlib.Tactic.LeftRight + +set_option tactic.hygienic false + +Game "TestGame" +World "TestWorld" +Level 14 + +Title "Oder" + +Introduction +" +Das logische ODER `A ∨ B` (`\\or`) funktioniert ein wenig anders als das UND. + +Wenn das Goal ein `∨` ist kann man mit `left` oder `right` entscheiden, +welche Seite man beweisen möchte. +" + +Statement (A B : Prop) (hA : A) : A ∨ (¬ B) := by + left + assumption + +Tactics left right assumption diff --git a/server/testgame/TestGame/Levels/Logic/L08b_Or.lean b/server/testgame/TestGame/Levels/Logic/L08b_Or.lean new file mode 100644 index 0000000..7de302e --- /dev/null +++ b/server/testgame/TestGame/Levels/Logic/L08b_Or.lean @@ -0,0 +1,58 @@ +import TestGame.Metadata +import Std.Tactic.RCases +import Mathlib.Tactic.LeftRight + +set_option tactic.hygienic false + +Game "TestGame" +World "TestWorld" +Level 15 + +Title "Oder" + +Introduction +" +Wenn man hingegen ein ODER - `(h : A ∨ B)` - in den Annahmen hat, kann man dieses +ähnlich wie beim UND mit `rcases h` aufteilen. + +ABER! Beim UND `(h : A ∧ B)` hat man dann zwei neue Annahmen erhalten, und diese hat man mit +`rcases h with ⟨hA, hB⟩` benannt. Beim ODER `(h : A ∨ B)` kriegt man stattdessen zwei **Goals** +wo man annimmt, dass entweder die linke oder rechte Seite von `h` war ist. +Diese Annahme benennt man dann mit `rcases h with hA | hB`. +" + +Statement and_or_imp + "Benutze alle vier Methoden mit UND und ODER umzugehen um folgende Aussage zu beweisen." + (A B C : Prop) (h : (A ∧ B) ∨ (A → C)) (hA : A) : (B ∨ (C ∧ A)) := by + rcases h with h₁ | h₂ + left + rcases h₁ with ⟨x, y⟩ + assumption + right + constructor + apply h₂ + assumption + assumption + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ∧ B ∨ (A → C)) (hA : A) : B ∨ (C ∧ A) => +"Ein ODER in den Annahmen teilt man mit `rcases h with h₁ | h₂`. Der `|` signalisiert +dass `h₁` und `h2` die Namen der neuen Annahmen in den verschiedenen Fällen sind." + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ∧ B) (hA : A) : B ∨ (C ∧ A) => +"Ein ODER im Goal kann mit `left` oder `right` angegangen werden." + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ∧ B) (hA : A) : B => +"Ein UND in den Annahmen kann man mit `rcases h with ⟨h₁, h₂⟩` aufteilen. +Der Konstruktor `⟨⟩` signalisiert hier, dass dann nur ein Goal aber zwei neu benannte +Annahmen erhält." + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ∧ B) : C => +"Sackgasse." + +Message (A : Prop) (B : Prop) (C : Prop) (h : A ∧ B) : C ∧ A => +"Hmmm..." + +Message (A : Prop) (B : Prop) (C : Prop) (h : A → C) : C ∧ A => +"Ein UND im Goal kann mit `constructor` aufgeteilt werden." + +Tactics left right assumption constructor rcases diff --git a/server/testgame/TestGame/Levels/StatementTest.lean b/server/testgame/TestGame/Levels/StatementTest.lean new file mode 100644 index 0000000..3ec654a --- /dev/null +++ b/server/testgame/TestGame/Levels/StatementTest.lean @@ -0,0 +1,38 @@ +import TestGame.Metadata +import Mathlib + +Game "TestGame" +World "TestWorld" +Level 1 + +Title "Annahmen" + +Introduction "yadaa yadaa" + +class MyClass (n : ℕ) where + +Statement name +"Beweise dieses Lemma." +(n m : ℕ) : CommSemigroup ℕ where + mul := fun i j => 0 + mul_comm := sorry + mul_assoc := sorry + +--@[exercise] +instance instTest (n m : ℕ) : CommSemigroup ℕ where + mul := fun i j => 0 + mul_comm := by + sorry + mul_assoc := by + sorry + +--@[exercise] +lemma asdf (a b c d : ℕ) (h₁ : c = d) (h₂ : a = b) (h₃ : a = d) : b = c := by + rewrite [h₁] + rw [←h₂] + assumption + + +Conclusion "" + +Tactics assumption diff --git a/server/testgame/TestGame/Metadata.lean b/server/testgame/TestGame/Metadata.lean index 3882c7f..af1f449 100644 --- a/server/testgame/TestGame/Metadata.lean +++ b/server/testgame/TestGame/Metadata.lean @@ -1,7 +1,8 @@ import GameServer.Commands -import TestGame.MyNat +--import TestGame.MyNat import TestGame.TacticDocs import TestGame.LemmaDocs +import Mathlib.Init.Data.Nat.Basic -- Imports the notation ℕ. Game "TestGame" @@ -10,10 +11,10 @@ Title "The Natural Number Game" Introduction "This is a sad day for mathematics. While trying to find glorious new foundations for mathematics, someone removed the law of excluded middle and the axiom of choice. Unsurprisingly, -everything collapsed. A brave rescue team managed to retrieve our precious axioms from the wreckage +everything collapsed. A brave rescue team managed to retrieve our precious axioms from the wreckage but now we need to rebuild all of mathematics from scratch. -As a beginning mathematics wizard, you've been tasked to rebuild the theory of natural numbers from +As a beginning mathematics wizard, you've been tasked to rebuild the theory of natural numbers from the axioms that Giuseppe Peano found under the collapsed tower of number theory. You've been equipped with a level 1 spell book. Good luck." @@ -31,4 +32,4 @@ World "v4" Path TestWorld → w1 → w2 → w3 Path w1 → v1 → v2 → v3 → w3 -Path v3 → v4 \ No newline at end of file +Path v3 → v4 diff --git a/server/testgame/TestGame/MyNat.lean b/server/testgame/TestGame/MyNat.lean index d30eff2..3cecede 100644 --- a/server/testgame/TestGame/MyNat.lean +++ b/server/testgame/TestGame/MyNat.lean @@ -1,6 +1,6 @@ axiom MyNat : Type -notation "ℕ" => MyNat +--notation "ℕ" => MyNat --axiom zero : ℕ @@ -17,4 +17,3 @@ axiom add_zero : ∀ a : ℕ, a + 0 = a axiom add_succ : ∀ a b : ℕ, a + succ b = succ (a + b) @[elab_as_elim] axiom myInduction {P : ℕ → Prop} (n : ℕ) (h₀ : P 0) (h : ∀ n, P n → P (succ n)) : P n - diff --git a/server/testgame/TestGame/TacticDocs.lean b/server/testgame/TestGame/TacticDocs.lean index 989e92f..9f2dfa4 100644 --- a/server/testgame/TestGame/TacticDocs.lean +++ b/server/testgame/TestGame/TacticDocs.lean @@ -4,29 +4,131 @@ import TestGame.Tactics TacticDoc rfl " -## Summary +## Beschreibung -`rfl` proves goals of the form `X = X`. +`rfl` beweist ein Goal der Form `X = X`. -## Details +## Detail -The `rfl` tactic will close any goal of the form `A = B` -where `A` and `B` are *exactly the same thing*. +`rfl` beweist jedes Goal `A = B` wenn `A` und `B` genau das gleiche sind. +Wichtig ist nicht, ob diese im Infoview gleich aussehen, sondern ob sie in +Lean gleich definiert sind. -### Example: -If it looks like this in the top right hand box: +## Beispiel +`rfl` kann folgenes Goal beweisen: +``` +Objects + a b c : ℕ +Prove: + (a + b) * c = (a + b) * c +``` + +`rfl` kann auch folgendes beweisen: +``` +Objects + n : ℕ +Prove: + 1 + 1 = 2 +``` +denn Lean liest dies intern als `0.succ.succ = 0.succ.succ`. +" + +TacticDoc assumption +" +## Beschreibung + +`assumption` sucht nach einer Annahme, die genau dem Goal entspricht. + +## Beispiel +Wenn das Goal wie folgt aussieht: ``` Objects a b c d : ℕ + h : a + b = c + g : a * b = 16 + t : c = 12 Prove: - (a + b) * (c + d) = (a + b) * (c + d) + a + b = c ``` -then +dann findet `assumption` die Annahme `h`und schliesst den Beweis. +" + +TacticDoc rewrite +" +## Beschreibung + +Wie `rw` aber ruft `rfl` am Schluss nicht automatisch auf. +" + +TacticDoc rw +" +## Beschreibung + +Wenn man eine Annahme `(h : X = Y)` hat, kann man mit +`rw [h]` alle `X` im Goal durch `Y` ersetzen. + +## Detail +- `rw [←h]` wendet `h` rückwärts an und ersetzt alle `Y` durch `X`. +- `rw [h, g, ←f]`: Man kann auch mehrere `rw` zusammenfassen. +- `rw [h] at h₂` ersetzt alle `X` in `h₂` zu `Y` (anstatt im Goal). + +`rw` funktioniert gleichermassen mit Annahmen `(h : X = Y)` also auch +mit Theoremen/Lemmas der Form `X = Y` + +## Beispiel + +TODO +" + + +TacticDoc apply +" +## Beschreibung + +TODO +" + +TacticDoc constructor +" +## Beschreibung + +TODO +" + +TacticDoc rcases +" +## Beschreibung + +TODO +" + +TacticDoc left +" +## Beschreibung + +TODO +" + +TacticDoc right +" +## Beschreibung + +TODO +" + + + + + + + + + + + -`rfl` -will close the goal and solve the level." TacticDoc induction_on " @@ -67,82 +169,9 @@ Prove: ``` " -TacticDoc rewrite -" -## Summary - -If `h` is a proof of `X = Y`, then `rewrite [h],` will change -all `X`s in the goal to `Y`s. Variants: `rewrite [<- h]` (changes -`Y` to `X`) and -`rewrite [h] at h2` (changes `X` to `Y` in hypothesis `h2` instead -of the goal). - -## Details -The `rewrite` tactic is a way to do \"substituting in\". There -are two distinct situations where use this tactics. - -1) If `h : A = B` is a hypothesis (i.e., a proof of `A = B`) -in your local context (the box in the top right) -and if your goal contains one or more `A`s, then `rewrite h` -will change them all to `B`'s. - -2) The `rewrite` tactic will also work with proofs of theorems -which are equalities (look for them in the inventory). -For example, if your inventory contains `add_zero x : x + 0 = x`, -then `rewrite [add_zero]` will change `x + 0` into `x` in your goal -(or fail with an error if Lean cannot find `x + 0` in the goal). - -Important note: if `h` is not a proof of the form `A = B` -or `A ↔ B` (for example if `h` is a function, an implication, -or perhaps even a proposition itself rather than its proof), -then `rewrite` is not the tactic you want to use. For example, -`rewrite [P = Q]` is never correct: `P = Q` is the true-false -statement itself, not the proof. -If `h : P = Q` is its proof, then `rewrite [h]` will work. - -Pro tip 1: If `h : A = B` and you want to change -`B`s to `A`s instead, try `rewrite [<- h]` (get the arrow with `\\l` and -note that this is a small letter L, not a number 1). - -### Example: -If it looks like this in the top right hand box: -``` -Objects - x y : ℕ -Assumptions - h : x = y + y -Prove: - succ (x + 0) = succ (y + y) -``` - -then - -`rewrite [add_zero]` - -will change the goal into `succ x = succ (y + y)`, and then - -`rewrite [h]` - -will change the goal into `succ (y + y) = succ (y + y)`, which -can be solved with `rfl,`. - -### Example: -You can use `rewrite` to change a hypothesis as well. -For example, if your local context looks like this: -``` -Objects - x y : ℕ -Assumptions - h1 : x = y + 3 - h2 : 2 * y = x -Prove: - y = 3 -``` -then `rewrite [h1] at h2` will turn `h2` into `h2 : 2 * y = y + 3`. -" TacticDoc intro "Useful to introduce stuff" -TacticSet basics := rfl induction_on intro rewrite \ No newline at end of file +TacticSet basics := rfl induction_on intro rewrite diff --git a/server/testgame/TestGame/Tactics.lean b/server/testgame/TestGame/Tactics.lean index 31016d1..5e95bfc 100644 --- a/server/testgame/TestGame/Tactics.lean +++ b/server/testgame/TestGame/Tactics.lean @@ -1,5 +1,5 @@ import Lean -import TestGame.MyNat +-- import TestGame.MyNat open Lean Elab Tactic @@ -8,5 +8,5 @@ elab "swap" : tactic => do | g₁::g₂::t => setGoals (g₂::g₁::t) | _ => pure () --- macro "induction_on" n:ident : tactic => --- `(tactic| refine myInduction $n ?base ?inductive_step; swap; clear $n; intro $n $(mkIdent `ind_hyp); swap) \ No newline at end of file +-- macro "induction_on" n:ident : tactic => +-- `(tactic| refine myInduction $n ?base ?inductive_step; swap; clear $n; intro $n $(mkIdent `ind_hyp); swap) diff --git a/server/testgame/lakefile.lean b/server/testgame/lakefile.lean index ff10607..9be3d46 100644 --- a/server/testgame/lakefile.lean +++ b/server/testgame/lakefile.lean @@ -3,6 +3,9 @@ open Lake DSL require GameServer from ".."/"leanserver" +require mathlib from git + "https://github.com/leanprover-community/mathlib4.git"@"b1cf06cb126ee163a7dc895c1aee17946ff20900" + package TestGame @[default_target]