├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .docker └── gitpod │ └── Dockerfile ├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── build_non_main.yml ├── .gitignore ├── .gitpod.yml ├── .i18n ├── config.json ├── de │ ├── Game.json │ └── Game.po └── en │ └── Game.pot ├── .sofi-test-folders ├── Test.lean ├── Test │ ├── L01_once-was-level-02.lean │ ├── L02_once-was-level-01.lean │ ├── L04a_once-was-level-05.lean │ ├── L04b_new_level.lean │ ├── L11_once-was-level-03.lean │ ├── L12_once-was-level-04.lean │ ├── future-level.lean │ └── notes.txt └── version.txt ├── .sofi-test.sh ├── .vscode ├── settings.json └── tasks.json ├── Game.lean ├── Game ├── Levels │ ├── DemoWorld.lean │ └── DemoWorld │ │ └── L01_HelloWorld.lean └── Metadata.lean ├── LICENSE ├── README.md ├── lake-manifest.json ├── lakefile.lean ├── lean-toolchain └── sofi.sh /.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) || echo \"ERROR: `cd ~/lean4game && npm install` failed\", try running it manually in your dev-container!", 16 | "lake_build": "(cd ~/game && lake update -R && lake build) || echo \"ERROR: `cd ~/game && lake update -R && lake exe cache get && lake build` failed!, try running it manually in your dev-container!\"" 17 | }, 18 | "postStartCommand": "(cd ~/lean4game && export VITE_LEAN4GAME_SINGLE=true && npm start) || echo \"ERROR: Did not start game server! See if you have warnings above, then try to start it manually using `cd ~/lean4game && export VITE_LEAN4GAME_SINGLE=true && npm start`!\"", 19 | "customizations": { 20 | "vscode": { 21 | "settings": { 22 | "remote.autoForwardPorts": false 23 | }, 24 | "extensions": [ 25 | "leanprover.lean4" 26 | ] 27 | }, 28 | "codespaces": { 29 | "openFiles": ["Game.lean"] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.lean] 12 | max_line_length = 100 13 | -------------------------------------------------------------------------------- /.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/ .i18n/ -r 56 | 57 | - name: upload compressed game folder 58 | uses: actions/upload-artifact@v4 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 | .i18n/**/*.mo 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.i18n/de/Game.json: -------------------------------------------------------------------------------- 1 | {"level completed! 🎉": "Level gelöst! 🎉", 2 | "level completed with warnings… 🎭": "Level mit Warnungen beendet… 🎭", 3 | "intermediate goal solved! 🎉": "Zwischenziel gelöst! 🎉", 4 | "You should use this game as a template for your own game and add your own levels.": 5 | "Dieses Spiel sollte als Grundgerüst für eigene Spiele verwendet und eigene Levels hinzugefügt werden.", 6 | "You should use `«{h}»` now.": "Jetzt sollte `«{h}»` verwendet werden.", 7 | "You should use `«{g}»` now.": "Jetzt sollte `«{g}»` verwendet werden.", 8 | "You can either start using `«{h}»` or `«{g}»`.": 9 | "Anfangen kann man mit der Benützung von `«{h}»` oder `«{g}»`.", 10 | "This text is shown as first message when the level is played.\nYou can insert hints in the proof below. They will appear in this side panel\ndepending on the proof a user provides.": 11 | "Dieser Text erscheint als erste Nachricht beim Spielen des Levels.\n\"Hints\" können in den folgenden Beweis eingefügt werden. Diese erscheinen hier in diesem Side-Panel,\nabhängig von dem Beweisstand des Spielers.", 12 | "This text appears on the starting page where one selects the world/level to play.\nYou can use markdown.": 13 | "Dieser Text erscheint auf der Startseite, wo man Welten/Levels zum spielen auswählt.\nMan kann Markdown verwenden.", 14 | "This last message appears if the level is solved.": 15 | "Diese letzte Nachricht erscheint beim erfolgreichen lösen eines Levels.", 16 | "This introduction is shown before one enters level 1 of the demo world. Use markdown.": 17 | "Diese Einführung wird gezeigt, bevor man Level 1 der Beispielwelt öffnet. Man kann Markdown verwenden.", 18 | "Here you can put additional information about the game. It is accessible\nfrom the starting through the drop-down menu.\n\nFor example: Game version, Credits, Link to Github and Zulip, etc.\n\nUse markdown.": 19 | "Hier können zusätzliche Inforamtionen über das Spiel hingeschrieben werden. Man erreicht diese\nvon der Startseite via Dropdown-Menü.\n\nZum Beispiel: Spielversion, Credits, Link auf Github und Zulip, etc.\n\nMan kann Markdown verwenden.", 20 | "Hello World Game": "Hallo-Welt-Spiel", 21 | "Hello World": "Hallo Welt", 22 | "Game Template": "Spiel-Grundgerüst", 23 | "Demo World": "Beispielwelt"} -------------------------------------------------------------------------------- /.i18n/de/Game.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: Game v4.7.0\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: Wed Apr 10 15:31:40 2024\n" 6 | "PO-Revision-Date: \n" 7 | "Last-Translator: \n" 8 | "Language-Team: none\n" 9 | "Language: de\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "X-Generator: Poedit 3.0.1\n" 15 | 16 | #: GameServer.RpcHandlers 17 | msgid "level completed! 🎉" 18 | msgstr "Level gelöst! 🎉" 19 | 20 | #: GameServer.RpcHandlers 21 | msgid "level completed with warnings… 🎭" 22 | msgstr "Level mit Warnungen beendet… 🎭" 23 | 24 | #: GameServer.RpcHandlers 25 | msgid "intermediate goal solved! 🎉" 26 | msgstr "Zwischenziel gelöst! 🎉" 27 | 28 | #: Game.Levels.DemoWorld.L01_HelloWorld 29 | msgid "Hello World" 30 | msgstr "Hallo Welt" 31 | 32 | #: Game.Levels.DemoWorld.L01_HelloWorld 33 | msgid "" 34 | "This text is shown as first message when the level is played.\n" 35 | "You can insert hints in the proof below. They will appear in this side " 36 | "panel\n" 37 | "depending on the proof a user provides." 38 | msgstr "" 39 | "Dieser Text erscheint als erste Nachricht beim Spielen des Levels.\n" 40 | "\"Hints\" können in den folgenden Beweis eingefügt werden. Diese erscheinen " 41 | "hier in diesem Side-Panel,\n" 42 | "abhängig von dem Beweisstand des Spielers." 43 | 44 | #: Game.Levels.DemoWorld.L01_HelloWorld 45 | msgid "You can either start using `«{h}»` or `«{g}»`." 46 | msgstr "Anfangen kann man mit der Benützung von `«{h}»` oder `«{g}»`." 47 | 48 | #: Game.Levels.DemoWorld.L01_HelloWorld 49 | msgid "You should use `«{h}»` now." 50 | msgstr "Jetzt sollte `«{h}»` verwendet werden." 51 | 52 | #: Game.Levels.DemoWorld.L01_HelloWorld 53 | msgid "You should use `«{g}»` now." 54 | msgstr "Jetzt sollte `«{g}»` verwendet werden." 55 | 56 | #: Game.Levels.DemoWorld.L01_HelloWorld 57 | msgid "This last message appears if the level is solved." 58 | msgstr "" 59 | "Diese letzte Nachricht erscheint beim erfolgreichen lösen eines Levels." 60 | 61 | #: Game.Levels.DemoWorld 62 | msgid "Demo World" 63 | msgstr "Beispielwelt" 64 | 65 | #: Game.Levels.DemoWorld 66 | msgid "" 67 | "This introduction is shown before one enters level 1 of the demo world. Use " 68 | "markdown." 69 | msgstr "" 70 | "Diese Einführung wird gezeigt, bevor man Level 1 der Beispielwelt öffnet. " 71 | "Man kann Markdown verwenden." 72 | 73 | #: Game 74 | msgid "Hello World Game" 75 | msgstr "Hallo-Welt-Spiel" 76 | 77 | #: Game 78 | msgid "" 79 | "This text appears on the starting page where one selects the world/level to " 80 | "play.\n" 81 | "You can use markdown." 82 | msgstr "" 83 | "Dieser Text erscheint auf der Startseite, wo man Welten/Levels zum spielen " 84 | "auswählt.\n" 85 | "Man kann Markdown verwenden." 86 | 87 | #: Game 88 | msgid "" 89 | "Here you can put additional information about the game. It is accessible\n" 90 | "from the starting through the drop-down menu.\n" 91 | "\n" 92 | "For example: Game version, Credits, Link to Github and Zulip, etc.\n" 93 | "\n" 94 | "Use markdown." 95 | msgstr "" 96 | "Hier können zusätzliche Inforamtionen über das Spiel hingeschrieben werden. " 97 | "Man erreicht diese\n" 98 | "von der Startseite via Dropdown-Menü.\n" 99 | "\n" 100 | "Zum Beispiel: Spielversion, Credits, Link auf Github und Zulip, etc.\n" 101 | "\n" 102 | "Man kann Markdown verwenden." 103 | 104 | #: Game 105 | msgid "Game Template" 106 | msgstr "Spiel-Grundgerüst" 107 | 108 | #: Game 109 | msgid "" 110 | "You should use this game as a template for your own game and add your own " 111 | "levels." 112 | msgstr "" 113 | "Dieses Spiel sollte als Grundgerüst für eigene Spiele verwendet und eigene " 114 | "Levels hinzugefügt werden." 115 | -------------------------------------------------------------------------------- /.i18n/en/Game.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "Project-Id-Version: Game v4.7.0\n" 3 | "Report-Msgid-Bugs-To: \n" 4 | "POT-Creation-Date: Wed Apr 10 15:31:40 2024\n" 5 | "Last-Translator: \n" 6 | "Language-Team: none\n" 7 | "Language: en\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit" 10 | 11 | #: GameServer.RpcHandlers 12 | msgid "level completed! 🎉" 13 | msgstr "" 14 | 15 | #: GameServer.RpcHandlers 16 | msgid "level completed with warnings… 🎭" 17 | msgstr "" 18 | 19 | #: GameServer.RpcHandlers 20 | msgid "intermediate goal solved! 🎉" 21 | msgstr "" 22 | 23 | #: Game.Levels.DemoWorld.L01_HelloWorld 24 | msgid "Hello World" 25 | msgstr "" 26 | 27 | #: Game.Levels.DemoWorld.L01_HelloWorld 28 | msgid "This text is shown as first message when the level is played.\n" 29 | "You can insert hints in the proof below. They will appear in this side panel\n" 30 | "depending on the proof a user provides." 31 | msgstr "" 32 | 33 | #: Game.Levels.DemoWorld.L01_HelloWorld 34 | msgid "You can either start using `«{h}»` or `«{g}»`." 35 | msgstr "" 36 | 37 | #: Game.Levels.DemoWorld.L01_HelloWorld 38 | msgid "You should use `«{h}»` now." 39 | msgstr "" 40 | 41 | #: Game.Levels.DemoWorld.L01_HelloWorld 42 | msgid "You should use `«{g}»` now." 43 | msgstr "" 44 | 45 | #: Game.Levels.DemoWorld.L01_HelloWorld 46 | msgid "This last message appears if the level is solved." 47 | msgstr "" 48 | 49 | #: Game.Levels.DemoWorld 50 | msgid "Demo World" 51 | msgstr "" 52 | 53 | #: Game.Levels.DemoWorld 54 | msgid "This introduction is shown before one enters level 1 of the demo world. Use markdown." 55 | msgstr "" 56 | 57 | #: Game 58 | msgid "Hello World Game" 59 | msgstr "" 60 | 61 | #: Game 62 | msgid "This text appears on the starting page where one selects the world/level to play.\n" 63 | "You can use markdown." 64 | msgstr "" 65 | 66 | #: Game 67 | msgid "Here you can put additional information about the game. It is accessible\n" 68 | "from the starting through the drop-down menu.\n" 69 | "\n" 70 | "For example: Game version, Credits, Link to Github and Zulip, etc.\n" 71 | "\n" 72 | "Use markdown." 73 | msgstr "" 74 | 75 | #: Game 76 | msgid "Game Template" 77 | msgstr "" 78 | 79 | #: Game 80 | msgid "You should use this game as a template for your own game and add your own levels." 81 | msgstr "" 82 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.Test.old-level-01 2 | import Game.Levels.Test.old-level-02 3 | import Game.Levels.Test.old-level-03 4 | import Game.Levels.Test.old-level-04 5 | import Game.Levels.Test.old-level-05 6 | 7 | 8 | 9 | Some additional lines: 10 | 1. ... 11 | 2. ... 12 | 3. ... 13 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L01_once-was-level-02.lean: -------------------------------------------------------------------------------- 1 | -- once was: level 2 2 | -- should become: level 1 3 | -- still imports: old level 1 4 | -- should import: nothing 5 | import Game.Levels.Test.old-level-01 6 | World "Test" 7 | Level 2 8 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L02_once-was-level-01.lean: -------------------------------------------------------------------------------- 1 | -- once was: level 1 2 | -- should become: level 2 3 | -- should retain all of the following imports without change 4 | -- should not acquire any new imports 5 | 6 | import Game.Metadata 7 | import Game.Levels.Babylon 8 | 9 | import Mathlib.Data.Matrix.Basic 10 | import Mathlib.Data.Real.Basic 11 | 12 | World "Test" 13 | Level 1 -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L04a_once-was-level-05.lean: -------------------------------------------------------------------------------- 1 | -- once was: level 5 2 | -- should become: level 3 3 | -- still imports: old level 3 4 | -- should import: new level 2 5 | import Game.Levels.Test.old-level-03 6 | 7 | World "Test" 8 | Level 5 9 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L04b_new_level.lean: -------------------------------------------------------------------------------- 1 | -- this is a new level, without any imports -- 2 | -- should become: level 4 3 | -- currently imports: nothing 4 | -- should import: nothing 5 | 6 | World "Test" 7 | Level ??? 8 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L11_once-was-level-03.lean: -------------------------------------------------------------------------------- 1 | -- once was: level 3 2 | -- should become: level 5 3 | -- still imports: old level 2 4 | -- should import: new level 4 5 | import Game.Levels.Test.old-level-05 6 | 7 | World "Test" 8 | Level 3 9 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/L12_once-was-level-04.lean: -------------------------------------------------------------------------------- 1 | -- once was: level 4 2 | -- should become: level 6 3 | -- still imports: old level 3 4 | -- should import: new level 5 5 | import Game.Levels.Test.old-level-03 6 | 7 | World "Test" 8 | Level 4 9 | -------------------------------------------------------------------------------- /.sofi-test-folders/Test/future-level.lean: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhu-adam/GameSkeleton/60d7593b1db83b1d5084e6705839855f52cefcac/.sofi-test-folders/Test/future-level.lean -------------------------------------------------------------------------------- /.sofi-test-folders/Test/notes.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhu-adam/GameSkeleton/60d7593b1db83b1d5084e6705839855f52cefcac/.sofi-test-folders/Test/notes.txt -------------------------------------------------------------------------------- /.sofi-test-folders/version.txt: -------------------------------------------------------------------------------- 1 | [2025-03-14] 2 | -------------------------------------------------------------------------------- /.sofi-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # version [2025-03-14] 4 | 5 | # This is just a short auxiliary script to that tests very basic functionality 6 | # of sofi.sh. It should be used in conjunction with the folder 7 | # .sofi-test-folders 8 | 9 | time="$(date +"%Y-%m-%d--%H-%M-%S")" 10 | testdir=".sofi-test_${time}" 11 | cp -r ./.sofi-test-folders ./$testdir 12 | echo -e "\nTEST1: Running sofi.sh without a parameter should result in an error." 13 | ./sofi.sh 14 | echo -e "\nTEST2: Running sofi.sh with a folder as parameter that does not have an accompanying .lean file should result in an error." 15 | ./sofi.sh "./${testdir}/BadTest" 16 | echo -e "\nTEST3: Running sofi.sh with a file as parameter should result in an error." 17 | ./sofi.sh "./${testdir}/Test.lean" 18 | echo -e "\nTEST4 (Correct usage of sofi.sh):" 19 | ./sofi.sh "./${testdir}/Test" 20 | -------------------------------------------------------------------------------- /.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 | import Game.Levels.DemoWorld 2 | 3 | -- Here's what we'll put on the title screen 4 | Title "Hello World Game" 5 | Introduction 6 | " 7 | This text appears on the starting page where one selects the world/level to play. 8 | You can use markdown. 9 | " 10 | 11 | Info " 12 | Here you can put additional information about the game. It is accessible 13 | from the starting through the drop-down menu. 14 | 15 | For example: Game version, Credits, Link to Github and Zulip, etc. 16 | 17 | Use markdown. 18 | " 19 | 20 | /-! Information to be displayed on the servers landing page. -/ 21 | Languages "English" 22 | CaptionShort "Game Template" 23 | CaptionLong "You should use this game as a template for your own game and add your own levels." 24 | -- Prerequisites "" -- add this if your game depends on other games 25 | -- CoverImage "images/cover.png" 26 | 27 | /-! Build the game. Show's warnings if it found a problem with your game. -/ 28 | MakeGame 29 | -------------------------------------------------------------------------------- /Game/Levels/DemoWorld.lean: -------------------------------------------------------------------------------- 1 | import Game.Levels.DemoWorld.L01_HelloWorld 2 | 3 | World "DemoWorld" 4 | Title "Demo World" 5 | 6 | Introduction " 7 | This introduction is shown before one enters level 1 of the demo world. Use markdown. 8 | " 9 | -------------------------------------------------------------------------------- /Game/Levels/DemoWorld/L01_HelloWorld.lean: -------------------------------------------------------------------------------- 1 | import Game.Metadata 2 | 3 | World "DemoWorld" 4 | Level 1 5 | 6 | Title "Hello World" 7 | 8 | Introduction "This text is shown as first message when the level is played. 9 | You can insert hints in the proof below. They will appear in this side panel 10 | depending on the proof a user provides." 11 | 12 | Statement (h : x = 2) (g: y = 4) : x + x = y := by 13 | Hint "You can either start using `{h}` or `{g}`." 14 | Branch 15 | rw [g] 16 | Hint "You should use `{h}` now." 17 | rw [h] 18 | rw [h] 19 | Hint "You should use `{g}` now." 20 | rw [g] 21 | 22 | Conclusion "This last message appears if the level is solved." 23 | 24 | /- Use these commands to add items to the game's inventory. -/ 25 | 26 | NewTactic rw rfl 27 | -- NewTheorem Nat.add_comm Nat.add_assoc 28 | -- NewDefinition Nat Add Eq 29 | -------------------------------------------------------------------------------- /Game/Metadata.lean: -------------------------------------------------------------------------------- 1 | import GameServer.Commands 2 | 3 | -- import Mathlib.Tactic.Common 4 | 5 | /-! Use this file to add things that should be available in all levels. 6 | 7 | For example, this demo imports the mathlib tactics 8 | 9 | *Note*: As long as `Game.lean` exists and ends with the `MakeGame` command, 10 | you are completely free how you structure your lean project, this is merely 11 | a suggestion. 12 | 13 | *Bug*: However, things are bugged out if the levels of different worlds are imported 14 | in a random order. Therefore, you should keep the structure of one file lean file per world 15 | that imports all its levels. 16 | -/ 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Game Skeleton 2 | 3 | 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. 4 | 5 | The documentation about how to use this template are at the [lean4game repository](https://github.com/leanprover-community/lean4game/): 6 | 7 | * [Creating a new game](https://github.com/leanprover-community/lean4game/blob/main/doc/create_game.md) 8 | * [Updating an existing game](https://github.com/leanprover-community/lean4game/blob/main/doc/update_game.md) 9 | * [Running a game locally](https://github.com/leanprover-community/lean4game/blob/main/doc/running_locally.md) 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | "name": "Game", 50 | "lakeDir": ".lake"} 51 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sofi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # version [2025-03-14] 4 | 5 | # PURPOSE: 6 | # 7 | # The purpose of this script is to make it easier to insert new levels and/or 8 | # reorder levels in an existing planet/world, by simply renaming the files in 9 | # such a way that the alphabetical order of the filenames reflects the intended 10 | # order of the levels in the game. 11 | # 12 | # For example, suppose we have world "Arithmetic" with three levels, and with 13 | # one additional file that is not ready for deployment yet: 14 | # 15 | # Arithmetic/L01_hello.lean 16 | # Arithmetic/L02_multiply.lean 17 | # Arithmetic/L03_add.lean 18 | # Arithmetic/possible_future_level.lean 19 | # 20 | # It is mandatory that the file names have the format "L**.lean" or 21 | # "L**_*****.lean", where "*" are arbitrary symbols other than "_" and ".". To 22 | # switch the order of levels L02 and L03 in this example, and to insert an 23 | # additional level on sustraction between them, use the following steps: 24 | # 25 | # STEP 1: Create a new file for the additional level and rename files as 26 | # follows: 27 | # 28 | # Arithmetic/L01_hello.lean 29 | # Arithmetic/L02a_add.lean (renamed L03 file) 30 | # Artihmetic/L02b_substract.lean (new file) 31 | # Arithmetic/L03_multiply.lean (renamed L02 file) 32 | # Arithmetic/possible_future_level.lean 33 | # 34 | # The file names should still have the general format specified above. Any 35 | # part of the filename between "_" and ".lean" will be left intact by the 36 | # script. The alphabetical order of the file name should reflect the 37 | # intended order in the game. 38 | # 39 | # STEP 2: To any new level, add a least a line 40 | # 41 | # Level ??" 42 | # 43 | # There should be no whitespace at the beginning of the line. The 44 | # characters "??" are optional -- the script will replace them by the 45 | # correct level number. Also, in case you'd like to import the previous 46 | # level, add the line 47 | # 48 | # import Game.Levels.Arithmetic.?? 49 | # 50 | # Again, there should be no whitespace at the beginning of the line, and 51 | # again, the characters ".??" are optional. The script will replace them 52 | # with the file name of the previous level. 53 | # 54 | # STEP 3: Execute the script by calling 55 | # 56 | # $ ./sofi /some_path/Game/Levels/Arithmetic 57 | # 58 | # The script will rename (renumber) the active level files in their 59 | # alphabetical order: 60 | # 61 | # Arithmetic/L01_hello.lean 62 | # Arithmetic/L02_add.lean (renamed file) 63 | # Artihmetic/L03_substract.lean (renamed file) 64 | # Arithmetic/L04_multiply.lean (renamed file) 65 | # Arithmetic/possible_future_level.lean 66 | # 67 | # Moreover, it will do the following: 68 | # 69 | # - Update the level number in each file. 70 | # 71 | # - In every level n > 1, replace the first import of a levels from the 72 | # current world with an import of level n-1. In level n = 1, remove all 73 | # imports of levels from the current world. 74 | # 75 | # - Update the base file for the world, e.g. the file Arithmetic.lean in the 76 | # above example, to import all active level files. 77 | 78 | ################################################################################ 79 | # Boiler plate code to check that path to a world folder is passed as an 80 | # argument: 81 | 82 | if ! [ -n "$1" ]; then 83 | echo "ERROR: Please specify a world folder." >&2 84 | exit 85 | fi 86 | 87 | path=$(realpath "$1" 2>/dev/null) 88 | 89 | if ! ([ -n "$path" ] && [ -e "$path" ]); then 90 | printf "ERROR: The specified world folder\n %s\ndoes not exist.\n" $path >&2 91 | exit 92 | fi 93 | 94 | if ! ([ -d "$path" ]); then 95 | printf "ERROR: The specified world folder\n %s\nis not recognized as a folder -- it appears to be a regular file.\n" $path >&2 96 | exit 97 | fi 98 | 99 | if ! ([ -e "${path}.lean" ]); then 100 | printf "ERROR: The specified world folder\n %s\ndoes not have an accompanying file\n %s\n" "${path}" "${path}.lean" >&2 101 | exit 102 | fi 103 | 104 | world_path=$(dirname "$path") 105 | world_name=$(basename "$path") 106 | 107 | #if ! [[ "$world_path" =~ ^(.*)/Game/Levels$ ]]; then 108 | # printf "ERROR: The specified folder\n %s\ndoes not look like a world folder.\n" $path >&2 109 | # printf "World folders are expected to be direct subfolders of\n '.../Game/Levels/'\n" >&2 110 | # exit 111 | #else 112 | printf "%s\n" \ 113 | "This script will make a number of changes to folder" \ 114 | " ${path}" \ 115 | "and to the associated file" \ 116 | " ${path}.lean " \ 117 | "It is recommended that you git commit all local " \ 118 | "changes before running the script. " 119 | #fi 120 | read -p "Are you sure you want to proceed?" 121 | 122 | ################################################################################ 123 | # Read names of existing level files and count them: 124 | 125 | cd ${world_path}/${world_name} 126 | readarray -t old_names < <(sort < <(find ./L*.lean)) 127 | # sort might be superfluous here 128 | # printf '%s\n' "${old_names[@]}" 129 | 130 | number_of_files=${#old_names[@]} 131 | #echo "$number_of_files"" level files found" 132 | 133 | ################################################################################ 134 | # Rename level files: 135 | 136 | echo "Renaming files ..." 137 | new_names=("${old_names[@]}") 138 | for (( n=0; n<${number_of_files}; n++ )); 139 | do 140 | old_names[$n]="$(echo ${old_names[$n]} | sed 's,^./,,')" # remove leading ./ in all level names 141 | old_names[$n]="$(echo ${old_names[$n]} | sed 's,.lean$,,')" # remove trailing .lean in all level names 142 | temp_name="$(echo ${old_names[$n]} | sed 's,L[^_]*,,')" # temporarily also remove leading L???_ 143 | new_names[$n]="$(printf 'L%02d%s\n' $((n+1)) "${temp_name}")" 144 | ## Display planned renaming scheme: 145 | compstr="===" 146 | [ "${old_names[$n]}" != "${new_names[$n]}" ] && compstr="-->" 147 | printf " %-20s %s %s\n" ${old_names[$n]} ${compstr} ${new_names[$n]} 148 | done 149 | #read -p "Proceed with renaming?" 150 | 151 | # Need to do renaming in two steps in case some of the new names match some of 152 | # the old names. For example, the following renaming 153 | # 154 | # L00.lean --> L01.lean 155 | # L01.lean --> L02.lean 156 | # 157 | # will not work file by file, because the renamed L00.lean will overwrite the 158 | # existing file L01.lean. So instead, we create a temporary subfolder 159 | # ./sofi-temp, rename and simultaneously move each file into that folder, and 160 | # then move all files back again. 161 | mkdir "${world_path}/${world_name}/.sofi-temp" 162 | for (( n=0; n<${number_of_files}; n++ )); 163 | do 164 | mv "${world_path}/${world_name}/${old_names[$n]}.lean" "${world_path}/${world_name}/.sofi-temp/${new_names[$n]}.lean" 165 | done 166 | for (( n=0; n<${number_of_files}; n++ )); 167 | do 168 | mv "${world_path}/${world_name}/.sofi-temp/${new_names[$n]}.lean" "${world_path}/${world_name}/${new_names[$n]}.lean" 169 | done 170 | rm -d "${world_path}/${world_name}/.sofi-temp" 171 | 172 | ################################################################################ 173 | # Edit the level files -- update level numbers and imports 174 | 175 | for (( n=0; n<${number_of_files}; n++ )); #loop over all levels n 176 | do 177 | FILE="${world_path}/${world_name}/${new_names[$n]}.lean" 178 | echo "Updating ${FILE} ..." 179 | # Update "level number": 180 | echo " Updating level number ..." 181 | sed -i "s/^Level.*$/Level $((n+1))/" "$FILE" 182 | # Replace import of old level k with import of new level k, provided k < n: 183 | for (( k=0; k<$n; k++)); #loop over all levels k < n 184 | do 185 | # echo "replace: import Game.Levels.${world_name}.${old_names[$k]}" 186 | # echo "by: import Game.Levels.${world_name}.${new_names[$k]}" 187 | sed -i "s/^import Game\.Levels\.${world_name}\.${old_names[$k]}[[:space:]]*$/import Game.Levels.${world_name}.${new_names[$k]}/" "$FILE" 188 | done 189 | echo " Updating imports ..." 190 | # Delete import of old level k if k > n: 191 | for (( k=$((n+1)); k<${number_of_files}; k++)); 192 | do 193 | # echo "delete: import Game.Levels.${world_name}.${old_names[$k]}" 194 | sed -i "s/^import Game.Levels.${world_name}.${old_names[$k]}[[:space:]]*$//" "$FILE" 195 | done 196 | # In level 0, delete imports from current world. In higher levels, replace 197 | # first import of level from current world with import of level n-1. 198 | if (( n == 0 )); then 199 | sed -i "/^import Game\.Levels\.${world_name}.*$/d" "$FILE" 200 | # Test this sed syntax with: 201 | # echo -e "import Game.Levels.abc\nQVM\nimport Game.Levels.qvm" | sed "/^import Game\.Levels.*$/d" 202 | else 203 | sed -i "0,/^import Game\.Levels\.${world_name}.*$/s/^import Game\.Levels\.${world_name}.*$/import Game.Levels.${world_name}.${new_names[$((n-1))]}/" "$FILE" 204 | # Test with sed syntax with: 205 | # echo -e "import Game.Levels.abc\nQVM\nimport Game.Levels.qvm" | sed "0,/^import Game\.Levels\.${world_name}.*$/s/^import Game\.Levels.*$/Super/" 206 | fi 207 | done 208 | 209 | ################################################################################ 210 | # Update $world_name.lean file 211 | FILE="${world_path}/${world_name}.lean" 212 | echo "Updating imports in ${FILE} ..." 213 | 214 | # delete all existing imports: 215 | sed -i '/^import /d' "$FILE" 216 | new_imports="" 217 | for (( n=0; n<${number_of_files}; n++)); 218 | do 219 | new_imports=$new_imports"import Game.Levels.${world_name}.${new_names[$n]}\n" 220 | done 221 | # add imports of all active levels at beginning of file: 222 | echo -e "$new_imports""$(cat $FILE)" > $FILE 223 | 224 | echo "Done." 225 | 226 | ################################################################################ 227 | --------------------------------------------------------------------------------