home · contact · privacy
b1dd9929700724c57004413edc17bd5432476c08
[plomrogue2] / rogue_chat_nocanvas_monochrome.html
1 <!DOCTYPE html>
2 <html><head>
3 <style>
4 </style>
5 </head><body>
6 <div>
7 terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
8 terminal columns: <input id="n_cols" type="number" step=4 min=80 value=80 />
9 </div>
10 <pre id="terminal" style="display: inline-block;"></pre>
11 <textarea id="input" style="opacity: 0; width: 0px;"></textarea>
12 <div>
13 <h3>for mouse players</h3>
14 <table style="float: left">
15   <tr>
16     <td><button id="move_upleft">up-left</button></td>
17     <td><button id="move_up">up</button></td>
18     <td><button id="move_upright">up-right</button></td>
19   </tr>
20   <tr>
21     <td><button id="move_left">left</button></td>
22     <td>MOVE</td>
23     <td><button id="move_right">right</button></td>
24   </tr>
25   <tr>
26     <td><button id="move_downleft">down-left</button></td>
27     <td><button id="move_down">down</button></td>
28     <td><button id="move_downright">down-right</button></td>
29   </tr>
30 </table>
31 <table>
32   <tr>
33     <td><button id="help">help</button></td>
34   </tr>
35   <tr>
36     <td><button id="switch_to_chat">chat mode</button><br /></td>
37   </tr>
38     <td><button id="switch_to_study">study mode</button></td>
39     <td><button id="toggle_map_mode">toggle terrain/annotations/control view</button>
40   <tr>
41     <td><button id="switch_to_play">play mode</button></td>
42     <td>
43       <table>
44         <tr>
45           <td><button id="take_thing">take thing</button></td>
46           <td><button id="switch_to_edit">change tile</button></td>
47           <td><button id="switch_to_admin">become admin</button></td>
48         </tr>
49         <tr>
50           <td><button id="drop_thing">drop thing</button></td>
51           <td><button id="switch_to_password">change tile editing password</button></td>
52           <td><button id="switch_to_control_pw_type">change tile control password</button></td>
53         </tr>
54         <tr>
55           <td><button id="flatten">flatten surroundings</button></td>
56           <td><button id="switch_to_annotate">annotate tile</button></td>
57           <td><button id="switch_to_control_tile_type">change tiles control</button></td>
58         </tr>
59         <tr>
60           <td><button id="teleport">teleport</button></td>
61           <td><button id="switch_to_portal">edit portal link</button></td>
62         </tr>
63       </table>
64     </td>
65   </tr>
66 </table>
67 <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 />
68 <ul>
69 <li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
70 <li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
71 <li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
72 <li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
73 <li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
74 <li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
75 <li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
76 <li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
77 <li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
78 <li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
79 <li>help: <input id="key_help" type="text" value="h" />
80 <li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
81 <li>teleport: <input id="key_teleport" type="text" value="p" />
82 <li>take thing under player: <input id="key_take_thing" type="text" value="z" />
83 <li>drop carried thing: <input id="key_drop_thing" type="text" value="u" />
84 <li><input id="key_switch_to_chat" type="text" value="t" />
85 <li><input id="key_switch_to_play" type="text" value="p" />
86 <li><input id="key_switch_to_study" type="text" value="?" />
87 <li><input id="key_switch_to_edit" type="text" value="m" />
88 <li><input id="key_switch_to_password" type="text" value="P" />
89 <li><input id="key_switch_to_admin" type="text" value="A" />
90 <li><input id="key_switch_to_control_pw_type" type="text" value="C" />
91 <li><input id="key_switch_to_control_tile_type" type="text" value="Q" />
92 <li><input id="key_switch_to_annotate" type="text" value="M" />
93 <li><input id="key_switch_to_portal" type="text" value="T" />
94 <li>toggle terrain/annotations/control view: <input id="key_toggle_map_mode" type="text" value="M" />
95 </ul>
96 </div>
97 <script>
98 "use strict";
99 let websocket_location = "wss://plomlompom.com/rogue_chat/";
100 //let websocket_location = "ws://localhost:8000/";
101
102 let mode_helps = {
103     'play': {
104         'short': 'play',
105         'long': 'This mode allows you to interact with the map.'
106     },
107     'study': {
108         'short': 'study',
109         'long': '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.'},
110     'edit': {
111         'short': 'terrain edit',
112         'long': 'This mode allows you to change the map tile you currently stand on (if your map editing password authorizes you so).  Just enter any printable ASCII character to imprint it on the ground below you.'
113     },
114     'control_pw_type': {
115         'short': 'change tiles control password',
116         'long': 'This mode is the first of two steps to change the password for a tile control character.  First enter the tile control character for which you want to change the password!'
117     },
118     'control_pw_pw': {
119         'short': 'change tiles control password',
120         'long': 'This mode is the second of two steps to change the password for a tile control character.  Enter the new password for the tile control character you chose.'
121     },
122     'control_tile_type': {
123         'short': 'change tiles control',
124         'long': 'This mode is the first of two steps to change tile control areas on the map.  First enter the tile control character you want to write.'
125     },
126     'control_tile_draw': {
127         'short': 'change tiles control',
128         'long': 'This mode is the second of two steps to change tile control areas on the map.  Move cursor around the map to draw selected tile control character'
129     },
130     'annotate': {
131         'short': 'annotate tile',
132         'long': '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.'
133     },
134     'portal': {
135         'short': 'edit portal',
136         'long': 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your map editing password authorizes you so).  Enter or edit a URL to imprint a teleportation target; enter emptiness to remove a pre-existing teleportation target.  Hit Return to leave.'
137     },
138     'chat': {
139         'short': 'chat',
140         'long': '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:'
141     },
142     'login': {
143         'short': 'login',
144         'long': 'Pick your player name.'
145     },
146     'waiting_for_server': {
147         'short': 'waiting for server response',
148         'long': 'Waiting for a server response.'
149     },
150     'post_login_wait': {
151         'short': 'waiting for server response',
152         'long': 'Waiting for a server response.'
153     },
154     'password': {
155         'short': 'map edit password',
156         'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles.  Hit return to confirm and leave.'
157     },
158     'admin': {
159         'short': 'become admin',
160         'long': 'This mode allows you to become admin if you know an admin password.'
161     }
162 }
163
164 let rows_selector = document.getElementById("n_rows");
165 let cols_selector = document.getElementById("n_cols");
166 let key_selectors = document.querySelectorAll('[id^="key_"]');
167
168 for (const key_switch_selector of document.querySelectorAll('[id^="key_switch_to_"]')) {
169     const action = key_switch_selector.id.slice("key_switch_to_".length);
170     key_switch_selector.parentNode.prepend(mode_helps[action].short + ': ');
171 }
172
173 function restore_selector_value(selector) {
174     let stored_selection = window.localStorage.getItem(selector.id);
175     if (stored_selection) {
176         selector.value = stored_selection;
177     }
178 }
179 restore_selector_value(rows_selector);
180 restore_selector_value(cols_selector);
181 for (let key_selector of key_selectors) {
182     restore_selector_value(key_selector);
183 }
184
185 let terminal = {
186   foreground: 'white',
187   background: 'black',
188   initialize: function() {
189     this.rows = rows_selector.value;
190     this.cols = cols_selector.value;
191     this.pre_el = document.getElementById("terminal");
192     this.pre_el.style.color = this.foreground;
193     this.pre_el.style.backgroundColor = this.background;
194     this.content = [];
195       let line = []
196     for (let y = 0, x = 0; y <= this.rows; x++) {
197         if (x == this.cols) {
198             x = 0;
199             y += 1;
200             this.content.push(line);
201             line = [];
202             if (y == this.rows) {
203                 break;
204             }
205         }
206         line.push(' ');
207     }
208   },
209   blink_screen: function() {
210       this.pre_el.style.color = this.background;
211       this.pre_el.style.backgroundColor = this.foreground;
212       setTimeout(() => {
213           this.pre_el.style.color = this.foreground;
214           this.pre_el.style.backgroundColor = this.background;
215       }, 100);
216   },
217   refresh: function() {
218       function escapeHTML(str) {
219           return str.
220               replace(/&/g, '&amp;').
221               replace(/</g, '&lt;').
222               replace(/>/g, '&gt;').
223               replace(/'/g, '&apos;').
224               replace(/"/g, '&quot;');
225       };
226       let pre_content = '';
227       for (let y = 0; y < this.rows; y++) {
228           let line = this.content[y].join('');
229           let chunks = [];
230           if (y in tui.links) {
231               let start_x = 0;
232               for (let span of tui.links[y]) {
233                   chunks.push(escapeHTML(line.slice(start_x, span[0])));
234                   chunks.push('<a href="');
235                   chunks.push(escapeHTML(span[2]));
236                   chunks.push('">');
237                   chunks.push(escapeHTML(line.slice(span[0], span[1])));
238                   chunks.push('</a>');
239                   start_x = span[1];
240               }
241               chunks.push(escapeHTML(line.slice(start_x)));
242           } else {
243               chunks = [escapeHTML(line)];
244           }
245           for (const chunk of chunks) {
246               pre_content += chunk;
247           }
248           pre_content += '\n';
249       }
250       this.pre_el.innerHTML = pre_content;
251   },
252   write: function(start_y, start_x, msg) {
253       for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
254           this.content[start_y][x] = msg[i];
255       }
256   },
257   drawBox: function(start_y, start_x, height, width) {
258     let end_y = start_y + height;
259     let end_x = start_x + width;
260     for (let y = start_y, x = start_x; y < this.rows; x++) {
261       if (x == end_x) {
262         x = start_x;
263         y += 1;
264         if (y == end_y) {
265             break;
266         }
267       };
268       this.content[y][x] = ' ';
269     }
270   },
271 }
272 terminal.initialize();
273
274 let parser = {
275   tokenize: function(str) {
276     let tokens = [];
277     let token = ''
278     let quoted = false;
279     let escaped = false;
280     for (let i = 0; i < str.length; i++) {
281       let c = str[i];
282       if (quoted) {
283         if (escaped) {
284           token += c;
285           escaped = false;
286         } else if (c == '\\') {
287           escaped = true;
288         } else if (c == '"') {
289           quoted = false
290         } else {
291           token += c;
292         }
293       } else if (c == '"') {
294         quoted = true
295       } else if (c === ' ') {
296         if (token.length > 0) {
297           tokens.push(token);
298           token = '';
299         }
300       } else {
301         token += c;
302       }
303     }
304     if (token.length > 0) {
305       tokens.push(token);
306     }
307     return tokens;
308   },
309   parse_yx: function(position_string) {
310     let coordinate_strings = position_string.split(',')
311     let position = [0, 0];
312     position[0] = parseInt(coordinate_strings[0].slice(2));
313     position[1] = parseInt(coordinate_strings[1].slice(2));
314     return position;
315   },
316 }
317
318 class Thing {
319     constructor(yx) {
320         this.position = yx;
321     }
322 }
323
324 let server = {
325     init: function(url) {
326         this.url = url;
327         this.websocket = new WebSocket(this.url);
328         this.websocket.onopen = function(event) {
329             server.connected = true;
330             game.thing_types = {};
331             game.terrains = {};
332             server.send(['TASKS']);
333             server.send(['TERRAINS']);
334             server.send(['THING_TYPES']);
335             tui.log_msg("@ server connected! :)");
336             tui.switch_mode('login');
337         };
338         this.websocket.onclose = function(event) {
339             server.connected = false;
340             tui.switch_mode('waiting_for_server');
341             tui.log_msg("@ server disconnected :(");
342         };
343             this.websocket.onmessage = this.handle_event;
344         },
345     reconnect_to: function(url) {
346         this.websocket.close();
347         this.init(url);
348     },
349     send: function(tokens) {
350         this.websocket.send(unparser.untokenize(tokens));
351     },
352     handle_event: function(event) {
353         let tokens = parser.tokenize(event.data);
354         if (tokens[0] === 'TURN') {
355             game.turn_complete = false;
356             explorer.empty_info_db();
357             game.things = {};
358             game.portals = {};
359             game.turn = parseInt(tokens[1]);
360         } else if (tokens[0] === 'THING') {
361             let t = game.get_thing(tokens[3], true);
362             t.position = parser.parse_yx(tokens[1]);
363             t.type_ = tokens[2];
364         } else if (tokens[0] === 'THING_NAME') {
365             let t = game.get_thing(tokens[1], false);
366             if (t) {
367                 t.name_ = tokens[2];
368             };
369         } else if (tokens[0] === 'THING_CHAR') {
370             let t = game.get_thing(tokens[1], false);
371             if (t) {
372                 t.player_char = tokens[2];
373             };
374         } else if (tokens[0] === 'TASKS') {
375             game.tasks = tokens[1].split(',');
376             tui.mode_edit.legal = game.tasks.includes('WRITE');
377         } else if (tokens[0] === 'THING_TYPE') {
378             game.thing_types[tokens[1]] = tokens[2]
379         } else if (tokens[0] === 'TERRAIN') {
380             game.terrains[tokens[1]] = tokens[2]
381         } else if (tokens[0] === 'MAP') {
382             game.map_geometry = tokens[1];
383             tui.init_keys();
384             game.map_size = parser.parse_yx(tokens[2]);
385             game.map = tokens[3]
386         } else if (tokens[0] === 'FOV') {
387             game.fov = tokens[1]
388         } else if (tokens[0] === 'MAP_CONTROL') {
389             game.map_control = tokens[1]
390         } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
391             game.turn_complete = true;
392             if (tui.mode.name == 'post_login_wait') {
393                 tui.switch_mode('play');
394             } else if (tui.mode.name == 'study') {
395                 explorer.query_info();
396             }
397             tui.full_refresh();
398         } else if (tokens[0] === 'CHAT') {
399              tui.log_msg('# ' + tokens[1], 1);
400         } else if (tokens[0] === 'PLAYER_ID') {
401             game.player_id = parseInt(tokens[1]);
402         } else if (tokens[0] === 'LOGIN_OK') {
403             this.send(['GET_GAMESTATE']);
404             tui.switch_mode('post_login_wait');
405         } else if (tokens[0] === 'PORTAL') {
406             let position = parser.parse_yx(tokens[1]);
407             game.portals[position] = tokens[2];
408         } else if (tokens[0] === 'ANNOTATION_HINT') {
409             let position = parser.parse_yx(tokens[1]);
410             explorer.info_hints = explorer.info_hints.concat([position]);
411         } else if (tokens[0] === 'ANNOTATION') {
412             let position = parser.parse_yx(tokens[1]);
413             explorer.update_info_db(position, tokens[2]);
414             tui.restore_input_values();
415             tui.full_refresh();
416         } else if (tokens[0] === 'UNHANDLED_INPUT') {
417             tui.log_msg('? unknown command');
418         } else if (tokens[0] === 'PLAY_ERROR') {
419             tui.log_msg('? ' + tokens[1]);
420             terminal.blink_screen();
421         } else if (tokens[0] === 'ARGUMENT_ERROR') {
422             tui.log_msg('? syntax error: ' + tokens[1]);
423         } else if (tokens[0] === 'GAME_ERROR') {
424             tui.log_msg('? game error: ' + tokens[1]);
425         } else if (tokens[0] === 'PONG') {
426             ;
427         } else {
428             tui.log_msg('? unhandled input: ' + event.data);
429         }
430     }
431 }
432
433 let unparser = {
434     quote: function(str) {
435         let quoted = ['"'];
436         for (let i = 0; i < str.length; i++) {
437             let c = str[i];
438             if (['"', '\\'].includes(c)) {
439                 quoted.push('\\');
440             };
441             quoted.push(c);
442         }
443         quoted.push('"');
444         return quoted.join('');
445     },
446     to_yx: function(yx_coordinate) {
447         return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
448     },
449     untokenize: function(tokens) {
450         let quoted_tokens = [];
451         for (let token of tokens) {
452             quoted_tokens.push(this.quote(token));
453         }
454         return quoted_tokens.join(" ");
455     }
456 }
457
458 class Mode {
459     constructor(name, has_input_prompt=false, shows_info=false,
460                 is_intro=false, is_single_char_entry=false) {
461         this.name = name;
462         this.short_desc = mode_helps[name].short;
463         this.available_modes = [];
464         this.has_input_prompt = has_input_prompt;
465         this.shows_info= shows_info;
466         this.is_intro = is_intro;
467         this.help_intro = mode_helps[name].long;
468         this.is_single_char_entry = is_single_char_entry;
469         this.legal = true;
470     }
471     *iter_available_modes() {
472         for (let mode_name of this.available_modes) {
473             let mode = tui['mode_' + mode_name];
474             if (!mode.legal) {
475                 continue;
476             }
477             let key = tui.keys['switch_to_' + mode.name];
478             yield [mode, key]
479         }
480     }
481     list_available_modes() {
482         let msg = ''
483         if (this.available_modes.length > 0) {
484             msg += 'Other modes available from here:\n';
485             for (let [mode, key] of this.iter_available_modes()) {
486                 msg += '[' + key + '] – ' + mode.short_desc + '\n';
487             }
488         }
489         return msg;
490     }
491     mode_switch_on_key(key_event) {
492         for (let [mode, key] of this.iter_available_modes()) {
493             if (key_event.key == key) {
494                 event.preventDefault();
495                 tui.switch_mode(mode.name);
496                 return true;
497             };
498         }
499         return false;
500     }
501 }
502 let tui = {
503   links: {},
504   log: [],
505   input_prompt: '> ',
506   input_lines: [],
507   window_width: terminal.cols / 2,
508   height_turn_line: 1,
509   height_mode_line: 1,
510   height_input: 1,
511   password: 'foo',
512   show_help: false,
513   mode_waiting_for_server: new Mode('waiting_for_server',
514                                      false, false, true),
515   mode_login: new Mode('login', true, false, true),
516   mode_post_login_wait: new Mode('post_login_wait'),
517   mode_chat: new Mode('chat', true),
518   mode_annotate: new Mode('annotate', true, true),
519   mode_play: new Mode('play'),
520   mode_study: new Mode('study', false, true),
521   mode_edit: new Mode('edit', false, false, false, true),
522   mode_control_pw_type: new Mode('control_pw_type',
523                                   false, false, false, true),
524   mode_portal: new Mode('portal', true, true),
525   mode_password: new Mode('password', true),
526   mode_admin: new Mode('admin', true),
527   mode_control_pw_pw: new Mode('control_pw_pw', true),
528   mode_control_tile_type: new Mode('control_tile_type',
529                                    false, false, false, true),
530   mode_control_tile_draw: new Mode('control_tile_draw'),
531   init: function() {
532       this.mode_play.available_modes = ["chat", "study", "edit",
533                                         "annotate", "portal",
534                                         "password", "admin",
535                                         "control_pw_type",
536                                         "control_tile_type"]
537       this.mode_study.available_modes = ["chat", "play"]
538       this.mode_control_tile_draw.available_modes = ["play"]
539       this.mode = this.mode_waiting_for_server;
540       this.inputEl = document.getElementById("input");
541       this.inputEl.focus();
542       this.recalc_input_lines();
543       this.height_header = this.height_turn_line + this.height_mode_line;
544       this.log_msg("@ waiting for server connection ...");
545       this.init_keys();
546   },
547   init_keys: function() {
548     this.keys = {};
549     for (let key_selector of key_selectors) {
550         this.keys[key_selector.id.slice(4)] = key_selector.value;
551     }
552     this.movement_keys = {
553         [this.keys.square_move_up]: 'UP',
554         [this.keys.square_move_left]: 'LEFT',
555         [this.keys.square_move_down]: 'DOWN',
556         [this.keys.square_move_right]: 'RIGHT'
557     };
558     if (game.map_geometry == 'Hex') {
559         this.movement_keys = {
560             [this.keys.hex_move_upleft]: 'UPLEFT',
561             [this.keys.hex_move_upright]: 'UPRIGHT',
562             [this.keys.hex_move_right]: 'RIGHT',
563             [this.keys.hex_move_downright]: 'DOWNRIGHT',
564             [this.keys.hex_move_downleft]: 'DOWNLEFT',
565             [this.keys.hex_move_left]: 'LEFT'
566         };
567     };
568   },
569   switch_mode: function(mode_name) {
570     this.inputEl.focus();
571     this.map_mode = 'terrain';
572     this.mode = this['mode_' + mode_name];
573     if (game.player_id in game.things && (this.mode.shows_info || this.mode.name == 'control_tile_draw')) {
574         explorer.position = game.things[game.player_id].position;
575         if (this.mode.shows_info) {
576         explorer.query_info();
577         } else if (this.mode.name == 'control_tile_draw') {
578             explorer.send_tile_control_command();
579             this.map_mode = 'control';
580         }
581     }
582     this.empty_input();
583     this.restore_input_values();
584     for (let el of document.getElementsByTagName("button")) {
585         el.disabled = true;
586     }
587     document.getElementById("help").disabled = false;
588     if (this.mode.name == 'play' || this.mode.name == 'study' || this.mode.name == 'control_tile_draw') {
589         document.getElementById("move_left").disabled = false;
590         document.getElementById("move_right").disabled = false;
591         if (game.map_geometry == 'Hex') {
592             document.getElementById("move_upleft").disabled = false;
593             document.getElementById("move_upright").disabled = false;
594             document.getElementById("move_downleft").disabled = false;
595             document.getElementById("move_downright").disabled = false;
596         } else {
597             document.getElementById("move_up").disabled = false;
598             document.getElementById("move_down").disabled = false;
599         }
600     }
601     if (!this.mode.is_intro && this.mode.name != 'play') {
602         document.getElementById("switch_to_play").disabled = false;
603     }
604     if (!this.mode.is_intro && this.mode.name != 'study') {
605         document.getElementById("switch_to_study").disabled = false;
606     }
607     if (!this.mode.is_intro && this.mode.name != 'chat') {
608         document.getElementById("switch_to_chat").disabled = false;
609     }
610     if (this.mode.name == 'login') {
611         if (this.login_name) {
612             server.send(['LOGIN', this.login_name]);
613         } else {
614             this.log_msg("? need login name");
615         }
616     } else if (this.mode.name == 'play') {
617         if (game.tasks.includes('PICK_UP')) {
618             document.getElementById("take_thing").disabled = false;
619         }
620         if (game.tasks.includes('DROP')) {
621             document.getElementById("drop_thing").disabled = false;
622         }
623         if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
624             document.getElementById("flatten").disabled = false;
625         }
626         if (game.tasks.includes('MOVE')) {
627         }
628         document.getElementById("teleport").disabled = false;
629         document.getElementById("switch_to_annotate").disabled = false;
630         document.getElementById("switch_to_edit").disabled = false;
631         document.getElementById("switch_to_portal").disabled = false;
632         document.getElementById("switch_to_password").disabled = false;
633         document.getElementById("switch_to_admin").disabled = false;
634         document.getElementById("switch_to_control_pw_type").disabled = false;
635         document.getElementById("switch_to_control_tile_type").disabled = false;
636     } else if (this.mode.name == 'study') {
637         document.getElementById("toggle_map_mode").disabled = false;
638     } else if (this.mode.is_single_char_entry) {
639         this.show_help = true;
640     } else if (this.mode.name == 'admin') {
641         this.log_msg('@ enter admin password:')
642     } else if (this.mode.name == 'control_pw_pw') {
643         this.log_msg('@ enter tile control password for "' + this.tile_control_char + '":');
644     } else if (this.mode.name == 'control_pw_pw') {
645         this.log_msg('@ enter tile control password for "' + this.tile_control_char + '":');
646     }
647     this.full_refresh();
648   },
649   offset_links: function(offset, links) {
650       for (let y in links) {
651           let real_y = offset[0] + parseInt(y);
652           if (!this.links[real_y]) {
653               this.links[real_y] = [];
654           }
655           for (let link of links[y]) {
656               const offset_link = [link[0] + offset[1], link[1] + offset[1], link[2]];
657               this.links[real_y].push(offset_link);
658           }
659       }
660   },
661   restore_input_values: function() {
662       if (this.mode.name == 'annotate' && explorer.position in explorer.info_db) {
663           let info = explorer.info_db[explorer.position];
664           if (info != "(none)") {
665               this.inputEl.value = info;
666               this.recalc_input_lines();
667           }
668       } else if (this.mode.name == 'portal' && explorer.position in game.portals) {
669           let portal = game.portals[explorer.position]
670           this.inputEl.value = portal;
671           this.recalc_input_lines();
672       } else if (this.mode.name == 'password') {
673           this.inputEl.value = this.password;
674           this.recalc_input_lines();
675       }
676   },
677   empty_input: function(str) {
678       this.inputEl.value = "";
679       if (this.mode.has_input_prompt) {
680           this.recalc_input_lines();
681       } else {
682           this.height_input = 0;
683       }
684   },
685   recalc_input_lines: function() {
686       let _ = null;
687       [this.input_lines, _] = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
688       this.height_input = this.input_lines.length;
689   },
690   msg_into_lines_of_width: function(msg, width) {
691       function push_inner_link(y, end_x) {
692           if (!inner_links[y]) {
693               inner_links[y] = [];
694           };
695           inner_links[y].push([url_start_x, end_x, url]);
696       };
697       const matches = msg.matchAll(/https?:\/\/[^\s]+/g)
698       let link_data = {};
699       let url_ends = [];
700       for (const match of matches) {
701           const url = match[0];
702           const url_start = match.index;
703           const url_end = match.index + match[0].length;
704           link_data[url_start] = url;
705           url_ends.push(url_end);
706       }
707       let url_start_x = 0;
708       let url = '';
709       let inner_links = {};
710       let in_link = false;
711       let chunk = "";
712       let lines = [];
713       for (let i = 0, x = 0, y = 0; i < msg.length; i++, x++) {
714           if (x >= width || msg[i] == "\n") {
715               if (in_link) {
716                   push_inner_link(y, chunk.length);
717                   url_start_x = 0;
718               };
719               lines.push(chunk);
720               chunk = "";
721               x = 0;
722               if (msg[i] == "\n") {
723                   x -= 1;
724               };
725               y += 1;
726           };
727           if (msg[i] != "\n") {
728               chunk += msg[i];
729           };
730           if (i in link_data) {
731               url_start_x = x;
732               url = link_data[i];
733               in_link = true;
734           } else if (url_ends.includes(i)) {
735               push_inner_link(y, x);
736               in_link = false;
737           }
738       }
739       lines.push(chunk);
740       if (in_link) {
741           push_inner_link(lines.length - 1, chunk.length);
742       }
743       return [lines, inner_links];
744   },
745   log_msg: function(msg) {
746       this.log.push(msg);
747       while (this.log.length > 100) {
748         this.log.shift();
749       };
750       this.full_refresh();
751   },
752   draw_map: function() {
753     let map_lines_split = [];
754     let line = [];
755     let map_content = game.map;
756     if (this.map_mode == 'control') {
757         map_content = game.map_control;
758     }
759     for (let i = 0, j = 0; i < game.map.length; i++, j++) {
760         if (j == game.map_size[1]) {
761             map_lines_split.push(line);
762             line = [];
763             j = 0;
764         };
765         line.push(map_content[i] + ' ');
766     };
767     map_lines_split.push(line);
768     if (this.map_mode == 'annotations') {
769         for (const coordinate of explorer.info_hints) {
770             map_lines_split[coordinate[0]][coordinate[1]] = 'A ';
771         }
772     } else if (this.map_mode == 'terrain') {
773         for (const p in game.portals) {
774             let coordinate = p.split(',')
775             map_lines_split[coordinate[0]][coordinate[1]] = 'P ';
776         }
777         let used_positions = [];
778         for (const thing_id in game.things) {
779             let t = game.things[thing_id];
780             let symbol = game.thing_types[t.type_];
781             let meta_char = ' ';
782             if (t.player_char) {
783                 meta_char = t.player_char;
784             }
785             if (used_positions.includes(t.position.toString())) {
786                 meta_char = '+';
787             };
788             map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
789             used_positions.push(t.position.toString());
790         };
791     }
792     if (tui.mode.shows_info || tui.mode.name == 'control_tile_draw') {
793         map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
794     }
795     let map_lines = []
796     if (game.map_geometry == 'Square') {
797         for (let line_split of map_lines_split) {
798             map_lines.push(line_split.join(''));
799         };
800     } else if (game.map_geometry == 'Hex') {
801         let indent = 0
802         for (let line_split of map_lines_split) {
803             map_lines.push(' '.repeat(indent) + line_split.join(''));
804             if (indent == 0) {
805                 indent = 1;
806             } else {
807                 indent = 0;
808             };
809         };
810     }
811     let window_center = [terminal.rows / 2, this.window_width / 2];
812     let player = game.things[game.player_id];
813     let center_position = [player.position[0], player.position[1]];
814     if (tui.mode.shows_info) {
815         center_position = [explorer.position[0], explorer.position[1]];
816     }
817     center_position[1] = center_position[1] * 2;
818     let offset = [center_position[0] - window_center[0],
819                   center_position[1] - window_center[1]]
820     if (game.map_geometry == 'Hex' && offset[0] % 2) {
821         offset[1] += 1;
822     };
823     let term_y = Math.max(0, -offset[0]);
824     let term_x = Math.max(0, -offset[1]);
825     let map_y = Math.max(0, offset[0]);
826     let map_x = Math.max(0, offset[1]);
827     for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
828         let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
829         terminal.write(term_y, term_x, to_draw);
830     }
831   },
832   draw_mode_line: function() {
833       let help = 'hit [' + this.keys.help + '] for help';
834       if (this.mode.has_input_prompt) {
835           help = 'enter /help for help';
836       }
837       terminal.write(0, this.window_width, 'MODE: ' + this.mode.short_desc + ' – ' + help);
838   },
839   draw_turn_line: function(n) {
840     terminal.write(1, this.window_width, 'TURN: ' + game.turn);
841   },
842   draw_history: function() {
843       let log_display_lines = [];
844       let log_links = {};
845       let y_offset_in_log = 0;
846       for (let line of this.log) {
847           let [new_lines, link_data] = this.msg_into_lines_of_width(line,
848                                                                     this.window_width)
849           log_display_lines = log_display_lines.concat(new_lines);
850           for (const y in link_data) {
851               const rel_y = y_offset_in_log + parseInt(y);
852               log_links[rel_y] = [];
853               for (let link of link_data[y]) {
854                   log_links[rel_y].push(link);
855               }
856           }
857           y_offset_in_log += new_lines.length;
858       };
859       let i = log_display_lines.length - 1;
860       for (let y = terminal.rows - 1 - this.height_input;
861            y >= this.height_header && i >= 0;
862            y--, i--) {
863           terminal.write(y, this.window_width, log_display_lines[i]);
864       }
865       for (const key of Object.keys(log_links)) {
866           if (parseInt(key) <= i) {
867               delete log_links[key];
868           }
869       }
870       let offset = [terminal.rows - this.height_input - log_display_lines.length,
871                     this.window_width];
872       this.offset_links(offset, log_links);
873   },
874   draw_info: function() {
875       let [lines, link_data] = this.msg_into_lines_of_width(explorer.get_info(),
876                                                             this.window_width);
877       let offset = [this.height_header, this.window_width];
878       for (let y = offset[0], i = 0; y < terminal.rows && i < lines.length; y++, i++) {
879         terminal.write(y, offset[1], lines[i]);
880       }
881       this.offset_links(offset, link_data);
882   },
883   draw_input: function() {
884     if (this.mode.has_input_prompt) {
885         for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
886             terminal.write(y, this.window_width, this.input_lines[i]);
887         }
888     }
889   },
890   draw_help: function() {
891       let movement_keys_desc = Object.keys(this.movement_keys).join(',');
892       let content = this.mode.short_desc + " help\n\n" + this.mode.help_intro + "\n\n";
893       if (this.mode.name == 'play') {
894           content += "Available actions:\n";
895           if (game.tasks.includes('MOVE')) {
896               content += "[" + movement_keys_desc + "] – move player\n";
897           }
898           if (game.tasks.includes('PICK_UP')) {
899               content += "[" + this.keys.take_thing + "] – take thing under player\n";
900           }
901           if (game.tasks.includes('DROP')) {
902               content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
903           }
904           if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
905               content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
906           }
907           content += "[" + tui.keys.teleport + "] – teleport to other space\n";
908           content += '\n';
909       } else if (this.mode.name == 'study') {
910           content += "Available actions:\n";
911           content += '[' + movement_keys_desc + '] – move question mark\n';
912           content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, annotations, and password protection areas\n';
913           content += '\n';
914       } else if (this.mode.name == 'chat') {
915           content += '/nick NAME – re-name yourself to NAME\n';
916           content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
917           content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
918       }
919       content += this.mode.list_available_modes();
920       let start_x = 0;
921       if (!this.mode.has_input_prompt) {
922           start_x = this.window_width
923       }
924       terminal.drawBox(0, start_x, terminal.rows, this.window_width);
925       let [lines, _] = this.msg_into_lines_of_width(content, this.window_width);
926       for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
927           terminal.write(y, start_x, lines[i]);
928       }
929   },
930   full_refresh: function() {
931     this.links = {};
932     terminal.drawBox(0, 0, terminal.rows, terminal.cols);
933     if (this.mode.is_intro) {
934         this.draw_history();
935         this.draw_input();
936     } else {
937         if (game.turn_complete) {
938             this.draw_map();
939             this.draw_turn_line();
940         }
941         this.draw_mode_line();
942         if (this.mode.shows_info) {
943           this.draw_info();
944         } else {
945           this.draw_history();
946         }
947         this.draw_input();
948     }
949     if (this.show_help) {
950         this.draw_help();
951     }
952     terminal.refresh();
953   }
954 }
955
956 let game = {
957     init: function() {
958         this.things = {};
959         this.turn = -1;
960         this.map = "";
961         this.map_control = "";
962         this.map_size = [0,0];
963         this.player_id = -1;
964         this.portals = {};
965         this.tasks = {};
966     },
967     get_thing: function(id_, create_if_not_found=false) {
968         if (id_ in game.things) {
969             return game.things[id_];
970         } else if (create_if_not_found) {
971             let t = new Thing([0,0]);
972             game.things[id_] = t;
973             return t;
974         };
975     },
976     move: function(start_position, direction) {
977         let target = [start_position[0], start_position[1]];
978         if (direction == 'LEFT') {
979             target[1] -= 1;
980         } else if (direction == 'RIGHT') {
981             target[1] += 1;
982         } else if (game.map_geometry == 'Square') {
983             if (direction == 'UP') {
984                 target[0] -= 1;
985             } else if (direction == 'DOWN') {
986                 target[0] += 1;
987             };
988         } else if (game.map_geometry == 'Hex') {
989             let start_indented = start_position[0] % 2;
990             if (direction == 'UPLEFT') {
991                 target[0] -= 1;
992                 if (!start_indented) {
993                     target[1] -= 1;
994                 }
995             } else if (direction == 'UPRIGHT') {
996                 target[0] -= 1;
997                 if (start_indented) {
998                     target[1] += 1;
999                 }
1000             } else if (direction == 'DOWNLEFT') {
1001                 target[0] += 1;
1002                 if (!start_indented) {
1003                     target[1] -= 1;
1004                 }
1005             } else if (direction == 'DOWNRIGHT') {
1006                 target[0] += 1;
1007                 if (start_indented) {
1008                     target[1] += 1;
1009                 }
1010             };
1011         };
1012         if (target[0] < 0 || target[1] < 0 ||
1013             target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
1014             return null;
1015         };
1016         return target;
1017     },
1018     teleport: function() {
1019         let player = this.get_thing(game.player_id);
1020         if (player.position in this.portals) {
1021             server.reconnect_to(this.portals[player.position]);
1022         } else {
1023             terminal.blink_screen();
1024             tui.log_msg('? not standing on portal')
1025         }
1026     }
1027 }
1028
1029 game.init();
1030 tui.init();
1031 tui.full_refresh();
1032 server.init(websocket_location);
1033
1034 let explorer = {
1035     position: [0,0],
1036     info_db: {},
1037     info_hints: [],
1038     move: function(direction) {
1039         let target = game.move(this.position, direction);
1040         if (target) {
1041             this.position = target
1042             if (tui.mode.shows_info) {
1043                 this.query_info();
1044             } else if (tui.mode.name == 'control_tile_draw') {
1045                 this.send_tile_control_command();
1046             }
1047         } else {
1048             terminal.blink_screen();
1049         };
1050     },
1051     update_info_db: function(yx, str) {
1052         this.info_db[yx] = str;
1053         if (tui.mode.name == 'study') {
1054             tui.full_refresh();
1055         }
1056     },
1057     empty_info_db: function() {
1058         this.info_db = {};
1059         this.info_hints = [];
1060         if (tui.mode.name == 'study') {
1061             tui.full_refresh();
1062         }
1063     },
1064     query_info: function() {
1065         server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
1066     },
1067     get_info: function() {
1068         let position_i = this.position[0] * game.map_size[1] + this.position[1];
1069         if (game.fov[position_i] != '.') {
1070             return 'outside field of view';
1071         };
1072         let info = "";
1073         let terrain_char = game.map[position_i]
1074         let terrain_desc = '?'
1075         if (game.terrains[terrain_char]) {
1076             terrain_desc = game.terrains[terrain_char];
1077         };
1078         info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
1079         let protection = game.map_control[position_i];
1080         if (protection == '.') {
1081             protection = 'unprotected';
1082         };
1083         info += 'PROTECTION: ' + protection + '\n';
1084         for (let t_id in game.things) {
1085              let t = game.things[t_id];
1086              if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
1087                  let symbol = game.thing_types[t.type_];
1088                  info += "THING: " + t.type_ + " / " + symbol;
1089                  if (t.player_char) {
1090                      info += t.player_char;
1091                  };
1092                  if (t.name_) {
1093                      info += " (" + t.name_ + ")";
1094                  }
1095                  info += "\n";
1096              }
1097         }
1098         if (this.position in game.portals) {
1099             info += "PORTAL: " + game.portals[this.position] + "\n";
1100         }
1101         if (this.position in this.info_db) {
1102             info += "ANNOTATIONS: " + this.info_db[this.position];
1103         } else {
1104             info += 'waiting …';
1105         }
1106         return info;
1107     },
1108     annotate: function(msg) {
1109         if (msg.length == 0) {
1110             msg = " ";  // triggers annotation deletion
1111         }
1112         server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
1113     },
1114     set_portal: function(msg) {
1115         if (msg.length == 0) {
1116             msg = " ";  // triggers portal deletion
1117         }
1118         server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
1119     },
1120     send_tile_control_command: function() {
1121         server.send(["SET_TILE_CONTROL", unparser.to_yx(this.position), tui.tile_control_char]);
1122     }
1123 }
1124
1125 tui.inputEl.addEventListener('input', (event) => {
1126     if (tui.mode.has_input_prompt) {
1127         let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
1128         if (tui.inputEl.value.length > max_length) {
1129             tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
1130         };
1131         tui.recalc_input_lines();
1132     } else if (tui.mode.name == 'edit' && tui.inputEl.value.length > 0) {
1133         server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
1134         tui.switch_mode('play');
1135     } else if (tui.mode.name == 'control_pw_type' && tui.inputEl.value.length > 0) {
1136         tui.tile_control_char = tui.inputEl.value[0];
1137         tui.switch_mode('control_pw_pw');
1138     } else if (tui.mode.name == 'control_tile_type' && tui.inputEl.value.length > 0) {
1139         tui.tile_control_char = tui.inputEl.value[0];
1140         tui.switch_mode('control_tile_draw');
1141     }
1142     tui.full_refresh();
1143 }, false);
1144 tui.inputEl.addEventListener('keydown', (event) => {
1145     tui.show_help = false;
1146     if (event.key == 'Enter') {
1147         event.preventDefault();
1148     }
1149     if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
1150         tui.show_help = true;
1151         tui.empty_input();
1152         tui.restore_input_values();
1153     } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help
1154                && !tui.mode.is_single_char_entry) {
1155         tui.show_help = true;
1156     } else if (tui.mode.name == 'login' && event.key == 'Enter') {
1157         tui.login_name = tui.inputEl.value;
1158         server.send(['LOGIN', tui.inputEl.value]);
1159         tui.empty_input();
1160     } else if (tui.mode.name == 'control_pw_pw' && event.key == 'Enter') {
1161         if (tui.inputEl.value.length == 0) {
1162             tui.log_msg('@ aborted');
1163         } else {
1164             server.send(['SET_MAP_CONTROL_PASSWORD',
1165                         tui.tile_control_char, tui.inputEl.value]);
1166         }
1167         tui.switch_mode('play');
1168     } else if (tui.mode.name == 'portal' && event.key == 'Enter') {
1169         explorer.set_portal(tui.inputEl.value);
1170         tui.switch_mode('play');
1171     } else if (tui.mode.name == 'annotate' && event.key == 'Enter') {
1172         explorer.annotate(tui.inputEl.value);
1173         tui.switch_mode('play');
1174     } else if (tui.mode.name == 'password' && event.key == 'Enter') {
1175         if (tui.inputEl.value.length == 0) {
1176             tui.inputEl.value = " ";
1177         }
1178         tui.password = tui.inputEl.value
1179         tui.switch_mode('play');
1180     } else if (tui.mode.name == 'admin' && event.key == 'Enter') {
1181         server.send(['BECOME_ADMIN', tui.inputEl.value]);
1182         tui.switch_mode('play');
1183     } else if (tui.mode.name == 'chat' && event.key == 'Enter') {
1184         let tokens = parser.tokenize(tui.inputEl.value);
1185         if (tokens.length > 0 && tokens[0].length > 0) {
1186             if (tui.inputEl.value[0][0] == '/') {
1187                 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
1188                     tui.switch_mode('play');
1189                 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
1190                     tui.switch_mode('study');
1191                 } else if (tokens[0].slice(1) == 'nick') {
1192                     if (tokens.length > 1) {
1193                         server.send(['NICK', tokens[1]]);
1194                     } else {
1195                         tui.log_msg('? need new name');
1196                     }
1197                 } else {
1198                     tui.log_msg('? unknown command');
1199                 }
1200             } else {
1201                     server.send(['ALL', tui.inputEl.value]);
1202             }
1203         } else if (tui.inputEl.valuelength > 0) {
1204                 server.send(['ALL', tui.inputEl.value]);
1205         }
1206         tui.empty_input();
1207     } else if (tui.mode.name == 'play') {
1208           if (tui.mode.mode_switch_on_key(event)) {
1209               null;
1210           } else if (event.key === tui.keys.flatten
1211                      && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
1212               server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
1213           } else if (event.key === tui.keys.take_thing
1214                      && game.tasks.includes('PICK_UP')) {
1215               server.send(["TASK:PICK_UP"]);
1216           } else if (event.key === tui.keys.drop_thing
1217                      && game.tasks.includes('DROP')) {
1218               server.send(["TASK:DROP"]);
1219           } else if (event.key in tui.movement_keys
1220                      && game.tasks.includes('MOVE')) {
1221               server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
1222           } else if (event.key === tui.keys.teleport) {
1223               game.teleport();
1224           } else if (event.key === tui.keys.switch_to_portal) {
1225               event.preventDefault();
1226               tui.switch_mode('portal');
1227           } else if (event.key === tui.keys.switch_to_annotate) {
1228               event.preventDefault();
1229               tui.switch_mode('annotate');
1230           };
1231     } else if (tui.mode.name == 'study') {
1232         if (tui.mode.mode_switch_on_key(event)) {
1233               null;
1234         } else if (event.key in tui.movement_keys) {
1235             explorer.move(tui.movement_keys[event.key]);
1236         } else if (event.key == tui.keys.toggle_map_mode) {
1237             if (tui.map_mode == 'terrain') {
1238                 tui.map_mode = 'annotations';
1239             } else if (tui.map_mode == 'annotations') {
1240                 tui.map_mode = 'control';
1241             } else {
1242                 tui.map_mode = 'terrain';
1243             }
1244         };
1245     } else if (tui.mode.name == 'control_tile_draw') {
1246         if (tui.mode.mode_switch_on_key(event)) {
1247               null;
1248         } else if (event.key in tui.movement_keys) {
1249             explorer.move(tui.movement_keys[event.key]);
1250         };
1251     }
1252     tui.full_refresh();
1253 }, false);
1254
1255 rows_selector.addEventListener('input', function() {
1256     if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
1257         return;
1258     }
1259     window.localStorage.setItem(rows_selector.id, rows_selector.value);
1260     terminal.initialize();
1261     tui.full_refresh();
1262 }, false);
1263 cols_selector.addEventListener('input', function() {
1264     if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
1265         return;
1266     }
1267     window.localStorage.setItem(cols_selector.id, cols_selector.value);
1268     terminal.initialize();
1269     tui.window_width = terminal.cols / 2,
1270     tui.full_refresh();
1271 }, false);
1272 for (let key_selector of key_selectors) {
1273     key_selector.addEventListener('input', function() {
1274         window.localStorage.setItem(key_selector.id, key_selector.value);
1275         tui.init_keys();
1276     }, false);
1277 }
1278 window.setInterval(function() {
1279     if (server.connected) {
1280         server.send(['PING']);
1281     } else {
1282         server.reconnect_to(server.url);
1283         tui.log_msg('@ attempting reconnect …')
1284     }
1285 }, 5000);
1286 document.getElementById("terminal").onclick = function() {
1287     tui.inputEl.focus();
1288 };
1289 document.getElementById("help").onclick = function() {
1290     tui.show_help = true;
1291     tui.full_refresh();
1292 };
1293 document.getElementById("switch_to_play").onclick = function() {
1294     tui.switch_mode('play');
1295     tui.full_refresh();
1296 };
1297 document.getElementById("switch_to_study").onclick = function() {
1298     tui.switch_mode('study');
1299     tui.full_refresh();
1300 };
1301 document.getElementById("switch_to_chat").onclick = function() {
1302     tui.switch_mode('chat');
1303     tui.full_refresh();
1304 };
1305 document.getElementById("switch_to_password").onclick = function() {
1306     tui.switch_mode('password');
1307     tui.full_refresh();
1308 };
1309 document.getElementById("switch_to_edit").onclick = function() {
1310     tui.switch_mode('edit');
1311     tui.full_refresh();
1312 };
1313 document.getElementById("switch_to_annotate").onclick = function() {
1314     tui.switch_mode('annotate');
1315     tui.full_refresh();
1316 };
1317 document.getElementById("switch_to_portal").onclick = function() {
1318     tui.switch_mode('portal');
1319     tui.full_refresh();
1320 };
1321 document.getElementById("switch_to_admin").onclick = function() {
1322     tui.switch_mode('admin');
1323     tui.full_refresh();
1324 };
1325 document.getElementById("switch_to_control_pw_type").onclick = function() {
1326     tui.switch_mode('control_pw_type');
1327     tui.full_refresh();
1328 };
1329 document.getElementById("switch_to_control_tile_type").onclick = function() {
1330     tui.switch_mode('control_tile_type');
1331     tui.full_refresh();
1332 };
1333 document.getElementById("toggle_map_mode").onclick = function() {
1334     if (tui.map_mode == 'terrain') {
1335         tui.map_mode = 'annotations';
1336     } else if (tui.map_mode == 'annotations') {
1337         tui.map_mode = 'control';
1338     } else {
1339         tui.map_mode = 'terrain';
1340     }
1341     tui.full_refresh();
1342 };
1343 document.getElementById("take_thing").onclick = function() {
1344         server.send(['TASK:PICK_UP']);
1345 };
1346 document.getElementById("drop_thing").onclick = function() {
1347         server.send(['TASK:DROP']);
1348 };
1349 document.getElementById("flatten").onclick = function() {
1350     server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1351 };
1352 document.getElementById("teleport").onclick = function() {
1353     game.teleport();
1354 };
1355 document.getElementById("move_upleft").onclick = function() {
1356     if (tui.mode.name == 'play') {
1357         server.send(['TASK:MOVE', 'UPLEFT']);
1358     } else {
1359         explorer.move('UPLEFT');
1360     };
1361 };
1362 document.getElementById("move_left").onclick = function() {
1363     if (tui.mode.name == 'play') {
1364         server.send(['TASK:MOVE', 'LEFT']);
1365     } else {
1366         explorer.move('LEFT');
1367     };
1368 };
1369 document.getElementById("move_downleft").onclick = function() {
1370     if (tui.mode.name == 'play') {
1371         server.send(['TASK:MOVE', 'DOWNLEFT']);
1372     } else {
1373         explorer.move('DOWNLEFT');
1374     };
1375 };
1376 document.getElementById("move_down").onclick = function() {
1377     if (tui.mode.name == 'play') {
1378         server.send(['TASK:MOVE', 'DOWN']);
1379     } else {
1380         explorer.move('DOWN');
1381     };
1382 };
1383 document.getElementById("move_up").onclick = function() {
1384     if (tui.mode.name == 'play') {
1385         server.send(['TASK:MOVE', 'UP']);
1386     } else {
1387         explorer.move('UP');
1388     };
1389 };
1390 document.getElementById("move_upright").onclick = function() {
1391     if (tui.mode.name == 'play') {
1392         server.send(['TASK:MOVE', 'UPRIGHT']);
1393     } else {
1394         explorer.move('UPRIGHT');
1395     };
1396 };
1397 document.getElementById("move_right").onclick = function() {
1398     if (tui.mode.name == 'play') {
1399         server.send(['TASK:MOVE', 'RIGHT']);
1400     } else {
1401         explorer.move('RIGHT');
1402     };
1403 };
1404 document.getElementById("move_downright").onclick = function() {
1405     if (tui.mode.name == 'play') {
1406         server.send(['TASK:MOVE', 'DOWNRIGHT']);
1407     } else {
1408         explorer.move('DOWNRIGHT');
1409     };
1410 };
1411 </script>
1412 </body></html>