X-Git-Url: https://plomlompom.com/repos/?a=blobdiff_plain;f=new2%2Frogue_chat_curses.py;h=a6d1ca7a81e9f8632bd82d2bb9e482da8529aae8;hb=HEAD;hp=643023bcc6292961974de7be71d4476ce28f4c77;hpb=1fcb132eabcaa1a95bf2b527dc18c92c15016d2a;p=plomrogue2-experiments diff --git a/new2/rogue_chat_curses.py b/new2/rogue_chat_curses.py index 643023b..a6d1ca7 100755 --- a/new2/rogue_chat_curses.py +++ b/new2/rogue_chat_curses.py @@ -1,14 +1,53 @@ #!/usr/bin/env python3 import curses -import socket import queue import threading -from plomrogue.io_tcp import PlomSocket 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 + +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 = n @@ -48,19 +87,19 @@ def cmd_MAP(game, geometry, size, content): game.map_content = content if type(game.map_geometry) == MapGeometrySquare: game.tui.movement_keys = { - 'w': 'UP', - 'a': 'LEFT', - 's': 'DOWN', - 'd': 'RIGHT', + game.tui.keys['square_move_up']: 'UP', + game.tui.keys['square_move_left']: 'LEFT', + game.tui.keys['square_move_down']: 'DOWN', + game.tui.keys['square_move_right']: 'RIGHT', } elif type(game.map_geometry) == MapGeometryHex: game.tui.movement_keys = { - 'w': 'UPLEFT', - 'e': 'UPRIGHT', - 'd': 'RIGHT', - 'c': 'DOWNRIGHT', - 'x': 'DOWNLEFT', - 's': 'LEFT', + game.tui.keys['hex_move_upleft']: 'UPLEFT', + game.tui.keys['hex_move_upright']: 'UPRIGHT', + game.tui.keys['hex_move_right']: 'RIGHT', + game.tui.keys['hex_move_downright']: 'DOWNRIGHT', + game.tui.keys['hex_move_downleft']: 'DOWNLEFT', + game.tui.keys['hex_move_left']: 'LEFT', } cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string' @@ -73,9 +112,7 @@ def cmd_GAME_STATE_COMPLETE(game): game.tui.query_info() player = game.get_thing(game.player_id, False) if player.position in game.portals: - host, port = game.portals[player.position].split(':') - game.tui.teleport_target_host = host - game.tui.teleport_target_port = port + game.tui.teleport_target_host = game.portals[player.position] game.tui.switch_mode('teleport') game.turn_complete = True game.tui.do_refresh = True @@ -106,25 +143,30 @@ def cmd_ANNOTATION(game, position, msg): game.tui.do_refresh = True cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string' +def cmd_PONG(game): + pass +cmd_PONG.argtypes = '' + class Game(GameBase): - commands = {'LOGIN_OK': cmd_LOGIN_OK, - 'CHAT': cmd_CHAT, - 'PLAYER_ID': cmd_PLAYER_ID, - 'TURN': cmd_TURN, - 'THING_POS': cmd_THING_POS, - 'THING_NAME': cmd_THING_NAME, - 'MAP': cmd_MAP, - 'PORTAL': cmd_PORTAL, - 'ANNOTATION': cmd_ANNOTATION, - 'GAME_STATE_COMPLETE': cmd_GAME_STATE_COMPLETE, - 'ARGUMENT_ERROR': cmd_ARGUMENT_ERROR, - 'GAME_ERROR': cmd_GAME_ERROR, - 'PLAY_ERROR': cmd_PLAY_ERROR} thing_type = ThingBase turn_complete = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.register_command(cmd_LOGIN_OK) + self.register_command(cmd_PONG) + self.register_command(cmd_CHAT) + self.register_command(cmd_PLAYER_ID) + self.register_command(cmd_TURN) + self.register_command(cmd_THING_POS) + self.register_command(cmd_THING_NAME) + self.register_command(cmd_MAP) + self.register_command(cmd_PORTAL) + self.register_command(cmd_ANNOTATION) + self.register_command(cmd_GAME_STATE_COMPLETE) + self.register_command(cmd_ARGUMENT_ERROR) + self.register_command(cmd_GAME_ERROR) + self.register_command(cmd_PLAY_ERROR) self.map_content = '' self.player_id = -1 self.info_db = {} @@ -152,9 +194,10 @@ class TUI: self.shows_info = shows_info self.is_intro = is_intro - def __init__(self, host, port): + def __init__(self, host): + import os + import json self.host = host - self.port = port self.mode_play = self.Mode('play') self.mode_study = self.Mode('study', shows_info=True) self.mode_edit = self.Mode('edit') @@ -171,7 +214,32 @@ class TUI: self.log = [] self.do_refresh = True self.queue = queue.Queue() + self.login_name = None self.switch_mode('waiting_for_server') + self.keys = { + 'switch_to_chat': 't', + 'switch_to_play': 'p', + 'switch_to_annotate': 'm', + 'switch_to_portal': 'P', + 'switch_to_study': '?', + 'switch_to_edit': 'm', + 'flatten': 'F', + 'hex_move_upleft': 'w', + 'hex_move_upright': 'e', + 'hex_move_right': 'd', + 'hex_move_downright': 'x', + 'hex_move_downleft': 'y', + 'hex_move_left': 'a', + 'square_move_up': 'w', + 'square_move_left': 'a', + 'square_move_down': 's', + 'square_move_right': 'd', + } + if os.path.isfile('config.json'): + with open('config.json', 'r') as f: + keys_conf = json.loads(f.read()) + for k in keys_conf: + self.keys[k] = keys_conf[k] curses.wrapper(self.loop) def flash(self): @@ -179,8 +247,10 @@ class TUI: def send(self, msg): try: + if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed: + raise BrokenSocketConnection self.socket.send(msg) - except BrokenPipeError: + except (BrokenPipeError, BrokenSocketConnection): self.log_msg('@ server disconnected :(') self.do_refresh = True @@ -200,11 +270,13 @@ class TUI: if self.mode.name == 'waiting_for_server': self.log_msg('@ waiting for server …') elif self.mode.name == 'login': - self.log_msg('@ enter username') + if self.login_name: + self.send('LOGIN ' + quote(self.login_name)) + else: + self.log_msg('@ enter username') elif self.mode.name == 'teleport': - self.log_msg("@ May teleport to %s:%s" % (self.teleport_target_host, - self.teleport_target_port)); - self.log_msg("@ Enter 'YES!' to affirm."); + self.log_msg("@ May teleport to %s" % (self.teleport_target_host)), + self.log_msg("@ Enter 'YES!' to enthusiastically affirm."); elif self.mode.name == 'annotate' and self.explorer in self.game.info_db: info = self.game.info_db[self.explorer] if info != '(none)': @@ -215,23 +287,25 @@ class TUI: def help(self): self.log_msg("HELP:"); self.log_msg("chat mode commands:"); - self.log_msg(" :nick NAME - re-name yourself to NAME"); - self.log_msg(" :msg USER TEXT - send TEXT to USER"); - self.log_msg(" :help - show this help"); - self.log_msg(" :p or :play - switch to play mode"); - self.log_msg(" :? or :study - switch to study mode"); + self.log_msg(" /nick NAME - re-name yourself to NAME"); + self.log_msg(" /msg USER TEXT - send TEXT to USER"); + self.log_msg(" /help - show this help"); + self.log_msg(" /%s or /play - switch to play mode" % self.keys['switch_to_play']); + self.log_msg(" /%s or /study - switch to study mode" % self.keys['switch_to_study']); self.log_msg("commands common to study and play mode:"); - self.log_msg(" w,a,s,d - move"); - self.log_msg(" c - switch to chat mode"); + self.log_msg(" %s - move" % ','.join(self.movement_keys)); + self.log_msg(" %s - switch to chat mode" % self.keys['switch_to_chat']); self.log_msg("commands specific to play mode:"); - self.log_msg(" e - write following ASCII character"); - self.log_msg(" f - flatten surroundings"); - self.log_msg(" ? - switch to study mode"); + self.log_msg(" %s - write following ASCII character" % self.keys['switch_to_edit']); + self.log_msg(" %s - flatten surroundings" % self.keys['flatten']); + self.log_msg(" %s - switch to study mode" % self.keys['switch_to_study']); self.log_msg("commands specific to study mode:"); - self.log_msg(" e - annotate terrain"); - self.log_msg(" p - switch to play mode"); + self.log_msg(" %s - annotate terrain" % self.keys['switch_to_annotate']); + self.log_msg(" %s - switch to play mode" % self.keys['switch_to_play']); def loop(self, stdscr): + import time + import datetime def safe_addstr(y, x, line): if y < self.size.y - 1 or x + len(line) < self.size.x: @@ -245,19 +319,20 @@ class TUI: stdscr.addstr(y, x, cut) def connect(): - import time - def recv_loop(): - for msg in self.socket.recv(): - if msg == 'BYE': - break + def handle_recv(msg): + if msg == 'BYE': + self.socket.close() + else: self.queue.put(msg) + socket_client_class = PlomSocketClient + if self.host.startswith('ws://') or self.host.startswith('wss://'): + socket_client_class = WebSocketClient while True: try: - s = socket.create_connection((self.host, self.port)) - self.socket = PlomSocket(s) - self.socket_thread = threading.Thread(target=recv_loop) + self.socket = socket_client_class(handle_recv, self.host) + self.socket_thread = threading.Thread(target=self.socket.run) self.socket_thread.start() self.switch_mode('login') return @@ -269,6 +344,8 @@ class TUI: def reconnect(): self.send('QUIT') + time.sleep(0.1) # FIXME necessitated by some some strange SSL race + # conditions with ws4py, find out what exactly self.switch_mode('waiting_for_server') connect() @@ -293,7 +370,7 @@ class TUI: def reset_screen_size(): self.size = YX(*stdscr.getmaxyx()) - self.size = self.size - YX(self.size.y % 2, 0) + self.size = self.size - YX(self.size.y % 4, 0) self.size = self.size - YX(0, self.size.x % 4) self.window_width = int(self.size.x / 2) @@ -327,10 +404,15 @@ class TUI: def draw_info(): if not self.game.turn_complete: return + pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x + info = 'TERRAIN: %s\n' % self.game.map_content[pos_i] + for t in self.game.things: + if t.position == self.explorer: + info += 'PLAYER @: %s\n' % t.name if self.explorer in self.game.portals: - info = 'PORTAL: ' + self.game.portals[self.explorer] + '\n' + info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n' else: - info = 'PORTAL: (none)\n' + info += 'PORTAL: (none)\n' if self.explorer in self.game.info_db: info += 'ANNOTATION: ' + self.game.info_db[self.explorer] else: @@ -377,15 +459,14 @@ class TUI: indent = 0 if indent else 1 else: for line in map_lines_split: - map_lines += [''.join(line)] + map_lines += [' '.join(line)] window_center = YX(int(self.size.y / 2), int(self.window_width / 2)) player = self.game.get_thing(self.game.player_id, False) center = player.position if self.mode.shows_info: center = self.explorer - if type(self.game.map_geometry) == MapGeometryHex: - center = YX(center.y, center.x * 2) + center = YX(center.y, center.x * 2) offset = center - window_center if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2: offset += YX(0, 1) @@ -414,13 +495,20 @@ class TUI: draw_map() curses.curs_set(False) # hide cursor + curses.use_default_colors(); stdscr.timeout(10) reset_screen_size() self.explorer = YX(0, 0) self.input_ = '' input_prompt = '> ' connect() + last_ping = datetime.datetime.now() + interval = datetime.timedelta(seconds=30) while True: + now = datetime.datetime.now() + if now - last_ping > interval: + self.send('PING') + last_ping = now if self.do_refresh: draw_screen() self.do_refresh = False @@ -445,25 +533,26 @@ class TUI: if len(self.input_) > max_length: self.input_ = self.input_[:max_length] elif self.mode == self.mode_login and key == '\n': + self.login_name = self.input_ self.send('LOGIN ' + quote(self.input_)) self.input_ = "" elif self.mode == self.mode_chat and key == '\n': - if self.input_[0] == ':': - if self.input_ in {':p', ':play'}: + if self.input_[0] == '/': + if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}: self.switch_mode('play') - elif self.input_ in {':?', ':study'}: + elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}: self.switch_mode('study') - if self.input_ == ':help': + elif self.input_ == '/help': self.help() - if self.input_ == ':reconnect': + elif self.input_ == '/reconnect': reconnect() - elif self.input_.startswith(':nick'): + elif self.input_.startswith('/nick'): tokens = self.input_.split(maxsplit=1) if len(tokens) == 2: self.send('LOGIN ' + quote(tokens[1])) else: self.log_msg('? need login name') - elif self.input_.startswith(':msg'): + elif self.input_.startswith('/msg'): tokens = self.input_.split(maxsplit=2) if len(tokens) == 3: self.send('QUERY %s %s' % (quote(tokens[1]), @@ -490,31 +579,30 @@ class TUI: elif self.mode == self.mode_teleport and key == '\n': if self.input_ == 'YES!': self.host = self.teleport_target_host - self.port = self.teleport_target_port reconnect() else: self.log_msg('@ teleport aborted') self.switch_mode('play') self.input_ = '' elif self.mode == self.mode_study: - if key == 'C': + if key == self.keys['switch_to_chat']: self.switch_mode('chat') - elif key == 'P': + elif key == self.keys['switch_to_play']: self.switch_mode('play') - elif key == 'A': + elif key == self.keys['switch_to_annotate']: self.switch_mode('annotate', keep_position=True) - elif key == 'p': + elif key == self.keys['switch_to_portal']: self.switch_mode('portal', keep_position=True) elif key in self.movement_keys: move_explorer(self.movement_keys[key]) elif self.mode == self.mode_play: - if key == 'C': + if key == self.keys['switch_to_chat']: self.switch_mode('chat') - elif key == '?': + elif key == self.keys['switch_to_study']: self.switch_mode('study') - if key == 'E': + if key == self.keys['switch_to_edit']: self.switch_mode('edit') - elif key == 'f': + elif key == self.keys['flatten']: self.send('TASK:FLATTEN_SURROUNDINGS') elif key in self.movement_keys: self.send('TASK:MOVE ' + self.movement_keys[key]) @@ -522,4 +610,4 @@ class TUI: self.send('TASK:WRITE ' + key) self.switch_mode('play') -TUI('127.0.0.1', 5000) +TUI('localhost:5000')