7 terminal rows: <input id="n_rows" type="number" step=4 min=8 value=24 />
8 terminal columns: <input id="n_cols" type="number" step=4 min=20 value=80 />
10 <pre id="terminal" style="display: inline-block;"></pre>
11 <textarea id="input" style="opacity: 0; width: 0px;"></textarea>
13 keys (see <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a> for non-obvious available values):<br />
14 move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)<br />
15 move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)<br />
16 move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)<br />
17 move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)<br />
18 move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" /><br />
19 move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" /><br />
20 move right (hex grid): <input id="key_hex_move_right" type="text" value="d" /><br />
21 move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" /><br />
22 move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" /><br />
23 move left (hex grid): <input id="key_hex_move_left" type="text" value="a" /><br />
24 help: <input id="key_help" type="text" value="h" /><br />
25 flatten surroundings: <input id="key_flatten" type="text" value="F" /><br />
26 take thing under player: <input id="key_take_thing" type="text" value="z" /><br />
27 drop carried thing: <input id="key_drop_thing" type="text" value="u" /><br />
28 switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" /><br />
29 switch to play mode: <input id="key_switch_to_play" type="text" value="p" /><br />
30 switch to study mode: <input id="key_switch_to_study" type="text" value="?" /><br />
31 edit tile (from play mode): <input id="key_switch_to_edit" type="text" value="m" /><br />
32 enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" /><br />
33 annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" /><br />
34 annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" /><br />
35 toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" /><br />
39 //let websocket_location = "wss://plomlompom.com/rogue_chat/";
40 let websocket_location = "ws://localhost:8000/";
42 let rows_selector = document.getElementById("n_rows");
43 let cols_selector = document.getElementById("n_cols");
44 let key_selectors = document.querySelectorAll('[id^="key_"]');
46 function restore_selector_value(selector) {
47 let stored_selection = window.localStorage.getItem(selector.id);
48 if (stored_selection) {
49 selector.value = stored_selection;
52 restore_selector_value(rows_selector);
53 restore_selector_value(cols_selector);
54 for (let key_selector of key_selectors) {
55 restore_selector_value(key_selector);
61 initialize: function() {
62 this.rows = rows_selector.value;
63 this.cols = cols_selector.value;
64 this.pre_el = document.getElementById("terminal");
65 this.pre_el.style.color = this.foreground;
66 this.pre_el.style.backgroundColor = this.background;
69 for (let y = 0, x = 0; y <= this.rows; x++) {
73 this.content.push(line);
82 blink_screen: function() {
83 this.pre_el.style.color = this.background;
84 this.pre_el.style.backgroundColor = this.foreground;
86 this.pre_el.style.color = this.foreground;
87 this.pre_el.style.backgroundColor = this.background;
92 for (let y = 0; y < this.rows; y++) {
93 let line = this.content[y].join('');
94 pre_string += line + '\n';
96 this.pre_el.textContent = pre_string;
98 write: function(start_y, start_x, msg) {
99 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
100 this.content[start_y][x] = msg[i];
103 drawBox: function(start_y, start_x, height, width) {
104 let end_y = start_y + height;
105 let end_x = start_x + width;
106 for (let y = start_y, x = start_x; y < this.rows; x++) {
114 this.content[y][x] = ' ';
118 terminal.initialize();
121 tokenize: function(str) {
127 for (let i = 0; i < str.length; i++) {
133 } else if (c == '\\') {
135 } else if (c == '"') {
140 } else if (c == '"') {
142 } else if (c === ' ') {
143 if (token.length > 0) {
152 if (token.length > 0) {
155 let token_starts = [];
156 for (let i = 0; i < token_ends.length; i++) {
157 token_starts.push(token_ends[i] - tokens[i].length);
159 return [tokens, token_starts];
161 parse_yx: function(position_string) {
162 let coordinate_strings = position_string.split(',')
163 let position = [0, 0];
164 position[0] = parseInt(coordinate_strings[0].slice(2));
165 position[1] = parseInt(coordinate_strings[1].slice(2));
177 init: function(url) {
179 this.websocket = new WebSocket(this.url);
180 this.websocket.onopen = function(event) {
181 server.connected = true;
182 game.thing_types = {};
183 server.send(['TASKS']);
184 server.send(['THING_TYPES']);
185 tui.log_msg("@ server connected! :)");
186 tui.switch_mode(mode_login);
188 this.websocket.onclose = function(event) {
189 server.connected = false;
190 tui.switch_mode(mode_waiting_for_server);
191 tui.log_msg("@ server disconnected :(");
193 this.websocket.onmessage = this.handle_event;
195 reconnect_to: function(url) {
196 this.websocket.close();
199 send: function(tokens) {
200 this.websocket.send(unparser.untokenize(tokens));
202 handle_event: function(event) {
203 let tokens = parser.tokenize(event.data)[0];
204 if (tokens[0] === 'TURN') {
205 game.turn_complete = false;
208 game.turn = parseInt(tokens[1]);
209 } else if (tokens[0] === 'THING') {
210 let t = game.get_thing(tokens[3], true);
211 t.position = parser.parse_yx(tokens[1]);
213 } else if (tokens[0] === 'THING_NAME') {
214 let t = game.get_thing(tokens[1], false);
218 } else if (tokens[0] === 'TASKS') {
219 game.tasks = tokens[1].split(',')
220 } else if (tokens[0] === 'THING_TYPE') {
221 game.thing_types[tokens[1]] = tokens[2]
222 } else if (tokens[0] === 'MAP') {
223 game.map_geometry = tokens[1];
225 game.map_size = parser.parse_yx(tokens[2]);
227 } else if (tokens[0] === 'FOV') {
229 } else if (tokens[0] === 'MAP_CONTROL') {
230 game.map_control = tokens[1]
231 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
232 game.turn_complete = true;
233 explorer.empty_info_db();
234 if (tui.mode == mode_post_login_wait) {
235 tui.switch_mode(mode_play);
236 } else if (tui.mode == mode_study) {
237 explorer.query_info();
239 let t = game.get_thing(game.player_id);
240 if (t.position in game.portals) {
241 tui.teleport_target = game.portals[t.position];
242 tui.switch_mode(mode_teleport);
246 } else if (tokens[0] === 'CHAT') {
247 tui.log_msg('# ' + tokens[1], 1);
248 } else if (tokens[0] === 'PLAYER_ID') {
249 game.player_id = parseInt(tokens[1]);
250 } else if (tokens[0] === 'LOGIN_OK') {
251 this.send(['GET_GAMESTATE']);
252 tui.switch_mode(mode_post_login_wait);
253 } else if (tokens[0] === 'PORTAL') {
254 let position = parser.parse_yx(tokens[1]);
255 game.portals[position] = tokens[2];
256 } else if (tokens[0] === 'ANNOTATION') {
257 let position = parser.parse_yx(tokens[1]);
258 explorer.update_info_db(position, tokens[2]);
259 } else if (tokens[0] === 'UNHANDLED_INPUT') {
260 tui.log_msg('? unknown command');
261 } else if (tokens[0] === 'PLAY_ERROR') {
262 terminal.blink_screen();
263 } else if (tokens[0] === 'ARGUMENT_ERROR') {
264 tui.log_msg('? syntax error: ' + tokens[1]);
265 } else if (tokens[0] === 'GAME_ERROR') {
266 tui.log_msg('? game error: ' + tokens[1]);
267 } else if (tokens[0] === 'PONG') {
270 tui.log_msg('? unhandled input: ' + event.data);
276 quote: function(str) {
278 for (let i = 0; i < str.length; i++) {
280 if (['"', '\\'].includes(c)) {
286 return quoted.join('');
288 to_yx: function(yx_coordinate) {
289 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
291 untokenize: function(tokens) {
292 let quoted_tokens = [];
293 for (let token of tokens) {
294 quoted_tokens.push(this.quote(token));
296 return quoted_tokens.join(" ");
301 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
303 this.has_input_prompt = has_input_prompt;
304 this.shows_info= shows_info;
305 this.is_intro = is_intro;
306 this.help_intro = help_intro;
309 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
310 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
311 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
312 let mode_chat = new Mode('chat', 'This mode allows you to engage in chit-chat with other users. Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message. Lines that start with a "/" are used for commands like:', true, false);
313 let mode_annotate = new Mode('annotate', 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.', true, true);
314 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
315 let mode_study = new Mode('study', 'This mode allows you to study the map and its tiles in detail. Move the question mark over a tile, and the right half of the screen will show detailed information on it.', false, true);
316 let mode_edit = new Mode('edit', '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.', false, false);
317 let mode_teleport = new Mode('teleport', 'Follow the instructions to re-connect and log-in to another server, or enter anything else to abort.', true);
318 let mode_portal = new Mode('portal', '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.', true, true);
319 let mode_password = new Mode('password', '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.', true, false, false);
322 mode: mode_waiting_for_server,
326 window_width: terminal.cols / 2,
333 this.inputEl = document.getElementById("input");
334 this.inputEl.focus();
335 this.recalc_input_lines();
336 this.height_header = this.height_turn_line + this.height_mode_line;
337 this.log_msg("@ waiting for server connection ...");
340 init_keys: function() {
342 for (let key_selector of key_selectors) {
343 this.keys[key_selector.id.slice(4)] = key_selector.value;
345 this.movement_keys = {
346 [this.keys.square_move_up]: 'UP',
347 [this.keys.square_move_left]: 'LEFT',
348 [this.keys.square_move_down]: 'DOWN',
349 [this.keys.square_move_right]: 'RIGHT'
351 if (game.map_geometry == 'Hex') {
352 this.movement_keys = {
353 [this.keys.hex_move_upleft]: 'UPLEFT',
354 [this.keys.hex_move_upright]: 'UPRIGHT',
355 [this.keys.hex_move_right]: 'RIGHT',
356 [this.keys.hex_move_downright]: 'DOWNRIGHT',
357 [this.keys.hex_move_downleft]: 'DOWNLEFT',
358 [this.keys.hex_move_left]: 'LEFT'
362 switch_mode: function(mode) {
363 this.show_help = false;
364 this.map_mode = 'terrain';
365 if (mode.shows_info && game.player_id in game.things) {
366 explorer.position = game.things[game.player_id].position;
370 this.restore_input_values();
371 if (mode == mode_login) {
372 if (this.login_name) {
373 server.send(['LOGIN', this.login_name]);
375 this.log_msg("? need login name");
377 } else if (mode == mode_edit) {
378 this.show_help = true;
379 } else if (mode == mode_teleport) {
380 tui.log_msg("@ May teleport to: " + tui.teleport_target);
381 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
385 restore_input_values: function() {
386 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
387 let info = explorer.info_db[explorer.position];
388 if (info != "(none)") {
389 this.inputEl.value = info;
390 this.recalc_input_lines();
392 } else if (this.mode == mode_portal && explorer.position in game.portals) {
393 let portal = game.portals[explorer.position]
394 this.inputEl.value = portal;
395 this.recalc_input_lines();
396 } else if (this.mode == mode_password) {
397 this.inputEl.value = this.password;
398 this.recalc_input_lines();
401 empty_input: function(str) {
402 this.inputEl.value = "";
403 if (this.mode.has_input_prompt) {
404 this.recalc_input_lines();
406 this.height_input = 0;
409 recalc_input_lines: function() {
410 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
411 this.height_input = this.input_lines.length;
413 msg_into_lines_of_width: function(msg, width) {
416 for (let i = 0, x = 0; i < msg.length; i++, x++) {
417 if (x >= width || msg[i] == "\n") {
422 if (msg[i] != "\n") {
429 log_msg: function(msg) {
431 while (this.log.length > 100) {
436 draw_map: function() {
437 let map_lines_split = [];
439 let map_content = game.map;
440 if (this.map_mode == 'control') {
441 map_content = game.map_control;
443 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
444 if (j == game.map_size[1]) {
445 map_lines_split.push(line);
449 line.push(map_content[i]);
451 map_lines_split.push(line);
452 if (this.map_mode == 'terrain') {
453 for (const thing_id in game.things) {
454 let t = game.things[thing_id];
455 let symbol = game.thing_types[t.type_];
456 map_lines_split[t.position[0]][t.position[1]] = symbol;
459 if (tui.mode.shows_info) {
460 map_lines_split[explorer.position[0]][explorer.position[1]] = '?';
463 if (game.map_geometry == 'Square') {
464 for (let line_split of map_lines_split) {
465 map_lines.push(line_split.join(' '));
467 } else if (game.map_geometry == 'Hex') {
469 for (let line_split of map_lines_split) {
470 map_lines.push(' '.repeat(indent) + line_split.join(' '));
478 let window_center = [terminal.rows / 2, this.window_width / 2];
479 let player = game.things[game.player_id];
480 let center_position = [player.position[0], player.position[1]];
481 if (tui.mode.shows_info) {
482 center_position = [explorer.position[0], explorer.position[1]];
484 center_position[1] = center_position[1] * 2;
485 let offset = [center_position[0] - window_center[0],
486 center_position[1] - window_center[1]]
487 if (game.map_geometry == 'Hex' && offset[0] % 2) {
490 let term_y = Math.max(0, -offset[0]);
491 let term_x = Math.max(0, -offset[1]);
492 let map_y = Math.max(0, offset[0]);
493 let map_x = Math.max(0, offset[1]);
494 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
495 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
496 terminal.write(term_y, term_x, to_draw);
499 draw_mode_line: function() {
500 let help = 'hit [' + this.keys.help + '] for help';
501 if (this.mode.has_input_prompt) {
502 help = 'enter /help for help';
504 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
506 draw_turn_line: function(n) {
507 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
509 draw_history: function() {
510 let log_display_lines = [];
511 for (let line of this.log) {
512 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
514 for (let y = terminal.rows - 1 - this.height_input,
515 i = log_display_lines.length - 1;
516 y >= this.height_header && i >= 0;
518 terminal.write(y, this.window_width, log_display_lines[i]);
521 draw_info: function() {
522 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
523 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
524 terminal.write(y, this.window_width, lines[i]);
527 draw_input: function() {
528 if (this.mode.has_input_prompt) {
529 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
530 terminal.write(y, this.window_width, this.input_lines[i]);
534 draw_help: function() {
535 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
536 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
537 if (this.mode == mode_play) {
538 content += "Available actions:\n";
539 if (game.tasks.includes('MOVE')) {
540 content += "[" + movement_keys_desc + "] – move player\n";
542 if (game.tasks.includes('PICK_UP')) {
543 content += "[" + this.keys.take_thing + "] – take thing under player\n";
545 if (game.tasks.includes('DROP')) {
546 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
548 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
549 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
551 content += '\nOther modes available from here:\n';
552 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
553 content += '[' + this.keys.switch_to_study + '] – study mode\n';
554 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
555 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
556 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
557 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
558 } else if (this.mode == mode_study) {
559 content += "Available actions:\n";
560 content += '[' + movement_keys_desc + '] – move question mark\n';
561 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
562 content += '\nOther modes available from here:\n';
563 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
564 content += '[' + this.keys.switch_to_play + '] – play mode\n';
565 } else if (this.mode == mode_chat) {
566 content += '/nick NAME – re-name yourself to NAME\n';
567 //content += '/msg USER TEXT – send TEXT to USER\n';
568 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
569 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
572 if (!this.mode.has_input_prompt) {
573 start_x = this.window_width
575 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
576 let lines = this.msg_into_lines_of_width(content, this.window_width);
577 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
578 terminal.write(y, start_x, lines[i]);
581 full_refresh: function() {
582 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
583 if (this.mode.is_intro) {
587 if (game.turn_complete) {
589 this.draw_turn_line();
591 this.draw_mode_line();
592 if (this.mode.shows_info) {
599 if (this.show_help) {
611 this.map_control = "";
612 this.map_size = [0,0];
617 get_thing: function(id_, create_if_not_found=false) {
618 if (id_ in game.things) {
619 return game.things[id_];
620 } else if (create_if_not_found) {
621 let t = new Thing([0,0]);
622 game.things[id_] = t;
626 move: function(start_position, direction) {
627 let target = [start_position[0], start_position[1]];
628 if (direction == 'LEFT') {
630 } else if (direction == 'RIGHT') {
632 } else if (game.map_geometry == 'Square') {
633 if (direction == 'UP') {
635 } else if (direction == 'DOWN') {
638 } else if (game.map_geometry == 'Hex') {
639 let start_indented = start_position[0] % 2;
640 if (direction == 'UPLEFT') {
642 if (!start_indented) {
645 } else if (direction == 'UPRIGHT') {
647 if (start_indented) {
650 } else if (direction == 'DOWNLEFT') {
652 if (!start_indented) {
655 } else if (direction == 'DOWNRIGHT') {
657 if (start_indented) {
662 if (target[0] < 0 || target[1] < 0 ||
663 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
673 server.init(websocket_location);
678 move: function(direction) {
679 let target = game.move(this.position, direction);
681 this.position = target
684 terminal.blink_screen();
687 update_info_db: function(yx, str) {
688 this.info_db[yx] = str;
689 if (tui.mode == mode_study) {
693 empty_info_db: function() {
695 if (tui.mode == mode_study) {
699 query_info: function() {
700 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
702 get_info: function() {
703 let position_i = this.position[0] * game.map_size[1] + this.position[1];
704 if (game.fov[position_i] != '.') {
705 return 'outside field of view';
708 info += "TERRAIN: " + game.map[position_i] + "\n";
709 for (let t_id in game.things) {
710 let t = game.things[t_id];
711 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
712 info += "THING: " + t.type_;
714 info += " (name: " + t.name_ + ")";
719 if (this.position in game.portals) {
720 info += "PORTAL: " + game.portals[this.position] + "\n";
722 if (this.position in this.info_db) {
723 info += "ANNOTATIONS: " + this.info_db[this.position];
729 annotate: function(msg) {
730 if (msg.length == 0) {
731 msg = " "; // triggers annotation deletion
733 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
735 set_portal: function(msg) {
736 if (msg.length == 0) {
737 msg = " "; // triggers portal deletion
739 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
743 tui.inputEl.addEventListener('input', (event) => {
744 if (tui.mode.has_input_prompt) {
745 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
746 if (tui.inputEl.value.length > max_length) {
747 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
749 tui.recalc_input_lines();
750 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
751 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
752 tui.switch_mode(mode_play);
756 tui.inputEl.addEventListener('keydown', (event) => {
757 tui.show_help = false;
758 if (event.key == 'Enter') {
759 event.preventDefault();
761 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
762 tui.show_help = true;
764 tui.restore_input_values();
765 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
766 tui.show_help = true;
767 } else if (tui.mode == mode_login && event.key == 'Enter') {
768 tui.login_name = tui.inputEl.value;
769 server.send(['LOGIN', tui.inputEl.value]);
771 } else if (tui.mode == mode_portal && event.key == 'Enter') {
772 explorer.set_portal(tui.inputEl.value);
773 tui.switch_mode(mode_play);
774 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
775 explorer.annotate(tui.inputEl.value);
776 tui.switch_mode(mode_play);
777 } else if (tui.mode == mode_password && event.key == 'Enter') {
778 if (tui.inputEl.value.length == 0) {
779 tui.inputEl.value = " ";
781 tui.password = tui.inputEl.value
782 tui.switch_mode(mode_play);
783 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
784 if (tui.inputEl.value == 'YES!') {
785 server.reconnect_to(tui.teleport_target);
787 tui.log_msg('@ teleport aborted');
788 tui.switch_mode(mode_play);
790 } else if (tui.mode == mode_chat && event.key == 'Enter') {
791 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
792 if (tokens.length > 0 && tokens[0].length > 0) {
793 if (tui.inputEl.value[0][0] == '/') {
794 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
795 tui.switch_mode(mode_play);
796 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
797 tui.switch_mode(mode_study);
798 } else if (tokens[0].slice(1) == 'nick') {
799 if (tokens.length > 1) {
800 server.send(['NICK', tokens[1]]);
802 tui.log_msg('? need new name');
804 //} else if (tokens[0].slice(1) == 'msg') {
805 // if (tokens.length > 2) {
806 // let msg = tui.inputEl.value.slice(token_starts[2]);
807 // server.send(['QUERY', tokens[1], msg]);
809 // tui.log_msg('? need message target and message');
812 tui.log_msg('? unknown command');
815 server.send(['ALL', tui.inputEl.value]);
817 } else if (tui.inputEl.valuelength > 0) {
818 server.send(['ALL', tui.inputEl.value]);
821 } else if (tui.mode == mode_play) {
822 if (event.key === tui.keys.switch_to_chat) {
823 event.preventDefault();
824 tui.switch_mode(mode_chat);
825 } else if (event.key === tui.keys.switch_to_edit
826 && game.tasks.includes('WRITE')) {
827 event.preventDefault();
828 tui.switch_mode(mode_edit);
829 } else if (event.key === tui.keys.switch_to_study) {
830 tui.switch_mode(mode_study);
831 } else if (event.key === tui.keys.switch_to_password) {
832 event.preventDefault();
833 tui.switch_mode(mode_password);
834 } else if (event.key === tui.keys.flatten
835 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
836 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
837 } else if (event.key === tui.keys.take_thing
838 && game.tasks.includes('PICK_UP')) {
839 server.send(["TASK:PICK_UP"]);
840 } else if (event.key === tui.keys.drop_thing
841 && game.tasks.includes('DROP')) {
842 server.send(["TASK:DROP"]);
843 } else if (event.key in tui.movement_keys
844 && game.tasks.includes('MOVE')) {
845 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
846 } else if (event.key === tui.keys.switch_to_portal) {
847 event.preventDefault();
848 tui.switch_mode(mode_portal);
849 } else if (event.key === tui.keys.switch_to_annotate) {
850 event.preventDefault();
851 tui.switch_mode(mode_annotate);
853 } else if (tui.mode == mode_study) {
854 if (event.key === tui.keys.switch_to_chat) {
855 event.preventDefault();
856 tui.switch_mode(mode_chat);
857 } else if (event.key == tui.keys.switch_to_play) {
858 tui.switch_mode(mode_play);
859 } else if (event.key in tui.movement_keys) {
860 explorer.move(tui.movement_keys[event.key]);
861 } else if (event.key == tui.keys.toggle_map_mode) {
862 if (tui.map_mode == 'terrain') {
863 tui.map_mode = 'control';
865 tui.map_mode = 'terrain';
872 rows_selector.addEventListener('input', function() {
873 if (rows_selector.value % 4 != 0) {
876 window.localStorage.setItem(rows_selector.id, rows_selector.value);
877 terminal.initialize();
880 cols_selector.addEventListener('input', function() {
881 if (cols_selector.value % 4 != 0) {
884 window.localStorage.setItem(cols_selector.id, cols_selector.value);
885 terminal.initialize();
886 tui.window_width = terminal.cols / 2,
889 for (let key_selector of key_selectors) {
890 key_selector.addEventListener('input', function() {
891 window.localStorage.setItem(key_selector.id, key_selector.value);
895 window.setInterval(function() {
896 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
897 || document.activeElement.id.startsWith('key_'))) {
901 window.setInterval(function() {
902 if (server.connected) {
903 server.send(['PING']);
905 server.reconnect_to(server.url);
906 tui.log_msg('@ attempting reconnect …')