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 } else if (tokens[0] === 'UNHANDLED_INPUT') {
281 tui.log_msg('? unknown command');
282 } else if (tokens[0] === 'PLAY_ERROR') {
283 tui.log_msg('? ' + tokens[1]);
284 terminal.blink_screen();
285 } else if (tokens[0] === 'ARGUMENT_ERROR') {
286 tui.log_msg('? syntax error: ' + tokens[1]);
287 } else if (tokens[0] === 'GAME_ERROR') {
288 tui.log_msg('? game error: ' + tokens[1]);
289 } else if (tokens[0] === 'PONG') {
292 tui.log_msg('? unhandled input: ' + event.data);
298 quote: function(str) {
300 for (let i = 0; i < str.length; i++) {
302 if (['"', '\\'].includes(c)) {
308 return quoted.join('');
310 to_yx: function(yx_coordinate) {
311 return "Y:" + yx_coordinate[0] + ",X:" + yx_coordinate[1];
313 untokenize: function(tokens) {
314 let quoted_tokens = [];
315 for (let token of tokens) {
316 quoted_tokens.push(this.quote(token));
318 return quoted_tokens.join(" ");
323 constructor(name, help_intro, has_input_prompt=false, shows_info=false, is_intro=false) {
325 this.has_input_prompt = has_input_prompt;
326 this.shows_info= shows_info;
327 this.is_intro = is_intro;
328 this.help_intro = help_intro;
331 let mode_waiting_for_server = new Mode('waiting_for_server', 'Waiting for a server response.', false, false, true);
332 let mode_login = new Mode('login', 'Pick your player name.', true, false, true);
333 let mode_post_login_wait = new Mode('waiting for game world', 'Waiting for a server response.', false, false, true);
334 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);
335 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);
336 let mode_play = new Mode('play', 'This mode allows you to interact with the map.', false, false);
337 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);
338 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);
339 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);
340 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);
343 mode: mode_waiting_for_server,
347 window_width: terminal.cols / 2,
354 this.inputEl = document.getElementById("input");
355 this.inputEl.focus();
356 this.recalc_input_lines();
357 this.height_header = this.height_turn_line + this.height_mode_line;
358 this.log_msg("@ waiting for server connection ...");
361 init_keys: function() {
363 for (let key_selector of key_selectors) {
364 this.keys[key_selector.id.slice(4)] = key_selector.value;
366 this.movement_keys = {
367 [this.keys.square_move_up]: 'UP',
368 [this.keys.square_move_left]: 'LEFT',
369 [this.keys.square_move_down]: 'DOWN',
370 [this.keys.square_move_right]: 'RIGHT'
372 if (game.map_geometry == 'Hex') {
373 this.movement_keys = {
374 [this.keys.hex_move_upleft]: 'UPLEFT',
375 [this.keys.hex_move_upright]: 'UPRIGHT',
376 [this.keys.hex_move_right]: 'RIGHT',
377 [this.keys.hex_move_downright]: 'DOWNRIGHT',
378 [this.keys.hex_move_downleft]: 'DOWNLEFT',
379 [this.keys.hex_move_left]: 'LEFT'
383 switch_mode: function(mode) {
384 this.inputEl.focus();
385 this.show_help = false;
386 this.map_mode = 'terrain';
387 if (mode.shows_info && game.player_id in game.things) {
388 explorer.position = game.things[game.player_id].position;
392 this.restore_input_values();
393 document.getElementById("take_thing").disabled = true;
394 document.getElementById("drop_thing").disabled = true;
395 document.getElementById("flatten").disabled = true;
396 document.getElementById("teleport").disabled = true;
397 document.getElementById("toggle_map_mode").disabled = true;
398 document.getElementById("switch_to_chat").disabled = true;
399 document.getElementById("switch_to_play").disabled = true;
400 document.getElementById("switch_to_study").disabled = true;
401 document.getElementById("switch_to_edit").disabled = true;
402 document.getElementById("switch_to_portal").disabled = true;
403 document.getElementById("switch_to_annotate").disabled = true;
404 document.getElementById("switch_to_password").disabled = true;
405 document.getElementById("move_left").disabled = true;
406 document.getElementById("move_upleft").disabled = true;
407 document.getElementById("move_up").disabled = true;
408 document.getElementById("move_upright").disabled = true;
409 document.getElementById("move_downleft").disabled = true;
410 document.getElementById("move_down").disabled = true;
411 document.getElementById("move_downright").disabled = true;
412 document.getElementById("move_right").disabled = true;
413 if (mode == mode_play || mode == mode_study) {
414 document.getElementById("move_left").disabled = false;
415 document.getElementById("move_right").disabled = false;
416 if (game.map_geometry == 'Hex') {
417 document.getElementById("move_upleft").disabled = false;
418 document.getElementById("move_upright").disabled = false;
419 document.getElementById("move_downleft").disabled = false;
420 document.getElementById("move_downright").disabled = false;
422 document.getElementById("move_up").disabled = false;
423 document.getElementById("move_down").disabled = false;
426 if (!mode.is_intro && mode != mode_play) {
427 document.getElementById("switch_to_play").disabled = false;
429 if (!mode.is_intro && mode != mode_study) {
430 document.getElementById("switch_to_study").disabled = false;
432 if (!mode.is_intro && mode != mode_chat) {
433 document.getElementById("switch_to_chat").disabled = false;
435 if (mode == mode_login) {
436 if (this.login_name) {
437 server.send(['LOGIN', this.login_name]);
439 this.log_msg("? need login name");
441 } else if (mode == mode_play) {
442 if (game.tasks.includes('PICK_UP')) {
443 document.getElementById("take_thing").disabled = false;
445 if (game.tasks.includes('DROP')) {
446 document.getElementById("drop_thing").disabled = false;
448 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
449 document.getElementById("flatten").disabled = false;
451 if (game.tasks.includes('MOVE')) {
453 document.getElementById("teleport").disabled = false;
454 document.getElementById("switch_to_annotate").disabled = false;
455 document.getElementById("switch_to_edit").disabled = false;
456 document.getElementById("switch_to_portal").disabled = false;
457 document.getElementById("switch_to_password").disabled = false;
458 } else if (mode == mode_study) {
459 document.getElementById("toggle_map_mode").disabled = false;
460 } else if (mode == mode_edit) {
461 this.show_help = true;
465 restore_input_values: function() {
466 if (this.mode == mode_annotate && explorer.position in explorer.info_db) {
467 let info = explorer.info_db[explorer.position];
468 if (info != "(none)") {
469 this.inputEl.value = info;
470 this.recalc_input_lines();
472 } else if (this.mode == mode_portal && explorer.position in game.portals) {
473 let portal = game.portals[explorer.position]
474 this.inputEl.value = portal;
475 this.recalc_input_lines();
476 } else if (this.mode == mode_password) {
477 this.inputEl.value = this.password;
478 this.recalc_input_lines();
481 empty_input: function(str) {
482 this.inputEl.value = "";
483 if (this.mode.has_input_prompt) {
484 this.recalc_input_lines();
486 this.height_input = 0;
489 recalc_input_lines: function() {
490 this.input_lines = this.msg_into_lines_of_width(this.input_prompt + this.inputEl.value, this.window_width);
491 this.height_input = this.input_lines.length;
493 msg_into_lines_of_width: function(msg, width) {
496 for (let i = 0, x = 0; i < msg.length; i++, x++) {
497 if (x >= width || msg[i] == "\n") {
502 if (msg[i] != "\n") {
509 log_msg: function(msg) {
511 while (this.log.length > 100) {
516 draw_map: function() {
517 let map_lines_split = [];
519 let map_content = game.map;
520 if (this.map_mode == 'control') {
521 map_content = game.map_control;
523 for (let i = 0, j = 0; i < game.map.length; i++, j++) {
524 if (j == game.map_size[1]) {
525 map_lines_split.push(line);
529 line.push(map_content[i] + ' ');
531 map_lines_split.push(line);
532 if (this.map_mode == 'terrain') {
533 for (const p in game.portals) {
534 let coordinate = p.split(',')
535 map_lines_split[coordinate[0]][coordinate[1]] = 'P ';
537 let used_positions = [];
538 for (const thing_id in game.things) {
539 let t = game.things[thing_id];
540 let symbol = game.thing_types[t.type_];
543 meta_char = t.player_char;
545 if (used_positions.includes(t.position.toString())) {
548 map_lines_split[t.position[0]][t.position[1]] = symbol + meta_char;
549 used_positions.push(t.position.toString());
552 if (tui.mode.shows_info) {
553 map_lines_split[explorer.position[0]][explorer.position[1]] = '??';
556 if (game.map_geometry == 'Square') {
557 for (let line_split of map_lines_split) {
558 map_lines.push(line_split.join(''));
560 } else if (game.map_geometry == 'Hex') {
562 for (let line_split of map_lines_split) {
563 map_lines.push(' '.repeat(indent) + line_split.join(''));
571 let window_center = [terminal.rows / 2, this.window_width / 2];
572 let player = game.things[game.player_id];
573 let center_position = [player.position[0], player.position[1]];
574 if (tui.mode.shows_info) {
575 center_position = [explorer.position[0], explorer.position[1]];
577 center_position[1] = center_position[1] * 2;
578 let offset = [center_position[0] - window_center[0],
579 center_position[1] - window_center[1]]
580 if (game.map_geometry == 'Hex' && offset[0] % 2) {
583 let term_y = Math.max(0, -offset[0]);
584 let term_x = Math.max(0, -offset[1]);
585 let map_y = Math.max(0, offset[0]);
586 let map_x = Math.max(0, offset[1]);
587 for (; term_y < terminal.rows && map_y < game.map_size[0]; term_y++, map_y++) {
588 let to_draw = map_lines[map_y].slice(map_x, this.window_width + offset[1]);
589 terminal.write(term_y, term_x, to_draw);
592 draw_mode_line: function() {
593 let help = 'hit [' + this.keys.help + '] for help';
594 if (this.mode.has_input_prompt) {
595 help = 'enter /help for help';
597 terminal.write(0, this.window_width, 'MODE: ' + this.mode.name + ' – ' + help);
599 draw_turn_line: function(n) {
600 terminal.write(1, this.window_width, 'TURN: ' + game.turn);
602 draw_history: function() {
603 let log_display_lines = [];
604 for (let line of this.log) {
605 log_display_lines = log_display_lines.concat(this.msg_into_lines_of_width(line, this.window_width));
607 for (let y = terminal.rows - 1 - this.height_input,
608 i = log_display_lines.length - 1;
609 y >= this.height_header && i >= 0;
611 terminal.write(y, this.window_width, log_display_lines[i]);
614 draw_info: function() {
615 let lines = this.msg_into_lines_of_width(explorer.get_info(), this.window_width);
616 for (let y = this.height_header, i = 0; y < terminal.rows && i < lines.length; y++, i++) {
617 terminal.write(y, this.window_width, lines[i]);
620 draw_input: function() {
621 if (this.mode.has_input_prompt) {
622 for (let y = terminal.rows - this.height_input, i = 0; i < this.input_lines.length; y++, i++) {
623 terminal.write(y, this.window_width, this.input_lines[i]);
627 draw_help: function() {
628 let movement_keys_desc = Object.keys(this.movement_keys).join(',');
629 let content = this.mode.name + " mode help\n\n" + this.mode.help_intro + "\n\n";
630 if (this.mode == mode_play) {
631 content += "Available actions:\n";
632 if (game.tasks.includes('MOVE')) {
633 content += "[" + movement_keys_desc + "] – move player\n";
635 if (game.tasks.includes('PICK_UP')) {
636 content += "[" + this.keys.take_thing + "] – take thing under player\n";
638 if (game.tasks.includes('DROP')) {
639 content += "[" + this.keys.drop_thing + "] – drop carried thing\n";
641 if (game.tasks.includes('FLATTEN_SURROUNDINGS')) {
642 content += "[" + tui.keys.flatten + "] – flatten player's surroundings\n";
644 content += "[" + tui.keys.teleport + "] – teleport to other space\n";
645 content += '\nOther modes available from here:\n';
646 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
647 content += '[' + this.keys.switch_to_study + '] – study mode\n';
648 content += '[' + this.keys.switch_to_edit + '] – terrain edit mode\n';
649 content += '[' + this.keys.switch_to_portal + '] – portal edit mode\n';
650 content += '[' + this.keys.switch_to_annotate + '] – annotation mode\n';
651 content += '[' + this.keys.switch_to_password + '] – password input mode\n';
652 } else if (this.mode == mode_study) {
653 content += "Available actions:\n";
654 content += '[' + movement_keys_desc + '] – move question mark\n';
655 content += '[' + this.keys.toggle_map_mode + '] – toggle view between terrain, and password protection areas\n';
656 content += '\nOther modes available from here:\n';
657 content += '[' + this.keys.switch_to_chat + '] – chat mode\n';
658 content += '[' + this.keys.switch_to_play + '] – play mode\n';
659 } else if (this.mode == mode_chat) {
660 content += '/nick NAME – re-name yourself to NAME\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);
867 tui.inputEl.addEventListener('keydown', (event) => {
868 tui.show_help = false;
869 if (event.key == 'Enter') {
870 event.preventDefault();
872 if (tui.mode.has_input_prompt && event.key == 'Enter' && tui.inputEl.value == '/help') {
873 tui.show_help = true;
875 tui.restore_input_values();
876 } else if (!tui.mode.has_input_prompt && event.key == tui.keys.help) {
877 tui.show_help = true;
878 } else if (tui.mode == mode_login && event.key == 'Enter') {
879 tui.login_name = tui.inputEl.value;
880 server.send(['LOGIN', tui.inputEl.value]);
882 } else if (tui.mode == mode_portal && event.key == 'Enter') {
883 explorer.set_portal(tui.inputEl.value);
884 tui.switch_mode(mode_play);
885 } else if (tui.mode == mode_annotate && event.key == 'Enter') {
886 explorer.annotate(tui.inputEl.value);
887 tui.switch_mode(mode_play);
888 } else if (tui.mode == mode_password && event.key == 'Enter') {
889 if (tui.inputEl.value.length == 0) {
890 tui.inputEl.value = " ";
892 tui.password = tui.inputEl.value
893 tui.switch_mode(mode_play);
894 } else if (tui.mode == mode_chat && event.key == 'Enter') {
895 let tokens = parser.tokenize(tui.inputEl.value);
896 if (tokens.length > 0 && tokens[0].length > 0) {
897 if (tui.inputEl.value[0][0] == '/') {
898 if (tokens[0].slice(1) == 'play' || tokens[0][1] == tui.keys.switch_to_play) {
899 tui.switch_mode(mode_play);
900 } else if (tokens[0].slice(1) == 'study' || tokens[0][1] == tui.keys.switch_to_study) {
901 tui.switch_mode(mode_study);
902 } else if (tokens[0].slice(1) == 'nick') {
903 if (tokens.length > 1) {
904 server.send(['NICK', tokens[1]]);
906 tui.log_msg('? need new name');
909 tui.log_msg('? unknown command');
912 server.send(['ALL', tui.inputEl.value]);
914 } else if (tui.inputEl.valuelength > 0) {
915 server.send(['ALL', tui.inputEl.value]);
918 } else if (tui.mode == mode_play) {
919 if (event.key === tui.keys.switch_to_chat) {
920 event.preventDefault();
921 tui.switch_mode(mode_chat);
922 } else if (event.key === tui.keys.switch_to_edit
923 && game.tasks.includes('WRITE')) {
924 event.preventDefault();
925 tui.switch_mode(mode_edit);
926 } else if (event.key === tui.keys.switch_to_study) {
927 tui.switch_mode(mode_study);
928 } else if (event.key === tui.keys.switch_to_password) {
929 event.preventDefault();
930 tui.switch_mode(mode_password);
931 } else if (event.key === tui.keys.flatten
932 && game.tasks.includes('FLATTEN_SURROUNDINGS')) {
933 server.send(["TASK:FLATTEN_SURROUNDINGS", tui.password]);
934 } else if (event.key === tui.keys.take_thing
935 && game.tasks.includes('PICK_UP')) {
936 server.send(["TASK:PICK_UP"]);
937 } else if (event.key === tui.keys.drop_thing
938 && game.tasks.includes('DROP')) {
939 server.send(["TASK:DROP"]);
940 } else if (event.key in tui.movement_keys
941 && game.tasks.includes('MOVE')) {
942 server.send(['TASK:MOVE', tui.movement_keys[event.key]]);
943 } else if (event.key === tui.keys.teleport) {
945 } else if (event.key === tui.keys.switch_to_portal) {
946 event.preventDefault();
947 tui.switch_mode(mode_portal);
948 } else if (event.key === tui.keys.switch_to_annotate) {
949 event.preventDefault();
950 tui.switch_mode(mode_annotate);
952 } else if (tui.mode == mode_study) {
953 if (event.key === tui.keys.switch_to_chat) {
954 event.preventDefault();
955 tui.switch_mode(mode_chat);
956 } else if (event.key == tui.keys.switch_to_play) {
957 tui.switch_mode(mode_play);
958 } else if (event.key in tui.movement_keys) {
959 explorer.move(tui.movement_keys[event.key]);
960 } else if (event.key == tui.keys.toggle_map_mode) {
961 if (tui.map_mode == 'terrain') {
962 tui.map_mode = 'control';
964 tui.map_mode = 'terrain';
971 rows_selector.addEventListener('input', function() {
972 if (rows_selector.value % 4 != 0 || rows_selector.value < 24) {
975 window.localStorage.setItem(rows_selector.id, rows_selector.value);
976 terminal.initialize();
979 cols_selector.addEventListener('input', function() {
980 if (cols_selector.value % 4 != 0 || cols_selector.value < 80) {
983 window.localStorage.setItem(cols_selector.id, cols_selector.value);
984 terminal.initialize();
985 tui.window_width = terminal.cols / 2,
988 for (let key_selector of key_selectors) {
989 key_selector.addEventListener('input', function() {
990 window.localStorage.setItem(key_selector.id, key_selector.value);
994 window.setInterval(function() {
995 if (server.connected) {
996 server.send(['PING']);
998 server.reconnect_to(server.url);
999 tui.log_msg('@ attempting reconnect …')
1002 document.getElementById("terminal").onclick = function() {
1003 tui.inputEl.focus();
1005 document.getElementById("help").onclick = function() {
1006 tui.show_help = true;
1009 document.getElementById("switch_to_play").onclick = function() {
1010 tui.switch_mode(mode_play);
1013 document.getElementById("switch_to_study").onclick = function() {
1014 tui.switch_mode(mode_study);
1017 document.getElementById("switch_to_chat").onclick = function() {
1018 tui.switch_mode(mode_chat);
1021 document.getElementById("switch_to_password").onclick = function() {
1022 tui.switch_mode(mode_password);
1025 document.getElementById("switch_to_edit").onclick = function() {
1026 tui.switch_mode(mode_edit);
1029 document.getElementById("switch_to_annotate").onclick = function() {
1030 tui.switch_mode(mode_annotate);
1033 document.getElementById("switch_to_portal").onclick = function() {
1034 tui.switch_mode(mode_portal);
1037 document.getElementById("toggle_map_mode").onclick = function() {
1038 if (tui.map_mode == 'terrain') {
1039 tui.map_mode = 'control';
1041 tui.map_mode = 'terrain';
1045 document.getElementById("take_thing").onclick = function() {
1046 server.send(['TASK:PICK_UP']);
1048 document.getElementById("drop_thing").onclick = function() {
1049 server.send(['TASK:DROP']);
1051 document.getElementById("flatten").onclick = function() {
1052 server.send(['TASK:FLATTEN_SURROUNDINGS', tui.password]);
1054 document.getElementById("teleport").onclick = function() {
1057 document.getElementById("move_upleft").onclick = function() {
1058 if (tui.mode == mode_play) {
1059 server.send(['TASK:MOVE', 'UPLEFT']);
1061 explorer.move('UPLEFT');
1064 document.getElementById("move_left").onclick = function() {
1065 if (tui.mode == mode_play) {
1066 server.send(['TASK:MOVE', 'LEFT']);
1068 explorer.move('LEFT');
1071 document.getElementById("move_downleft").onclick = function() {
1072 if (tui.mode == mode_play) {
1073 server.send(['TASK:MOVE', 'DOWNLEFT']);
1075 explorer.move('DOWNLEFT');
1078 document.getElementById("move_down").onclick = function() {
1079 if (tui.mode == mode_play) {
1080 server.send(['TASK:MOVE', 'DOWN']);
1082 explorer.move('DOWN');
1085 document.getElementById("move_up").onclick = function() {
1086 if (tui.mode == mode_play) {
1087 server.send(['TASK:MOVE', 'UP']);
1089 explorer.move('UP');
1092 document.getElementById("move_upright").onclick = function() {
1093 if (tui.mode == mode_play) {
1094 server.send(['TASK:MOVE', 'UPRIGHT']);
1096 explorer.move('UPRIGHT');
1099 document.getElementById("move_right").onclick = function() {
1100 if (tui.mode == mode_play) {
1101 server.send(['TASK:MOVE', 'RIGHT']);
1103 explorer.move('RIGHT');
1106 document.getElementById("move_downright").onclick = function() {
1107 if (tui.mode == mode_play) {
1108 server.send(['TASK:MOVE', 'DOWNRIGHT']);
1110 explorer.move('DOWNRIGHT');