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/";
41 let rows_selector = document.getElementById("n_rows");
42 let cols_selector = document.getElementById("n_cols");
43 let key_selectors = document.querySelectorAll('[id^="key_"]');
45 function restore_selector_value(selector) {
46 let stored_selection = window.localStorage.getItem(selector.id);
47 if (stored_selection) {
48 selector.value = stored_selection;
51 restore_selector_value(rows_selector);
52 restore_selector_value(cols_selector);
53 for (let key_selector of key_selectors) {
54 restore_selector_value(key_selector);
60 initialize: function() {
61 this.rows = rows_selector.value;
62 this.cols = cols_selector.value;
63 this.pre_el = document.getElementById("terminal");
64 this.pre_el.style.color = this.foreground;
65 this.pre_el.style.backgroundColor = this.background;
68 for (let y = 0, x = 0; y <= this.rows; x++) {
72 this.content.push(line);
81 blink_screen: function() {
82 this.pre_el.style.color = this.background;
83 this.pre_el.style.backgroundColor = this.foreground;
85 this.pre_el.style.color = this.foreground;
86 this.pre_el.style.backgroundColor = this.background;
91 for (let y = 0; y < this.rows; y++) {
92 let line = this.content[y].join('');
93 pre_string += line + '\n';
95 this.pre_el.textContent = pre_string;
97 write: function(start_y, start_x, msg) {
98 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
99 this.content[start_y][x] = msg[i];
102 drawBox: function(start_y, start_x, height, width) {
103 let end_y = start_y + height;
104 let end_x = start_x + width;
105 for (let y = start_y, x = start_x; y < this.rows; x++) {
113 this.content[y][x] = ' ';
117 terminal.initialize();
120 tokenize: function(str) {
126 for (let i = 0; i < str.length; i++) {
132 } else if (c == '\\') {
134 } else if (c == '"') {
139 } else if (c == '"') {
141 } else if (c === ' ') {
142 if (token.length > 0) {
151 if (token.length > 0) {
154 let token_starts = [];
155 for (let i = 0; i < token_ends.length; i++) {
156 token_starts.push(token_ends[i] - tokens[i].length);
158 return [tokens, token_starts];
160 parse_yx: function(position_string) {
161 let coordinate_strings = position_string.split(',')
162 let position = [0, 0];
163 position[0] = parseInt(coordinate_strings[0].slice(2));
164 position[1] = parseInt(coordinate_strings[1].slice(2));
176 init: function(url) {
178 this.websocket = new WebSocket(this.url);
179 this.websocket.onopen = function(event) {
180 server.connected = true;
181 game.thing_types = {};
183 server.send(['TASKS']);
184 server.send(['TERRAINS']);
185 server.send(['THING_TYPES']);
186 tui.log_msg("@ server connected! :)");
187 tui.switch_mode(mode_login);
189 this.websocket.onclose = function(event) {
190 server.connected = false;
191 tui.switch_mode(mode_waiting_for_server);
192 tui.log_msg("@ server disconnected :(");
194 this.websocket.onmessage = this.handle_event;
196 reconnect_to: function(url) {
197 this.websocket.close();
200 send: function(tokens) {
201 this.websocket.send(unparser.untokenize(tokens));
203 handle_event: function(event) {
204 let tokens = parser.tokenize(event.data)[0];
205 if (tokens[0] === 'TURN') {
206 game.turn_complete = false;
209 game.turn = parseInt(tokens[1]);
210 } else if (tokens[0] === 'THING') {
211 let t = game.get_thing(tokens[3], true);
212 t.position = parser.parse_yx(tokens[1]);
214 } else if (tokens[0] === 'THING_NAME') {
215 let t = game.get_thing(tokens[1], false);
219 } else if (tokens[0] === 'THING_CHAR') {
220 let t = game.get_thing(tokens[1], false);
222 t.player_char = tokens[2];
224 } else if (tokens[0] === 'TASKS') {
225 game.tasks = tokens[1].split(',')
226 } else if (tokens[0] === 'THING_TYPE') {
227 game.thing_types[tokens[1]] = tokens[2]
228 } else if (tokens[0] === 'TERRAIN') {
229 game.terrains[tokens[1]] = tokens[2]
230 } else if (tokens[0] === 'MAP') {
231 game.map_geometry = tokens[1];
233 game.map_size = parser.parse_yx(tokens[2]);
235 } else if (tokens[0] === 'FOV') {
237 } else if (tokens[0] === 'MAP_CONTROL') {
238 game.map_control = tokens[1]
239 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
240 game.turn_complete = true;
241 explorer.empty_info_db();
242 if (tui.mode == mode_post_login_wait) {
243 tui.switch_mode(mode_play);
244 } else if (tui.mode == mode_study) {
245 explorer.query_info();
247 let t = game.get_thing(game.player_id);
248 if (t.position in game.portals) {
249 tui.teleport_target = game.portals[t.position];
250 tui.switch_mode(mode_teleport);
254 } else if (tokens[0] === 'CHAT') {
255 tui.log_msg('# ' + tokens[1], 1);
256 } else if (tokens[0] === 'PLAYER_ID') {
257 game.player_id = parseInt(tokens[1]);
258 } else if (tokens[0] === 'LOGIN_OK') {
259 this.send(['GET_GAMESTATE']);
260 tui.switch_mode(mode_post_login_wait);
261 } else if (tokens[0] === 'PORTAL') {
262 let position = parser.parse_yx(tokens[1]);
263 game.portals[position] = tokens[2];
264 } else if (tokens[0] === 'ANNOTATION') {
265 let position = parser.parse_yx(tokens[1]);
266 explorer.update_info_db(position, tokens[2]);
267 } else if (tokens[0] === 'UNHANDLED_INPUT') {
268 tui.log_msg('? unknown command');
269 } else if (tokens[0] === 'PLAY_ERROR') {
270 terminal.blink_screen();
271 } else if (tokens[0] === 'ARGUMENT_ERROR') {
272 tui.log_msg('? syntax error: ' + tokens[1]);
273 } else if (tokens[0] === 'GAME_ERROR') {
274 tui.log_msg('? game error: ' + tokens[1]);
275 } else if (tokens[0] === 'PONG') {
278 tui.log_msg('? unhandled input: ' + event.data);
284 quote: function(str) {
286 for (let i = 0; i < str.length; i++) {
288 if (['"', '\\'].includes(c)) {
294 return quoted.join('');
296 to_yx: function(yx_coordinate) {
297 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
299 untokenize: function(tokens) {
300 let quoted_tokens = [];
301 for (let token of tokens) {
302 quoted_tokens.push(this.quote(token));
304 return quoted_tokens.join(" ");
309 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
311 this.has_input_prompt = has_input_prompt;
312 this.shows_info= shows_info;
313 this.is_intro = is_intro;
314 this.help_intro = help_intro;
317 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
318 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
319 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
320 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);
321 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);
322 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
323 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);
324 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);
325 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);
326 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);
327 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);
330 mode: mode_waiting_for_server,
334 window_width: terminal.cols / 2,
341 this.inputEl = document.getElementById("input");
342 this.inputEl.focus();
343 this.recalc_input_lines();
344 this.height_header = this.height_turn_line + this.height_mode_line;
345 this.log_msg("@ waiting for server connection ...");
348 init_keys: function() {
350 for (let key_selector of key_selectors) {
351 this.keys[key_selector.id.slice(4)] = key_selector.value;
353 this.movement_keys = {
354 [this.keys.square_move_up]: 'UP',
355 [this.keys.square_move_left]: 'LEFT',
356 [this.keys.square_move_down]: 'DOWN',
357 [this.keys.square_move_right]: 'RIGHT'
359 if (game.map_geometry == 'Hex') {
360 this.movement_keys = {
361 [this.keys.hex_move_upleft]: 'UPLEFT',
362 [this.keys.hex_move_upright]: 'UPRIGHT',
363 [this.keys.hex_move_right]: 'RIGHT',
364 [this.keys.hex_move_downright]: 'DOWNRIGHT',
365 [this.keys.hex_move_downleft]: 'DOWNLEFT',
366 [this.keys.hex_move_left]: 'LEFT'
370 switch_mode: function(mode) {
371 this.show_help = false;
372 this.map_mode = 'terrain';
373 if (mode.shows_info && game.player_id in game.things) {
374 explorer.position = game.things[game.player_id].position;
378 this.restore_input_values();
379 if (mode == mode_login) {
380 if (this.login_name) {
381 server.send(['LOGIN', this.login_name]);
383 this.log_msg("? need login name");
385 } else if (mode == mode_edit) {
386 this.show_help = true;
387 } else if (mode == mode_teleport) {
388 tui.log_msg("@ May teleport to: " + tui.teleport_target);
389 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
393 restore_input_values: function() {
394 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
395 let info = explorer.info_db[explorer.position];
396 if (info != "(none)") {
397 this.inputEl.value = info;
398 this.recalc_input_lines();
400 } else if (this.mode == mode_portal && explorer.position in game.portals) {
401 let portal = game.portals[explorer.position]
402 this.inputEl.value = portal;
403 this.recalc_input_lines();
404 } else if (this.mode == mode_password) {
405 this.inputEl.value = this.password;
406 this.recalc_input_lines();
409 empty_input: function(str) {
410 this.inputEl.value = "";
411 if (this.mode.has_input_prompt) {
412 this.recalc_input_lines();
414 this.height_input = 0;
417 recalc_input_lines: function() {
418 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
419 this.height_input = this.input_lines.length;
421 msg_into_lines_of_width: function(msg, width) {
424 for (let i = 0, x = 0; i < msg.length; i++, x++) {
425 if (x >= width || msg[i] == "\n") {
430 if (msg[i] != "\n") {
437 log_msg: function(msg) {
439 while (this.log.length > 100) {
444 draw_map: function() {
445 let map_lines_split = [];
447 let map_content = game.map;
448 if (this.map_mode == 'control') {
449 map_content = game.map_control;
451 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
452 if (j == game.map_size[1]) {
453 map_lines_split.push(line);
457 line.push(map_content[i] + ' ');
459 map_lines_split.push(line);
460 if (this.map_mode == 'terrain') {
461 let used_positions = [];
462 for (const thing_id in game.things) {
463 let t = game.things[thing_id];
464 let symbol = game.thing_types[t.type_];
467 meta_char = t.player_char;
469 if (used_positions.includes(t.position.toString())) {
472 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
473 used_positions.push(t.position.toString());
476 if (tui.mode.shows_info) {
477 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
480 if (game.map_geometry == 'Square') {
481 for (let line_split of map_lines_split) {
482 map_lines.push(line_split.join(''));
484 } else if (game.map_geometry == 'Hex') {
486 for (let line_split of map_lines_split) {
487 map_lines.push(' '.repeat(indent) + line_split.join(''));
495 let window_center = [terminal.rows / 2, this.window_width / 2];
496 let player = game.things[game.player_id];
497 let center_position = [player.position[0], player.position[1]];
498 if (tui.mode.shows_info) {
499 center_position = [explorer.position[0], explorer.position[1]];
501 center_position[1] = center_position[1] * 2;
502 let offset = [center_position[0] - window_center[0],
503 center_position[1] - window_center[1]]
504 if (game.map_geometry == 'Hex' && offset[0] % 2) {
507 let term_y = Math.max(0, -offset[0]);
508 let term_x = Math.max(0, -offset[1]);
509 let map_y = Math.max(0, offset[0]);
510 let map_x = Math.max(0, offset[1]);
511 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
512 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
513 terminal.write(term_y, term_x, to_draw);
516 draw_mode_line: function() {
517 let help = 'hit [' + this.keys.help + '] for help';
518 if (this.mode.has_input_prompt) {
519 help = 'enter /help for help';
521 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
523 draw_turn_line: function(n) {
524 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
526 draw_history: function() {
527 let log_display_lines = [];
528 for (let line of this.log) {
529 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
531 for (let y = terminal.rows - 1 - this.height_input,
532 i = log_display_lines.length - 1;
533 y >= this.height_header && i >= 0;
535 terminal.write(y, this.window_width, log_display_lines[i]);
538 draw_info: function() {
539 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
540 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
541 terminal.write(y, this.window_width, lines[i]);
544 draw_input: function() {
545 if (this.mode.has_input_prompt) {
546 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
547 terminal.write(y, this.window_width, this.input_lines[i]);
551 draw_help: function() {
552 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
553 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
554 if (this.mode == mode_play) {
555 content += "Available actions:\n";
556 if (game.tasks.includes('MOVE')) {
557 content += "[" + movement_keys_desc + "] – move player\n";
559 if (game.tasks.includes('PICK_UP')) {
560 content += "[" + this.keys.take_thing + "] – take thing under player\n";
562 if (game.tasks.includes('DROP')) {
563 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
565 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
566 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
568 content += '\nOther modes available from here:\n';
569 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
570 content += '[' + this.keys.switch_to_study + '] – study mode\n';
571 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
572 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
573 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
574 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
575 } else if (this.mode == mode_study) {
576 content += "Available actions:\n";
577 content += '[' + movement_keys_desc + '] – move question mark\n';
578 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
579 content += '\nOther modes available from here:\n';
580 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
581 content += '[' + this.keys.switch_to_play + '] – play mode\n';
582 } else if (this.mode == mode_chat) {
583 content += '/nick NAME – re-name yourself to NAME\n';
584 //content += '/msg USER TEXT – send TEXT to USER\n';
585 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
586 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
589 if (!this.mode.has_input_prompt) {
590 start_x = this.window_width
592 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
593 let lines = this.msg_into_lines_of_width(content, this.window_width);
594 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
595 terminal.write(y, start_x, lines[i]);
598 full_refresh: function() {
599 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
600 if (this.mode.is_intro) {
604 if (game.turn_complete) {
606 this.draw_turn_line();
608 this.draw_mode_line();
609 if (this.mode.shows_info) {
616 if (this.show_help) {
628 this.map_control = "";
629 this.map_size = [0,0];
634 get_thing: function(id_, create_if_not_found=false) {
635 if (id_ in game.things) {
636 return game.things[id_];
637 } else if (create_if_not_found) {
638 let t = new Thing([0,0]);
639 game.things[id_] = t;
643 move: function(start_position, direction) {
644 let target = [start_position[0], start_position[1]];
645 if (direction == 'LEFT') {
647 } else if (direction == 'RIGHT') {
649 } else if (game.map_geometry == 'Square') {
650 if (direction == 'UP') {
652 } else if (direction == 'DOWN') {
655 } else if (game.map_geometry == 'Hex') {
656 let start_indented = start_position[0] % 2;
657 if (direction == 'UPLEFT') {
659 if (!start_indented) {
662 } else if (direction == 'UPRIGHT') {
664 if (start_indented) {
667 } else if (direction == 'DOWNLEFT') {
669 if (!start_indented) {
672 } else if (direction == 'DOWNRIGHT') {
674 if (start_indented) {
679 if (target[0] < 0 || target[1] < 0 ||
680 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
690 server.init(websocket_location);
695 move: function(direction) {
696 let target = game.move(this.position, direction);
698 this.position = target
701 terminal.blink_screen();
704 update_info_db: function(yx, str) {
705 this.info_db[yx] = str;
706 if (tui.mode == mode_study) {
710 empty_info_db: function() {
712 if (tui.mode == mode_study) {
716 query_info: function() {
717 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
719 get_info: function() {
720 let position_i = this.position[0] * game.map_size[1] + this.position[1];
721 if (game.fov[position_i] != '.') {
722 return 'outside field of view';
725 let terrain_char = game.map[position_i]
726 let terrain_desc = '?'
727 if (game.terrains[terrain_char]) {
728 terrain_desc = game.terrains[terrain_char];
730 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
731 for (let t_id in game.things) {
732 let t = game.things[t_id];
733 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
734 let symbol = game.thing_types[t.type_];
735 info += "THING: " + t.type_ + " / " + symbol;
737 info += t.player_char;
740 info += " (" + t.name_ + ")";
745 if (this.position in game.portals) {
746 info += "PORTAL: " + game.portals[this.position] + "\n";
748 if (this.position in this.info_db) {
749 info += "ANNOTATIONS: " + this.info_db[this.position];
755 annotate: function(msg) {
756 if (msg.length == 0) {
757 msg = " "; // triggers annotation deletion
759 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
761 set_portal: function(msg) {
762 if (msg.length == 0) {
763 msg = " "; // triggers portal deletion
765 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
769 tui.inputEl.addEventListener('input', (event) => {
770 if (tui.mode.has_input_prompt) {
771 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
772 if (tui.inputEl.value.length > max_length) {
773 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
775 tui.recalc_input_lines();
776 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
777 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
778 tui.switch_mode(mode_play);
782 tui.inputEl.addEventListener('keydown', (event) => {
783 tui.show_help = false;
784 if (event.key == 'Enter') {
785 event.preventDefault();
787 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
788 tui.show_help = true;
790 tui.restore_input_values();
791 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
792 tui.show_help = true;
793 } else if (tui.mode == mode_login && event.key == 'Enter') {
794 tui.login_name = tui.inputEl.value;
795 server.send(['LOGIN', tui.inputEl.value]);
797 } else if (tui.mode == mode_portal && event.key == 'Enter') {
798 explorer.set_portal(tui.inputEl.value);
799 tui.switch_mode(mode_play);
800 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
801 explorer.annotate(tui.inputEl.value);
802 tui.switch_mode(mode_play);
803 } else if (tui.mode == mode_password && event.key == 'Enter') {
804 if (tui.inputEl.value.length == 0) {
805 tui.inputEl.value = " ";
807 tui.password = tui.inputEl.value
808 tui.switch_mode(mode_play);
809 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
810 if (tui.inputEl.value == 'YES!') {
811 server.reconnect_to(tui.teleport_target);
813 tui.log_msg('@ teleport aborted');
814 tui.switch_mode(mode_play);
816 } else if (tui.mode == mode_chat && event.key == 'Enter') {
817 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
818 if (tokens.length > 0 && tokens[0].length > 0) {
819 if (tui.inputEl.value[0][0] == '/') {
820 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
821 tui.switch_mode(mode_play);
822 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
823 tui.switch_mode(mode_study);
824 } else if (tokens[0].slice(1) == 'nick') {
825 if (tokens.length > 1) {
826 server.send(['NICK', tokens[1]]);
828 tui.log_msg('? need new name');
830 //} else if (tokens[0].slice(1) == 'msg') {
831 // if (tokens.length > 2) {
832 // let msg = tui.inputEl.value.slice(token_starts[2]);
833 // server.send(['QUERY', tokens[1], msg]);
835 // tui.log_msg('? need message target and message');
838 tui.log_msg('? unknown command');
841 server.send(['ALL', tui.inputEl.value]);
843 } else if (tui.inputEl.valuelength > 0) {
844 server.send(['ALL', tui.inputEl.value]);
847 } else if (tui.mode == mode_play) {
848 if (event.key === tui.keys.switch_to_chat) {
849 event.preventDefault();
850 tui.switch_mode(mode_chat);
851 } else if (event.key === tui.keys.switch_to_edit
852 && game.tasks.includes('WRITE')) {
853 event.preventDefault();
854 tui.switch_mode(mode_edit);
855 } else if (event.key === tui.keys.switch_to_study) {
856 tui.switch_mode(mode_study);
857 } else if (event.key === tui.keys.switch_to_password) {
858 event.preventDefault();
859 tui.switch_mode(mode_password);
860 } else if (event.key === tui.keys.flatten
861 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
862 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
863 } else if (event.key === tui.keys.take_thing
864 && game.tasks.includes('PICK_UP')) {
865 server.send(["TASK:PICK_UP"]);
866 } else if (event.key === tui.keys.drop_thing
867 && game.tasks.includes('DROP')) {
868 server.send(["TASK:DROP"]);
869 } else if (event.key in tui.movement_keys
870 && game.tasks.includes('MOVE')) {
871 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
872 } else if (event.key === tui.keys.switch_to_portal) {
873 event.preventDefault();
874 tui.switch_mode(mode_portal);
875 } else if (event.key === tui.keys.switch_to_annotate) {
876 event.preventDefault();
877 tui.switch_mode(mode_annotate);
879 } else if (tui.mode == mode_study) {
880 if (event.key === tui.keys.switch_to_chat) {
881 event.preventDefault();
882 tui.switch_mode(mode_chat);
883 } else if (event.key == tui.keys.switch_to_play) {
884 tui.switch_mode(mode_play);
885 } else if (event.key in tui.movement_keys) {
886 explorer.move(tui.movement_keys[event.key]);
887 } else if (event.key == tui.keys.toggle_map_mode) {
888 if (tui.map_mode == 'terrain') {
889 tui.map_mode = 'control';
891 tui.map_mode = 'terrain';
898 rows_selector.addEventListener('input', function() {
899 if (rows_selector.value % 4 != 0) {
902 window.localStorage.setItem(rows_selector.id, rows_selector.value);
903 terminal.initialize();
906 cols_selector.addEventListener('input', function() {
907 if (cols_selector.value % 4 != 0) {
910 window.localStorage.setItem(cols_selector.id, cols_selector.value);
911 terminal.initialize();
912 tui.window_width = terminal.cols / 2,
915 for (let key_selector of key_selectors) {
916 key_selector.addEventListener('input', function() {
917 window.localStorage.setItem(key_selector.id, key_selector.value);
921 window.setInterval(function() {
922 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
923 || document.activeElement.id.startsWith('key_'))) {
927 window.setInterval(function() {
928 if (server.connected) {
929 server.send(['PING']);
931 server.reconnect_to(server.url);
932 tui.log_msg('@ attempting reconnect …')