├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── sass.js ├── screenshot.png ├── src ├── copy │ └── index.html ├── games │ ├── ai │ │ ├── config.json │ │ ├── gamestate.json │ │ └── vocabulary.json │ ├── de │ │ ├── config.json │ │ ├── gamestate.json │ │ ├── messages.json │ │ └── vocabulary.json │ ├── escape │ │ ├── cell1.png │ │ ├── cell2.png │ │ ├── config.json │ │ ├── gamestate.json │ │ ├── messages.json │ │ ├── narrow-passage.png │ │ ├── stone-texture.png │ │ └── vocabulary.json │ ├── games.json │ ├── messages.json │ └── tutorial │ │ ├── config.json │ │ ├── gamestate.json │ │ ├── messages.json │ │ └── vocabulary.json ├── img │ └── adventex.png ├── js │ ├── const.js │ ├── eventactionhandler.js │ ├── eventhandler.js │ ├── functions.js │ ├── helper.js │ ├── interpreter.js │ ├── inventoryhandler.js │ ├── json.js │ ├── locationhandler.js │ ├── main.js │ └── parser.js ├── scss │ └── main.scss └── svg │ └── icecream.svg └── test ├── browser-sync.js ├── index.html └── test.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules 3 | dist 4 | 5 | ## OS X 6 | .DS_Store 7 | ._* 8 | .Spotlight-V100 9 | .Trashes 10 | deploy.sh 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adventex 2 | 3 | Adventex is a simple interactive fiction (text adventure) game with its own adventure system. The adventure is defined by a couple of JSON files. The game is playable at [thoster.net](https://thoster.net/adventex). 4 | 5 | It starts with a demo adventure (kind of an escape room game) and a tutorial. 6 | 7 | ![](screenshot.png) 8 | 9 | ## Prerequisites 10 | 11 | You need Node.js and npm. 12 | 13 | ## Install 14 | 15 | First, you need to install the npm dependencies: 16 | ```bash 17 | npm install 18 | ``` 19 | 20 | ## Run 21 | 22 | You can serve Adventex locally: 23 | ```bash 24 | npm run serve 25 | ``` 26 | 27 | or build it with: 28 | ```bash 29 | npm run build 30 | ``` 31 | 32 | ## Unit Tests 33 | 34 | Unit tests only work with a real browser. Start them like this: 35 | ```bash 36 | npm run serve-tests 37 | ``` 38 | 39 | ## Playing 40 | 41 | If you want to learn how to play text adventures, go to "options" and hit "tutorial". This is what you get if you enter *help*: 42 | 43 | Most of the time, typing something like *verb object* works. Example: *open door*. 44 | 45 | You can go to a possible direction typing *go (direction)*. 46 | 47 | You can examine the room or any object typing *examine (object)*. Look around with *look*. 48 | 49 | More complex sentences are possible, example: *open box with crowbar*. 50 | 51 | Type *inventory* to show all your collected items. 52 | 53 | Type *help verbs* to get a list of possible verbs. 54 | 55 | ## Adventure Creation 56 | 57 | Adventures in Adventex are created / edited using JSON files. Below is a detailed explanation of how to set up an adventure. 58 | 59 | ### Events 60 | 61 | Events define the actions and descriptions of different events in the adventure, such as starting the game or interacting with objects. 62 | 63 | #### Example: `src/games/tutorial/events.json` 64 | 65 | ```json 66 | { 67 | "events": { 68 | "start_event": { 69 | "name": "Awake", 70 | "description": "Welcome to the Adventex tutorial!\nAdventex is a simple text adventure (also called 'interactive fiction').\nYou can:\nExplore different locations, pick up items (into your 'inventory') and interact with objects and the environment to solve puzzles.\nYou interact with your environment by entering simple sentences, starting with a verb.\nFor a list of possible verbs, enter 'help verbs'.\nOften it is useful to examine objects: 'examine table'. If you want to see the description of the current location, enter 'look'.\nSome objects are portable, to pick up a book, enter 'take book'\nStart with exploring your environment by visiting the other room. To do so, enter 'go east'\n", 71 | "action_move_to_location": "start_location" 72 | }, 73 | "tutorial_openclose": { 74 | "name": "Open/Close Tutorial", 75 | "description": "You can open and close doors and containers. Try 'open door' or 'close chest'.", 76 | "action_move_to_location": "next_location" 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | ### Locations 83 | 84 | Locations define the different places in the adventure, including their descriptions and possible exits. 85 | 86 | #### Example: `src/games/tutorial/locations.json` 87 | 88 | ```json 89 | { 90 | "locations": { 91 | "start_location": { 92 | "name": "Starting Room", 93 | "description": "You are in a small, dimly lit room. There is a door to the east.", 94 | "exits": { 95 | "east": "next_location" 96 | }, 97 | "objects": ["book", "table"] 98 | }, 99 | "next_location": { 100 | "name": "Next Room", 101 | "description": "You are in a larger room with a window. There is a door to the west.", 102 | "exits": { 103 | "west": "start_location" 104 | }, 105 | "objects": ["chest", "window"] 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | ### Verbs 112 | 113 | Verbs define the actions that players can perform in the game. They are used to interact with objects and the environment. 114 | 115 | #### Example: `src/games/tutorial/verbs.json` 116 | 117 | ```json 118 | { 119 | "verbs": { 120 | "go": { 121 | "description": "Move to a different location.", 122 | "action": "move_to_location" 123 | }, 124 | "take": { 125 | "description": "Pick up an item.", 126 | "action": "pick_up_item" 127 | }, 128 | "examine": { 129 | "description": "Look at an object or location.", 130 | "action": "examine_object" 131 | }, 132 | "look": { 133 | "description": "Look around the current location.", 134 | "action": "look_around" 135 | }, 136 | "open": { 137 | "description": "Open a door or container.", 138 | "action": "open_object" 139 | }, 140 | "close": { 141 | "description": "Close a door or container.", 142 | "action": "close_object" 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | ### Synonyms 149 | 150 | Synonyms allow players to use different words for the same action, making the game more flexible and user-friendly. 151 | 152 | #### Example: `src/games/tutorial/synonyms.json` 153 | 154 | ```json 155 | { 156 | "synonyms": { 157 | "take": [ 158 | "get", 159 | "pick" 160 | ], 161 | "go": [ 162 | "walk", 163 | "drive", 164 | "climb" 165 | ], 166 | "look": [ 167 | "watch" 168 | ], 169 | "push": [ 170 | "press" 171 | ], 172 | "extinguish": [ 173 | "delete" 174 | ], 175 | "clean": [ 176 | "wash" 177 | ], 178 | "into": [ 179 | "inside" 180 | ] 181 | } 182 | } 183 | ``` 184 | 185 | ### Key Components 186 | 187 | - **Events**: Define the actions and descriptions of different events in the adventure. 188 | - **Locations**: Define the different places in the adventure, including their descriptions and possible exits. 189 | - **Verbs**: Define the actions that players can perform in the game. 190 | - **Synonyms**: Allow players to use different words for the same action, making the game more flexible and user-friendly. 191 | 192 | By configuring these JSON files, you can create and customize your own text adventures in Adventex. 193 | 194 | If you need any additional details or modifications, feel free to ask! -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adventex", 3 | "version": "1.1.0", 4 | "description": "A javascript text adventure framework", 5 | "author": { 6 | "name": "Stefan Ostermann", 7 | "url": "https://thoster.net" 8 | }, 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/soster/adventex" 13 | }, 14 | "scripts": { 15 | "clean": "recursive-delete 'dist'", 16 | "js": "rollup --config", 17 | "css": "node sass.js", 18 | "svg": "svgo -f src/svg dist/svg -r", 19 | "statics": "copyfiles -u 3 node_modules/jquery.terminal/js/jquery.terminal.min.js dist/js && copyfiles -u 3 node_modules/jquery/dist/jquery.min.js dist/js && copyfiles -u 4 node_modules/bootstrap/dist/css/bootstrap.min.css dist/css && copyfiles -u 3 node_modules/jquery.terminal/css/jquery.terminal.min.css dist/css", 20 | "img": "imagemin src/img/* --out-dir=dist/img --plugin=mozjpeg --plugin=pngcrush", 21 | "img-copy": "recursive-copy 'src/img' 'dist/img'", 22 | "copy": "recursive-copy 'src/copy' 'dist' && recursive-copy 'src/games' 'dist/games' && copyfiles -u 2 src/js/functions.js dist/js", 23 | "build-dirty": "npm-run-all -p js css img copy statics", 24 | "build": "npm-run-all -s clean build-dirty", 25 | "watch-css": "chokidar './src/**/*.scss' -c 'npm run css'", 26 | "watch-js": "chokidar './src/**/*.js' -c 'npm run js'", 27 | "watch-svg": "chokidar './src/**/*.svg' -c 'npm run svg'", 28 | "watch-img": "chokidar './src/img/**/*.*' -c 'npm run img'", 29 | "watch-copy": "chokidar './src/copy/**/*.*' -c 'npm run copy'", 30 | "watch": "npm-run-all -p build watch-css watch-js watch-svg watch-img watch-copy", 31 | "serve-start": "browser-sync start --files 'dist' --server 'dist'", 32 | "serve": "npm-run-all -p watch serve-start", 33 | "test": "mocha -r esm", 34 | "serve-tests": "browser-sync start --config 'test/browser-sync.js' " 35 | }, 36 | 37 | "devDependencies": { 38 | "@rollup/plugin-commonjs": "^23.0.2", 39 | "@rollup/plugin-legacy": "^3.0.1", 40 | "@rollup/plugin-node-resolve": "^15.0.1", 41 | "babel": "^6.23.0", 42 | "browser-sync": "^2.26.14", 43 | "chai": "^4.3.6", 44 | "chokidar-cli": "^2.1.0", 45 | "copyfiles": "^2.4.1", 46 | "esm": "^3.2.25", 47 | "imagemin-cli": "^7.0.0", 48 | "imagemin-mozjpeg": "^10.0.0", 49 | "imagemin-pngcrush": "^7.0.0", 50 | "mocha": "^10.1.0", 51 | "npm-run-all": "^4.1.5", 52 | "recursive-fs": "^2.1.0", 53 | "rollup": "^3.2.3", 54 | "rollup-plugin-minification": "^0.1.0", 55 | "sass": "^1.26.5", 56 | "svgo": "^2.8.0", 57 | "systemjs": "^6.13.0" 58 | }, 59 | "dependencies": { 60 | "bootstrap": "npm:bootstrap@4.6.0", 61 | "jquery": "npm:jquery@^3.2.1", 62 | "jquery.terminal": "npm:jquery.terminal@^2.34.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | //import { terser } from "rollup-plugin-terser"; 4 | //replacement because terser needs rollup 2 as a dependency: 5 | import { terser } from "rollup-plugin-minification"; 6 | 7 | // `npm run build` -> `production` is true 8 | // `npm run dev` -> `production` is false 9 | 10 | const production = !process.env.ROLLUP_WATCH; 11 | 12 | export default { 13 | input: ['src/js/main.js'], 14 | 15 | output: { 16 | file: 'dist/js/main.js', 17 | format: 'umd', // immediately-invoked function expression — suitable for 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/games/ai/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "console": { 3 | "height": 320, 4 | "prompt": "advntx> " 5 | }, 6 | "standard_wait_eventtext": 1000, 7 | "text_color": "white", 8 | "error_color": "red", 9 | "warn_color": "coral", 10 | "win_color": "navy", 11 | "debug": false 12 | } -------------------------------------------------------------------------------- /src/games/ai/gamestate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "1999-12-30T23:00:00.000Z", 3 | "inventory": [], 4 | "location": "start_location", 5 | "seconds": 0, 6 | "steps": 0, 7 | "hints": 0, 8 | "points": 0, 9 | "locations": { 10 | "start_location": { 11 | "name": "Starting room", 12 | "description": "You are in an unadorned, rectangular room without windows.\nA simple light bulb is hanging from the ceiling, the only source of light, casting harsh shadows.\nThere is a passage to the east, apparently to another room much like this.", 13 | "objects": [ 14 | "table", 15 | "light", 16 | "book", 17 | "key" 18 | ], 19 | "states": {}, 20 | "connections": { 21 | "east": "room_two" 22 | } 23 | }, 24 | "room_two": { 25 | "name": "Another room", 26 | "description": "You are in another unadorned, rectangular room. There is a passage to the west. In the opposite direction you see a large wardrobe.", 27 | "connections": { 28 | "west": "start_location" 29 | }, 30 | "objects": [ 31 | "wardrobe" 32 | ] 33 | 34 | }, 35 | "hidden_room": { 36 | "name": "A hidden room", 37 | "description": "You are in another unadorned, rectangular room. There is a passage to the west.", 38 | "objects": [ 39 | "wardrobe" 40 | ], 41 | "reversed": [ 42 | "wardrobe" 43 | ] 44 | 45 | } 46 | }, 47 | "objects": { 48 | "table": { 49 | "name": "table", 50 | "description": "This is a simple, wooden table.", 51 | "portable": false, 52 | "error_portable": "Guess what: The table is to heavy to carry around.", 53 | "hidden": false, 54 | "article": "the" 55 | }, 56 | "light": { 57 | "name": "light bulb", 58 | "description": "A simple light bulb. It reads: 60W.", 59 | "portable": false, 60 | "error_portable": "How many players of interactive fiction does it take to unscrew this light bulb? Apparently, more than one.", 61 | "hidden": false, 62 | "article": "the" 63 | }, 64 | "book": { 65 | "name": "book", 66 | "description": "A book, the title reads: '101 things to do in adventure games.'", 67 | "portable": true, 68 | "hidden": false, 69 | "article": "the" 70 | }, 71 | "key": { 72 | "name": "key", 73 | "description": "A small key.", 74 | "portable": true, 75 | "hidden": false, 76 | "article": "the" 77 | }, 78 | "wardrobe": { 79 | "name": "wardrobe", 80 | "description": "A large, white wardrobe. It looks like an Ikea model.", 81 | "portable": false, 82 | "hidden": false, 83 | "error_portable": "You try to pick up the wardrobe... Well... no.", 84 | "state": "closed", 85 | "locked": true, 86 | "lock_object": "key", 87 | "lock_error": "The wardrobe is locked!", 88 | "states": { 89 | "open": { 90 | "name": "open", 91 | "description": "The wardrobe is open.", 92 | "error": "It is locked!", 93 | "objects": [ 94 | "coat" 95 | ], 96 | "connections": { 97 | "east": "hidden_room" 98 | }, 99 | "reversed_connections": { 100 | "west":"room_two" 101 | } 102 | }, 103 | "closed": { 104 | "name": "closed", 105 | "description": "The wardrobe is closed." 106 | } 107 | }, 108 | "article": "the" 109 | }, 110 | "coat": { 111 | "name": "coat", 112 | "description": "A large, black coat.", 113 | "portable": true, 114 | "hidden": false 115 | } 116 | }, 117 | "events": { 118 | "start_event": { 119 | "name": "Awake", 120 | "description": "Welcome to the adventex tutorial!\nAdventex is a simple text adventure (also called 'interactive fiction').\nYou can:\nExplore different locations, pick up items (into your 'inventory') and interact with objects and the environment to solve puzzles.\nYou interact with your environment by entering simple sentences, starting with a verb.\nFor a list of possible verbs, enter 'help verbs'.\nOften it is useful to examine objects: 'examine table'. If you want to see the description of the current location, enter 'look'.\nSome objects are portable, to pick up a book, enter 'take book'\nStart with exploring your enviromnent by visiting the other room. To do so, enter 'go east'\n", 121 | "action_move_to_location": "start_location" 122 | }, 123 | "tutorial_openclose": { 124 | "description": "Tutorial: You are in a new location. You see a wardrobe in the list of objects.\nTry to open the wardrobe and take whatever is inside.", 125 | "prereq_location": "room_two", 126 | "trigger_once": true 127 | }, 128 | "tutorial_coat": { 129 | "description": "Tutorial: Very good! You successfully took the coat out of the wardrobe.\nAfter taking the coat, you recognise a hidden passage in the wardrobe. The passage leads east.\nGo through the hidden passage!", 130 | "prereq_inventory_items": ["coat"], 131 | "trigger_once": true 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /src/games/ai/vocabulary.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbs": [ 3 | "go", 4 | "walk", 5 | "talk", 6 | "give", 7 | "take", 8 | "get", 9 | "drop", 10 | "use", 11 | "climb", 12 | "open", 13 | "close", 14 | "push", 15 | "press", 16 | "pull", 17 | "unlock", 18 | "lock", 19 | "catch", 20 | "switch", 21 | "examine", 22 | "look", 23 | "fetch", 24 | "pick", 25 | "read", 26 | "run", 27 | "search", 28 | "conjure", 29 | "bribe", 30 | "kindle", 31 | "extinguish", 32 | "light", 33 | "hide", 34 | "emerge", 35 | "throw", 36 | "empty", 37 | "fill", 38 | "clean", 39 | "wash", 40 | "drink", 41 | "eat", 42 | "hint", 43 | "help", 44 | "restart", 45 | "load", 46 | "save", 47 | "list", 48 | "inventory" 49 | ], 50 | "directions": [ 51 | "north", 52 | "east", 53 | "south", 54 | "west", 55 | "up", 56 | "down", 57 | "left", 58 | "right", 59 | "forward", 60 | "backward", 61 | "away" 62 | ], 63 | "prepositions": [ 64 | "from", 65 | "to", 66 | "with", 67 | "around", 68 | "by", 69 | "across", 70 | "for", 71 | "on", 72 | "off", 73 | "at", 74 | "in", 75 | "out", 76 | "into", 77 | "onto", 78 | "over", 79 | "under", 80 | "behind", 81 | "through", 82 | "inside", 83 | "into" 84 | ], 85 | "adjectives": [ 86 | "black", 87 | "white", 88 | "gray", 89 | "grey", 90 | "blue", 91 | "yellow", 92 | "red", 93 | "brown", 94 | "green", 95 | "orange", 96 | "purple", 97 | "heavy", 98 | "light", 99 | "defect", 100 | "shiny", 101 | "bright", 102 | "dark", 103 | "dirty", 104 | "rotten", 105 | "fancy", 106 | "vast", 107 | "odd", 108 | "broad", 109 | "strong", 110 | "fat", 111 | "big", 112 | "huge", 113 | "small", 114 | "tall", 115 | "ancient", 116 | "deep", 117 | "flat", 118 | "shallow", 119 | "bitter", 120 | "sweet", 121 | "delicious", 122 | "tiny", 123 | "wet", 124 | "hot", 125 | "unconscious", 126 | "cold" 127 | ], 128 | "synonyms": { 129 | "take": [ 130 | "get", 131 | "pick" 132 | ], 133 | "go": [ 134 | "walk", 135 | "drive", 136 | "climb" 137 | ], 138 | "look": [ 139 | "watch" 140 | ], 141 | "push": [ 142 | "press" 143 | ], 144 | "extinguish": [ 145 | "delete" 146 | ], 147 | "clean": [ 148 | "wash" 149 | ], 150 | "into": [ 151 | "inside" 152 | ] 153 | } 154 | } -------------------------------------------------------------------------------- /src/games/de/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "console": { 3 | "height": 320, 4 | "prompt": "advntx de> " 5 | }, 6 | "standard_wait_eventtext": 1000, 7 | "text_color": "white", 8 | "error_color": "red", 9 | "warn_color": "coral", 10 | "win_color": "navy", 11 | "debug": false 12 | } -------------------------------------------------------------------------------- /src/games/de/gamestate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "1999-12-30T23:00:00.000Z", 3 | "inventory": [], 4 | "location": "start_location", 5 | "seconds": 0, 6 | "steps": 0, 7 | "hints": 0, 8 | "points": 0, 9 | "locations": { 10 | "start_location": { 11 | "name": "Startraum", 12 | "description": "Du befindest Dich in einem kahlen Raum.\nEine alte Glühbirne hängt von der Decke. Sie ist die einzige Lichtquelle im Raum.\nNach Osten gibt es einen Durchgang, vermutlich in einen ähnlich kargen Raum.", 13 | "objects": [ 14 | "tisch", 15 | "licht", 16 | "buch", 17 | "schluessel" 18 | ], 19 | "states": {}, 20 | "connections": { 21 | "osten": "room_two" 22 | } 23 | }, 24 | "room_two": { 25 | "name": "Ein zweiter Raum", 26 | "description": "Du befindest Dich in einem weiteren kargen Raum. Ein Durchgang führt nach Westen. An der gegenüberliegenden Wand steht ein großer, schmuckloser Kleiderschrank.", 27 | "connections": { 28 | "westen": "start_location" 29 | }, 30 | "objects": [ 31 | "kleiderschrank" 32 | ] 33 | 34 | }, 35 | "hidden_room": { 36 | "name": "Ein versteckter Raum", 37 | "description": "Du befindest Dich in einem kleinen, kargen Raum. Ein enger Weg führt nach Westen.", 38 | "objects": [ 39 | "kleiderschrank" 40 | ], 41 | "reversed": [ 42 | "kleiderschrank" 43 | ] 44 | 45 | } 46 | }, 47 | "objects": { 48 | "tisch": { 49 | "name": "Tisch", 50 | "description": "Dies ist ein einfacher Holztisch.", 51 | "portable": false, 52 | "error_portable": "Dieser Tisch ist etwas zu schwer, um ihn herumzutragen.", 53 | "hidden": false, 54 | "article": "der" 55 | }, 56 | "licht": { 57 | "name": "Glühbirne", 58 | "description": "Eine altmodische Glühbirne mit der Aufschrift: 60W.", 59 | "portable": false, 60 | "error_portable": "Wie viele Textadventure-Spieler benötigt man, um eine Birne auszuwechseln? Scheinbar mehr als einen.", 61 | "hidden": false, 62 | "article": "die" 63 | }, 64 | "buch": { 65 | "name": "Buch", 66 | "description": "Ein Buch. Auf dem Umschlag steht in großen, freundlichen Buchstaben: '101 things to do in adventure games.'", 67 | "portable": true, 68 | "hidden": false, 69 | "article": "das" 70 | 71 | }, 72 | "schluessel": { 73 | "name": "Schlüssel", 74 | "description": "Ein kleiner Schlüssel.", 75 | "portable": true, 76 | "hidden": false, 77 | "article": "der", 78 | "article_acc": "den" 79 | }, 80 | "kleiderschrank": { 81 | "name": "Kleiderschrank", 82 | "description": "Ein großer, weißer Kleiderschrank. Scheinbar von Ikea.", 83 | "synonyms": ["Schrank"], 84 | "portable": false, 85 | "hidden": false, 86 | "error_portable": "Du versuchst es erst gar nicht. Er sieht sehr schwer aus...", 87 | "state": "closed", 88 | "locked": true, 89 | "lock_object": "schluessel", 90 | "lock_error": "Der Kleiderschrank ist verschlossen!", 91 | "states": { 92 | "open": { 93 | "name": "offen", 94 | "description": "Der Kleiderschrank ist offen!", 95 | "error": "Er ist abgeschlossen!", 96 | "objects": [ 97 | "mantel" 98 | ], 99 | "connections": { 100 | "osten": "hidden_room" 101 | }, 102 | "reversed_connections": { 103 | "westen":"room_two" 104 | } 105 | }, 106 | "closed": { 107 | "name": "verschlossen", 108 | "description": "Der Kleiderschrank ist verschlossen." 109 | } 110 | }, 111 | "article": "der" 112 | }, 113 | "mantel": { 114 | "name": "Mantel", 115 | "description": "Ein großer, schwarzer Mantel.", 116 | "portable": true, 117 | "hidden": false, 118 | "article": "der", 119 | "article_acc": "den" 120 | } 121 | }, 122 | "events": { 123 | "start_event": { 124 | "name": "Erwacht", 125 | "description": "Willkommen zum Adventex Tutorial!\nAdventex ist ein einfaches Textadventure.\nDu kannst:\nOrte erforschen, Gegenstände aufheben (in Dein Inventar) sowie mit Gegenständen sowie der Umgebung interagieren, um Rätsel zu lösen.\nDies machst Du, indem Du einfache Sätze mit Verben und Objekten eingibst.\nFor a list of possible verbs, enter 'help verbs'.\nOften it is useful to examine objects: 'examine table'. If you want to see the description of the current location, enter 'look'.\nSome objects are portable, to pick up a book, enter 'take book'\nStart with exploring your enviromnent by visiting the other room. To do so, enter 'go east'\n", 126 | "action_move_to_location": "start_location" 127 | }, 128 | "tutorial_openclose": { 129 | "description": "Tutorial: Du befindest Dich an einem neuen Ort. Du siehst einen Kleiderschrank in der Objektliste.\nVersuche ihn zu öffnen und was immer drin ist an Dich zu nehmen.", 130 | "prereq_location": "room_two", 131 | "trigger_once": true 132 | }, 133 | "tutorial_coat": { 134 | "description": "Tutorial: Sehr gut! Du hast den Mantel aus dem Schrank genommen.\nNachdem der Mantel nicht mehr im Schrank hängt, siehst Du eine Art Geheimgang. Er führt nach Osten.\nGehe hindurch!", 135 | "prereq_inventory_items": ["mantel"], 136 | "trigger_once": true 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/games/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tutorial (DE)", 3 | "help": "Meistens hilft eine Eingabe wie . Beispiel: <öffne Tür>.\nDu kannst in eine mögliche Richtung gehen mit . Gehe ist dabei optional.\nDu kannst einen Raum oder Gegenstand mit anschauen. Schaue Dich um mit .\nEs sind auch komplexere Sätze möglich, Beispiel: <Öffne Kiste mit Brechstange>.\nTippe um alle gesammelten Gegenstände anzuzeigen.\nTippe für eine Liste der möglichen Verben.", 4 | "help_verbs": "Du kannst diese Verben benutzen:\n{0}", 5 | "error_movement": "Da kannst Du nicht hingehen!", 6 | "error_movement_direction": "Du kannst nich nach {0} von hier aus gehen!", 7 | "error_movement_thing": "Du musst nicht extra zu {0} gehen, Du bist bereits in Reichweite!", 8 | "error_get": "Du kannst {0} nicht mitnehmen!", 9 | "error_specific_get": "Du kannst {0} hier nicht mitnehmen!", 10 | "error_portable": "{0} ist zu schwer, das kannst Du nicht mitnehmen!", 11 | "error_thing": "Du trägst oder siehst {0} nicht!", 12 | "error_drop": "Du trägst {0} nicht!", 13 | "error_verb_object": "Du kannst nicht {0}{1}{2}!", 14 | "error_verb": "Du kannst hier nicht {0}!", 15 | "error": "Ich verstehe \"{0}\" nicht, sorry.", 16 | "error_generic_open_close": "Du kannst nicht einfach {0} ohne einen Gegenstand zu benennen!", 17 | "error_open_close_with": "{0} funktioniert nicht mit {1}", 18 | "error_look": "Du kannst das nicht betrachten!", 19 | "error_examine": "Was möchtest Du Dir genauer anschauen?", 20 | "error_read": "Du kannst das nicht lesen!", 21 | "this": "dies", 22 | "verb_help": "hilfe", 23 | "verb_debug": "debug", 24 | "verb_go": "gehe", 25 | "verb_take": "nimm", 26 | "verb_examine": "untersuche", 27 | "verb_look": "schaue", 28 | "verb_open": "öffne", 29 | "verb_close": "schließe", 30 | "verb_unlock": "öffne", 31 | "verb_lock": "schließe", 32 | "verb_drop": "wirf", 33 | "verb_restart": "restart", 34 | "verb_load": "load", 35 | "verb_save": "save", 36 | "verb_list": "list", 37 | "verb_read": "lese", 38 | "verb_inventory": "inventar", 39 | "with": "mit", 40 | "preposition_inside": "in", 41 | "preposition_at": "an", 42 | "preposition_around": "herum", 43 | "preposition_lock": "ab", 44 | "preposition_unlock": "auf", 45 | "state_closed": "closed", 46 | "state_open": "open", 47 | "info_enter_savegame_name": "Dateinamen eingeben und Enter drücken:", 48 | "info_game_loaded": "Game {0} geladen.", 49 | "info_game_saved": "Game {0} gespeichert.", 50 | "info_you_see": "Du siehst:", 51 | "info_inside_you_see": "In {0} siehst Du:", 52 | "info_you_see_nothing": "Du siehst hier nichts interessantes.", 53 | "info_you_took": "Du hast {0} genommen.", 54 | "info_you_dropped": "Du hast {0} fallen gelassen.", 55 | "info_you_win": "Du hast gewonnen! Congratulations.\nMoves: {0}, Points: {1}", 56 | "info_inventory_empty": "Dein Inventar ist leer.", 57 | "info_press_key": "Drücke eine Taste oder Enter", 58 | "info_success": "erledigt", 59 | "greetings": "TUTORIAL\nVersion: {0}\nTippe \"hilfe\" für Hilfe.\n", 60 | "greetings_old": " .___ __ \n _____ __| ____ __ ____ _____/ |_ ____ ___ ___\n \\__ \\ / __ |\\ \\/ _/ __ \\ / \\ ___/ __ \\\\ \\/ /\n / __ \\/ /_/ | \\ /\\ ___/| | | | \\ ___/ > < \n (____ \\____ | \\_/ \\___ |___| |__| \\___ /__/\\_ \\\n \\/ \\/ \\/ \\/ \\/ \\/\n V{0}. Type \"help\" for help.\n" 61 | } -------------------------------------------------------------------------------- /src/games/de/vocabulary.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbs": [ 3 | "öffne", 4 | "schließe", 5 | "nimm", 6 | "gib", 7 | "untersuche", 8 | "schaue", 9 | "lege", 10 | "drücke", 11 | "ziehe", 12 | "entzünde", 13 | "lösche", 14 | "benutze", 15 | "zerstöre", 16 | "löse", 17 | "stecke", 18 | "zerlege", 19 | "befestige", 20 | "zerschneide", 21 | "frage", 22 | "gehe", 23 | "gib", 24 | "wirf", 25 | "lies", 26 | "drehe", 27 | "warte", 28 | "schiebe", 29 | "leere", 30 | "fülle", 31 | "säubere", 32 | "wasche", 33 | "trinke", 34 | "esse", 35 | "reibe", 36 | "hinweis", 37 | "hilfe", 38 | "neustart", 39 | "lade", 40 | "speichere", 41 | "liste", 42 | "inventar" 43 | ], 44 | "directions": [ 45 | "norden", 46 | "osten", 47 | "süden", 48 | "westen", 49 | "hoch", 50 | "runter", 51 | "links", 52 | "rechts", 53 | "vorwärts", 54 | "rückwärts", 55 | "weg" 56 | ], 57 | "prepositions": [ 58 | "von", 59 | "nach", 60 | "mit", 61 | "herum", 62 | "zu", 63 | "an", 64 | "hindurch", 65 | "für", 66 | "an", 67 | "aus", 68 | "in", 69 | "aus", 70 | "auf", 71 | "heraus", 72 | "über", 73 | "unter", 74 | "hinter", 75 | "durch" 76 | ], 77 | "adjectives": [ 78 | "schwarz", 79 | "weiß", 80 | "grau", 81 | "blau", 82 | "gelb", 83 | "rot", 84 | "braun", 85 | "grün", 86 | "orange", 87 | "lila", 88 | "schwer", 89 | "leicht", 90 | "kaputt", 91 | "leuchtend", 92 | "hell", 93 | "dunkel", 94 | "schmutzig", 95 | "verfault", 96 | "weit", 97 | "seltsam", 98 | "weit", 99 | "stark", 100 | "fett", 101 | "groß", 102 | "riesig", 103 | "klein", 104 | "hoch", 105 | "uralt", 106 | "tief", 107 | "flach", 108 | "shallow", 109 | "bitter", 110 | "süß", 111 | "kalt", 112 | "winzig", 113 | "nass", 114 | "heiß", 115 | "bewusstlos", 116 | "eisig" 117 | ], 118 | "synonyms": { 119 | "nimm": [ 120 | "nehme" 121 | ], 122 | "gehe": [ 123 | "geh" 124 | ], 125 | "untersuche": [ 126 | "betrachte" 127 | ], 128 | "schaue": [ 129 | "schau" 130 | ], 131 | "drücke": [ 132 | "drück" 133 | ], 134 | "säubere": [ 135 | "wasche" 136 | ], 137 | "in": [ 138 | "hinein" 139 | ], 140 | "wirf": [ 141 | "werfe" 142 | ] 143 | 144 | } 145 | } -------------------------------------------------------------------------------- /src/games/escape/cell1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soster/adventex/b5e99eab70f8bc44e5011326afb15e63e1bf6b26/src/games/escape/cell1.png -------------------------------------------------------------------------------- /src/games/escape/cell2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soster/adventex/b5e99eab70f8bc44e5011326afb15e63e1bf6b26/src/games/escape/cell2.png -------------------------------------------------------------------------------- /src/games/escape/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "console": { 3 | "height": 320, 4 | "prompt": "advntx> " 5 | }, 6 | "standard_wait_eventtext": 1000, 7 | "text_color": "white", 8 | "error_color": "red", 9 | "warn_color": "coral", 10 | "win_color": "navy", 11 | "debug": false 12 | } -------------------------------------------------------------------------------- /src/games/escape/gamestate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "1999-12-30T23:00:00.000Z", 3 | "inventory": [], 4 | "location": "dungeon_cell", 5 | "seconds": 0, 6 | "steps": 0, 7 | "hints": 0, 8 | "points": 0, 9 | "locations": { 10 | "dungeon_cell": { 11 | "name": "Dungeon cell", 12 | "image": "cell1.png", 13 | "description": "A weak light shines through a small barred window just below the ceiling.\nYou see the outlines of the few things in your cell.\nThere is a door north from you.\nDirty, half rotten straw is lying on the floor.", 14 | "objects": [ 15 | "straw", 16 | "bucket", 17 | "bed", 18 | "barred_window", 19 | "cell_door", 20 | "trap_door" 21 | ], 22 | "connections": {} 23 | }, 24 | "lower_cell": { 25 | "name": "Another dungeon cell", 26 | "image": "cell2.png", 27 | "description": "It is moist and dark, but a weak light shines through the small open trap door in the ceiling.\nThe hole in the ceiling is too high for you to climb back.\nThe cell door in the north seems very massive.\n\nThe cell is deserted and has not seen a cell dweller in a while.", 28 | "objects": [ 29 | "bed", 30 | "unlocked_cell_door" 31 | ], 32 | "connections": { 33 | } 34 | }, 35 | 36 | "corridor": { 37 | "name": "Narrow corridor", 38 | "image": "narrow-passage.png", 39 | "description": "The opened cell door is to the south. The corridor stretches to the east and to the west, slightly curved.\nYou hear a faint noise from the west!", 40 | "additional_description": "There is a burning torch on a mount on the wall, it flickers and casts creepy shadows.", 41 | "objects": [ 42 | "torch", 43 | "unlocked_cell_door" 44 | ], 45 | "connections": { 46 | "east": "forward_corridor", 47 | "west": "backward_corridor" 48 | }, 49 | "reversed": ["unlocked_cell_door"] 50 | }, 51 | "forward_corridor": { 52 | "name": "Narrow and dirty corridor", 53 | "description": "Coming from the west, it stretches further to the east, leading to a staircase.\nThere is some debris on the floor, but it is too dark to see any details.", 54 | "objects": ["stone"], 55 | "connections": { 56 | "west": "corridor", 57 | "east": "staircase" 58 | } 59 | }, 60 | "backward_corridor": { 61 | "name": "Narrow, guarded corridor", 62 | "description": "Coming from the east, it stretches further to the west, slightly curved.", 63 | "objects": ["guard", "barrel"], 64 | "connections": { 65 | "east": "corridor", 66 | "west": "entrance_corridor" 67 | }, 68 | "states": { 69 | "hidden": { 70 | "description":"A narrow corridor.\nIt stretches further to the west. You are hidden behind the barrel." 71 | } 72 | } 73 | }, 74 | "staircase": { 75 | "name": "Dark staircase", 76 | "description": "North from you, the steps lead up.", 77 | "objects": [], 78 | "connections": { 79 | "west": "forward_corridor", 80 | "north": "upper_corridor", 81 | "up": "upper_corridor", 82 | "east": "latrine_room" 83 | }, 84 | "states": { 85 | "torch": { 86 | "description": "A staircase\nThe steps lead up to the north.\nIn a dark corner half way up the light of the torch reveals a small passage to the east." 87 | } 88 | } 89 | 90 | 91 | }, 92 | 93 | "latrine_room": { 94 | "name": "Latrine room", 95 | "description": "A small, smelly room, the latrine.\nIn the end of the room is a wooden board with a hole in it.", 96 | "objects": ["latrine"], 97 | "connections": { 98 | "west":"staircase" 99 | } 100 | }, 101 | 102 | "upper_corridor": { 103 | "name": "Narrow corridor, 2nd floor", 104 | "description": "South from you, the stairs lead down.\nThe corridor stretches further to the west.\nYou hear voices from the west.", 105 | "objects": [], 106 | "connections": { 107 | "south": "staircase", 108 | "down": "staircase", 109 | "west": "guard_room" 110 | } 111 | }, 112 | 113 | "guard_room": { 114 | "name": "Guard room", 115 | "description": "You manage to hide behind a ledge before anyone could see you.\nYou hear a fire cracking in a fireplace.", 116 | "objects": ["small_guard","big_guard", "cell_door", "fireplace"], 117 | "connections": { 118 | "east": "upper_corridor" 119 | } 120 | }, 121 | 122 | "entrance_corridor": { 123 | "name": "Entrance hall", 124 | "description": "The corridor from the east ends here. There is a big portal to the south.", 125 | "objects": ["portal"], 126 | "connections": { 127 | "east": "backward_corridor" 128 | } 129 | }, 130 | 131 | "outside": { 132 | "name": "Outside world", 133 | "description": "A meadow! Trees! Singing birds! You are back in the outside world.", 134 | "connections": { 135 | "north": "entrance_corridor" 136 | } 137 | } 138 | }, 139 | "objects": { 140 | "straw": { 141 | "name": "straw", 142 | "effect": "blurry-text", 143 | "description": "The straw is dirty, rotten and full with vermin. But underneath you see a trap door in the floor!", 144 | "portable": false, 145 | "error_portable": "While touching the rotten straw you feel an iron ring which belongs to a hidden trap door!\nBut it makes no sense to fill your pockets with the straw, so you leave it where it is.", 146 | "hidden": false, 147 | "article": "the", 148 | "state":"rotten", 149 | "states": { 150 | "rotten":{ 151 | "name":"half rotten" 152 | } 153 | } 154 | }, 155 | "bucket": { 156 | "name": "wooden bucket", 157 | "effect": "three-d", 158 | "description": "A wooden bucket.\nIt appears to be used for... well you can imagine. No other facilities here.", 159 | "portable": false, 160 | "error_portable": "Trust me, you DON'T want to carry THAT around! It is nearly full with... you know.", 161 | "hidden": false, 162 | "article": "the", 163 | "state":"full", 164 | "states": { 165 | "full":{ 166 | "name":"full" 167 | }, 168 | "empty": { 169 | "name":"empty", 170 | "description":"A wooden bucket.\nIt appears to be used for... well you can imagine. No other facilities here.\nIt is now empty." 171 | } 172 | } 173 | }, 174 | "bed": { 175 | "name": "bunk bed", 176 | "description": "A bunk bed. Comfortable as a wooden board. Maybe due to the fact that it IS a wooden board.\nIt is mounted on the wall.", 177 | "portable": false, 178 | "hidden": false, 179 | "article": "the" 180 | }, 181 | "trap_door": { 182 | "name": "trap door", 183 | "description": "After removing the straw, you can see a trap door in the floor.", 184 | "portable": false, 185 | "hidden": true, 186 | "state": "closed", 187 | "states": { 188 | "open": { 189 | "name": "open", 190 | "description": "The trap door is now open. The opening reveals a small corridor below your cell.", 191 | "connections": { 192 | "down":"lower_cell" 193 | } 194 | }, 195 | "closed": { 196 | "name": "closed" 197 | } 198 | }, 199 | 200 | "article": "the" 201 | }, 202 | "cell_door": { 203 | "name": "cell door", 204 | "description": "The door is very sturdy. As expected, it is locked.", 205 | "error_portable": "I can't imagine a reason to carry around a door down here!", 206 | "portable": false, 207 | "hidden": false, 208 | "article": "the" 209 | }, 210 | "unlocked_cell_door": { 211 | "name": "cell door", 212 | "description": "The door is very sturdy. Surprisingly, it is unlocked.", 213 | "error_portable": "Come on, why should you carry a cell door?", 214 | "portable": false, 215 | "hidden": false, 216 | "states": { 217 | "open":{ 218 | "name":"open", 219 | "description": "The massive cell door is open.", 220 | "connections": { 221 | "north":"corridor" 222 | }, 223 | "reversed_connections": { 224 | "south":"lower_cell" 225 | } 226 | }, 227 | "closed": { 228 | "name":"closed", 229 | "description": "The massive cell door is closed." 230 | } 231 | }, 232 | "article": "the" 233 | }, 234 | "barred_window": { 235 | "name": "barred window", 236 | "description": "It is too high for you, you can't reach it!", 237 | "error_portable": "It is too high for you, you can't reach it!\nBesides, carrying a window around? Seriously?", 238 | "portable": false, 239 | "article": "the" 240 | }, 241 | "torch": { 242 | "name": "torch", 243 | "description": "The torch is quite bright!", 244 | "portable": true, 245 | "state": "burning", 246 | "color": "brown", 247 | "states": { "burning": { 248 | "name": "burning", 249 | "effect": "fire" 250 | }}, 251 | "article": "the" 252 | }, 253 | "barrel": { 254 | "name": "barrel", 255 | "description": "A large barrel, approx. half your height. It is very heavy, but you can't look inside because it is closed.", 256 | "portable": false, 257 | "article": "the", 258 | "states": { 259 | "open": { 260 | "name": "open", 261 | "description": "A large barrel, approx. half your height. The lid is open and you see that it is filled with water.", 262 | "objects": ["water"] 263 | }, 264 | "closed": { 265 | "name": "closed" 266 | } 267 | } 268 | }, 269 | 270 | "water": { 271 | "name": "water", 272 | "description": "Clean drinking water inside a barrel.", 273 | "article": "the", 274 | "portable": false 275 | }, 276 | 277 | 278 | "guard": { 279 | "name": "guard", 280 | "description": "A big, dim looking guard. Looks like he could eat you for breakfast.", 281 | "error_portable": "He is not your type!", 282 | "portable": false, 283 | "states": { 284 | "unconscious": { 285 | "name":"unconscious", 286 | "description":"A big, dim looking guard. But he is harmless now, due to his unconciousness.\nIt might be a good idea to search him.", 287 | "objects": ["key"] 288 | } 289 | }, 290 | "article": "the", 291 | "person": "true" 292 | }, 293 | 294 | "small_guard": { 295 | "name": "small guard", 296 | "description": "A small, grim looking guard with a ferret face.", 297 | "error_portable": "He is not your type!", 298 | "portable": false, 299 | "article": "the", 300 | "person": "true" 301 | }, 302 | 303 | "big_guard": { 304 | "name": "big guard", 305 | "description": "A hefty guard with broad shoulders.", 306 | "error_portable": "He is not your type!", 307 | "portable": false, 308 | "article": "the", 309 | "person": "true" 310 | }, 311 | 312 | "stone": { 313 | "name": "stone", 314 | "description": "A stone the size of a fist.", 315 | "portable": true, 316 | "article": "the", 317 | "state": "hidden", 318 | "states": { 319 | "none": { 320 | "hidden": false 321 | }, 322 | "hidden": { 323 | "hidden": true 324 | } 325 | }, 326 | "custom_errors": { 327 | "throw": "Leave it, you throw like a girl. Besides, where do you wanna throw it?" 328 | } 329 | }, 330 | "key": { 331 | "name": "key", 332 | "description": "A large, slightly rusty key.", 333 | "portable": true, 334 | "hidden": true, 335 | "article": "the" 336 | }, 337 | "latrine": { 338 | "name": "latrine", 339 | "description": "Ugh, the cesspool of the latrine is not very deep but very full.\nMaybe you can find something in there?" 340 | }, 341 | 342 | "fireplace": { 343 | "name": "fireplace", 344 | "description": "An open fireplace, the fire is cracking and radiates a cosy warmth." 345 | }, 346 | 347 | "ring": { 348 | "name": "ring", 349 | "description": "A golden ring.\nIt looks very valuable!", 350 | "state":"dirty", 351 | "article": "the", 352 | "states": { 353 | "dirty": { 354 | "name":"dirty", 355 | "description":"A ring.\nIt is very dirty and should be cleaned." 356 | } 357 | } 358 | 359 | }, 360 | 361 | "portal": { 362 | "name": "portal", 363 | "description": "A large portal with two door leafs. The left one has a large keyhole. The doors are locked.", 364 | "portable": true, 365 | "article": "the", 366 | "locked": true, 367 | "lock_object": "key", 368 | "lock_error": "The portal is locked!", 369 | "states": { 370 | "open": { 371 | "description": "You open the left door leaf with the key.", 372 | "connections": { 373 | "south":"outside" 374 | }, 375 | "reversed_connections": { 376 | "north":"entrance_corridor" 377 | } 378 | }, 379 | "closed": { 380 | "description": "Both door leafs are closed." 381 | } 382 | } 383 | }, 384 | 385 | "merchant": { 386 | "name": "merchant woman", 387 | "description": "An old woman with a large backpack.\nShe appears to be a merchant.", 388 | "portable": false, 389 | "person": true 390 | } 391 | }, 392 | "events": { 393 | "start_event": { 394 | "name": "Awake", 395 | "description": "It begins as soon as you wake up, with a searing pain piercing your temples and spreading across your head.\nThen comes the sickness, the dry mouth and the sudden panic -\nall accompanied alongside that phrase, \"never again\".\n\nAs soon as you open your eyes you realize: This is no hangover.\nYou are not in your bedroom.\nYou have lost your memories of yesterday, the last thing you remember is...\nwell, you are not sure WHAT you remember.", 396 | "action_move_to_location": "dungeon_cell" 397 | }, 398 | 399 | "empty_bucket": { 400 | "description": "You empty the bucket in your cell.\nNot very wise from you!\nThe floor is now covered with what was in the bucket before.", 401 | "prereq_verb": "empty", 402 | "prereq_used_items":["bucket|full"], 403 | "action_set_state_items":["bucket|empty"] 404 | }, 405 | 406 | "open_cell_door": { 407 | "description": "You try to open the cell door, but it is locked.\nYou hear someone shout from the other side of the door:\n- \"Don't you dare breaking out!\"", 408 | "prereq_verb": "open", 409 | "prereq_used_items": ["cell_door"], 410 | "prereq_location": "dungeon_cell" 411 | }, 412 | 413 | "open_barred_window": { 414 | "description": "The window is too high, you can't reach it!", 415 | "prereq_verb": "open", 416 | "prereq_used_items": ["barred_window"], 417 | "prereq_location": "dungeon_cell" 418 | }, 419 | 420 | "empty_barrel": { 421 | "description": "The barrel is too heavy for that!", 422 | "prereq_verb": "empty", 423 | "prereq_used_items":["barrel"] 424 | }, 425 | 426 | "get_torch": { 427 | "description": "The torch makes it easier to find your way. The dungeon down here is mostly dark.\nBut you will be seen immediately with the bright torch in your hands.", 428 | "prereq_verb": "take", 429 | "prereq_used_items": ["torch"], 430 | "not": { 431 | "prereq_inventory_items": ["torch"] 432 | }, 433 | "action_continue": true 434 | }, 435 | 436 | "drop_torch": { 437 | "description": "It is darker around you.", 438 | "prereq_verb": "drop", 439 | "prereq_used_items": ["torch"], 440 | "prereq_inventory_items": ["torch"], 441 | "action_continue": true 442 | }, 443 | 444 | "stone_visible_with_torch": { 445 | "description": "The light of the torch reveals a stone lying on the ground.", 446 | "prereq_inventory_items": ["torch"], 447 | "prereq_location": "forward_corridor", 448 | "prereq_location_items": ["stone|hidden"], 449 | "action_set_state_items": ["stone|none"] 450 | }, 451 | 452 | "guard_trigger": { 453 | "name": "", 454 | "description": "You notice a big guard standing in a corner of the room. He doesn't look into your direction, yet.", 455 | "prereq_location_items": ["guard|none"], 456 | "trigger_once": true, 457 | "only_after": true 458 | }, 459 | 460 | "hide_barrel": { 461 | "description": "You duck and hide behind the barrel. The guard seems to have not yet noticed you.", 462 | "prereq_location": "backward_corridor", 463 | "prereq_verb": "hide", 464 | "prereq_preposition": "behind", 465 | "prereq_used_items": ["barrel"], 466 | "action_disable_events": ["guard_trigger","throw_stone_at_guard"], 467 | "action_set_state_locations": ["backward_corridor|hidden"] 468 | }, 469 | 470 | "unhide_barrel": { 471 | "prereq_verb": "go", 472 | "prereq_location_state": "backward_corridor|hidden", 473 | "only_after": true, 474 | "action_untrigger_events": ["hide_barrel", "guard_trigger"], 475 | "action_enable_events": ["guard_trigger","throw_stone_at_guard"], 476 | "action_set_state_locations": ["backward_corridor|none"] 477 | }, 478 | 479 | "clean_ring": { 480 | "description": "You clean the ring. It looks like it is pure gold!", 481 | "prereq_verb": "clean", 482 | "prereq_used_items": ["ring|dirty","water"], 483 | "action_set_state_items": ["ring|none"] 484 | }, 485 | 486 | "drink_barrel": { 487 | "description": "You drink some water from the barrel. Refreshing!", 488 | "prereq_verb": "drink", 489 | "prereq_used_items": ["barrel|open"] 490 | }, 491 | 492 | "throw_stone_at_guard_hidden": { 493 | "description": "You throw the stone at the guard. You hit him on his head and he sinks to the ground.", 494 | "prereq_location": "backward_corridor", 495 | "prereq_verb": "throw", 496 | "prereq_used_items": ["stone","guard"], 497 | "prereq_inventory_items": ["stone"], 498 | "prereq_triggered_events":["hide_barrel"], 499 | "action_disable_events":["arest", "arest_torch"], 500 | "action_untrigger_events":["hide_barrel"], 501 | "action_remove_items": ["stone"], 502 | "action_add_items": ["backward_corridor:stone"], 503 | "action_set_state_items": ["guard|unconscious"], 504 | "action_points": 10 505 | 506 | }, 507 | 508 | "throw_stone_at_guard": { 509 | "description": "You try to throw the stone. But the guard is quicker.", 510 | "prereq_location": "backward_corridor", 511 | "prereq_verb": "throw", 512 | "prereq_used_items": ["stone","guard|none"], 513 | "prereq_inventory_items": ["stone"], 514 | "action_trigger_event": "arest", 515 | "action_remove_items": ["stone"], 516 | "action_add_items": ["backward_corridor:stone"] 517 | }, 518 | 519 | "search_guard": { 520 | "description": "The guard has a key chained on his belt.", 521 | "prereq_verb": "search", 522 | "prereq_used_items": ["guard|unconscious"] 523 | }, 524 | 525 | "arest": { 526 | "name": "The guard arests you!", 527 | "description": "The guard points his sword into your direction. \"You! Don't move!\" he barks.\n\nYou stand still. He grabs your arm with his free hand and turns you around in a swift movement.\nAfterwards he hits you with his sword handle on the back of your head.\n\nAgain, it gets dark around you when you loose consciousness...", 528 | "prereq_location": "backward_corridor", 529 | "prereq_triggered_events": ["guard_trigger"], 530 | "prereq_triggered_event_step_offset": 3, 531 | "prereq_location_items": ["guard|none"], 532 | "action_move_to_location": "dungeon_cell", 533 | "action_untrigger_events":["guard_trigger"] 534 | }, 535 | 536 | "arest_torch": { 537 | "name": "", 538 | "description": "The guard looks at your burning torch, baffled.", 539 | "prereq_location": "backward_corridor", 540 | "prereq_inventory_items": ["torch|burning"], 541 | "action_trigger_event": "arest" 542 | }, 543 | 544 | "two_guards_trigger_eavedrop": { 545 | "name": "", 546 | "description": "After a quick glance around the ledge you hear two guards talking to each other.\nYou can eavedrop some of their talk:\n- \"..But where did you loose it?\"\n- \"I don't know, I still had the f*'$! ring this morning!\n Must have lost it somewhere..\"", 547 | "prereq_location": "guard_room", 548 | "trigger_once": true, 549 | "only_after": true, 550 | "action_trigger_event": "two_guards_trigger" 551 | }, 552 | 553 | "two_guards_trigger": { 554 | "prereq_location": "guard_room", 555 | "trigger_once": true, 556 | "only_after": true 557 | }, 558 | 559 | "two_guards_untrigger": { 560 | "prereq_location": "upper_corridor", 561 | "action_untrigger_events":["two_guards_trigger"], 562 | "action_continue": true 563 | }, 564 | 565 | "two_guards_arest": { 566 | "name": "", 567 | "description": "Both guards turn around simultanously and attack you.\nIt all happens very quickly, you have no chance against them.\nAfter getting hit on your head, you loose conscioiusness (again)...", 568 | "prereq_location": "guard_room", 569 | "only_before": true, 570 | "prereq_triggered_events": ["two_guards_trigger"], 571 | "prereq_triggered_event_step_offset": 3, 572 | "action_untrigger_events":["two_guards_trigger"], 573 | "action_move_to_location": "dungeon_cell" 574 | }, 575 | 576 | "staircase_torch": { 577 | "description": "The light of the torch reveals a small passage to the east.", 578 | "prereq_location": "staircase", 579 | "prereq_inventory_items": ["torch|burning"], 580 | "prereq_location_state": "staircase|none", 581 | "action_set_state_locations": ["staircase|torch"] 582 | }, 583 | 584 | "staircase_no_torch": { 585 | "prereq_location": "staircase", 586 | "not": { 587 | "prereq_inventory_items": ["torch"] 588 | }, 589 | "prereq_location_state": "staircase|torch", 590 | "action_set_state_locations": ["staircase|none"] 591 | }, 592 | 593 | "search_latrine": { 594 | "description": "Searching through fecies is no fun.\nHowever, your fingers feel something small and solid.\nIt is a (albeit dirty) ring!\nYou manage to fetch it from the latrine.", 595 | "prereq_verb": "search", 596 | "prereq_location": "latrine_room", 597 | "prereq_used_items": ["latrine"], 598 | "action_add_items": ["ring|dirty"], 599 | "action_points": 10 600 | }, 601 | 602 | "use_latrine": { 603 | "description": "You feel relieved.", 604 | "prereq_verb": "use", 605 | "prereq_location": "latrine_room", 606 | "prereq_used_items": ["latrine"] 607 | }, 608 | 609 | "arest_go_west": { 610 | "name": "", 611 | "prereq_verb": "go", 612 | "prereq_location": "backward_corridor", 613 | "description": "You try to sneak away...", 614 | "prereq_location_items": ["guard|none"], 615 | "only_before": true, 616 | "action_trigger_event": "arest" 617 | }, 618 | 619 | "open_portal": { 620 | "description": "Directly after opening the portal an old woman comes through the opening.\nShe does not wear the clothes of a guard but a large backpack, she appears to be a traveling merchant.\nShe says:\n- \"Who are you? You look like a prisoner.\n do you know a reason why I should not alarm the guards right away?\"", 621 | "prereq_location_items": ["portal|open"], 622 | "trigger_once": true, 623 | "action_add_items":["location:merchant"], 624 | "action_points": 5 625 | }, 626 | 627 | 628 | "merchant_ring": { 629 | "description": "You give her the ring you found.\nShe says:\n- \"Hmm. Ok boy, I keep my mouth shut. Now leave before I change my mind!\"\nShe quickly disappears into the building.", 630 | "prereq_location_items": ["merchant"], 631 | "prereq_verb": "give", 632 | "prereq_used_items": ["ring|none"], 633 | "action_remove_items": ["entrance_corridor:merchant","inventory:ring"] 634 | }, 635 | 636 | "merchant_ring_dirty": { 637 | "description": "You give her the dirty ring you found.\nShe says:\n- \"Oh boy, what will I do with this dirty, useless crap?\"", 638 | "prereq_location_items": ["merchant"], 639 | "prereq_verb": "give", 640 | "prereq_used_items": ["ring|dirty"] 641 | }, 642 | 643 | "merchant_alarm": { 644 | "prereq_location_items": ["merchant"], 645 | "prereq_verb": "go", 646 | "description": "The old woman blocks your way with her large backpack.\nShe screams for help and before you manage to overwhelm her, two guards are running towards you.\nThey hit you on your head.\nThe last thing you think about before it gets dark around you is \"not again!\"", 647 | "action_move_to_location": "dungeon_cell", 648 | "only_before": true 649 | }, 650 | 651 | "win_event": { 652 | "description": "You managed to find out of the dungeon!", 653 | "prereq_location": "outside", 654 | "action_points": 10 655 | 656 | }, 657 | "demo": { 658 | "name": "Demo event to demonstrate the event system.", 659 | "comment": "This comment is for describing this object, it is not displayed in the game.", 660 | "description": "This is the demo event! Just for testing purposes...", 661 | "prereq_verb": "switch", 662 | "prereq_location": "", 663 | "prereq_used_items": [], 664 | "not": { 665 | "prereq_location_items": [], 666 | "prereq_inventory_items": [], 667 | "prereq_preposition": "on", 668 | "prereq_min_steps": 0, 669 | "prereq_max_steps": 0 670 | }, 671 | "or": { 672 | "prereq_triggered_event_step_offset": 0, 673 | "prereq_triggered_events": [], 674 | "prereq_visited_locations": [] 675 | }, 676 | 677 | "prereq_location_state": "", 678 | "only_before": false, 679 | "only_after": false, 680 | "action_add_items": [""], 681 | "action_remove_items": [""], 682 | "action_set_state_locations": [], 683 | "action_move_to_location": "", 684 | "action_new_connections": "", 685 | "action_disable_events": [], 686 | "action_untrigger_events": [], 687 | "action_trigger_event": "", 688 | "action_enable_events": [] 689 | } 690 | } 691 | } -------------------------------------------------------------------------------- /src/games/escape/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Escape Game", 3 | "help": "Most of the time, typing something like works. Example: .\nYou can go to a possible direction typing .\nYou can examine the room or any object typing . Look around with .\nMore complex sentences are possible, example: .\nType to show all your collected items.\nType to get a list of possible verbs.", 4 | "help_verbs": "You can use these verbs:\n{0}", 5 | "error_movement": "You can't go there!", 6 | "error_movement_direction": "You can't go {0} from here!", 7 | "error_movement_thing": "You don't need to go to {0}, you are already in reach!", 8 | "error_get": "You can't get {0}!", 9 | "error_specific_get": "You can't take {0}, here!", 10 | "error_portable": "{0} is too heavy, you can't pick that up!", 11 | "error_thing": "You don't carry or see {0}!", 12 | "error_drop": "You don't carry {0}!", 13 | "error_verb_object": "You can't {0}{1}{2}!", 14 | "error_verb": "You can't {0} here!", 15 | "error": "I don't understand \"{0}\", sorry.", 16 | "error_generic_open_close": "You can't just {0} without naming an object!", 17 | "error_open_close_with": "{0} doesn't work on {1}", 18 | "error_look": "You can't look at that!", 19 | "error_examine": "What do you want to examine?", 20 | "this": "this", 21 | "verb_help": "help", 22 | "verb_debug": "debug", 23 | "verb_go": "go", 24 | "verb_take": "take", 25 | "verb_examine": "examine", 26 | "verb_look": "look", 27 | "verb_open": "open", 28 | "verb_unlock": "unlock", 29 | "verb_lock": "lock", 30 | "verb_close": "close", 31 | "verb_drop": "drop", 32 | "verb_restart": "restart", 33 | "verb_load": "load", 34 | "verb_save": "save", 35 | "verb_list": "list", 36 | "verb_read": "read", 37 | "verb_inventory": "inventory", 38 | "with": "with", 39 | "preposition_inside": "inside", 40 | "preposition_at": "at", 41 | "preposition_around": "around", 42 | "state_closed": "closed", 43 | "state_open": "open", 44 | "info_enter_savegame_name": "Enter savegame name and hit enter:", 45 | "info_game_loaded": "Game {0} loaded.", 46 | "info_game_saved": "Game {0} saved.", 47 | "info_you_see": "You see:", 48 | "info_inside_you_see": "Inside {0} you see:", 49 | "info_you_see_nothing": "You see nothing of interest here.", 50 | "info_you_took": "You took {0}.", 51 | "info_you_dropped": "You dropped {0}.", 52 | "info_you_win": "You have won! Congratulations.\nMoves: {0}, Points: {1}", 53 | "info_inventory_empty": "Your inventory is empty.", 54 | "info_press_key": "press any key or tap enter button", 55 | "info_success": "done", 56 | "greetings": "Escape Game\nVersion: {0}\nType \"help\" for help.", 57 | "greetings_old": " .___ __ \n _____ __| ____ __ ____ _____/ |_ ____ ___ ___\n \\__ \\ / __ |\\ \\/ _/ __ \\ / \\ ___/ __ \\\\ \\/ /\n / __ \\/ /_/ | \\ /\\ ___/| | | | \\ ___/ > < \n (____ \\____ | \\_/ \\___ |___| |__| \\___ /__/\\_ \\\n \\/ \\/ \\/ \\/ \\/ \\/\n V{0}. Type \"help\" for help.\n" 58 | } -------------------------------------------------------------------------------- /src/games/escape/narrow-passage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soster/adventex/b5e99eab70f8bc44e5011326afb15e63e1bf6b26/src/games/escape/narrow-passage.png -------------------------------------------------------------------------------- /src/games/escape/stone-texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soster/adventex/b5e99eab70f8bc44e5011326afb15e63e1bf6b26/src/games/escape/stone-texture.png -------------------------------------------------------------------------------- /src/games/escape/vocabulary.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbs": [ 3 | "go", 4 | "walk", 5 | "talk", 6 | "give", 7 | "take", 8 | "get", 9 | "drop", 10 | "use", 11 | "climb", 12 | "open", 13 | "close", 14 | "push", 15 | "press", 16 | "pull", 17 | "unlock", 18 | "lock", 19 | "catch", 20 | "switch", 21 | "examine", 22 | "look", 23 | "fetch", 24 | "pick", 25 | "read", 26 | "run", 27 | "search", 28 | "conjure", 29 | "bribe", 30 | "kindle", 31 | "extinguish", 32 | "light", 33 | "hide", 34 | "emerge", 35 | "throw", 36 | "empty", 37 | "fill", 38 | "clean", 39 | "wash", 40 | "drink", 41 | "eat", 42 | "hint", 43 | "help", 44 | "restart", 45 | "load", 46 | "save", 47 | "list", 48 | "inventory" 49 | ], 50 | "directions": [ 51 | "north", 52 | "east", 53 | "south", 54 | "west", 55 | "up", 56 | "down", 57 | "left", 58 | "right", 59 | "forward", 60 | "backward", 61 | "away" 62 | ], 63 | "prepositions": [ 64 | "from", 65 | "to", 66 | "with", 67 | "around", 68 | "by", 69 | "across", 70 | "for", 71 | "on", 72 | "off", 73 | "at", 74 | "in", 75 | "out", 76 | "into", 77 | "onto", 78 | "over", 79 | "under", 80 | "behind", 81 | "through", 82 | "inside", 83 | "into" 84 | ], 85 | "adjectives": [ 86 | "black", 87 | "white", 88 | "gray", 89 | "grey", 90 | "blue", 91 | "yellow", 92 | "red", 93 | "brown", 94 | "green", 95 | "orange", 96 | "purple", 97 | "heavy", 98 | "light", 99 | "defect", 100 | "shiny", 101 | "bright", 102 | "dark", 103 | "dirty", 104 | "rotten", 105 | "fancy", 106 | "vast", 107 | "odd", 108 | "broad", 109 | "strong", 110 | "fat", 111 | "big", 112 | "huge", 113 | "small", 114 | "tall", 115 | "ancient", 116 | "deep", 117 | "flat", 118 | "shallow", 119 | "bitter", 120 | "sweet", 121 | "delicious", 122 | "tiny", 123 | "wet", 124 | "hot", 125 | "unconscious", 126 | "cold" 127 | ], 128 | "synonyms": { 129 | "take": [ 130 | "get", 131 | "pick" 132 | ], 133 | "go": [ 134 | "walk", 135 | "drive", 136 | "climb" 137 | ], 138 | "look": [ 139 | "watch" 140 | ], 141 | "push": [ 142 | "press" 143 | ], 144 | "extinguish": [ 145 | "delete" 146 | ], 147 | "clean": [ 148 | "wash" 149 | ], 150 | "into": [ 151 | "inside" 152 | ] 153 | } 154 | } -------------------------------------------------------------------------------- /src/games/games.json: -------------------------------------------------------------------------------- 1 | { 2 | "default":"escape", 3 | 4 | "tutorial": { 5 | "name": "Tutorial", 6 | "path": "tutorial" 7 | }, 8 | 9 | "deutsch": { 10 | "name": "Tutorial auf Deutsch", 11 | "path": "de" 12 | }, 13 | 14 | "escape": { 15 | "name": "Escape Game", 16 | "path": "escape" 17 | } 18 | 19 | 20 | } -------------------------------------------------------------------------------- /src/games/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Escape Game", 3 | "help": "Most of the time, typing something like works. Example: .\nYou can go to a possible direction typing .\nYou can examine the room or any object typing . Look around with .\nMore complex sentences are possible, example: .\nType to show all your collected items.\nType to get a list of possible verbs.", 4 | "help_verbs": "You can use these verbs:\n{0}", 5 | "error_movement": "You can't go there!", 6 | "error_movement_direction": "You can't go {0} from here!", 7 | "error_movement_thing": "You don't need to go to {0}, you are already in reach!", 8 | "error_get": "You can't get {0}!", 9 | "error_specific_get": "You can't take {0}, here!", 10 | "error_portable": "{0} is too heavy, you can't pick that up!", 11 | "error_thing": "You don't carry or see {0}!", 12 | "error_drop": "You don't carry {0}!", 13 | "error_verb_object": "You can't {0}{1}{2}!", 14 | "error_verb": "You can't {0} here!", 15 | "error": "I don't understand \"{0}\", sorry.", 16 | "error_generic_open_close": "You can't just {0} without naming an object!", 17 | "error_open_close_with": "{0} doesn't work on {1}", 18 | "error_look": "You can't look at that!", 19 | "error_examine": "What do you want to examine?", 20 | "this": "this", 21 | "verb_help": "help", 22 | "verb_debug": "debug", 23 | "verb_go": "go", 24 | "verb_take": "take", 25 | "verb_examine": "examine", 26 | "verb_look": "look", 27 | "verb_open": "open", 28 | "verb_unlock": "unlock", 29 | "verb_lock": "lock", 30 | "verb_close": "close", 31 | "verb_drop": "drop", 32 | "verb_restart": "restart", 33 | "verb_load": "load", 34 | "verb_save": "save", 35 | "verb_list": "list", 36 | "verb_read": "read", 37 | "verb_inventory": "inventory", 38 | "with": "with", 39 | "preposition_inside": "inside", 40 | "preposition_at": "at", 41 | "preposition_around": "around", 42 | "state_closed": "closed", 43 | "state_open": "open", 44 | "info_enter_savegame_name": "Enter savegame name and hit enter:", 45 | "info_game_loaded": "Game {0} loaded.", 46 | "info_game_saved": "Game {0} saved.", 47 | "info_you_see": "You see:", 48 | "info_inside_you_see": "Inside {0} you see:", 49 | "info_you_see_nothing": "You see nothing of interest here.", 50 | "info_you_took": "You took {0}.", 51 | "info_you_dropped": "You dropped {0}.", 52 | "info_you_win": "You have won! Congratulations.\nMoves: {0}, Points: {1}", 53 | "info_inventory_empty": "Your inventory is empty.", 54 | "info_press_key": "press any key or tap enter button", 55 | "info_success": "done", 56 | "greetings": "Escape Game\nVersion: {0}\nType \"help\" for help.", 57 | "greetings_old": " .___ __ \n _____ __| ____ __ ____ _____/ |_ ____ ___ ___\n \\__ \\ / __ |\\ \\/ _/ __ \\ / \\ ___/ __ \\\\ \\/ /\n / __ \\/ /_/ | \\ /\\ ___/| | | | \\ ___/ > < \n (____ \\____ | \\_/ \\___ |___| |__| \\___ /__/\\_ \\\n \\/ \\/ \\/ \\/ \\/ \\/\n V{0}. Type \"help\" for help.\n" 58 | } -------------------------------------------------------------------------------- /src/games/tutorial/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "console": { 3 | "height": 320, 4 | "prompt": "advntx> " 5 | }, 6 | "standard_wait_eventtext": 1000, 7 | "text_color": "white", 8 | "error_color": "red", 9 | "warn_color": "coral", 10 | "win_color": "navy", 11 | "debug": false 12 | } -------------------------------------------------------------------------------- /src/games/tutorial/gamestate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "1999-12-30T23:00:00.000Z", 3 | "inventory": [], 4 | "location": "start_location", 5 | "seconds": 0, 6 | "steps": 0, 7 | "hints": 0, 8 | "points": 0, 9 | "locations": { 10 | "start_location": { 11 | "name": "Starting room", 12 | "description": "You are in an unadorned, rectangular room without windows.\nA simple light bulb is hanging from the ceiling, the only source of light, casting harsh shadows.\nThere is a passage to the east, apparently to another room much like this.", 13 | "objects": [ 14 | "table", 15 | "light", 16 | "book", 17 | "key" 18 | ], 19 | "states": {}, 20 | "connections": { 21 | "east": "room_two" 22 | } 23 | }, 24 | "room_two": { 25 | "name": "Another room", 26 | "description": "You are in another unadorned, rectangular room. There is a passage to the west. In the opposite direction you see a large wardrobe.", 27 | "connections": { 28 | "west": "start_location" 29 | }, 30 | "objects": [ 31 | "wardrobe" 32 | ] 33 | 34 | }, 35 | "hidden_room": { 36 | "name": "A hidden room", 37 | "description": "You are in another unadorned, rectangular room. There is a passage to the west.", 38 | "objects": [ 39 | "wardrobe" 40 | ], 41 | "reversed": [ 42 | "wardrobe" 43 | ] 44 | 45 | } 46 | }, 47 | "objects": { 48 | "table": { 49 | "name": "table", 50 | "description": "This is a simple, wooden table.", 51 | "portable": false, 52 | "error_portable": "Guess what: The table is to heavy to carry around.", 53 | "hidden": false, 54 | "article": "the" 55 | }, 56 | "light": { 57 | "name": "light bulb", 58 | "description": "A simple light bulb. It reads: 60W.", 59 | "portable": false, 60 | "error_portable": "How many players of interactive fiction does it take to unscrew this light bulb? Apparently, more than one.", 61 | "hidden": false, 62 | "article": "the" 63 | }, 64 | "book": { 65 | "name": "book", 66 | "description": "A book, the title reads: '101 things to do in adventure games.'", 67 | "portable": true, 68 | "hidden": false, 69 | "article": "the" 70 | }, 71 | "key": { 72 | "name": "key", 73 | "description": "A small key.", 74 | "portable": true, 75 | "hidden": false, 76 | "article": "the" 77 | }, 78 | "wardrobe": { 79 | "name": "wardrobe", 80 | "description": "A large, white wardrobe. It looks like an Ikea model.", 81 | "portable": false, 82 | "hidden": false, 83 | "error_portable": "You try to pick up the wardrobe... Well... no.", 84 | "state": "closed", 85 | "locked": true, 86 | "lock_object": "key", 87 | "lock_error": "The wardrobe is locked!", 88 | "states": { 89 | "open": { 90 | "name": "open", 91 | "description": "The wardrobe is open.", 92 | "error": "It is locked!", 93 | "objects": [ 94 | "coat" 95 | ], 96 | "connections": { 97 | "east": "hidden_room" 98 | }, 99 | "reversed_connections": { 100 | "west":"room_two" 101 | } 102 | }, 103 | "closed": { 104 | "name": "closed", 105 | "description": "The wardrobe is closed." 106 | } 107 | }, 108 | "article": "the" 109 | }, 110 | "coat": { 111 | "name": "coat", 112 | "description": "A large, black coat.", 113 | "portable": true, 114 | "hidden": false 115 | } 116 | }, 117 | "events": { 118 | "start_event": { 119 | "name": "Awake", 120 | "description": "Welcome to the adventex tutorial!\nAdventex is a simple text adventure (also called 'interactive fiction').\nYou can:\nExplore different locations, pick up items (into your 'inventory') and interact with objects and the environment to solve puzzles.\nYou interact with your environment by entering simple sentences, starting with a verb.\nFor a list of possible verbs, enter 'help verbs'.\nOften it is useful to examine objects: 'examine table'. If you want to see the description of the current location, enter 'look'.\nSome objects are portable, to pick up a book, enter 'take book'\nStart with exploring your enviromnent by visiting the other room. To do so, enter 'go east'\n", 121 | "action_move_to_location": "start_location" 122 | }, 123 | "tutorial_openclose": { 124 | "description": "Tutorial: You are in a new location. You see a wardrobe in the list of objects.\nTry to open the wardrobe and take whatever is inside.", 125 | "prereq_location": "room_two", 126 | "trigger_once": true 127 | }, 128 | "tutorial_coat": { 129 | "description": "Tutorial: Very good! You successfully took the coat out of the wardrobe.\nAfter taking the coat, you recognise a hidden passage in the wardrobe. The passage leads east.\nGo through the hidden passage!", 130 | "prereq_inventory_items": ["coat"], 131 | "trigger_once": true 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /src/games/tutorial/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tutorial", 3 | "help": "Most of the time, typing something like works. Example: .\nYou can go to a possible direction typing .\nYou can examine the room or any object typing . Look around with .\nMore complex sentences are possible, example: .\nType to show all your collected items.\nType to get a list of possible verbs.", 4 | "help_verbs": "You can use these verbs:\n{0}", 5 | "error_movement": "You can't go there!", 6 | "error_movement_direction": "You can't go {0} from here!", 7 | "error_movement_thing": "You don't need to go to {0}, you are already in reach!", 8 | "error_get": "You can't get {0}!", 9 | "error_specific_get": "You can't take {0}, here!", 10 | "error_portable": "{0} is too heavy, you can't pick that up!", 11 | "error_thing": "You don't carry or see {0}!", 12 | "error_drop": "You don't carry {0}!", 13 | "error_verb_object": "You can't {0}{1}{2}!", 14 | "error_verb": "You can't {0} here!", 15 | "error": "I don't understand \"{0}\", sorry.", 16 | "error_generic_open_close": "You can't just {0} without naming an object!", 17 | "error_open_close_with": "{0} doesn't work on {1}", 18 | "error_look": "You can't look at that!", 19 | "error_examine": "What do you want to examine?", 20 | "error_read": "You can't read that!", 21 | "this": "this", 22 | "verb_help": "help", 23 | "verb_debug": "debug", 24 | "verb_go": "go", 25 | "verb_take": "take", 26 | "verb_examine": "examine", 27 | "verb_look": "look", 28 | "verb_open": "open", 29 | "verb_unlock": "unlock", 30 | "verb_lock": "lock", 31 | "verb_close": "close", 32 | "verb_drop": "drop", 33 | "verb_restart": "restart", 34 | "verb_load": "load", 35 | "verb_save": "save", 36 | "verb_list": "list", 37 | "verb_read": "read", 38 | "verb_inventory": "inventory", 39 | "with": "with", 40 | "preposition_inside": "inside", 41 | "preposition_at": "at", 42 | "preposition_around": "around", 43 | "state_closed": "closed", 44 | "state_open": "open", 45 | "info_enter_savegame_name": "Enter savegame name and hit enter:", 46 | "info_game_loaded": "Game {0} loaded.", 47 | "info_game_saved": "Game {0} saved.", 48 | "info_you_see": "You see:", 49 | "info_inside_you_see": "Inside {0} you see:", 50 | "info_you_see_nothing": "You see nothing of interest here.", 51 | "info_you_took": "You took {0}.", 52 | "info_you_dropped": "You dropped {0}.", 53 | "info_you_win": "You have won! Congratulations.\nMoves: {0}, Points: {1}", 54 | "info_inventory_empty": "Your inventory is empty.", 55 | "info_press_key": "press any key or tap enter button", 56 | "info_success": "done", 57 | "greetings": "TUTORIAL\nVersion: {0}\nType \"help\" for help.\n", 58 | "greetings_old": " .___ __ \n _____ __| ____ __ ____ _____/ |_ ____ ___ ___\n \\__ \\ / __ |\\ \\/ _/ __ \\ / \\ ___/ __ \\\\ \\/ /\n / __ \\/ /_/ | \\ /\\ ___/| | | | \\ ___/ > < \n (____ \\____ | \\_/ \\___ |___| |__| \\___ /__/\\_ \\\n \\/ \\/ \\/ \\/ \\/ \\/\n V{0}. Type \"help\" for help.\n" 59 | } -------------------------------------------------------------------------------- /src/games/tutorial/vocabulary.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbs": [ 3 | "go", 4 | "walk", 5 | "talk", 6 | "give", 7 | "take", 8 | "get", 9 | "drop", 10 | "use", 11 | "climb", 12 | "open", 13 | "close", 14 | "push", 15 | "press", 16 | "pull", 17 | "unlock", 18 | "lock", 19 | "catch", 20 | "switch", 21 | "examine", 22 | "look", 23 | "fetch", 24 | "pick", 25 | "read", 26 | "run", 27 | "search", 28 | "conjure", 29 | "bribe", 30 | "kindle", 31 | "extinguish", 32 | "light", 33 | "hide", 34 | "emerge", 35 | "throw", 36 | "empty", 37 | "fill", 38 | "clean", 39 | "wash", 40 | "drink", 41 | "eat", 42 | "hint", 43 | "help", 44 | "restart", 45 | "load", 46 | "save", 47 | "list", 48 | "inventory" 49 | ], 50 | "directions": [ 51 | "north", 52 | "east", 53 | "south", 54 | "west", 55 | "up", 56 | "down", 57 | "left", 58 | "right", 59 | "forward", 60 | "backward", 61 | "away" 62 | ], 63 | "prepositions": [ 64 | "from", 65 | "to", 66 | "with", 67 | "around", 68 | "by", 69 | "across", 70 | "for", 71 | "on", 72 | "off", 73 | "at", 74 | "in", 75 | "out", 76 | "into", 77 | "onto", 78 | "over", 79 | "under", 80 | "behind", 81 | "through", 82 | "inside", 83 | "into" 84 | ], 85 | "adjectives": [ 86 | "black", 87 | "white", 88 | "gray", 89 | "grey", 90 | "blue", 91 | "yellow", 92 | "red", 93 | "brown", 94 | "green", 95 | "orange", 96 | "purple", 97 | "heavy", 98 | "light", 99 | "defect", 100 | "shiny", 101 | "bright", 102 | "dark", 103 | "dirty", 104 | "rotten", 105 | "fancy", 106 | "vast", 107 | "odd", 108 | "broad", 109 | "strong", 110 | "fat", 111 | "big", 112 | "huge", 113 | "small", 114 | "tall", 115 | "ancient", 116 | "deep", 117 | "flat", 118 | "shallow", 119 | "bitter", 120 | "sweet", 121 | "delicious", 122 | "tiny", 123 | "wet", 124 | "hot", 125 | "unconscious", 126 | "cold" 127 | ], 128 | "synonyms": { 129 | "take": [ 130 | "get", 131 | "pick" 132 | ], 133 | "go": [ 134 | "walk", 135 | "drive", 136 | "climb" 137 | ], 138 | "look": [ 139 | "watch" 140 | ], 141 | "push": [ 142 | "press" 143 | ], 144 | "extinguish": [ 145 | "delete" 146 | ], 147 | "clean": [ 148 | "wash" 149 | ], 150 | "into": [ 151 | "inside" 152 | ] 153 | } 154 | } -------------------------------------------------------------------------------- /src/img/adventex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soster/adventex/b5e99eab70f8bc44e5011326afb15e63e1bf6b26/src/img/adventex.png -------------------------------------------------------------------------------- /src/js/const.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global constants. 3 | */ 4 | 'use strict'; 5 | 6 | // special keywords for gamestate.json: 7 | export const NONE = 'none'; 8 | export const INVENTORY = 'inventory'; 9 | export const LOCATION = 'location'; 10 | 11 | 12 | export const ACTION_PREFIX = 'action_'; -------------------------------------------------------------------------------- /src/js/eventactionhandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | checkSynonyms, 5 | setStateOfObject 6 | } from './helper.js' 7 | 8 | import * as constants from './const.js'; 9 | 10 | 11 | export default class EventActionHandler { 12 | constructor(state, vocabulary, inventoryHandler, locationHandler, echo) { 13 | this.state = state; 14 | this.vocabulary = vocabulary; 15 | this.inventoryHandler = inventoryHandler; 16 | this.locationHandler = locationHandler; 17 | this.echo = echo; 18 | } 19 | 20 | execute_action(action) { 21 | throw 'executeEvent not implemented!'; 22 | } 23 | 24 | static get_name() { 25 | throw 'get_name not implemented!'; 26 | } 27 | 28 | static create_action_handler_properties(object, state, vocabulary, inventoryHandler, locationHandler, echo) { 29 | // ActionEventHandler as object properties, later it should be possible to have new ActionEvents per game: 30 | object[MoveItemActionHandler.get_name()] = new MoveItemActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 31 | object[RemoveItemActionHandler.get_name()] = new RemoveItemActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 32 | object[AddItemActionHandler.get_name()] = new AddItemActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 33 | object[NewConnectionsHandler.get_name()] = new NewConnectionsHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 34 | object[MoveToLocationActionHandler.get_name()] = new MoveToLocationActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 35 | object[DisableEventsActionHandler.get_name()] = new DisableEventsActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 36 | object[EnableEventsActionHandler.get_name()] = new EnableEventsActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 37 | object[UntriggerEventsActionHandler.get_name()] = new UntriggerEventsActionHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 38 | object[SetStateItemsActionEventHandler.get_name()] = new SetStateItemsActionEventHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 39 | object[SetStateLocationsActionEventHandler.get_name()] = new SetStateLocationsActionEventHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 40 | object[PointsActionEventHandler.get_name()] = new PointsActionEventHandler(state, vocabulary, inventoryHandler, locationHandler, echo); 41 | 42 | } 43 | 44 | add_items(action) { 45 | // into the inventory 46 | for (var i = 0; i < action.length; i++) { 47 | var temp = action[i].split(':'); 48 | if (temp.length == 1 || (temp.length == 2 && temp[0] == constants.INVENTORY)) {//inventory 49 | var obj = temp[0]; 50 | if (temp.length == 2) { 51 | obj = temp[1]; 52 | } 53 | var stateSplit = obj.split('|'); 54 | if (stateSplit.length == 2) { 55 | this.state.objects[stateSplit[0]].state = stateSplit[1]; 56 | } 57 | this.inventoryHandler.addToInventory(stateSplit[0]); 58 | } else if (temp.length == 2) {//location 59 | var location; 60 | if (temp[0] == constants.LOCATION) { 61 | location = this.state.locations[this.state.location]; 62 | } else { 63 | location = this.state.locations[temp[0]]; 64 | } 65 | 66 | var stateSplit = temp[1].split('|'); 67 | if (stateSplit.length == 2) { 68 | this.state.objects[stateSplit[0]].state = stateSplit[1]; 69 | } 70 | 71 | if (location.objects[stateSplit[0]] === undefined) { 72 | location.objects.push(stateSplit[0]); 73 | } 74 | } 75 | } 76 | } 77 | 78 | remove_items(action) { 79 | var removed = false; 80 | 81 | for (var i = 0; i < action.length; i++) { 82 | var temp = action[i].split(':'); 83 | if (temp.length == 1 || (temp.length == 2 && temp[0] == constants.INVENTORY)) {//inventory 84 | var ilength = this.state.inventory.length; 85 | var obj = temp[0]; 86 | if (temp.length == 2) { 87 | obj = temp[1]; 88 | } 89 | this.inventoryHandler.removeFromInventory(obj); 90 | if (this.state.inventory.length < ilength) { 91 | removed = true; 92 | } 93 | } else if (temp.length == 2) { 94 | var location; 95 | if (temp[0] == constants.LOCATION) { 96 | location = this.state.locations[this.state.location]; 97 | } else { 98 | location = this.state.locations[temp[0]]; 99 | } 100 | var olength = location.objects.length; 101 | location.objects.remove(temp[1]); 102 | if (location.objects.length < olength) { 103 | removed = true; 104 | } 105 | } 106 | } 107 | return removed; 108 | } 109 | 110 | move_items(action) { 111 | for (var i = 0; i < action.length; i++) { 112 | var temp = action[i].split(':'); 113 | if (temp.length == 3) { 114 | var obj = temp[0]; 115 | var from = temp[1]; 116 | var to = temp[2]; 117 | var removed = false; 118 | 119 | if (from === constants.INVENTORY) { 120 | removed = this.remove_items(obj); 121 | } else { 122 | removed = this.remove_items([from + ':' + obj]); 123 | } 124 | 125 | if (to === constants.INVENTORY && removed) { 126 | this.add_items(obj); 127 | } else if (removed) { 128 | this.add_items([to + ':' + obj]); 129 | } 130 | } 131 | } 132 | } 133 | 134 | } 135 | 136 | 137 | class MoveItemActionHandler extends EventActionHandler { 138 | execute_action(action) { 139 | this.move_items(action); 140 | } 141 | 142 | static get_name() { 143 | return 'action_move_items'; 144 | } 145 | } 146 | 147 | class RemoveItemActionHandler extends EventActionHandler { 148 | execute_action(action) { 149 | this.remove_items(action); 150 | } 151 | 152 | static get_name() { 153 | return 'action_remove_items'; 154 | } 155 | } 156 | 157 | class AddItemActionHandler extends EventActionHandler { 158 | execute_action(action) { 159 | this.add_items(action); 160 | } 161 | 162 | static get_name() { 163 | return 'action_add_items'; 164 | } 165 | } 166 | 167 | class NewConnectionsHandler extends EventActionHandler { 168 | execute_action(action) { 169 | for (var i = 0; i < action.length; i++) { 170 | var temp = action[i].split(':'); 171 | var place = this.state.locations[temp[0]]; 172 | var direction = temp[1]; 173 | var to = temp[2]; 174 | place.connections[direction] = to; 175 | } 176 | } 177 | 178 | static get_name() { 179 | return 'action_new_connections'; 180 | } 181 | } 182 | 183 | class MoveToLocationActionHandler extends EventActionHandler { 184 | execute_action(action) { 185 | this.locationHandler.setLocation(action); 186 | } 187 | static get_name() { 188 | return 'action_move_to_location'; 189 | } 190 | } 191 | 192 | class DisableEventsActionHandler extends EventActionHandler { 193 | execute_action(action) { 194 | for (var i = 0; i < action.length; i++) { 195 | var nevent = this.state.events[action[i]]; 196 | nevent.disabled = true; 197 | } 198 | } 199 | static get_name() { 200 | return 'action_disable_events'; 201 | } 202 | } 203 | 204 | class EnableEventsActionHandler extends EventActionHandler { 205 | execute_action(action) { 206 | if (!isEmpty(action)) { 207 | for (var i = 0; i < action.length; i++) { 208 | var nevent = this.state.events[action[i]]; 209 | if (nevent != undefined) { 210 | nevent.disabled = false; 211 | nevent.triggered_steps = this.state.steps; 212 | } 213 | } 214 | 215 | } 216 | } 217 | static get_name() { 218 | return 'action_enable_events'; 219 | } 220 | } 221 | 222 | 223 | class UntriggerEventsActionHandler extends EventActionHandler { 224 | execute_action(action) { 225 | for (var i = 0; i < action.length; i++) { 226 | var nevent = this.state.events[action[i]]; 227 | if (nevent != undefined) { 228 | nevent.triggered = false; 229 | nevent.triggered_steps = 0; 230 | } 231 | } 232 | } 233 | static get_name() { 234 | return 'action_untrigger_events'; 235 | } 236 | } 237 | 238 | class SetStateItemsActionEventHandler extends EventActionHandler { 239 | execute_action(action) { 240 | for (var i = 0; i < action.length; i++) { 241 | var arr = action[i].split('|'); 242 | setStateOfObject(arr[0], arr[1], this.state.objects); 243 | } 244 | } 245 | static get_name() { 246 | return 'action_set_state_items'; 247 | } 248 | } 249 | 250 | class SetStateLocationsActionEventHandler extends EventActionHandler { 251 | execute_action(action) { 252 | for (var i = 0; i < action.length; i++) { 253 | var arr = action[i].split('|'); 254 | setStateOfObject(arr[0], arr[1], this.state.locations); 255 | } 256 | } 257 | static get_name() { 258 | return 'action_set_state_locations'; 259 | } 260 | } 261 | 262 | class PointsActionEventHandler extends EventActionHandler { 263 | execute_action(action) { 264 | this.state.points += action; 265 | } 266 | static get_name() { 267 | return 'action_points'; 268 | } 269 | } 270 | 271 | -------------------------------------------------------------------------------- /src/js/eventhandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | checkSynonyms, 5 | setStateOfObject, 6 | listFormattedObjects 7 | } from './helper.js' 8 | 9 | import InventoryHandler from './inventoryhandler.js'; 10 | import LocationHandler from './locationhandler.js'; 11 | import EventActionHandler from './eventactionhandler.js'; 12 | 13 | import * as constants from './const.js'; 14 | 15 | 16 | 17 | 18 | export default class EventHandler { 19 | 20 | constructor(state, vocabulary, initInventory) { 21 | this.state = state; 22 | this.vocabulary = vocabulary; 23 | this.initInventory = initInventory; 24 | 25 | this.inventoryHandler = new InventoryHandler(state, initInventory); 26 | this.locationHandler = new LocationHandler(state); 27 | 28 | // ActionEventHandler as object properties, later it should be possible to have new ActionEvents per game: 29 | EventActionHandler.create_action_handler_properties(this, this.state, this.vocabulary, this.inventoryHandler, this.locationHandler, this.echo); 30 | } 31 | 32 | checkEventPrereq(prereq, to_check) { 33 | if (isEmpty(prereq) || prereq == to_check) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | checkEventPrereqInventory(prereq) { 40 | return this.checkEventPrereqArray(prereq, this.state.inventory); 41 | } 42 | 43 | checkEventPrereqNotInventory(prereq) { 44 | return this.checkEventPrereqNotArray(prereq, this.state.inventory); 45 | } 46 | 47 | checkEventPrereqArray(prereq, to_check) { 48 | if (prereq === undefined || prereq == '') { 49 | return true; 50 | } 51 | 52 | if (to_check === undefined) { 53 | return false; 54 | } 55 | 56 | for (var i = 0; i < prereq.length; i++) { 57 | var arr = prereq[i].split('|'); 58 | if (to_check.indexOf(arr[0]) == -1) { 59 | return false; 60 | } else { 61 | if (arr.length > 1) { 62 | var state = this.inventoryHandler.getStateOfObject(arr[0]); 63 | if (state == undefined && arr[1] == constants.NONE) { 64 | continue; 65 | } 66 | if (state != arr[1]) { 67 | return false; 68 | } 69 | } 70 | } 71 | } 72 | 73 | return true; 74 | } 75 | 76 | checkEventPrereqNotArray(prereq, to_check) { 77 | if (prereq === undefined || prereq == '') { 78 | return true; 79 | } 80 | 81 | if (to_check === undefined) { 82 | return true; 83 | } 84 | 85 | for (var i = 0; i < prereq.length; i++) { 86 | var arr = prereq[i].split('|'); 87 | if (to_check.indexOf(arr[0]) != -1) { 88 | if (arr.length > 1) { 89 | var state = this.inventoryHandler.get_state_of_object(arr[0]); 90 | if (state == arr[1]) { 91 | return false; 92 | } 93 | } else { 94 | return false; 95 | } 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | /** checks if the events in the list are already triggered AND enabled */ 103 | checkTriggeredEvents(prereq_triggered_events, prereq_triggered_event_step_offset) { 104 | if (isEmpty(prereq_triggered_events)) { 105 | return true; 106 | } 107 | 108 | for (var i = 0; i < prereq_triggered_events.length; i++) { 109 | var event = this.state.events[prereq_triggered_events[i]]; 110 | if (event === undefined) { 111 | return true; 112 | } 113 | if ((event.triggered === undefined || event.triggered == false) || (event.disabled != undefined && event.disabled == true)) { 114 | return false; 115 | } else if (prereq_triggered_event_step_offset != undefined && prereq_triggered_event_step_offset > 0) { 116 | if (this.state.steps - event.triggered_steps < prereq_triggered_event_step_offset) { 117 | return false; 118 | } 119 | } 120 | } 121 | return true; 122 | } 123 | 124 | checkVisitedLocations(prereq_visited_locations) { 125 | if (isEmpty(prereq_visited_locations)) { 126 | return true; 127 | } 128 | 129 | for (var i = 0; i < prereq_visited_locations.length; i++) { 130 | var loc = this.state.locations[prereq_visited_locations[i]]; 131 | if (loc.visited === undefined || loc.visited == false) { 132 | return false; 133 | } 134 | } 135 | return true; 136 | } 137 | 138 | checkEventPrereqLocationState(prereq) { 139 | if (isEmpty(prereq)) { 140 | return true; 141 | } 142 | var arr = prereq.split('|'); 143 | if (arr.length == 2) { 144 | var location = this.state.locations[arr[0]]; 145 | if (location === undefined) { 146 | return false; 147 | } 148 | if (location.state == arr[1]) { 149 | return true; 150 | } else if (location.state === undefined && arr[1] == constants.NONE) { 151 | return true; 152 | } 153 | } else { 154 | throw 'location and state for prereq not set!'; 155 | } 156 | return false; 157 | } 158 | 159 | checkTriggerOnce(event) { 160 | if (event.triggered === undefined || event.triggered == false || event.trigger_once === undefined) { 161 | return true; 162 | } 163 | 164 | if (event.triggered == true && event.trigger_once == true) { 165 | return false; 166 | } 167 | } 168 | 169 | findEvents(location, used_items, location_items, verb, preposition, events) { 170 | var retEvents = []; 171 | for (var property in events) { 172 | if (events.hasOwnProperty(property) && property != 'start_event') { 173 | var event = events[property]; 174 | if (event.disabled != undefined && event.disabled) { 175 | continue; 176 | } 177 | if (!isEmpty(event.prereq_verb) && !isEmpty(verb)) { 178 | if (checkSynonyms(event.prereq_verb, verb, this.vocabulary.synonyms)) { 179 | verb = event.prereq_verb; 180 | } 181 | } 182 | 183 | 184 | var keyNames = Object.keys(event); 185 | var condition = true; 186 | 187 | // all of them 188 | for (var i = 0; i < keyNames.length; i++) { 189 | var name = keyNames[i]; 190 | if (name.startsWith('prereq')) { 191 | if (!this.checkGeneralPrereq(name, event[name], event, location, used_items, location_items, verb, preposition)) { 192 | condition = false; 193 | break; 194 | } 195 | } 196 | } 197 | 198 | 199 | // none of them 200 | if (condition && event.not != undefined) { 201 | keyNames = Object.keys(event.not); 202 | for (var i = 0; i < keyNames.length; i++) { 203 | var name = keyNames[i]; 204 | if (name.startsWith('prereq')) { 205 | if (this.checkGeneralPrereq(name, event.not[name], event, location, used_items, location_items, verb, preposition)) { 206 | condition = false; 207 | break; 208 | } 209 | } 210 | } 211 | } 212 | 213 | 214 | // any of them 215 | if (condition && event.any != undefined) { 216 | keyNames = Object.keys(event.any); 217 | for (var i = 0; i < keyNames.length; i++) { 218 | var name = keyNames[i]; 219 | if (name.startsWith('prereq')) { 220 | if (this.checkGeneralPrereq(name, event.any[name], event, location, used_items, location_items, verb, preposition)) { 221 | condition = true; 222 | break; 223 | } 224 | } 225 | } 226 | } 227 | 228 | 229 | 230 | if (condition && this.checkTriggerOnce(event)) { 231 | retEvents.push(event); 232 | } 233 | } 234 | } 235 | 236 | return retEvents; 237 | } 238 | 239 | checkGeneralPrereq(prereq_name, prereq, event, location, used_items, location_items, verb, preposition) { 240 | switch (prereq_name) { 241 | case 'prereq_location': 242 | return this.checkEventPrereq(prereq, location); 243 | break; 244 | case 'prereq_verb': 245 | return this.checkEventPrereq(prereq, verb); 246 | break; 247 | case 'prereq_preposition': 248 | return this.checkEventPrereq(prereq, preposition); 249 | break; 250 | case 'prereq_used_items': 251 | return this.checkEventPrereqArray(prereq, used_items) 252 | break; 253 | case 'prereq_location_items': 254 | return this.checkEventPrereqArray(prereq, location_items) 255 | break; 256 | case 'prereq_inventory_items': 257 | return this.checkEventPrereqInventory(prereq); 258 | break; 259 | case 'prereq_location_state': 260 | return this.checkEventPrereqLocationState(prereq); 261 | break; 262 | case 'prereq_triggered_events': 263 | return this.checkTriggeredEvents(prereq, event.prereq_triggered_event_step_offset) 264 | break; 265 | case 'prereq_visited_locations': 266 | return this.checkVisitedLocations(prereq); 267 | break; 268 | } 269 | return false; 270 | } 271 | 272 | 273 | 274 | executeEvent(event, echo) { 275 | var location = this.state.locations[this.state.location]; 276 | var objects_message_before = listFormattedObjects(location.objects, this.state.objects, this.inventoryHandler); 277 | 278 | for (var property in event) { 279 | if (event.hasOwnProperty(property)) { 280 | if (property.startsWith(constants.ACTION_PREFIX)) { 281 | var handler = this[property]; 282 | if (handler !== undefined) { 283 | handler.execute_action(event[property]); 284 | } 285 | } 286 | } 287 | } 288 | 289 | if (!isEmpty(event.description)) { 290 | echo(event.description); 291 | } 292 | 293 | event.triggered = true; 294 | event.triggered_steps = this.state.steps; 295 | 296 | if (!isEmpty(event.action_trigger_event)) { 297 | var nevent = this.state.events[event.action_trigger_event]; 298 | return this.executeEvent(nevent, echo); 299 | } 300 | 301 | var objects_message = listFormattedObjects(location.objects, this.state.objects, this.inventoryHandler); 302 | if (objects_message !== objects_message_before && objects_message.length > 0) { 303 | echo(advntx.messages.info_you_see+'\n'+ objects_message); 304 | } 305 | 306 | if (!isEmpty(event.action_continue) && event.action_continue) { 307 | return true; 308 | } 309 | return false; 310 | 311 | 312 | } 313 | 314 | } -------------------------------------------------------------------------------- /src/js/functions.js: -------------------------------------------------------------------------------- 1 | /** some useful global functions. */ 2 | 3 | /** 4 | * Simple format function: 5 | * 'Replace {0}'.format('this') 6 | */ 7 | String.prototype.format = String.prototype.f = function() { 8 | var s = this, 9 | i = arguments.length; 10 | 11 | while (i--) { 12 | s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]); 13 | } 14 | return s; 15 | }; 16 | 17 | Array.prototype.remove = function(value) { 18 | if (this.indexOf(value)!==-1) { 19 | this.splice(this.indexOf(value), 1); 20 | return true; 21 | } else { 22 | return false; 23 | }; 24 | } 25 | 26 | function isEmpty(str) { 27 | return (!str || 0 === str.length); 28 | } 29 | 30 | function stringEquals(a, b) { 31 | return typeof a === 'string' && typeof b === 'string' 32 | ? a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0 33 | : a === b; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/js/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper functions. They do not manipulate states. 3 | */ 4 | 'use strict'; 5 | 6 | 7 | import * as constants from './const.js' 8 | 9 | 10 | 11 | export function getDescription(objects, id) { 12 | var obj = objects[id]; 13 | if (obj != undefined && !isEmpty(obj.state) && obj.state != constants.NONE && obj.states != undefined) { 14 | var state = obj.states[obj.state]; 15 | var desc = state['description']; 16 | if (!isEmpty(desc)) { 17 | return desc; 18 | } 19 | } 20 | return getProperty(objects, 'description', id) 21 | } 22 | 23 | export function getName(objects, id) { 24 | return getProperty(objects, 'name', id); 25 | } 26 | 27 | export function getImage(objects, id) { 28 | return getProperty(objects, 'image', id); 29 | } 30 | 31 | export function isHidden(objects, id) { 32 | return getProperty(objects, 'hidden', id); 33 | } 34 | 35 | 36 | export function getProperty(objects, property, id) { 37 | id = id.toLowerCase(); 38 | var obj = objects[id]; 39 | var state = constants.NONE; 40 | 41 | if (obj !== undefined && obj.state !== undefined) { 42 | state = obj.state; 43 | } 44 | 45 | // name can not be overriden by state. 46 | if (property !== 'name' && obj !== undefined && obj.states !== undefined && obj.states[state] !== undefined) { 47 | var retVal = obj.states[state][property]; 48 | if (retVal !== undefined) { 49 | return retVal; 50 | } 51 | } 52 | 53 | if (obj === undefined) { 54 | return ''; 55 | } 56 | var retVal = obj[property]; 57 | if (retVal !== undefined) 58 | return retVal; 59 | return ''; 60 | } 61 | 62 | export function getFirstOfType(words, type) { 63 | return getOfType(words, type, 0); 64 | } 65 | 66 | 67 | export function getSecondOfType(words, type) { 68 | return getOfType(words, type, 1); 69 | } 70 | 71 | export function getLastOfType(words, type) { 72 | return getOfType(words, type, words[type].length - 1); 73 | } 74 | 75 | 76 | export function getOfType(words, type, number) { 77 | if (words[type].length > number) { 78 | return words[type][number]; 79 | } 80 | return ''; 81 | } 82 | 83 | export function getObjectIdsForState(state) { 84 | var ids = []; 85 | if (state===undefined||state.objects===undefined) { 86 | return ids; 87 | } 88 | 89 | for (var i=0;i 0) { 117 | for (var i = 0; i < list.length; i++) { 118 | if (isHidden(list_of_all, list[i])) { 119 | continue; 120 | } 121 | if (i > 0 && !isHidden(list_of_all, list[i - 1])) { 122 | message += ', '; 123 | } 124 | var effect = inventoryHandler.getEffect(list[i]); 125 | var color = inventoryHandler.getColor(list[i]); 126 | if (color === undefined) { 127 | color = ''; 128 | } 129 | if (effect === undefined) { 130 | effect = ''; 131 | } 132 | 133 | var name = getName(list_of_all, list[i]); 134 | message += '[[!;' + color + ';;' + effect + ';javascript:advntx.terminalLink(\' '+name+'\');]' + name; 135 | 136 | var stateString = inventoryHandler.getStateString(list[i]); 137 | if (!isEmpty(stateString)) { 138 | message += ' ' + stateString; 139 | } 140 | message += ']'; 141 | } 142 | } 143 | return message; 144 | 145 | } 146 | 147 | export function checkSynonyms(main, to_check, synonyms) { 148 | if (main == to_check) { 149 | return true; 150 | } 151 | if (synonyms[main] == undefined) { 152 | return false; 153 | } 154 | if (synonyms[main].indexOf(to_check) != -1) { 155 | return true; 156 | } 157 | return false; 158 | } 159 | 160 | export function findItemIds(names, objects, allObjects) { 161 | var retItemIds = []; 162 | if (names === undefined || objects === undefined) 163 | return retItemIds; 164 | 165 | for (var i = 0; i < names.length; i++) { 166 | var name = names[i]; 167 | var itemIds = findItemIdsForName(name, allObjects); 168 | for (var i2 = 0; i2 < itemIds.length; i2++) { 169 | var itemId = itemIds[i2]; 170 | var index = objects.indexOf(itemId); 171 | if (index != -1 && retItemIds.indexOf(itemId) == -1) { 172 | retItemIds.push(objects[index]); 173 | } 174 | } 175 | 176 | } 177 | return retItemIds; 178 | } 179 | 180 | export function setStateOfObject(id, state, objects) { 181 | var object = objects[id]; 182 | if (state != constants.NONE && object.states[state] === undefined) { 183 | throw state + ' is not an allowed state for ' + id; 184 | } 185 | return object.state = state; 186 | } 187 | 188 | export function findItemIdsForName(name, objects) { 189 | var itemIds = []; 190 | for (var property in objects) { 191 | var item = objects[property]; 192 | if (stringEquals(item.name,name)) { 193 | itemIds.push(property); 194 | } 195 | for (var synid in item.synonyms) { 196 | var syn = item.synonyms[synid]; 197 | if (stringEquals(syn, name)) { 198 | itemIds.push(property); 199 | } 200 | } 201 | } 202 | return itemIds; 203 | } 204 | 205 | export function getObjectNameArray(objects) { 206 | var names = []; 207 | for (var property in objects) { 208 | var item = objects[property]; 209 | names.push(item.name); 210 | } 211 | return names; 212 | } 213 | 214 | export function getFromStateOrObject(objectId, property, objects) { 215 | var object = objects[objectId]; 216 | 217 | if (object === undefined) { 218 | return ''; 219 | } 220 | 221 | var state = object.state; 222 | var effect; 223 | if (!isEmpty(state) && state != constants.NONE && object.states[state] != undefined) { 224 | effect = object.states[state][property]; 225 | } 226 | if (effect === undefined) { 227 | effect = object[property]; 228 | } 229 | return effect; 230 | } 231 | 232 | // otherwise clicking on links in the terminal opens a new tab... 233 | export function removeTargetFromLinks() { 234 | Array.from(document.querySelectorAll('#terminal a[target="_blank"]')) 235 | .forEach(link => link.removeAttribute('target')); 236 | } 237 | 238 | -------------------------------------------------------------------------------- /src/js/interpreter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Interpret commands. 3 | */ 4 | 'use strict'; 5 | 6 | import { 7 | checkSynonyms, 8 | findItemIds, 9 | getDescription, 10 | getFirstOfType, 11 | getLastOfType, 12 | getName, 13 | listFormattedObjects, 14 | getObjectIdsForState, 15 | removeTargetFromLinks 16 | } from './helper.js' 17 | 18 | export default class Interpreter { 19 | constructor(advntx) { 20 | this.advntx = advntx; 21 | this.helpString = undefined; 22 | this.textColor = advntx.config.text_color; 23 | this.errorColor = advntx.config.error_color; 24 | this.warnColor = advntx.config.warn_color; 25 | this.previousObject = undefined; 26 | } 27 | 28 | 29 | 30 | interpret(command, describeLocationEcho, initInventory, echo, initGame, load, save, listSaveGames) { 31 | var original = command; 32 | this.echo = echo; 33 | this.describeLocationEcho = describeLocationEcho; 34 | this.initInventory = initInventory; 35 | this.initGame = initGame; 36 | this.load = load; 37 | this.save = save; 38 | this.listSaveGames = listSaveGames; 39 | 40 | // lower case and remove backslash from auto suggest: 41 | command = command.toLowerCase().replace('\\', '').trim(); 42 | 43 | if (isEmpty(command)) { 44 | removeTargetFromLinks(); 45 | return; 46 | } 47 | var words = this.advntx.parser.parse(command); 48 | var firstVerb = getFirstOfType(words, 'verbs'); 49 | var firstObject = getFirstOfType(words, 'objects'); 50 | var lastVerb = getLastOfType(words, 'verbs'); 51 | var preposition = getFirstOfType(words, 'prepositions'); 52 | 53 | //sometimes we need just the second word of the command without distinguishing between objects / verbs and so on: 54 | var secondWord = this.getSecondWord(command) 55 | var objects = words['objects']; 56 | var misc = words['misc']; 57 | var firstMisc = getFirstOfType(words, 'misc'); 58 | var foundNothing = false; 59 | 60 | var itemIdsFromLocation = this.advntx.locationHandler.findItemIdsInLocation(objects, this.advntx.state.locations[this.advntx.state.location]); 61 | var itemIdsFromInventory = this.advntx.inventoryHandler.findItemIdsInInventory(objects); 62 | var itemIds = []; 63 | 64 | var itemIds = itemIdsFromLocation.concat(itemIdsFromInventory); 65 | var locationObject = this.advntx.state.locations[this.advntx.state.location]; 66 | 67 | var executedPreEvents = []; 68 | var foundEvent = false; 69 | 70 | var firstObject = undefined; 71 | if (itemIds.length>0) { 72 | firstObject = itemIds[0]; 73 | } 74 | var withObject = undefined; 75 | if (itemIds.length>1 && preposition==advntx.messages.with) { 76 | withObject = itemIds[1]; 77 | } 78 | 79 | 80 | 81 | var direction = getFirstOfType(words, 'directions'); 82 | 83 | // check if the player entered a direction, then artificially add the verb for 'go'. 84 | // important: Do this before any event is executed! 85 | if (isEmpty(firstVerb) && !isEmpty(direction)) { 86 | firstVerb = advntx.messages.verb_go; 87 | } 88 | 89 | if (isEmpty(firstVerb) && !isEmpty(firstObject)) { 90 | firstVerb = advntx.messages.verb_examine; 91 | } 92 | 93 | // find and execute events BEFORE executing standard verbs: 94 | var preEvents = this.advntx.eventHandler.findEvents(this.advntx.state.location, itemIds, locationObject.objects, firstVerb, preposition, this.advntx.state.events); 95 | var doContinue = true; 96 | for (var i = 0; i < preEvents.length; i++) { 97 | var event = preEvents[i]; 98 | if (event.only_after == true) { 99 | continue; 100 | } 101 | doContinue = doContinue & this.triggerEvent(event); 102 | foundEvent = true; 103 | executedPreEvents.push(event); 104 | } 105 | 106 | 107 | 108 | // check for 'standard' verbs, all other verbs are dealt with in the events. 109 | if (!foundEvent || doContinue) { 110 | if (checkSynonyms(advntx.messages.verb_help, firstVerb, this.advntx.vocabulary.synonyms)) { 111 | this.help(secondWord); 112 | } else if (checkSynonyms(advntx.messages.verb_go, firstVerb, this.advntx.vocabulary.synonyms)) { 113 | // go / move 114 | this.move(direction, itemIdsFromLocation, misc); 115 | } else if (checkSynonyms(advntx.messages.verb_take, firstVerb, this.advntx.vocabulary.synonyms)) { 116 | // take/get 117 | this.getItem(objects, itemIdsFromLocation); 118 | } else if (checkSynonyms(advntx.messages.verb_examine, firstVerb, this.advntx.vocabulary.synonyms)) { 119 | this.examine(firstObject); 120 | } else if (checkSynonyms(advntx.messages.verb_look, firstVerb, this.advntx.vocabulary.synonyms)) { 121 | this.look(firstObject, preposition); 122 | } else if (checkSynonyms(advntx.messages.verb_drop, firstVerb, this.advntx.vocabulary.synonyms)) { 123 | // drop 124 | this.drop(objects, itemIdsFromInventory); 125 | 126 | } else if (checkSynonyms(advntx.messages.verb_open, firstVerb, this.advntx.vocabulary.synonyms) || 127 | checkSynonyms(advntx.messages.verb_unlock, firstVerb, this.advntx.vocabulary.synonyms) || 128 | checkSynonyms(advntx.messages.verb_lock, firstVerb, this.advntx.vocabulary.synonyms)) { 129 | // open / unlock (maybe handle unlock differently to open, therefore not in synonyms) 130 | this.interactWithObjectState(advntx.messages.verb_open,firstObject,withObject,preposition); 131 | } else if (checkSynonyms(advntx.messages.verb_close, firstVerb, this.advntx.vocabulary.synonyms)) { 132 | // close 133 | this.interactWithObjectState(advntx.messages.verb_close,firstObject,withObject,preposition); 134 | } else if (checkSynonyms(advntx.messages.verb_load, firstVerb, this.advntx.vocabulary.synonyms)) { 135 | this.load(secondWord); 136 | } else if (checkSynonyms(advntx.messages.verb_save, firstVerb, this.advntx.vocabulary.synonyms)) { 137 | this.save(secondWord); 138 | } else if (checkSynonyms(advntx.messages.verb_list, firstVerb, this.advntx.vocabulary.synonyms)) { 139 | this.listSaveGames(); 140 | } else if (checkSynonyms(advntx.messages.verb_read, firstVerb, this.advntx.vocabulary.synonyms)) { 141 | this.read(firstObject); 142 | } else if (checkSynonyms(advntx.messages.verb_restart, firstVerb, this.advntx.vocabulary.synonyms)) { 143 | this.echo('\n'); 144 | this.initGame(true); 145 | } else if (checkSynonyms(advntx.messages.verb_inventory, firstVerb, this.advntx.vocabulary.synonyms)) { 146 | this.echo(listFormattedObjects(advntx.state.inventory, advntx.state.objects, advntx.inventoryHandler)); 147 | } 148 | 149 | else {// I give up... however, there might be an event to execute. 150 | foundNothing = true; 151 | } 152 | } 153 | 154 | this.previousObject = firstObject; 155 | 156 | locationObject = this.advntx.state.locations[this.advntx.state.location]; 157 | 158 | // find and execute events AFTER executing standard verbs: 159 | var postEvents = this.advntx.eventHandler.findEvents(this.advntx.state.location, itemIds, locationObject.objects, firstVerb, preposition, this.advntx.state.events); 160 | if (doContinue) { 161 | for (var i = 0; i < postEvents.length; i++) { 162 | var event = postEvents[i]; 163 | if (executedPreEvents.indexOf(event) != -1 || event.only_before) { 164 | // event already executed or not suitable 165 | continue; 166 | } 167 | 168 | this.triggerEvent(event); 169 | foundEvent = true; 170 | } 171 | } 172 | 173 | 174 | if (!foundEvent && foundNothing && !isEmpty(firstVerb) && objects.length > 0) { 175 | var itemId = itemIds[0]; 176 | var obj = this.advntx.state.objects[itemId]; 177 | 178 | if (obj != undefined && obj.custom_errors != undefined) { 179 | var error = obj.custom_errors; 180 | var errorMessage = error[firstVerb]; 181 | echo(errorMessage, this.warnColor); 182 | } else { 183 | var placeholder = ' '; 184 | if (!isEmpty(preposition)) { 185 | placeholder = ' ' + preposition + ' '; 186 | } 187 | echo(this.advntx.messages.error_verb_object.format(firstVerb, placeholder, this.advntx.inventoryHandler.getNameWithArticle(itemId)), this.errorColor); 188 | } 189 | } else if (foundNothing && !foundEvent) { 190 | if (!isEmpty(firstVerb)) { 191 | this.echo(this.advntx.messages.error_verb.format(firstVerb), this.warnColor); 192 | } else { 193 | this.standardError(command); 194 | } 195 | } 196 | 197 | var allEvents = preEvents.concat(postEvents); 198 | if (this.checkWinningCondition(allEvents)) { 199 | echo(this.advntx.messages.info_you_win.format(this.advntx.state.steps, this.advntx.state.points), advntx.config.win_color); 200 | } 201 | 202 | 203 | if (!foundNothing || foundEvent) { 204 | this.advntx.state.steps++; 205 | this.initInventory(); 206 | } 207 | 208 | removeTargetFromLinks(); 209 | } 210 | 211 | getSecondWord(command) { 212 | var ws = command.split(' '); 213 | var name = ''; 214 | if (ws.length > 1) { 215 | name = ws[1]; 216 | } 217 | return name; 218 | } 219 | 220 | help(secondWord) { 221 | this.echo(this.advntx.messages.verb_help,undefined,'headline'); 222 | if (secondWord != undefined) { 223 | if (secondWord == 'verbs') { 224 | this.echo(this.buildVocabularyHelpString(advntx.messages.help_verbs, advntx.vocabulary.verbs)); 225 | return; 226 | } else if (advntx.messages['help_' + secondWord] != undefined) { 227 | this.echo(advntx.messages['help_' + secondWord]); 228 | return; 229 | } 230 | } 231 | // otherwise: 232 | this.echo(this.advntx.messages.help); 233 | } 234 | 235 | 236 | read(firstObjectId) { 237 | if (!isEmpty(firstObjectId)) { 238 | var obj = this.advntx.state.objects[firstObjectId]; 239 | var read = this.advntx.inventoryHandler.getReadOfState(firstObjectId,obj.state); 240 | if (isEmpty(read)) { 241 | read = obj.read; 242 | } 243 | 244 | if (!isEmpty(read)) { 245 | this.echo(read); 246 | } else { 247 | this.echo(advntx.messages.error_read); 248 | } 249 | } 250 | } 251 | 252 | // open/unlock and close 253 | interactWithObjectState(verb,firstObjectId, withObjectId, preposition) { 254 | var state = ''; 255 | 256 | if (verb==this.advntx.messages.verb_open) { 257 | state = this.advntx.messages.state_open; 258 | } else if (verb==this.advntx.messages.verb_close) { 259 | state = this.advntx.messages.state_closed; 260 | } else if (verb==this.advntx.messages.verb_unlock) { 261 | state = this.advntx.messages.state_open; 262 | } else if (verb==this.advntx.messages.verb_lock) { 263 | state = this.advntx.messages.state_close; 264 | } 265 | 266 | 267 | if (isEmpty(firstObjectId) && !isEmpty(this.previousObject)) { 268 | firstObjectId = this.previousObject; 269 | } 270 | 271 | if (isEmpty(firstObjectId)) { 272 | this.echo(this.advntx.messages.error_generic_open_close.format(verb),this.advntx.config.warn_color); 273 | return; 274 | } 275 | if (this.advntx.inventoryHandler.hasState(firstObjectId, state)) { 276 | var needed; 277 | if (state==this.advntx.messages.state_open && this.advntx.inventoryHandler.isLocked(firstObjectId)) { 278 | needed = this.advntx.inventoryHandler.getLockUnlockItem(firstObjectId); 279 | } else { 280 | needed = this.advntx.inventoryHandler.needItemForState(firstObjectId, state); 281 | } 282 | 283 | if (needed===undefined||needed==withObjectId) { 284 | this.advntx.inventoryHandler.setState(firstObjectId,state); 285 | 286 | if (state==this.advntx.messages.state_open && withObjectId!==undefined){ 287 | // unlock 288 | this.advntx.inventoryHandler.lock(firstObjectId, false); 289 | } 290 | 291 | if (state==this.advntx.messages.state_closed && withObjectId==this.advntx.inventoryHandler.getLockUnlockItem(firstObjectId)) { 292 | // lock 293 | this.advntx.inventoryHandler.lock(firstObjectId, true); 294 | } 295 | 296 | var text = advntx.inventoryHandler.getDescriptionOfState(firstObjectId, state); 297 | if (text===undefined) { 298 | text = advntx.messages.info_success; 299 | } 300 | this.echo(text); 301 | 302 | var objects = advntx.state.objects[firstObjectId].states[state].objects; 303 | if (objects!==undefined&&objects.length>0) { 304 | this.echo(advntx.messages.info_you_see); 305 | this.echo(listFormattedObjects(objects, advntx.state.objects, advntx.inventoryHandler)); 306 | } 307 | 308 | } else { 309 | var error = this.advntx.inventoryHandler.getErrorOfState(firstObjectId, state); 310 | if (isEmpty(error)) { 311 | error = this.advntx.messages.error_verb_object.format(verb+' ',getName(this.advntx.state.objects,firstObjectId),''); 312 | } 313 | this.echo(error,this.advntx.config.warn_color); 314 | } 315 | } else { 316 | this.echo(this.advntx.messages.error_verb_object.format(verb+' ',getName(this.advntx.state.objects,firstObjectId),' '),this.advntx.config.warn_color); 317 | } 318 | } 319 | 320 | 321 | move(direction, item_ids, misc) { 322 | var new_location = undefined; 323 | if (isEmpty(direction)) { 324 | if (item_ids.length > 0) { 325 | var item_id = item_ids[0]; 326 | this.advntx.interpreter.echo(this.advntx.messages.error_movement_thing.format(this.advntx.inventoryHandler.getNameWithArticle(item_id)), this.warnColor); 327 | return; 328 | } 329 | if (this.advntx.config.debug && misc.length > 0) { 330 | var loc = this.advntx.state.locations[misc[0]]; 331 | if (loc != undefined) { 332 | new_location = misc[0]; 333 | } 334 | } 335 | if (new_location == undefined) { 336 | this.advntx.interpreter.echo(this.advntx.messages.error_movement, this.errorColor); 337 | return; 338 | } 339 | } 340 | var location = this.advntx.state.locations[this.advntx.state.location]; 341 | 342 | if (new_location == undefined) { 343 | new_location = this.advntx.locationHandler.findConnectionsForDirection(location, direction); 344 | } 345 | 346 | if (new_location != undefined) { 347 | this.advntx.locationHandler.setLocation(new_location); 348 | this.advntx.interpreter.describeLocationEcho(new_location, false); 349 | } else { 350 | this.advntx.interpreter.echo(this.advntx.messages.error_movement_direction.format(direction), this.errorColor); 351 | } 352 | } 353 | 354 | getItem(objects, item_ids) { 355 | if (item_ids.length == 0 && objects.length > 0) { 356 | this.echo(this.advntx.messages.error_specific_get.format(objects[0]), this.errorColor) 357 | } else if (objects.length == 0) { 358 | this.echo(this.advntx.messages.error_specific_get.format(this.advntx.messages.this), this.errorColor); 359 | } 360 | for (var i = 0; i < item_ids.length; i++) { 361 | var item_id = item_ids[i]; 362 | if (!this.advntx.inventoryHandler.isPortable(item_id)) { 363 | var portable_error = this.advntx.inventoryHandler.getPortableError(item_id); 364 | if (!isEmpty(portable_error)) { 365 | this.echo(portable_error, this.warnColor); 366 | } else { 367 | var indevname = this.advntx.inventoryHandler.getNameWithArticle(item_id); 368 | this.echo(this.advntx.messages.error_portable.format(indevname), this.warnColor); 369 | } 370 | 371 | } else { 372 | this.echo(this.advntx.messages.info_you_took.format(this.advntx.inventoryHandler.getNameWithArticle(item_id, true))); 373 | this.advntx.inventoryHandler.addToInventory(item_id); 374 | this.initInventory(); 375 | this.advntx.locationHandler.removeItemFromLocation(this.advntx.state.location, item_id); 376 | } 377 | } 378 | } 379 | 380 | examine(firstObject) { 381 | if (firstObject===undefined) { 382 | this.echo(advntx.messages.error_examine, this.errorColor); 383 | return; 384 | } 385 | 386 | var desc = getDescription(this.advntx.state.objects, firstObject); 387 | this.echo(desc); 388 | } 389 | 390 | look(firstObject, preposition) { 391 | if (isEmpty(firstObject) && (isEmpty(preposition) || preposition==advntx.messages.preposition_around)) { 392 | this.describeLocationEcho(this.advntx.state.location, true); 393 | } else { 394 | if (isEmpty(firstObject) && !isEmpty(preposition) && !isEmpty(this.previousObject)) { 395 | firstObject = this.previousObject; 396 | } 397 | 398 | if (!isEmpty(firstObject) && preposition==this.advntx.messages.preposition_at) { 399 | var desc = getDescription(this.advntx.state.objects, firstObject); 400 | this.echo(desc); 401 | } else if (!isEmpty(firstObject) && preposition==this.advntx.messages.preposition_inside) { 402 | var object = advntx.state.objects[firstObject]; 403 | if (object.state!==undefined && object.state!='none') { 404 | var ids = getObjectIdsForState(object.states[object.state]); 405 | var objectsMessage = listFormattedObjects(ids, advntx.state.objects, advntx.inventoryHandler); 406 | this.echo(advntx.messages.info_inside_you_see.format(advntx.inventoryHandler.getNameWithArticle(firstObject))); 407 | this.echo(objectsMessage); 408 | } 409 | 410 | } else { 411 | this.echo(advntx.messages.error_look); 412 | } 413 | } 414 | } 415 | 416 | drop(objects, item_ids) { 417 | if (item_ids.length == 0 && objects.length > 0) { 418 | this.echo(this.advntx.messages.error_drop.format(objects[0])) 419 | } else if (objects.length == 0) { 420 | this.echo(this.advntx.messages.error_drop.format(this.advntx.messages.this)); 421 | } 422 | 423 | for (var i = 0; i < item_ids.length; i++) { 424 | var item_id = item_ids[i]; 425 | if (isEmpty(item_id)) { 426 | this.echo(this.advntx.messages.error_thing.format(item), this.errorColor); 427 | break; 428 | } else { 429 | this.advntx.inventoryHandler.removeFromInventory(item_id); 430 | this.advntx.locationHandler.addItemToLocation(this.advntx.state.location, item_id); 431 | this.echo(this.advntx.messages.info_you_dropped.format(this.advntx.inventoryHandler.getNameWithArticle(item_id, true))); 432 | } 433 | } 434 | 435 | 436 | } 437 | 438 | triggerEvent(event) { 439 | var old_location = this.advntx.state.location; 440 | 441 | var doContinue = this.advntx.eventHandler.executeEvent(event, this.advntx.interpreter.echo); 442 | 443 | if (old_location != this.advntx.state.location) { 444 | setTimeout(function () { 445 | this.advntx.interpreter.describeLocationEcho(this.advntx.state.location, false) 446 | }, this.advntx.config.standard_wait_eventtext); 447 | } 448 | 449 | return doContinue; 450 | } 451 | 452 | 453 | standardError(command) { 454 | this.echo(this.advntx.messages.error.format(command), this.errorColor); 455 | } 456 | 457 | /** 458 | * Checks a command for equality and for synonyms. 459 | */ 460 | check(input, to_check) { 461 | if (input == to_check) { 462 | return true; 463 | } 464 | if (vocabulary.synonyms[to_check] != undefined) { 465 | if (vocabulary.synonyms[to_check].indexOf(input) != -1) { 466 | return true; 467 | } 468 | } 469 | return false; 470 | 471 | } 472 | 473 | buildVocabularyHelpString(helpString, vocabularyObjects) { 474 | var vocabString = ''; 475 | for (var i = 0; i < vocabularyObjects.length; i++) { 476 | if (i != 0) 477 | vocabString += ', '; 478 | vocabString += vocabularyObjects[i]; 479 | } 480 | var string = helpString.format(vocabString); 481 | return string; 482 | } 483 | 484 | 485 | checkWinningCondition(events) { 486 | for (var i = 0; i < events.length; i++) { 487 | if (this.advntx.state.events['win_event'] === events[i]) { 488 | return true; 489 | } 490 | } 491 | return false; 492 | } 493 | 494 | } 495 | 496 | -------------------------------------------------------------------------------- /src/js/inventoryhandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | findItemIds, 5 | getFromStateOrObject 6 | } from './helper.js'; 7 | 8 | import * as constants from './const.js'; 9 | 10 | export default class InventoryHandler { 11 | constructor(state, initInventory) { 12 | this.l_state = state; 13 | this.initInventory = initInventory; 14 | } 15 | 16 | 17 | inInventory (itemId) { 18 | if (this.l_state.inventory.indexOf(itemId) != -1) { 19 | return true; 20 | } 21 | return false; 22 | } 23 | 24 | 25 | isPortable (itemId) { 26 | if (this.l_state.objects[itemId] === undefined) { 27 | return false; 28 | } 29 | 30 | if (this.l_state.objects[itemId].portable === undefined) { 31 | return true; 32 | } 33 | return this.l_state.objects[itemId].portable; 34 | } 35 | 36 | getPortableError (itemId) { 37 | var text = this.l_state.objects[itemId].error_portable; 38 | return text; 39 | } 40 | 41 | addToInventory (itemId) { 42 | this.l_state.inventory.push(itemId); 43 | this.initInventory(); 44 | } 45 | 46 | removeFromInventory (item) { 47 | this.l_state.inventory.remove(item); 48 | this.initInventory(); 49 | } 50 | 51 | findItemIdsInInventory (names) { 52 | return findItemIds(names, this.l_state.inventory, this.l_state.objects); 53 | } 54 | 55 | findItemIdsForNameAnywhere (name) { 56 | var itemIds = []; 57 | for (var property in this.l_state.objects) { 58 | var item = this.l_state.objects[property]; 59 | if (item.name.endsWith(name) && itemIds.indexOf(item.name)==-1) { 60 | itemIds.push(property); 61 | } 62 | } 63 | return itemIds; 64 | } 65 | 66 | getStateOfObject(itemId) { 67 | var object = this.l_state.objects[itemId]; 68 | if (object===undefined) { 69 | return constants.NONE; 70 | } 71 | if (isEmpty(object.state)) { 72 | return constants.NONE; 73 | } 74 | return object.state; 75 | } 76 | 77 | 78 | getSpecificStateOfObject(itemId, state) { 79 | var object = this.l_state.objects[itemId]; 80 | 81 | if (object===undefined || object.states === undefined) { 82 | return undefined; 83 | } 84 | 85 | if (state!=constants.NONE&&object.states[state]===undefined) { 86 | return undefined; 87 | } 88 | if (state===undefined || state==constants.NONE) { 89 | return undefined; 90 | } 91 | return object.states[state]; 92 | } 93 | 94 | getNameOfState(itemId, state) { 95 | var sobj = this.getSpecificStateOfObject(itemId, state); 96 | if (sobj===undefined) { 97 | return ''; 98 | } 99 | return sobj.name; 100 | } 101 | 102 | getDescriptionOfState(itemId, state) { 103 | var sobj = this.getSpecificStateOfObject(itemId, state); 104 | if (sobj===undefined) { 105 | return ''; 106 | } 107 | return sobj.description; 108 | } 109 | 110 | getReadOfState(itemId, state) { 111 | var sobj = this.getSpecificStateOfObject(itemId, state); 112 | if (sobj===undefined) { 113 | return ''; 114 | } 115 | return sobj.read; 116 | } 117 | 118 | getErrorOfState(itemId, state) { 119 | var object = this.l_state.objects[itemId]; 120 | if (state!=constants.NONE&&object.states[state]===undefined) { 121 | throw state+' is not an allowed state for '+itemId; 122 | } 123 | if (state==constants.NONE) { 124 | return ''; 125 | } 126 | return object.states[state].error; 127 | } 128 | 129 | hasState(itemId, state) { 130 | var object = this.l_state.objects[itemId]; 131 | if (state!=constants.NONE&&(object.states===undefined||object.states[state]===undefined)) { 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | setState(itemId, state) { 138 | var object = this.l_state.objects[itemId]; 139 | if (this.hasState(itemId, state)) { 140 | object.state = state; 141 | } 142 | } 143 | 144 | lock(itemId, lock) { 145 | var object = this.l_state.objects[itemId]; 146 | if (object!==undefined) { 147 | object.locked = lock; 148 | } 149 | } 150 | 151 | getStateObject(itemId, state) { 152 | if (this.hasState(itemId, state)) { 153 | return this.l_state.objects[itemId].states[state]; 154 | } 155 | } 156 | 157 | needItemForState(itemId, state) { 158 | if (this.hasState(itemId, state)) { 159 | return this.l_state.objects[itemId].states[state].with; 160 | } 161 | return undefined; 162 | } 163 | 164 | isLocked(itemId) { 165 | return this.l_state.objects[itemId].locked; 166 | } 167 | 168 | getLockUnlockItem(itemId) { 169 | return this.l_state.objects[itemId].lock_object; 170 | } 171 | 172 | getLockedError(itemId) { 173 | return this.l_state.objects[itemId].lock_error; 174 | } 175 | 176 | getStateString(itemId) { 177 | var stateName = this.getNameOfState(itemId, this.getStateOfObject(itemId)); 178 | var stateString = ''; 179 | if (!isEmpty(stateName)) { 180 | stateString = '('+stateName+')'; 181 | } 182 | return stateString; 183 | } 184 | 185 | getEffect(itemId) { 186 | return getFromStateOrObject(itemId,'effect', this.l_state.objects); 187 | } 188 | 189 | getColor(itemId) { 190 | return getFromStateOrObject(itemId,'color', this.l_state.objects); 191 | } 192 | 193 | 194 | getNameWithArticle (itemId, accusativ) { 195 | var item = this.l_state.objects[itemId]; 196 | if (item===undefined) { 197 | return ''; 198 | } 199 | var article = item.article; 200 | 201 | if (accusativ === true && item.article_acc != undefined) { 202 | article = item.article_acc; 203 | } 204 | 205 | var name = item.name; 206 | 207 | 208 | if ( ! isEmpty(article)) { 209 | return article + ' ' + name; 210 | }else { 211 | return name; 212 | } 213 | } 214 | 215 | } -------------------------------------------------------------------------------- /src/js/json.js: -------------------------------------------------------------------------------- 1 | import {getJSON as getJSON} from './helper.js'; 2 | 3 | 4 | export default function parseJson(asyncInit, advntx) { 5 | 6 | const j_vocabulary = advntx.currentGame+'/vocabulary.json'; 7 | const j_messages = advntx.currentGame+'/messages.json'; 8 | const j_game = advntx.currentGame+'/gamestate.json'; 9 | const j_config = advntx.currentGame+'/config.json'; 10 | 11 | 12 | // counter for number of necessary requests: 13 | var jsons = 0; 14 | const num_requests_necessary = 4; 15 | 16 | 17 | const parameter = '?v=' + advntx.version; 18 | 19 | function asyncInitLocal() { 20 | advntx.vocabulary.objects = []; 21 | for (var property in advntx.state.objects) { 22 | var item = advntx.state.objects[property]; 23 | advntx.vocabulary.objects.push(item.name); 24 | } 25 | asyncInit(true); 26 | } 27 | 28 | getJSON(j_vocabulary + parameter, 29 | function (result) { 30 | advntx.vocabulary = result; 31 | jsons++; 32 | if (jsons == num_requests_necessary) { 33 | asyncInitLocal(); 34 | } 35 | 36 | }); 37 | 38 | 39 | getJSON(j_messages + parameter, 40 | function (result) { 41 | advntx.messages = result; 42 | jsons++; 43 | if (jsons == num_requests_necessary) { 44 | asyncInitLocal(); 45 | } 46 | }); 47 | 48 | getJSON(j_game + parameter, 49 | function (result) { 50 | advntx.state = result; 51 | jsons++; 52 | if (jsons == num_requests_necessary) { 53 | asyncInitLocal(); 54 | } 55 | 56 | }); 57 | 58 | getJSON(j_config + parameter, 59 | function (result) { 60 | advntx.config = result; 61 | jsons++; 62 | if (jsons == num_requests_necessary) { 63 | asyncInitLocal(); 64 | } 65 | 66 | }); 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/js/locationhandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | checkSynonyms, 5 | findFirstMatch, 6 | findItemIds, 7 | findItemIdsForName, 8 | getDescription, 9 | getImage, 10 | getFirstOfType, 11 | getLastOfType, 12 | getName, 13 | getOfType, 14 | getProperty, 15 | getSecondOfType, 16 | isHidden, 17 | listFormattedObjects, 18 | getObjectIdsForState, 19 | setStateOfObject 20 | } from './helper.js'; 21 | 22 | import * as constants from './const.js'; 23 | 24 | export default class LocationHandler { 25 | 26 | constructor(state) { 27 | this.state = state; 28 | } 29 | 30 | 31 | removeItemFromLocation(location, item) { 32 | var place = this.state.locations[location]; 33 | if (place !== undefined && place.objects !== undefined) { 34 | var loc = place.objects.indexOf(item); 35 | if (loc != -1) { 36 | place.objects.splice(loc, 1); 37 | } 38 | 39 | for (var i = 0; i < place.objects.length; i++) { 40 | var obj = this.state.objects[place.objects[i]]; 41 | if (!isEmpty(obj.state) && obj.state != constants.NONE && obj.states[obj.state].objects !== undefined) { 42 | var loc = obj.states[obj.state].objects.indexOf(item); 43 | if (loc != -1) { 44 | obj.states[obj.state].objects.splice(loc, 1); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | addItemToLocation(location, item) { 52 | var place = this.state.locations[location]; 53 | place.objects.push(item); 54 | } 55 | 56 | inLocation(itemOrPerson) { 57 | var olocation = this.state.locations[this.state.location]; 58 | if (olocation.objects.indexOf(itemOrPerson.toLowerCase()) != -1) { 59 | return true; 60 | } 61 | if (olocation.persons.indexOf(itemOrPerson.toLowerCase()) != -1) { 62 | return true; 63 | } 64 | return false; 65 | 66 | } 67 | 68 | 69 | //FIXME 70 | findItemIdsInLocation(names, location) { 71 | if (location == undefined) { 72 | return []; 73 | } 74 | 75 | if (names===undefined||names.length==0) { 76 | return []; 77 | } 78 | 79 | var itemIds = findItemIds(names, location.objects, this.state.objects); 80 | 81 | if (location.objects !== undefined) { 82 | for (var i = 0; i < location.objects.length; i++) { 83 | var obj = this.state.objects[location.objects[i]]; 84 | if (!isEmpty(obj.state) && obj.state != constants.NONE) { 85 | itemIds = itemIds.concat(findItemIds(names, obj.states[obj.state].objects, this.state.objects)); 86 | } 87 | } 88 | } 89 | 90 | if (!isEmpty(location.state) && location.state != constants.NONE && location.states[location.state].objects !== undefined) { 91 | itemIds = itemIds.concat(findItemIds(names, location.states[location.state], this.state.objects)); 92 | } 93 | return itemIds; 94 | } 95 | 96 | getItemIdsFromLocation(location) { 97 | var itemIds = []; 98 | if (location.objects != undefined) { 99 | itemIds = itemIds.concat(location.objects); 100 | } 101 | 102 | if (!isEmpty(location.state) && location.state != constants.NONE) { 103 | itemIds = itemIds.concat(getObjectIdsForState(location.states[location.state])); 104 | } 105 | 106 | if (location.objects !== undefined) { 107 | 108 | for (var i = 0; i < location.objects.length; i++) { 109 | var obj = this.state.objects[location.objects[i]]; 110 | if (!isEmpty(obj.state) && obj.state != constants.NONE) { 111 | itemIds = itemIds.concat(getObjectIdsForState(obj.states[obj.state])); 112 | } 113 | } 114 | } 115 | 116 | return itemIds; 117 | } 118 | 119 | setLocation(location_id) { 120 | this.state.location = location_id; 121 | } 122 | 123 | getLocationDescription(location_id) { 124 | var loc = this.state.locations[location_id]; 125 | var description = getDescription(this.state.locations, location_id); 126 | return description; 127 | } 128 | 129 | 130 | getLocationImage(location_id) { 131 | var loc = this.state.locations[location_id]; 132 | if (loc!=undefined) { 133 | var image = getImage(this.state.locations, location_id); 134 | return image; 135 | } 136 | return undefined; 137 | } 138 | 139 | getLocationById(location_id) { 140 | return this.state.locations[location_id]; 141 | } 142 | 143 | findConnectionsForDirection(location, direction) { 144 | var state = location.state; 145 | if (!isEmpty(state) && location.states != undefined) { 146 | var stateObj = location.states[state]; 147 | if (stateObj != undefined && stateObj.connections != undefined) { 148 | return stateObj.connections[direction]; 149 | } 150 | } 151 | if (location.connections !== undefined && location.connections[direction] !== undefined) { 152 | return (location.connections[direction]); 153 | } 154 | 155 | for (var i = 0; i < location.objects.length; i++) { 156 | var obj = this.state.objects[location.objects[i]]; 157 | if (!isEmpty(obj.state) && obj.state != constants.NONE) { 158 | var connections = obj.states[obj.state].connections; 159 | if (connections !== undefined && (location.reversed === undefined || location.reversed.indexOf(location.objects[i]) == -1)) { 160 | return connections[direction]; 161 | } 162 | 163 | if (location.reversed !== undefined && location.reversed.indexOf(location.objects[i]) != -1 && obj.states[obj.state].reversed_connections !== undefined) { 164 | return obj.states[obj.state].reversed_connections[direction]; 165 | } 166 | } 167 | } 168 | return undefined; 169 | } 170 | 171 | visited(location_id) { 172 | var loc = this.getLocationById(location_id); 173 | if (loc === undefined) { 174 | return false; 175 | } 176 | if (loc.visited === undefined || loc.visited == false) { 177 | loc.visited = true; 178 | return false; 179 | } 180 | return true; 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import Parser from './parser.js'; 2 | import { default as parseJson } from './json.js'; 3 | import { 4 | checkSynonyms, 5 | findFirstMatch, 6 | findItemIds, 7 | findItemIdsForName, 8 | getDescription, 9 | getFirstOfType, 10 | getLastOfType, 11 | getName, 12 | getOfType, 13 | getProperty, 14 | getSecondOfType, 15 | isHidden, 16 | listFormattedObjects, 17 | setStateOfObject, 18 | getObjectNameArray, 19 | getJSON, 20 | removeTargetFromLinks 21 | } from './helper.js' 22 | 23 | import InventoryHandler from './inventoryhandler.js' 24 | import LocationHandler from './locationhandler.js' 25 | import EventHandler from './eventhandler.js' 26 | import Interpreter from './interpreter.js' 27 | 28 | 29 | 30 | 31 | 32 | /** 33 | * Set advntx as global variable. 34 | * window is needed because of system.js. 35 | */ 36 | window.advntx = { 37 | 38 | echo(text, color, clazz, bold) { 39 | if (color != undefined || clazz != undefined || bold != undefined) { 40 | if (clazz === undefined) { 41 | clazz = ''; 42 | } 43 | if (color === undefined) { 44 | color = ''; 45 | } 46 | var formatting = ''; 47 | if (bold) { 48 | formatting = 'b'; 49 | } 50 | text = '[[' + formatting + ';' + color + ';;' + clazz + ']' + text + ']'; 51 | } 52 | 53 | var old_prompt = advntx.term.get_prompt(); 54 | var less = true; 55 | var cols = advntx.term.cols(); 56 | var rows = advntx.term.rows()-2; 57 | var lines = text.split('\n'); 58 | var numLines = lines.length; 59 | for (var i=0;i= rows) { 66 | var pos = 0; 67 | var left = 0; 68 | advntx.waitForKey = true; 69 | function print() { 70 | var to_print = []; 71 | var numOfLines = 0; 72 | var origLinesPrinted = 0; 73 | for (var i=pos;i=rows) { 80 | break; 81 | } 82 | } 83 | pos+=origLinesPrinted; 84 | advntx.term.echo(to_print.join('\n'), { 85 | keepWords: true 86 | }); 87 | } 88 | 89 | print(); 90 | 91 | advntx.term.push($.noop, { 92 | keydown: function (e) { 93 | print(); 94 | if (pos >= lines.length) { 95 | advntx.term.pop(); 96 | advntx.term.set_prompt(old_prompt); 97 | advntx.waitForKey = false; 98 | removeTargetFromLinks(); 99 | return false; 100 | } 101 | 102 | } 103 | }); 104 | 105 | advntx.term.set_prompt(advntx.messages.info_press_key); 106 | 107 | } else { 108 | advntx.term.echo(text, { 109 | keepWords: true 110 | }); 111 | } 112 | removeTargetFromLinks(); 113 | 114 | }, 115 | 116 | formatHeadline(text) { 117 | var lines = text.split('\n'); 118 | for (var i=0;i'; 143 | advntx.term.set_prompt('[[b;;]' + prompt + ']'); 144 | 145 | var objects = advntx.locationHandler.getItemIdsFromLocation(advntx.state.locations[locationId]); 146 | var message = advntx.messages.info_you_see; 147 | var objectsMessage = listFormattedObjects(objects, advntx.state.objects, advntx.inventoryHandler); 148 | 149 | var text = ''; 150 | 151 | if (!isEmpty(preEventText)) { 152 | text = preEventText+'\n'; 153 | } 154 | text += headline+'\n'+description+'\n'; 155 | 156 | 157 | if (!isEmpty(objectsMessage)) { 158 | text+=message+'\n'+objectsMessage; 159 | } else { 160 | text+=advntx.messages.info_you_see_nothing; 161 | } 162 | 163 | if (!isEmpty(postEventText)) { 164 | text+=postEventText; 165 | } 166 | 167 | advntx.echo(text); 168 | 169 | if (!isEmpty(image)) { 170 | document.getElementById("imagecontainer").src=image; 171 | } 172 | 173 | 174 | }, 175 | 176 | addToInventoryEcho(item) { 177 | advntx.inventoryHandler.addToInventory(item); 178 | advntx.initInventory(); 179 | }, 180 | 181 | 182 | initGame(refreshJson, gameId) { 183 | // version string, add to json calls to avoid browser caching: 184 | advntx.version = g_ver; 185 | 186 | getJSON('games/games.json', function (result) { 187 | advntx.games = result; 188 | $('#game_buttons').children().remove(); 189 | for (var key in advntx.games) { 190 | if (key == 'default') { 191 | continue; 192 | } 193 | var $button = $(''); 194 | var $space = $(' '); 195 | $button.appendTo($('#game_buttons')); 196 | $space.appendTo($('#game_buttons')); 197 | } 198 | 199 | if (isEmpty(gameId)) { 200 | gameId = result.default; 201 | } 202 | advntx.gameId = gameId; 203 | advntx.currentGame = 'games/' + result[gameId].path; 204 | if (advntx.term != undefined) { 205 | advntx.term.clear(); 206 | } 207 | 208 | 209 | if (refreshJson == true) { 210 | parseJson(advntx.initGameAsync, advntx); 211 | } else { 212 | advntx.initGameAsync(false); 213 | } 214 | 215 | }); 216 | removeTargetFromLinks(); 217 | }, 218 | 219 | terminalLink(name) { 220 | advntx.term.insert(name); 221 | advntx.asyncRefocusTerminal(); 222 | }, 223 | 224 | executeLink(name) { 225 | advntx.term.exec(name); 226 | advntx.asyncRefocusTerminal(); 227 | }, 228 | 229 | executeCurrent() { 230 | if (advntx.waitForKey) { 231 | // simulate backspace key: 232 | var e = $.Event('keydown', { keyCode: 8}); 233 | advntx.term.trigger(e); 234 | } else { 235 | advntx.term.exec(advntx.term.get_command()); 236 | advntx.term.set_command(''); 237 | } 238 | advntx.asyncRefocusTerminal(); 239 | }, 240 | 241 | load(name) { 242 | var retrievedObject = localStorage.getItem('advntx' + name); 243 | if (retrievedObject != undefined) { 244 | advntx.state = JSON.parse(retrievedObject); 245 | advntx.echo(advntx.messages.info_game_loaded.format(name)); 246 | advntx.term.exec('clear'); 247 | advntx.initGame(false); 248 | } 249 | }, 250 | 251 | save(name) { 252 | localStorage.setItem('advntx' + name, JSON.stringify(advntx.state)); 253 | advntx.echo(advntx.messages.info_game_saved.format(name)); 254 | advntx.initInventory(); 255 | advntx.asyncRefocusTerminal(); 256 | }, 257 | 258 | listSavegames() { 259 | for (var i in localStorage) { 260 | if (i.startsWith('advntx')) { 261 | var name = i.replace('advntx', ''); 262 | var link = name; 263 | if (isEmpty(name)) { 264 | name = 'default'; 265 | } 266 | advntx.echo('[[!;;;;javascript:advntx.terminalLink(\'load ' + link + '\');]' + name + ']'); 267 | } 268 | } 269 | }, 270 | 271 | initGameAsync(reset) { 272 | 273 | if (advntx.term === undefined) { 274 | advntx.term = $('#terminal').terminal(function (command) { 275 | var echo = advntx.echo; 276 | 277 | advntx.interpreter.interpret(command, advntx.describeLocationEcho, advntx.initInventory, advntx.echo, advntx.initGame, advntx.load, advntx.save, advntx.listSavegames); 278 | }, { 279 | greetings: '', 280 | name: advntx.messages.name, 281 | prompt: advntx.config.console.prompt, 282 | height: advntx.config.console.height, 283 | anyLinks: true, 284 | completion: advntx.vocabulary.verbs.concat(advntx.vocabulary.directions).concat(advntx.vocabulary.prepositions).concat(getObjectNameArray(advntx.state.objects)) 285 | }); 286 | } 287 | 288 | 289 | $('#inventory_container').css('max-height', advntx.config.console.height + 'px'); 290 | 291 | advntx.parser = new Parser(advntx.vocabulary.verbs, advntx.vocabulary.directions, advntx.vocabulary.prepositions, 292 | advntx.vocabulary.adjectives, advntx.vocabulary.objects, advntx.state.objects); 293 | advntx.inventoryHandler = new InventoryHandler(advntx.state, advntx.initInventory); 294 | advntx.interpreter = new Interpreter(advntx); 295 | advntx.locationHandler = new LocationHandler(advntx.state); 296 | advntx.eventHandler = new EventHandler(advntx.state, advntx.vocabulary, advntx.initInventory); 297 | 298 | 299 | 300 | if (advntx.htmlInitialized === undefined) { 301 | $('textarea.clipboard').attr('autocomplete', 'off'); 302 | $('textarea.clipboard').attr('autocorrect', 'off'); 303 | $('textarea.clipboard').attr('autocapitalize', 'off'); 304 | $('textarea.clipboard').attr('spellcheck', 'off'); 305 | $('#options').toggle(); 306 | 307 | // set color scheme for terminal: 308 | $('#terminal').toggleClass('termcolor'); 309 | $('.cmd').toggleClass('termcolor'); 310 | advntx.htmlInitialized = true; 311 | } 312 | 313 | var text = advntx.formatHeadline(advntx.messages.greetings.format(advntx.version))+'\n'; 314 | advntx.initInventory(); 315 | if (reset) { 316 | var startEvent = advntx.state.events['start_event']; 317 | advntx.eventHandler.executeEvent(startEvent, function(eventText) { 318 | text += eventText; 319 | }); 320 | advntx.echo(text); 321 | } 322 | 323 | setTimeout(function () { 324 | advntx.describeLocationEcho(advntx.state.location,false,undefined,undefined); 325 | },1500); 326 | 327 | 328 | 329 | 330 | 331 | removeTargetFromLinks(); 332 | advntx.asyncRefocusTerminal(); 333 | }, 334 | 335 | toggleTerminalColors() { 336 | $('#terminal').toggleClass('termcolor'); 337 | $('.cmd').toggleClass('termcolor'); 338 | $('#terminal').toggleClass('inverted'); 339 | $('.cmd').toggleClass('inverted'); 340 | advntx.asyncRefocusTerminal(); 341 | }, 342 | 343 | refocusTerminal() { 344 | advntx.term.focus(); 345 | removeTargetFromLinks(); 346 | }, 347 | 348 | asyncRefocusTerminal() { 349 | window.setTimeout('advntx.refocusTerminal();', 250); 350 | }, 351 | 352 | inventoryClick(item) { 353 | advntx.term.insert(' ' + item + ' '); 354 | advntx.asyncRefocusTerminal(); 355 | }, 356 | 357 | initInventory() { 358 | $('#inventory > .inventory_item').remove(); 359 | if (advntx.state.inventory.length == 0) { 360 | $('#inventory').append('

