From 253f4ce3544c6e9982ba5296c4b28f5fc1888db5 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Mon, 7 Jun 2021 01:58:41 +0200 Subject: [PATCH] More TUI client refactoring. --- plomrogue_client/tui.py | 37 ++-- rogue_chat_curses.py | 456 ++++++++++++++++++++-------------------- 2 files changed, 249 insertions(+), 244 deletions(-) diff --git a/plomrogue_client/tui.py b/plomrogue_client/tui.py index 9f28a04..3ed1b62 100644 --- a/plomrogue_client/tui.py +++ b/plomrogue_client/tui.py @@ -33,24 +33,17 @@ class TUI: self.size = self.size - YX(self.size.y % 4, 0) self.size = self.size - YX(0, self.size.x % 4) + def log(self, msg): + self._log += [msg] + self.do_refresh = True + def init_loop(self): curses.curs_set(0) # hide cursor self.stdscr.timeout(10) self.reset_size() - def run_loop(self, stdscr): - self.stdscr = stdscr - self.init_loop() - while True: - try: - self.loop() - except AbortOnGetkey: - continue - self.do_refresh = True - - def log(self, msg): - self._log += [msg] - self.do_refresh = True + def draw_screen(self): + self.stdscr.clear() def get_key_and_keycode(self): try: @@ -69,6 +62,24 @@ class TUI: raise AbortOnGetkey return key, keycode + def run_loop(self, stdscr): + self.stdscr = stdscr + self.init_loop() + while True: + self.on_each_loop_start() + for msg in self.socket.get_message(): + self.handle_server_message(msg) + if self.do_refresh: + self.draw_screen() + self.do_refresh = False + try: + key, keycode = self.get_key_and_keycode() + except AbortOnGetkey: + continue + self.on_key(key, keycode) + self.do_refresh = True + + def msg_into_lines_of_width(msg, width): chunk = '' diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py index 60c46b2..888d9ff 100755 --- a/rogue_chat_curses.py +++ b/rogue_chat_curses.py @@ -881,23 +881,236 @@ class RogueChatTUI(TUI): 'on some color effects') super().init_loop() - def loop(self): + def recalc_input_lines(self): + if not self.mode.has_input_prompt: + self.input_lines = [] + else: + self.input_lines = msg_into_lines_of_width(self.input_prompt + + self.input_ + '█', + self.right_window_width) + def draw_history(self): + lines = [] + for line in self._log: + lines += msg_into_lines_of_width(line, self.right_window_width) + lines.reverse() + height_header = 2 + max_y = self.size.y - len(self.input_lines) + for i in range(len(lines)): + if (i >= max_y - height_header): + break + self.addstr(max_y - i - 1, self.left_window_width, lines[i]) + + def draw_info(self): + info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info()) + lines = msg_into_lines_of_width(info, self.right_window_width) + height_header = 2 + for i in range(len(lines)): + y = height_header + i + if y >= self.size.y - len(self.input_lines): + break + self.addstr(y, self.left_window_width, lines[i]) + + def draw_input(self): + y = self.size.y - len(self.input_lines) + for i in range(len(self.input_lines)): + self.addstr(y, self.left_window_width, self.input_lines[i]) + y += 1 + + def draw_stats(self): + stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy, + self.game.bladder_pressure) + self.addstr(0, self.left_window_width, stats) + + def draw_mode(self): + help = "hit [%s] for help" % self.keys['help'] + if self.mode.has_input_prompt: + help = "enter /help for help" + self.addstr(1, self.left_window_width, + 'MODE: %s – %s' % (self.mode.short_desc, help)) + + def draw_map(self): + if (not self.game.turn_complete) and len(self.map_lines) == 0: + return + if self.game.turn_complete: + map_lines_split = [] + for y in range(self.game.map_geometry.size.y): + start = self.game.map_geometry.size.x * y + end = start + self.game.map_geometry.size.x + if self.map_mode == 'protections': + map_lines_split += [[c + ' ' for c + in self.game.map_control_content[start:end]]] + else: + map_lines_split += [[c + ' ' for c + in self.game.map_content[start:end]]] + if self.map_mode == 'terrain + annotations': + for p in self.game.annotations: + map_lines_split[p.y][p.x] = 'A ' + elif self.map_mode == 'terrain + things': + for p in self.game.portals.keys(): + original = map_lines_split[p.y][p.x] + map_lines_split[p.y][p.x] = original[0] + 'P' + used_positions = [] + + def draw_thing(t, used_positions): + symbol = self.game.thing_types[t.type_] + meta_char = ' ' + if hasattr(t, 'thing_char'): + meta_char = t.thing_char + if t.position in used_positions: + meta_char = '+' + if hasattr(t, 'carrying') and t.carrying: + meta_char = '$' + map_lines_split[t.position.y][t.position.x] = symbol + meta_char + used_positions += [t.position] + + for t in [t for t in self.game.things if t.type_ != 'Player']: + draw_thing(t, used_positions) + for t in [t for t in self.game.things if t.type_ == 'Player']: + draw_thing(t, used_positions) + if self.mode.shows_info or self.mode.name == 'control_tile_draw': + map_lines_split[self.explorer.y][self.explorer.x] = '??' + elif self.map_mode != 'terrain + things': + map_lines_split[self.game.player.position.y]\ + [self.game.player.position.x] = '??' + self.map_lines = [] + if type(self.game.map_geometry) == MapGeometryHex: + indent = 0 + for line in map_lines_split: + self.map_lines += [indent * ' ' + ''.join(line)] + indent = 0 if indent else 1 + else: + for line in map_lines_split: + self.map_lines += [''.join(line)] + window_center = YX(int(self.size.y / 2), + int(self.left_window_width / 2)) + center = self.game.player.position + if self.mode.shows_info or self.mode.name == 'control_tile_draw': + center = self.explorer + center = YX(center.y, center.x * 2) + self.offset = center - window_center + if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2: + self.offset += YX(0, 1) + term_y = max(0, -self.offset.y) + term_x = max(0, -self.offset.x) + map_y = max(0, self.offset.y) + map_x = max(0, self.offset.x) + while term_y < self.size.y and map_y < len(self.map_lines): + to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x] + self.addstr(term_y, term_x, to_draw) + term_y += 1 + map_y += 1 + + def draw_names(self): + players = [t for t in self.game.things if t.type_ == 'Player'] + players.sort(key=lambda t: len(t.name)) + players.reverse() + shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2) + y = 0 + for t in players: + offset_y = y - shrink_offset + max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8) + name = t.name[:] + if len(name) > max_len: + name = name[:max_len - 1] + '…' + self.addstr(y, 0, '@%s:%s' % (t.thing_char, name)) + y += 1 + if y >= self.size.y: + break + + def draw_face_popup(self): + t = self.game.get_thing(self.draw_face) + if not t or not hasattr(t, 'face'): + self.draw_face = False + return + + start_x = self.left_window_width - 10 + def draw_body_part(body_part, end_y): + self.addstr(end_y - 3, start_x, '----------') + self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |') + self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |') + self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |') + + if hasattr(t, 'face'): + draw_body_part(t.face, self.size.y - 3) + if hasattr(t, 'hat'): + draw_body_part(t.hat, self.size.y - 6) + self.addstr(self.size.y - 2, start_x, '----------') + name = t.name[:] + if len(name) > 7: + name = name[:6 - 1] + '…' + self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name)) + + def draw_help(self): + content = "%s help\n\n%s\n\n" % (self.mode.short_desc, + self.mode.help_intro) + if len(self.mode.available_actions) > 0: + content += "Available actions:\n" + for action in self.mode.available_actions: + if action in self.action_tasks: + if self.action_tasks[action] not in self.game.tasks: + continue + if action == 'move_explorer': + action = 'move' + if action == 'move': + key = ','.join(self.movement_keys) + else: + key = self.keys[action] + content += '[%s] – %s\n' % (key, self.action_descriptions[action]) + content += '\n' + content += self.mode.list_available_modes(self) + for i in range(self.size.y): + self.addstr(i, + self.left_window_width * (not self.mode.has_input_prompt), + ' ' * self.left_window_width) + lines = [] + for line in content.split('\n'): + lines += msg_into_lines_of_width(line, self.right_window_width) + for i in range(len(lines)): + if i >= self.size.y: + break + self.addstr(i, + self.left_window_width * (not self.mode.has_input_prompt), + lines[i]) + + def draw_screen(self): + super().draw_screen() + self.stdscr.bkgd(' ', curses.color_pair(1)) + self.recalc_input_lines() + if self.mode.has_input_prompt: + self.draw_input() + if self.mode.shows_info: + self.draw_info() + else: + self.draw_history() + self.draw_mode() + if not self.mode.is_intro: + self.draw_stats() + self.draw_map() + if self.show_help: + self.draw_help() + if self.mode.name in {'chat', 'play'}: + self.draw_names() + if self.draw_face: + self.draw_face_popup() + + def handle_server_message(self, msg): + command, args = self.parser.parse(msg) + command(*args) + + def on_each_loop_start(self): + prev_disconnected = self.socket.disconnected + self.socket.keep_connection_alive() + if prev_disconnected and not self.socket.disconnected: + self.update_on_connect() + if self.flash: + curses.flash() + self.flash = False - def handle_input(msg): - command, args = self.parser.parse(msg) - command(*args) + def on_key(self, key, keycode): def task_action_on(action): return self.action_tasks[action] in self.game.tasks - def recalc_input_lines(): - if not self.mode.has_input_prompt: - self.input_lines = [] - else: - self.input_lines = msg_into_lines_of_width(self.input_prompt - + self.input_ + '█', - self.right_window_width) - def move_explorer(direction): target = self.game.map_geometry.move_yx(self.explorer, direction) if target: @@ -907,212 +1120,6 @@ class RogueChatTUI(TUI): self.send_tile_control_command() else: self.flash = True - - def draw_history(): - lines = [] - for line in self._log: - lines += msg_into_lines_of_width(line, self.right_window_width) - lines.reverse() - height_header = 2 - max_y = self.size.y - len(self.input_lines) - for i in range(len(lines)): - if (i >= max_y - height_header): - break - self.addstr(max_y - i - 1, self.left_window_width, lines[i]) - - def draw_info(): - info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info()) - lines = msg_into_lines_of_width(info, self.right_window_width) - height_header = 2 - for i in range(len(lines)): - y = height_header + i - if y >= self.size.y - len(self.input_lines): - break - self.addstr(y, self.left_window_width, lines[i]) - - def draw_input(): - y = self.size.y - len(self.input_lines) - for i in range(len(self.input_lines)): - self.addstr(y, self.left_window_width, self.input_lines[i]) - y += 1 - - def draw_stats(): - stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy, - self.game.bladder_pressure) - self.addstr(0, self.left_window_width, stats) - - def draw_mode(): - help = "hit [%s] for help" % self.keys['help'] - if self.mode.has_input_prompt: - help = "enter /help for help" - self.addstr(1, self.left_window_width, - 'MODE: %s – %s' % (self.mode.short_desc, help)) - - def draw_map(): - if (not self.game.turn_complete) and len(self.map_lines) == 0: - return - if self.game.turn_complete: - map_lines_split = [] - for y in range(self.game.map_geometry.size.y): - start = self.game.map_geometry.size.x * y - end = start + self.game.map_geometry.size.x - if self.map_mode == 'protections': - map_lines_split += [[c + ' ' for c - in self.game.map_control_content[start:end]]] - else: - map_lines_split += [[c + ' ' for c - in self.game.map_content[start:end]]] - if self.map_mode == 'terrain + annotations': - for p in self.game.annotations: - map_lines_split[p.y][p.x] = 'A ' - elif self.map_mode == 'terrain + things': - for p in self.game.portals.keys(): - original = map_lines_split[p.y][p.x] - map_lines_split[p.y][p.x] = original[0] + 'P' - used_positions = [] - - def draw_thing(t, used_positions): - symbol = self.game.thing_types[t.type_] - meta_char = ' ' - if hasattr(t, 'thing_char'): - meta_char = t.thing_char - if t.position in used_positions: - meta_char = '+' - if hasattr(t, 'carrying') and t.carrying: - meta_char = '$' - map_lines_split[t.position.y][t.position.x] = symbol + meta_char - used_positions += [t.position] - - for t in [t for t in self.game.things if t.type_ != 'Player']: - draw_thing(t, used_positions) - for t in [t for t in self.game.things if t.type_ == 'Player']: - draw_thing(t, used_positions) - if self.mode.shows_info or self.mode.name == 'control_tile_draw': - map_lines_split[self.explorer.y][self.explorer.x] = '??' - elif self.map_mode != 'terrain + things': - map_lines_split[self.game.player.position.y]\ - [self.game.player.position.x] = '??' - self.map_lines = [] - if type(self.game.map_geometry) == MapGeometryHex: - indent = 0 - for line in map_lines_split: - self.map_lines += [indent * ' ' + ''.join(line)] - indent = 0 if indent else 1 - else: - for line in map_lines_split: - self.map_lines += [''.join(line)] - window_center = YX(int(self.size.y / 2), - int(self.left_window_width / 2)) - center = self.game.player.position - if self.mode.shows_info or self.mode.name == 'control_tile_draw': - center = self.explorer - center = YX(center.y, center.x * 2) - self.offset = center - window_center - if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2: - self.offset += YX(0, 1) - term_y = max(0, -self.offset.y) - term_x = max(0, -self.offset.x) - map_y = max(0, self.offset.y) - map_x = max(0, self.offset.x) - while term_y < self.size.y and map_y < len(self.map_lines): - to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x] - self.addstr(term_y, term_x, to_draw) - term_y += 1 - map_y += 1 - - def draw_names(): - players = [t for t in self.game.things if t.type_ == 'Player'] - players.sort(key=lambda t: len(t.name)) - players.reverse() - shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2) - y = 0 - for t in players: - offset_y = y - shrink_offset - max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8) - name = t.name[:] - if len(name) > max_len: - name = name[:max_len - 1] + '…' - self.addstr(y, 0, '@%s:%s' % (t.thing_char, name)) - y += 1 - if y >= self.size.y: - break - - def draw_face_popup(): - t = self.game.get_thing(self.draw_face) - if not t or not hasattr(t, 'face'): - self.draw_face = False - return - - start_x = self.left_window_width - 10 - def draw_body_part(body_part, end_y): - self.addstr(end_y - 3, start_x, '----------') - self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |') - self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |') - self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |') - - if hasattr(t, 'face'): - draw_body_part(t.face, self.size.y - 3) - if hasattr(t, 'hat'): - draw_body_part(t.hat, self.size.y - 6) - self.addstr(self.size.y - 2, start_x, '----------') - name = t.name[:] - if len(name) > 7: - name = name[:6 - 1] + '…' - self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name)) - - def draw_help(): - content = "%s help\n\n%s\n\n" % (self.mode.short_desc, - self.mode.help_intro) - if len(self.mode.available_actions) > 0: - content += "Available actions:\n" - for action in self.mode.available_actions: - if action in self.action_tasks: - if self.action_tasks[action] not in self.game.tasks: - continue - if action == 'move_explorer': - action = 'move' - if action == 'move': - key = ','.join(self.movement_keys) - else: - key = self.keys[action] - content += '[%s] – %s\n' % (key, self.action_descriptions[action]) - content += '\n' - content += self.mode.list_available_modes(self) - for i in range(self.size.y): - self.addstr(i, - self.left_window_width * (not self.mode.has_input_prompt), - ' ' * self.left_window_width) - lines = [] - for line in content.split('\n'): - lines += msg_into_lines_of_width(line, self.right_window_width) - for i in range(len(lines)): - if i >= self.size.y: - break - self.addstr(i, - self.left_window_width * (not self.mode.has_input_prompt), - lines[i]) - - def draw_screen(): - self.stdscr.clear() - self.stdscr.bkgd(' ', curses.color_pair(1)) - recalc_input_lines() - if self.mode.has_input_prompt: - draw_input() - if self.mode.shows_info: - draw_info() - else: - draw_history() - draw_mode() - if not self.mode.is_intro: - draw_stats() - draw_map() - if self.show_help: - draw_help() - if self.mode.name in {'chat', 'play'}: - draw_names() - if self.draw_face: - draw_face_popup() - def pick_selectable(task_name): try: i = int(self.input_) @@ -1172,19 +1179,6 @@ class RogueChatTUI(TUI): self.input_ = "" self.switch_mode('edit') - prev_disconnected = self.socket.disconnected - self.socket.keep_connection_alive() - if prev_disconnected and not self.socket.disconnected: - self.update_on_connect() - if self.flash: - curses.flash() - self.flash = False - if self.do_refresh: - draw_screen() - self.do_refresh = False - for msg in self.socket.get_message(): - handle_input(msg) - key, keycode = self.get_key_and_keycode() self.show_help = False self.draw_face = False if key == 'KEY_RESIZE': -- 2.30.2