# MIT License # # Copyright (c) 2026 # # 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: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # 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. # # Third-party exception: bitmap glyph data derived from Noto Sans CJK is licensed # under the SIL Open Font License 1.1. See assets/NOTO-LICENSE.txt. """未来からの修理票・教材4/5。架空の工場Aの仕組みを段階化。 実行: python 04_one_change.py """ from dataclasses import dataclass TICKS = 6 TARGET = 5 CHOICES = ("NO CHANGE", "PUMP 2", "CAPACITY 6", "COST 1", "INITIAL WATER 4") def settings(choice): if choice not in range(5): raise ValueError("choice must be 0..4") return (2 if choice == 1 else 1, 6 if choice == 2 else 4, 1 if choice == 3 else 2, 4 if choice == 4 else 2) @dataclass(frozen=True) class State: water: int = 2 output: int = 0 tick: int = 0 timeline: tuple = () choice: int = 0 @property def done(self): return self.tick >= TICKS @property def success(self): return self.done and self.output >= TARGET def new(choice=0): pump, capacity, cost, initial_water = settings(choice) return State(water=initial_water, choice=choice) def step(state): if state.done: return state pump, capacity, cost, initial_water = settings(state.choice) # 補給を先に行う。同じ刻の補給を、その刻の生産で使える。 supplied = min(capacity, state.water + pump) produced = int(supplied >= cost) water = supplied - cost if produced else supplied entry = (state.tick + 1, state.water, supplied, water, produced) return State(water=water, output=state.output + produced, tick=state.tick + 1, timeline=state.timeline + (entry,), choice=state.choice) def main(headless=False, screenshot=None): import pyxel state = new() selected = 0 def update(): nonlocal state, selected if headless and pyxel.frame_count >= 12: if screenshot: pyxel.screenshot(screenshot, scale=2) pyxel.quit() return if pyxel.btnp(pyxel.KEY_R): state = new() selected = 0 return current = state if current.done: if pyxel.btnp(pyxel.KEY_LEFT): selected = (selected - 1) % len(CHOICES) elif pyxel.btnp(pyxel.KEY_RIGHT): selected = (selected + 1) % len(CHOICES) elif pyxel.btnp(pyxel.KEY_RETURN): state = new(selected) return if pyxel.btnp(pyxel.KEY_SPACE): state = step(state) def draw(): current = state pyxel.cls(1) pyxel.text(10, 10, "FUTURE REPAIR / STEP 4", 10) pyxel.text(10, 25, f"TICK {current.tick} WATER {current.water} OUTPUT {current.output}", 7) pyxel.rect(10, 44, 130, 14, 5) pyxel.rect(10, 44, min(current.water, 6) * 20, 14, 12) pyxel.text(155, 48, "WATER", 6) pyxel.text(10, 66, "ACTIVE: " + CHOICES[current.choice], 11) pyxel.text(10, 86, "TICK BEFORE FILLED LEFT MADE", 6) for row, (tick, before, filled, left, made) in enumerate(current.timeline): pyxel.text(10, 100 + row * 12, f"{tick:4} {before:6} {filled:6} {left:4} {made:4}", 7 if made else 8) if current.done: pyxel.text(10, 181, "GOAL!" if current.success else "OUTPUT BELOW TARGET", 10) pyxel.text(10, 193, f"RESULT {current.output} / TARGET {TARGET}", 7) pyxel.text(10, 209, "TRY: " + CHOICES[selected], 11) pyxel.text(10, 222, "LEFT/RIGHT: CHOOSE ENTER: RESTART", 6) pyxel.text(10, 248, "SPACE: ONE TICK R: RESET ESC: QUIT", 6) pyxel.init(310, 280, title="Future Repair - Stage 4", fps=30, headless=headless) pyxel.run(update, draw) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--headless-smoke", action="store_true", help="12 frames then quit; not a playtest") parser.add_argument("--screenshot", help="PNG path used with --headless-smoke") args = parser.parse_args() main(headless=args.headless_smoke, screenshot=args.screenshot)