X-Git-Url: https://plomlompom.com/repos/?a=blobdiff_plain;f=rogue_chat_curses.py;h=11fe94868c72ee8891877eec4a487c123ac41368;hb=d1b7ae346e13f77a72926173a3092d1e83663a9d;hp=d2cc704173f33243e52ee37ab08cbc371021e30a;hpb=97b606810ee702789497f9e842718441585ad545;p=plomrogue2 diff --git a/rogue_chat_curses.py b/rogue_chat_curses.py index d2cc704..11fe948 100755 --- a/rogue_chat_curses.py +++ b/rogue_chat_curses.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 import curses -import queue -import threading -import time import sys from plomrogue.game import GameBase from plomrogue.parser import Parser from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex from plomrogue.things import ThingBase from plomrogue.misc import quote -from plomrogue.errors import BrokenSocketConnection, ArgError +from plomrogue.errors import ArgError +from plomrogue_client.socket import ClientSocket +from plomrogue_client.tui import msg_into_lines_of_width, CursesScreen + + mode_helps = { 'play': { @@ -133,46 +134,6 @@ mode_helps = { } } -from ws4py.client import WebSocketBaseClient -class WebSocketClient(WebSocketBaseClient): - - def __init__(self, recv_handler, *args, **kwargs): - super().__init__(*args, **kwargs) - self.recv_handler = recv_handler - self.connect() - - def received_message(self, message): - if message.is_text: - message = str(message) - self.recv_handler(message) - - @property - def plom_closed(self): - return self.client_terminated - -from plomrogue.io_tcp import PlomSocket -class PlomSocketClient(PlomSocket): - - def __init__(self, recv_handler, url): - import socket - self.recv_handler = recv_handler - host, port = url.split(':') - super().__init__(socket.create_connection((host, port))) - - def close(self): - self.socket.close() - - def run(self): - import ssl - try: - for msg in self.recv(): - if msg == 'NEED_SSL': - self.socket = ssl.wrap_socket(self.socket) - continue - self.recv_handler(msg) - except BrokenSocketConnection: - pass # we assume socket will be known as dead by now - def cmd_TURN(game, n): game.turn_complete = False cmd_TURN.argtypes = 'int:nonneg' @@ -533,13 +494,12 @@ class TUI: self.mode_edit.available_actions = ["move", "flatten", "install", "toggle_map_mode"] self.mode = None - self.host = host + self.socket = ClientSocket(host, self.socket_log) self.game = Game() self.game.tui = self self.parser = Parser(self.game) self.log = [] self.do_refresh = True - self.queue = queue.Queue() self.login_name = None self.map_mode = 'terrain + things' self.password = 'foo' @@ -591,8 +551,6 @@ class TUI: for k in keys_conf: self.keys[k] = keys_conf[k] self.show_help = False - self.disconnected = True - self.force_instant_connect = True self.input_lines = [] self.fov = '' self.flash = False @@ -600,58 +558,33 @@ class TUI: self.ascii_draw_stage = 0 self.full_ascii_draw = '' self.offset = YX(0,0) - curses.wrapper(self.loop) - - def connect(self): + self.screen = CursesScreen() + self.screen.wrap_loop(self.loop) - def handle_recv(msg): - if msg == 'BYE': - self.socket.close() - else: - self.queue.put(msg) - - self.log_msg('@ attempting connect') - socket_client_class = PlomSocketClient - if self.host.startswith('ws://') or self.host.startswith('wss://'): - socket_client_class = WebSocketClient - try: - self.socket = socket_client_class(handle_recv, self.host) - self.socket_thread = threading.Thread(target=self.socket.run) - self.socket_thread.start() - self.disconnected = False - self.game.thing_types = {} - self.game.terrains = {} - self.is_admin = False - time.sleep(0.1) # give potential SSL negotation some time … - self.socket.send('TASKS') - self.socket.send('TERRAINS') - self.socket.send('THING_TYPES') - self.switch_mode('login') - except ConnectionRefusedError: - self.log_msg('@ server connect failure') - self.disconnected = True - self.switch_mode('waiting_for_server') - self.do_refresh = True + def update_on_connect(self): + self.socket.send('TASKS') + self.socket.send('TERRAINS') + self.socket.send('THING_TYPES') + self.switch_mode('login') def reconnect(self): + import time self.log_msg('@ attempting reconnect') - self.send('QUIT') + self.socket.send('QUIT') # necessitated by some strange SSL race conditions with ws4py time.sleep(0.1) # FIXME find out why exactly necessary self.switch_mode('waiting_for_server') - self.connect() + self.socket.connect() + self.update_on_connect() def send(self, msg): - try: - if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed: - raise BrokenSocketConnection - self.socket.send(msg) - except (BrokenPipeError, BrokenSocketConnection): - self.log_msg('@ server disconnected :(') - self.disconnected = True - self.force_instant_connect = True + self.socket.send(msg) + if self.socket.disconnected: self.do_refresh = True + def socket_log(self, msg): + self.log_msg('@ ' + msg) + def log_msg(self, msg): self.log += [msg] if len(self.log) > 100: @@ -901,19 +834,10 @@ class TUI: info += ')' return info - def loop(self, stdscr): - import datetime + def loop(self): def safe_addstr(y, x, line): - if y < self.size.y - 1 or x + len(line) < self.size.x: - stdscr.addstr(y, x, line, curses.color_pair(1)) - else: # workaround to - cut_i = self.size.x - x - 1 - cut = line[:cut_i] - last_char = line[cut_i] - stdscr.addstr(y, self.size.x - 2, last_char, curses.color_pair(1)) - stdscr.insstr(y, self.size.x - 2, ' ') - stdscr.addstr(y, x, cut, curses.color_pair(1)) + self.screen.safe_addstr(y, x, line, curses.color_pair(1)) def handle_input(msg): command, args = self.parser.parse(msg) @@ -922,29 +846,10 @@ class TUI: def task_action_on(action): return action_tasks[action] in self.game.tasks - def msg_into_lines_of_width(msg, width): - chunk = '' - lines = [] - x = 0 - for i in range(len(msg)): - if x >= width or msg[i] == "\n": - lines += [chunk] - chunk = '' - x = 0 - if msg[i] == "\n": - x -= 1 - if msg[i] != "\n": - chunk += msg[i] - x += 1 - lines += [chunk] - return lines - def reset_screen_size(): - self.size = YX(*stdscr.getmaxyx()) - self.size = self.size - YX(self.size.y % 4, 0) - self.size = self.size - YX(0, self.size.x % 4) - self.left_window_width = min(52, int(self.size.x / 2)) - self.right_window_width = self.size.x - self.left_window_width + self.screen.reset_size() + self.left_window_width = min(52, int(self.screen.size.x / 2)) + self.right_window_width = self.screen.size.x - self.left_window_width def recalc_input_lines(): if not self.mode.has_input_prompt: @@ -970,7 +875,7 @@ class TUI: 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) + max_y = self.screen.size.y - len(self.input_lines) for i in range(len(lines)): if (i >= max_y - height_header): break @@ -982,12 +887,12 @@ class TUI: height_header = 2 for i in range(len(lines)): y = height_header + i - if y >= self.size.y - len(self.input_lines): + if y >= self.screen.size.y - len(self.input_lines): break safe_addstr(y, self.left_window_width, lines[i]) def draw_input(): - y = self.size.y - len(self.input_lines) + y = self.screen.size.y - len(self.input_lines) for i in range(len(self.input_lines)): safe_addstr(y, self.left_window_width, self.input_lines[i]) y += 1 @@ -1057,7 +962,7 @@ class TUI: else: for line in map_lines_split: self.map_lines += [''.join(line)] - window_center = YX(int(self.size.y / 2), + window_center = YX(int(self.screen.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': @@ -1070,7 +975,7 @@ class TUI: 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): + while term_y < self.screen.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] safe_addstr(term_y, term_x, to_draw) term_y += 1 @@ -1080,7 +985,7 @@ class TUI: 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) + shrink_offset = max(0, (self.screen.size.y - self.left_window_width // 2) // 2) y = 0 for t in players: offset_y = y - shrink_offset @@ -1090,7 +995,7 @@ class TUI: name = name[:max_len - 1] + '…' safe_addstr(y, 0, '@%s:%s' % (t.thing_char, name)) y += 1 - if y >= self.size.y: + if y >= self.screen.size.y: break def draw_face_popup(): @@ -1107,14 +1012,14 @@ class TUI: safe_addstr(end_y, start_x, '| ' + body_part[12:18] + ' |') if hasattr(t, 'face'): - draw_body_part(t.face, self.size.y - 3) + draw_body_part(t.face, self.screen.size.y - 3) if hasattr(t, 'hat'): - draw_body_part(t.hat, self.size.y - 6) - safe_addstr(self.size.y - 2, start_x, '----------') + draw_body_part(t.hat, self.screen.size.y - 6) + safe_addstr(self.screen.size.y - 2, start_x, '----------') name = t.name[:] if len(name) > 7: name = name[:6 - 1] + '…' - safe_addstr(self.size.y - 1, start_x, + safe_addstr(self.screen.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name)) def draw_help(): @@ -1135,7 +1040,7 @@ class TUI: content += '[%s] – %s\n' % (key, action_descriptions[action]) content += '\n' content += self.mode.list_available_modes(self) - for i in range(self.size.y): + for i in range(self.screen.size.y): safe_addstr(i, self.left_window_width * (not self.mode.has_input_prompt), ' ' * self.left_window_width) @@ -1143,15 +1048,15 @@ class TUI: 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: + if i >= self.screen.size.y: break safe_addstr(i, self.left_window_width * (not self.mode.has_input_prompt), lines[i]) def draw_screen(): - stdscr.clear() - stdscr.bkgd(' ', curses.color_pair(1)) + self.screen.stdscr.clear() + self.screen.stdscr.bkgd(' ', curses.color_pair(1)) recalc_input_lines() if self.mode.has_input_prompt: draw_input() @@ -1259,7 +1164,6 @@ class TUI: 'dance': 'DANCE', } - curses.curs_set(0) # hide cursor curses.start_color() self.set_default_colors() curses.init_pair(1, 7, 0) @@ -1267,39 +1171,26 @@ class TUI: self.log_msg('@ unfortunately, your terminal does not seem to ' 'support re-definition of colors; you might miss out ' 'on some color effects') - stdscr.timeout(10) reset_screen_size() self.explorer = YX(0, 0) self.input_ = '' store_widechar = False input_prompt = '> ' - interval = datetime.timedelta(seconds=5) - last_ping = datetime.datetime.now() - interval while True: - if self.disconnected and self.force_instant_connect: - self.force_instant_connect = False - self.connect() - now = datetime.datetime.now() - if now - last_ping > interval: - if self.disconnected: - self.connect() - else: - self.send('PING') - last_ping = now + 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 - while True: - try: - msg = self.queue.get(block=False) - handle_input(msg) - except queue.Empty: - break + for msg in self.socket.get_message(): + handle_input(msg) try: - key = stdscr.getkey() + key = self.screen.stdscr.getkey() self.do_refresh = True except curses.error: continue @@ -1334,7 +1225,7 @@ class TUI: self.restore_input_values() elif self.mode.has_input_prompt and key != '\n': # Return key self.input_ += key - max_length = self.right_window_width * self.size.y - len(input_prompt) - 1 + max_length = self.right_window_width * self.screen.size.y - len(input_prompt) - 1 if len(self.input_) > max_length: self.input_ = self.input_[:max_length] elif key == self.keys['help'] and not self.mode.is_single_char_entry: @@ -1453,7 +1344,7 @@ class TUI: self.send('TASK:DANCE') elif key == self.keys['teleport']: if self.game.player.position in self.game.portals: - self.host = self.game.portals[self.game.player.position] + self.socket.host = self.game.portals[self.game.player.position] self.reconnect() else: self.flash = True