home · contact · privacy
More TUI client refactoring.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 6 Jun 2021 23:58:41 +0000 (01:58 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 6 Jun 2021 23:58:41 +0000 (01:58 +0200)
plomrogue_client/tui.py
rogue_chat_curses.py

index 9f28a04c542ba586ad3650679c43c70b3fd24734..3ed1b62772d0e635db5574a7e2f804f0f26b1c51 100644 (file)
@@ -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)
 
         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 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:
 
     def get_key_and_keycode(self):
         try:
@@ -69,6 +62,24 @@ class TUI:
                 raise AbortOnGetkey
         return key, keycode
 
                 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 = ''
 
 def msg_into_lines_of_width(msg, width):
     chunk = ''
index 60c46b270b30dac481408f23bc46181f79288732..888d9ff4aa27fd4ad2c557116f2bda66eb40dba1 100755 (executable)
@@ -881,23 +881,236 @@ class RogueChatTUI(TUI):
                          'on some color effects')
         super().init_loop()
 
                          '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 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:
         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
                     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_)
         def pick_selectable(task_name):
             try:
                 i = int(self.input_)
@@ -1172,19 +1179,6 @@ class RogueChatTUI(TUI):
                 self.input_ = ""
                 self.switch_mode('edit')
 
                 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':
         self.show_help = False
         self.draw_face = False
         if key == 'KEY_RESIZE':