' + advntx.messages.info_inventory_empty + '

'); 361 | } 362 | for (var i = 0; i < advntx.state.inventory.length; i++) { 363 | var item = advntx.state.inventory[i]; 364 | var itemName = getName(advntx.state.objects, item); 365 | var stateString = ' ' + advntx.inventoryHandler.getStateString(item); 366 | $('#inventory').append('

'); 367 | } 368 | } 369 | 370 | }; 371 | 372 | window.periodicUpdates = function periodicUpdates() { 373 | advntx.state.seconds++; 374 | $('#time_element').text(advntx.state.seconds); 375 | } 376 | 377 | 378 | /** Global Initializations */ 379 | $(document).ready(function () { 380 | $('#btn_help').click(function () { 381 | advntx.term.exec(advntx.messages.verb_help, false); 382 | advntx.initInventory(); 383 | advntx.asyncRefocusTerminal(); 384 | }); 385 | 386 | $('#btn_load_storage').click(function () { 387 | advntx.echo(advntx.messages.info_enter_savegame_name); 388 | advntx.term.insert(advntx.messages.verb_load + ' '); 389 | }); 390 | 391 | $('#btn_list_storage').click(function () { 392 | advntx.term.exec(advntx.messages.verb_list); 393 | advntx.asyncRefocusTerminal(); 394 | }); 395 | 396 | $('#btn_save_storage').click(function () { 397 | advntx.echo(advntx.messages.info_enter_savegame_name); 398 | advntx.term.insert(advntx.messages.verb_save + ' '); 399 | }); 400 | 401 | $('#btn_load_file').click(function () { 402 | var files = document.getElementById('selectFiles').files; 403 | if (files===undefined || files.length <= 0) { 404 | return false; 405 | } 406 | 407 | var fr = new FileReader(); 408 | 409 | fr.onload = function(e) { 410 | console.log(e); 411 | var result = JSON.parse(e.target.result); 412 | advntx.state = result; 413 | advntx.term.exec('clear'); 414 | advntx.initGame(false); 415 | } 416 | 417 | fr.readAsText(files.item(0)); 418 | 419 | }); 420 | 421 | $('#btn_save_file').click(function() { 422 | var dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(advntx.state, null, 2)); 423 | var downloadAnchorNode = document.createElement('a'); 424 | downloadAnchorNode.setAttribute('href', dataStr); 425 | var date = new Date(); 426 | var df = date.getMonth()+'-'+date.getDate()+'-'+date.getYear()+' '+(date.getHours()+1)+'_'+date.getMinutes(); 427 | downloadAnchorNode.setAttribute('download', 'advntx_' + df + '.json'); 428 | 429 | document.body.appendChild(downloadAnchorNode); 430 | 431 | downloadAnchorNode.click(); 432 | downloadAnchorNode.remove(); 433 | 434 | }); 435 | 436 | $('#btn_restart').click(function () { 437 | advntx.initGame(true,advntx.gameId); 438 | 439 | }); 440 | 441 | 442 | 443 | window.setInterval('window.periodicUpdates()', 1000); 444 | advntx.initGame(true); 445 | }); 446 | 447 | -------------------------------------------------------------------------------- /src/js/parser.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | export default class Parser { 5 | constructor(verbs, directions, prepositions, adjectives, objectNames, objects) { 6 | this.l_verbs = verbs; 7 | this.l_directions = directions; 8 | this.l_prepositions = prepositions; 9 | this.l_adjectives = adjectives; 10 | this.l_objectNames = objectNames; 11 | this.l_objects = objects; 12 | this.l_simple_objects = []; 13 | this.l_simple_object_map = []; 14 | for (var i = 0; i < objectNames.length; i++) { 15 | var words = objectNames[i].split(/[ ,]+/).filter(Boolean); 16 | if (words.length > 1) { 17 | var obj = this.l_simple_objects[words[words.length - 1]]; 18 | if (obj == undefined) { 19 | this.l_simple_objects.push(words[words.length - 1]); 20 | this.l_simple_object_map[words[words.length - 1]] = []; 21 | } 22 | this.l_simple_object_map[words[words.length - 1]].push(objectNames[i]); 23 | } 24 | } 25 | } 26 | 27 | parse(command) { 28 | var words = command.split(/[ ,]+/).filter(Boolean); 29 | var verbs = []; 30 | var directions = []; 31 | var prepositions = []; 32 | var adjectives = []; 33 | var objects = []; 34 | var misc = []; 35 | 36 | for (var i = 0; i < words.length; i++) { 37 | var word_consumed = false; 38 | if (this.isInArray(words[i], this.l_verbs)) { 39 | verbs.push(words[i]); 40 | word_consumed = true; 41 | } else { 42 | 43 | if (this.isInArray(words[i], this.l_directions)) { 44 | directions.push(words[i]); 45 | word_consumed = true; 46 | } 47 | if (this.isInArray(words[i], this.l_prepositions)) { 48 | prepositions.push(words[i]); 49 | word_consumed = true; 50 | } 51 | if (this.isInArray(words[i], this.l_adjectives)) { 52 | adjectives.push(words[i]); 53 | word_consumed = true; 54 | } 55 | if (this.isInArray(words[i], this.l_objectNames)) { 56 | objects.push(words[i]); 57 | word_consumed = true; 58 | } else if (words.length > i + 1 && this.isInArray(words[i] + ' ' + words[i + 1], this.l_objectNames)) { 59 | //special case: 2 word objects. 60 | objects.push(words[i] + ' ' + words[i + 1]); 61 | i++; 62 | word_consumed = true; 63 | } else { 64 | for (var property in this.l_objects) { 65 | var obj = this.l_objects[property]; 66 | if (obj.synonyms != undefined) { 67 | if (this.isInArray(words[i],obj.synonyms)) { 68 | objects.push(obj.name); 69 | word_consumed = true; 70 | } 71 | } 72 | } 73 | } 74 | 75 | if (!word_consumed && this.isInArray(words[i], this.l_simple_objects)) { 76 | var object_array = this.l_simple_object_map[words[i]]; 77 | for (var i2 = 0; i2 < object_array.length; i2++) { 78 | objects.push(object_array[i2]); 79 | } 80 | 81 | word_consumed = true; 82 | } 83 | 84 | } 85 | if (!word_consumed) { 86 | misc.push(words[i]) 87 | } 88 | } 89 | return { verbs: verbs, prepositions: prepositions, directions: directions, adjectives: adjectives, objects: objects, misc: misc }; 90 | 91 | } 92 | 93 | isInArray(value, array) { 94 | for (var i=0;i hr { 129 | margin: 2rem 0; 130 | } 131 | 132 | /* Main marketing message and sign up button */ 133 | .jumbotron { 134 | text-align: center; 135 | border-bottom: .05rem solid #e5e5e5; 136 | } 137 | .jumbotron .btn { 138 | padding: .75rem 1.5rem; 139 | font-size: 1.5rem; 140 | } 141 | 142 | /* Supporting marketing content */ 143 | .marketing { 144 | margin: 3rem 0; 145 | } 146 | .marketing p + h4 { 147 | margin-top: 1.5rem; 148 | } 149 | 150 | /* Responsive: Portrait tablets and up */ 151 | @media screen and (min-width: 48em) { 152 | /* Remove the padding we set earlier */ 153 | .header, 154 | .marketing, 155 | .footer { 156 | padding-right: 0; 157 | padding-left: 0; 158 | } 159 | /* Space out the masthead */ 160 | .header { 161 | margin-bottom: 2rem; 162 | } 163 | /* Remove the bottom border on the jumbotron for visual effect */ 164 | .jumbotron { 165 | border-bottom: 0; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/svg/icecream.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icecream 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/browser-sync.js: -------------------------------------------------------------------------------- 1 | /* 2 | config for the build in server browser-sync for mocha / chai unit testing. 3 | */ 4 | module.exports = { 5 | notify: false, 6 | port: 9000, 7 | ui: false, 8 | server: { 9 | baseDir: ['test','src'], 10 | routes: { 11 | '/node_modules': 'node_modules', 12 | '/src': 'src', 13 | '/games': 'games' 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mocha Spec Runner 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/test.mjs: -------------------------------------------------------------------------------- 1 | //import { chai } from './node_modules/chai/chai.js'; 2 | 3 | import Parser from '../src/js/parser.js'; 4 | import parseJson from '../src/js/json.js'; 5 | import EventHandler from '../src/js/eventhandler.js' 6 | import InventoryHandler from '../src/js/inventoryhandler.js' 7 | import LocationHandler from '../src/js/locationhandler.js' 8 | import Interpreter from '../src/js/interpreter.js' 9 | 10 | import { 11 | checkSynonyms, 12 | findFirstMatch, 13 | findItemIds, 14 | findItemIdsForName, 15 | getDescription, 16 | getFirstOfType, 17 | getLastOfType, 18 | getName, 19 | getOfType, 20 | getProperty, 21 | getSecondOfType, 22 | isHidden, 23 | setStateOfObject 24 | } from '../src/js/helper.js' 25 | 26 | 27 | 28 | var assert = chai.assert; 29 | 30 | 31 | 32 | 33 | // some mocking: 34 | window.advntx = (function (my) { 35 | var parser; 36 | 37 | 38 | 39 | 40 | my.echo = function (text, color) { 41 | console.info('advntx> ' + text); 42 | }; 43 | 44 | my.version = 14; 45 | return my; 46 | 47 | }(window.advntx || {})); 48 | 49 | 50 | 51 | 52 | before(function (done) { 53 | var advntx = window.advntx; 54 | advntx.currentGame = 'games/escape'; 55 | // waits until done is called (async!) 56 | function async_done(bool) { 57 | var objectIds = Object.keys(objects); 58 | advntx.parser = new Parser(advntx.vocabulary.verbs, advntx.vocabulary.directions, advntx.vocabulary.prepositions, advntx.vocabulary.adjectives, objectIds); 59 | 60 | var dummy = function() { 61 | 62 | }; 63 | advntx.inventoryHandler = new InventoryHandler(advntx.state,dummy); 64 | advntx.interpreter = new Interpreter(advntx); 65 | advntx.locationHandler = new LocationHandler(advntx.state); 66 | advntx.eventHandler = new EventHandler(advntx.state, advntx.vocabulary, dummy); 67 | done(); 68 | }; 69 | parseJson(async_done, advntx); 70 | }); 71 | 72 | function echo(string) { 73 | console.log(string); 74 | 75 | }; 76 | 77 | // set up some testing events: 78 | var events = { 79 | open_door: { 80 | name: 'open door', 81 | prereq_verb: 'open', 82 | prereq_location: 'room', 83 | prereq_used_items: ['door'], 84 | action_new_connections: ['room:east:room_two'] 85 | }, 86 | 87 | close_door: { 88 | name: 'close door', 89 | prereq_verb: 'close', 90 | prereq_location: 'room', 91 | prereq_used_items: ['door'] 92 | }, 93 | add_stone: { 94 | name: 'add stone', 95 | prereq_location: 'undefined', 96 | action_add_items: ['location:stone'] 97 | }, 98 | remove_stone: { 99 | name: 'remove stone', 100 | prereq_location: 'undefined', 101 | action_remove_items: ['room:stone'] 102 | }, 103 | not_inventory: { 104 | name: 'not in inventory', 105 | prereq_verb: 'take', 106 | not: { 107 | prereq_inventory_items: ['torch'] 108 | } 109 | }, 110 | 111 | any_event: { 112 | name: 'any', 113 | prereq_verb: 'help', 114 | any: { 115 | prereq_inventory_items: ['torch'], 116 | prereq_inventory_items: ['stone'] 117 | } 118 | }, 119 | 120 | drop_torch: { 121 | name: 'drop torch', 122 | prereq_verb: 'drop', 123 | prereq_used_items: ['torch'] 124 | }, 125 | 126 | kindle_torch: { 127 | name: 'kindle torch', 128 | prereq_verb: 'kindle', 129 | prereq_used_items: ['torch'], 130 | prereq_inventory_items: ['torch|none'], 131 | action_set_state_items: ['torch|burning'] 132 | }, 133 | 134 | burn_on_torch: { 135 | name: 'burned on torch', 136 | prereq_verb: 'examine', 137 | prereq_location_items: ['torch|burning'], 138 | }, 139 | 140 | extinguish_torch: { 141 | name: 'extinguish torch', 142 | prereq_verb: 'extinguish', 143 | prereq_used_items: ['torch'], 144 | prereq_inventory_items: ['torch|burning'], 145 | action_set_state_items: ['torch|none'] 146 | }, 147 | 148 | move_book: { 149 | name: 'move book', 150 | prereq_verb: 'move', 151 | prereq_used_items: ['book'], 152 | action_move_items: ['book:room_two:room_three'] 153 | }, 154 | 155 | throw_stone_at_guard: { 156 | description: '', 157 | prereq_verb: 'throw', 158 | prereq_used_items: ['torch', 'guard'] 159 | }, 160 | 161 | clean_ring: { 162 | description: 'You clean the ring. It looks like it is pure gold!', 163 | prereq_verb: 'clean', 164 | prereq_used_items: ['ring|dirty', 'barrel|open'], 165 | action_set_state_items: ['ring|none'] 166 | } 167 | 168 | }; 169 | 170 | // set up some locations: 171 | var locations = { 172 | room: { 173 | name: 'room', 174 | description: 'Testroom', 175 | connections: { 176 | 177 | }, 178 | objects: ['door'] 179 | }, 180 | room_two: { 181 | name: 'room 2', 182 | description: 'second testroom', 183 | connections: { 184 | west: 'room' 185 | }, 186 | objects: ['portal', 'book'], 187 | reversed: ['portal'] 188 | }, 189 | room_three: { 190 | name: 'room 3', 191 | description: 'Room with torch in it', 192 | connections: { 193 | east: 'room_two' 194 | }, 195 | objects: ['torch'] 196 | }, 197 | room_open_close: { 198 | name: 'room 4', 199 | description: 'a room with nested objects.', 200 | objects: ['chest','portal'], 201 | connections: { 202 | 203 | } 204 | }, 205 | 206 | room_connections: { 207 | name: 'room with connections', 208 | state: 'con', 209 | connections: { 210 | 211 | }, 212 | states: { 213 | con: { 214 | name: 'con', 215 | connections: { 216 | north: ['room_two'] 217 | } 218 | } 219 | } 220 | } 221 | 222 | }; 223 | 224 | // set up the objects: 225 | var objects = { 226 | guard: { name: 'guard' }, 227 | unconscious_guard: { 228 | name: 'unconscious guard', 229 | synonyms: ['guard'] }, 230 | barrel: { 231 | name: 'barrel', 232 | description: 'A large barrel, approx. half your height. It is very heavy, it seems to be filled with water.', 233 | portable: false, 234 | states: { 235 | open: { 236 | name: 'open', 237 | description: 'A large barrel, approx. half your height. The lid is open and you see that it is filled with water.' 238 | } 239 | } 240 | }, 241 | ring: { 242 | name: 'ring', 243 | description: 'A golden ring.\nIt looks very valuable!', 244 | state: 'dirty', 245 | states: { 246 | dirty: { 247 | name: 'dirty', 248 | description: 'A ring.\nIt is very dirty and should be cleaned.' 249 | } 250 | } 251 | }, 252 | door: { name: 'door' }, 253 | portal: { 254 | name: 'portal', 255 | state: 'closed', 256 | locked: true, 257 | lock_object: 'key', 258 | states: { 259 | open: { 260 | connections: { 261 | north: 'room_two' 262 | }, 263 | reversed_connections: { 264 | south: 'room_open_close' 265 | } 266 | }, 267 | closed: { 268 | name: 'closed' 269 | } 270 | } 271 | }, 272 | torch: { 273 | name: 'torch', 274 | states: { 275 | burning: { 276 | name: 'burniiiiing' 277 | } 278 | }, 279 | state: 'burning' 280 | }, 281 | chest: { 282 | name: 'chest', 283 | state: 'closed', 284 | states: { 285 | open: { 286 | name: 'open', 287 | objects: ['ring'] 288 | }, 289 | closed: { 290 | name: 'closed' 291 | } 292 | } 293 | }, 294 | key: { 295 | name: 'key' 296 | }, 297 | newspaper: { 298 | name: 'newspaper', 299 | desription: 'A large tabloid', 300 | read: 'This is the text which you can read.' 301 | } 302 | }; 303 | 304 | 305 | describe('advntx test suite', function () { 306 | it('format string test', function () { 307 | assert.equal('test: test', 'test: {0}'.format('test')); 308 | }); 309 | 310 | it('vocabulary loaded', function () { 311 | assert.notEqual(-1, advntx.vocabulary.verbs.indexOf('go')); 312 | assert.notEqual(-1, advntx.vocabulary.verbs.indexOf('throw')); 313 | assert.notEqual(-1, advntx.vocabulary.adjectives.indexOf('big')); 314 | assert.notEqual(-1, advntx.vocabulary.objects.indexOf('stone')); 315 | assert.notEqual(-1, advntx.vocabulary.directions.indexOf('north')); 316 | }); 317 | 318 | it('test parser', function () { 319 | var localParser = new Parser(['go', 'throw'], [], [], [], ['something', 'barrel', 'stone']); 320 | 321 | var obj = localParser.parse('throw stone'); 322 | assert.equal('throw', obj.verbs[0]); 323 | assert.equal('stone', obj.objects[0]); 324 | }); 325 | 326 | it('find objects', function () { 327 | 328 | var names = ['guard', 'sugar']; 329 | var room_item_ids = ['unconscious_guard', 'barrel', 'stone']; 330 | 331 | assert.equal('unconscious_guard', findItemIds(names, room_item_ids, objects)[0]); 332 | }); 333 | 334 | it('locations and connections', function () { 335 | advntx.state.inventory = ['stone']; 336 | advntx.state.objects = objects; 337 | advntx.state.locations = locations; 338 | advntx.state.location = 'room'; 339 | 340 | var location = advntx.state.locations['room_connections']; 341 | assert.equal('room_two', advntx.locationHandler.findConnectionsForDirection(location, 'north')); 342 | }); 343 | 344 | it('testing open, close and objects in another objects', function () { 345 | var save = advntx.state; 346 | 347 | // mock everything: 348 | advntx.state = { 349 | steps: 0 350 | }; 351 | advntx.state.inventory = []; 352 | advntx.state.objects = objects; 353 | advntx.state.locations = locations; 354 | advntx.state.events = []; 355 | advntx.eventHandler = new EventHandler(advntx.state, advntx.vocabulary, function() { }); 356 | advntx.inventoryHandler = new InventoryHandler(advntx.state,function() {}); 357 | advntx.locationHandler = new LocationHandler(advntx.state); 358 | 359 | 360 | advntx.state.location = 'room_open_close'; 361 | advntx.interpreter.interpret('open chest', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 362 | assert.equal('open',advntx.state.objects['chest'].state); 363 | advntx.interpreter.interpret('take ring', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 364 | assert.equal(0,advntx.state.objects['chest'].states['open'].objects.length); 365 | assert.equal('ring', advntx.state.inventory[0]); 366 | advntx.interpreter.interpret('close chest', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 367 | assert.equal('closed',advntx.state.objects['chest'].state); 368 | advntx.interpreter.interpret('north', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 369 | assert.equal('room_open_close',advntx.state.location); 370 | // magically give the player the portal key: 371 | advntx.state.inventory.push('key'); 372 | advntx.interpreter.interpret('open portal', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 373 | // still closed, because locked: 374 | assert.equal('closed',advntx.state.objects['portal'].state); 375 | advntx.interpreter.interpret('open portal with key', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 376 | // now open: 377 | assert.equal('open',advntx.state.objects['portal'].state); 378 | assert.equal(false,advntx.state.objects['portal'].locked); 379 | advntx.interpreter.interpret('north', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 380 | assert.equal('room_two',advntx.state.location); 381 | // testing reversed direction of portal: 382 | advntx.interpreter.interpret('south', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 383 | assert.equal('room_open_close',advntx.state.location); 384 | // lock portal: 385 | advntx.interpreter.interpret('close portal with key', function(){}, function(){}, echo, function(){}, function(){}, function(){}, function(){}); 386 | assert.equal('closed',advntx.state.objects['portal'].state); 387 | assert.equal(true,advntx.state.objects['portal'].locked); 388 | advntx.state = save; 389 | }); 390 | 391 | it('read something', function() { 392 | var save = advntx.state; 393 | 394 | // mock everything: 395 | advntx.state = { 396 | steps: 0 397 | }; 398 | advntx.state.inventory = ['newspaper']; 399 | advntx.state.objects = objects; 400 | advntx.state.locations = locations; 401 | advntx.state.events = []; 402 | advntx.state.location = 'room_open_close'; 403 | advntx.eventHandler = new EventHandler(advntx.state, advntx.vocabulary, function() { }); 404 | advntx.inventoryHandler = new InventoryHandler(advntx.state,function() {}); 405 | advntx.locationHandler = new LocationHandler(advntx.state); 406 | 407 | advntx.interpreter.interpret('read newspaper', function(){}, function(){}, function(text){ 408 | assert.equal(objects['newspaper'].read,text); 409 | }, function(){}, function(){}, function(){}, function(){}); 410 | 411 | advntx.state = save; 412 | }); 413 | 414 | it('find events', function () { 415 | assert.equal(events[0], advntx.eventHandler.findEvents(location, ['door', 'stone'], 'open', undefined, events)[0]); 416 | assert.equal(0, advntx.eventHandler.findEvents(location, ['torch'], [], 'throw', undefined, events).length); 417 | assert.equal(1, advntx.eventHandler.findEvents(location, ['torch', 'guard'], [], 'throw', undefined, events).length); 418 | 419 | 420 | }); 421 | 422 | 423 | it('execute some events', function () { 424 | var save = advntx.state; 425 | advntx.state = { 426 | steps: 0 427 | }; 428 | advntx.state.inventory = ['stone']; 429 | advntx.state.objects = objects; 430 | advntx.state.locations = locations; 431 | advntx.state.location = 'room'; 432 | var eventHandler = new EventHandler(advntx.state, advntx.vocabulary, function() { }); 433 | 434 | assert.equal(2,locations['room_two'].objects.length); 435 | eventHandler.executeEvent(events['move_book'], echo); 436 | assert.equal(1,locations['room_two'].objects.length, 'move action not successful'); 437 | assert.equal(2,locations['room_three'].objects.length); 438 | 439 | eventHandler.executeEvent(events['open_door'], echo); 440 | assert.equal(1, Object.keys(locations['room'].connections).length); 441 | eventHandler.executeEvent(events['add_stone'], echo); 442 | assert.equal('stone', locations['room'].objects[1]); 443 | eventHandler.executeEvent(events['remove_stone'], echo); 444 | assert.equal(1, locations['room'].objects.length); 445 | advntx.state.location = 'room_two'; 446 | eventHandler.executeEvent(events['add_stone'], echo); 447 | assert.equal(2, locations['room_two'].objects.length); 448 | advntx.state = save; 449 | }); 450 | 451 | it('testing "not" condition events', function () { 452 | var save = advntx.state; 453 | advntx.state = { 454 | steps: 0 455 | }; 456 | advntx.state.inventory = ['torch', 'stone']; 457 | advntx.state.objects = objects; 458 | advntx.state.locations = locations; 459 | advntx.state.location = 'room_three'; 460 | var locationObjects = locations[advntx.state.location].objects; 461 | var oldEventHandler = advntx.eventHandler; 462 | advntx.eventHandler = new EventHandler(advntx.state,oldEventHandler.vocabulary, oldEventHandler.initInventory); 463 | var foundEvents = advntx.eventHandler.findEvents(location, ['torch'], locationObjects, 'take', undefined, events); 464 | assert.equal(0, foundEvents.length); 465 | advntx.state.inventory = []; 466 | var foundEvents = advntx.eventHandler.findEvents(location, ['torch'], locationObjects, 'take', undefined, events); 467 | assert.equal(1, foundEvents.length); 468 | assert.equal(events['not_inventory'], foundEvents[0]); 469 | advntx.state = save; 470 | advntx.eventHandler = oldEventHandler; 471 | 472 | }); 473 | 474 | it('testing "any" condition events', function () { 475 | advntx.state.inventory = ['torch']; 476 | var foundEvents = advntx.eventHandler.findEvents(location, ['torch'], [], 'help', undefined, events); 477 | assert.equal(1, foundEvents.length); 478 | assert.equal(events['any_event'], foundEvents[0]); 479 | }); 480 | 481 | it('testing events on states', function () { 482 | var save = advntx.state; 483 | advntx.state = { 484 | steps: 0 485 | }; 486 | advntx.state.inventory = ['torch', 'stone']; 487 | advntx.state.objects = objects; 488 | advntx.state.locations = locations; 489 | advntx.state.location = 'room_three'; 490 | var eventHandler = new EventHandler(advntx.state, advntx.vocabulary, function(){}); 491 | var locationObjects = locations[advntx.state.location].objects; 492 | 493 | var foundEvents = eventHandler.findEvents(location, ['torch'], locationObjects, 'kindle', undefined, events); 494 | assert.equal(0, foundEvents.length); 495 | objects['torch'].state = 'none'; 496 | var foundEvents = eventHandler.findEvents(location, ['torch'], locationObjects, 'kindle', undefined, events); 497 | assert.equal(events['kindle_torch'], foundEvents[0]) 498 | eventHandler.executeEvent(events['kindle_torch'], echo); 499 | assert.equal('burning', objects['torch'].state); 500 | assert.equal('burniiiiing', advntx.inventoryHandler.getNameOfState('torch', 'burning')); 501 | var foundEvents = eventHandler.findEvents(location, ['torch'], locationObjects, 'extinguish', undefined, events); 502 | eventHandler.executeEvent(foundEvents[0], echo); 503 | assert.equal('none', objects['torch'].state); 504 | var foundEvents = eventHandler.findEvents(location, ['torch'], locationObjects, 'examine', undefined, events); 505 | assert.equal(0, foundEvents.length); 506 | objects['torch'].state = 'burning'; 507 | var foundEvents = eventHandler.findEvents(location, ['torch'], locationObjects, 'examine', undefined, events); 508 | assert.equal(1, foundEvents.length); 509 | eventHandler.executeEvent(foundEvents[0], echo); 510 | 511 | var foundEvents = eventHandler.findEvents(location, ['ring'], ['ring', 'barrel'], 'clean', undefined, events); 512 | assert.equal(0, foundEvents.length); 513 | advntx.state = save; 514 | }); 515 | 516 | }); 517 | 518 | --------------------------------------------------------------------------------