1 const express = require("express");
2 const cors = require("cors");
3 const body_parser = require("body-parser");
4 const session = require("express-session");
7 var lmno_config = require("./lmno-config.json");
13 function config_usage() {
14 console.log(`Error: Refusing to run without configuration.
16 Please create a file named lmno-config.json that looks as follows:
19 "session_secret": "<this should be a long string of true-random characters>";
22 Note: Don't use the exact text above, but instead replace the string
23 with what it describes: a long string of random characters.`);
26 const app = express();
29 secret: lmno_config.session_secret,
31 saveUninitialized: false
34 /* Load each of our game mini-apps. */
35 var empires = require("./empires");
43 return [null,null,null,null].map(() => LMNO.letters.charAt(Math.floor(Math.random() * LMNO.letters.length))).join('');
48 var id = this.generate_id();
49 } while (id in this.ids);
51 const game = new empires.Game();
63 /* Some letters we don't use in our IDs:
65 * 1. Vowels (AEIOU) to avoid accidentally spelling an unfortunate word
66 * 2. Lowercase letters (replace with corresponding capital on input)
67 * 3. N (replace with M on input)
68 * 4. P (replace with B on input)
69 * 5. S (replace with F on input)
71 LMNO.letters = "BCDFGHJKLMQRTVWXYZ";
73 const lmno = new LMNO();
75 /* Force a game ID into a canonical form as described above. */
76 function lmno_canonize(id) {
78 id = id.toUpperCase();
80 /* Replace unused letters with nearest phonetic match. */
81 id = id.replace(/N/g, 'M');
82 id = id.replace(/P/g, 'B');
83 id = id.replace(/S/g, 'F');
85 /* Replace unused numbers nearest visual match. */
86 id = id.replace(/0/g, 'O');
87 id = id.replace(/1/g, 'I');
88 id = id.replace(/5/g, 'S');
93 app.post('/new/:game_engine', (request, response) => {
94 const game_engine = request.params.game_engine;
95 const game_id = lmno.create_game(game_engine);
96 response.send(JSON.stringify(game_id));
99 /* Redirect any requests to a game ID at the top-level.
101 * Specifically, after obtaining the game ID (from the path) we simply
102 * lookup the game engine for the corresponding game and then redirect
103 * to the engine- and game-specific path.
105 app.get('/[a-zA-Z0-9]{4}', (request, response) => {
106 const game_id = request.path.replace(/\//g, "");
107 const canon_id = lmno_canonize(game_id);
109 /* Redirect user to page with the canonical ID in it. */
110 if (game_id !== canon_id) {
111 response.redirect(301, `/${canon_id}/`);
115 const game = lmno.ids[game_id];
116 if (game === undefined) {
117 response.sendStatus(404);
120 response.redirect(301, `/${game.engine}/${game.id}/`);
123 /* LMNO middleware to lookup the game. */
124 app.use('/empires/:game_id([a-zA-Z0-9]{4})', (request, response, next) => {
125 const game_id = request.params.game_id;
126 const canon_id = lmno_canonize(game_id);
128 /* Redirect user to page with the canonical ID in it. */
129 if (game_id !== canon_id) {
130 const new_url = request.originalUrl.replace("/empires/" + game_id,
131 "/empires/" + canon_id);
132 response.redirect(301, new_url);
136 request.game = lmno.ids[game_id].game;
137 if (request.game === undefined) {
138 response.sendStatus(404);
144 /* Mount sub apps. only _after_ we have done all the middleware we need. */
145 app.use('/empires/[a-zA-Z0-9]{4}/', empires.app);
147 app.listen(4000, function () {
148 console.log('LMNO server listening on localhost:4000');