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