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 <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>
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/control view</button>
34 <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 />
36 <li>move up (square grid): <input id="key_square_move_up" type="text" value="w" /> (hint: ArrowUp)
37 <li>move left (square grid): <input id="key_square_move_left" type="text" value="a" /> (hint: ArrowLeft)
38 <li>move down (square grid): <input id="key_square_move_down" type="text" value="s" /> (hint: ArrowDown)
39 <li>move right (square grid): <input id="key_square_move_right" type="text" value="d" /> (hint: ArrowRight)
40 <li>move up-left (hex grid): <input id="key_hex_move_upleft" type="text" value="w" />
41 <li>move up-right (hex grid): <input id="key_hex_move_upright" type="text" value="e" />
42 <li>move right (hex grid): <input id="key_hex_move_right" type="text" value="d" />
43 <li>move down-right (hex grid): <input id="key_hex_move_downright" type="text" value="x" />
44 <li>move down-left (hex grid): <input id="key_hex_move_downleft" type="text" value="y" />
45 <li>move left (hex grid): <input id="key_hex_move_left" type="text" value="a" />
46 <li>help: <input id="key_help" type="text" value="h" />
47 <li>flatten surroundings: <input id="key_flatten" type="text" value="F" />
48 <li>teleport: <input id="key_teleport" type="text" value="p" />
49 <li>take thing under player: <input id="key_take_thing" type="text" value="z" />
50 <li>drop carried thing: <input id="key_drop_thing" type="text" value="u" />
51 <li>switch to chat mode: <input id="key_switch_to_chat" type="text" value="t" />
52 <li>switch to play mode: <input id="key_switch_to_play" type="text" value="p" />
53 <li>switch to study mode: <input id="key_switch_to_study" type="text" value="?" />
54 <li>edit tile (from play mode): <input id="key_switch_to_edit" type="text" value="m" />
55 <li>enter tile password (from play mode): <input id="key_switch_to_password" type="text" value="P" />
56 <li>annotate tile (from play mode): <input id="key_switch_to_annotate" type="text" value="M" />
57 <li>annotate portal (from play mode): <input id="key_switch_to_portal" type="text" value="T" />
58 <li>toggle terrain/control view (from study mode): <input id="key_toggle_map_mode" type="text" value="M" />
63 let websocket_location = "wss://plomlompom.com/rogue_chat/";
65 let rows_selector = document.getElementById("n_rows");
66 let cols_selector = document.getElementById("n_cols");
67 let key_selectors = document.querySelectorAll('[id^="key_"]');
69 function restore_selector_value(selector) {
70 let stored_selection = window.localStorage.getItem(selector.id);
71 if (stored_selection) {
72 selector.value = stored_selection;
75 restore_selector_value(rows_selector);
76 restore_selector_value(cols_selector);
77 for (let key_selector of key_selectors) {
78 restore_selector_value(key_selector);
84 initialize: function() {
85 this.rows = rows_selector.value;
86 this.cols = cols_selector.value;
87 this.pre_el = document.getElementById("terminal");
88 this.pre_el.style.color = this.foreground;
89 this.pre_el.style.backgroundColor = this.background;
92 for (let y = 0, x = 0; y <= this.rows; x++) {
96 this.content.push(line);
105 blink_screen: function() {
106 this.pre_el.style.color = this.background;
107 this.pre_el.style.backgroundColor = this.foreground;
109 this.pre_el.style.color = this.foreground;
110 this.pre_el.style.backgroundColor = this.background;
113 refresh: function() {
115 for (let y = 0; y < this.rows; y++) {
116 let line = this.content[y].join('');
117 pre_string += line + '\n';
119 this.pre_el.textContent = pre_string;
121 write: function(start_y, start_x, msg) {
122 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
123 this.content[start_y][x] = msg[i];
126 drawBox: function(start_y, start_x, height, width) {
127 let end_y = start_y + height;
128 let end_x = start_x + width;
129 for (let y = start_y, x = start_x; y < this.rows; x++) {
137 this.content[y][x] = ' ';
141 terminal.initialize();
144 tokenize: function(str) {
150 for (let i = 0; i < str.length; i++) {
156 } else if (c == '\\') {
158 } else if (c == '"') {
163 } else if (c == '"') {
165 } else if (c === ' ') {
166 if (token.length > 0) {
175 if (token.length > 0) {
178 let token_starts = [];
179 for (let i = 0; i < token_ends.length; i++) {
180 token_starts.push(token_ends[i] - tokens[i].length);
182 return [tokens, token_starts];
184 parse_yx: function(position_string) {
185 let coordinate_strings = position_string.split(',')
186 let position = [0, 0];
187 position[0] = parseInt(coordinate_strings[0].slice(2));
188 position[1] = parseInt(coordinate_strings[1].slice(2));
200 init: function(url) {
202 this.websocket = new WebSocket(this.url);
203 this.websocket.onopen = function(event) {
204 server.connected = true;
205 game.thing_types = {};
207 server.send(['TASKS']);
208 server.send(['TERRAINS']);
209 server.send(['THING_TYPES']);
210 tui.log_msg("@ server connected! :)");
211 tui.switch_mode(mode_login);
213 this.websocket.onclose = function(event) {
214 server.connected = false;
215 tui.switch_mode(mode_waiting_for_server);
216 tui.log_msg("@ server disconnected :(");
218 this.websocket.onmessage = this.handle_event;
220 reconnect_to: function(url) {
221 this.websocket.close();
224 send: function(tokens) {
225 this.websocket.send(unparser.untokenize(tokens));
227 handle_event: function(event) {
228 let tokens = parser.tokenize(event.data)[0];
229 if (tokens[0] === 'TURN') {
230 game.turn_complete = false;
233 game.turn = parseInt(tokens[1]);
234 } else if (tokens[0] === 'THING') {
235 let t = game.get_thing(tokens[3], true);
236 t.position = parser.parse_yx(tokens[1]);
238 } else if (tokens[0] === 'THING_NAME') {
239 let t = game.get_thing(tokens[1], false);
243 } else if (tokens[0] === 'THING_CHAR') {
244 let t = game.get_thing(tokens[1], false);
246 t.player_char = tokens[2];
248 } else if (tokens[0] === 'TASKS') {
249 game.tasks = tokens[1].split(',')
250 } else if (tokens[0] === 'THING_TYPE') {
251 game.thing_types[tokens[1]] = tokens[2]
252 } else if (tokens[0] === 'TERRAIN') {
253 game.terrains[tokens[1]] = tokens[2]
254 } else if (tokens[0] === 'MAP') {
255 game.map_geometry = tokens[1];
257 game.map_size = parser.parse_yx(tokens[2]);
259 } else if (tokens[0] === 'FOV') {
261 } else if (tokens[0] === 'MAP_CONTROL') {
262 game.map_control = tokens[1]
263 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
264 game.turn_complete = true;
265 explorer.empty_info_db();
266 if (tui.mode == mode_post_login_wait) {
267 tui.switch_mode(mode_play);
268 } else if (tui.mode == mode_study) {
269 explorer.query_info();
272 } else if (tokens[0] === 'CHAT') {
273 tui.log_msg('# ' + tokens[1], 1);
274 } else if (tokens[0] === 'PLAYER_ID') {
275 game.player_id = parseInt(tokens[1]);
276 } else if (tokens[0] === 'LOGIN_OK') {
277 this.send(['GET_GAMESTATE']);
278 tui.switch_mode(mode_post_login_wait);
279 } else if (tokens[0] === 'PORTAL') {
280 let position = parser.parse_yx(tokens[1]);
281 game.portals[position] = tokens[2];
282 } else if (tokens[0] === 'ANNOTATION') {
283 let position = parser.parse_yx(tokens[1]);
284 explorer.update_info_db(position, tokens[2]);
285 } else if (tokens[0] === 'UNHANDLED_INPUT') {
286 tui.log_msg('? unknown command');
287 } else if (tokens[0] === 'PLAY_ERROR') {
288 terminal.blink_screen();
289 } else if (tokens[0] === 'ARGUMENT_ERROR') {
290 tui.log_msg('? syntax error: ' + tokens[1]);
291 } else if (tokens[0] === 'GAME_ERROR') {
292 tui.log_msg('? game error: ' + tokens[1]);
293 } else if (tokens[0] === 'PONG') {
296 tui.log_msg('? unhandled input: ' + event.data);
302 quote: function(str) {
304 for (let i = 0; i < str.length; i++) {
306 if (['"', '\\'].includes(c)) {
312 return quoted.join('');
314 to_yx: function(yx_coordinate) {
315 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
317 untokenize: function(tokens) {
318 let quoted_tokens = [];
319 for (let token of tokens) {
320 quoted_tokens.push(this.quote(token));
322 return quoted_tokens.join(" ");
327 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
329 this.has_input_prompt = has_input_prompt;
330 this.shows_info= shows_info;
331 this.is_intro = is_intro;
332 this.help_intro = help_intro;
335 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
336 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
337 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
338 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);
339 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);
340 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
341 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);
342 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);
343 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);
344 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);
347 mode: mode_waiting_for_server,
351 window_width: terminal.cols / 2,
358 this.inputEl = document.getElementById("input");
359 this.inputEl.focus();
360 this.recalc_input_lines();
361 this.height_header = this.height_turn_line + this.height_mode_line;
362 this.log_msg("@ waiting for server connection ...");
365 init_keys: function() {
367 for (let key_selector of key_selectors) {
368 this.keys[key_selector.id.slice(4)] = key_selector.value;
370 this.movement_keys = {
371 [this.keys.square_move_up]: 'UP',
372 [this.keys.square_move_left]: 'LEFT',
373 [this.keys.square_move_down]: 'DOWN',
374 [this.keys.square_move_right]: 'RIGHT'
376 if (game.map_geometry == 'Hex') {
377 this.movement_keys = {
378 [this.keys.hex_move_upleft]: 'UPLEFT',
379 [this.keys.hex_move_upright]: 'UPRIGHT',
380 [this.keys.hex_move_right]: 'RIGHT',
381 [this.keys.hex_move_downright]: 'DOWNRIGHT',
382 [this.keys.hex_move_downleft]: 'DOWNLEFT',
383 [this.keys.hex_move_left]: 'LEFT'
387 switch_mode: function(mode) {
388 this.show_help = false;
389 this.map_mode = 'terrain';
390 if (mode.shows_info && game.player_id in game.things) {
391 explorer.position = game.things[game.player_id].position;
395 this.restore_input_values();
396 document.getElementById("take_thing").disabled = true;
397 document.getElementById("drop_thing").disabled = true;
398 document.getElementById("flatten").disabled = true;
399 document.getElementById("teleport").disabled = true;
400 document.getElementById("toggle_map_mode").disabled = true;
401 document.getElementById("switch_to_chat").disabled = true;
402 document.getElementById("switch_to_play").disabled = true;
403 document.getElementById("switch_to_study").disabled = true;
404 document.getElementById("switch_to_edit").disabled = true;
405 document.getElementById("switch_to_portal").disabled = true;
406 document.getElementById("switch_to_annotate").disabled = true;
407 document.getElementById("switch_to_password").disabled = true;
408 document.getElementById("move_left").disabled = true;
409 document.getElementById("move_upleft").disabled = true;
410 document.getElementById("move_up").disabled = true;
411 document.getElementById("move_upright").disabled = true;
412 document.getElementById("move_downleft").disabled = true;
413 document.getElementById("move_down").disabled = true;
414 document.getElementById("move_downright").disabled = true;
415 document.getElementById("move_right").disabled = true;
416 if (mode == mode_play || mode == mode_study) {
417 document.getElementById("move_left").disabled = false;
418 document.getElementById("move_right").disabled = false;
419 if (game.map_geometry == 'Hex') {
420 document.getElementById("move_upleft").disabled = false;
421 document.getElementById("move_upright").disabled = false;
422 document.getElementById("move_downleft").disabled = false;
423 document.getElementById("move_downright").disabled = false;
425 document.getElementById("move_up").disabled = false;
426 document.getElementById("move_down").disabled = false;
429 if (!mode.is_intro && mode != mode_play) {
430 document.getElementById("switch_to_play").disabled = false;
432 if (!mode.is_intro && mode != mode_study) {
433 document.getElementById("switch_to_study").disabled = false;
435 if (!mode.is_intro && mode != mode_chat) {
436 document.getElementById("switch_to_chat").disabled = false;
438 if (mode == mode_login) {
439 if (this.login_name) {
440 server.send(['LOGIN', this.login_name]);
442 this.log_msg("? need login name");
444 } else if (mode == mode_play) {
445 if (game.tasks.includes('PICK_UP')) {
446 document.getElementById("take_thing").disabled = false;
448 if (game.tasks.includes('DROP')) {
449 document.getElementById("drop_thing").disabled = false;
451 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
452 document.getElementById("flatten").disabled = false;
454 if (game.tasks.includes('MOVE')) {
456 document.getElementById("teleport").disabled = false;
457 document.getElementById("switch_to_annotate").disabled = false;
458 document.getElementById("switch_to_edit").disabled = false;
459 document.getElementById("switch_to_portal").disabled = false;
460 document.getElementById("switch_to_password").disabled = false;
461 } else if (mode == mode_study) {
462 document.getElementById("toggle_map_mode").disabled = false;
463 } else if (mode == mode_edit) {
464 this.show_help = true;
468 restore_input_values: function() {
469 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
470 let info = explorer.info_db[explorer.position];
471 if (info != "(none)") {
472 this.inputEl.value = info;
473 this.recalc_input_lines();
475 } else if (this.mode == mode_portal && explorer.position in game.portals) {
476 let portal = game.portals[explorer.position]
477 this.inputEl.value = portal;
478 this.recalc_input_lines();
479 } else if (this.mode == mode_password) {
480 this.inputEl.value = this.password;
481 this.recalc_input_lines();
484 empty_input: function(str) {
485 this.inputEl.value = "";
486 if (this.mode.has_input_prompt) {
487 this.recalc_input_lines();
489 this.height_input = 0;
492 recalc_input_lines: function() {
493 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
494 this.height_input = this.input_lines.length;
496 msg_into_lines_of_width: function(msg, width) {
499 for (let i = 0, x = 0; i < msg.length; i++, x++) {
500 if (x >= width || msg[i] == "\n") {
505 if (msg[i] != "\n") {
512 log_msg: function(msg) {
514 while (this.log.length > 100) {
519 draw_map: function() {
520 let map_lines_split = [];
522 let map_content = game.map;
523 if (this.map_mode == 'control') {
524 map_content = game.map_control;
526 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
527 if (j == game.map_size[1]) {
528 map_lines_split.push(line);
532 line.push(map_content[i] + ' ');
534 map_lines_split.push(line);
535 if (this.map_mode == 'terrain') {
536 let used_positions = [];
537 for (const thing_id in game.things) {
538 let t = game.things[thing_id];
539 let symbol = game.thing_types[t.type_];
542 meta_char = t.player_char;
544 if (used_positions.includes(t.position.toString())) {
547 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
548 used_positions.push(t.position.toString());
551 if (tui.mode.shows_info) {
552 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
555 if (game.map_geometry == 'Square') {
556 for (let line_split of map_lines_split) {
557 map_lines.push(line_split.join(''));
559 } else if (game.map_geometry == 'Hex') {
561 for (let line_split of map_lines_split) {
562 map_lines.push(' '.repeat(indent) + line_split.join(''));
570 let window_center = [terminal.rows / 2, this.window_width / 2];
571 let player = game.things[game.player_id];
572 let center_position = [player.position[0], player.position[1]];
573 if (tui.mode.shows_info) {
574 center_position = [explorer.position[0], explorer.position[1]];
576 center_position[1] = center_position[1] * 2;
577 let offset = [center_position[0] - window_center[0],
578 center_position[1] - window_center[1]]
579 if (game.map_geometry == 'Hex' && offset[0] % 2) {
582 let term_y = Math.max(0, -offset[0]);
583 let term_x = Math.max(0, -offset[1]);
584 let map_y = Math.max(0, offset[0]);
585 let map_x = Math.max(0, offset[1]);
586 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
587 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
588 terminal.write(term_y, term_x, to_draw);
591 draw_mode_line: function() {
592 let help = 'hit [' + this.keys.help + '] for help';
593 if (this.mode.has_input_prompt) {
594 help = 'enter /help for help';
596 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
598 draw_turn_line: function(n) {
599 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
601 draw_history: function() {
602 let log_display_lines = [];
603 for (let line of this.log) {
604 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
606 for (let y = terminal.rows - 1 - this.height_input,
607 i = log_display_lines.length - 1;
608 y >= this.height_header && i >= 0;
610 terminal.write(y, this.window_width, log_display_lines[i]);
613 draw_info: function() {
614 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
615 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
616 terminal.write(y, this.window_width, lines[i]);
619 draw_input: function() {
620 if (this.mode.has_input_prompt) {
621 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
622 terminal.write(y, this.window_width, this.input_lines[i]);
626 draw_help: function() {
627 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
628 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
629 if (this.mode == mode_play) {
630 content += "Available actions:\n";
631 if (game.tasks.includes('MOVE')) {
632 content += "[" + movement_keys_desc + "] – move player\n";
634 if (game.tasks.includes('PICK_UP')) {
635 content += "[" + this.keys.take_thing + "] – take thing under player\n";
637 if (game.tasks.includes('DROP')) {
638 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
640 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
641 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
643 content += "[" + tui.keys.teleport + "] – teleport to other space\n";
644 content += '\nOther modes available from here:\n';
645 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
646 content += '[' + this.keys.switch_to_study + '] – study mode\n';
647 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
648 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
649 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
650 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
651 } else if (this.mode == mode_study) {
652 content += "Available actions:\n";
653 content += '[' + movement_keys_desc + '] – move question mark\n';
654 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
655 content += '\nOther modes available from here:\n';
656 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
657 content += '[' + this.keys.switch_to_play + '] – play mode\n';
658 } else if (this.mode == mode_chat) {
659 content += '/nick NAME – re-name yourself to NAME\n';
660 //content += '/msg USER TEXT – send TEXT to USER\n';
661 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
662 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
665 if (!this.mode.has_input_prompt) {
666 start_x = this.window_width
668 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
669 let lines = this.msg_into_lines_of_width(content, this.window_width);
670 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
671 terminal.write(y, start_x, lines[i]);
674 full_refresh: function() {
675 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
676 if (this.mode.is_intro) {
680 if (game.turn_complete) {
682 this.draw_turn_line();
684 this.draw_mode_line();
685 if (this.mode.shows_info) {
692 if (this.show_help) {
704 this.map_control = "";
705 this.map_size = [0,0];
710 get_thing: function(id_, create_if_not_found=false) {
711 if (id_ in game.things) {
712 return game.things[id_];
713 } else if (create_if_not_found) {
714 let t = new Thing([0,0]);
715 game.things[id_] = t;
719 move: function(start_position, direction) {
720 let target = [start_position[0], start_position[1]];
721 if (direction == 'LEFT') {
723 } else if (direction == 'RIGHT') {
725 } else if (game.map_geometry == 'Square') {
726 if (direction == 'UP') {
728 } else if (direction == 'DOWN') {
731 } else if (game.map_geometry == 'Hex') {
732 let start_indented = start_position[0] % 2;
733 if (direction == 'UPLEFT') {
735 if (!start_indented) {
738 } else if (direction == 'UPRIGHT') {
740 if (start_indented) {
743 } else if (direction == 'DOWNLEFT') {
745 if (!start_indented) {
748 } else if (direction == 'DOWNRIGHT') {
750 if (start_indented) {
755 if (target[0] < 0 || target[1] < 0 ||
756 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
761 teleport: function() {
762 let player = this.get_thing(game.player_id);
763 if (player.position in this.portals) {
764 server.reconnect_to(this.portals[player.position]);
766 terminal.blink_screen();
767 tui.log_msg('? not standing on portal')
775 server.init(websocket_location);
780 move: function(direction) {
781 let target = game.move(this.position, direction);
783 this.position = target
786 terminal.blink_screen();
789 update_info_db: function(yx, str) {
790 this.info_db[yx] = str;
791 if (tui.mode == mode_study) {
795 empty_info_db: function() {
797 if (tui.mode == mode_study) {
801 query_info: function() {
802 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
804 get_info: function() {
805 let position_i = this.position[0] * game.map_size[1] + this.position[1];
806 if (game.fov[position_i] != '.') {
807 return 'outside field of view';
810 let terrain_char = game.map[position_i]
811 let terrain_desc = '?'
812 if (game.terrains[terrain_char]) {
813 terrain_desc = game.terrains[terrain_char];
815 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
816 for (let t_id in game.things) {
817 let t = game.things[t_id];
818 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
819 let symbol = game.thing_types[t.type_];
820 info += "THING: " + t.type_ + " / " + symbol;
822 info += t.player_char;
825 info += " (" + t.name_ + ")";
830 if (this.position in game.portals) {
831 info += "PORTAL: " + game.portals[this.position] + "\n";
833 if (this.position in this.info_db) {
834 info += "ANNOTATIONS: " + this.info_db[this.position];
840 annotate: function(msg) {
841 if (msg.length == 0) {
842 msg = " "; // triggers annotation deletion
844 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
846 set_portal: function(msg) {
847 if (msg.length == 0) {
848 msg = " "; // triggers portal deletion
850 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
854 tui.inputEl.addEventListener('input', (event) => {
855 if (tui.mode.has_input_prompt) {
856 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
857 if (tui.inputEl.value.length > max_length) {
858 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
860 tui.recalc_input_lines();
861 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
862 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
863 tui.switch_mode(mode_play);
868 tui.inputEl.addEventListener('keydown', (event) => {
869 tui.show_help = false;
870 if (event.key == 'Enter') {
871 event.preventDefault();
873 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
874 tui.show_help = true;
876 tui.restore_input_values();
877 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
878 tui.show_help = true;
879 } else if (tui.mode == mode_login && event.key == 'Enter') {
880 tui.login_name = tui.inputEl.value;
881 server.send(['LOGIN', tui.inputEl.value]);
883 } else if (tui.mode == mode_portal && event.key == 'Enter') {
884 explorer.set_portal(tui.inputEl.value);
885 tui.switch_mode(mode_play);
886 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
887 explorer.annotate(tui.inputEl.value);
888 tui.switch_mode(mode_play);
889 } else if (tui.mode == mode_password && event.key == 'Enter') {
890 if (tui.inputEl.value.length == 0) {
891 tui.inputEl.value = " ";
893 tui.password = tui.inputEl.value
894 tui.switch_mode(mode_play);
895 } else if (tui.mode == mode_chat && event.key == 'Enter') {
896 let [tokens, token_starts] = parser.tokenize(tui.inputEl.value);
897 if (tokens.length > 0 && tokens[0].length > 0) {
898 if (tui.inputEl.value[0][0] == '/') {
899 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
900 tui.switch_mode(mode_play);
901 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
902 tui.switch_mode(mode_study);
903 } else if (tokens[0].slice(1) == 'nick') {
904 if (tokens.length > 1) {
905 server.send(['NICK', tokens[1]]);
907 tui.log_msg('? need new name');
909 //} else if (tokens[0].slice(1) == 'msg') {
910 // if (tokens.length > 2) {
911 // let msg = tui.inputEl.value.slice(token_starts[2]);
912 // server.send(['QUERY', tokens[1], msg]);
914 // tui.log_msg('? need message target and message');
917 tui.log_msg('? unknown command');
920 server.send(['ALL', tui.inputEl.value]);
922 } else if (tui.inputEl.valuelength > 0) {
923 server.send(['ALL', tui.inputEl.value]);
926 } else if (tui.mode == mode_play) {
927 if (event.key === tui.keys.switch_to_chat) {
928 event.preventDefault();
929 tui.switch_mode(mode_chat);
930 } else if (event.key === tui.keys.switch_to_edit
931 && game.tasks.includes('WRITE')) {
932 event.preventDefault();
933 tui.switch_mode(mode_edit);
934 } else if (event.key === tui.keys.switch_to_study) {
935 tui.switch_mode(mode_study);
936 } else if (event.key === tui.keys.switch_to_password) {
937 event.preventDefault();
938 tui.switch_mode(mode_password);
939 } else if (event.key === tui.keys.flatten
940 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
941 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
942 } else if (event.key === tui.keys.take_thing
943 && game.tasks.includes('PICK_UP')) {
944 server.send(["TASK:PICK_UP"]);
945 } else if (event.key === tui.keys.drop_thing
946 && game.tasks.includes('DROP')) {
947 server.send(["TASK:DROP"]);
948 } else if (event.key in tui.movement_keys
949 && game.tasks.includes('MOVE')) {
950 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
951 } else if (event.key === tui.keys.teleport) {
953 } else if (event.key === tui.keys.switch_to_portal) {
954 event.preventDefault();
955 tui.switch_mode(mode_portal);
956 } else if (event.key === tui.keys.switch_to_annotate) {
957 event.preventDefault();
958 tui.switch_mode(mode_annotate);
960 } else if (tui.mode == mode_study) {
961 if (event.key === tui.keys.switch_to_chat) {
962 event.preventDefault();
963 tui.switch_mode(mode_chat);
964 } else if (event.key == tui.keys.switch_to_play) {
965 tui.switch_mode(mode_play);
966 } else if (event.key in tui.movement_keys) {
967 explorer.move(tui.movement_keys[event.key]);
968 } else if (event.key == tui.keys.toggle_map_mode) {
969 if (tui.map_mode == 'terrain') {
970 tui.map_mode = 'control';
972 tui.map_mode = 'terrain';
979 rows_selector.addEventListener('input', function() {
980 if (rows_selector.value % 4 != 0) {
983 window.localStorage.setItem(rows_selector.id, rows_selector.value);
984 terminal.initialize();
987 cols_selector.addEventListener('input', function() {
988 if (cols_selector.value % 4 != 0) {
991 window.localStorage.setItem(cols_selector.id, cols_selector.value);
992 terminal.initialize();
993 tui.window_width = terminal.cols / 2,
996 for (let key_selector of key_selectors) {
997 key_selector.addEventListener('input', function() {
998 window.localStorage.setItem(key_selector.id, key_selector.value);
1002 window.setInterval(function() {
1003 if (!(['input', 'n_cols', 'n_rows'].includes(document.activeElement.id)
1004 || document.activeElement.id.startsWith('key_'))) {
1005 tui.inputEl.focus();
1008 window.setInterval(function() {
1009 if (server.connected) {
1010 server.send(['PING']);
1012 server.reconnect_to(server.url);
1013 tui.log_msg('@ attempting reconnect …')
1017 document.getElementById("help").onclick = function() {
1018 tui.show_help = true;
1021 document.getElementById("switch_to_play").onclick = function() {
1022 tui.switch_mode(mode_play);
1025 document.getElementById("switch_to_study").onclick = function() {
1026 tui.switch_mode(mode_study);
1029 document.getElementById("switch_to_chat").onclick = function() {
1030 tui.switch_mode(mode_chat);
1033 document.getElementById("switch_to_password").onclick = function() {
1034 tui.switch_mode(mode_password);
1037 document.getElementById("switch_to_edit").onclick = function() {
1038 tui.switch_mode(mode_edit);
1041 document.getElementById("switch_to_annotate").onclick = function() {
1042 tui.switch_mode(mode_annotate);
1045 document.getElementById("switch_to_portal").onclick = function() {
1046 tui.switch_mode(mode_portal);
1049 document.getElementById("toggle_map_mode").onclick = function() {
1050 if (tui.map_mode == 'terrain') {
1051 tui.map_mode = 'control';
1053 tui.map_mode = 'terrain';
1057 document.getElementById("take_thing").onclick = function() {
1058 server.send(['TASK:PICK_UP']);
1060 document.getElementById("drop_thing").onclick = function() {
1061 server.send(['TASK:DROP']);
1063 document.getElementById("flatten").onclick = function() {
1064 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1066 document.getElementById("teleport").onclick = function() {
1069 document.getElementById("move_upleft").onclick = function() {
1070 if (tui.mode == mode_play) {
1071 server.send(['TASK:MOVE', 'UPLEFT']);
1073 explorer.move('UPLEFT');
1076 document.getElementById("move_left").onclick = function() {
1077 if (tui.mode == mode_play) {
1078 server.send(['TASK:MOVE', 'LEFT']);
1080 explorer.move('LEFT');
1083 document.getElementById("move_downleft").onclick = function() {
1084 if (tui.mode == mode_play) {
1085 server.send(['TASK:MOVE', 'DOWNLEFT']);
1087 explorer.move('DOWNLEFT');
1090 document.getElementById("move_down").onclick = function() {
1091 if (tui.mode == mode_play) {
1092 server.send(['TASK:MOVE', 'DOWN']);
1094 explorer.move('DOWN');
1097 document.getElementById("move_up").onclick = function() {
1098 if (tui.mode == mode_play) {
1099 server.send(['TASK:MOVE', 'UP']);
1101 explorer.move('UP');
1104 document.getElementById("move_upright").onclick = function() {
1105 if (tui.mode == mode_play) {
1106 server.send(['TASK:MOVE', 'UPRIGHT']);
1108 explorer.move('UPRIGHT');
1111 document.getElementById("move_right").onclick = function() {
1112 if (tui.mode == mode_play) {
1113 server.send(['TASK:MOVE', 'RIGHT']);
1115 explorer.move('RIGHT');
1118 document.getElementById("move_downright").onclick = function() {
1119 if (tui.mode == mode_play) {
1120 server.send(['TASK:MOVE', 'DOWNRIGHT']);
1122 explorer.move('DOWNRIGHT');