Game version in the menu: VERSION holds vYYYYMM-NNNNN, shown on the title screen and in the pause menu, tools/bump_version.py raises it with every change

This commit is contained in:
Richard Zink committed 2026-11-04 09:57:10 +01:00
1 parent 1ec7492189
commit dd4bb75b24
9 files changed
+86 -1

No files matched your search

+3
View File
@@ -1,2 +1,5 @@
target/ target/
Cargo.lock.bak Cargo.lock.bak
# WASM build outputs copied into www/ for the standalone file (the Docker image builds its own)
www/dungeoncraft.wasm
www/mq_js_bundle.js
+1
View File
@@ -4,6 +4,7 @@ RUN rustup target add wasm32-unknown-unknown
WORKDIR /src WORKDIR /src
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY .cargo .cargo COPY .cargo .cargo
COPY VERSION ./
# build dependencies first (Docker layer cache) # build dependencies first (Docker layer cache)
RUN mkdir src && echo "fn main() {}" > src/main.rs \ RUN mkdir src && echo "fn main() {}" > src/main.rs \
&& cargo build --release --target wasm32-unknown-unknown \ && cargo build --release --target wasm32-unknown-unknown \
+11
View File
@@ -116,6 +116,16 @@ A forgotten password is set again on the server with `docker compose stop saves
All requests except health and login need `Authorization: Bearer <token>`. Limits: 1 MB per savegame, 500 MB in total, 30 write requests and 20 login attempts per minute. Settings: `SAVE_DIR` (default `/data`), `LISTEN` (default `0.0.0.0:8080`). All requests except health and login need `Authorization: Bearer <token>`. Limits: 1 MB per savegame, 500 MB in total, 30 write requests and 20 login attempts per minute. Settings: `SAVE_DIR` (default `/data`), `LISTEN` (default `0.0.0.0:8080`).
## Version
`VERSION` holds the game version as `vYYYYMM-NNNNN`: year and month of the change, then the number of
changes in that month (`v202611-00001`). The title screen and the pause menu show it, `src/menu.rs`
reads the file with `include_str!`, and a test checks the scheme.
**With every change to the game** run `python3 tools/bump_version.py` before committing: it raises the
number by one (a new month starts again at `00001`) and prints the old and the new version. Do not edit
`VERSION` by hand.
## Adding a language ## Adding a language
1. Copy `lang/en.lang` to `lang/<code>.lang` and translate the texts (placeholders `{0}`, `{1}` stay as they are). 1. Copy `lang/en.lang` to `lang/<code>.lang` and translate the texts (placeholders `{0}`, `{1}` stay as they are).
@@ -129,3 +139,4 @@ All requests except health and login need `Authorization: Bearer <token>`. Limit
| `play/DungeonCraft.html` | build the WASM file, then `python3 tools/make_standalone.py <www dir with dungeoncraft.wasm and mq_js_bundle.js> play/DungeonCraft.html` (the Docker image builds the same file under `/standalone/`) | | `play/DungeonCraft.html` | build the WASM file, then `python3 tools/make_standalone.py <www dir with dungeoncraft.wasm and mq_js_bundle.js> play/DungeonCraft.html` (the Docker image builds the same file under `/standalone/`) |
| `docs/img/units`, `docs/img/icons` | `cargo test --release export_doc_images -- --ignored` | | `docs/img/units`, `docs/img/icons` | `cargo test --release export_doc_images -- --ignored` |
| `BESTIARY.md` | `python3 tools/make_bestiary.py` | | `BESTIARY.md` | `python3 tools/make_bestiary.py` |
| `VERSION` | `python3 tools/bump_version.py` - with every change to the game |
+1
View File
@@ -0,0 +1 @@
v202611-00002
+1
View File
@@ -201,6 +201,7 @@ langsel.hint = Klicke eine Sprache an. Du kannst sie später in den Einstellunge
# ---- Menüs # ---- Menüs
menu.subtitle = Graben. Bauen. Verteidigen. Erobern. menu.subtitle = Graben. Bauen. Verteidigen. Erobern.
menu.version = Version
menu.continue = Fortsetzen menu.continue = Fortsetzen
menu.new = Neues Spiel menu.new = Neues Spiel
menu.load = Spiel laden menu.load = Spiel laden
+1
View File
@@ -202,6 +202,7 @@ langsel.hint = Click a language. You can change it later in the settings.
# ---- menus # ---- menus
menu.subtitle = Dig. Build. Defend. Conquer. menu.subtitle = Dig. Build. Defend. Conquer.
menu.version = Version
menu.continue = Continue menu.continue = Continue
menu.new = New Game menu.new = New Game
menu.load = Load Game menu.load = Load Game
+1 -1
View File
@@ -17,7 +17,7 @@
<script>var register_plugin; /* expected by the quad_net plugin in the bundle */</script> <script>var register_plugin; /* expected by the quad_net plugin in the bundle */</script>
<script> <script>
// the game module is embedded: fetching it is answered from these bytes // the game module is embedded: fetching it is answered from these bytes
var DC_WASM = "AGFzbQEAAAAB2wZnYAF/AGAAAGADf39/AX9gA39/fwBgBn9/f39/fwBgBX9/f39/AGAEf39/fwBgAn9/AGAHf39/f39/fwF/YAJ/fwF/YAF/AX9gBH9/f38Bf2AFf39/f38Bf2ADf319AGAFf319fX0AYAd/fX19fX19AGAGf39/fX9/AGAGf39/f39/AX9gAAF8YAABf2AAAX1gCH9/f39/f39/AGAJf39/f39/f39/AGAHf39/f39/fwBgBH19fX0AYAF9AGAKf39/f39/f39/fwBgBH5+f38BfmADfn5/AX5gAX4Bf2AEf39/fQF/YAJ+fwF/YAJ/fQBgAn1/AX9gBn99fXx/fwBgB39/f39/f3wAYAV/f39/fABgC39/f39/f399fX98AGAGf39/f319AGAEf399fQBgBX9/fX1/AGAHf39/fX1/fABgB39/f39/fX0AYAN/f30AYAV/f398fwBgDX9/fX19f39/f39/f38AYAV9fX19fwBgBn19fX19fwBgBX99fX9/AGAHf39/fX19fwBgBn9/f398fwBgCH9/f39/f398AGAJf319f399f39/AGAMf319f399f39/f39/AGABfwF9YAl/f39/fX19f30AYAZ/f399fX8Bf2AFf39/fX0AYAJ9fQF/YAJ/fgBgB399fX99f30AYAN/f30BfWAHf399fX1/fwBgAn19AX1gCH9/f399fX19AGAJf399fX1/f39/AGACf3wAYAV/fX9/fwBgCH9/fX19fX1/AGAEfX1/fQBgBH9/fX8AYAN/fX8AYAR/fX9/AGAKf39/f39/f399fQBgBn9/fX19fQF/YAN/f30Bf2ACf38BfWAIf399f39/f38AYAV/f319fwF/YAR/f399AGAEf39/fgBgBX9/f35/AGAHf319fX1/fwBgBH9/fX0BfWATf39/f39/f39/f39/f39/f39/fwF/YAF/AX5gBn9+f39/fwBgCX9/f39/f35+fgBgBH99f38Bf2ADf31/AX9gA39+fgBgC39/f39/f39/f39/AX9gAAF+YAJ/fgF/YAN+fn4BfmAIf39/f39/f38Bf2AFf39+fX0AYAN/fn8AYAt/f39/f39/f39/fwBgAnx/AX9gAX0BfWAFf35+fn4AYAJ9fwF9AtcUegNlbnYLY29uc29sZV9sb2cAAANlbnYHZGNfaHR0cAARA2VudgxkY19zZXRfdG9rZW4ABwNlbnYDbm93ABIDZW52EWRjX2NvYXJzZV9wb2ludGVyABMDZW52DGRjX2ZpbGVfcG9sbAATA2VudgxkY19maWxlX3Rha2UAAANlbnYMZGNfaHR0cF9wb2xsAAoDZW52C2RjX2h0dHBfbGVuAAoDZW52DGRjX2h0dHBfdGFrZQAHA2Vudg5kY19zdG9yYWdlX3NldAAGA2VudhdkY19mZXRjaF9kb3dubG9hZF9zdGF0ZQATA2VudgtkY19kb3dubG9hZAAGA2VudgxkY19vcGVuX2ZpbGUAAQNlbnYLZGNfb3Blbl91cmwABwNlbnYMZGNfY2FjaGVfc2V0AAYDZW52EWRjX2ZldGNoX2Rvd25sb2FkAAYDZW52CmluaXRfd2ViZ2wAAANlbnYRc2V0dXBfY2FudmFzX3NpemUAAANlbnYMY2FudmFzX3dpZHRoABMDZW52DWNhbnZhc19oZWlnaHQAEwNlbnYJZHBpX3NjYWxlABQDZW52EnJ1bl9hbmltYXRpb25fbG9vcAAAA2Vudg5kY19zdG9yYWdlX2dldAAJA2Vudg9kY19zdG9yYWdlX3Rha2UAAANlbnYJZGNfcHJvbXB0AAsDZW52DWdsQmluZFRleHR1cmUABwNlbnYQZ2xDb3B5VGV4SW1hZ2UyRAAVA2Vudg1nbEdldEludGVnZXJ2AAcDZW52EWdsR2VuVmVydGV4QXJyYXlzAAcDZW52EWdsQmluZFZlcnRleEFycmF5AAADZW52C2dsR2V0U3RyaW5nAAoDZW52FHNhcHBfc2NoZWR1bGVfdXBkYXRlAAEDZW52DGdsR2VuUXVlcmllcwAHA2VudgxnbEJlZ2luUXVlcnkABwNlbnYKZ2xFbmRRdWVyeQAAA2VudgdnbEZsdXNoAAEDZW52CGdsRmluaXNoAAEDZW52DGNvbnNvbGVfd2FybgAAA2Vudg9nbENyZWF0ZVByb2dyYW0AEwNlbnYOZ2xBdHRhY2hTaGFkZXIABwNlbnYNZ2xMaW5rUHJvZ3JhbQAAA2Vudg5nbERldGFjaFNoYWRlcgAHA2Vudg5nbERlbGV0ZVNoYWRlcgAAA2Vudg5nbEdldFByb2dyYW1pdgADA2VudhNnbEdldFByb2dyYW1JbmZvTG9nAAYDZW52DGdsVXNlUHJvZ3JhbQAAA2VudhRnbEdldFVuaWZvcm1Mb2NhdGlvbgAJA2Vudg1nbEdlblRleHR1cmVzAAcDZW52D2dsQWN0aXZlVGV4dHVyZQAAA2Vudg1nbFBpeGVsU3RvcmVpAAcDZW52EmdsR2VuUmVuZGVyYnVmZmVycwAHA2VudhJnbEJpbmRSZW5kZXJidWZmZXIABwNlbnYgZ2xSZW5kZXJidWZmZXJTdG9yYWdlTXVsdGlzYW1wbGUABQNlbnYMZ2xUZXhJbWFnZTJEABYDZW52D2dsVGV4UGFyYW1ldGVyaQADA2VudhBnbEdlbmVyYXRlTWlwbWFwAAADZW52EWdsR2VuRnJhbWVidWZmZXJzAAcDZW52EWdsQmluZEZyYW1lYnVmZmVyAAcDZW52FmdsRnJhbWVidWZmZXJUZXh0dXJlMkQABQNlbnYMZ2xSZWFkUGl4ZWxzABcDZW52FGdsRGVsZXRlRnJhbWVidWZmZXJzAAcDZW52D2dsVGV4U3ViSW1hZ2UyRAAWA2VudhlnbEZyYW1lYnVmZmVyUmVuZGVyYnVmZmVyAAYDZW52DWdsRHJhd0J1ZmZlcnMABwNlbnYYZ2xDaGVja0ZyYW1lYnVmZmVyU3RhdHVzAAoDZW52FWdsRGVsZXRlUmVuZGVyYnVmZmVycwAHA2VudhBnbERlbGV0ZVRleHR1cmVzAAcDZW52E2dsR2V0QXR0cmliTG9jYXRpb24ACQNlbnYIZ2xFbmFibGUAAANlbnYJZ2xEaXNhYmxlAAADZW52C2dsRGVwdGhGdW5jAAADZW52C2dsRnJvbnRGYWNlAAADZW52CmdsQ3VsbEZhY2UAAANlbnYTZ2xCbGVuZEZ1bmNTZXBhcmF0ZQAGA2VudhdnbEJsZW5kRXF1YXRpb25TZXBhcmF0ZQAHA2VudgtnbEJsZW5kRnVuYwAHA2VudhNnbFN0ZW5jaWxPcFNlcGFyYXRlAAYDZW52FWdsU3RlbmNpbEZ1bmNTZXBhcmF0ZQAGA2VudhVnbFN0ZW5jaWxNYXNrU2VwYXJhdGUABwNlbnYLZ2xDb2xvck1hc2sABgNlbnYMZ2xHZW5CdWZmZXJzAAcDZW52DGdsQmluZEJ1ZmZlcgAHA2VudgxnbEJ1ZmZlckRhdGEABgNlbnYPZ2xCdWZmZXJTdWJEYXRhAAYDZW52D2dsRGVsZXRlQnVmZmVycwAHA2VudhpnbERpc2FibGVWZXJ0ZXhBdHRyaWJBcnJheQAAA2Vudg9nbERlbGV0ZVByb2dyYW0AAANlbnYKZ2xWaWV3cG9ydAAGA2VudglnbFNjaXNzb3IABgNlbnYLZ2xVbmlmb3JtMWkABwNlbnYWZ2xWZXJ0ZXhBdHRyaWJJUG9pbnRlcgAFA2VudhVnbFZlcnRleEF0dHJpYlBvaW50ZXIABANlbnYVZ2xWZXJ0ZXhBdHRyaWJEaXZpc29yAAcDZW52GWdsRW5hYmxlVmVydGV4QXR0cmliQXJyYXkAAANlbnYMZ2xVbmlmb3JtMWZ2AAMDZW52DGdsVW5pZm9ybTJmdgADA2VudgxnbFVuaWZvcm0zZnYAAwNlbnYMZ2xVbmlmb3JtNGZ2AAMDZW52DGdsVW5pZm9ybTFpdgADA2VudgxnbFVuaWZvcm0yaXYAAwNlbnYMZ2xVbmlmb3JtM2l2AAMDZW52DGdsVW5pZm9ybTRpdgADA2VudhJnbFVuaWZvcm1NYXRyaXg0ZnYABgNlbnYMZ2xDbGVhckNvbG9yABgDZW52DWdsQ2xlYXJEZXB0aGYAGQNlbnYOZ2xDbGVhclN0ZW5jaWwAAANlbnYHZ2xDbGVhcgAAA2VudgxnbFJlYWRCdWZmZXIAAANlbnYRZ2xCbGl0RnJhbWVidWZmZXIAGgNlbnYXZ2xEcmF3RWxlbWVudHNJbnN0YW5jZWQABQNlbnYOZ2xDcmVhdGVTaGFkZXIACgNlbnYOZ2xTaGFkZXJTb3VyY2UABgNlbnYPZ2xDb21waWxlU2hhZGVyAAADZW52DWdsR2V0U2hhZGVyaXYAAwNlbnYSZ2xHZXRTaGFkZXJJbmZvTG9nAAYDZW52EnNhcHBfc2V0X2NsaXBib2FyZAAHA2VudhJmc19nZXRfYnVmZmVyX3NpemUACgNlbnYOZnNfdGFrZV9idWZmZXIAAwNlbnYUc2FwcF9zZXRfY3Vyc29yX2dyYWIAAANlbnYPc2FwcF9zZXRfY3Vyc29yAAcDZW52E3NhcHBfc2V0X2Z1bGxzY3JlZW4AAAOqCqgKAwMJBgIAAwkDAQkDBwkDAAIDFwoABwAHABYXFhUVAhYFFQYWBQkHAxsJHAkcCQEAAQABAQEGBQAAAAAAAAAAAAAAAAADAAAAAAAHAAAAAAAAAAAAAAAAAAUDBwcCAwIHBwIHBwcJCQAJAwIAAwkdBwcAAwQHBQcFBwUHBQMEAwQDBAwLCQsLCwwMAwMDAwEDAwYGBgMHBwcDAxcEBAQEFxcXBgQDBwcHBwkJCwIMDAIHCwseCwsfCwsMByAJDAICCQICAgIMAgIJAgcCAwMKCgkMLine truncated var DC_WASM = "AGFzbQEAAAAB0wZmYAF/AGAAAGADf39/AX9gA39/fwBgBn9/f39/fwBgBX9/f39/AGAEf39/fwBgAn9/AGAHf39/f39/fwF/YAJ/fwF/YAF/AX9gBH9/f38Bf2AFf39/f38Bf2ADf319AGAFf319fX0AYAd/fX19fX19AGAGf39/fX9/AGAGf39/f39/AX9gAAF8YAABf2AAAX1gCH9/f39/f39/AGAJf39/f39/f39/AGAHf39/f39/fwBgBH19fX0AYAF9AGAKf39/f39/f39/fwBgBH5+f38BfmADfn5/AX5gAX4Bf2AEf39/fQF/YAJ+fwF/YAJ/fQBgAn1/AX9gBn99fXx/fwBgB39/f39/f3wAYAV/f39/fABgC39/f39/f399fX98AGAGf39/f319AGAEf399fQBgBX9/fX1/AGAHf39/fX1/fABgB39/f39/fX0AYAN/f30AYAV/f398fwBgDX9/fX19f39/f39/f38AYAV9fX19fwBgBn19fX19fwBgBX99fX9/AGAHf39/fX19fwBgBn9/f398fwBgCH9/f39/f398AGAJf319f399f39/AGAMf319f399f39/f39/AGABfwF9YAJ/fgBgB399fX99f30AYAJ9fQF/YAZ/f399fX8Bf2AJf39/f319fX99AGADf399AX1gB39/fX19f38AYAJ9fQF9YAh/f39/fX19fQBgCX9/fX19f39/fwBgAn98AGAFf31/f38AYAh/f319fX19fwBgBH19f30AYAR/f31/AGADf31/AGAEf31/fwBgCn9/f39/f39/fX0AYAZ/f319fX0Bf2ADf399AX9gAn9/AX1gCH9/fX9/f39/AGAFf399fX8Bf2AEf39/fQBgBH9/f34AYAV/f39+fwBgB399fX19f38AYAR/f319AX1gE39/f39/f39/f39/f39/f39/f38Bf2ABfwF+YAZ/fn9/f38AYAl/f39/f39+fn4AYAR/fX9/AX9gA399fwF/YAN/fn4AYAt/f39/f39/f39/fwF/YAABfmACf34Bf2ADfn5+AX5gCH9/f39/f39/AX9gBX9/fn19AGADf35/AGALf39/f39/f39/f38AYAJ8fwF/YAF9AX1gBX9+fn5+AGACfX8BfQLXFHoDZW52C2NvbnNvbGVfbG9nAAADZW52B2RjX2h0dHAAEQNlbnYMZGNfc2V0X3Rva2VuAAcDZW52A25vdwASA2VudhFkY19jb2Fyc2VfcG9pbnRlcgATA2VudgxkY19maWxlX3BvbGwAEwNlbnYMZGNfZmlsZV90YWtlAAADZW52DGRjX2h0dHBfcG9sbAAKA2VudgtkY19odHRwX2xlbgAKA2VudgxkY19odHRwX3Rha2UABwNlbnYOZGNfc3RvcmFnZV9zZXQABgNlbnYXZGNfZmV0Y2hfZG93bmxvYWRfc3RhdGUAEwNlbnYLZGNfZG93bmxvYWQABgNlbnYMZGNfb3Blbl9maWxlAAEDZW52C2RjX29wZW5fdXJsAAcDZW52DGRjX2NhY2hlX3NldAAGA2VudhFkY19mZXRjaF9kb3dubG9hZAAGA2Vudgppbml0X3dlYmdsAAADZW52EXNldHVwX2NhbnZhc19zaXplAAADZW52DGNhbnZhc193aWR0aAATA2Vudg1jYW52YXNfaGVpZ2h0ABMDZW52CWRwaV9zY2FsZQAUA2VudhJydW5fYW5pbWF0aW9uX2xvb3AAAANlbnYOZGNfc3RvcmFnZV9nZXQACQNlbnYPZGNfc3RvcmFnZV90YWtlAAADZW52CWRjX3Byb21wdAALA2Vudg1nbEJpbmRUZXh0dXJlAAcDZW52EGdsQ29weVRleEltYWdlMkQAFQNlbnYNZ2xHZXRJbnRlZ2VydgAHA2VudhFnbEdlblZlcnRleEFycmF5cwAHA2VudhFnbEJpbmRWZXJ0ZXhBcnJheQAAA2VudgtnbEdldFN0cmluZwAKA2VudhRzYXBwX3NjaGVkdWxlX3VwZGF0ZQABA2VudgxnbEdlblF1ZXJpZXMABwNlbnYMZ2xCZWdpblF1ZXJ5AAcDZW52CmdsRW5kUXVlcnkAAANlbnYHZ2xGbHVzaAABA2VudghnbEZpbmlzaAABA2Vudgxjb25zb2xlX3dhcm4AAANlbnYPZ2xDcmVhdGVQcm9ncmFtABMDZW52DmdsQXR0YWNoU2hhZGVyAAcDZW52DWdsTGlua1Byb2dyYW0AAANlbnYOZ2xEZXRhY2hTaGFkZXIABwNlbnYOZ2xEZWxldGVTaGFkZXIAAANlbnYOZ2xHZXRQcm9ncmFtaXYAAwNlbnYTZ2xHZXRQcm9ncmFtSW5mb0xvZwAGA2VudgxnbFVzZVByb2dyYW0AAANlbnYUZ2xHZXRVbmlmb3JtTG9jYXRpb24ACQNlbnYNZ2xHZW5UZXh0dXJlcwAHA2Vudg9nbEFjdGl2ZVRleHR1cmUAAANlbnYNZ2xQaXhlbFN0b3JlaQAHA2VudhJnbEdlblJlbmRlcmJ1ZmZlcnMABwNlbnYSZ2xCaW5kUmVuZGVyYnVmZmVyAAcDZW52IGdsUmVuZGVyYnVmZmVyU3RvcmFnZU11bHRpc2FtcGxlAAUDZW52DGdsVGV4SW1hZ2UyRAAWA2Vudg9nbFRleFBhcmFtZXRlcmkAAwNlbnYQZ2xHZW5lcmF0ZU1pcG1hcAAAA2VudhFnbEdlbkZyYW1lYnVmZmVycwAHA2VudhFnbEJpbmRGcmFtZWJ1ZmZlcgAHA2VudhZnbEZyYW1lYnVmZmVyVGV4dHVyZTJEAAUDZW52DGdsUmVhZFBpeGVscwAXA2VudhRnbERlbGV0ZUZyYW1lYnVmZmVycwAHA2Vudg9nbFRleFN1YkltYWdlMkQAFgNlbnYZZ2xGcmFtZWJ1ZmZlclJlbmRlcmJ1ZmZlcgAGA2Vudg1nbERyYXdCdWZmZXJzAAcDZW52GGdsQ2hlY2tGcmFtZWJ1ZmZlclN0YXR1cwAKA2VudhVnbERlbGV0ZVJlbmRlcmJ1ZmZlcnMABwNlbnYQZ2xEZWxldGVUZXh0dXJlcwAHA2VudhNnbEdldEF0dHJpYkxvY2F0aW9uAAkDZW52CGdsRW5hYmxlAAADZW52CWdsRGlzYWJsZQAAA2VudgtnbERlcHRoRnVuYwAAA2VudgtnbEZyb250RmFjZQAAA2VudgpnbEN1bGxGYWNlAAADZW52E2dsQmxlbmRGdW5jU2VwYXJhdGUABgNlbnYXZ2xCbGVuZEVxdWF0aW9uU2VwYXJhdGUABwNlbnYLZ2xCbGVuZEZ1bmMABwNlbnYTZ2xTdGVuY2lsT3BTZXBhcmF0ZQAGA2VudhVnbFN0ZW5jaWxGdW5jU2VwYXJhdGUABgNlbnYVZ2xTdGVuY2lsTWFza1NlcGFyYXRlAAcDZW52C2dsQ29sb3JNYXNrAAYDZW52DGdsR2VuQnVmZmVycwAHA2VudgxnbEJpbmRCdWZmZXIABwNlbnYMZ2xCdWZmZXJEYXRhAAYDZW52D2dsQnVmZmVyU3ViRGF0YQAGA2Vudg9nbERlbGV0ZUJ1ZmZlcnMABwNlbnYaZ2xEaXNhYmxlVmVydGV4QXR0cmliQXJyYXkAAANlbnYPZ2xEZWxldGVQcm9ncmFtAAADZW52CmdsVmlld3BvcnQABgNlbnYJZ2xTY2lzc29yAAYDZW52C2dsVW5pZm9ybTFpAAcDZW52FmdsVmVydGV4QXR0cmliSVBvaW50ZXIABQNlbnYVZ2xWZXJ0ZXhBdHRyaWJQb2ludGVyAAQDZW52FWdsVmVydGV4QXR0cmliRGl2aXNvcgAHA2VudhlnbEVuYWJsZVZlcnRleEF0dHJpYkFycmF5AAADZW52DGdsVW5pZm9ybTFmdgADA2VudgxnbFVuaWZvcm0yZnYAAwNlbnYMZ2xVbmlmb3JtM2Z2AAMDZW52DGdsVW5pZm9ybTRmdgADA2VudgxnbFVuaWZvcm0xaXYAAwNlbnYMZ2xVbmlmb3JtMml2AAMDZW52DGdsVW5pZm9ybTNpdgADA2VudgxnbFVuaWZvcm00aXYAAwNlbnYSZ2xVbmlmb3JtTWF0cml4NGZ2AAYDZW52DGdsQ2xlYXJDb2xvcgAYA2Vudg1nbENsZWFyRGVwdGhmABkDZW52DmdsQ2xlYXJTdGVuY2lsAAADZW52B2dsQ2xlYXIAAANlbnYMZ2xSZWFkQnVmZmVyAAADZW52EWdsQmxpdEZyYW1lYnVmZmVyABoDZW52F2dsRHJhd0VsZW1lbnRzSW5zdGFuY2VkAAUDZW52DmdsQ3JlYXRlU2hhZGVyAAoDZW52DmdsU2hhZGVyU291cmNlAAYDZW52D2dsQ29tcGlsZVNoYWRlcgAAA2Vudg1nbEdldFNoYWRlcml2AAMDZW52EmdsR2V0U2hhZGVySW5mb0xvZwAGA2VudhJzYXBwX3NldF9jbGlwYm9hcmQABwNlbnYSZnNfZ2V0X2J1ZmZlcl9zaXplAAoDZW52DmZzX3Rha2VfYnVmZmVyAAMDZW52FHNhcHBfc2V0X2N1cnNvcl9ncmFiAAADZW52D3NhcHBfc2V0X2N1cnNvcgAHA2VudhNzYXBwX3NldF9mdWxsc2NyZWVuAAADrgqsCgMDCQYCAAMJAwEJAwcJAwACAxcKAAcABwAWFxUVFQIWBRUGFQYJBwMbCRwJHAkBAAEAAQEBBgUAAAAAAAAAAAAAAAAAAwAAAAAABwAAAAAAAAAAAAAAAAAFAwcHAgMCBwcCBwcHCQkACQMCAAMJHQcHAAMEBwUHBQcFBwUDBAMEAwQHBgwLCQsLCwsMDAMDAwMBAwMGBgYDBwcHAwMXBAQEBBcXFwYHBAMHBwcHCQkLAgwMAgcLCx4LCx8LCwwHIAkMAgIJAgICAgwCAgkCBwIDAwoKCQwJAwMLLine truncated
(function () { (function () {
var realFetch = window.fetch ? window.fetch.bind(window) : null; var realFetch = window.fetch ? window.fetch.bind(window) : null;
window.fetch = function (url, opts) { window.fetch = function (url, opts) {
+27
View File
@@ -231,6 +231,14 @@ enum Btn {
pub const REPO_URL: &str = "https://git.familie-zink.org/zink/dungeoncraft"; pub const REPO_URL: &str = "https://git.familie-zink.org/zink/dungeoncraft";
pub const CLAUDE_CODE_URL: &str = "https://claude.com/claude-code"; pub const CLAUDE_CODE_URL: &str = "https://claude.com/claude-code";
// the game version (file VERSION, "vYYYYMM-NNNNN"): tools/bump_version.py raises it with every
// change, and the title screen and the pause menu show it
const VERSION_FILE: &str = include_str!("../VERSION");
pub fn version() -> &'static str {
VERSION_FILE.trim()
}
// the Claude Code mark as pixel art (drawn next to the "made with" note) // the Claude Code mark as pixel art (drawn next to the "made with" note)
const CLAUDE_MARK: [&str; 5] = [ const CLAUDE_MARK: [&str; 5] = [
"...############...", "...############...",
@@ -683,6 +691,9 @@ impl Menu {
self.buttons.push((r, Btn::ClaudeCode)); self.buttons.push((r, Btn::ClaudeCode));
let lic = tr("menu.license"); let lic = tr("menu.license");
gfx.text(&lic, grid_center(sw, gfx.measure(&lic, SMALL())), sh - CH() * 2.5, SMALL(), DIM); gfx.text(&lic, grid_center(sw, gfx.measure(&lic, SMALL())), sh - CH() * 2.5, SMALL(), DIM);
let ver = tr("menu.version");
let line = format!("{} {}", ver, version());
gfx.text(&line, grid_center(sw, gfx.measure(&line, SMALL())), sh - CH() * 5.5, SMALL(), DIM);
let url = REPO_URL.trim_start_matches("https://"); let url = REPO_URL.trim_start_matches("https://");
let w = gfx.measure(url, SMALL()); let w = gfx.measure(url, SMALL());
let r = Rect::new(grid_center(sw, w), sh - CH() * 1.5, w, GH * SMALL() + 2.0); let r = Rect::new(grid_center(sw, w), sh - CH() * 1.5, w, GH * SMALL() + 2.0);
@@ -760,6 +771,9 @@ impl Menu {
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI); gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI);
let w = (cols - 6) as f32 * CW(); let w = (cols - 6) as f32 * CW();
self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items); self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items);
let ver = tr("menu.version");
let line = format!("{} {}", ver, version());
gfx.text(&line, x + (cols as f32 * CW() - gfx.measure(&line, SMALL())) * 0.5, y + (rows as f32 - 1.7) * CH(), SMALL(), DIM);
} }
fn draw_settings(&mut self, gfx: &Gfx, s: &Settings, tab: usize) { fn draw_settings(&mut self, gfx: &Gfx, s: &Settings, tab: usize) {
@@ -1241,3 +1255,16 @@ pub fn goal_text(g: Goal) -> String {
Goal::Freed(n) => trf("goal.freed", &[n.to_string()]), Goal::Freed(n) => trf("goal.freed", &[n.to_string()]),
} }
} }
#[cfg(test)]
mod tests {
#[test]
fn the_version_follows_the_scheme() {
let rest = super::version().strip_prefix('v').expect("the version starts with v");
let (month, number) = rest.split_once('-').expect("the version is vYYYYMM-NNNNN");
assert_eq!(month.len(), 6);
assert!(month.chars().all(|c| c.is_ascii_digit()));
assert_eq!(number.len(), 5);
assert!(number.parse::<u32>().expect("a number") >= 1);
}
}
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Raise the game version in VERSION: vYYYYMM-NNNNN.
Run with every change to the game (the number counts the changes of the current month and
starts again at 00001 when the month turns). The version is shown on the title screen and in
the pause menu, and it is the version a savegame support question refers to.
"""
import datetime
import pathlib
import re
import sys
FILE = pathlib.Path(__file__).resolve().parent.parent / "VERSION"
PATTERN = re.compile(r"^v(\d{6})-(\d{1,5})$")
def main() -> int:
month = datetime.date.today().strftime("%Y%m")
current = FILE.read_text(encoding="utf-8").strip() if FILE.exists() else ""
match = PATTERN.match(current)
if match and match.group(1) == month:
number = int(match.group(2)) + 1
elif match: # a new month: count again from the first change
number = 1
elif current:
print(f"VERSION does not look like vYYYYMM-NNNNN: {current!r}", file=sys.stderr)
return 1
else:
number = 1
version = f"v{month}-{number:05d}"
FILE.write_text(version + "\n", encoding="utf-8")
if match:
print(f"{current} -> {version}")
else:
print(version)
return 0
if __name__ == "__main__":
sys.exit(main())