7 terminal rows: <input id="n_rows" type="number" step=4 min=24 value=24 />
8 terminal columns: <input id="n_cols" type="number" step=4 min=80 value=80 />
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/";
64 //let websocket_location = "ws://localhost:8000/";
66 let rows_selector = document.getElementById("n_rows");
67 let cols_selector = document.getElementById("n_cols");
68 let key_selectors = document.querySelectorAll('[id^="key_"]');
70 function restore_selector_value(selector) {
71 let stored_selection = window.localStorage.getItem(selector.id);
72 if (stored_selection) {
73 selector.value = stored_selection;
76 restore_selector_value(rows_selector);
77 restore_selector_value(cols_selector);
78 for (let key_selector of key_selectors) {
79 restore_selector_value(key_selector);
85 initialize: function() {
86 this.rows = rows_selector.value;
87 this.cols = cols_selector.value;
88 this.pre_el = document.getElementById("terminal");
89 this.pre_el.style.color = this.foreground;
90 this.pre_el.style.backgroundColor = this.background;
93 for (let y = 0, x = 0; y <= this.rows; x++) {
97 this.content.push(line);
106 blink_screen: function() {
107 this.pre_el.style.color = this.background;
108 this.pre_el.style.backgroundColor = this.foreground;
110 this.pre_el.style.color = this.foreground;
111 this.pre_el.style.backgroundColor = this.background;
114 refresh: function() {
116 for (let y = 0; y < this.rows; y++) {
117 let line = this.content[y].join('');
118 pre_string += line + '\n';
120 this.pre_el.textContent = pre_string;
122 write: function(start_y, start_x, msg) {
123 for (let x = start_x, i = 0; x < this.cols && i < msg.length; x++, i++) {
124 this.content[start_y][x] = msg[i];
127 drawBox: function(start_y, start_x, height, width) {
128 let end_y = start_y + height;
129 let end_x = start_x + width;
130 for (let y = start_y, x = start_x; y < this.rows; x++) {
138 this.content[y][x] = ' ';
142 terminal.initialize();
145 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) {
174 if (token.length > 0) {
179 parse_yx: function(position_string) {
180 let coordinate_strings = position_string.split(',')
181 let position = [0, 0];
182 position[0] = parseInt(coordinate_strings[0].slice(2));
183 position[1] = parseInt(coordinate_strings[1].slice(2));
195 init: function(url) {
197 this.websocket = new WebSocket(this.url);
198 this.websocket.onopen = function(event) {
199 server.connected = true;
200 game.thing_types = {};
202 server.send(['TASKS']);
203 server.send(['TERRAINS']);
204 server.send(['THING_TYPES']);
205 tui.log_msg("@ server connected! :)");
206 tui.switch_mode(mode_login);
208 this.websocket.onclose = function(event) {
209 server.connected = false;
210 tui.switch_mode(mode_waiting_for_server);
211 tui.log_msg("@ server disconnected :(");
213 this.websocket.onmessage = this.handle_event;
215 reconnect_to: function(url) {
216 this.websocket.close();
219 send: function(tokens) {
220 this.websocket.send(unparser.untokenize(tokens));
222 handle_event: function(event) {
223 let tokens = parser.tokenize(event.data);
224 if (tokens[0] === 'TURN') {
225 game.turn_complete = false;
228 game.turn = parseInt(tokens[1]);
229 } else if (tokens[0] === 'THING') {
230 let t = game.get_thing(tokens[3], true);
231 t.position = parser.parse_yx(tokens[1]);
233 } else if (tokens[0] === 'THING_NAME') {
234 let t = game.get_thing(tokens[1], false);
238 } else if (tokens[0] === 'THING_CHAR') {
239 let t = game.get_thing(tokens[1], false);
241 t.player_char = tokens[2];
243 } else if (tokens[0] === 'TASKS') {
244 game.tasks = tokens[1].split(',')
245 } else if (tokens[0] === 'THING_TYPE') {
246 game.thing_types[tokens[1]] = tokens[2]
247 } else if (tokens[0] === 'TERRAIN') {
248 game.terrains[tokens[1]] = tokens[2]
249 } else if (tokens[0] === 'MAP') {
250 game.map_geometry = tokens[1];
252 game.map_size = parser.parse_yx(tokens[2]);
254 } else if (tokens[0] === 'FOV') {
256 } else if (tokens[0] === 'MAP_CONTROL') {
257 game.map_control = tokens[1]
258 } else if (tokens[0] === 'GAME_STATE_COMPLETE') {
259 game.turn_complete = true;
260 explorer.empty_info_db();
261 if (tui.mode == mode_post_login_wait) {
262 tui.switch_mode(mode_play);
263 } else if (tui.mode == mode_study) {
264 explorer.query_info();
267 } else if (tokens[0] === 'CHAT') {
268 tui.log_msg('# ' + tokens[1], 1);
269 } else if (tokens[0] === 'PLAYER_ID') {
270 game.player_id = parseInt(tokens[1]);
271 } else if (tokens[0] === 'LOGIN_OK') {
272 this.send(['GET_GAMESTATE']);
273 tui.switch_mode(mode_post_login_wait);
274 } else if (tokens[0] === 'PORTAL') {
275 let position = parser.parse_yx(tokens[1]);
276 game.portals[position] = tokens[2];
277 } else if (tokens[0] === 'ANNOTATION') {
278 let position = parser.parse_yx(tokens[1]);
279 explorer.update_info_db(position, tokens[2]);
280 tui.restore_input_values();
282 } else if (tokens[0] === 'UNHANDLED_INPUT') {
283 tui.log_msg('? unknown command');
284 } else if (tokens[0] === 'PLAY_ERROR') {
285 tui.log_msg('? ' + tokens[1]);
286 terminal.blink_screen();
287 } else if (tokens[0] === 'ARGUMENT_ERROR') {
288 tui.log_msg('? syntax error: ' + tokens[1]);
289 } else if (tokens[0] === 'GAME_ERROR') {
290 tui.log_msg('? game error: ' + tokens[1]);
291 } else if (tokens[0] === 'PONG') {
294 tui.log_msg('? unhandled input: ' + event.data);
300 quote: function(str) {
302 for (let i = 0; i < str.length; i++) {
304 if (['"', '\\'].includes(c)) {
310 return quoted.join('');
312 to_yx: function(yx_coordinate) {
313 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
315 untokenize: function(tokens) {
316 let quoted_tokens = [];
317 for (let token of tokens) {
318 quoted_tokens.push(this.quote(token));
320 return quoted_tokens.join(" ");
325 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
327 this.has_input_prompt = has_input_prompt;
328 this.shows_info= shows_info;
329 this.is_intro = is_intro;
330 this.help_intro = help_intro;
333 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
334 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
335 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
336 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);
337 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);
338 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
339 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);
340 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);
341 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);
342 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);
345 mode: mode_waiting_for_server,
349 window_width: terminal.cols / 2,
356 this.inputEl = document.getElementById("input");
357 this.inputEl.focus();
358 this.recalc_input_lines();
359 this.height_header = this.height_turn_line + this.height_mode_line;
360 this.log_msg("@ waiting for server connection ...");
363 init_keys: function() {
365 for (let key_selector of key_selectors) {
366 this.keys[key_selector.id.slice(4)] = key_selector.value;
368 this.movement_keys = {
369 [this.keys.square_move_up]: 'UP',
370 [this.keys.square_move_left]: 'LEFT',
371 [this.keys.square_move_down]: 'DOWN',
372 [this.keys.square_move_right]: 'RIGHT'
374 if (game.map_geometry == 'Hex') {
375 this.movement_keys = {
376 [this.keys.hex_move_upleft]: 'UPLEFT',
377 [this.keys.hex_move_upright]: 'UPRIGHT',
378 [this.keys.hex_move_right]: 'RIGHT',
379 [this.keys.hex_move_downright]: 'DOWNRIGHT',
380 [this.keys.hex_move_downleft]: 'DOWNLEFT',
381 [this.keys.hex_move_left]: 'LEFT'
385 switch_mode: function(mode) {
386 this.inputEl.focus();
387 this.show_help = false;
388 this.map_mode = 'terrain';
389 if (mode.shows_info && game.player_id in game.things) {
390 explorer.position = game.things[game.player_id].position;
391 explorer.query_info();
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 for (const p in game.portals) {
537 let coordinate = p.split(',')
538 map_lines_split[coordinate[0]][coordinate[1]] = 'P ';
540 let used_positions = [];
541 for (const thing_id in game.things) {
542 let t = game.things[thing_id];
543 let symbol = game.thing_types[t.type_];
546 meta_char = t.player_char;
548 if (used_positions.includes(t.position.toString())) {
551 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
552 used_positions.push(t.position.toString());
555 if (tui.mode.shows_info) {
556 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
559 if (game.map_geometry == 'Square') {
560 for (let line_split of map_lines_split) {
561 map_lines.push(line_split.join(''));
563 } else if (game.map_geometry == 'Hex') {
565 for (let line_split of map_lines_split) {
566 map_lines.push(' '.repeat(indent) + line_split.join(''));
574 let window_center = [terminal.rows / 2, this.window_width / 2];
575 let player = game.things[game.player_id];
576 let center_position = [player.position[0], player.position[1]];
577 if (tui.mode.shows_info) {
578 center_position = [explorer.position[0], explorer.position[1]];
580 center_position[1] = center_position[1] * 2;
581 let offset = [center_position[0] - window_center[0],
582 center_position[1] - window_center[1]]
583 if (game.map_geometry == 'Hex' && offset[0] % 2) {
586 let term_y = Math.max(0, -offset[0]);
587 let term_x = Math.max(0, -offset[1]);
588 let map_y = Math.max(0, offset[0]);
589 let map_x = Math.max(0, offset[1]);
590 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
591 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
592 terminal.write(term_y, term_x, to_draw);
595 draw_mode_line: function() {
596 let help = 'hit [' + this.keys.help + '] for help';
597 if (this.mode.has_input_prompt) {
598 help = 'enter /help for help';
600 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
602 draw_turn_line: function(n) {
603 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
605 draw_history: function() {
606 let log_display_lines = [];
607 for (let line of this.log) {
608 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
610 for (let y = terminal.rows - 1 - this.height_input,
611 i = log_display_lines.length - 1;
612 y >= this.height_header && i >= 0;
614 terminal.write(y, this.window_width, log_display_lines[i]);
617 draw_info: function() {
618 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
619 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
620 terminal.write(y, this.window_width, lines[i]);
623 draw_input: function() {
624 if (this.mode.has_input_prompt) {
625 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
626 terminal.write(y, this.window_width, this.input_lines[i]);
630 draw_help: function() {
631 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
632 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
633 if (this.mode == mode_play) {
634 content += "Available actions:\n";
635 if (game.tasks.includes('MOVE')) {
636 content += "[" + movement_keys_desc + "] – move player\n";
638 if (game.tasks.includes('PICK_UP')) {
639 content += "[" + this.keys.take_thing + "] – take thing under player\n";
641 if (game.tasks.includes('DROP')) {
642 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
644 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
645 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
647 content += "[" + tui.keys.teleport + "] – teleport to other space\n";
648 content += '\nOther modes available from here:\n';
649 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
650 content += '[' + this.keys.switch_to_study + '] – study mode\n';
651 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
652 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
653 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
654 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
655 } else if (this.mode == mode_study) {
656 content += "Available actions:\n";
657 content += '[' + movement_keys_desc + '] – move question mark\n';
658 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
659 content += '\nOther modes available from here:\n';
660 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
661 content += '[' + this.keys.switch_to_play + '] – play mode\n';
662 } else if (this.mode == mode_chat) {
663 content += '/nick NAME – re-name yourself to NAME\n';
664 content += '/' + this.keys.switch_to_play + ' or /play – switch to play mode\n';
665 content += '/' + this.keys.switch_to_study + ' or /study – switch to study mode\n';
668 if (!this.mode.has_input_prompt) {
669 start_x = this.window_width
671 terminal.drawBox(0, start_x, terminal.rows, this.window_width);
672 let lines = this.msg_into_lines_of_width(content, this.window_width);
673 for (let y = 0, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
674 terminal.write(y, start_x, lines[i]);
677 full_refresh: function() {
678 terminal.drawBox(0, 0, terminal.rows, terminal.cols);
679 if (this.mode.is_intro) {
683 if (game.turn_complete) {
685 this.draw_turn_line();
687 this.draw_mode_line();
688 if (this.mode.shows_info) {
695 if (this.show_help) {
707 this.map_control = "";
708 this.map_size = [0,0];
713 get_thing: function(id_, create_if_not_found=false) {
714 if (id_ in game.things) {
715 return game.things[id_];
716 } else if (create_if_not_found) {
717 let t = new Thing([0,0]);
718 game.things[id_] = t;
722 move: function(start_position, direction) {
723 let target = [start_position[0], start_position[1]];
724 if (direction == 'LEFT') {
726 } else if (direction == 'RIGHT') {
728 } else if (game.map_geometry == 'Square') {
729 if (direction == 'UP') {
731 } else if (direction == 'DOWN') {
734 } else if (game.map_geometry == 'Hex') {
735 let start_indented = start_position[0] % 2;
736 if (direction == 'UPLEFT') {
738 if (!start_indented) {
741 } else if (direction == 'UPRIGHT') {
743 if (start_indented) {
746 } else if (direction == 'DOWNLEFT') {
748 if (!start_indented) {
751 } else if (direction == 'DOWNRIGHT') {
753 if (start_indented) {
758 if (target[0] < 0 || target[1] < 0 ||
759 target[0] >= this.map_size[0] || target[1] >= this.map_size[1]) {
764 teleport: function() {
765 let player = this.get_thing(game.player_id);
766 if (player.position in this.portals) {
767 server.reconnect_to(this.portals[player.position]);
769 terminal.blink_screen();
770 tui.log_msg('? not standing on portal')
778 server.init(websocket_location);
783 move: function(direction) {
784 let target = game.move(this.position, direction);
786 this.position = target
789 terminal.blink_screen();
792 update_info_db: function(yx, str) {
793 this.info_db[yx] = str;
794 if (tui.mode == mode_study) {
798 empty_info_db: function() {
800 if (tui.mode == mode_study) {
804 query_info: function() {
805 server.send(["GET_ANNOTATION", unparser.to_yx(explorer.position)]);
807 get_info: function() {
808 let position_i = this.position[0] * game.map_size[1] + this.position[1];
809 if (game.fov[position_i] != '.') {
810 return 'outside field of view';
813 let terrain_char = game.map[position_i]
814 let terrain_desc = '?'
815 if (game.terrains[terrain_char]) {
816 terrain_desc = game.terrains[terrain_char];
818 info += 'TERRAIN: "' + terrain_char + '" / ' + terrain_desc + "\n";
819 for (let t_id in game.things) {
820 let t = game.things[t_id];
821 if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
822 let symbol = game.thing_types[t.type_];
823 info += "THING: " + t.type_ + " / " + symbol;
825 info += t.player_char;
828 info += " (" + t.name_ + ")";
833 if (this.position in game.portals) {
834 info += "PORTAL: " + game.portals[this.position] + "\n";
836 if (this.position in this.info_db) {
837 info += "ANNOTATIONS: " + this.info_db[this.position];
843 annotate: function(msg) {
844 if (msg.length == 0) {
845 msg = " "; // triggers annotation deletion
847 server.send(["ANNOTATE", unparser.to_yx(explorer.position), msg, tui.password]);
849 set_portal: function(msg) {
850 if (msg.length == 0) {
851 msg = " "; // triggers portal deletion
853 server.send(["PORTAL", unparser.to_yx(explorer.position), msg, tui.password]);
857 tui.inputEl.addEventListener('input', (event) => {
858 if (tui.mode.has_input_prompt) {
859 let max_length = tui.window_width * terminal.rows - tui.input_prompt.length;
860 if (tui.inputEl.value.length > max_length) {
861 tui.inputEl.value = tui.inputEl.value.slice(0, max_length);
863 tui.recalc_input_lines();
864 } else if (tui.mode == mode_edit && tui.inputEl.value.length > 0) {
865 server.send(["TASK:WRITE", tui.inputEl.value[0], tui.password]);
866 tui.switch_mode(mode_play);
870 tui.inputEl.addEventListener('keydown', (event) => {
871 tui.show_help = false;
872 if (event.key == 'Enter') {
873 event.preventDefault();
875 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
876 tui.show_help = true;
878 tui.restore_input_values();
879 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
880 tui.show_help = true;
881 } else if (tui.mode == mode_login && event.key == 'Enter') {
882 tui.login_name = tui.inputEl.value;
883 server.send(['LOGIN', tui.inputEl.value]);
885 } else if (tui.mode == mode_portal && event.key == 'Enter') {
886 explorer.set_portal(tui.inputEl.value);
887 tui.switch_mode(mode_play);
888 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
889 explorer.annotate(tui.inputEl.value);
890 tui.switch_mode(mode_play);
891 } else if (tui.mode == mode_password && event.key == 'Enter') {
892 if (tui.inputEl.value.length == 0) {
893 tui.inputEl.value = " ";
895 tui.password = tui.inputEl.value
896 tui.switch_mode(mode_play);
897 } else if (tui.mode == mode_chat && event.key == 'Enter') {
898 let tokens = parser.tokenize(tui.inputEl.value);
899 if (tokens.length > 0 && tokens[0].length > 0) {
900 if (tui.inputEl.value[0][0] == '/') {
901 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
902 tui.switch_mode(mode_play);
903 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
904 tui.switch_mode(mode_study);
905 } else if (tokens[0].slice(1) == 'nick') {
906 if (tokens.length > 1) {
907 server.send(['NICK', tokens[1]]);
909 tui.log_msg('? need new name');
912 tui.log_msg('? unknown command');
915 server.send(['ALL', tui.inputEl.value]);
917 } else if (tui.inputEl.valuelength > 0) {
918 server.send(['ALL', tui.inputEl.value]);
921 } else if (tui.mode == mode_play) {
922 if (event.key === tui.keys.switch_to_chat) {
923 event.preventDefault();
924 tui.switch_mode(mode_chat);
925 } else if (event.key === tui.keys.switch_to_edit
926 && game.tasks.includes('WRITE')) {
927 event.preventDefault();
928 tui.switch_mode(mode_edit);
929 } else if (event.key === tui.keys.switch_to_study) {
930 tui.switch_mode(mode_study);
931 } else if (event.key === tui.keys.switch_to_password) {
932 event.preventDefault();
933 tui.switch_mode(mode_password);
934 } else if (event.key === tui.keys.flatten
935 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
936 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
937 } else if (event.key === tui.keys.take_thing
938 && game.tasks.includes('PICK_UP')) {
939 server.send(["TASK:PICK_UP"]);
940 } else if (event.key === tui.keys.drop_thing
941 && game.tasks.includes('DROP')) {
942 server.send(["TASK:DROP"]);
943 } else if (event.key in tui.movement_keys
944 && game.tasks.includes('MOVE')) {
945 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
946 } else if (event.key === tui.keys.teleport) {
948 } else if (event.key === tui.keys.switch_to_portal) {
949 event.preventDefault();
950 tui.switch_mode(mode_portal);
951 } else if (event.key === tui.keys.switch_to_annotate) {
952 event.preventDefault();
953 tui.switch_mode(mode_annotate);
955 } else if (tui.mode == mode_study) {
956 if (event.key === tui.keys.switch_to_chat) {
957 event.preventDefault();
958 tui.switch_mode(mode_chat);
959 } else if (event.key == tui.keys.switch_to_play) {
960 tui.switch_mode(mode_play);
961 } else if (event.key in tui.movement_keys) {
962 explorer.move(tui.movement_keys[event.key]);
963 } else if (event.key == tui.keys.toggle_map_mode) {
964 if (tui.map_mode == 'terrain') {
965 tui.map_mode = 'control';
967 tui.map_mode = 'terrain';
974 rows_selector.addEventListener('input', function() {
975 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
978 window.localStorage.setItem(rows_selector.id, rows_selector.value);
979 terminal.initialize();
982 cols_selector.addEventListener('input', function() {
983 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
986 window.localStorage.setItem(cols_selector.id, cols_selector.value);
987 terminal.initialize();
988 tui.window_width = terminal.cols / 2,
991 for (let key_selector of key_selectors) {
992 key_selector.addEventListener('input', function() {
993 window.localStorage.setItem(key_selector.id, key_selector.value);
997 window.setInterval(function() {
998 if (server.connected) {
999 server.send(['PING']);
1001 server.reconnect_to(server.url);
1002 tui.log_msg('@ attempting reconnect …')
1005 document.getElementById("terminal").onclick = function() {
1006 tui.inputEl.focus();
1008 document.getElementById("help").onclick = function() {
1009 tui.show_help = true;
1012 document.getElementById("switch_to_play").onclick = function() {
1013 tui.switch_mode(mode_play);
1016 document.getElementById("switch_to_study").onclick = function() {
1017 tui.switch_mode(mode_study);
1020 document.getElementById("switch_to_chat").onclick = function() {
1021 tui.switch_mode(mode_chat);
1024 document.getElementById("switch_to_password").onclick = function() {
1025 tui.switch_mode(mode_password);
1028 document.getElementById("switch_to_edit").onclick = function() {
1029 tui.switch_mode(mode_edit);
1032 document.getElementById("switch_to_annotate").onclick = function() {
1033 tui.switch_mode(mode_annotate);
1036 document.getElementById("switch_to_portal").onclick = function() {
1037 tui.switch_mode(mode_portal);
1040 document.getElementById("toggle_map_mode").onclick = function() {
1041 if (tui.map_mode == 'terrain') {
1042 tui.map_mode = 'control';
1044 tui.map_mode = 'terrain';
1048 document.getElementById("take_thing").onclick = function() {
1049 server.send(['TASK:PICK_UP']);
1051 document.getElementById("drop_thing").onclick = function() {
1052 server.send(['TASK:DROP']);
1054 document.getElementById("flatten").onclick = function() {
1055 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1057 document.getElementById("teleport").onclick = function() {
1060 document.getElementById("move_upleft").onclick = function() {
1061 if (tui.mode == mode_play) {
1062 server.send(['TASK:MOVE', 'UPLEFT']);
1064 explorer.move('UPLEFT');
1067 document.getElementById("move_left").onclick = function() {
1068 if (tui.mode == mode_play) {
1069 server.send(['TASK:MOVE', 'LEFT']);
1071 explorer.move('LEFT');
1074 document.getElementById("move_downleft").onclick = function() {
1075 if (tui.mode == mode_play) {
1076 server.send(['TASK:MOVE', 'DOWNLEFT']);
1078 explorer.move('DOWNLEFT');
1081 document.getElementById("move_down").onclick = function() {
1082 if (tui.mode == mode_play) {
1083 server.send(['TASK:MOVE', 'DOWN']);
1085 explorer.move('DOWN');
1088 document.getElementById("move_up").onclick = function() {
1089 if (tui.mode == mode_play) {
1090 server.send(['TASK:MOVE', 'UP']);
1092 explorer.move('UP');
1095 document.getElementById("move_upright").onclick = function() {
1096 if (tui.mode == mode_play) {
1097 server.send(['TASK:MOVE', 'UPRIGHT']);
1099 explorer.move('UPRIGHT');
1102 document.getElementById("move_right").onclick = function() {
1103 if (tui.mode == mode_play) {
1104 server.send(['TASK:MOVE', 'RIGHT']);
1106 explorer.move('RIGHT');
1109 document.getElementById("move_downright").onclick = function() {
1110 if (tui.mode == mode_play) {
1111 server.send(['TASK:MOVE', 'DOWNRIGHT']);
1113 explorer.move('DOWNRIGHT');