diff -ruN a/hexoshi.py b/hexoshi.py --- a/hexoshi.py +++ b/hexoshi.py @@ -19,6 +19,8 @@ __version__ = "0.3a0" +import pygame +import asyncio import argparse import datetime import gettext @@ -48,6 +50,10 @@ __file__ = sys.executable hlib.datadir = os.path.join(os.path.dirname(__file__), "data") +if os.path.isdir("/saves"): + # On the web the page keeps /saves in the browser (IndexedDB). + hlib.configdir = "/saves/hexoshi/config" + hlib.localdir = "/saves/hexoshi/local" gettext.install("hexoshi", os.path.abspath(os.path.join(hlib.datadir, "locale"))) @@ -108,7 +114,8 @@ help=_("Quit immediately on startup (useful with -m or -s)."), action="store_true") parser.add_argument("--god") -args = parser.parse_args() +# The web player has no command line; the build runs this with -m -q to make the map. +args = parser.parse_args(os.environ.get("HEXOSHI_ARGS", "-p").split()) PRINT_ERRORS = args.print_errors DELTA = not args.nodelta @@ -185,10 +192,10 @@ print(_("Invalid cheat code: {}").format(self.cheatcode)) def event_close(self): - self.end() + pass def event_paused_close(self): - self.event_close() + pass class Level(sge.dsp.Room): @@ -4438,14 +4445,17 @@ y = get_yregion(self.image_ycenter) i = (sge.game.current_room.fname, self.spawn_id, x, y) dlg = TeleportDialog(i) - dlg.show() - if dlg.selection and dlg.selection != i: - other.x = self.image_xcenter - other.bbox_bottom = self.image_top - other.warp_dest = "{}:{}".format(*dlg.selection[:2]) - other.warp_out() - play_sound(hlib.teleport_sound, self.image_xcenter, - self.image_ycenter) + + def teleport_done(): + if dlg.selection and dlg.selection != i: + other.x = self.image_xcenter + other.bbox_bottom = self.image_top + other.warp_dest = "{}:{}".format(*dlg.selection[:2]) + other.warp_out() + play_sound(hlib.teleport_sound, self.image_xcenter, + self.image_ycenter) + + dlg.show(on_close=teleport_done) def create_children(self): self.created = True @@ -4868,8 +4878,7 @@ class MainMenu(Menu): - items = [_("New Game"), _("Load Game"), _("Options"), _("Credits"), - _("Quit")] + items = [_("New Game"), _("Load Game"), _("Options"), _("Credits")] def event_choose(self): if self.choice == 0: @@ -4885,8 +4894,6 @@ credits_room = CreditsScreen.load(os.path.join("special", "credits.json")) credits_room.start() - else: - sge.game.end() class NewGameMenu(Menu): @@ -4986,7 +4993,6 @@ def create_page(cls, default=0): smt = hlib.scale_method if hlib.scale_method else "fastest" cls.items = [ - _("Fullscreen: {}").format(_("On") if hlib.fullscreen else _("Off")), _("Scale Method: {}").format(smt), _("Sound Volume: {}%").format(int(hlib.sound_volume * 100)), _("Music Volume: {}%").format(int(hlib.music_volume * 100)), @@ -4996,7 +5002,9 @@ _("Joystick Threshold: {}%").format(int(hlib.joystick_threshold * 100)), _("Configure keyboard"), _("Configure joysticks"), _("Detect joysticks"), _("Back")] - return cls.create(default) + # The list lost its first entry (fullscreen): the numbers used by + # event_choose still count it, so the default shifts by one. + return cls.create(max(default - 1, 0)) def event_press_left(self): # Variant of event_press_enter which passes True to the custom @@ -5014,12 +5022,9 @@ self.event_press_enter() def event_choose(self, left=False): - if self.choice == 0: - play_sound(hlib.select_sound) - hlib.fullscreen = not hlib.fullscreen - hlib.game.update_fullscreen() - OptionsMenu.create_page(default=self.choice) - elif self.choice == 1: + if self.choice is not None: + self.choice += 1 # see create_page + if self.choice == 1: choices = [None, "noblur", "smooth"] + sge.SCALE_METHODS if hlib.scale_method in choices: i = choices.index(hlib.scale_method) @@ -5146,102 +5151,41 @@ text = _("Press the key you wish to bind to this function, or the " "Escape key to cancel.") + def bound(target, self=self, default=self.choice): + # What to do with the key picked (None: cancelled). + def done(k): + if k is not None: + bind_key(target, k) + play_sound(hlib.confirm_sound) + else: + play_sound(hlib.cancel_sound) + self.__class__.create_page(default=default) + return done + if self.choice == 0: - k = wait_key(text) - if k is not None: - bind_key(hlib.left_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.left_key)) elif self.choice == 1: - k = wait_key(text) - if k is not None: - bind_key(hlib.right_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.right_key)) elif self.choice == 2: - k = wait_key(text) - if k is not None: - bind_key(hlib.up_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.up_key)) elif self.choice == 3: - k = wait_key(text) - if k is not None: - bind_key(hlib.down_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.down_key)) elif self.choice == 4: - k = wait_key(text) - if k is not None: - bind_key(hlib.jump_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.jump_key)) elif self.choice == 5: - k = wait_key(text) - if k is not None: - bind_key(hlib.shoot_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.shoot_key)) elif self.choice == 6: - k = wait_key(text) - if k is not None: - bind_key(hlib.secondary_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.secondary_key)) elif self.choice == 7: - k = wait_key(text) - if k is not None: - bind_key(hlib.aim_diag_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.aim_diag_key)) elif self.choice == 8: - k = wait_key(text) - if k is not None: - bind_key(hlib.aim_up_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.aim_up_key)) elif self.choice == 9: - k = wait_key(text) - if k is not None: - bind_key(hlib.aim_down_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.aim_down_key)) elif self.choice == 10: - k = wait_key(text) - if k is not None: - bind_key(hlib.pause_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.pause_key)) elif self.choice == 11: - k = wait_key(text) - if k is not None: - bind_key(hlib.map_key, k) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_key(text, bound(hlib.map_key)) else: play_sound(hlib.cancel_sound) OptionsMenu.create_page(default=5) @@ -5295,102 +5239,41 @@ text = _("Press the joystick button, axis, or hat direction you wish " "to bind to this function, or the Escape key to cancel.") + def bound(target, self=self, default=self.choice): + # What to do with the joystick input picked (None: cancelled). + def done(js): + if js is not None: + bind_js(target, js) + play_sound(hlib.confirm_sound) + else: + play_sound(hlib.cancel_sound) + self.__class__.create_page(default=default) + return done + if self.choice == 0: - js = wait_js(text) - if js is not None: - bind_js(hlib.left_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.left_js)) elif self.choice == 1: - js = wait_js(text) - if js is not None: - bind_js(hlib.right_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.right_js)) elif self.choice == 2: - js = wait_js(text) - if js is not None: - bind_js(hlib.up_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.up_js)) elif self.choice == 3: - js = wait_js(text) - if js is not None: - bind_js(hlib.down_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.down_js)) elif self.choice == 4: - js = wait_js(text) - if js is not None: - bind_js(hlib.jump_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.jump_js)) elif self.choice == 5: - js = wait_js(text) - if js is not None: - bind_js(hlib.shoot_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.shoot_js)) elif self.choice == 6: - js = wait_js(text) - if js is not None: - bind_js(hlib.secondary_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.secondary_js)) elif self.choice == 7: - js = wait_js(text) - if js is not None: - bind_js(hlib.aim_diag_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.aim_diag_js)) elif self.choice == 8: - js = wait_js(text) - if js is not None: - bind_js(hlib.aim_up_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.aim_up_js)) elif self.choice == 9: - js = wait_js(text) - if js is not None: - bind_js(hlib.aim_down_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.aim_down_js)) elif self.choice == 10: - js = wait_js(text) - if js is not None: - bind_js(hlib.pause_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.pause_js)) elif self.choice == 11: - js = wait_js(text) - if js is not None: - bind_js(hlib.map_js, js) - play_sound(hlib.confirm_sound) - else: - play_sound(hlib.cancel_sound) - self.__class__.create_page(default=self.choice) + wait_js(text, bound(hlib.map_js)) else: play_sound(hlib.cancel_sound) OptionsMenu.create_page(default=6) @@ -5824,74 +5707,51 @@ xsge_gui.escape_joystick_events = [(0, "button", 8)] -def wait_key(text): - # Wait for a key press and return it. - while True: - # Input events - sge.game.pump_input() - while sge.game.input_events: - event = sge.game.input_events.pop(0) - if isinstance(event, sge.input.KeyPress): - sge.game.pump_input() - sge.game.input_events = [] - if event.key == "escape": - return None - else: - return event.key - - # Regulate speed - sge.game.regulate_speed(fps=10) - - # Project text - sge.game.project_text(hlib.font, text, sge.game.width / 2, - sge.game.height / 2, width=sge.game.width, - height=sge.game.height, - color=sge.gfx.Color("white"), - halign="center", valign="middle", - outline=sge.gfx.Color("black"), - outline_thickness=1) - - # Refresh - hlib.game.refresh_screen(0, 0) +class WaitInputDialog(xsge_gui.Dialog): + # Waits for a key press (or a joystick event) and keeps it in + # ``result`` (None: cancelled with Escape). It replaces the loops that + # wait_key() and wait_js() used to run. + + def __init__(self, text, joystick=False): + self.result = None + self.joystick = joystick + w = sge.game.width + h = sge.game.height + super().__init__(gui_handler, 0, 0, w, h, + background_color=sge.gfx.Color("black"), + border=False) + xsge_gui.Label(self, 0, 0, 0, text, font=hlib.font, width=w, height=h, + color=sge.gfx.Color("white"), halign="center", + valign="middle") + + def event_global_key_press(self, key, char): + if self.joystick: + if key == "escape": + self.destroy() + else: + self.result = None if key == "escape" else key + self.destroy() -def wait_js(text): - # Wait for a joystick press and return it. - sge.game.pump_input() - sge.game.input_events = [] + def event_global_joystick(self, js_name, js_id, input_type, input_id, + value): + if (self.joystick + and input_type not in {"axis0", "hat_center_x", "hat_center_y"} + and value >= hlib.joystick_threshold): + self.result = (js_id, input_type, input_id) + self.destroy() - while True: - # Input events - sge.game.pump_input() - while sge.game.input_events: - event = sge.game.input_events.pop(0) - if isinstance(event, sge.input.KeyPress): - if event.key == "escape": - sge.game.pump_input() - sge.game.input_events = [] - return None - elif isinstance(event, sge.input.JoystickEvent): - if (event.input_type not in {"axis0", "hat_center_x", - "hat_center_y"} - and event.value >= hlib.joystick_threshold): - sge.game.pump_input() - sge.game.input_events = [] - return (event.js_id, event.input_type, event.input_id) - # Regulate speed - sge.game.regulate_speed(fps=10) +def wait_key(text, callback): + # Wait for a key press and pass it to callback (None if cancelled). + dialog = WaitInputDialog(text) + dialog.show(on_close=lambda: callback(dialog.result)) - # Project text - sge.game.project_text(hlib.font, text, sge.game.width / 2, - sge.game.height / 2, width=sge.game.width, - height=sge.game.height, - color=sge.gfx.Color("white"), - halign="center", valign="middle", - outline=sge.gfx.Color("black"), - outline_thickness=1) - # Refresh - hlib.game.refresh_screen(0, 0) +def wait_js(text, callback): + # Wait for a joystick press and pass it to callback (None if cancelled). + dialog = WaitInputDialog(text, joystick=True) + dialog.show(on_close=lambda: callback(dialog.result)) def show_error(message): @@ -7109,10 +6969,14 @@ print(_("Starting game…")) -try: - if not QUIT: - sge.game.start() - else: - print(_("Successfully started Hexoshi. Quitting now as -q was passed.")) -finally: - write_to_disk() +async def main(): + try: + if not QUIT: + await sge.game.start() + else: + print(_("Successfully started Hexoshi. Quitting now as -q was passed.")) + finally: + write_to_disk() + + +asyncio.run(main()) diff -ruN a/hlib/game.py b/hlib/game.py --- a/hlib/game.py +++ b/hlib/game.py @@ -20,13 +20,8 @@ def update_fullscreen(): - if hlib.fullscreen: - sge.game.scale = hlib.fsscale or None - sge.game.fullscreen = True - else: - sge.game.fullscreen = False - sge.game.scale = hlib.scale - sge.game.scale = None + # Web port: the page has its own full-screen button. + pass def refresh_screen(time_passed, delta_mult):