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 = {};
182 server.send(['TASKS']);
183 server.send(['THING_TYPES']);
184 tui.log_msg("@ server connected! :)");
185 tui.switch_mode(mode_login);
187 this.websocket.onclose = function(event) {
188 server.connected = false;
189 tui.switch_mode(mode_waiting_for_server);
190 tui.log_msg("@ server disconnected :(");
192 this.websocket.onmessage = this.handle_event;
194 reconnect_to: function(url) {
195 this.websocket.close();
198 send: function(tokens) {
199 this.websocket.send(unparser.untokenize(tokens));
201 handle_event: function(event) {
202 let tokens = parser.tokenize(event.data)[0];
203 if (tokens[0] === 'TURN') {
204 game.turn_complete = false;
207 game.turn = parseInt(tokens[1]);
208 } else if (tokens[0] === 'THING') {
209 let t = game.get_thing(tokens[3], true);
210 t.position = parser.parse_yx(tokens[1]);
212 } else if (tokens[0] === 'THING_NAME') {
213 let t = game.get_thing(tokens[1], false);
217 } else if (tokens[0] === 'THING_CHAR') {
218 let t = game.get_thing(tokens[1], false);
220 t.player_char = tokens[2];
222 } else if (tokens[0] === 'TASKS') {
223 game.tasks = tokens[1].split(',')
224 } else if (tokens[0] === 'THING_TYPE') {
225 game.thing_types[tokens[1]] = tokens[2]
226 } else if (tokens[0] === 'MAP') {
227 game.map_geometry = tokens[1];
229 game.map_size = parser.parse_yx(tokens[2]);
231 } else if (tokens[0] === 'FOV') {
233 } else if (tokens[0] === 'MAP_CONTROL') {
234 game.map_control = tokens[1]
235 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
236 game.turn_complete = true;
237 explorer.empty_info_db();
238 if (tui.mode == mode_post_login_wait) {
239 tui.switch_mode(mode_play);
240 } else if (tui.mode == mode_study) {
241 explorer.query_info();
243 let t = game.get_thing(game.player_id);
244 if (t.position in game.portals) {
245 tui.teleport_target = game.portals[t.position];
246 tui.switch_mode(mode_teleport);
250 } else if (tokens[0] === 'CHAT') {
251 tui.log_msg('# ' + tokens[1], 1);
252 } else if (tokens[0] === 'PLAYER_ID') {
253 game.player_id = parseInt(tokens[1]);
254 } else if (tokens[0] === 'LOGIN_OK') {
255 this.send(['GET_GAMESTATE']);
256 tui.switch_mode(mode_post_login_wait);
257 } else if (tokens[0] === 'PORTAL') {
258 let position = parser.parse_yx(tokens[1]);
259 game.portals[position] = tokens[2];
260 } else if (tokens[0] === 'ANNOTATION') {
261 let position = parser.parse_yx(tokens[1]);
262 explorer.update_info_db(position, tokens[2]);
263 } else if (tokens[0] === 'UNHANDLED_INPUT') {
264 tui.log_msg('? unknown command');
265 } else if (tokens[0] === 'PLAY_ERROR') {
266 terminal.blink_screen();
267 } else if (tokens[0] === 'ARGUMENT_ERROR') {
268 tui.log_msg('? syntax error: ' + tokens[1]);
269 } else if (tokens[0] === 'GAME_ERROR') {
270 tui.log_msg('? game error: ' + tokens[1]);
271 } else if (tokens[0] === 'PONG') {
274 tui.log_msg('? unhandled input: ' + event.data);
280 quote: function(str) {
282 for (let i = 0; i < str.length; i++) {
284 if (['"', '\\'].includes(c)) {
290 return quoted.join('');
292 to_yx: function(yx_coordinate) {
293 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
295 untokenize: function(tokens) {
296 let quoted_tokens = [];
297 for (let token of tokens) {
298 quoted_tokens.push(this.quote(token));
300 return quoted_tokens.join(" ");
305 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
307 this.has_input_prompt = has_input_prompt;
308 this.shows_info= shows_info;
309 this.is_intro = is_intro;
310 this.help_intro = help_intro;
313 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
314 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
315 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
316 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);
317 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);
318 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
319 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);
320 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);
321 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);
322 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);
323 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);
326 mode: mode_waiting_for_server,
330 window_width: terminal.cols / 2,
337 this.inputEl = document.getElementById("input");
338 this.inputEl.focus();
339 this.recalc_input_lines();
340 this.height_header = this.height_turn_line + this.height_mode_line;
341 this.log_msg("@ waiting for server connection ...");
344 init_keys: function() {
346 for (let key_selector of key_selectors) {
347 this.keys[key_selector.id.slice(4)] = key_selector.value;
349 this.movement_keys = {
350 [this.keys.square_move_up]: 'UP',
351 [this.keys.square_move_left]: 'LEFT',
352 [this.keys.square_move_down]: 'DOWN',
353 [this.keys.square_move_right]: 'RIGHT'
355 if (game.map_geometry == 'Hex') {
356 this.movement_keys = {
357 [this.keys.hex_move_upleft]: 'UPLEFT',
358 [this.keys.hex_move_upright]: 'UPRIGHT',
359 [this.keys.hex_move_right]: 'RIGHT',
360 [this.keys.hex_move_downright]: 'DOWNRIGHT',
361 [this.keys.hex_move_downleft]: 'DOWNLEFT',
362 [this.keys.hex_move_left]: 'LEFT'
366 switch_mode: function(mode) {
367 this.show_help = false;
368 this.map_mode = 'terrain';
369 if (mode.shows_info && game.player_id in game.things) {
370 explorer.position = game.things[game.player_id].position;
374 this.restore_input_values();
375 if (mode == mode_login) {
376 if (this.login_name) {
377 server.send(['LOGIN', this.login_name]);
379 this.log_msg("? need login name");
381 } else if (mode == mode_edit) {
382 this.show_help = true;
383 } else if (mode == mode_teleport) {
384 tui.log_msg("@ May teleport to: " + tui.teleport_target);
385 tui.log_msg("@ Enter 'YES!' to entusiastically affirm.");
389 restore_input_values: function() {
390 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
391 let info = explorer.info_db[explorer.position];
392 if (info != "(none)") {
393 this.inputEl.value = info;
394 this.recalc_input_lines();
396 } else if (this.mode == mode_portal && explorer.position in game.portals) {
397 let portal = game.portals[explorer.position]
398 this.inputEl.value = portal;
399 this.recalc_input_lines();
400 } else if (this.mode == mode_password) {
401 this.inputEl.value = this.password;
402 this.recalc_input_lines();
405 empty_input: function(str) {
406 this.inputEl.value = "";
407 if (this.mode.has_input_prompt) {
408 this.recalc_input_lines();
410 this.height_input = 0;
413 recalc_input_lines: function() {
414 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
415 this.height_input = this.input_lines.length;
417 msg_into_lines_of_width: function(msg, width) {
420 for (let i = 0, x = 0; i < msg.length; i++, x++) {
421 if (x >= width || msg[i] == "\n") {
426 if (msg[i] != "\n") {
433 log_msg: function(msg) {
435 while (this.log.length > 100) {
440 draw_map: function() {
441 let map_lines_split = [];
443 let map_content = game.map;
444 if (this.map_mode == 'control') {
445 map_content = game.map_control;
447 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
448 if (j == game.map_size[1]) {
449 map_lines_split.push(line);
453 line.push(map_content[i] + ' ');
455 map_lines_split.push(line);
456 if (this.map_mode == 'terrain') {
457 let used_positions = [];
458 for (const thing_id in game.things) {
459 let t = game.things[thing_id];
460 let symbol = game.thing_types[t.type_];
463 meta_char = t.player_char;
465 if (used_positions.includes(t.position.toString())) {
468 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
469 used_positions.push(t.position.toString());
472 if (tui.mode.shows_info) {
473 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
476 if (game.map_geometry == 'Square') {
477 for (let line_split of map_lines_split) {
478 map_lines.push(line_split.join(''));
480 } else if (game.map_geometry == 'Hex') {
482 for (let line_split of map_lines_split) {
483 map_lines.push(' '.repeat(indent) + line_split.join(''));
491 let window_center = [terminal.rows / 2, this.window_width / 2];
492 let player = game.things[game.player_id];
493 let center_position = [player.position[0], player.position[1]];
494 if (tui.mode.shows_info) {
495 center_position = [explorer.position[0], explorer.position[1]];
497 center_position[1] = center_position[1] * 2;
498 let offset = [center_position[0] - window_center[0],
499 center_position[1] - window_center[1]]
500 if (game.map_geometry == 'Hex' && offset[0] % 2) {
503 let term_y = Math.max(0, -offset[0]);
504 let term_x = Math.max(0, -offset[1]);
505 let map_y = Math.max(0, offset[0]);
506 let map_x = Math.max(0, offset[1]);
507 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
508 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
509 terminal.write(term_y, term_x, to_draw);
512 draw_mode_line: function() {
513 let help = 'hit [' + this.keys.help + '] for help';
514 if (this.mode.has_input_prompt) {
515 help = 'enter /help for help';
517 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
519 draw_turn_line: function(n) {
520 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
522 draw_history: function() {
523 let log_display_lines = [];
524 for (let line of this.log) {
525 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
527 for (let y = terminal.rows - 1 - this.height_input,
528 i = log_display_lines.length - 1;
529 y >= this.height_header && i >= 0;
531 terminal.write(y, this.window_width, log_display_lines[i]);
534 draw_info: function() {
535 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
536 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
537 terminal.write(y, this.window_width, lines[i]);
540 draw_input: function() {
541 if (this.mode.has_input_prompt) {
542 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
543 terminal.write(y, this.window_width, this.input_lines[i]);
547 draw_help: function() {
548 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
549 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
550 if (this.mode == mode_play) {
551 content += "Available actions:\n";
552 if (game.tasks.includes('MOVE')) {
553 content += "[" + movement_keys_desc + "] – move player\n";
555 if (game.tasks.includes('PICK_UP')) {
556 content += "[" + this.keys.take_thing + "] – take thing under player\n";
558 if (game.tasks.includes('DROP')) {
559 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
561 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
562 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
564 content += '\nOther modes available from here:\n';
565 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
566 content += '[' + this.keys.switch_to_study + '] – study mode\n';
567 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
568 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
569 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
570 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
571 } else if (this.mode == mode_study) {
572 content += "Available actions:\n";
573 content += '[' + movement_keys_desc + '] – move question mark\n';
574 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
575 content += '\nOther modes available from here:\n';
576 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
577 content += '[' + this.keys.switch_to_play + '] – play mode\n';
578 } else if (this.mode == mode_chat) {
579 content += '/nick NAME – re-name yourself to NAME\n';
580 //content += '/msg USER TEXT – send TEXT to USER\n';
581 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
582 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
585 if (!this.mode.has_input_prompt) {
586 start_x = this.window_width
588 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
589 let lines = this.msg_into_lines_of_width(content, this.window_width);
590 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
591 terminal.write(y, start_x, lines[i]);
594 full_refresh: function() {
595 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
596 if (this.mode.is_intro) {
600 if (game.turn_complete) {
602 this.draw_turn_line();
604 this.draw_mode_line();
605 if (this.mode.shows_info) {
612 if (this.show_help) {
624 this.map_control = "";
625 this.map_size = [0,0];
630 get_thing: function(id_, create_if_not_found=false) {
631 if (id_ in game.things) {
632 return game.things[id_];
633 } else if (create_if_not_found) {
634 let t = new Thing([0,0]);
635 game.things[id_] = t;
639 move: function(start_position, direction) {
640 let target = [start_position[0], start_position[1]];
641 if (direction == 'LEFT') {
643 } else if (direction == 'RIGHT') {
645 } else if (game.map_geometry == 'Square') {
646 if (direction == 'UP') {
648 } else if (direction == 'DOWN') {
651 } else if (game.map_geometry == 'Hex') {
652 let start_indented = start_position[0] % 2;
653 if (direction == 'UPLEFT') {
655 if (!start_indented) {
658 } else if (direction == 'UPRIGHT') {
660 if (start_indented) {
663 } else if (direction == 'DOWNLEFT') {
665 if (!start_indented) {
668 } else if (direction == 'DOWNRIGHT') {
670 if (start_indented) {
675 if (target[0] < 0 || target[1] < 0 ||
676 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
686 server.init(websocket_location);
691 move: function(direction) {
692 let target = game.move(this.position, direction);
694 this.position = target
697 terminal.blink_screen();
700 update_info_db: function(yx, str) {
701 this.info_db[yx] = str;
702 if (tui.mode == mode_study) {
706 empty_info_db: function() {
708 if (tui.mode == mode_study) {
712 query_info: function() {
713 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
715 get_info: function() {
716 let position_i = this.position[0] * game.map_size[1] + this.position[1];
717 if (game.fov[position_i] != '.') {
718 return 'outside field of view';
721 info += "TERRAIN: " + game.map[position_i] + "\n";
722 for (let t_id in game.things) {
723 let t = game.things[t_id];
724 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
725 let symbol = game.thing_types[t.type_];
726 info += "THING: " + t.type_ + " / " + symbol;
728 info += t.player_char;
731 info += " (" + t.name_ + ")";
736 if (this.position in game.portals) {
737 info += "PORTAL: " + game.portals[this.position] + "\n";
739 if (this.position in this.info_db) {
740 info += "ANNOTATIONS: " + this.info_db[this.position];
746 annotate: function(msg) {
747 if (msg.length == 0) {
748 msg = " "; // triggers annotation deletion
750 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
752 set_portal: function(msg) {
753 if (msg.length == 0) {
754 msg = " "; // triggers portal deletion
756 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
760 tui.inputEl.addEventListener('input', (event) => {
761 if (tui.mode.has_input_prompt) {
762 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
763 if (tui.inputEl.value.length > max_length) {
764 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
766 tui.recalc_input_lines();
767 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
768 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
769 tui.switch_mode(mode_play);
773 tui.inputEl.addEventListener('keydown', (event) => {
774 tui.show_help = false;
775 if (event.key == 'Enter') {
776 event.preventDefault();
778 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
779 tui.show_help = true;
781 tui.restore_input_values();
782 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
783 tui.show_help = true;
784 } else if (tui.mode == mode_login && event.key == 'Enter') {
785 tui.login_name = tui.inputEl.value;
786 server.send(['LOGIN', tui.inputEl.value]);
788 } else if (tui.mode == mode_portal && event.key == 'Enter') {
789 explorer.set_portal(tui.inputEl.value);
790 tui.switch_mode(mode_play);
791 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
792 explorer.annotate(tui.inputEl.value);
793 tui.switch_mode(mode_play);
794 } else if (tui.mode == mode_password && event.key == 'Enter') {
795 if (tui.inputEl.value.length == 0) {
796 tui.inputEl.value = " ";
798 tui.password = tui.inputEl.value
799 tui.switch_mode(mode_play);
800 } else if (tui.mode == mode_teleport && event.key == 'Enter') {
801 if (tui.inputEl.value == 'YES!') {
802 server.reconnect_to(tui.teleport_target);
804 tui.log_msg('@ teleport aborted');
805 tui.switch_mode(mode_play);
807 } else if (tui.mode == mode_chat && event.key == 'Enter') {
808 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
809 if (tokens.length > 0 && tokens[0].length > 0) {
810 if (tui.inputEl.value[0][0] == '/') {
811 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
812 tui.switch_mode(mode_play);
813 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
814 tui.switch_mode(mode_study);
815 } else if (tokens[0].slice(1) == 'nick') {
816 if (tokens.length > 1) {
817 server.send(['NICK', tokens[1]]);
819 tui.log_msg('? need new name');
821 //} else if (tokens[0].slice(1) == 'msg') {
822 // if (tokens.length > 2) {
823 // let msg = tui.inputEl.value.slice(token_starts[2]);
824 // server.send(['QUERY', tokens[1], msg]);
826 // tui.log_msg('? need message target and message');
829 tui.log_msg('? unknown command');
832 server.send(['ALL', tui.inputEl.value]);
834 } else if (tui.inputEl.valuelength > 0) {
835 server.send(['ALL', tui.inputEl.value]);
838 } else if (tui.mode == mode_play) {
839 if (event.key === tui.keys.switch_to_chat) {
840 event.preventDefault();
841 tui.switch_mode(mode_chat);
842 } else if (event.key === tui.keys.switch_to_edit
843 && game.tasks.includes('WRITE')) {
844 event.preventDefault();
845 tui.switch_mode(mode_edit);
846 } else if (event.key === tui.keys.switch_to_study) {
847 tui.switch_mode(mode_study);
848 } else if (event.key === tui.keys.switch_to_password) {
849 event.preventDefault();
850 tui.switch_mode(mode_password);
851 } else if (event.key === tui.keys.flatten
852 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
853 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
854 } else if (event.key === tui.keys.take_thing
855 && game.tasks.includes('PICK_UP')) {
856 server.send(["TASK:PICK_UP"]);
857 } else if (event.key === tui.keys.drop_thing
858 && game.tasks.includes('DROP')) {
859 server.send(["TASK:DROP"]);
860 } else if (event.key in tui.movement_keys
861 && game.tasks.includes('MOVE')) {
862 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
863 } else if (event.key === tui.keys.switch_to_portal) {
864 event.preventDefault();
865 tui.switch_mode(mode_portal);
866 } else if (event.key === tui.keys.switch_to_annotate) {
867 event.preventDefault();
868 tui.switch_mode(mode_annotate);
870 } else if (tui.mode == mode_study) {
871 if (event.key === tui.keys.switch_to_chat) {
872 event.preventDefault();
873 tui.switch_mode(mode_chat);
874 } else if (event.key == tui.keys.switch_to_play) {
875 tui.switch_mode(mode_play);
876 } else if (event.key in tui.movement_keys) {
877 explorer.move(tui.movement_keys[event.key]);
878 } else if (event.key == tui.keys.toggle_map_mode) {
879 if (tui.map_mode == 'terrain') {
880 tui.map_mode = 'control';
882 tui.map_mode = 'terrain';
889 rows_selector.addEventListener('input', function() {
890 if (rows_selector.value % 4 != 0) {
893 window.localStorage.setItem(rows_selector.id, rows_selector.value);
894 terminal.initialize();
897 cols_selector.addEventListener('input', function() {
898 if (cols_selector.value % 4 != 0) {
901 window.localStorage.setItem(cols_selector.id, cols_selector.value);
902 terminal.initialize();
903 tui.window_width = terminal.cols / 2,
906 for (let key_selector of key_selectors) {
907 key_selector.addEventListener('input', function() {
908 window.localStorage.setItem(key_selector.id, key_selector.value);
912 window.setInterval(function() {
913 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
914 || document.activeElement.id.startsWith('key_'))) {
918 window.setInterval(function() {
919 if (server.connected) {
920 server.send(['PING']);
922 server.reconnect_to(server.url);
923 tui.log_msg('@ attempting reconnect …')