home · contact · privacy
Add button control to web client for mouse players.
[plomrogue2] / rogue_chat_nocanvas_monochrome.html
index 28d3773b0898b2866cb9c9a7a5a34f1d7e5f730c..f037bc74978d5f837434a40c9b65aa038a7c1ed3 100644 (file)
@@ -10,27 +10,51 @@ terminal columns: <input id="n_cols" type="number" step=4 min=20 value=80 />
 <pre id="terminal" style="display: inline-block;"></pre>
 <textarea id="input" style="opacity: 0; width: 0px;"></textarea>
 <div>
-keys (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
-move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)<br />
-move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)<br />
-move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)<br />
-move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)<br />
-move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" /><br />
-move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" /><br />
-move right (hex grid): <input id="key_hex_move_right" type="text" value="d" /><br />
-move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" /><br />
-move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" /><br />
-move left (hex grid): <input id="key_hex_move_left" type="text" value="a" /><br />
-help: <input id="key_help" type="text" value="h" /><br />
-flatten surroundings: <input id="key_flatten" type="text" value="F" /><br />
-switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" /><br />
-switch to play mode: <input id="key_switch_to_play" type="text" value="p" /><br />
-switch to study mode: <input id="key_switch_to_study" type="text" value="?" /><br />
-edit terrain (from play mode): <input id="key_switch_to_edit" type="text" value="m" /><br />
-enter terrain password (from play mode): <input id="key_switch_to_password" type="text" value="P" /><br />
-annotate terrain (from study mode): <input id="key_switch_to_annotate" type="text" value="m" /><br />
-annotate portal (from study mode): <input id="key_switch_to_portal" type="text" value="P" /><br />
-toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" /><br />
+<h3>for mouse players</h3>
+<table style="float: left">
+<tr><td><button id="move_upleft">up-left</button></td><td><button id="move_up">up</button></td><td><button id="move_upright">up-right</button></td></tr>
+<tr><td><button id="move_left">left</button></td><td>MOVE</td><td><button id="move_right">right</button></td></tr>
+<tr><td><button id="move_downleft">down-left</button></td><td><button id="move_down">down</button></td><td><button id="move_downright">down-right</button></td></tr>
+</table>
+<div>
+<button id="help">help</button>
+<button id="switch_to_play">play mode</button>
+<button id="switch_to_study">study mode</button>
+<button id="switch_to_chat">chat mode</button><br />
+<button id="take_thing">take thing</button>
+<button id="drop_thing">drop thing</button>
+<button id="flatten">flatten surroundings</button>
+<button id="switch_to_edit">change tile</button><br />
+<button id="switch_to_password">change tile editing password</button>
+<button id="switch_to_annotate">annotate tile</button>
+<button id="switch_to_portal">edit portal link</button>
+<button id="toggle_map_mode">toggle terrain/control view</button>
+</div>
+<h3>edit keybindings</h3> (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
+<ul>
+<li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
+<li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
+<li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
+<li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
+<li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
+<li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
+<li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
+<li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
+<li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
+<li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
+<li>help: <input id="key_help" type="text" value="h" />
+<li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
+<li>take thing under player: <input id="key_take_thing" type="text" value="z" />
+<li>drop carried thing: <input id="key_drop_thing" type="text" value="u" />
+<li>switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" />
+<li>switch to play mode: <input id="key_switch_to_play" type="text" value="p" />
+<li>switch to study mode: <input id="key_switch_to_study" type="text" value="?" />
+<li>edit tile (from play mode): <input id="key_switch_to_edit" type="text" value="m" />
+<li>enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" />
+<li>annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" />
+<li>annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" />
+<li>toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" />
+</ul>
 </div>
 <script>
 "use strict";
@@ -175,20 +199,22 @@ let server = {
         this.url = url;
         this.websocket = new WebSocket(this.url);
         this.websocket.onopen = function(event) {
-            window.setInterval(function() { server.send(['PING']) }, 30000);
-            this.send('TASKS');
+            server.connected = true;
+            game.thing_types = {};
+            game.terrains = {};
+            server.send(['TASKS']);
+            server.send(['TERRAINS']);
+            server.send(['THING_TYPES']);
             tui.log_msg("@ server connected! :)");
             tui.switch_mode(mode_login);
         };
         this.websocket.onclose = function(event) {
+            server.connected = false;
+            tui.switch_mode(mode_waiting_for_server);
             tui.log_msg("@ server disconnected :(");
-            tui.log_msg("@ hint: try the '/reconnect' command");
         };
            this.websocket.onmessage = this.handle_event;
         },
-    reconnect: function() {
-          this.reconnect_to(this.url);
-    },
     reconnect_to: function(url) {
         this.websocket.close();
         this.init(url);
@@ -203,17 +229,33 @@ let server = {
             game.things = {};
             game.portals = {};
             game.turn = parseInt(tokens[1]);
-        } else if (tokens[0] === 'THING_POS') {
-            game.get_thing(tokens[1], true).position = parser.parse_yx(tokens[2]);
+        } else if (tokens[0] === 'THING') {
+            let t = game.get_thing(tokens[3], true);
+            t.position = parser.parse_yx(tokens[1]);
+            t.type_ = tokens[2];
         } else if (tokens[0] === 'THING_NAME') {
-            game.get_thing(tokens[1], true).name_ = tokens[2];
+            let t = game.get_thing(tokens[1], false);
+            if (t) {
+                t.name_ = tokens[2];
+            };
+        } else if (tokens[0] === 'THING_CHAR') {
+            let t = game.get_thing(tokens[1], false);
+            if (t) {
+                t.player_char = tokens[2];
+            };
         } else if (tokens[0] === 'TASKS') {
             game.tasks = tokens[1].split(',')
+        } else if (tokens[0] === 'THING_TYPE') {
+            game.thing_types[tokens[1]] = tokens[2]
+        } else if (tokens[0] === 'TERRAIN') {
+            game.terrains[tokens[1]] = tokens[2]
         } else if (tokens[0] === 'MAP') {
             game.map_geometry = tokens[1];
             tui.init_keys();
             game.map_size = parser.parse_yx(tokens[2]);
             game.map = tokens[3]
+        } else if (tokens[0] === 'FOV') {
+            game.fov = tokens[1]
         } else if (tokens[0] === 'MAP_CONTROL') {
             game.map_control = tokens[1]
         } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
@@ -253,7 +295,7 @@ let server = {
         } else if (tokens[0] === 'GAME_ERROR') {
             tui.log_msg('? game error: ' + tokens[1]);
         } else if (tokens[0] === 'PONG') {
-            console.log('PONG');
+            ;
         } else {
             tui.log_msg('? unhandled input: ' + event.data);
         }
@@ -297,7 +339,7 @@ class Mode {
 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
-let mode_chat = new Mode('chat', 'This mode allows you to engage in chit-chat with other users.  Any line you enter into the input prompt that does not start with a "/" will be sent to all users.  Lines that start with a "/" are used for commands like:', true, false);
+let mode_chat = new Mode('chat', 'This mode allows you to engage in chit-chat with other users.  Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message.  Lines that start with a "/" are used for commands like:', true, false);
   let mode_annotate = new Mode('annotate', 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so).  Hit Return to leave.', true, true);
 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
 let mode_study = new Mode('study', 'This mode allows you to study the map and its tiles in detail.  Move the question mark over a tile, and the right half of the screen will show detailed information on it.', false, true);
@@ -347,21 +389,80 @@ let tui = {
         };
     };
   },
-  switch_mode: function(mode, keep_pos=false) {
+  switch_mode: function(mode) {
     this.show_help = false;
     this.map_mode = 'terrain';
-    if (mode == mode_study && !keep_pos && game.player_id in game.things) {
+    if (mode.shows_info && game.player_id in game.things) {
       explorer.position = game.things[game.player_id].position;
     }
     this.mode = mode;
     this.empty_input();
     this.restore_input_values();
+    document.getElementById("take_thing").disabled = true;
+    document.getElementById("drop_thing").disabled = true;
+    document.getElementById("flatten").disabled = true;
+    document.getElementById("toggle_map_mode").disabled = true;
+    document.getElementById("switch_to_chat").disabled = true;
+    document.getElementById("switch_to_play").disabled = true;
+    document.getElementById("switch_to_study").disabled = true;
+    document.getElementById("switch_to_edit").disabled = true;
+    document.getElementById("switch_to_portal").disabled = true;
+    document.getElementById("switch_to_annotate").disabled = true;
+    document.getElementById("switch_to_password").disabled = true;
+    document.getElementById("move_left").disabled = true;
+    document.getElementById("move_upleft").disabled = true;
+    document.getElementById("move_up").disabled = true;
+    document.getElementById("move_upright").disabled = true;
+    document.getElementById("move_downleft").disabled = true;
+    document.getElementById("move_down").disabled = true;
+    document.getElementById("move_downright").disabled = true;
+    document.getElementById("move_right").disabled = true;
+    if (mode == mode_play || mode == mode_study) {
+        document.getElementById("move_left").disabled = false;
+        document.getElementById("move_right").disabled = false;
+        if (game.map_geometry == 'Hex') {
+            document.getElementById("move_upleft").disabled = false;
+            document.getElementById("move_upright").disabled = false;
+            document.getElementById("move_downleft").disabled = false;
+            document.getElementById("move_downright").disabled = false;
+        } else {
+            document.getElementById("move_up").disabled = false;
+            document.getElementById("move_down").disabled = false;
+        }
+    }
+    if (!mode.is_intro && mode != mode_play) {
+        document.getElementById("switch_to_play").disabled = false;
+    }
+    if (!mode.is_intro && mode != mode_study) {
+        document.getElementById("switch_to_study").disabled = false;
+    }
+    if (!mode.is_intro && mode != mode_chat) {
+        document.getElementById("switch_to_chat").disabled = false;
+    }
     if (mode == mode_login) {
         if (this.login_name) {
             server.send(['LOGIN', this.login_name]);
         } else {
             this.log_msg("? need login name");
         }
+    } else if (mode == mode_play) {
+        if (game.tasks.includes('PICK_UP')) {
+            document.getElementById("take_thing").disabled = false;
+        }
+        if (game.tasks.includes('DROP')) {
+            document.getElementById("drop_thing").disabled = false;
+        }
+        if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
+            document.getElementById("flatten").disabled = false;
+        }
+        if (game.tasks.includes('MOVE')) {
+        }
+        document.getElementById("switch_to_annotate").disabled = false;
+        document.getElementById("switch_to_edit").disabled = false;
+        document.getElementById("switch_to_portal").disabled = false;
+        document.getElementById("switch_to_password").disabled = false;
+    } else if (mode == mode_study) {
+        document.getElementById("toggle_map_mode").disabled = false;
     } else if (mode == mode_edit) {
         this.show_help = true;
     } else if (mode == mode_teleport) {
@@ -434,27 +535,37 @@ let tui = {
             line = [];
             j = 0;
         };
-        line.push(map_content[i]);
+        line.push(map_content[i] + ' ');
     };
     map_lines_split.push(line);
     if (this.map_mode == 'terrain') {
+        let used_positions = [];
         for (const thing_id in game.things) {
             let t = game.things[thing_id];
-            map_lines_split[t.position[0]][t.position[1]] = '@';
+            let symbol = game.thing_types[t.type_];
+            let meta_char = ' ';
+            if (t.player_char) {
+                meta_char = t.player_char;
+            }
+            if (used_positions.includes(t.position.toString())) {
+                meta_char = '+';
+            };
+            map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
+            used_positions.push(t.position.toString());
         };
     }
     if (tui.mode.shows_info) {
-        map_lines_split[explorer.position[0]][explorer.position[1]] = '?';
+        map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
     }
     let map_lines = []
     if (game.map_geometry == 'Square') {
         for (let line_split of map_lines_split) {
-            map_lines.push(line_split.join(' '));
+            map_lines.push(line_split.join(''));
         };
     } else if (game.map_geometry == 'Hex') {
         let indent = 0
         for (let line_split of map_lines_split) {
-            map_lines.push(' '.repeat(indent) + line_split.join(' '));
+            map_lines.push(' '.repeat(indent) + line_split.join(''));
             if (indent == 0) {
                 indent = 1;
             } else {
@@ -520,20 +631,28 @@ let tui = {
   },
   draw_help: function() {
       let movement_keys_desc = Object.keys(this.movement_keys).join(',');
-      let content = this.mode.name + " mode help (hit any key to disappear)\n\n" + this.mode.help_intro + "\n\n";
+      let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
       if (this.mode == mode_play) {
           content += "Available actions:\n";
           if (game.tasks.includes('MOVE')) {
               content += "[" + movement_keys_desc + "] – move player\n";
           }
+          if (game.tasks.includes('PICK_UP')) {
+              content += "[" + this.keys.take_thing + "] – take thing under player\n";
+          }
+          if (game.tasks.includes('DROP')) {
+              content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
+          }
           if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
               content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
           }
           content += '\nOther modes available from here:\n';
-          content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
-          content += '[' + this.keys.switch_to_password + '] – terrain password edit mode\n';
           content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
           content += '[' + this.keys.switch_to_study + '] – study mode\n';
+          content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
+          content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
+          content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
+          content += '[' + this.keys.switch_to_password + '] – password input mode\n';
       } else if (this.mode == mode_study) {
           content += "Available actions:\n";
           content += '[' + movement_keys_desc + '] – move question mark\n';
@@ -541,11 +660,9 @@ let tui = {
           content += '\nOther modes available from here:\n';
           content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
           content += '[' + this.keys.switch_to_play + '] – play mode\n';
-          content += '[' + this.keys.switch_to_portal + '] – portal mode\n';
-          content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
       } else if (this.mode == mode_chat) {
           content += '/nick NAME – re-name yourself to NAME\n';
-          content += '/msg USER TEXT – send TEXT to USER\n';
+          //content += '/msg USER TEXT – send TEXT to USER\n';
           content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
           content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
       }
@@ -661,7 +778,6 @@ let explorer = {
         if (target) {
             this.position = target
             this.query_info();
-            tui.full_refresh();
         } else {
             terminal.blink_screen();
         };
@@ -682,15 +798,27 @@ let explorer = {
         server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
     },
     get_info: function() {
-        let info = "";
         let position_i = this.position[0] * game.map_size[1] + this.position[1];
-        info += "TERRAIN: " + game.map[position_i] + "\n";
+        if (game.fov[position_i] != '.') {
+            return 'outside field of view';
+        };
+        let info = "";
+        let terrain_char = game.map[position_i]
+        let terrain_desc = '?'
+        if (game.terrains[terrain_char]) {
+            terrain_desc = game.terrains[terrain_char];
+        };
+        info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
         for (let t_id in game.things) {
              let t = game.things[t_id];
              if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
-                 info += "PLAYER @";
+                 let symbol = game.thing_types[t.type_];
+                 info += "THING: " + t.type_ + " / " + symbol;
+                 if (t.player_char) {
+                     info += t.player_char;
+                 };
                  if (t.name_) {
-                     info += ": " + t.name_;
+                     info += " (" + t.name_ + ")";
                  }
                  info += "\n";
              }
@@ -709,13 +837,13 @@ let explorer = {
         if (msg.length == 0) {
             msg = " ";  // triggers annotation deletion
         }
-        server.send(["ANNOTATE", tui.password, unparser.to_yx(explorer.position), msg]);
+        server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
     },
     set_portal: function(msg) {
         if (msg.length == 0) {
             msg = " ";  // triggers portal deletion
         }
-        server.send(["PORTAL", tui.password, unparser.to_yx(explorer.position), msg]);
+        server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
     }
 }
 
@@ -726,19 +854,13 @@ tui.inputEl.addEventListener('input', (event) => {
             tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
         };
         tui.recalc_input_lines();
-        tui.full_refresh();
     } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
         server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
         tui.switch_mode(mode_play);
-    } else if (tui.mode == mode_teleport) {
-        if (['Y', 'y'].includes(tui.inputEl.value[0])) {
-            server.reconnect_to(tui.teleport_target);
-       } else {
-            tui.log_msg("@ teleportation aborted");
-            tui.switch_mode(mode_play);
-       }
     }
+    tui.full_refresh();
 }, false);
+
 tui.inputEl.addEventListener('keydown', (event) => {
     tui.show_help = false;
     if (event.key == 'Enter') {
@@ -748,20 +870,18 @@ tui.inputEl.addEventListener('keydown', (event) => {
         tui.show_help = true;
         tui.empty_input();
         tui.restore_input_values();
-        tui.full_refresh();
     } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
         tui.show_help = true;
-        tui.full_refresh();
     } else if (tui.mode == mode_login && event.key == 'Enter') {
         tui.login_name = tui.inputEl.value;
         server.send(['LOGIN', tui.inputEl.value]);
         tui.empty_input();
     } else if (tui.mode == mode_portal && event.key == 'Enter') {
         explorer.set_portal(tui.inputEl.value);
-        tui.switch_mode(mode_study, true);
+        tui.switch_mode(mode_play);
     } else if (tui.mode == mode_annotate && event.key == 'Enter') {
         explorer.annotate(tui.inputEl.value);
-        tui.switch_mode(mode_study, true);
+        tui.switch_mode(mode_play);
     } else if (tui.mode == mode_password && event.key == 'Enter') {
         if (tui.inputEl.value.length == 0) {
             tui.inputEl.value = " ";
@@ -789,31 +909,24 @@ tui.inputEl.addEventListener('keydown', (event) => {
                     } else {
                         tui.log_msg('? need new name');
                     }
-                } else if (tokens[0].slice(1) == 'msg') {
-                    if (tokens.length > 2) {
-                        let msg = tui.inputEl.value.slice(token_starts[2]);
-                        server.send(['QUERY', tokens[1], msg]);
-                    } else {
-                        tui.log_msg('? need message target and message');
-                    }
-                } else if (tokens[0].slice(1) == 'reconnect') {
-                   if (tokens.length > 1) {
-                        server.reconnect_to(tokens[1]);
-                   } else {
-                        server.reconnect();
-                   }
+                //} else if (tokens[0].slice(1) == 'msg') {
+                //    if (tokens.length > 2) {
+                //        let msg = tui.inputEl.value.slice(token_starts[2]);
+                //        server.send(['QUERY', tokens[1], msg]);
+                //    } else {
+                //        tui.log_msg('? need message target and message');
+                //    }
                 } else {
                     tui.log_msg('? unknown command');
                 }
-           } else {
-               server.send(['ALL', tui.inputEl.value]);
+            } else {
+                   server.send(['ALL', tui.inputEl.value]);
             }
-       } else if (tui.inputEl.valuelength > 0) {
-           server.send(['ALL', tui.inputEl.value]);
+        } else if (tui.inputEl.valuelength > 0) {
+               server.send(['ALL', tui.inputEl.value]);
         }
         tui.empty_input();
-        tui.full_refresh();
-      } else if (tui.mode == mode_play) {
+    } else if (tui.mode == mode_play) {
           if (event.key === tui.keys.switch_to_chat) {
               event.preventDefault();
               tui.switch_mode(mode_chat);
@@ -829,9 +942,21 @@ tui.inputEl.addEventListener('keydown', (event) => {
           } else if (event.key === tui.keys.flatten
                      && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
               server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
+          } else if (event.key === tui.keys.take_thing
+                     && game.tasks.includes('PICK_UP')) {
+              server.send(["TASK:PICK_UP"]);
+          } else if (event.key === tui.keys.drop_thing
+                     && game.tasks.includes('DROP')) {
+              server.send(["TASK:DROP"]);
           } else if (event.key in tui.movement_keys
                      && game.tasks.includes('MOVE')) {
               server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
+          } else if (event.key === tui.keys.switch_to_portal) {
+              event.preventDefault();
+              tui.switch_mode(mode_portal);
+          } else if (event.key === tui.keys.switch_to_annotate) {
+              event.preventDefault();
+              tui.switch_mode(mode_annotate);
           };
     } else if (tui.mode == mode_study) {
         if (event.key === tui.keys.switch_to_chat) {
@@ -839,9 +964,6 @@ tui.inputEl.addEventListener('keydown', (event) => {
             tui.switch_mode(mode_chat);
         } else if (event.key == tui.keys.switch_to_play) {
             tui.switch_mode(mode_play);
-        } else if (event.key === tui.keys.switch_to_portal) {
-            event.preventDefault();
-            tui.switch_mode(mode_portal);
         } else if (event.key in tui.movement_keys) {
             explorer.move(tui.movement_keys[event.key]);
         } else if (event.key == tui.keys.toggle_map_mode) {
@@ -850,12 +972,9 @@ tui.inputEl.addEventListener('keydown', (event) => {
             } else {
                 tui.map_mode = 'terrain';
             }
-            tui.full_refresh();
-        } else if (event.key === tui.keys.switch_to_annotate) {
-            event.preventDefault();
-            tui.switch_mode(mode_annotate);
         };
     }
+    tui.full_refresh();
 }, false);
 
 rows_selector.addEventListener('input', function() {
@@ -887,5 +1006,119 @@ window.setInterval(function() {
         tui.inputEl.focus();
     }
 }, 100);
+window.setInterval(function() {
+    if (server.connected) {
+        server.send(['PING']);
+    } else {
+        server.reconnect_to(server.url);
+        tui.log_msg('@ attempting reconnect …')
+    }
+}, 5000);
+
+document.getElementById("help").onclick = function() {
+    tui.show_help = true;
+    tui.full_refresh();
+};
+document.getElementById("switch_to_play").onclick = function() {
+    tui.switch_mode(mode_play);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_study").onclick = function() {
+    tui.switch_mode(mode_study);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_chat").onclick = function() {
+    tui.switch_mode(mode_chat);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_password").onclick = function() {
+    tui.switch_mode(mode_password);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_edit").onclick = function() {
+    tui.switch_mode(mode_edit);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_annotate").onclick = function() {
+    tui.switch_mode(mode_annotate);
+    tui.full_refresh();
+};
+document.getElementById("switch_to_portal").onclick = function() {
+    tui.switch_mode(mode_portal);
+    tui.full_refresh();
+};
+document.getElementById("toggle_map_mode").onclick = function() {
+    if (tui.map_mode == 'terrain') {
+        tui.map_mode = 'control';
+    } else {
+        tui.map_mode = 'terrain';
+    }
+    tui.full_refresh();
+};
+document.getElementById("take_thing").onclick = function() {
+        server.send(['TASK:PICK_UP']);
+};
+document.getElementById("drop_thing").onclick = function() {
+        server.send(['TASK:DROP']);
+};
+document.getElementById("flatten").onclick = function() {
+    server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
+};
+document.getElementById("move_upleft").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'UPLEFT']);
+    } else {
+        explorer.move('UPLEFT');
+    };
+};
+document.getElementById("move_left").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'LEFT']);
+    } else {
+        explorer.move('LEFT');
+    };
+};
+document.getElementById("move_downleft").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'DOWNLEFT']);
+    } else {
+        explorer.move('DOWNLEFT');
+    };
+};
+document.getElementById("move_down").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'DOWN']);
+    } else {
+        explorer.move('DOWN');
+    };
+};
+document.getElementById("move_up").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'UP']);
+    } else {
+        explorer.move('UP');
+    };
+};
+document.getElementById("move_upright").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'UPRIGHT']);
+    } else {
+        explorer.move('UPRIGHT');
+    };
+};
+document.getElementById("move_right").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'RIGHT']);
+    } else {
+        explorer.move('RIGHT');
+    };
+};
+document.getElementById("move_downright").onclick = function() {
+    if (tui.mode == mode_play) {
+        server.send(['TASK:MOVE', 'DOWNRIGHT']);
+    } else {
+        explorer.move('DOWNRIGHT');
+    };
+};
 </script>
 </body></html>