home · contact · privacy
Add default new keybindings in-code and in-file to curses client.
[plomrogue2-experiments] / new2 / rogue_chat_curses.py
index 5b3a7a81ae4f5c84b699d7b97b30538c502e3d9a..94c808b44093723832c17a6b3c63e16bd201bc40 100755 (executable)
@@ -1,14 +1,49 @@
 #!/usr/bin/env python3
 import curses
 #!/usr/bin/env python3
 import curses
-import socket
 import queue
 import threading
 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.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):
+        try:
+            for msg in self.recv():
+                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
 
 def cmd_TURN(game, n):
     game.turn = n
@@ -48,19 +83,19 @@ def cmd_MAP(game, geometry, size, content):
     game.map_content = content
     if type(game.map_geometry) == MapGeometrySquare:
         game.tui.movement_keys = {
     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 = {
         }
     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'
 
         }
 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
 
@@ -73,9 +108,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:
         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 = game.portals[player.position]
         game.tui.teleport_target_host = game.portals[player.position]
-        game.tui.teleport_target_port = 5000
         game.tui.switch_mode('teleport')
     game.turn_complete = True
     game.tui.do_refresh = True
         game.tui.switch_mode('teleport')
     game.turn_complete = True
     game.tui.do_refresh = True
@@ -152,9 +185,10 @@ class TUI:
             self.shows_info = shows_info
             self.is_intro = is_intro
 
             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.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')
         self.mode_play = self.Mode('play')
         self.mode_study = self.Mode('study', shows_info=True)
         self.mode_edit = self.Mode('edit')
@@ -173,6 +207,30 @@ class TUI:
         self.queue = queue.Queue()
         self.login_name = None
         self.switch_mode('waiting_for_server')
         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):
         curses.wrapper(self.loop)
 
     def flash(self):
@@ -180,8 +238,10 @@ class TUI:
 
     def send(self, msg):
         try:
 
     def send(self, msg):
         try:
+            if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
+                raise BrokenSocketConnection
             self.socket.send(msg)
             self.socket.send(msg)
-        except BrokenPipeError:
+        except (BrokenPipeError, BrokenSocketConnection):
             self.log_msg('@ server disconnected :(')
             self.do_refresh = True
 
             self.log_msg('@ server disconnected :(')
             self.do_refresh = True
 
@@ -206,8 +266,7 @@ class TUI:
             else:
                 self.log_msg('@ enter username')
         elif self.mode.name == 'teleport':
             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("@ 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]
             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]
@@ -222,23 +281,21 @@ class TUI:
         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("  /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("  /%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("commands common to study and play mode:");
-        if type(self.game.map_geometry) == MapGeometrySquare:
-            self.log_msg("  w,a,s,d - move");
-        elif type(self.game.map_geometry) == MapGeometryHex:
-            self.log_msg("  e,d,c,x,s,w - 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("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("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):
 
     def loop(self, stdscr):
+        import time
 
         def safe_addstr(y, x, line):
             if y < self.size.y - 1 or x + len(line) < self.size.x:
 
         def safe_addstr(y, x, line):
             if y < self.size.y - 1 or x + len(line) < self.size.x:
@@ -252,19 +309,20 @@ class TUI:
                 stdscr.addstr(y, x, cut)
 
         def connect():
                 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)
 
                     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:
             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
                     self.socket_thread.start()
                     self.switch_mode('login')
                     return
@@ -276,6 +334,8 @@ class TUI:
 
         def reconnect():
             self.send('QUIT')
 
         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()
 
             self.switch_mode('waiting_for_server')
             connect()
 
@@ -497,36 +557,38 @@ class TUI:
             elif self.mode == self.mode_teleport and key == '\n':
                 if self.input_ == 'YES!':
                     self.host = self.teleport_target_host
             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:
                     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')
                     self.switch_mode('chat')
-                elif key == 'P':
+                elif key == self.keys['switch_to_play']:
                     self.switch_mode('play')
                     self.switch_mode('play')
-                elif key == 'A':
+                elif key == self.keys['switch_to_annotate']:
                     self.switch_mode('annotate', keep_position=True)
                     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:
                     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')
                     self.switch_mode('chat')
-                elif key == '?':
+                elif key == self.keys['switch_to_study']:
                     self.switch_mode('study')
                     self.switch_mode('study')
-                if key == 'E':
+                if key == self.keys['switch_to_edit']:
                     self.switch_mode('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])
                     self.send('TASK:FLATTEN_SURROUNDINGS')
                 elif key in self.movement_keys:
                     self.send('TASK:MOVE ' + self.movement_keys[key])
+                elif key == 'q':
+                    self.log_msg('quitting')
+                    self.send('QUIT')
             elif self.mode == self.mode_edit:
                 self.send('TASK:WRITE ' + key)
                 self.switch_mode('play')
 
             elif self.mode == self.mode_edit:
                 self.send('TASK:WRITE ' + key)
                 self.switch_mode('play')
 
-TUI('127.0.0.1', 5000)
+TUI('127.0.0.1:5000')