From 377f534f6b665a3af0967ad6b868915d14b67f5f Mon Sep 17 00:00:00 2001 From: rope Date: Wed, 28 Oct 2026 19:09:33 +0000 Subject: [PATCH] Crafting cards: one-row buttons, list all missing reagents Co-Authored-By: Claude Opus 5.5 --- game.py | 10 ++++++---- static/styles.css | 11 ++++++++++- tests/test_game.py | 8 ++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/game.py b/game.py index 1b1471b..42058f0 100644 --- a/game.py +++ b/game.py @@ -3053,10 +3053,10 @@ def can_start(world: dict, player: dict, action: dict) -> str | None: if order is not None or "order" in (player["action"] or {}) and player["action"]["id"] == action["id"]: if not order or order["status"] != "open" or order["claimed"] != player["id"] and not order.get("city"): return "Work order done" if order and order["status"] == "done" else "Work order closed" - for item, qty in fire_inputs(world, action).items(): - pooled = order["provided"].get(item, 0) if order else 0 - if pooled + player["bank"].get(item, 0) < qty: - return f"Out of {ITEMS[item]['name'].lower()}" + short = [ITEMS[item]["name"].lower() for item, qty in fire_inputs(world, action).items() + if (order["provided"].get(item, 0) if order else 0) + player["bank"].get(item, 0) < qty] + if short: + return f"Out of: {', '.join(short)}" if action.get("special") == "tend" and not tend_target(world, player, action): return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full" if action["tend"] else "Your piles are all full" if action.get("special") == "sabotage": @@ -6753,6 +6753,8 @@ ORDER_RECIPES, ORDER_RAW = _order_sources() # line here (new / balance / fix), and a new release a new entry on top. PATCH_NOTES = [ {"date": "2026-10-28", "title": "Queue", "notes": [ + ("fix", "Crafting cards: buttons stay on one row on phones and narrow cards."), + ("new", "\"Out of:\" lists every missing reagent."), ("new", "Updated guild roster layout"), ("new", "Firekeeping: +Q next to 🔥 Tend all queues tending until your piles are full, then the queue moves on."), ("new", "City work orders: 🏘️ City when posting, or 🔨 on anything a building needs (goes straight into its fund). Anyone can craft them, several at once. Reagents earn their worth in favour, the maker the rest."), diff --git a/static/styles.css b/static/styles.css index d10784c..211cf5c 100644 --- a/static/styles.css +++ b/static/styles.css @@ -225,7 +225,16 @@ span[data-goto]:not(.item-chip):hover, b[data-goto]:hover { text-decoration: und .bar.hp > span { background: var(--bad); } .warn { color: var(--warn); } select { background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 5px 8px; margin-left: 6px; } -.action .foot { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-top: auto; } +.action .foot { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; margin-top: auto; } +/* The reason gets its own line when it and the buttons don't fit side by side; the buttons stay one row. */ +.action .foot .why:empty { display: none; } +.action .foot > .btns { flex-wrap: nowrap; } +.action .foot > :last-child { margin-left: auto; } +.action { container-type: inline-size; } +@container (max-width: 270px) { + .action .foot .btn { padding-inline: 9px; } + .action .foot .make input { width: 48px; padding-inline: 6px; } +} /* --- bank ------------------------------------------------------------- */ diff --git a/tests/test_game.py b/tests/test_game.py index 9f915a5..7b4f6a1 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -244,7 +244,7 @@ class ActionTest(unittest.TestCase): self.assertEqual(p["bank"]["copper_bar"], 3) self.assertNotIn("copper_ore", p["bank"]) self.assertIsNone(p["action"]) - self.assertEqual(summary["stopped"], "Out of copper ore") + self.assertEqual(summary["stopped"], "Out of: copper ore") def test_crafting_stops_as_the_last_one_finishes(self): world = world_with(("ann", "commoner")) @@ -254,7 +254,7 @@ class ActionTest(unittest.TestCase): duration = game.action_time(world, p, game.ACTION_BY_ID["smith_copper_bar"]) summary = game.process_player(world, p, T0 + duration + 0.1, random.Random(1)) self.assertIsNone(p["action"]) - self.assertEqual(summary["stopped"], "Out of copper ore") + self.assertEqual(summary["stopped"], "Out of: copper ore") def test_level_and_city_requirements(self): world = world_with(("ann", "commoner")) @@ -2709,7 +2709,7 @@ class WorkOrderTest(unittest.TestCase): self.dave["bank"] = {"iron_bar": 1} game.provide_reagent(self.world, self.dave, order["id"], "iron_bar") self.fred["bank"] = {"iron_bar": 1, "oak_plank": 1, "clay_core": 3} - self.assertEqual(game.work_order(self.world, self.fred, order["id"], T0), "Out of iron bar") + self.assertEqual(game.work_order(self.world, self.fred, order["id"], T0), "Out of: iron bar") self.fred["bank"]["iron_bar"] = 2 self.assertIsNone(game.work_order(self.world, self.fred, order["id"], T0)) game.process_player(self.world, self.fred, T0 + 600, random.Random(1)) @@ -2850,7 +2850,7 @@ class JewelcraftingTest(unittest.TestCase): game.start_action(self.world, self.ann, "prospect_garnet", T0) summary = game.process_player(self.world, self.ann, T0 + 1000, Always()) self.assertEqual(self.ann["bank"], {"iron_ore": 2, "garnet": 1}) - self.assertEqual(summary["stopped"], "Out of iron ore") + self.assertEqual(summary["stopped"], "Out of: iron ore") self.assertGreater(self.ann["xp"]["jewelcrafting"], game.XP_TABLE[15]) def test_jewellery_bonuses(self):