# 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. """風まかせ郵便気球・教材2/5。原作Aの有限航路。""" from dataclasses import dataclass, replace WINDS = ((-1,-1,1,1),(0,1,0,-1),(2,2,-2,-2)) START = 1 @dataclass(frozen=True) class State: t: int = 0 x: int = START h: int = 1 outcome: str = "flying" def initial(): return State() def step(state, action): if state.outcome != "flying": return state, "end" if action not in ("up","down","stay"): return state, "invalid" h = state.h+{"up":1,"down":-1,"stay":0}[action] if not 0<=h<=2: return state, "height" wind = WINDS[h][state.t%len(WINDS[0])] x = state.x+wind return State(state.t+1,x,h,"flying" if 0<=x<=12 else "drift"), "ok" class App: def __init__(self): self.reset() def reset(self): self.state = initial() self.gated = False self.message = "READY" def control(self, pressed, held): if "R" in pressed: self.reset() self.gated = True return if self.gated: if not pressed and not held: self.gated = False return if len(pressed | held)>1: self.gated = True self.message = "RELEASE ALL KEYS" return if len(pressed)!=1: return key = next(iter(pressed)) if self.state.outcome!="flying": return action = {"UP":"up","DOWN":"down","RIGHT":"stay"}.get(key) if action is None: return self.state,self.message = step(self.state,action) self.gated = True def draw(self, p): p.cls(1) p.text(8,8,"WIND POST / STAGE 2",10) s = self.state p.text(8,23,"T "+str(s.t)+" X "+str(s.x)+" H "+str(s.h)+" "+s.outcome.upper(),7) for h in range(3): y = 150-h*36 p.line(40,y,400,y,5) p.text(8,y-3,"H"+str(h),6) for x in range(13): p.text(39+x*30,165,str(x),6) if 0<=s.x<=12: x=40+s.x*30;y=150-s.h*36 p.circ(x,y-9,5,10) p.line(x,y-4,x,y,7) p.rect(x-2,y,5,3,9) phase=s.t%len(WINDS[0]) p.text(8,199,"WIND BY TURN COLUMN / NOW "+str(phase),7) for h in range(3): p.text(8,214+h*14,"H"+str(h),6) for col,wind in enumerate(WINDS[h]): x=38+col*45 if col==phase:p.rectb(x-2,211+h*14,30,11,10) p.text(x,214+h*14,format(wind,"+d"),7) preview_actions = (('UP', 'up'), ('RIGHT', 'stay'), ('DOWN', 'down')) for row,(key,action) in enumerate(preview_actions): after,reason=step(s,action) text=key+": X"+str(after.x)+" H"+str(after.h)+" T"+str(after.t) if hasattr(after,"fuel"):text+=" F"+str(after.fuel) if after==s:text=key+": "+reason.upper() p.text(235,214+row*14,text,11 if after!=s else 6) p.text(8,263,self.message.upper(),7) p.text(8,280,"UP / DOWN CHANGE LAYER RIGHT STAY R RESET",6) p.text(8,306,"ESC QUIT",6) DEMO_KEYS = ('UP', 'RIGHT', 'DOWN', 'DOWN') KEYS = ("UP","DOWN","RIGHT","Z","SPACE","C","X","R") def main(headless=False,screenshot=None,demo=False): from pathlib import Path import pyxel as p screenshot=str(Path(screenshot).resolve()) if screenshot else None if screenshot: Path(screenshot).parent.mkdir(parents=True,exist_ok=True) app=App() p.init(448,320,title="Wind Post",fps=30,display_scale=2,headless=headless) keys={k:getattr(p,"KEY_"+k) for k in KEYS} limit=max(12,2*len(DEMO_KEYS)+3) if demo else 12 def update(): if headless and p.frame_count>=limit: if screenshot:p.screenshot(screenshot,scale=2) p.quit();return if headless and demo: index=p.frame_count//2 pressed={DEMO_KEYS[index]} if p.frame_count%2==0 and index