├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── launch.json └── tasks.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── community │ ├── ecosystem.md │ ├── roadmap.md │ └── socialize.md ├── maker │ ├── adding-levels.md │ ├── creating-tower.md │ ├── defining-abilities.md │ ├── defining-units.md │ ├── introduction.md │ ├── publishing.md │ ├── refactoring.md │ ├── space-api.md │ ├── testing.md │ └── unit-api.md └── player │ ├── abilities.md │ ├── ai-tips.md │ ├── cli-tips.md │ ├── effects.md │ ├── epic-mode.md │ ├── gameplay.md │ ├── general-tips.md │ ├── install.md │ ├── js-tips.md │ ├── object.md │ ├── options.md │ ├── overview.md │ ├── perspective.md │ ├── scoring.md │ ├── space-api.md │ ├── spaces.md │ ├── towers.md │ ├── turn-api.md │ ├── unit-api.md │ ├── units.md │ └── warrior.md ├── jest.config.js ├── lerna.json ├── logo ├── LICENSE ├── README.md ├── warriorjs-avatar-dark.png ├── warriorjs-avatar-dark.svg ├── warriorjs-avatar-light.png ├── warriorjs-avatar-light.svg ├── warriorjs-banner-dark.png ├── warriorjs-banner-dark.svg ├── warriorjs-banner-light.png ├── warriorjs-banner-light.svg ├── warriorjs-icon-dark.png ├── warriorjs-icon-dark.svg ├── warriorjs-icon-light.png ├── warriorjs-icon-light.svg ├── warriorjs-logo-dark.png ├── warriorjs-logo-dark.svg ├── warriorjs-logo-light.png ├── warriorjs-logo-light.svg └── warriorjs.sketch ├── package.json ├── packages ├── README.md ├── warriorjs-abilities │ ├── README.md │ ├── package.json │ └── src │ │ ├── attack.js │ │ ├── attack.test.js │ │ ├── bind.js │ │ ├── bind.test.js │ │ ├── detonate.js │ │ ├── detonate.test.js │ │ ├── directionOf.js │ │ ├── directionOf.test.js │ │ ├── directionOfStairs.js │ │ ├── directionOfStairs.test.js │ │ ├── distanceOf.js │ │ ├── distanceOf.test.js │ │ ├── feel.js │ │ ├── feel.test.js │ │ ├── health.js │ │ ├── health.test.js │ │ ├── index.js │ │ ├── listen.js │ │ ├── listen.test.js │ │ ├── look.js │ │ ├── look.test.js │ │ ├── maxHealth.js │ │ ├── maxHealth.test.js │ │ ├── pivot.js │ │ ├── pivot.test.js │ │ ├── rescue.js │ │ ├── rescue.test.js │ │ ├── rest.js │ │ ├── rest.test.js │ │ ├── shoot.js │ │ ├── shoot.test.js │ │ ├── think.js │ │ ├── think.test.js │ │ ├── walk.js │ │ └── walk.test.js ├── warriorjs-cli │ ├── README.md │ ├── bin │ │ └── warriorjs.js │ ├── package.json │ ├── src │ │ ├── Game.js │ │ ├── Game.test.js │ │ ├── GameError.js │ │ ├── Profile.js │ │ ├── Profile.test.js │ │ ├── ProfileGenerator.js │ │ ├── ProfileGenerator.test.js │ │ ├── Tower.js │ │ ├── Tower.test.js │ │ ├── cli.js │ │ ├── cli.test.js │ │ ├── loadTowers.js │ │ ├── loadTowers.test.js │ │ ├── parseArgs.js │ │ ├── parseArgs.test.js │ │ ├── ui │ │ │ ├── getScreenSize.js │ │ │ ├── getScreenSize.test.js │ │ │ ├── getUnitStyle.js │ │ │ ├── getUnitStyle.test.js │ │ │ ├── print.js │ │ │ ├── print.test.js │ │ │ ├── printBoard.js │ │ │ ├── printBoard.test.js │ │ │ ├── printFailureLine.js │ │ │ ├── printFailureLine.test.js │ │ │ ├── printFloorMap.js │ │ │ ├── printFloorMap.test.js │ │ │ ├── printLevel.js │ │ │ ├── printLevel.test.js │ │ │ ├── printLevelHeader.js │ │ │ ├── printLevelHeader.test.js │ │ │ ├── printLevelReport.js │ │ │ ├── printLevelReport.test.js │ │ │ ├── printLine.js │ │ │ ├── printLine.test.js │ │ │ ├── printLogMessage.js │ │ │ ├── printLogMessage.test.js │ │ │ ├── printPlay.js │ │ │ ├── printPlay.test.js │ │ │ ├── printRow.js │ │ │ ├── printRow.test.js │ │ │ ├── printSeparator.js │ │ │ ├── printSeparator.test.js │ │ │ ├── printSuccessLine.js │ │ │ ├── printSuccessLine.test.js │ │ │ ├── printTotalScore.js │ │ │ ├── printTotalScore.test.js │ │ │ ├── printTowerReport.js │ │ │ ├── printTowerReport.test.js │ │ │ ├── printTurnHeader.js │ │ │ ├── printTurnHeader.test.js │ │ │ ├── printWarningLine.js │ │ │ ├── printWarningLine.test.js │ │ │ ├── printWarriorStatus.js │ │ │ ├── printWarriorStatus.test.js │ │ │ ├── printWelcomeHeader.js │ │ │ ├── printWelcomeHeader.test.js │ │ │ ├── requestChoice.js │ │ │ ├── requestChoice.test.js │ │ │ ├── requestConfirmation.js │ │ │ ├── requestConfirmation.test.js │ │ │ ├── requestInput.js │ │ │ └── requestInput.test.js │ │ └── utils │ │ │ ├── getFloorMap.js │ │ │ ├── getFloorMap.test.js │ │ │ ├── getFloorMapKey.js │ │ │ ├── getFloorMapKey.test.js │ │ │ ├── getTowerId.js │ │ │ ├── getTowerId.test.js │ │ │ ├── getWarriorNameSuggestions.js │ │ │ └── getWarriorNameSuggestions.test.js │ └── templates │ │ ├── Player.js │ │ ├── README.md.ejs │ │ └── readme │ │ ├── abilities.ejs │ │ ├── ability.ejs │ │ └── level.ejs ├── warriorjs-core │ ├── README.md │ ├── package.json │ └── src │ │ ├── Floor.js │ │ ├── Floor.test.js │ │ ├── Level.js │ │ ├── Level.test.js │ │ ├── Logger.js │ │ ├── Position.js │ │ ├── Position.test.js │ │ ├── Space.js │ │ ├── Space.test.js │ │ ├── Unit.js │ │ ├── Unit.test.js │ │ ├── Warrior.js │ │ ├── Warrior.test.js │ │ ├── getLevel.js │ │ ├── getLevel.test.js │ │ ├── index.js │ │ ├── loadLevel.js │ │ ├── loadPlayer.js │ │ ├── loadPlayer.test.js │ │ ├── runLevel.js │ │ └── runLevel.test.js ├── warriorjs-effects │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.js │ │ ├── ticking.js │ │ └── ticking.test.js ├── warriorjs-geography │ ├── README.md │ ├── package.json │ └── src │ │ ├── absoluteDirections.js │ │ ├── absoluteDirections.test.js │ │ ├── getAbsoluteDirection.js │ │ ├── getAbsoluteDirection.test.js │ │ ├── getAbsoluteOffset.js │ │ ├── getAbsoluteOffset.test.js │ │ ├── getDirectionOfLocation.js │ │ ├── getDirectionOfLocation.test.js │ │ ├── getDistanceOfLocation.js │ │ ├── getDistanceOfLocation.test.js │ │ ├── getRelativeDirection.js │ │ ├── getRelativeDirection.test.js │ │ ├── getRelativeOffset.js │ │ ├── getRelativeOffset.test.js │ │ ├── index.js │ │ ├── relativeDirections.js │ │ ├── relativeDirections.test.js │ │ ├── rotateRelativeOffset.js │ │ ├── rotateRelativeOffset.test.js │ │ ├── translateLocation.js │ │ ├── translateLocation.test.js │ │ ├── verifyAbsoluteDirection.js │ │ ├── verifyAbsoluteDirection.test.js │ │ ├── verifyRelativeDirection.js │ │ └── verifyRelativeDirection.test.js ├── warriorjs-helper-get-grade-letter │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.js │ │ └── index.test.js ├── warriorjs-helper-get-level-config │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.js │ │ └── index.test.js ├── warriorjs-helper-get-level-score │ ├── README.md │ ├── package.json │ └── src │ │ ├── getClearBonus.js │ │ ├── getClearBonus.test.js │ │ ├── getLastEvent.js │ │ ├── getLastEvent.test.js │ │ ├── getRemainingTimeBonus.js │ │ ├── getRemainingTimeBonus.test.js │ │ ├── getTurnCount.js │ │ ├── getTurnCount.test.js │ │ ├── getWarriorScore.js │ │ ├── getWarriorScore.test.js │ │ ├── index.js │ │ ├── index.test.js │ │ ├── isFloorClear.js │ │ └── isFloorClear.test.js ├── warriorjs-tower-baby-steps │ ├── README.md │ ├── package.json │ └── src │ │ └── index.js ├── warriorjs-tower-tick-tick-boom │ ├── README.md │ ├── package.json │ └── src │ │ └── index.js └── warriorjs-units │ ├── README.md │ ├── package.json │ └── src │ ├── Archer.js │ ├── Archer.test.js │ ├── Captive.js │ ├── Captive.test.js │ ├── Sludge.js │ ├── Sludge.test.js │ ├── ThickSludge.js │ ├── ThickSludge.test.js │ ├── Warrior.js │ ├── Warrior.test.js │ ├── Wizard.js │ ├── Wizard.test.js │ └── index.js ├── website ├── .eslintrc.json ├── core │ ├── Footer.js │ ├── GitHubButton.js │ └── TwitterButton.js ├── crowdin.yaml ├── data │ └── sponsors.json ├── i18n │ └── en.json ├── languages.js ├── package.json ├── pages │ └── en │ │ └── index.js ├── sidebars.json ├── siteConfig.js ├── static │ ├── .circleci │ │ └── config.yml │ ├── css │ │ ├── custom.css │ │ └── nord.css │ ├── googlee0ff7b5bc8d30f78.html │ └── img │ │ ├── code-preview.png │ │ ├── favicon.png │ │ ├── make-preview.png │ │ ├── play-preview.png │ │ ├── warriorjs-sword.svg │ │ ├── warriorjs-text.svg │ │ ├── warriorjs.png │ │ └── warriorjs.svg └── utils │ ├── getDocUrl.js │ └── getImgUrl.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "8" 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": ["add-module-exports", "transform-object-rest-spread"] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,json}] 10 | indent_size = 2 11 | indent_style = space 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true, 4 | "node": true 5 | }, 6 | "extends": ["airbnb", "plugin:prettier/recommended"] 7 | } 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 38 | 39 | 40 | 41 | # Environment 42 | 43 | 49 | 50 | # Steps to reproduce 51 | 52 | # Expected Behavior 53 | 54 | # Actual Behavior 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | lerna-debug.log* 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # Build directories 62 | packages/*/lib/ 63 | 64 | # Website 65 | website/translated_docs 66 | website/build/ 67 | website/i18n/* 68 | !website/i18n/en.json 69 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | website/i18n/en.json 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always", 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch WarriorJS CLI", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/packages/warriorjs-cli/src/cli.js", 13 | "args": ["-t", "0.1"], 14 | "outFiles": ["${workspaceFolder}/packages/**/lib/**/*.js"], 15 | "console": "integratedTerminal", 16 | "internalConsoleOptions": "neverOpen", 17 | "smartStep": true 18 | }, 19 | { 20 | "type": "node", 21 | "request": "launch", 22 | "name": "Debug Jest Tests", 23 | "program": "${workspaceFolder}/node_modules/.bin/jest", 24 | "args": ["--runInBand"], 25 | "console": "integratedTerminal", 26 | "internalConsoleOptions": "neverOpen", 27 | "smartStep": true 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "npm", 9 | "script": "build", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Matías Olivera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/community/ecosystem.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: ecosystem 3 | title: Ecosystem 4 | --- 5 | 6 | WarriorJS uses NPM/Yarn, so any WarriorJS related project can live on 7 | [npm](https://npmjs.com). 8 | 9 | Official WarriorJS packages live under the 10 | [@warriorjs scope](https://npmjs.com/org/warriorjs), whereas community packages 11 | should be prefixed with `warriorjs-`. Please also put the keywords "warriorjs" 12 | and "warriorjs-tower" (if you're publishing a tower) in package.json's keywords 13 | field. 14 | -------------------------------------------------------------------------------- /docs/community/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: roadmap 3 | title: Roadmap & Contribution 4 | --- 5 | 6 | ## WarriorJS TODOs 7 | 8 | Some future plans are briefly discussed in the repo's 9 | [issues page](https://github.com/olistic/warriorjs/issues). 10 | 11 | ## Contribution Opportunities 12 | 13 | These are the many ways you can help: 14 | 15 | - Submit patches and features 16 | - Make [towers](player/towers.md) (new levels for the game) 17 | - Improve this documentation and website 18 | - Report bugs 19 | - Follow us on [Twitter](https://twitter.com/warrior_js) 20 | - Participate in the [Spectrum community](https://spectrum.chat/warriorjs) 21 | - And [donate financially](https://opencollective.com/warriorjs)! 22 | 23 | Please read our 24 | [contribution guide](https://github.com/olistic/warriorjs/blob/master/CONTRIBUTING.md) 25 | to get started. 26 | -------------------------------------------------------------------------------- /docs/community/socialize.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: socialize 3 | title: Socialize 4 | --- 5 | 6 | Do you have any questions, ideas, or suggestions? 7 | 8 | - Come say hi in [Spectrum](https://spectrum.chat/warriorjs) 9 | - Tweet us [@warrior_js](https://twitter.com/warrior_js) 10 | -------------------------------------------------------------------------------- /docs/maker/creating-tower.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: creating-tower 3 | title: Creating Your Tower 4 | --- 5 | 6 | A WarriorJS tower is a regular JavaScript module with a single export: 7 | 8 | ```js 9 | module.exports = { 10 | // Tower definition. 11 | }; 12 | ``` 13 | 14 | Let's define the name of our tower and write a brief description: 15 | 16 | ```js 17 | module.exports = { 18 | name: 'Game of Thrones', 19 | description: 20 | 'There is only one war that matters: the Great War. And it is here.', 21 | }; 22 | ``` 23 | 24 | Cool! But there's nothing to climb yet. Let's add some levels to this tower! 25 | -------------------------------------------------------------------------------- /docs/maker/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: introduction 3 | title: Introduction 4 | --- 5 | 6 | In this guide, you'll learn how to make your own tower by following a quick 7 | example. 8 | 9 | This guide assumes that you're familiar with the basic concepts of WarriorJS. 10 | It's also important that you have played the game, ideally having completed at 11 | least the "Baby Steps" tower. 12 | 13 | Let's get started! 14 | -------------------------------------------------------------------------------- /docs/maker/publishing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: publishing 3 | title: Publishing 4 | --- 5 | 6 | This is the minimal structure of a tower package: 7 | 8 | ```sh 9 | warriorjs-tower-got 10 | ├── index.js 11 | └── package.json 12 | ``` 13 | 14 | Where `index.js` would contain the code we've been writing through this guide, 15 | and `package.json` the npm package info: 16 | 17 | ```json 18 | { 19 | "name": "warriorjs-tower-got", 20 | "version": "0.1.0", 21 | "description": "There is only one war that matters: the Great War. And it is here.", 22 | "main": "index.js", 23 | "keywords": ["warriorjs-tower"], 24 | "dependencies": { 25 | "@warriorjs/geography": "^0.4.0" 26 | } 27 | } 28 | ``` 29 | 30 | Some special considerations: 31 | 32 | - The package name must start with `warriorjs-tower-` for the tower to be 33 | automatically loaded by WarriorJS. 34 | - `warriorjs-tower` should be in the "keywords" field for better discoverability 35 | of your tower. 36 | 37 | When working on a tower, you can use 38 | [`npm pack`](https://docs.npmjs.com/cli/pack) to create a tarball for it, and 39 | then install it where you installed `@warriorjs/cli` by doing: 40 | 41 | ```sh 42 | npm install 43 | ``` 44 | 45 | After doing that, running `warriorjs` should load your tower automatically. 46 | 47 | Once you've tested and adjusted your tower, you're ready to publish it to 48 | [npm](https://npmjs.com) for others to play it. Follow this 49 | [guide](https://docs.npmjs.com/getting-started/publishing-npm-packages) to learn 50 | how to publish a package to npm. 51 | -------------------------------------------------------------------------------- /docs/maker/space-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: space-api 3 | title: Space API 4 | --- 5 | 6 | As a maker, you call the same methods the player call on a sensed space, but on 7 | a regular space. 8 | 9 | ## Class Methods 10 | 11 | Here are the various methods that are available to you: 12 | 13 | ### `space.isEmpty()`: 14 | 15 | Determines if nothing (except maybe stairs) is at this space. 16 | 17 | **Returns** 18 | 19 | _(boolean)_: Whether this space is empty or not. 20 | 21 | ### `space.isStairs()` 22 | 23 | Determines if the stairs are at this space. 24 | 25 | **Returns** 26 | 27 | _(boolean)_: Whether the stairs are at this space or not. 28 | 29 | ### `space.isWall()` 30 | 31 | Determines if this is the edge of the level. 32 | 33 | **Returns** 34 | 35 | _(boolean)_: Whether this space is a wall or not. 36 | 37 | ### `space.isUnit()` 38 | 39 | Determines if there's a unit at this space. 40 | 41 | **Returns** 42 | 43 | _(boolean)_: Whether a unit is at this space or not. 44 | 45 | ### `space.getUnit()` 46 | 47 | Returns the unit located at this space (if any). 48 | 49 | **This unit will be a regular unit, not a sensed unit.** 50 | 51 | **Returns** 52 | 53 | _(Unit)_: The unit at this location or `undefined` if there's none. 54 | 55 | ## Instance Properties 56 | 57 | ### `location` _(number[])_ 58 | 59 | The absolute location of this space as the pair of coordinates `[x, y]`. 60 | -------------------------------------------------------------------------------- /docs/maker/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: testing 3 | title: Testing 4 | --- 5 | 6 | First, you want to make the top of your tower can be reached. And then, you'll 7 | want to fine-tune the time bonus and ace score values for each level. 8 | -------------------------------------------------------------------------------- /docs/player/abilities.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: abilities 3 | title: Abilities 4 | --- 5 | 6 | An **ability** is a skill possessed by a unit. As the player, you activate 7 | abilities during your warrior's turn. 8 | 9 | > Ability selection is the way you can customize how your warrior plays. 10 | 11 | ## Learning new abilities 12 | 13 | When you first start, your warrior will only have a few abilities. Additional 14 | abilities are acquired progressing through the tower. With each level, you'll 15 | learn new things or find artifacts that will expand your capabilities. 16 | 17 | ## Ability types 18 | 19 | There are two types of abilities: actions and senses. 20 | 21 | ### Actions 22 | 23 | An **action** is an ability that affects the game in some way. Is through 24 | actions that you're able to inflict damage, protect yourself or other units, and 25 | interact with your environment. 26 | 27 | > Only one action can be performed per turn, so choose wisely. 28 | 29 | ### Senses 30 | 31 | A **sense**, on the contrary, doesn't affect the game but gathers information 32 | about the floor. You can perform senses as often as you want per turn to collect 33 | information about your surroundings and to aid you in choosing the best action 34 | according to the circumstances. 35 | 36 | > Since what you sense will change each turn, you may want to record the 37 | > information you gather for use on the next turn. For example, you can 38 | > determine if you are being attacked if your health has gone down since the 39 | > last turn. 40 | -------------------------------------------------------------------------------- /docs/player/ai-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: ai-tips 3 | sidebar_label: Artificial Intelligence 4 | title: AI Tips 5 | --- 6 | 7 | - Once you've made some progress in the tower, your code may have turned into a 8 | bunch of nested if/else statements. If that's the case, you may want to apply 9 | some AI concepts like 10 | [FSMs and the State pattern](http://gameprogrammingpatterns.com/state.html), 11 | or more trendy things like 12 | [Behavior Trees](https://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php). 13 | -------------------------------------------------------------------------------- /docs/player/cli-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: cli-tips 3 | sidebar_label: CLI 4 | title: CLI Tips 5 | --- 6 | 7 | - Running `warriorjs` while you are in your profile's directory will auto-select 8 | that profile so you don't have to each time. 9 | 10 | - Make sure to try the different options you can pass to the `warriorjs` 11 | command. Run `warriorjs --help` to see them all. 12 | 13 | * If you're on Windows, consider using [cmder](http://cmder.net) instead of 14 | `cmd.exe`. 15 | -------------------------------------------------------------------------------- /docs/player/effects.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: effects 3 | title: Effects 4 | --- 5 | 6 | An **effect** is any kind of status that affects a unit. They're generally 7 | applied by actions and can have a positive or negative impact. Most effects are 8 | temporary. 9 | -------------------------------------------------------------------------------- /docs/player/epic-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: epic-mode 3 | title: Epic Mode 4 | --- 5 | 6 | Once you reach the top of the tower, you'll have the option to enter **epic 7 | mode**. If you choose so, running `warriorjs` again will run your current 8 | `Player.js` through all levels in the tower without stopping. 9 | 10 | You'll most likely not succeed the first time around in epic mode. If that's the 11 | case, you'll need to make adjustments to your warrior by editing `Player.js`. 12 | 13 | Once your warrior reaches the top again, you will receive a grade for each 14 | level, along with an average grade for the tower. The grades, from best to 15 | worst, are: S, A, B, C, D, and F. Try to get an S on each level for the ultimate 16 | score! 17 | -------------------------------------------------------------------------------- /docs/player/gameplay.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: gameplay 3 | title: Gameplay 4 | --- 5 | 6 | The play happens through a series of turns. On each one and starting with your 7 | warrior, the units in the floor will have the chance to use their abilities. 8 | 9 | ## Code 10 | 11 | Open the `Player.js` file in your profile's directory. You should see some 12 | starting code: 13 | 14 | ```js 15 | class Player { 16 | playTurn(warrior) { 17 | // Cool code goes here. 18 | } 19 | } 20 | ``` 21 | 22 | You need to fill the `playTurn` method with logic to teach the warrior what to 23 | do depending on the situation. 24 | 25 | See the README in your profile's directory for details on what's on the current 26 | level and what abilities your warrior has available to deal with it. 27 | 28 | Here is an example from the "Baby Steps" tower which will instruct the warrior 29 | to walk if there's nothing ahead, otherwise attack: 30 | 31 | ```js 32 | class Player { 33 | playTurn(warrior) { 34 | if (warrior.feel().isEmpty()) { 35 | warrior.walk(); 36 | } else { 37 | warrior.attack(); 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | > This is assuming your warrior has "attack", "feel", and "walk" abilities 44 | > available. 45 | 46 | ## Play 47 | 48 | Once you're done editing `Player.js`, save the file and run the `warriorjs` 49 | command again to start playing the level. 50 | 51 | You cannot change your code in the middle of a level, so you must take into 52 | account everything that may happen on that level and give your warrior the 53 | proper instructions from the start. 54 | 55 | ## Outcome 56 | 57 | Losing all of your health will cause you to fail the level. You're not punished 58 | by this; just go back to the `Player.js` file, improve your code, and try again. 59 | 60 | Once you pass a level (by reaching the stairs), the README will be updated for 61 | the next level. Alter the `Player.js` file and run `warriorjs` again to play the 62 | next level. 63 | -------------------------------------------------------------------------------- /docs/player/general-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: general-tips 3 | sidebar_label: General 4 | title: General Tips 5 | --- 6 | 7 | - **If you ever get stuck on a level, review the README** and be sure you're 8 | trying each ability out. 9 | 10 | - **If you can't keep your health up, be sure to rest when no enemy is around** 11 | (while keeping an eye on your health). Also, try to use far-ranged weapons 12 | whenever possible (such as the bow in the "Baby Steps" tower). 13 | 14 | - **Senses are cheap, so use them liberally.** Store the sensed information to 15 | help you better determine what actions to take in the future. 16 | 17 | - **If you're aiming for points, remember to sweep the area.** Even if you're 18 | close to the stairs, don't go in until you've gotten everything (if you have 19 | the health). Use far-ranged senses (such as "look" and "listen" in the "Baby 20 | Steps" tower) to determine if there are any enemies left. 21 | -------------------------------------------------------------------------------- /docs/player/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: install 3 | title: Install 4 | --- 5 | 6 | Let's start by installing WarriorJS globally with [npm](https://npmjs.com). 7 | 8 | Open the terminal and run: 9 | 10 | ```sh 11 | npm install --global @warriorjs/cli 12 | ``` 13 | 14 | > **IMPORTANT:** [Node.js](https://nodejs.org) >=8 needs to be installed in your 15 | > computer before running the `npm` command. The recommended installation method 16 | > is through the [official installer](https://nodejs.org/en/download). 17 | 18 | After installing the game, you can execute it by running the `warriorjs` command 19 | in the terminal: 20 | 21 | ```sh 22 | warriorjs 23 | ``` 24 | 25 | That's it! This will guide you through the creation of your warrior. Give your 26 | warrior a proper name and choose the "Baby Steps" tower. 27 | 28 | After you've done that, you should have the following file structure (we decided 29 | to name our warrior after the bastard son of Lord Eddard Stark): 30 | 31 | ```sh 32 | warriorjs 33 | └── jon-snow-baby-steps 34 | ├── Player.js 35 | └── README.md 36 | ``` 37 | 38 | - `jon-snow-baby-steps` is your profile's directory. 39 | - `Player.js` is your warrior's brain, you'll be editing this file often. 40 | - `README.md` contains the instructions for the current level. 41 | 42 | Go ahead and open `README.md` to find the instructions for the first level. 43 | -------------------------------------------------------------------------------- /docs/player/js-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: js-tips 3 | sidebar_label: JavaScript 4 | title: JavaScript Tips 5 | --- 6 | 7 | - Don't simply fill up the `playTurn` method with a lot of code, **organize your 8 | code with methods and classes**. For example: 9 | 10 | ```js 11 | class Player { 12 | playTurn(warrior) { 13 | if (this.isInjured(warrior)) { 14 | warrior.rest(); 15 | } 16 | } 17 | 18 | isInjured(warrior) { 19 | return warrior.health() < 20; 20 | } 21 | } 22 | ``` 23 | 24 | - If you want some code to be executed at the beginning of each level, **define 25 | a [constructor][] in the `Player` class**, like this: 26 | 27 | ```js 28 | class Player { 29 | constructor() { 30 | // This code will be executed only once, at the beginning of the level. 31 | this.health = 20; 32 | } 33 | 34 | // ... 35 | } 36 | ``` 37 | 38 | - You can call methods of the Space API directly after a sense. For example, the 39 | "feel" sense in the "Baby Steps" tower returns one space. You can call 40 | `isEmpty()` on this to determine if the space is clear before walking there: 41 | 42 | ```js 43 | class Player { 44 | playTurn(warrior) { 45 | if (warrior.feel().isEmpty()) { 46 | warrior.walk(); 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | - Some senses (like "look" and "listen" in the "Baby Steps" tower) return an 53 | array of spaces instead, so **you might find many of the [Array prototype 54 | methods][] really useful**. Here is an example of the [Array.prototype.find][] 55 | method: 56 | 57 | ```js 58 | class Player { 59 | // ... 60 | 61 | isEnemyInSight(warrior) { 62 | const spaceWithUnit = warrior.look().find(space => space.isUnit()); 63 | return spaceWithUnit && spaceWithUnit.getUnit().isEnemy(); 64 | } 65 | } 66 | ``` 67 | 68 | [constructor]: 69 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor 70 | [array prototype methods]: 71 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods 72 | [array.prototype.find]: 73 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find 74 | -------------------------------------------------------------------------------- /docs/player/object.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: object 3 | title: Object 4 | --- 5 | 6 | The goal of the game is to climb to the top of a tower. You progress through the 7 | tower by reaching the stairs on each level, but the higher you are, the more 8 | difficult it gets. With each level, your abilities will grow along with the 9 | difficulty. But it's up to you to put those abilities to good use and make your 10 | warrior smart enough to face the dangers that stand between him or her and the 11 | stairs. 12 | -------------------------------------------------------------------------------- /docs/player/options.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: options 3 | title: Options 4 | --- 5 | 6 | There are various options you can pass to the `warriorjs` command to customize 7 | the game. You can run `warriorjs --help` to see all the available options. 8 | 9 | Here is a detailed list: 10 | 11 | ## `--directory ` 12 | 13 | Path to a directory under which to run the game. By default, the current working 14 | directory is used. 15 | 16 | ## `--level ` (epic mode only) 17 | 18 | Practice a level. Use this option on levels you are having difficulty or want to 19 | fine-tune the scoring. 20 | 21 | ## `--silent` 22 | 23 | Suppress play log. Use this option if you just care about the outcome of playing 24 | a level, and not each step of the play. 25 | 26 | ## `--time ` 27 | 28 | Delay each turn by seconds. By default, each step of each turn is delayed by 0.6 29 | seconds. 30 | 31 | ## `--yes` 32 | 33 | Assume yes in non-destructive confirmation dialogs. 34 | -------------------------------------------------------------------------------- /docs/player/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: overview 3 | title: Overview 4 | --- 5 | 6 | In WarriorJS, you are a warrior climbing a tall tower to reach _The JavaScript 7 | Sword_ at the top level. Legend has it that the sword bearer becomes enlightened 8 | in the JavaScript language, but be warned: the journey will not be easy. On each 9 | floor, you need to write JavaScript to instruct the warrior to battle enemies, 10 | rescue captives, and reach the stairs alive... 11 | 12 | **No matter if you are new to programming or a JavaScript guru, WarriorJS will 13 | put your skills to the test. Will you dare?** 14 | -------------------------------------------------------------------------------- /docs/player/perspective.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: perspective 3 | title: Perspective 4 | --- 5 | 6 | Even though this is a text-based game, think of it as two-dimensional where you 7 | are viewing the level's floor from overhead. 8 | 9 | Each floor is always rectangular in shape and is made up of a number of squares. 10 | At its edge there are walls; you can't move there. You can move to any other 11 | square, including the stairs, that isn't already occupied by another unit (only 12 | one unit can be on a given square at a time). 13 | 14 | Here is an example of a floor map and key: 15 | 16 | ``` 17 | ╔════╗ 18 | ║C s>║ 19 | ║ S s║ 20 | ║C @ ║ 21 | ╚════╝ 22 | 23 | > = stairs 24 | @ = Jon Snow (20 HP) 25 | s = Sludge (12 HP) 26 | S = Thick Sludge (24 HP) 27 | C = Captive (1 HP) 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/player/scoring.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: scoring 3 | title: Scoring 4 | --- 5 | 6 | Your objective is to not only reach the stairs, but to get the highest score you 7 | can. 8 | 9 | There are many ways you can earn points on a level: 10 | 11 | - **Defeat an enemy** to add his max health to your score. 12 | 13 | - **Rescue a captive** to earn a reward. 14 | 15 | - **Pass the level within the bonus time** to earn the amount of bonus time 16 | remaining (each level has a bonus time that decreases turn by turn). 17 | 18 | - **Defeat all enemies and rescue all captives** to receive a 20% overall bonus. 19 | 20 | But you must be careful, because you can also lose points: 21 | 22 | - **Kill a captive** and you'll receive a penalty. 23 | 24 | A total score is kept as you progress through the levels. When you pass a level, 25 | that score is added to your total. 26 | -------------------------------------------------------------------------------- /docs/player/space-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: space-api 3 | title: Space API 4 | --- 5 | 6 | Whenever you sense an area, often one or multiple spaces (in an array) will be 7 | returned. For example, the "feel" sense in the "Baby Steps" tower returns one 8 | space: 9 | 10 | ```js 11 | const space = warrior.feel(); 12 | ``` 13 | 14 | You can call methods on a space to gather information about what's there. 15 | 16 | ## Class Methods 17 | 18 | Here are the various methods that are available to you: 19 | 20 | ### `space.getLocation()`: 21 | 22 | Returns the relative location of this space as the number of spaces forward and 23 | to the right of your position. 24 | 25 | **Returns** 26 | 27 | _(number[])_: The relative location of this space as the offset 28 | `[forward, right]`. 29 | 30 | ### `space.isEmpty()`: 31 | 32 | Determines if nothing (except maybe stairs) is at this space. 33 | 34 | **Returns** 35 | 36 | _(boolean)_: Whether this space is empty or not. 37 | 38 | ### `space.isStairs()` 39 | 40 | Determines if the stairs are at this space. 41 | 42 | **Returns** 43 | 44 | _(boolean)_: Whether the stairs are at this space or not. 45 | 46 | ### `space.isWall()` 47 | 48 | Determines if this is the edge of the level. 49 | 50 | **Returns** 51 | 52 | _(boolean)_: Whether this space is a wall or not. 53 | 54 | ### `space.isUnit()` 55 | 56 | Determines if there's a unit at this space. 57 | 58 | **Returns** 59 | 60 | _(boolean)_: Whether a unit is at this space or not. 61 | 62 | ### `space.getUnit()` 63 | 64 | Returns the unit located at this space (if any). 65 | 66 | **Returns** 67 | 68 | _(Unit)_: The unit at this location or `undefined` if there's none. 69 | -------------------------------------------------------------------------------- /docs/player/spaces.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: spaces 3 | title: Spaces 4 | --- 5 | 6 | A **space** is an object representing a square in the floor. 7 | 8 | A space can be empty, or it can have a wall or a unit located at it. One of the 9 | spaces in the floor has the stairs, which you need to climb to move on to the 10 | next level. 11 | -------------------------------------------------------------------------------- /docs/player/towers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: towers 3 | title: Towers 4 | --- 5 | 6 | A **tower** is a WarriorJS world. In addition to defining levels, towers can 7 | also add new abilities, effects, and units to the game. 8 | 9 | WarriorJS CLI ships with an entry-level tower built-in. You'll need to install 10 | any additional tower you want to play. 11 | 12 | ## Installing Towers 13 | 14 | Towers are automatically loaded if you have them installed in the same 15 | `node_modules` directory where `@warriorjs/cli` is located. This means that if 16 | you have installed the game globally, you'll need to install additional towers 17 | globally. If, on the other hand, you're running the game from a local 18 | installation, you'll need to install additional towers locally. 19 | 20 | Tower package names start with `@warriorjs/tower-` for official towers, or 21 | `warriorjs-tower-` for community towers. 22 | 23 | ### Official Towers 24 | 25 | - [`@warriorjs/tower-baby-steps`][warriorjs-tower-baby-steps] 26 | - [`@warriorjs/tower-tick-tick-boom`][warriorjs-tower-tick-tick-boom] (beta) 27 | 28 | ### Community Towers 29 | 30 | Have you made a tower? [Add it][add-community-tower] to the list! 31 | 32 | ## Making Towers 33 | 34 | Follow this [guide](maker/introduction.md). 35 | 36 | [warriorjs-tower-baby-steps]: 37 | https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-tower-baby-steps 38 | [warriorjs-tower-tick-tick-boom]: 39 | https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-tower-tick-tick-boom 40 | [add-community-tower]: 41 | https://github.com/olistic/warriorjs/edit/master/docs/player/towers.md 42 | -------------------------------------------------------------------------------- /docs/player/turn-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: turn-api 3 | title: Turn API 4 | --- 5 | 6 | The `playTurn` method in `Player.js` gets passed an instance of your warrior's 7 | turn. The methods you can call on that turn are determined by the abilities your 8 | warrior has available in the current level. See the README in your profile's 9 | directory to find that out. 10 | 11 | Here is an example extracted from the README of the second level in the "Baby 12 | Steps" tower: 13 | 14 | ```markdown 15 | ### Abilities 16 | 17 | #### Actions 18 | 19 | - `warrior.attack()` 20 | - `warrior.walk()` 21 | 22 | #### Senses 23 | 24 | - `warrior.feel()` 25 | ``` 26 | 27 | In this level, your warrior has the abilities "attack", "feel", and "walk", 28 | which means you can call these three methods on your turn: `warrior.attack()`, 29 | `warrior.feel()`, and `warrior.walk()`. 30 | 31 | > Many abilities can be performed in the following directions: "forward", 32 | > "backward", "left", and "right". You have to pass a string with the direction 33 | > as the first argument, e.g. `warrior.walk('backward')`. 34 | -------------------------------------------------------------------------------- /docs/player/unit-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: unit-api 3 | title: Unit API 4 | --- 5 | 6 | You can call `getUnit()` on a space to retrieve the unit located there (but keep 7 | in mind that not all spaces have units on them): 8 | 9 | ```js 10 | const unit = space.getUnit(); 11 | ``` 12 | 13 | You can call methods on a unit to know more about it. 14 | 15 | ## Class Methods 16 | 17 | Here are the various methods that are available to you: 18 | 19 | ### `unit.isBound()` 20 | 21 | Determines if the unit is bound. 22 | 23 | **Returns** 24 | 25 | _(boolean)_: Whether this unit is bound or not. 26 | 27 | ### `unit.isEnemy()`: 28 | 29 | Determines if the unit is an enemy. 30 | 31 | **Returns** 32 | 33 | _(boolean)_: Whether this is an enemy unit or not. 34 | -------------------------------------------------------------------------------- /docs/player/units.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: units 3 | title: Units 4 | --- 5 | 6 | A **unit** is any character that populates the floors of the tower, including 7 | your warrior. 8 | 9 | ## Attributes 10 | 11 | A unit has the following attributes: 12 | 13 | - **Health**: the total damage the unit may take before dying, measured in 14 | Health Points (HP). 15 | - **Max Health**: the starting Health value. 16 | 17 | ## Abilities & Effects 18 | 19 | A unit can also have abilities and be under effects. 20 | -------------------------------------------------------------------------------- /docs/player/warrior.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: warrior 3 | title: Warrior 4 | --- 5 | 6 | The **warrior** is the player-character in WarriorJS, which means it's 7 | controlled by you. To create a warrior, you'll be guided through a two-step 8 | process where you'll determine your warrior's name and the tower you want to 9 | climb. Once created, you'll use the warrior to progress through that tower. 10 | 11 | > The warrior and tower combination is usually referred to as a **profile**. The 12 | > number of profiles you can have is unlimited, but you can't use the same 13 | > warrior/tower combination twice. 14 | 15 | A warrior has the same attributes that any other unit. It also has abilities and 16 | can be under effects. 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | collectCoverageFrom: [ 4 | 'packages/**/src/**/*.js', 5 | '!packages/warriorjs-tower-**/src/**', 6 | ], 7 | roots: ['/packages'], 8 | testEnvironment: 'node', 9 | }; 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.9.0", 3 | "version": "0.14.0", 4 | "command": { 5 | "bootstrap": { 6 | "ignore": "warriorjs-website" 7 | }, 8 | "publish": { 9 | "allowBranch": "master", 10 | "ignore": "warriorjs-website" 11 | } 12 | }, 13 | "npmClient": "yarn", 14 | "useWorkspaces": true 15 | } 16 | -------------------------------------------------------------------------------- /logo/README.md: -------------------------------------------------------------------------------- 1 |

2 | The WarriorJS logo concept, in dark. 6 |

7 |

8 | The WarriorJS logo concept, in light. 12 |

13 | -------------------------------------------------------------------------------- /logo/warriorjs-avatar-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-avatar-dark.png -------------------------------------------------------------------------------- /logo/warriorjs-avatar-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-avatar-light.png -------------------------------------------------------------------------------- /logo/warriorjs-banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-banner-dark.png -------------------------------------------------------------------------------- /logo/warriorjs-banner-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-banner-light.png -------------------------------------------------------------------------------- /logo/warriorjs-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-icon-dark.png -------------------------------------------------------------------------------- /logo/warriorjs-icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-icon-light.png -------------------------------------------------------------------------------- /logo/warriorjs-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-logo-dark.png -------------------------------------------------------------------------------- /logo/warriorjs-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs-logo-light.png -------------------------------------------------------------------------------- /logo/warriorjs.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/logo/warriorjs.sketch -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "packages/*", 5 | "website" 6 | ], 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs", 9 | "scripts": { 10 | "bootstrap": "lerna bootstrap", 11 | "release": "yarn clean:build && yarn build && yarn test && lerna publish", 12 | "build": "lerna run build --stream --ignore warriorjs-website", 13 | "clean": "yarn clean:build & yarn clean:coverage & yarn clean:modules", 14 | "clean:build": "rm -rf packages/*/lib", 15 | "clean:coverage": "rm -rf coverage", 16 | "clean:modules": "lerna clean --yes", 17 | "lint": "eslint --cache packages/**/src", 18 | "lint:fix": "yarn lint --fix", 19 | "pretest": "yarn lint", 20 | "test": "jest", 21 | "test:coverage": "yarn test --coverage", 22 | "test:watch": "yarn test --watch" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-plugin-add-module-exports": "^1.0.0", 27 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 28 | "babel-preset-env": "^1.7.0", 29 | "eslint": "^5.9.0", 30 | "eslint-config-airbnb": "^17.1.0", 31 | "eslint-config-prettier": "^3.3.0", 32 | "eslint-plugin-import": "^2.14.0", 33 | "eslint-plugin-jsx-a11y": "^6.1.2", 34 | "eslint-plugin-prettier": "^3.0.0", 35 | "eslint-plugin-react": "^7.11.1", 36 | "husky": "^1.2.0", 37 | "jest": "^23.6.0", 38 | "lerna": "^2.11.0", 39 | "lint-staged": "^8.1.0", 40 | "prettier": "1.15.3" 41 | }, 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "lint-staged" 45 | } 46 | }, 47 | "lint-staged": { 48 | "*.{js,json,css,md,yaml}": [ 49 | "prettier --write", 50 | "git add" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/abilities 2 | 3 | > WarriorJS official abilities. 4 | 5 | ## [Actions][actions] 6 | 7 | ### `unit.attack([direction])`: 8 | 9 | Attack a unit in the given direction (forward by default) dealing `[power]` HP 10 | of damage. 11 | 12 | ### `unit.bind([direction])`: 13 | 14 | Bind a unit in the given direction (forward by default) to keep him from moving. 15 | 16 | ### `unit.detonate([direction])`: 17 | 18 | Detonate a bomb in a given direction (forward by default) dealing 19 | `[targetPower]` HP of damage to that space and `[surroundingPower]` HP of damage 20 | to surrounding 4 spaces (including yourself). 21 | 22 | ### `unit.pivot([direction])`: 23 | 24 | Rotate in the given direction (backward by default). 25 | 26 | ### `unit.rescue([direction])`: 27 | 28 | Release a unit from his chains in the given direction (forward by default). 29 | 30 | ### `unit.rest()`: 31 | 32 | Gain `[healthGainPercentage]` of max health back, but do nothing more. 33 | 34 | ### `unit.shoot([direction])`: 35 | 36 | Shoot your bow & arrow in the given direction (forward by default) dealing 37 | `[power]` HP of damage to the first unit in a range of `[range]` spaces. 38 | 39 | ### `unit.walk([direction])`: 40 | 41 | Move one space in the given direction (forward by default). 42 | 43 | ## [Senses][senses] 44 | 45 | ### `unit.directionOf(space)`: 46 | 47 | Return the direction (forward, right, backward or left) to the given 48 | [space][spaces]. 49 | 50 | ### `unit.directionOfStairs()`: 51 | 52 | Return the direction (forward, right, backward or left) the stairs are from your 53 | location. 54 | 55 | ### `unit.distanceOf(space)`: 56 | 57 | Return an integer representing the distance to the given [space][spaces]. 58 | 59 | ### `unit.feel([direction])`: 60 | 61 | Return the adjacent [space][spaces] in the given direction (forward by default). 62 | 63 | ### `unit.health()`: 64 | 65 | Return an integer representing your health. 66 | 67 | ### `unit.listen()`: 68 | 69 | Return an array of all [spaces][spaces] which have units in them (excluding 70 | yourself). 71 | 72 | ### `unit.look([direction])`: 73 | 74 | Returns an array of up to `[range]` [spaces][spaces] in the given direction 75 | (forward by default). 76 | 77 | ### `unit.think(thought)`: 78 | 79 | Think out loud (`console.log` replacement). 80 | 81 | [actions]: https://warrior.js.org/docs/player/abilities#actions 82 | [senses]: https://warrior.js.org/docs/player/abilities#senses 83 | [spaces]: https://warrior.js.org/docs/player/spaces 84 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/abilities", 3 | "version": "0.13.0", 4 | "description": "WarriorJS base abilities", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-abilities", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | }, 19 | "dependencies": { 20 | "@warriorjs/geography": "^0.7.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/attack.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function attack({ power }) { 6 | return unit => ({ 7 | action: true, 8 | description: `Attacks a unit in the given direction (\`'${defaultDirection}'\` by default), dealing ${power} HP of damage.`, 9 | perform(direction = defaultDirection) { 10 | const receiver = unit.getSpaceAt(direction).getUnit(); 11 | if (receiver) { 12 | unit.log(`attacks ${direction} and hits ${receiver}`); 13 | const attackingBackward = direction === BACKWARD; 14 | const amount = attackingBackward ? Math.ceil(power / 2.0) : power; 15 | unit.damage(receiver, amount); 16 | } else { 17 | unit.log(`attacks ${direction} and hits nothing`); 18 | } 19 | }, 20 | }); 21 | } 22 | 23 | export default attack; 24 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/attack.test.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD, LEFT } from '@warriorjs/geography'; 2 | 3 | import attackCreator from './attack'; 4 | 5 | describe('attack', () => { 6 | let attack; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { 11 | damage: jest.fn(), 12 | log: jest.fn(), 13 | }; 14 | attack = attackCreator({ power: 3 })(unit); 15 | }); 16 | 17 | test('is an action', () => { 18 | expect(attack.action).toBe(true); 19 | }); 20 | 21 | test('has a description', () => { 22 | expect(attack.description).toBe( 23 | `Attacks a unit in the given direction (\`'${FORWARD}'\` by default), dealing 3 HP of damage.`, 24 | ); 25 | }); 26 | 27 | describe('performing', () => { 28 | test('attacks forward by default', () => { 29 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 30 | attack.perform(); 31 | expect(unit.getSpaceAt).toHaveBeenCalledWith(FORWARD); 32 | }); 33 | 34 | test('allows to specify direction', () => { 35 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 36 | attack.perform(LEFT); 37 | expect(unit.getSpaceAt).toHaveBeenCalledWith(LEFT); 38 | }); 39 | 40 | test('misses if no receiver', () => { 41 | unit.getSpaceAt = () => ({ getUnit: () => null }); 42 | attack.perform(); 43 | expect(unit.log).toHaveBeenCalledWith( 44 | `attacks ${FORWARD} and hits nothing`, 45 | ); 46 | expect(unit.damage).not.toHaveBeenCalled(); 47 | }); 48 | 49 | describe('with receiver', () => { 50 | beforeEach(() => { 51 | unit.getSpaceAt = () => ({ getUnit: () => 'receiver' }); 52 | }); 53 | 54 | test('damages receiver', () => { 55 | attack.perform(); 56 | expect(unit.log).toHaveBeenCalledWith( 57 | `attacks ${FORWARD} and hits receiver`, 58 | ); 59 | expect(unit.damage).toHaveBeenCalledWith('receiver', 3); 60 | }); 61 | 62 | test('reduces power when attacking backward', () => { 63 | attack.perform(BACKWARD); 64 | expect(unit.log).toHaveBeenCalledWith( 65 | `attacks ${BACKWARD} and hits receiver`, 66 | ); 67 | expect(unit.damage).toHaveBeenCalledWith('receiver', 2); 68 | }); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/bind.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function bind() { 6 | return unit => ({ 7 | action: true, 8 | description: `Binds a unit in the given direction (\`'${defaultDirection}'\` by default) to keep him from moving.`, 9 | perform(direction = defaultDirection) { 10 | const receiver = unit.getSpaceAt(direction).getUnit(); 11 | if (receiver) { 12 | unit.log(`binds ${direction} and restricts ${receiver}`); 13 | receiver.bind(); 14 | } else { 15 | unit.log(`binds ${direction} and restricts nothing`); 16 | } 17 | }, 18 | }); 19 | } 20 | 21 | export default bind; 22 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/bind.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, LEFT } from '@warriorjs/geography'; 2 | 3 | import bindCreator from './bind'; 4 | 5 | describe('bind', () => { 6 | let bind; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { log: jest.fn() }; 11 | bind = bindCreator()(unit); 12 | }); 13 | 14 | test('is an action', () => { 15 | expect(bind.action).toBe(true); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(bind.description).toBe( 20 | `Binds a unit in the given direction (\`'${FORWARD}'\` by default) to keep him from moving.`, 21 | ); 22 | }); 23 | 24 | describe('performing', () => { 25 | test('binds forward by default', () => { 26 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 27 | bind.perform(); 28 | expect(unit.getSpaceAt).toHaveBeenCalledWith(FORWARD); 29 | }); 30 | 31 | test('allows to specify direction', () => { 32 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 33 | bind.perform(LEFT); 34 | expect(unit.getSpaceAt).toHaveBeenCalledWith(LEFT); 35 | }); 36 | 37 | test('misses if no receiver', () => { 38 | unit.getSpaceAt = () => ({ getUnit: () => null }); 39 | bind.perform(); 40 | expect(unit.log).toHaveBeenCalledWith( 41 | `binds ${FORWARD} and restricts nothing`, 42 | ); 43 | }); 44 | 45 | describe('with receiver', () => { 46 | let receiver; 47 | 48 | beforeEach(() => { 49 | receiver = { 50 | bind: jest.fn(), 51 | toString: () => 'receiver', 52 | }; 53 | unit.getSpaceAt = () => ({ getUnit: () => receiver }); 54 | }); 55 | 56 | test('binds receiver', () => { 57 | bind.perform(); 58 | expect(unit.log).toHaveBeenCalledWith( 59 | `binds ${FORWARD} and restricts receiver`, 60 | ); 61 | expect(receiver.bind).toHaveBeenCalled(); 62 | }); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/detonate.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | const surroundingOffsets = [[1, 1], [1, -1], [2, 0], [0, 0]]; 5 | 6 | function detonate({ targetPower, surroundingPower }) { 7 | return unit => ({ 8 | action: true, 9 | description: `Detonates a bomb in a given direction (\`'${defaultDirection}'\` by default), dealing ${targetPower} HP of damage to that space and ${surroundingPower} HP of damage to surrounding 4 spaces (including yourself).`, 10 | perform(direction = defaultDirection) { 11 | unit.log(`detonates a bomb ${direction} launching a deadly explosion`); 12 | const targetSpace = unit.getSpaceAt(direction); 13 | this.bomb(targetSpace, targetPower); 14 | surroundingOffsets 15 | .map(([forward, right]) => unit.getSpaceAt(direction, forward, right)) 16 | .forEach(surroundingSpace => { 17 | this.bomb(surroundingSpace, surroundingPower); 18 | }); 19 | }, 20 | bomb(space, power) { 21 | const receiver = space.getUnit(); 22 | if (receiver) { 23 | unit.damage(receiver, power); 24 | if (receiver.isUnderEffect('ticking')) { 25 | receiver.log( 26 | "caught in bomb's flames which detonates ticking explosive", 27 | ); 28 | receiver.triggerEffect('ticking'); 29 | } 30 | } 31 | }, 32 | }); 33 | } 34 | 35 | export default detonate; 36 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/directionOf.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD, LEFT, RIGHT } from '@warriorjs/geography'; 2 | 3 | function directionOf() { 4 | return unit => ({ 5 | description: `Returns the direction (${FORWARD}, ${RIGHT}, ${BACKWARD} or ${LEFT}) to the given space.`, 6 | perform(space) { 7 | return unit.getDirectionOf(space); 8 | }, 9 | }); 10 | } 11 | 12 | export default directionOf; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/directionOf.test.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD, LEFT, RIGHT } from '@warriorjs/geography'; 2 | 3 | import directionOfCreator from './directionOf'; 4 | 5 | describe('directionOf', () => { 6 | let directionOf; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { getDirectionOf: jest.fn() }; 11 | directionOf = directionOfCreator()(unit); 12 | }); 13 | 14 | test('is not an action', () => { 15 | expect(directionOf.action).toBeUndefined(); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(directionOf.description).toBe( 20 | `Returns the direction (${FORWARD}, ${RIGHT}, ${BACKWARD} or ${LEFT}) to the given space.`, 21 | ); 22 | }); 23 | 24 | describe('performing', () => { 25 | test('returns direction of specified space', () => { 26 | unit.getDirectionOf.mockReturnValue(RIGHT); 27 | expect(directionOf.perform()).toBe(RIGHT); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/directionOfStairs.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD, LEFT, RIGHT } from '@warriorjs/geography'; 2 | 3 | function directionOfStairs() { 4 | return unit => ({ 5 | description: `Returns the direction (${FORWARD}, ${RIGHT}, ${BACKWARD} or ${LEFT}) the stairs are from your location.`, 6 | perform() { 7 | return unit.getDirectionOfStairs(); 8 | }, 9 | }); 10 | } 11 | 12 | export default directionOfStairs; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/directionOfStairs.test.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, FORWARD, LEFT, RIGHT } from '@warriorjs/geography'; 2 | 3 | import directionOfStairsCreator from './directionOfStairs'; 4 | 5 | describe('directionOfStairs', () => { 6 | let directionOfStairs; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { getDirectionOfStairs: jest.fn() }; 11 | directionOfStairs = directionOfStairsCreator()(unit); 12 | }); 13 | 14 | test('is not an action', () => { 15 | expect(directionOfStairs.action).toBeUndefined(); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(directionOfStairs.description).toBe( 20 | `Returns the direction (${FORWARD}, ${RIGHT}, ${BACKWARD} or ${LEFT}) the stairs are from your location.`, 21 | ); 22 | }); 23 | 24 | describe('performing', () => { 25 | test('returns direction of stairs', () => { 26 | unit.getDirectionOfStairs.mockReturnValue(RIGHT); 27 | expect(directionOfStairs.perform()).toBe(RIGHT); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/distanceOf.js: -------------------------------------------------------------------------------- 1 | function distanceOf() { 2 | return unit => ({ 3 | description: 4 | 'Returns an integer representing the distance to the given space.', 5 | perform(space) { 6 | return unit.getDistanceOf(space); 7 | }, 8 | }); 9 | } 10 | 11 | export default distanceOf; 12 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/distanceOf.test.js: -------------------------------------------------------------------------------- 1 | import distanceOfCreator from './distanceOf'; 2 | 3 | describe('distanceOf', () => { 4 | let distanceOf; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { getDistanceOf: jest.fn() }; 9 | distanceOf = distanceOfCreator()(unit); 10 | }); 11 | 12 | test('is not an action', () => { 13 | expect(distanceOf.action).toBeUndefined(); 14 | }); 15 | 16 | test('has a description', () => { 17 | expect(distanceOf.description).toBe( 18 | 'Returns an integer representing the distance to the given space.', 19 | ); 20 | }); 21 | 22 | describe('performing', () => { 23 | test('returns distance of specified space', () => { 24 | unit.getDistanceOf.mockReturnValue(3); 25 | expect(distanceOf.perform()).toBe(3); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/feel.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function feel() { 6 | return unit => ({ 7 | description: `Returns the adjacent space in the given direction (\`'${defaultDirection}'\` by default).`, 8 | perform(direction = defaultDirection) { 9 | return unit.getSensedSpaceAt(direction); 10 | }, 11 | }); 12 | } 13 | 14 | export default feel; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/feel.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, LEFT } from '@warriorjs/geography'; 2 | 3 | import feelCreator from './feel'; 4 | 5 | describe('feel', () => { 6 | let feel; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { getSensedSpaceAt: jest.fn() }; 11 | feel = feelCreator()(unit); 12 | }); 13 | 14 | test('is not an action', () => { 15 | expect(feel.action).toBeUndefined(); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(feel.description).toBe( 20 | `Returns the adjacent space in the given direction (\`'${FORWARD}'\` by default).`, 21 | ); 22 | }); 23 | 24 | describe('performing', () => { 25 | test('feels forward by default', () => { 26 | feel.perform(); 27 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD); 28 | }); 29 | 30 | test('allows to specify direction', () => { 31 | feel.perform(LEFT); 32 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(LEFT); 33 | }); 34 | 35 | test('returns adjacent space in specified direction', () => { 36 | unit.getSensedSpaceAt.mockReturnValue('space'); 37 | expect(feel.perform()).toBe('space'); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/health.js: -------------------------------------------------------------------------------- 1 | function health() { 2 | return unit => ({ 3 | description: 'Returns an integer representing your health.', 4 | perform() { 5 | return unit.health; 6 | }, 7 | }); 8 | } 9 | 10 | export default health; 11 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/health.test.js: -------------------------------------------------------------------------------- 1 | import healthCreator from './health'; 2 | 3 | describe('health', () => { 4 | let health; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { health: 10 }; 9 | health = healthCreator()(unit); 10 | }); 11 | 12 | test('is not an action', () => { 13 | expect(health.action).toBeUndefined(); 14 | }); 15 | 16 | test('has a description', () => { 17 | expect(health.description).toBe( 18 | 'Returns an integer representing your health.', 19 | ); 20 | }); 21 | 22 | describe('performing', () => { 23 | test('returns the amount of health', () => { 24 | expect(health.perform()).toBe(10); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as attack } from './attack'; 2 | export { default as bind } from './bind'; 3 | export { default as detonate } from './detonate'; 4 | export { default as directionOf } from './directionOf'; 5 | export { default as directionOfStairs } from './directionOfStairs'; 6 | export { default as distanceOf } from './distanceOf'; 7 | export { default as feel } from './feel'; 8 | export { default as health } from './health'; 9 | export { default as listen } from './listen'; 10 | export { default as look } from './look'; 11 | export { default as maxHealth } from './maxHealth'; 12 | export { default as pivot } from './pivot'; 13 | export { default as rescue } from './rescue'; 14 | export { default as rest } from './rest'; 15 | export { default as shoot } from './shoot'; 16 | export { default as think } from './think'; 17 | export { default as walk } from './walk'; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/listen.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, getRelativeOffset } from '@warriorjs/geography'; 2 | 3 | function listen() { 4 | return unit => ({ 5 | description: 6 | 'Returns an array of all spaces which have units in them (excluding yourself).', 7 | perform() { 8 | return unit 9 | .getOtherUnits() 10 | .map(anotherUnit => 11 | getRelativeOffset( 12 | anotherUnit.getSpace().location, 13 | unit.position.location, 14 | unit.position.orientation, 15 | ), 16 | ) 17 | .map(([forward, right]) => 18 | unit.getSensedSpaceAt(FORWARD, forward, right), 19 | ); 20 | }, 21 | }); 22 | } 23 | 24 | export default listen; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/listen.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, NORTH } from '@warriorjs/geography'; 2 | 3 | import listenCreator from './listen'; 4 | 5 | describe('listen', () => { 6 | let listen; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { 11 | position: { 12 | location: [1, 1], 13 | orientation: NORTH, 14 | }, 15 | getOtherUnits: () => [ 16 | { getSpace: () => ({ location: [0, 0] }) }, 17 | { getSpace: () => ({ location: [2, 3] }) }, 18 | ], 19 | getSensedSpaceAt: jest.fn(), 20 | }; 21 | listen = listenCreator()(unit); 22 | }); 23 | 24 | test('is not an action', () => { 25 | expect(listen.action).toBeUndefined(); 26 | }); 27 | 28 | test('has a description', () => { 29 | expect(listen.description).toBe( 30 | 'Returns an array of all spaces which have units in them (excluding yourself).', 31 | ); 32 | }); 33 | 34 | describe('performing', () => { 35 | test('returns all spaces which have units in them', () => { 36 | unit.getSensedSpaceAt 37 | .mockReturnValueOnce('space1') 38 | .mockReturnValueOnce('space2'); 39 | expect(listen.perform()).toEqual(['space1', 'space2']); 40 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD, 1, -1); 41 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD, -2, 1); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/look.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function look({ range }) { 6 | return unit => ({ 7 | description: `Returns an array of up to ${range} spaces in the given direction (\`'${defaultDirection}'\` by default).`, 8 | perform(direction = defaultDirection) { 9 | const offsets = Array.from(new Array(range), (_, index) => index + 1); 10 | const spaces = offsets.map(offset => 11 | unit.getSensedSpaceAt(direction, offset), 12 | ); 13 | const firstWallIndex = spaces.findIndex(space => space && space.isWall()); 14 | return firstWallIndex === -1 15 | ? spaces 16 | : spaces.slice(0, firstWallIndex + 1); 17 | }, 18 | }); 19 | } 20 | 21 | export default look; 22 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/look.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, LEFT } from '@warriorjs/geography'; 2 | 3 | import lookCreator from './look'; 4 | 5 | describe('look', () => { 6 | let look; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { getSensedSpaceAt: jest.fn() }; 11 | look = lookCreator({ range: 3 })(unit); 12 | }); 13 | 14 | test('is not an action', () => { 15 | expect(look.action).toBeUndefined(); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(look.description).toBe( 20 | `Returns an array of up to 3 spaces in the given direction (\`'${FORWARD}'\` by default).`, 21 | ); 22 | }); 23 | 24 | describe('performing', () => { 25 | test('looks forward by default', () => { 26 | look.perform(); 27 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD, 1); 28 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD, 2); 29 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(FORWARD, 3); 30 | }); 31 | 32 | test('allows to specify direction', () => { 33 | look.perform(LEFT); 34 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(LEFT, 1); 35 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(LEFT, 2); 36 | expect(unit.getSensedSpaceAt).toHaveBeenCalledWith(LEFT, 3); 37 | }); 38 | 39 | test('returns spaces in range in specified direction', () => { 40 | const space1 = { isWall: () => false }; 41 | const space2 = { isWall: () => false }; 42 | const space3 = { isWall: () => false }; 43 | const space4 = { isWall: () => false }; 44 | 45 | unit.getSensedSpaceAt 46 | .mockReturnValueOnce(space1) 47 | .mockReturnValueOnce(space2) 48 | .mockReturnValueOnce(space3) 49 | .mockReturnValueOnce(space4); 50 | expect(look.perform()).toEqual([space1, space2, space3]); 51 | }); 52 | 53 | test("can't see through walls", () => { 54 | const space1 = { isWall: () => false }; 55 | const space2 = { isWall: () => true }; 56 | const space3 = { isWall: () => false }; 57 | 58 | unit.getSensedSpaceAt 59 | .mockReturnValueOnce(space1) 60 | .mockReturnValueOnce(space2) 61 | .mockReturnValueOnce(space3); 62 | 63 | expect(look.perform()).toEqual([space1, space2]); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/maxHealth.js: -------------------------------------------------------------------------------- 1 | function maxHealth() { 2 | return unit => ({ 3 | description: 'Returns an integer representing your maximum health.', 4 | perform() { 5 | return unit.maxHealth; 6 | }, 7 | }); 8 | } 9 | 10 | export default maxHealth; 11 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/maxHealth.test.js: -------------------------------------------------------------------------------- 1 | import maxHealthCreator from './maxHealth'; 2 | 3 | describe('maxHealth', () => { 4 | let maxHealth; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { maxHealth: 10 }; 9 | maxHealth = maxHealthCreator()(unit); 10 | }); 11 | 12 | test('is not an action', () => { 13 | expect(maxHealth.action).toBeUndefined(); 14 | }); 15 | 16 | test('has a description', () => { 17 | expect(maxHealth.description).toBe( 18 | 'Returns an integer representing your maximum health.', 19 | ); 20 | }); 21 | 22 | describe('performing', () => { 23 | test('returns the maximum health', () => { 24 | expect(maxHealth.perform()).toBe(10); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/pivot.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = BACKWARD; 4 | 5 | function pivot() { 6 | return unit => ({ 7 | action: true, 8 | description: `Rotates in the given direction (\`'${defaultDirection}'\` by default).`, 9 | perform(direction = defaultDirection) { 10 | unit.rotate(direction); 11 | unit.log(`pivots ${direction}`); 12 | }, 13 | }); 14 | } 15 | 16 | export default pivot; 17 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/pivot.test.js: -------------------------------------------------------------------------------- 1 | import { BACKWARD, RIGHT } from '@warriorjs/geography'; 2 | 3 | import pivotCreator from './pivot'; 4 | 5 | describe('pivot', () => { 6 | let pivot; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { 11 | rotate: jest.fn(), 12 | log: jest.fn(), 13 | }; 14 | pivot = pivotCreator()(unit); 15 | }); 16 | 17 | test('is an action', () => { 18 | expect(pivot.action).toBe(true); 19 | }); 20 | 21 | test('has a description', () => { 22 | expect(pivot.description).toBe( 23 | `Rotates in the given direction (\`'${BACKWARD}'\` by default).`, 24 | ); 25 | }); 26 | 27 | describe('performing', () => { 28 | test('flips around when not passing direction', () => { 29 | pivot.perform(); 30 | expect(unit.log).toHaveBeenCalledWith(`pivots ${BACKWARD}`); 31 | expect(unit.rotate).toHaveBeenCalledWith(BACKWARD); 32 | }); 33 | 34 | test('rotates in specified direction', () => { 35 | pivot.perform(RIGHT); 36 | expect(unit.log).toHaveBeenCalledWith(`pivots ${RIGHT}`); 37 | expect(unit.rotate).toHaveBeenCalledWith(RIGHT); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/rescue.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function rescue() { 6 | return unit => ({ 7 | action: true, 8 | description: `Releases a unit from his chains in the given direction (\`'${defaultDirection}'\` by default).`, 9 | perform(direction = defaultDirection) { 10 | const receiver = unit.getSpaceAt(direction).getUnit(); 11 | if (receiver && receiver.isBound()) { 12 | unit.log(`unbinds ${direction} and rescues ${receiver}`); 13 | unit.release(receiver); 14 | } else { 15 | unit.log(`unbinds ${direction} and rescues nothing`); 16 | } 17 | }, 18 | }); 19 | } 20 | 21 | export default rescue; 22 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/rescue.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, RIGHT } from '@warriorjs/geography'; 2 | 3 | import rescueCreator from './rescue'; 4 | 5 | describe('rescue', () => { 6 | let rescue; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { 11 | release: jest.fn(), 12 | log: jest.fn(), 13 | }; 14 | rescue = rescueCreator()(unit); 15 | }); 16 | 17 | test('is an action', () => { 18 | expect(rescue.action).toBe(true); 19 | }); 20 | 21 | test('has a description', () => { 22 | expect(rescue.description).toBe( 23 | `Releases a unit from his chains in the given direction (\`'${FORWARD}'\` by default).`, 24 | ); 25 | }); 26 | 27 | describe('performing', () => { 28 | test('rescues forward by default', () => { 29 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 30 | rescue.perform(); 31 | expect(unit.getSpaceAt).toHaveBeenCalledWith(FORWARD); 32 | }); 33 | 34 | test('allows to specify direction', () => { 35 | unit.getSpaceAt = jest.fn(() => ({ getUnit: () => null })); 36 | rescue.perform(RIGHT); 37 | expect(unit.getSpaceAt).toHaveBeenCalledWith(RIGHT); 38 | }); 39 | 40 | test('misses if no receiver', () => { 41 | unit.getSpaceAt = () => ({ getUnit: () => null }); 42 | rescue.perform(); 43 | expect(unit.log).toHaveBeenCalledWith( 44 | `unbinds ${FORWARD} and rescues nothing`, 45 | ); 46 | }); 47 | 48 | describe('with receiver', () => { 49 | let receiver; 50 | 51 | beforeEach(() => { 52 | receiver = { 53 | isBound: () => true, 54 | toString: () => 'receiver', 55 | }; 56 | unit.getSpaceAt = () => ({ getUnit: () => receiver }); 57 | }); 58 | 59 | test("does nothing to receiver if it's not bound", () => { 60 | receiver.isBound = () => false; 61 | rescue.perform(); 62 | expect(unit.log).toHaveBeenCalledWith( 63 | `unbinds ${FORWARD} and rescues nothing`, 64 | ); 65 | expect(unit.release).not.toHaveBeenCalled(); 66 | }); 67 | 68 | test('releases receiver', () => { 69 | rescue.perform(); 70 | expect(unit.log).toHaveBeenCalledWith( 71 | `unbinds ${FORWARD} and rescues receiver`, 72 | ); 73 | expect(unit.release).toHaveBeenCalledWith(receiver); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/rest.js: -------------------------------------------------------------------------------- 1 | function rest({ healthGain }) { 2 | const healthGainPercentage = healthGain * 100; 3 | return unit => ({ 4 | action: true, 5 | description: `Gains ${healthGainPercentage}% of max health back, but does nothing more.`, 6 | perform() { 7 | if (unit.health < unit.maxHealth) { 8 | unit.log('rests'); 9 | const amount = Math.round(unit.maxHealth * healthGain); 10 | unit.heal(amount); 11 | } else { 12 | unit.log('is already fit as a fiddle'); 13 | } 14 | }, 15 | }); 16 | } 17 | 18 | export default rest; 19 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/rest.test.js: -------------------------------------------------------------------------------- 1 | import restCreator from './rest'; 2 | 3 | describe('rest', () => { 4 | let rest; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { 9 | maxHealth: 20, 10 | health: 10, 11 | heal: jest.fn(), 12 | log: jest.fn(), 13 | }; 14 | rest = restCreator({ healthGain: 0.1 })(unit); 15 | }); 16 | 17 | test('is an action', () => { 18 | expect(rest.action).toBe(true); 19 | }); 20 | 21 | test('has a description', () => { 22 | expect(rest.description).toBe( 23 | 'Gains 10% of max health back, but does nothing more.', 24 | ); 25 | }); 26 | 27 | describe('performing', () => { 28 | test('gives health back', () => { 29 | rest.perform(); 30 | expect(unit.log).toHaveBeenCalledWith('rests'); 31 | expect(unit.heal).toHaveBeenCalledWith(2); 32 | }); 33 | 34 | test("doesn't add health when at max", () => { 35 | unit.health = 20; 36 | rest.perform(); 37 | expect(unit.log).toHaveBeenCalledWith('is already fit as a fiddle'); 38 | expect(unit.heal).not.toHaveBeenCalled(); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/shoot.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function shoot({ power, range }) { 6 | return unit => ({ 7 | action: true, 8 | description: `Shoots the bow & arrow in the given direction (\`'${defaultDirection}'\` by default), dealing ${power} HP of damage to the first unit in a range of ${range} spaces.`, 9 | perform(direction = defaultDirection) { 10 | const offsets = Array.from(new Array(range), (_, index) => index + 1); 11 | const receiver = offsets 12 | .map(offset => unit.getSpaceAt(direction, offset).getUnit()) 13 | .find(unitInRange => unitInRange); 14 | if (receiver) { 15 | unit.log(`shoots ${direction} and hits ${receiver}`); 16 | unit.damage(receiver, power); 17 | } else { 18 | unit.log(`shoots ${direction} and hits nothing`); 19 | } 20 | }, 21 | }); 22 | } 23 | 24 | export default shoot; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/think.js: -------------------------------------------------------------------------------- 1 | import util from 'util'; 2 | 3 | function think() { 4 | return unit => ({ 5 | description: 'Thinks out loud (`console.log` replacement).', 6 | perform(...args) { 7 | const thought = args.length > 0 ? util.format(...args) : 'nothing'; 8 | unit.log(`thinks ${thought}`); 9 | }, 10 | }); 11 | } 12 | 13 | export default think; 14 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/think.test.js: -------------------------------------------------------------------------------- 1 | import thinkCreator from './think'; 2 | 3 | describe('think', () => { 4 | let think; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { log: jest.fn() }; 9 | think = thinkCreator()(unit); 10 | }); 11 | 12 | test('is not an action', () => { 13 | expect(think.action).toBeUndefined(); 14 | }); 15 | 16 | test('has a description', () => { 17 | expect(think.description).toBe( 18 | 'Thinks out loud (`console.log` replacement).', 19 | ); 20 | }); 21 | 22 | describe('performing', () => { 23 | test('thinks nothing by default', () => { 24 | think.perform(); 25 | expect(unit.log).toHaveBeenCalledWith('thinks nothing'); 26 | }); 27 | 28 | test('allows to specify thought', () => { 29 | think.perform('he should be brave'); 30 | expect(unit.log).toHaveBeenCalledWith('thinks he should be brave'); 31 | }); 32 | 33 | test('allows complex thoughts', () => { 34 | think.perform('that %o', { brave: true }); 35 | expect(unit.log).toHaveBeenCalledWith('thinks that { brave: true }'); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/walk.js: -------------------------------------------------------------------------------- 1 | import { FORWARD } from '@warriorjs/geography'; 2 | 3 | const defaultDirection = FORWARD; 4 | 5 | function walk() { 6 | return unit => ({ 7 | action: true, 8 | description: `Moves one space in the given direction (\`'${defaultDirection}'\` by default).`, 9 | perform(direction = defaultDirection) { 10 | const space = unit.getSpaceAt(direction); 11 | if (space.isEmpty()) { 12 | unit.move(direction); 13 | unit.log(`walks ${direction}`); 14 | } else { 15 | unit.log(`walks ${direction} and bumps into ${space}`); 16 | } 17 | }, 18 | }); 19 | } 20 | 21 | export default walk; 22 | -------------------------------------------------------------------------------- /packages/warriorjs-abilities/src/walk.test.js: -------------------------------------------------------------------------------- 1 | import { FORWARD, RIGHT } from '@warriorjs/geography'; 2 | 3 | import walkCreator from './walk'; 4 | 5 | describe('walk', () => { 6 | let walk; 7 | let unit; 8 | 9 | beforeEach(() => { 10 | unit = { 11 | move: jest.fn(), 12 | log: jest.fn(), 13 | }; 14 | walk = walkCreator()(unit); 15 | }); 16 | 17 | test('is an action', () => { 18 | expect(walk.action).toBe(true); 19 | }); 20 | 21 | test('has a description', () => { 22 | expect(walk.description).toBe( 23 | `Moves one space in the given direction (\`'${FORWARD}'\` by default).`, 24 | ); 25 | }); 26 | 27 | describe('performing', () => { 28 | test('walks forward by default', () => { 29 | unit.getSpaceAt = jest.fn(() => ({ isEmpty: () => true })); 30 | walk.perform(); 31 | expect(unit.getSpaceAt).toHaveBeenCalledWith(FORWARD); 32 | }); 33 | 34 | test('allows to specify direction', () => { 35 | unit.getSpaceAt = jest.fn(() => ({ isEmpty: () => true })); 36 | walk.perform(RIGHT); 37 | expect(unit.getSpaceAt).toHaveBeenCalledWith(RIGHT); 38 | }); 39 | 40 | test('keeps position if something is in the way', () => { 41 | unit.getSpaceAt = () => ({ 42 | isEmpty: () => false, 43 | toString: () => 'space', 44 | }); 45 | walk.perform(); 46 | expect(unit.log).toHaveBeenCalledWith( 47 | `walks ${FORWARD} and bumps into space`, 48 | ); 49 | expect(unit.move).not.toHaveBeenCalled(); 50 | }); 51 | 52 | test('moves in specified direction if space if empty', () => { 53 | unit.getSpaceAt = () => ({ isEmpty: () => true }); 54 | walk.perform(RIGHT); 55 | expect(unit.log).toHaveBeenCalledWith(`walks ${RIGHT}`); 56 | expect(unit.move).toHaveBeenCalledWith(RIGHT); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/cli 2 | 3 | > WarriorJS command line. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install --global @warriorjs/cli 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```sh 14 | warriorjs 15 | ``` 16 | 17 | For more in depth documentation see: https://warrior.js.org/docs/player/options. 18 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/bin/warriorjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli').run(process.argv); 4 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/cli", 3 | "version": "0.14.0", 4 | "description": "WarriorJS command line", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-cli", 9 | "keywords": [ 10 | "warriorjs", 11 | "warriorjs-cli", 12 | "warrior", 13 | "epic", 14 | "battle", 15 | "game", 16 | "learn", 17 | "polish", 18 | "refine", 19 | "test", 20 | "js", 21 | "javascript", 22 | "nodejs", 23 | "ai", 24 | "artificial-intelligence", 25 | "skills" 26 | ], 27 | "bin": { 28 | "warriorjs": "./bin/warriorjs.js" 29 | }, 30 | "engines": { 31 | "node": ">=8" 32 | }, 33 | "files": [ 34 | "bin", 35 | "lib", 36 | "templates" 37 | ], 38 | "publishConfig": { 39 | "access": "public" 40 | }, 41 | "scripts": { 42 | "build": "babel src --out-dir lib --ignore test.js", 43 | "postinstall": "node -e \"console.log('\\u001b[35m\\u001b[1mLove WarriorJS? You can now donate to our open collective:\\u001b[22m\\u001b[39m\\n> \\u001b[34mhttps://opencollective.com/warriorjs/donate\\u001b[0m')\"" 44 | }, 45 | "devDependencies": { 46 | "ansi-styles": "^3.2.1", 47 | "mock-fs": "^4.7.0" 48 | }, 49 | "dependencies": { 50 | "@warriorjs/core": "^0.14.0", 51 | "@warriorjs/helper-get-grade-letter": "^0.11.1", 52 | "@warriorjs/helper-get-level-config": "^0.12.3", 53 | "@warriorjs/helper-get-level-score": "^0.14.0", 54 | "@warriorjs/tower-baby-steps": "^0.13.0", 55 | "ansi-escapes": "^3.0.0", 56 | "array-shuffle": "^1.0.1", 57 | "chalk": "^2.4.1", 58 | "delay": "^4.1.0", 59 | "ejs": "^2.6.1", 60 | "find-up": "^3.0.0", 61 | "globby": "^8.0.1", 62 | "inquirer": "^6.2.1", 63 | "inquirer-prompt-suggest": "^0.1.0", 64 | "lodash.uniqby": "^4.7.0", 65 | "resolve": "^1.8.1", 66 | "string-width": "^2.1.1", 67 | "superheroes": "^2.0.0", 68 | "yargs": "^12.0.5" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/GameError.js: -------------------------------------------------------------------------------- 1 | /** Class representing a game error. */ 2 | class GameError extends Error { 3 | /** 4 | * Creates a game error. 5 | * 6 | * @param {string} message The error message. 7 | */ 8 | constructor(message) { 9 | super(message); 10 | Error.captureStackTrace(this, GameError); 11 | } 12 | } 13 | 14 | export default GameError; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ProfileGenerator.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import ejs from 'ejs'; 5 | 6 | import getFloorMap from './utils/getFloorMap'; 7 | import getFloorMapKey from './utils/getFloorMapKey'; 8 | 9 | const templatesPath = path.resolve(__dirname, '..', 'templates'); 10 | export const PLAYER_CODE_TEMPLATE_FILE_PATH = path.join( 11 | templatesPath, 12 | 'Player.js', 13 | ); 14 | export const README_TEMPLATE_FILE_PATH = path.join( 15 | templatesPath, 16 | 'README.md.ejs', 17 | ); 18 | 19 | /** Class representing a profile generator. */ 20 | class ProfileGenerator { 21 | /** 22 | * Creates a profile generator. 23 | * 24 | * @param {Profile} profile The profile. 25 | * @param {Object} level The level. 26 | */ 27 | constructor(profile, level) { 28 | this.profile = profile; 29 | this.level = level; 30 | } 31 | 32 | /** 33 | * Generates the profile files (README and, if first level, player code). 34 | */ 35 | generate() { 36 | this.generateReadmeFile(); 37 | if (this.profile.levelNumber === 1) { 38 | this.generatePlayerCodeFile(); 39 | } 40 | } 41 | 42 | /** 43 | * Generates the README file (README.md). 44 | */ 45 | generateReadmeFile() { 46 | const template = fs.readFileSync(README_TEMPLATE_FILE_PATH, 'utf8'); 47 | const data = { 48 | getFloorMap, 49 | getFloorMapKey, 50 | profile: this.profile, 51 | level: this.level, 52 | }; 53 | const options = { filename: README_TEMPLATE_FILE_PATH }; 54 | const renderedReadme = ejs.render(template, data, options); 55 | fs.writeFileSync(this.profile.getReadmeFilePath(), renderedReadme); 56 | } 57 | 58 | /** 59 | * Generates the player code file (Player.js). 60 | */ 61 | generatePlayerCodeFile() { 62 | fs.copyFileSync( 63 | PLAYER_CODE_TEMPLATE_FILE_PATH, 64 | this.profile.getPlayerCodeFilePath(), 65 | ); 66 | } 67 | } 68 | 69 | export default ProfileGenerator; 70 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/Tower.js: -------------------------------------------------------------------------------- 1 | class Tower { 2 | /** 3 | * Creates a tower. 4 | * 5 | * @param {string} id The identifier of the tower. 6 | * @param {string} name The name of the tower. 7 | * @param {string} description The description of the tower. 8 | * @param {Object[]} levels The levels of the tower. 9 | */ 10 | constructor(id, name, description, levels) { 11 | this.id = id; 12 | this.name = name; 13 | this.description = description; 14 | this.levels = levels; 15 | } 16 | 17 | /** 18 | * Checks if the tower has a level with the given number. 19 | * 20 | * @param {number} levelNumber The number of the level. 21 | * 22 | * @returns {boolean} Whether the tower has the level or not. 23 | */ 24 | hasLevel(levelNumber) { 25 | return !!this.getLevel(levelNumber); 26 | } 27 | 28 | /** 29 | * Returns the level with the given number. 30 | * 31 | * @param {number} levelNumber The number of the level. 32 | * 33 | * @returns {Object} The level. 34 | */ 35 | getLevel(levelNumber) { 36 | return this.levels[levelNumber - 1]; 37 | } 38 | 39 | /** 40 | * Returns the string representation of this tower. 41 | * 42 | * @returns {string} The string representation. 43 | */ 44 | toString() { 45 | return this.name; 46 | } 47 | } 48 | 49 | export default Tower; 50 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/Tower.test.js: -------------------------------------------------------------------------------- 1 | import Tower from './Tower'; 2 | 3 | describe('Tower', () => { 4 | let tower; 5 | 6 | beforeEach(() => { 7 | tower = new Tower('foo', 'Foo', 'bar baz', ['level1', 'level2']); 8 | }); 9 | 10 | test('has an id', () => { 11 | expect(tower.id).toBe('foo'); 12 | }); 13 | 14 | test('has a name', () => { 15 | expect(tower.name).toBe('Foo'); 16 | }); 17 | 18 | test('has a description', () => { 19 | expect(tower.description).toBe('bar baz'); 20 | }); 21 | 22 | test('has some levels', () => { 23 | expect(tower.levels).toEqual(['level1', 'level2']); 24 | }); 25 | 26 | test('knows if it has a given level', () => { 27 | expect(tower.hasLevel(1)).toBe(true); 28 | expect(tower.hasLevel(3)).toBe(false); 29 | }); 30 | 31 | test('returns the level with the given number', () => { 32 | expect(tower.getLevel(1)).toBe('level1'); 33 | }); 34 | 35 | test('has a nice string representation', () => { 36 | expect(tower.toString()).toBe('Foo'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/cli.js: -------------------------------------------------------------------------------- 1 | import Game from './Game'; 2 | import parseArgs from './parseArgs'; 3 | 4 | /** 5 | * Starts the game. 6 | * 7 | * @param {string[]} args The command line arguments. 8 | */ 9 | async function run(args) { 10 | const { directory, level, silent, time, yes } = parseArgs(args); 11 | const game = new Game(directory, level, silent, time, yes); 12 | await game.start(); 13 | } 14 | 15 | export { run }; // eslint-disable-line import/prefer-default-export 16 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/cli.test.js: -------------------------------------------------------------------------------- 1 | import Game from './Game'; 2 | import { run } from './cli'; 3 | 4 | jest.mock('./Game'); 5 | 6 | test('starts the game', async () => { 7 | const mockStart = jest.fn(); 8 | Game.mockImplementation(() => ({ start: mockStart })); 9 | await run(['-d', '/path/to/game', '-l', '2', '-s', '-t', '0.3', '-y']); 10 | expect(Game).toHaveBeenCalledWith('/path/to/game', 2, true, 0.3, true); 11 | expect(mockStart).toHaveBeenCalled(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/loadTowers.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import findUp from 'find-up'; 4 | import globby from 'globby'; 5 | import resolve from 'resolve'; 6 | import uniqBy from 'lodash.uniqby'; 7 | 8 | import Tower from './Tower'; 9 | import getTowerId from './utils/getTowerId'; 10 | 11 | const internalTowerPackageNames = ['@warriorjs/tower-baby-steps']; 12 | 13 | const officialTowerPackageJsonPattern = '@warriorjs/tower-*/package.json'; 14 | const communityTowerPackageJsonPattern = 'warriorjs-tower-*/package.json'; 15 | 16 | /** 17 | * Returns the internal towers info. 18 | * 19 | * @returns {Object[]} The internal towers info. 20 | */ 21 | function getInternalTowersInfo() { 22 | return internalTowerPackageNames.map(towerPackageName => ({ 23 | id: getTowerId(towerPackageName), 24 | requirePath: towerPackageName, 25 | })); 26 | } 27 | 28 | /** 29 | * Returns the external towers info. 30 | * 31 | * It searches for official and community towers installed in the nearest 32 | * `node_modules` directory that is parent to @warriorjs/cli. 33 | * 34 | * @returns {Object[]} The external towers info. 35 | */ 36 | function getExternalTowersInfo() { 37 | const cliDir = findUp.sync('@warriorjs/cli', { cwd: __dirname }); 38 | if (!cliDir) { 39 | return []; 40 | } 41 | 42 | const cliParentDir = path.resolve(cliDir, '..'); 43 | const towerSearchDir = findUp.sync('node_modules', { cwd: cliParentDir }); 44 | const towerPackageJsonPaths = globby.sync( 45 | [officialTowerPackageJsonPattern, communityTowerPackageJsonPattern], 46 | { cwd: towerSearchDir }, 47 | ); 48 | const towerPackageNames = towerPackageJsonPaths.map(path.dirname); 49 | return towerPackageNames.map(towerPackageName => ({ 50 | id: getTowerId(towerPackageName), 51 | requirePath: resolve.sync(towerPackageName, { basedir: towerSearchDir }), 52 | })); 53 | } 54 | 55 | /** 56 | * Loads the installed towers. 57 | * 58 | * @returns {Tower[]} The loaded towers. 59 | */ 60 | function loadTowers() { 61 | const internalTowersInfo = getInternalTowersInfo(); 62 | const externalTowersInfo = getExternalTowersInfo(); 63 | return uniqBy(internalTowersInfo.concat(externalTowersInfo), 'id').map( 64 | ({ id, requirePath }) => { 65 | const { name, description, levels } = require(requirePath); // eslint-disable-line global-require, import/no-dynamic-require 66 | return new Tower(id, name, description, levels); 67 | }, 68 | ); 69 | } 70 | 71 | export default loadTowers; 72 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/parseArgs.js: -------------------------------------------------------------------------------- 1 | import yargs from 'yargs'; 2 | 3 | /** 4 | * Parses the provided args. 5 | * 6 | * @param {string[]} args The args no parse. 7 | * 8 | * @returns {Object} The parsed args. 9 | */ 10 | function parseArgs(args) { 11 | return yargs 12 | .usage('Usage: $0 [options]') 13 | .options({ 14 | d: { 15 | alias: 'directory', 16 | default: '.', 17 | describe: 'Run under given directory', 18 | type: 'string', 19 | }, 20 | l: { 21 | alias: 'level', 22 | coerce: arg => { 23 | const parsed = Number.parseInt(arg, 10); 24 | if (Number.isNaN(parsed)) { 25 | throw new Error('Invalid argument: level must be a number'); 26 | } 27 | 28 | return parsed; 29 | }, 30 | describe: 'Practice level (epic mode only)', 31 | type: 'number', 32 | }, 33 | s: { 34 | alias: 'silent', 35 | default: false, 36 | describe: 'Suppress play log', 37 | type: 'boolean', 38 | }, 39 | t: { 40 | alias: 'time', 41 | coerce: arg => { 42 | const parsed = Number.parseFloat(arg); 43 | if (Number.isNaN(parsed)) { 44 | throw new Error('Invalid argument: time must be a number'); 45 | } 46 | 47 | return parsed; 48 | }, 49 | default: 0.6, 50 | describe: 'Delay each turn by seconds', 51 | type: 'number', 52 | }, 53 | y: { 54 | alias: 'yes', 55 | default: false, 56 | describe: 'Assume yes in non-destructive confirmation dialogs', 57 | type: 'boolean', 58 | }, 59 | }) 60 | .version() 61 | .help() 62 | .strict() 63 | .parse(args); 64 | } 65 | 66 | export default parseArgs; 67 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/getScreenSize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the size of the screen. 3 | * 4 | * @returns {number[]} The size of the screen as an array of [width, height]. 5 | */ 6 | function getScreenSize() { 7 | return [process.stdout.columns, process.stdout.rows]; 8 | } 9 | 10 | export default getScreenSize; 11 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/getScreenSize.test.js: -------------------------------------------------------------------------------- 1 | import getScreenSize from './getScreenSize'; 2 | 3 | test('returns number of rows and columns from stdout', () => { 4 | process.stdout.columns = 4; 5 | process.stdout.rows = 2; 6 | expect(getScreenSize()).toEqual([4, 2]); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/getUnitStyle.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | // Downsample colors from RGB to 256 color ANSI for greater uniformity. 4 | const ctx = new chalk.constructor({ level: 2 }); 5 | 6 | /** 7 | * Returns the style for the given unit. 8 | * 9 | * @param {Object} unit The unit to get the style for. 10 | * @param {string} unit.color The color of the unit (hex). 11 | * 12 | * @returns {Function} The style function. 13 | */ 14 | function getUnitStyle({ color }) { 15 | return ctx.hex(color); 16 | } 17 | 18 | export default getUnitStyle; 19 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/getUnitStyle.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import getUnitStyle from './getUnitStyle'; 4 | 5 | test('downsamples RGB to 256 color ANSI', () => { 6 | const color = '#8fbcbb'; 7 | const unitStyle = getUnitStyle({ color }); 8 | expect(unitStyle('@')).toBe( 9 | `${style.color.ansi256.hex(color)}@${style.color.close}`, 10 | ); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/print.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a message to stdout. 3 | * 4 | * @param {string} message The message to print. 5 | */ 6 | function print(message) { 7 | process.stdout.write(message); 8 | } 9 | 10 | export default print; 11 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/print.test.js: -------------------------------------------------------------------------------- 1 | import print from './print'; 2 | 3 | test('prints given message to stdout', () => { 4 | process.stdout.write = jest.fn(); 5 | print('foo'); 6 | expect(process.stdout.write).toHaveBeenCalledWith('foo'); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printBoard.js: -------------------------------------------------------------------------------- 1 | import ansiEscapes from 'ansi-escapes'; 2 | 3 | import print from './print'; 4 | import printFloorMap from './printFloorMap'; 5 | import printWarriorStatus from './printWarriorStatus'; 6 | 7 | const warriorStatusRows = 2; 8 | 9 | /** 10 | * Prints the game board after moving the cursor up a given number of rows. 11 | * 12 | * @param {Object[][]} floorMap The map of the floor. 13 | * @param {Object} warriorStatus The status of the warrior. 14 | * @param {number} offset The number of rows. 15 | */ 16 | function printBoard(floorMap, warriorStatus, offset) { 17 | if (offset > 0) { 18 | const floorMapRows = floorMap.length; 19 | print(ansiEscapes.cursorUp(offset + floorMapRows + warriorStatusRows)); 20 | } 21 | 22 | printWarriorStatus(warriorStatus); 23 | printFloorMap(floorMap); 24 | 25 | if (offset > 0) { 26 | print(ansiEscapes.cursorDown(offset)); 27 | } 28 | } 29 | 30 | export default printBoard; 31 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printBoard.test.js: -------------------------------------------------------------------------------- 1 | import ansiEscapes from 'ansi-escapes'; 2 | 3 | import print from './print'; 4 | import printBoard from './printBoard'; 5 | import printFloorMap from './printFloorMap'; 6 | import printWarriorStatus from './printWarriorStatus'; 7 | 8 | jest.mock('./print'); 9 | jest.mock('./printFloorMap'); 10 | jest.mock('./printWarriorStatus'); 11 | 12 | test('prints warrior status and floor map', () => { 13 | const floorMap = ['row1', 'row2']; 14 | const warriorStatus = 'status'; 15 | printBoard(floorMap, warriorStatus); 16 | expect(print).not.toHaveBeenCalled(); 17 | expect(printWarriorStatus).toHaveBeenCalledWith(warriorStatus); 18 | expect(printFloorMap).toHaveBeenCalledWith(floorMap); 19 | }); 20 | 21 | test('moves cursor up and down with offset', () => { 22 | const floorMap = ['row1', 'row2']; 23 | const warriorStatus = 'status'; 24 | printBoard(floorMap, warriorStatus, 1); 25 | expect(print).toHaveBeenCalledWith(ansiEscapes.cursorUp(5)); 26 | expect(printWarriorStatus).toHaveBeenCalledWith(warriorStatus); 27 | expect(printFloorMap).toHaveBeenCalledWith(floorMap); 28 | expect(print).toHaveBeenCalledWith(ansiEscapes.cursorDown(1)); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printFailureLine.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printLine from './printLine'; 4 | 5 | /** 6 | * Prints a message in color red followed by a line-break. 7 | * 8 | * @param {string} message The message to print. 9 | */ 10 | function printFailureLine(message) { 11 | printLine(chalk.red(message)); 12 | } 13 | 14 | export default printFailureLine; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printFailureLine.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printFailureLine from './printFailureLine'; 4 | import printLine from './printLine'; 5 | 6 | jest.mock('./printLine'); 7 | 8 | test('prints given line in red', () => { 9 | printFailureLine('foo'); 10 | expect(printLine).toHaveBeenCalledWith( 11 | `${style.red.open}foo${style.red.close}`, 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printFloorMap.js: -------------------------------------------------------------------------------- 1 | import getUnitStyle from './getUnitStyle'; 2 | import printLine from './printLine'; 3 | 4 | /** 5 | * Prints the floor map. 6 | * 7 | * @param {Object[][]} floorMap The map of the floor. 8 | */ 9 | function printFloorMap(floorMap) { 10 | printLine( 11 | floorMap 12 | .map(row => 13 | row 14 | .map(({ character, unit }) => { 15 | if (unit) { 16 | return getUnitStyle(unit)(character); 17 | } 18 | 19 | return character; 20 | }) 21 | .join(''), 22 | ) 23 | .join('\n'), 24 | ); 25 | } 26 | 27 | export default printFloorMap; 28 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printFloorMap.test.js: -------------------------------------------------------------------------------- 1 | import getUnitStyle from './getUnitStyle'; 2 | import printFloorMap from './printFloorMap'; 3 | import printLine from './printLine'; 4 | 5 | jest.mock('./getUnitStyle'); 6 | jest.mock('./printLine'); 7 | 8 | test('prints floor map', () => { 9 | const unitStyle = jest.fn(string => string); 10 | getUnitStyle.mockReturnValue(unitStyle); 11 | const map = [ 12 | [{ character: 'a' }, { character: 'b', unit: 'unit' }], 13 | [{ character: 'c' }, { character: 'd' }], 14 | ]; 15 | printFloorMap(map); 16 | expect(getUnitStyle).toHaveBeenCalledWith('unit'); 17 | expect(unitStyle).toHaveBeenCalledWith('b'); 18 | expect(printLine).toHaveBeenCalledWith('ab\ncd'); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevel.js: -------------------------------------------------------------------------------- 1 | import printFloorMap from './printFloorMap'; 2 | import printLevelHeader from './printLevelHeader'; 3 | 4 | /** 5 | * Prints the level. 6 | * 7 | * @param {Object} level The level to print. 8 | * @param {number} level.number The number of the level. 9 | * @param {Object[][]} level.floorMap The map of the floor. 10 | */ 11 | function printLevel({ number, floorMap }) { 12 | printLevelHeader(number); 13 | printFloorMap(floorMap); 14 | } 15 | 16 | export default printLevel; 17 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevel.test.js: -------------------------------------------------------------------------------- 1 | import printFloorMap from './printFloorMap'; 2 | import printLevel from './printLevel'; 3 | import printLevelHeader from './printLevelHeader'; 4 | 5 | jest.mock('./printFloorMap'); 6 | jest.mock('./printLevelHeader'); 7 | 8 | test('prints level header, description, and floor map', () => { 9 | const level = { 10 | number: 1, 11 | description: 'foo', 12 | floorMap: 'map', 13 | }; 14 | printLevel(level); 15 | expect(printLevelHeader).toHaveBeenCalledWith(1); 16 | expect(printFloorMap).toHaveBeenCalledWith('map'); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevelHeader.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printRow from './printRow'; 4 | 5 | /** 6 | * Prints the level header. 7 | * 8 | * @param {number} levelNumber The level number. 9 | */ 10 | function printLevelHeader(levelNumber) { 11 | printRow(chalk.gray.dim(` level ${levelNumber} `), { 12 | position: 'middle', 13 | padding: chalk.gray.dim('~'), 14 | }); 15 | } 16 | 17 | export default printLevelHeader; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevelHeader.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printRow from './printRow'; 4 | import printLevelHeader from './printLevelHeader'; 5 | 6 | jest.mock('./printRow'); 7 | 8 | test('prints level header', () => { 9 | printLevelHeader(1); 10 | expect(printRow).toHaveBeenCalledWith( 11 | `${style.gray.open}${style.dim.open} level 1 ${style.dim.close}${ 12 | style.gray.close 13 | }`, 14 | { 15 | position: 'middle', 16 | padding: `${style.gray.open}${style.dim.open}~${style.dim.close}${ 17 | style.gray.close 18 | }`, 19 | }, 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevelReport.js: -------------------------------------------------------------------------------- 1 | import getGradeLetter from '@warriorjs/helper-get-grade-letter'; 2 | 3 | import printLine from './printLine'; 4 | import printTotalScore from './printTotalScore'; 5 | 6 | /** 7 | * Prints the level report. 8 | * 9 | * @param {Profile} profile The profile. 10 | * @param {Object} scoreParts The score components. 11 | * @param {number} scoreParts.warrior The points earned by the warrior. 12 | * @param {number} scoreParts.timeBonus The time bonus. 13 | * @param {number} scoreParts.clearBonus The clear bonus. 14 | * @param {number} totalScore The total score. 15 | * @param {number} grade The grade. 16 | */ 17 | function printLevelReport( 18 | profile, 19 | { warrior: warriorScore, timeBonus, clearBonus }, 20 | totalScore, 21 | grade, 22 | ) { 23 | printLine(`Warrior Score: ${warriorScore}`); 24 | printLine(`Time Bonus: ${timeBonus}`); 25 | printLine(`Clear Bonus: ${clearBonus}`); 26 | 27 | if (profile.isEpic()) { 28 | printLine(`Level Grade: ${getGradeLetter(grade)}`); 29 | printTotalScore(profile.currentEpicScore, totalScore); 30 | } else { 31 | printTotalScore(profile.score, totalScore); 32 | } 33 | } 34 | 35 | export default printLevelReport; 36 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLevelReport.test.js: -------------------------------------------------------------------------------- 1 | import getGradeLetter from '@warriorjs/helper-get-grade-letter'; 2 | 3 | import printLevelReport from './printLevelReport'; 4 | import printLine from './printLine'; 5 | import printTotalScore from './printTotalScore'; 6 | 7 | jest.mock('@warriorjs/helper-get-grade-letter'); 8 | jest.mock('./printLine'); 9 | jest.mock('./printTotalScore'); 10 | 11 | const profile = { 12 | currentEpicScore: 2, 13 | score: 3, 14 | isEpic: () => false, 15 | }; 16 | 17 | const score = { 18 | warrior: 3, 19 | timeBonus: 2, 20 | clearBonus: 0, 21 | }; 22 | const totalScore = 5; 23 | const grade = 0.9; 24 | 25 | test('prints level score', () => { 26 | printLevelReport(profile, score, totalScore, grade); 27 | expect(printLine).toHaveBeenCalledWith('Warrior Score: 3'); 28 | expect(printLine).toHaveBeenCalledWith('Time Bonus: 2'); 29 | expect(printLine).toHaveBeenCalledWith('Clear Bonus: 0'); 30 | }); 31 | 32 | test('prints regular score if not epic', () => { 33 | printLevelReport(profile, score, totalScore, grade); 34 | expect(printTotalScore).toHaveBeenCalledWith(3, 5); 35 | }); 36 | 37 | describe('epic', () => { 38 | beforeEach(() => { 39 | profile.isEpic = () => true; 40 | }); 41 | 42 | test('prints epic score', () => { 43 | printLevelReport(profile, score, totalScore, grade); 44 | expect(printTotalScore).toHaveBeenCalledWith(2, 5); 45 | }); 46 | 47 | test('prints level grade', () => { 48 | getGradeLetter.mockReturnValue('A'); 49 | printLevelReport(profile, score, totalScore, grade); 50 | expect(printLine).toHaveBeenCalledWith('Level Grade: A'); 51 | expect(getGradeLetter).toHaveBeenCalledWith(0.9); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLine.js: -------------------------------------------------------------------------------- 1 | import print from './print'; 2 | 3 | /** 4 | * Prints a message followed by a line-break. 5 | * 6 | * @param {string} message The message to print. 7 | */ 8 | function printLine(message) { 9 | print(`${message}\n`); 10 | } 11 | 12 | export default printLine; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLine.test.js: -------------------------------------------------------------------------------- 1 | import print from './print'; 2 | import printLine from './printLine'; 3 | 4 | jest.mock('./print'); 5 | 6 | test('prints given message followed by a line-break', () => { 7 | printLine('foo'); 8 | expect(print).toHaveBeenCalledWith('foo\n'); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLogMessage.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import getUnitStyle from './getUnitStyle'; 4 | import printLine from './printLine'; 5 | 6 | /** 7 | * Prints a message to the log. 8 | * 9 | * @param {Object} unit The unit the message belongs to. 10 | * @param {string} message The message to print. 11 | */ 12 | function printLogMessage(unit, message) { 13 | const prompt = chalk.gray.dim('>'); 14 | const logMessage = getUnitStyle(unit)(`${unit.name} ${message}`); 15 | printLine(`${prompt} ${logMessage}`); 16 | } 17 | 18 | export default printLogMessage; 19 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printLogMessage.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import getUnitStyle from './getUnitStyle'; 4 | import printLine from './printLine'; 5 | import printLogMessage from './printLogMessage'; 6 | 7 | jest.mock('./getUnitStyle'); 8 | jest.mock('./printLine'); 9 | 10 | test('prints log message with dimmed gray prompt', () => { 11 | const unitStyle = jest.fn(string => string); 12 | getUnitStyle.mockReturnValue(unitStyle); 13 | const unit = { name: 'Joe' }; 14 | const prompt = `${style.gray.open}${style.dim.open}>${style.dim.close}${ 15 | style.gray.close 16 | }`; 17 | printLogMessage(unit, 'is awesome!'); 18 | expect(getUnitStyle).toHaveBeenCalledWith(unit); 19 | expect(unitStyle).toHaveBeenCalledWith('Joe is awesome!'); 20 | expect(printLine).toHaveBeenCalledWith(`${prompt} Joe is awesome!`); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printPlay.js: -------------------------------------------------------------------------------- 1 | import sleep from 'delay'; 2 | 3 | import printBoard from './printBoard'; 4 | import printTurnHeader from './printTurnHeader'; 5 | import printLogMessage from './printLogMessage'; 6 | 7 | /** 8 | * Prints a play. 9 | * 10 | * @param {Object[]} events The events that happened during the play. 11 | * @param {nunber} delay The delay between each turn in ms. 12 | */ 13 | async function printPlay(events, delay) { 14 | let turnNumber = 0; 15 | let boardOffset = 0; 16 | 17 | await sleep(delay); 18 | 19 | // eslint-disable-next-line no-restricted-syntax 20 | for (const turnEvents of events) { 21 | turnNumber += 1; 22 | boardOffset = 0; 23 | printTurnHeader(turnNumber); 24 | 25 | // eslint-disable-next-line no-restricted-syntax 26 | for (const event of turnEvents) { 27 | printBoard(event.floorMap, event.warriorStatus, boardOffset); 28 | printLogMessage(event.unit, event.message); 29 | boardOffset += 1; 30 | 31 | await sleep(delay); // eslint-disable-line no-await-in-loop 32 | } 33 | } 34 | } 35 | 36 | export default printPlay; 37 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printPlay.test.js: -------------------------------------------------------------------------------- 1 | import delay from 'delay'; 2 | 3 | import printBoard from './printBoard'; 4 | import printLogMessage from './printLogMessage'; 5 | import printPlay from './printPlay'; 6 | import printTurnHeader from './printTurnHeader'; 7 | 8 | jest.mock('delay'); 9 | jest.mock('./printBoard'); 10 | jest.mock('./printLevelHeader'); 11 | jest.mock('./printLogMessage'); 12 | jest.mock('./printTurnHeader'); 13 | 14 | test('prints turn header on each new turn', async () => { 15 | const events = [[]]; 16 | await printPlay(events); 17 | expect(printTurnHeader).toHaveBeenCalledWith(1); 18 | }); 19 | 20 | test('prints board on each event', async () => { 21 | const events = [ 22 | [{ floorMap: 'floor1', warriorStatus: 'status1' }], 23 | [{ floorMap: 'floor2', warriorStatus: 'status2' }], 24 | ]; 25 | await printPlay(events); 26 | expect(printBoard).toHaveBeenCalledWith('floor1', 'status1', 0); 27 | expect(printBoard).toHaveBeenCalledWith('floor2', 'status2', 0); 28 | }); 29 | 30 | test('prints log message on each event', async () => { 31 | const events = [[{ unit: 'foo', message: 'bar' }]]; 32 | await printPlay(events); 33 | expect(printLogMessage).toHaveBeenCalledWith('foo', 'bar'); 34 | }); 35 | 36 | test('starts counting turn from zero and increments on each new turn', async () => { 37 | const events = [[], []]; 38 | await printPlay(events); 39 | expect(printTurnHeader).toHaveBeenCalledWith(1); 40 | expect(printTurnHeader).toHaveBeenCalledWith(2); 41 | }); 42 | 43 | test('starts counting board offset from zero, increments on each event and resets on each new turn', async () => { 44 | const events = [ 45 | [{ unit: 'foo', message: 'bar' }, { unit: 'foo', message: 'bar' }], 46 | [{ unit: 'foo', message: 'bar' }], 47 | [{ unit: 'foo', message: 'bar' }], 48 | ]; 49 | await printPlay(events); 50 | expect(printBoard.mock.calls[0][2]).toBe(0); 51 | expect(printBoard.mock.calls[1][2]).toBe(1); 52 | expect(printBoard.mock.calls[2][2]).toBe(0); 53 | expect(printBoard.mock.calls[3][2]).toBe(0); 54 | }); 55 | 56 | test('sleeps the specified time at the beginning and after each event', async () => { 57 | const events = [ 58 | [{ unit: 'foo', message: 'bar' }, { unit: 'foo', message: 'bar' }], 59 | [{ unit: 'foo', message: 'bar' }], 60 | [{ unit: 'foo', message: 'bar' }], 61 | ]; 62 | await printPlay(events, 42); 63 | expect(delay).toHaveBeenCalledTimes(5); 64 | expect(delay).toHaveBeenCalledWith(42); 65 | }); 66 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printRow.js: -------------------------------------------------------------------------------- 1 | import stringWidth from 'string-width'; 2 | 3 | import getScreenSize from './getScreenSize'; 4 | import printLine from './printLine'; 5 | 6 | /** 7 | * Prints a message and fills the rest of the line with a padding character. 8 | * 9 | * @param {string} message The message to print. 10 | * @param {Object} options The options. 11 | * @param {string} options.position The position of the message. 12 | * @param {string} options.padding The padding character. 13 | */ 14 | function printRow(message, { position = 'start', padding = ' ' } = {}) { 15 | const [screenWidth] = getScreenSize(); 16 | const rowWidth = screenWidth - 1; // Consider line break length. 17 | const messageWidth = stringWidth(message); 18 | const paddingWidth = (rowWidth - messageWidth) / 2; 19 | const startPadding = padding.repeat(Math.floor(paddingWidth)); 20 | const endPadding = padding.repeat(Math.ceil(paddingWidth)); 21 | if (position === 'start') { 22 | printLine(`${message}${startPadding}${endPadding}`); 23 | } else if (position === 'middle') { 24 | printLine(`${startPadding}${message}${endPadding}`); 25 | } else if (position === 'end') { 26 | printLine(`${startPadding}${endPadding}${message}`); 27 | } 28 | } 29 | 30 | export default printRow; 31 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printRow.test.js: -------------------------------------------------------------------------------- 1 | import getScreenSize from './getScreenSize'; 2 | import printLine from './printLine'; 3 | import printRow from './printRow'; 4 | 5 | jest.mock('./getScreenSize'); 6 | jest.mock('./printLine'); 7 | 8 | test('prints message and fills with padding', () => { 9 | getScreenSize.mockReturnValue([11, 0]); 10 | printRow('foo', { position: 'start', padding: ' ' }); 11 | expect(printLine).toHaveBeenCalledWith('foo '); 12 | }); 13 | 14 | test('positions message at the start by default', () => { 15 | getScreenSize.mockReturnValue([11, 0]); 16 | printRow('foo'); 17 | expect(printLine).toHaveBeenCalledWith('foo '); 18 | }); 19 | 20 | test('positions message in the middle if specified', () => { 21 | getScreenSize.mockReturnValue([11, 0]); 22 | printRow('foo', { position: 'middle' }); 23 | expect(printLine).toHaveBeenCalledWith(' foo '); 24 | }); 25 | 26 | test("doesn't print message if position is unknown", () => { 27 | printRow('foo', { position: 'foo' }); 28 | expect(printLine).not.toHaveBeenCalled(); 29 | }); 30 | 31 | test('positions message at the end if specified', () => { 32 | getScreenSize.mockReturnValue([11, 0]); 33 | printRow('foo', { position: 'end' }); 34 | expect(printLine).toHaveBeenCalledWith(' foo'); 35 | }); 36 | 37 | test('uses whitespace as padding character by default', () => { 38 | getScreenSize.mockReturnValue([11, 0]); 39 | printRow('foo'); 40 | expect(printLine).toHaveBeenCalledWith('foo '); 41 | }); 42 | 43 | test('allows to specify padding character', () => { 44 | getScreenSize.mockReturnValue([11, 0]); 45 | printRow('foo', { padding: '-' }); 46 | expect(printLine).toHaveBeenCalledWith('foo-------'); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printSeparator.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printRow from './printRow'; 4 | 5 | /** 6 | * Prints a separator row. 7 | */ 8 | function printSeparator() { 9 | printRow('', { 10 | padding: chalk.gray.dim('~'), 11 | }); 12 | } 13 | 14 | export default printSeparator; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printSeparator.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printRow from './printRow'; 4 | import printSeparator from './printSeparator'; 5 | 6 | jest.mock('./printRow'); 7 | 8 | test('prints row of dimmed gray tildes', () => { 9 | printSeparator(); 10 | expect(printRow).toHaveBeenCalledWith('', { 11 | padding: `${style.gray.open}${style.dim.open}~${style.dim.close}${ 12 | style.gray.close 13 | }`, 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printSuccessLine.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printLine from './printLine'; 4 | 5 | /** 6 | * Prints a message in color green followed by a line-break. 7 | * 8 | * @param {string} message The message to print. 9 | */ 10 | function printSuccessLine(message) { 11 | printLine(chalk.green(message)); 12 | } 13 | 14 | export default printSuccessLine; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printSuccessLine.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printLine from './printLine'; 4 | import printSuccessLine from './printSuccessLine'; 5 | 6 | jest.mock('./printLine'); 7 | 8 | test('prints given line in green', () => { 9 | printSuccessLine('foo'); 10 | expect(printLine).toHaveBeenCalledWith( 11 | `${style.green.open}foo${style.green.close}`, 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTotalScore.js: -------------------------------------------------------------------------------- 1 | import printLine from './printLine'; 2 | 3 | /** 4 | * Prints the total score as the sum of the current score and the addition. 5 | * 6 | * If the current score is zero, just the addition and not the sum will be 7 | * printed. 8 | * 9 | * @param {number} currentScore The current score. 10 | * @param {number} addition The score to add to the current score. 11 | */ 12 | function printTotalScore(currentScore, addition) { 13 | if (currentScore === 0) { 14 | printLine(`Total Score: ${addition.toString()}`); 15 | } else { 16 | printLine( 17 | `Total Score: ${currentScore} + ${addition} = ${currentScore + addition}`, 18 | ); 19 | } 20 | } 21 | 22 | export default printTotalScore; 23 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTotalScore.test.js: -------------------------------------------------------------------------------- 1 | import printLine from './printLine'; 2 | import printTotalScore from './printTotalScore'; 3 | 4 | jest.mock('./printLine'); 5 | 6 | test('prints only addition if current score is zero', () => { 7 | printTotalScore(0, 42); 8 | expect(printLine).toHaveBeenCalledWith('Total Score: 42'); 9 | }); 10 | 11 | test('prints sum if current score is greater than zero', () => { 12 | printTotalScore(41, 1); 13 | expect(printLine).toHaveBeenCalledWith('Total Score: 41 + 1 = 42'); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTowerReport.js: -------------------------------------------------------------------------------- 1 | import getGradeLetter from '@warriorjs/helper-get-grade-letter'; 2 | 3 | import printLine from './printLine'; 4 | 5 | /** 6 | * Prints the tower report. 7 | * 8 | * @param {Profile} profile The profile. 9 | */ 10 | function printTowerReport(profile) { 11 | const averageGrade = profile.calculateAverageGrade(); 12 | if (!averageGrade) { 13 | return; 14 | } 15 | 16 | const averageGradeLetter = getGradeLetter(averageGrade); 17 | printLine(`Your average grade for this tower is: ${averageGradeLetter}\n`); 18 | 19 | Object.keys(profile.currentEpicGrades) 20 | .sort() 21 | .forEach(levelNumber => { 22 | const grade = profile.currentEpicGrades[levelNumber]; 23 | const gradeLetter = getGradeLetter(grade); 24 | printLine(` Level ${levelNumber}: ${gradeLetter}`); 25 | }); 26 | 27 | printLine('\nTo practice a level, use the -l option.'); 28 | } 29 | 30 | export default printTowerReport; 31 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTowerReport.test.js: -------------------------------------------------------------------------------- 1 | import getGradeLetter from '@warriorjs/helper-get-grade-letter'; 2 | 3 | import printLine from './printLine'; 4 | import printTowerReport from './printTowerReport'; 5 | 6 | jest.mock('@warriorjs/helper-get-grade-letter'); 7 | jest.mock('./printLine'); 8 | 9 | test("doesn't print if no average grade", () => { 10 | const profile = { calculateAverageGrade: () => null }; 11 | printTowerReport(profile); 12 | expect(printLine).not.toHaveBeenCalled(); 13 | }); 14 | 15 | test("prints tower's average grade and each level's grade", () => { 16 | getGradeLetter 17 | .mockReturnValueOnce('A') 18 | .mockReturnValueOnce('B') 19 | .mockReturnValueOnce('A'); 20 | const profile = { 21 | currentEpicGrades: { 22 | 2: 0.9, 23 | 1: 0.8, 24 | }, 25 | calculateAverageGrade: () => 0.9, 26 | }; 27 | printTowerReport(profile); 28 | expect(printLine).toHaveBeenCalledWith( 29 | 'Your average grade for this tower is: A\n', 30 | ); 31 | expect(printLine).toHaveBeenCalledWith(' Level 1: B'); 32 | expect(printLine).toHaveBeenCalledWith(' Level 2: A'); 33 | expect(printLine).toHaveBeenCalledWith( 34 | '\nTo practice a level, use the -l option.', 35 | ); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTurnHeader.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printRow from './printRow'; 4 | 5 | /** 6 | * Prints the turn header. 7 | * 8 | * @param {number} turnNumber The turn number. 9 | */ 10 | function printTurnHeader(turnNumber) { 11 | printRow(chalk.gray.dim(` ${String(turnNumber).padStart(3, '0')} `), { 12 | position: 'middle', 13 | padding: chalk.gray.dim('~'), 14 | }); 15 | } 16 | 17 | export default printTurnHeader; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printTurnHeader.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printRow from './printRow'; 4 | import printTurnHeader from './printTurnHeader'; 5 | 6 | jest.mock('./printRow'); 7 | 8 | test('prints turn header', () => { 9 | printTurnHeader(1); 10 | expect(printRow).toHaveBeenCalledWith( 11 | `${style.gray.open}${style.dim.open} 001 ${style.dim.close}${ 12 | style.gray.close 13 | }`, 14 | { 15 | position: 'middle', 16 | padding: `${style.gray.open}${style.dim.open}~${style.dim.close}${ 17 | style.gray.close 18 | }`, 19 | }, 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWarningLine.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printLine from './printLine'; 4 | 5 | /** 6 | * Prints a message in color yellow followed by a line-break. 7 | * 8 | * @param {string} message The message to print. 9 | */ 10 | function printWarningLine(message) { 11 | printLine(chalk.yellow(message)); 12 | } 13 | 14 | export default printWarningLine; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWarningLine.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printLine from './printLine'; 4 | import printWarningLine from './printWarningLine'; 5 | 6 | jest.mock('./printLine'); 7 | 8 | test('prints given line in yellow', () => { 9 | printWarningLine('foo'); 10 | expect(printLine).toHaveBeenCalledWith( 11 | `${style.yellow.open}foo${style.yellow.close}`, 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWarriorStatus.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printRow from './printRow'; 4 | 5 | /** 6 | * Prints the warrior status line. 7 | * 8 | * @param {Object} warriorStatus The status of the warrior. 9 | * @param {number} warriorStatus.health The health of the warrior. 10 | * @param {number} warriorStatus.score The score of the warrior. 11 | */ 12 | function printWarriorStatus({ health, score }) { 13 | printRow(chalk.redBright(`♥ ${health}`)); 14 | printRow(chalk.yellowBright(`♦ ${score}`)); 15 | } 16 | 17 | export default printWarriorStatus; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWarriorStatus.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printRow from './printRow'; 4 | import printWarriorStatus from './printWarriorStatus'; 5 | 6 | jest.mock('./printRow'); 7 | 8 | test('prints warrior health in bright red', () => { 9 | const warriorStatus = { 10 | health: 20, 11 | }; 12 | printWarriorStatus(warriorStatus); 13 | expect(printRow).toHaveBeenCalledWith( 14 | `${style.redBright.open}♥ 20${style.redBright.close}`, 15 | ); 16 | }); 17 | 18 | test('prints warrior score in bright yellow', () => { 19 | const warriorStatus = { 20 | score: 10, 21 | }; 22 | printWarriorStatus(warriorStatus); 23 | expect(printRow).toHaveBeenCalledWith( 24 | `${style.yellowBright.open}♦ 10${style.yellowBright.close}`, 25 | ); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWelcomeHeader.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import printRow from './printRow'; 4 | 5 | function printWelcomeHeader() { 6 | printRow(chalk.gray.dim('+*+~~~+*$#$*+~~~+*+'), { 7 | position: 'middle', 8 | padding: chalk.gray.dim('~'), 9 | }); 10 | printRow(chalk.cyan('Welcome to WarriorJS!'), { 11 | position: 'middle', 12 | }); 13 | printRow(chalk.gray.dim('+*+~~~+*$#$*+~~~+*+'), { 14 | position: 'middle', 15 | padding: chalk.gray.dim('~'), 16 | }); 17 | } 18 | 19 | export default printWelcomeHeader; 20 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/printWelcomeHeader.test.js: -------------------------------------------------------------------------------- 1 | import style from 'ansi-styles'; 2 | 3 | import printRow from './printRow'; 4 | import printWelcomeHeader from './printWelcomeHeader'; 5 | 6 | jest.mock('./printRow'); 7 | 8 | test('prints welcome header', () => { 9 | printWelcomeHeader(); 10 | expect(printRow).toHaveBeenCalledWith( 11 | `${style.gray.open}${style.dim.open}+*+~~~+*$#$*+~~~+*+${style.dim.close}${ 12 | style.gray.close 13 | }`, 14 | { 15 | position: 'middle', 16 | padding: `${style.gray.open}${style.dim.open}~${style.dim.close}${ 17 | style.gray.close 18 | }`, 19 | }, 20 | ); 21 | expect(printRow).toHaveBeenCalledWith( 22 | `${style.cyan.open}Welcome to WarriorJS!${style.cyan.close}`, 23 | { 24 | position: 'middle', 25 | }, 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestChoice.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | export const SEPARATOR = ''; 4 | 5 | function getChoices(items) { 6 | return items.map(item => { 7 | if (item === SEPARATOR) { 8 | return new inquirer.Separator(); 9 | } 10 | 11 | return { 12 | name: item.toString(), 13 | short: item.toString(), 14 | value: item, 15 | }; 16 | }); 17 | } 18 | 19 | /** 20 | * Requests a selection of one of the given items from the user. 21 | * 22 | * @param {string} message The prompt message. 23 | * @param {any[]} items The items to choose from. 24 | */ 25 | async function requestChoice(message, items) { 26 | const answerName = 'requestChoice'; 27 | const answers = await inquirer.prompt([ 28 | { 29 | message, 30 | name: answerName, 31 | type: 'list', 32 | choices: getChoices(items), 33 | }, 34 | ]); 35 | return answers[answerName]; 36 | } 37 | 38 | export default requestChoice; 39 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestChoice.test.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | import requestChoice from './requestChoice'; 4 | 5 | jest.mock('inquirer', () => ({ 6 | prompt: jest.fn(), 7 | })); 8 | 9 | test('requests selection from the user', async () => { 10 | inquirer.prompt.mockResolvedValue({ requestChoice: 'bar' }); 11 | const answer = await requestChoice('foo', ['bar', 'baz']); 12 | expect(answer).toBe('bar'); 13 | expect(inquirer.prompt).toHaveBeenCalledWith([ 14 | { 15 | name: 'requestChoice', 16 | type: 'list', 17 | message: 'foo', 18 | choices: [ 19 | { 20 | name: 'bar', 21 | short: 'bar', 22 | value: 'bar', 23 | }, 24 | { 25 | name: 'baz', 26 | short: 'baz', 27 | value: 'baz', 28 | }, 29 | ], 30 | }, 31 | ]); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestConfirmation.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | /** 4 | * Requests confirmation from the user. 5 | * 6 | * @param {string} message The prompt message. 7 | * @param {boolean} defaultAnswer The default answer. 8 | */ 9 | async function requestConfirmation(message, defaultAnswer = false) { 10 | const answerName = 'requestConfirmation'; 11 | const answers = await inquirer.prompt([ 12 | { 13 | message, 14 | name: answerName, 15 | type: 'confirm', 16 | default: defaultAnswer, 17 | }, 18 | ]); 19 | return answers[answerName]; 20 | } 21 | 22 | export default requestConfirmation; 23 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestConfirmation.test.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | import requestConfirmation from './requestConfirmation'; 4 | 5 | jest.mock('inquirer', () => ({ 6 | prompt: jest.fn(), 7 | })); 8 | 9 | test('requests confirmation from the user', async () => { 10 | inquirer.prompt.mockResolvedValue({ requestConfirmation: 42 }); 11 | const answer = await requestConfirmation('foo', true); 12 | expect(answer).toBe(42); 13 | expect(inquirer.prompt).toHaveBeenCalledWith([ 14 | { 15 | name: 'requestConfirmation', 16 | type: 'confirm', 17 | message: 'foo', 18 | default: true, 19 | }, 20 | ]); 21 | }); 22 | 23 | test('default option defaults to false', async () => { 24 | await requestConfirmation('foo'); 25 | expect(inquirer.prompt).toHaveBeenCalledWith([ 26 | { 27 | name: 'requestConfirmation', 28 | type: 'confirm', 29 | message: 'foo', 30 | default: false, 31 | }, 32 | ]); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestInput.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | inquirer.registerPrompt('suggest', require('inquirer-prompt-suggest')); 4 | 5 | /** 6 | * Requests input from the user. 7 | * 8 | * @param {string} message The prompt message. 9 | * @param {string[]} suggestions The input suggestions. 10 | */ 11 | async function requestInput(message, suggestions = []) { 12 | const answerName = 'requestInput'; 13 | const answers = await inquirer.prompt([ 14 | { 15 | message, 16 | suggestions, 17 | name: answerName, 18 | type: suggestions.length ? 'suggest' : 'input', 19 | }, 20 | ]); 21 | return answers[answerName]; 22 | } 23 | 24 | export default requestInput; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/ui/requestInput.test.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | import requestInput from './requestInput'; 4 | 5 | jest.mock('inquirer', () => ({ 6 | prompt: jest.fn(), 7 | registerPrompt: jest.fn(), 8 | })); 9 | 10 | test('requests input from the user', async () => { 11 | inquirer.prompt.mockResolvedValue({ requestInput: 42 }); 12 | const answer = await requestInput('foo'); 13 | expect(answer).toBe(42); 14 | expect(inquirer.prompt).toHaveBeenCalledWith([ 15 | { 16 | name: 'requestInput', 17 | type: 'input', 18 | message: 'foo', 19 | suggestions: [], 20 | }, 21 | ]); 22 | }); 23 | 24 | test('requests input from the user and makes suggestions', async () => { 25 | inquirer.prompt.mockResolvedValue({ requestInput: 'bar' }); 26 | const answer = await requestInput('foo', ['bar', 'baz']); 27 | expect(answer).toBe('bar'); 28 | expect(inquirer.prompt).toHaveBeenCalledWith([ 29 | { 30 | name: 'requestInput', 31 | type: 'suggest', 32 | message: 'foo', 33 | suggestions: ['bar', 'baz'], 34 | }, 35 | ]); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getFloorMap.js: -------------------------------------------------------------------------------- 1 | function getFloorMap(map) { 2 | return map.map(row => row.map(space => space.character).join('')).join('\n'); 3 | } 4 | 5 | export default getFloorMap; 6 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getFloorMap.test.js: -------------------------------------------------------------------------------- 1 | import getFloorMap from './getFloorMap'; 2 | 3 | test('returns the floor map', () => { 4 | const map = [ 5 | [{ character: 'a' }, { character: 'b' }], 6 | [{ character: 'c' }, { character: 'd' }], 7 | ]; 8 | expect(getFloorMap(map)).toBe('ab\ncd'); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getFloorMapKey.js: -------------------------------------------------------------------------------- 1 | function getFloorMapKey(map) { 2 | return map 3 | .reduce((acc, row) => acc.concat(row), []) 4 | .filter(space => space.unit) 5 | .filter( 6 | (space, index, arr) => 7 | arr.findIndex( 8 | anotherSpace => anotherSpace.character === space.character, 9 | ) === index, 10 | ) 11 | .map(({ character, unit }) => { 12 | const { name, maxHealth } = unit; 13 | return `${character} = ${name} (${maxHealth} HP)`; 14 | }) 15 | .concat(['> = stairs']) 16 | .join('\n'); 17 | } 18 | 19 | export default getFloorMapKey; 20 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getFloorMapKey.test.js: -------------------------------------------------------------------------------- 1 | import getFloorMapKey from './getFloorMapKey'; 2 | 3 | test('returns the floor map key', () => { 4 | const map = [ 5 | [ 6 | { character: '@', unit: { name: 'Joe', maxHealth: 20 } }, 7 | { character: 'b' }, 8 | ], 9 | [{ character: 'c' }], 10 | ]; 11 | expect(getFloorMapKey(map)).toBe('@ = Joe (20 HP)\n> = stairs'); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getTowerId.js: -------------------------------------------------------------------------------- 1 | const towerIdRegex = /(?:@warriorjs\/|warriorjs-)tower-(.+)/; 2 | 3 | /** 4 | * Returns the identifier of the tower based on the package name. 5 | * 6 | * @param {string} towerPackageName The package name of the tower. 7 | */ 8 | function getTowerId(towerPackageName) { 9 | return towerPackageName.match(towerIdRegex)[1]; 10 | } 11 | 12 | export default getTowerId; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getTowerId.test.js: -------------------------------------------------------------------------------- 1 | import getTowerId from './getTowerId'; 2 | 3 | test('returns the tower id for official towers', () => { 4 | expect(getTowerId('@warriorjs/tower-foo')).toBe('foo'); 5 | }); 6 | 7 | test('returns the tower id for community towers', () => { 8 | expect(getTowerId('warriorjs-tower-foo')).toBe('foo'); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getWarriorNameSuggestions.js: -------------------------------------------------------------------------------- 1 | import arrayShuffle from 'array-shuffle'; 2 | import superheroes from 'superheroes'; 3 | 4 | function getWarriorNameSuggestions() { 5 | return arrayShuffle(superheroes.all); 6 | } 7 | 8 | export default getWarriorNameSuggestions; 9 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/src/utils/getWarriorNameSuggestions.test.js: -------------------------------------------------------------------------------- 1 | import arrayShuffle from 'array-shuffle'; 2 | 3 | import getWarriorNameSuggestions from './getWarriorNameSuggestions'; 4 | 5 | jest.mock('array-shuffle'); 6 | jest.mock('superheroes', () => ({ 7 | all: ['foo', 'bar'], 8 | })); 9 | 10 | test('returns shuffled list of warrior names', () => { 11 | getWarriorNameSuggestions(); 12 | expect(arrayShuffle).toHaveBeenCalledWith(['foo', 'bar']); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/templates/Player.js: -------------------------------------------------------------------------------- 1 | class Player { 2 | playTurn(warrior) { 3 | // Cool code goes here. 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/templates/README.md.ejs: -------------------------------------------------------------------------------- 1 | # <%- profile.warriorName %> - <%- profile.tower.name %> 2 | <% if (profile.tower.description) { -%> 3 | 4 | ### _<%- profile.tower.description %>_ 5 | <% } -%> 6 | 7 | ## Level <%- level.number %> 8 | 9 | <%- include('readme/level', { getFloorMap, getFloorMapKey, level }); -%> 10 | 11 | ## Abilities 12 | 13 | <%- 14 | include('readme/abilities', { 15 | abilities: level.warriorAbilities, 16 | }); 17 | -%> 18 | 19 | ## Next Steps 20 | 21 | When you're done editing `Player.js`, run the `warriorjs` command again. 22 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/templates/readme/abilities.ejs: -------------------------------------------------------------------------------- 1 | ### Actions (only one per turn) 2 | 3 | <% abilities.actions.forEach(ability => { -%> 4 | <%- include('ability', { ability }); %> 5 | <% }); -%> 6 | <% if (abilities.senses.length) { -%> 7 | 8 | ### Senses 9 | 10 | <% abilities.senses.forEach(ability => { -%> 11 | <%- include('ability', { ability }); %> 12 | <% }); -%> 13 | <% } -%> 14 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/templates/readme/ability.ejs: -------------------------------------------------------------------------------- 1 | - `warrior.<%- ability.name %>()`: <%- ability.description -%> 2 | -------------------------------------------------------------------------------- /packages/warriorjs-cli/templates/readme/level.ejs: -------------------------------------------------------------------------------- 1 | _<%- level.description %>_ 2 | 3 | > **TIP:** <%- level.tip %> 4 | <% if (profile.isShowingClue()) { -%> 5 | 6 | > **CLUE:** <%- level.clue %> 7 | <% } -%> 8 | 9 | ### Floor Map 10 | 11 | ``` 12 | <%- getFloorMap(level.floorMap) %> 13 | 14 | <%- getFloorMapKey(level.floorMap) %> 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/warriorjs-core/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/core 2 | 3 | > WarriorJS core. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install @warriorjs/core 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const warriorjs = require('@warriorjs/core'); 15 | import { runLevel } from '@warriorjs/core'); 16 | import * as warriorjs from '@warriorjs/core'; 17 | ``` 18 | 19 | ## API Reference 20 | 21 | ### warriorjs.runLevel(levelConfig: Object, playerCode: string) 22 | 23 | Runs the given level config with the given player code. 24 | 25 | ### warriorjs.getLevel(levelConfig: Object) 26 | 27 | Returns the level for the given level config. 28 | -------------------------------------------------------------------------------- /packages/warriorjs-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/core", 3 | "version": "0.14.0", 4 | "description": "WarriorJS core", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-core", 9 | "main": "lib/index.js", 10 | "keywords": [ 11 | "warriorjs", 12 | "warriorjs-core", 13 | "warrior", 14 | "epic", 15 | "battle", 16 | "game", 17 | "learn", 18 | "polish", 19 | "refine", 20 | "test", 21 | "js", 22 | "javascript", 23 | "nodejs", 24 | "ai", 25 | "artificial-intelligence", 26 | "skills" 27 | ], 28 | "files": [ 29 | "lib" 30 | ], 31 | "publishConfig": { 32 | "access": "public" 33 | }, 34 | "scripts": { 35 | "build": "babel src --out-dir lib --ignore test.js" 36 | }, 37 | "dependencies": { 38 | "@warriorjs/geography": "^0.7.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/Level.test.js: -------------------------------------------------------------------------------- 1 | import { EAST, FORWARD } from '@warriorjs/geography'; 2 | 3 | import Floor from './Floor'; 4 | import Level from './Level'; 5 | import Warrior from './Warrior'; 6 | 7 | describe('Level', () => { 8 | let floor; 9 | let level; 10 | let warrior; 11 | 12 | beforeEach(() => { 13 | warrior = new Warrior('Joe', '@', '#8fbcbb', 20); 14 | warrior.log = jest.fn(); 15 | floor = new Floor(2, 1, [1, 0]); 16 | floor.addUnit(warrior, { x: 0, y: 0, facing: EAST }); 17 | floor.warrior = warrior; 18 | level = new Level(1, 'a description', 'a tip', 'a clue', floor); 19 | }); 20 | 21 | describe('playing', () => { 22 | beforeEach(() => { 23 | warrior.prepareTurn = jest.fn(); 24 | warrior.performTurn = jest.fn(); 25 | }); 26 | 27 | test('calls prepareTurn and playTurn on each unit once per turn', () => { 28 | level.play(2); 29 | expect(warrior.prepareTurn).toHaveBeenCalledTimes(2); 30 | expect(warrior.performTurn).toHaveBeenCalledTimes(2); 31 | }); 32 | 33 | test('plays for a max number of turns which defaults to 200', () => { 34 | level.play(); 35 | expect(warrior.prepareTurn).toHaveBeenCalledTimes(200); 36 | expect(warrior.performTurn).toHaveBeenCalledTimes(200); 37 | }); 38 | 39 | test('returns immediately when passed', () => { 40 | level.wasPassed = () => true; 41 | level.play(2); 42 | expect(warrior.performTurn).not.toHaveBeenCalled(); 43 | }); 44 | 45 | test('returns immediately when failed', () => { 46 | level.wasFailed = () => true; 47 | level.play(2); 48 | expect(warrior.performTurn).not.toHaveBeenCalled(); 49 | }); 50 | }); 51 | 52 | test('considers passed when warrior is on stairs', () => { 53 | warrior.move(FORWARD); 54 | expect(level.wasPassed()).toBe(true); 55 | }); 56 | 57 | test('considers failed when warrior is dead', () => { 58 | warrior.isAlive = () => false; 59 | expect(level.wasFailed()).toBe(true); 60 | }); 61 | 62 | test('has a minimal JSON representation', () => { 63 | expect(level.toJSON()).toEqual({ 64 | number: 1, 65 | description: 'a description', 66 | tip: 'a tip', 67 | clue: 'a clue', 68 | floorMap: level.floor.getMap(), 69 | warriorStatus: level.floor.warrior.getStatus(), 70 | warriorAbilities: level.floor.warrior.getAbilities(), 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/Logger.js: -------------------------------------------------------------------------------- 1 | const Logger = { 2 | /** 3 | * Prepares logger for a play. 4 | * 5 | * @param {Floor} floor The floor of the level. 6 | */ 7 | play(floor) { 8 | Logger.floor = floor; 9 | Logger.events = []; 10 | Logger.lastTurn = null; 11 | }, 12 | /** 13 | * Prepares logger for a turn. 14 | */ 15 | turn() { 16 | Logger.lastTurn = []; 17 | Logger.events.push(Logger.lastTurn); 18 | }, 19 | /** 20 | * Logs a message with the accompanying unit. 21 | * 22 | * @param {Unit} unit The owner of the message. 23 | * @param {string} message The message. 24 | */ 25 | unit(unit, message) { 26 | Logger.lastTurn.push({ 27 | message, 28 | unit: JSON.parse(JSON.stringify(unit)), 29 | floorMap: JSON.parse(JSON.stringify(Logger.floor.getMap())), 30 | warriorStatus: Logger.floor.warrior.getStatus(), 31 | }); 32 | }, 33 | }; 34 | 35 | export default Logger; 36 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/Warrior.js: -------------------------------------------------------------------------------- 1 | import Unit from './Unit'; 2 | 3 | /** Class representing a warrior. */ 4 | class Warrior extends Unit { 5 | /** 6 | * Creates a warrior. 7 | * 8 | * @param {string} name The name of the warrior. 9 | * @param {string} character The character of the warrior. 10 | * @param {string} color The color of the warrior. 11 | * @param {number} maxHealth The max health in HP. 12 | */ 13 | constructor(name, character, color, maxHealth) { 14 | super(name, character, color, maxHealth, null, false); 15 | } 16 | 17 | performTurn() { 18 | super.performTurn(); 19 | if (!this.turn.action || this.isBound()) { 20 | this.log('does nothing'); 21 | } 22 | } 23 | 24 | earnPoints(points) { 25 | super.earnPoints(points); 26 | this.log(`earns ${points} points`); 27 | } 28 | 29 | losePoints(points) { 30 | super.losePoints(points); 31 | this.log(`loses ${points} points`); 32 | } 33 | 34 | /** 35 | * Returns a grouped collection of abilities. 36 | * 37 | * @returns {Object} The collection of abilities. 38 | */ 39 | getAbilities() { 40 | const abilities = [...this.abilities].map( 41 | ([name, { action, description }]) => ({ 42 | name, 43 | action, 44 | description, 45 | }), 46 | ); 47 | const sortedAbilities = abilities.sort((a, b) => a.name > b.name); 48 | const actions = sortedAbilities 49 | .filter(ability => ability.action) 50 | .map(({ action, ...rest }) => rest); 51 | const senses = sortedAbilities 52 | .filter(ability => !ability.action) 53 | .map(({ action, ...rest }) => rest); 54 | return { 55 | actions, 56 | senses, 57 | }; 58 | } 59 | 60 | /** 61 | * Returns the status of the warrior. 62 | * 63 | * The status includes the current health and score values. 64 | * 65 | * @returns {Object} The status of the warrior. 66 | */ 67 | getStatus() { 68 | return { 69 | health: this.health, 70 | score: this.score, 71 | }; 72 | } 73 | } 74 | 75 | export default Warrior; 76 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/Warrior.test.js: -------------------------------------------------------------------------------- 1 | import Warrior from './Warrior'; 2 | 3 | describe('Warrior', () => { 4 | let warrior; 5 | 6 | beforeEach(() => { 7 | warrior = new Warrior('Joe', '@', '#8fbcbb', 20); 8 | warrior.addAbility('feel', { description: 'a description' }); 9 | warrior.addAbility('walk', { action: true, description: 'a description' }); 10 | warrior.log = jest.fn(); 11 | }); 12 | 13 | test('is upset for not doing anything when no action', () => { 14 | warrior.turn = { action: null }; 15 | warrior.performTurn(); 16 | expect(warrior.log).toHaveBeenCalledWith('does nothing'); 17 | }); 18 | 19 | test('is upset for not doing anything when bound', () => { 20 | warrior.bind(); 21 | warrior.turn = { action: ['walk', []] }; 22 | warrior.performTurn(); 23 | expect(warrior.log).toHaveBeenCalledWith('does nothing'); 24 | }); 25 | 26 | test('is proud of earning points', () => { 27 | warrior.earnPoints(5); 28 | expect(warrior.log).toHaveBeenCalledWith('earns 5 points'); 29 | }); 30 | 31 | test('is upset for losing points', () => { 32 | warrior.losePoints(5); 33 | expect(warrior.log).toHaveBeenCalledWith('loses 5 points'); 34 | }); 35 | 36 | test('has a grouped collection of abilities', () => { 37 | expect(warrior.getAbilities()).toEqual({ 38 | actions: [{ name: 'walk', description: 'a description' }], 39 | senses: [{ name: 'feel', description: 'a description' }], 40 | }); 41 | }); 42 | 43 | test('has a status', () => { 44 | expect(warrior.getStatus()).toEqual({ 45 | health: 20, 46 | score: 0, 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/getLevel.js: -------------------------------------------------------------------------------- 1 | import loadLevel from './loadLevel'; 2 | 3 | /** 4 | * Returns the level for the given level config. 5 | * 6 | * @param {Object} levelConfig The config of the level. 7 | * 8 | * @returns {Object} The level. 9 | */ 10 | function getLevel(levelConfig) { 11 | const level = loadLevel(levelConfig); 12 | return JSON.parse(JSON.stringify(level)); 13 | } 14 | 15 | export default getLevel; 16 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as getLevel } from './getLevel'; 2 | export { default as runLevel } from './runLevel'; 3 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/loadPlayer.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import vm from 'vm'; 3 | 4 | const playerCodeFilename = 'Player.js'; 5 | const playerCodeTimeout = 3000; 6 | 7 | /** 8 | * Loads the player code and returns the playTurn function. 9 | * 10 | * @param {string} playerCode The code of the player. 11 | * 12 | * @returns {Function} The playTurn function. 13 | */ 14 | function loadPlayer(playerCode) { 15 | const sandbox = vm.createContext(); 16 | 17 | // Do not collect stack frames for errors in the player code. 18 | vm.runInContext('Error.stackTraceLimit = 0;', sandbox); 19 | 20 | try { 21 | vm.runInContext(playerCode, sandbox, { 22 | filename: playerCodeFilename, 23 | timeout: playerCodeTimeout, 24 | }); 25 | } catch (err) { 26 | const error = new Error(`Check your syntax and try again!\n\n${err.stack}`); 27 | error.code = 'InvalidPlayerCode'; 28 | throw error; 29 | } 30 | 31 | try { 32 | const player = vm.runInContext('new Player();', sandbox, { 33 | timeout: playerCodeTimeout, 34 | }); 35 | assert(typeof player.playTurn === 'function', 'playTurn is not defined'); 36 | const playTurn = turn => { 37 | try { 38 | player.playTurn(turn); 39 | } catch (err) { 40 | const error = new Error(err.message); 41 | error.code = 'InvalidPlayerCode'; 42 | throw error; 43 | } 44 | }; 45 | return playTurn; 46 | } catch (err) { 47 | if (err.message === 'Player is not defined') { 48 | const error = new Error('You must define a Player class!'); 49 | error.code = 'InvalidPlayerCode'; 50 | throw error; 51 | } else if (err.message === 'playTurn is not defined') { 52 | const error = new Error( 53 | 'Your Player class must define a playTurn method!', 54 | ); 55 | error.code = 'InvalidPlayerCode'; 56 | throw error; 57 | } 58 | 59 | throw err; 60 | } 61 | } 62 | 63 | export default loadPlayer; 64 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/loadPlayer.test.js: -------------------------------------------------------------------------------- 1 | import loadPlayer from './loadPlayer'; 2 | 3 | test('runs player code and returns playTurn function', () => { 4 | const playerCode = ` 5 | class Player { 6 | playTurn(warrior) { 7 | warrior.walk(); 8 | } 9 | } 10 | `; 11 | const warrior = { walk: jest.fn() }; 12 | const playTurn = loadPlayer(playerCode); 13 | playTurn(warrior); 14 | expect(warrior.walk).toHaveBeenCalled(); 15 | }); 16 | 17 | test('throws if invalid syntax', () => { 18 | const playerCode = ` 19 | class Player { 20 | playTurn() {} 21 | `; 22 | expect(() => { 23 | loadPlayer(playerCode); 24 | }).toThrow( 25 | new Error( 26 | 'Check your syntax and try again!\n\nPlayer.js:3\n playTurn() {}\n ^\n\nSyntaxError: Unexpected end of input', 27 | ), 28 | ); 29 | }); 30 | 31 | test('throws if Player class is not defined', () => { 32 | const playerCode = 'function playTurn() {}'; 33 | expect(() => { 34 | loadPlayer(playerCode); 35 | }).toThrow(new Error('You must define a Player class!')); 36 | }); 37 | 38 | test('throws if playTurn method is not defined', () => { 39 | const playerCode = 'class Player {}'; 40 | expect(() => { 41 | loadPlayer(playerCode); 42 | }).toThrow(new Error('Your Player class must define a playTurn method!')); 43 | }); 44 | 45 | test("throws when playing turn if there's something wrong", () => { 46 | const playerCode = ` 47 | class Player { 48 | playTurn(warrior) { 49 | warrior.walk(); 50 | } 51 | } 52 | `; 53 | const playTurn = loadPlayer(playerCode); 54 | const warrior = {}; 55 | expect(() => { 56 | playTurn(warrior); 57 | }).toThrow(new Error('warrior.walk is not a function')); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/warriorjs-core/src/runLevel.js: -------------------------------------------------------------------------------- 1 | import loadLevel from './loadLevel'; 2 | 3 | /** 4 | * Runs the given level config. 5 | * 6 | * @param {Object} levelConfig The config of the level. 7 | * @param {string} playerCode The code of the player. 8 | * 9 | * @returns {Object} The result of the level. 10 | */ 11 | function runLevel(levelConfig, playerCode) { 12 | const level = loadLevel(levelConfig, playerCode); 13 | return level.play(); 14 | } 15 | 16 | export default runLevel; 17 | -------------------------------------------------------------------------------- /packages/warriorjs-effects/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/effects 2 | 3 | > WarriorJS official effects. 4 | 5 | ## [Effects](https://warrior.js.org/docs/player/effects) 6 | 7 | ### ticking 8 | 9 | Kills you and all surrounding units when time reaches zero. 10 | -------------------------------------------------------------------------------- /packages/warriorjs-effects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/effects", 3 | "version": "0.12.2", 4 | "description": "WarriorJS base effects", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-effects", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/warriorjs-effects/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as ticking } from './ticking'; // eslint-disable-line import/prefer-default-export 2 | -------------------------------------------------------------------------------- /packages/warriorjs-effects/src/ticking.js: -------------------------------------------------------------------------------- 1 | function ticking({ time }) { 2 | return unit => ({ 3 | time, 4 | description: 'Kills you and all surrounding units when time reaches zero.', 5 | passTurn() { 6 | if (this.time) { 7 | this.time -= 1; 8 | } 9 | 10 | unit.log('is ticking'); 11 | 12 | if (!this.time) { 13 | this.trigger(); 14 | } 15 | }, 16 | trigger() { 17 | unit.log('explodes, collapsing the ceiling and killing every unit'); 18 | [...unit.getOtherUnits(), unit].forEach(anotherUnit => 19 | anotherUnit.takeDamage(anotherUnit.health), 20 | ); 21 | }, 22 | }); 23 | } 24 | 25 | export default ticking; 26 | -------------------------------------------------------------------------------- /packages/warriorjs-effects/src/ticking.test.js: -------------------------------------------------------------------------------- 1 | import tickingCreator from './ticking'; 2 | 3 | describe('ticking', () => { 4 | let ticking; 5 | let unit; 6 | 7 | beforeEach(() => { 8 | unit = { 9 | health: 20, 10 | takeDamage: jest.fn(), 11 | log: jest.fn(), 12 | }; 13 | ticking = tickingCreator({ time: 3 })(unit); 14 | }); 15 | 16 | test('has a description', () => { 17 | expect(ticking.description).toBe( 18 | 'Kills you and all surrounding units when time reaches zero.', 19 | ); 20 | }); 21 | 22 | describe('passing turn', () => { 23 | test('counts down bomb timer once', () => { 24 | ticking.passTurn(); 25 | expect(ticking.time).toBe(2); 26 | expect(unit.log).toHaveBeenCalledWith('is ticking'); 27 | }); 28 | 29 | test("doesn't count down bomb timer below zero", () => { 30 | ticking.trigger = () => {}; 31 | ticking.time = 0; 32 | ticking.passTurn(); 33 | expect(ticking.time).toBe(0); 34 | }); 35 | 36 | test('triggers when bomb time reaches zero', () => { 37 | ticking.trigger = jest.fn(); 38 | ticking.time = 2; 39 | ticking.passTurn(); 40 | expect(ticking.trigger).not.toHaveBeenCalled(); 41 | ticking.passTurn(); 42 | expect(ticking.trigger).toHaveBeenCalled(); 43 | }); 44 | }); 45 | 46 | describe('triggering', () => { 47 | test('kills each unit on the floor', () => { 48 | const anotherUnit = { 49 | health: 10, 50 | takeDamage: jest.fn(), 51 | }; 52 | unit.getOtherUnits = () => [anotherUnit]; 53 | ticking.trigger(); 54 | expect(unit.log).toHaveBeenCalledWith( 55 | 'explodes, collapsing the ceiling and killing every unit', 56 | ); 57 | expect(anotherUnit.takeDamage).toHaveBeenCalledWith(10); 58 | expect(unit.takeDamage).toHaveBeenCalledWith(20); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/geography", 3 | "version": "0.7.0", 4 | "description": "WarriorJS directioning", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-geography", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/absoluteDirections.js: -------------------------------------------------------------------------------- 1 | export const NORTH = 'north'; 2 | export const EAST = 'east'; 3 | export const SOUTH = 'south'; 4 | export const WEST = 'west'; 5 | 6 | /** 7 | * The absolute directions in clockwise order. 8 | */ 9 | export const ABSOLUTE_DIRECTIONS = [NORTH, EAST, SOUTH, WEST]; 10 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/absoluteDirections.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | ABSOLUTE_DIRECTIONS, 3 | EAST, 4 | NORTH, 5 | SOUTH, 6 | WEST, 7 | } from './absoluteDirections'; 8 | 9 | test("exports a NORTH constant whose value is 'north'", () => { 10 | expect(NORTH).toBe('north'); 11 | }); 12 | 13 | test("exports a EAST constant whose value is 'east'", () => { 14 | expect(EAST).toBe('east'); 15 | }); 16 | 17 | test("exports a SOUTH constant whose value is 'south'", () => { 18 | expect(SOUTH).toBe('south'); 19 | }); 20 | 21 | test("exports a WEST constant whose value is 'west'", () => { 22 | expect(WEST).toBe('west'); 23 | }); 24 | 25 | test('exports an array with the absolute directions in clockwise order', () => { 26 | expect(ABSOLUTE_DIRECTIONS).toEqual([NORTH, EAST, SOUTH, WEST]); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getAbsoluteDirection.js: -------------------------------------------------------------------------------- 1 | import verifyAbsoluteDirection from './verifyAbsoluteDirection'; 2 | import verifyRelativeDirection from './verifyRelativeDirection'; 3 | import { ABSOLUTE_DIRECTIONS } from './absoluteDirections'; 4 | import { RELATIVE_DIRECTIONS } from './relativeDirections'; 5 | 6 | /** 7 | * Returns the absolute direction for a given direction, with reference to 8 | * another direction (reference direction). 9 | * 10 | * @param {string} direction The direction (relative). 11 | * @param {string} referenceDirection The reference direction (absolute). 12 | * 13 | * @returns {string} The absolute direction. 14 | */ 15 | function getAbsoluteDirection(direction, referenceDirection) { 16 | verifyRelativeDirection(direction); 17 | verifyAbsoluteDirection(referenceDirection); 18 | 19 | const index = 20 | (ABSOLUTE_DIRECTIONS.indexOf(referenceDirection) + 21 | RELATIVE_DIRECTIONS.indexOf(direction)) % 22 | 4; 23 | return ABSOLUTE_DIRECTIONS[index]; 24 | } 25 | 26 | export default getAbsoluteDirection; 27 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getAbsoluteOffset.js: -------------------------------------------------------------------------------- 1 | import verifyAbsoluteDirection from './verifyAbsoluteDirection'; 2 | import { EAST, NORTH, SOUTH } from './absoluteDirections'; 3 | 4 | /** 5 | * Returns the absolute offset for a given relative offset with reference 6 | * to a given direction (reference direction). 7 | * 8 | * @param {number[]} offset The relative offset as [forward, right]. 9 | * @param {string} referenceDirection The reference direction (absolute). 10 | * 11 | * @returns {number[]} The absolute offset as [deltaX, deltaY]. 12 | */ 13 | function getAbsoluteOffset([forward, right], referenceDirection) { 14 | verifyAbsoluteDirection(referenceDirection); 15 | 16 | if (referenceDirection === NORTH) { 17 | return [right, -forward]; 18 | } 19 | 20 | if (referenceDirection === EAST) { 21 | return [forward, right]; 22 | } 23 | 24 | if (referenceDirection === SOUTH) { 25 | return [-right, forward]; 26 | } 27 | 28 | return [-forward, -right]; 29 | } 30 | 31 | export default getAbsoluteOffset; 32 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getAbsoluteOffset.test.js: -------------------------------------------------------------------------------- 1 | import getAbsoluteOffset from './getAbsoluteOffset'; 2 | import { EAST, NORTH, SOUTH, WEST } from './absoluteDirections'; 3 | 4 | test('returns the absolute offset based on direction', () => { 5 | expect(getAbsoluteOffset([1, 2], NORTH)).toEqual([2, -1]); 6 | expect(getAbsoluteOffset([1, 2], EAST)).toEqual([1, 2]); 7 | expect(getAbsoluteOffset([1, 2], SOUTH)).toEqual([-2, 1]); 8 | expect(getAbsoluteOffset([1, 2], WEST)).toEqual([-1, -2]); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getDirectionOfLocation.js: -------------------------------------------------------------------------------- 1 | import { EAST, NORTH, SOUTH, WEST } from './absoluteDirections'; 2 | 3 | /** 4 | * Returns the direction of a location from another location (reference 5 | * location). 6 | * 7 | * @param {number[]} location The location as [x, y]. 8 | * @param {number[]} referenceLocation The reference location as [x, y]. 9 | * 10 | * @returns {string} The direction. 11 | */ 12 | function getDirectionOfLocation([x1, y1], [x2, y2]) { 13 | if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) { 14 | if (x1 > x2) { 15 | return EAST; 16 | } 17 | 18 | return WEST; 19 | } 20 | 21 | if (y1 > y2) { 22 | return SOUTH; 23 | } 24 | 25 | return NORTH; 26 | } 27 | 28 | export default getDirectionOfLocation; 29 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getDirectionOfLocation.test.js: -------------------------------------------------------------------------------- 1 | import getDirectionOfLocation from './getDirectionOfLocation'; 2 | import { EAST, NORTH, SOUTH, WEST } from './absoluteDirections'; 3 | 4 | test('returns the direction from a given location to another given location', () => { 5 | expect(getDirectionOfLocation([1, 1], [1, 2])).toEqual(NORTH); 6 | expect(getDirectionOfLocation([2, 2], [1, 2])).toEqual(EAST); 7 | expect(getDirectionOfLocation([1, 3], [1, 2])).toEqual(SOUTH); 8 | expect(getDirectionOfLocation([0, 2], [1, 2])).toEqual(WEST); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getDistanceOfLocation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the Manhattan distance of a location from another location (reference 3 | * location). 4 | * 5 | * @param {number[]} location The location as [x, y]. 6 | * @param {number[]} referenceLocation The reference location as [x, y]. 7 | * 8 | * @returns {number} The distance between the locations. 9 | */ 10 | function getDistanceOfLocation([x1, y1], [x2, y2]) { 11 | return Math.abs(x2 - x1) + Math.abs(y2 - y1); 12 | } 13 | 14 | export default getDistanceOfLocation; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getDistanceOfLocation.test.js: -------------------------------------------------------------------------------- 1 | import getDistanceOfLocation from './getDistanceOfLocation'; 2 | 3 | test('returns the distance between the two given locations', () => { 4 | expect(getDistanceOfLocation([5, 3], [1, 2])).toBe(5); 5 | expect(getDistanceOfLocation([4, 2], [1, 2])).toBe(3); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getRelativeDirection.js: -------------------------------------------------------------------------------- 1 | import verifyAbsoluteDirection from './verifyAbsoluteDirection'; 2 | import { ABSOLUTE_DIRECTIONS } from './absoluteDirections'; 3 | import { RELATIVE_DIRECTIONS } from './relativeDirections'; 4 | 5 | /** 6 | * Returns the relative direction for a given direction, with reference to a 7 | * another direction (reference direction). 8 | * 9 | * @param {string} direction The direction (absolute). 10 | * @param {string} referenceDirection The reference direction (absolute). 11 | * 12 | * @returns {string} The relative direction. 13 | */ 14 | function getRelativeDirection(direction, referenceDirection) { 15 | verifyAbsoluteDirection(direction); 16 | verifyAbsoluteDirection(referenceDirection); 17 | 18 | const index = 19 | (ABSOLUTE_DIRECTIONS.indexOf(direction) - 20 | ABSOLUTE_DIRECTIONS.indexOf(referenceDirection) + 21 | RELATIVE_DIRECTIONS.length) % 22 | RELATIVE_DIRECTIONS.length; 23 | return RELATIVE_DIRECTIONS[index]; 24 | } 25 | 26 | export default getRelativeDirection; 27 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getRelativeOffset.js: -------------------------------------------------------------------------------- 1 | import verifyAbsoluteDirection from './verifyAbsoluteDirection'; 2 | import { EAST, NORTH, SOUTH } from './absoluteDirections'; 3 | 4 | /** 5 | * Returns the relative offset for a given location, with reference to another 6 | * location (reference location) and direction (reference direction). 7 | * 8 | * @param {number[]} location The location. 9 | * @param {number[]} referenceLocation The reference location. 10 | * @param {string} referenceDirection The reference direction (absolute). 11 | * 12 | * @returns {number[]} The relative offset as [forward, right]. 13 | */ 14 | function getRelativeOffset([x1, y1], [x2, y2], referenceDirection) { 15 | verifyAbsoluteDirection(referenceDirection); 16 | 17 | const [deltaX, deltaY] = [x1 - x2, y1 - y2]; 18 | 19 | if (referenceDirection === NORTH) { 20 | return [-deltaY, deltaX]; 21 | } 22 | 23 | if (referenceDirection === EAST) { 24 | return [deltaX, deltaY]; 25 | } 26 | 27 | if (referenceDirection === SOUTH) { 28 | return [deltaY, -deltaX]; 29 | } 30 | 31 | return [-deltaX, -deltaY]; 32 | } 33 | 34 | export default getRelativeOffset; 35 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/getRelativeOffset.test.js: -------------------------------------------------------------------------------- 1 | import getRelativeOffset from './getRelativeOffset'; 2 | import { EAST, NORTH, SOUTH, WEST } from './absoluteDirections'; 3 | 4 | test('returns the relative offset based on location and direction', () => { 5 | expect(getRelativeOffset([3, 3], [1, 2], NORTH)).toEqual([-1, 2]); 6 | expect(getRelativeOffset([3, 3], [1, 2], EAST)).toEqual([2, 1]); 7 | expect(getRelativeOffset([3, 3], [1, 2], SOUTH)).toEqual([1, -2]); 8 | expect(getRelativeOffset([3, 3], [1, 2], WEST)).toEqual([-2, -1]); 9 | expect(getRelativeOffset([0, 0], [1, 2], NORTH)).toEqual([2, -1]); 10 | expect(getRelativeOffset([0, 0], [1, 2], EAST)).toEqual([-1, -2]); 11 | expect(getRelativeOffset([0, 0], [1, 2], SOUTH)).toEqual([-2, 1]); 12 | expect(getRelativeOffset([0, 0], [1, 2], WEST)).toEqual([1, 2]); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | ABSOLUTE_DIRECTIONS, 3 | EAST, 4 | NORTH, 5 | SOUTH, 6 | WEST, 7 | } from './absoluteDirections'; 8 | export { default as getAbsoluteDirection } from './getAbsoluteDirection'; 9 | export { default as verifyAbsoluteDirection } from './verifyAbsoluteDirection'; 10 | 11 | export { 12 | BACKWARD, 13 | FORWARD, 14 | LEFT, 15 | RELATIVE_DIRECTIONS, 16 | RIGHT, 17 | } from './relativeDirections'; 18 | export { default as getRelativeDirection } from './getRelativeDirection'; 19 | export { default as verifyRelativeDirection } from './verifyRelativeDirection'; 20 | 21 | export { default as getAbsoluteOffset } from './getAbsoluteOffset'; 22 | export { default as getRelativeOffset } from './getRelativeOffset'; 23 | export { default as rotateRelativeOffset } from './rotateRelativeOffset'; 24 | export { default as translateLocation } from './translateLocation'; 25 | 26 | export { default as getDirectionOfLocation } from './getDirectionOfLocation'; 27 | 28 | export { default as getDistanceOfLocation } from './getDistanceOfLocation'; 29 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/relativeDirections.js: -------------------------------------------------------------------------------- 1 | export const FORWARD = 'forward'; 2 | export const RIGHT = 'right'; 3 | export const BACKWARD = 'backward'; 4 | export const LEFT = 'left'; 5 | 6 | /** 7 | * The relative directions in clockwise order. 8 | */ 9 | export const RELATIVE_DIRECTIONS = [FORWARD, RIGHT, BACKWARD, LEFT]; 10 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/relativeDirections.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | BACKWARD, 3 | FORWARD, 4 | LEFT, 5 | RELATIVE_DIRECTIONS, 6 | RIGHT, 7 | } from './relativeDirections'; 8 | 9 | test("exports a FORWARD constant whose value is 'forward'", () => { 10 | expect(FORWARD).toBe('forward'); 11 | }); 12 | 13 | test("exports a RIGHT constant whose value is 'right'", () => { 14 | expect(RIGHT).toBe('right'); 15 | }); 16 | 17 | test("exports a BACKWARD constant whose value is 'backward'", () => { 18 | expect(BACKWARD).toBe('backward'); 19 | }); 20 | 21 | test("exports a LEFT constant whose value is 'left'", () => { 22 | expect(LEFT).toBe('left'); 23 | }); 24 | 25 | test('exports an array with the relative directions in clockwise order', () => { 26 | expect(RELATIVE_DIRECTIONS).toEqual([FORWARD, RIGHT, BACKWARD, LEFT]); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/rotateRelativeOffset.js: -------------------------------------------------------------------------------- 1 | import verifyRelativeDirection from './verifyRelativeDirection'; 2 | import { BACKWARD, FORWARD, RIGHT } from './relativeDirections'; 3 | 4 | /** 5 | * Rotates the given relative offset in the given direction. 6 | * 7 | * @param {number[]} offset The relative offset as [forward, right]. 8 | * @param {string} direction The direction (relative direction). 9 | * 10 | * @returns {number[]} The rotated offset as [forward, right]. 11 | */ 12 | function rotateRelativeOffset([forward, right], direction) { 13 | verifyRelativeDirection(direction); 14 | 15 | if (direction === FORWARD) { 16 | return [forward, right]; 17 | } 18 | 19 | if (direction === RIGHT) { 20 | return [-right, forward]; 21 | } 22 | 23 | if (direction === BACKWARD) { 24 | return [-forward, -right]; 25 | } 26 | 27 | return [right, -forward]; 28 | } 29 | 30 | export default rotateRelativeOffset; 31 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/rotateRelativeOffset.test.js: -------------------------------------------------------------------------------- 1 | import rotateRelativeOffset from './rotateRelativeOffset'; 2 | import { BACKWARD, FORWARD, LEFT, RIGHT } from './relativeDirections'; 3 | 4 | test('rotates the relative offset in the given direction', () => { 5 | expect(rotateRelativeOffset([1, 2], FORWARD)).toEqual([1, 2]); 6 | expect(rotateRelativeOffset([1, 2], RIGHT)).toEqual([-2, 1]); 7 | expect(rotateRelativeOffset([1, 2], BACKWARD)).toEqual([-1, -2]); 8 | expect(rotateRelativeOffset([1, 2], LEFT)).toEqual([2, -1]); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/translateLocation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Translates the given location by a given offset. 3 | * 4 | * @param {number[]} location The location as [x, y]. 5 | * @param {number[]} offset The offset as [deltaX, deltaY]. 6 | * 7 | * @returns {number[]} The translated location. 8 | */ 9 | function translateLocation([x, y], [deltaX, deltaY]) { 10 | return [x + deltaX, y + deltaY]; 11 | } 12 | 13 | export default translateLocation; 14 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/translateLocation.test.js: -------------------------------------------------------------------------------- 1 | import translateLocation from './translateLocation'; 2 | 3 | test('translates the given location by the given offset', () => { 4 | expect(translateLocation([1, 2], [2, -1])).toEqual([3, 1]); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/verifyAbsoluteDirection.js: -------------------------------------------------------------------------------- 1 | import { 2 | ABSOLUTE_DIRECTIONS, 3 | EAST, 4 | NORTH, 5 | SOUTH, 6 | WEST, 7 | } from './absoluteDirections'; 8 | 9 | /** 10 | * Checks if the given direction is a valid absolute direction. 11 | * 12 | * @param {string} direction The direction. 13 | * 14 | * @throws Will throw if the direction is not valid. 15 | */ 16 | function verifyAbsoluteDirection(direction) { 17 | if (!ABSOLUTE_DIRECTIONS.includes(direction)) { 18 | throw new Error( 19 | `Unknown direction: '${direction}'. Should be one of: '${NORTH}', '${EAST}', '${SOUTH}' or '${WEST}'.`, 20 | ); 21 | } 22 | } 23 | 24 | export default verifyAbsoluteDirection; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/verifyAbsoluteDirection.test.js: -------------------------------------------------------------------------------- 1 | import verifyAbsoluteDirection from './verifyAbsoluteDirection'; 2 | import { 3 | ABSOLUTE_DIRECTIONS, 4 | EAST, 5 | NORTH, 6 | SOUTH, 7 | WEST, 8 | } from './absoluteDirections'; 9 | 10 | test("doesn't throw if direction is valid", () => { 11 | const validDirections = ABSOLUTE_DIRECTIONS; 12 | validDirections.forEach(validDirection => 13 | verifyAbsoluteDirection(validDirection), 14 | ); 15 | }); 16 | 17 | test('throws an error if direction is not valid', () => { 18 | const invalidDirections = ['', 'foo', 'north\n', 'North', 'southern']; 19 | invalidDirections.forEach(invalidDirection => { 20 | expect(() => { 21 | verifyAbsoluteDirection(invalidDirection); 22 | }).toThrow( 23 | `Unknown direction: '${invalidDirection}'. Should be one of: '${NORTH}', '${EAST}', '${SOUTH}' or '${WEST}'.`, 24 | ); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/verifyRelativeDirection.js: -------------------------------------------------------------------------------- 1 | import { 2 | BACKWARD, 3 | FORWARD, 4 | LEFT, 5 | RELATIVE_DIRECTIONS, 6 | RIGHT, 7 | } from './relativeDirections'; 8 | 9 | /** 10 | * Checks if the given direction is a valid relative direction. 11 | * 12 | * @param {string} direction The direction. 13 | * 14 | * @throws Will throw if the direction is not valid. 15 | */ 16 | function verifyRelativeDirection(direction) { 17 | if (!RELATIVE_DIRECTIONS.includes(direction)) { 18 | throw new Error( 19 | `Unknown direction: '${direction}'. Should be one of: '${FORWARD}', '${RIGHT}', '${BACKWARD}' or '${LEFT}'.`, 20 | ); 21 | } 22 | } 23 | 24 | export default verifyRelativeDirection; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-geography/src/verifyRelativeDirection.test.js: -------------------------------------------------------------------------------- 1 | import verifyRelativeDirection from './verifyRelativeDirection'; 2 | import { 3 | BACKWARD, 4 | FORWARD, 5 | LEFT, 6 | RELATIVE_DIRECTIONS, 7 | RIGHT, 8 | } from './relativeDirections'; 9 | 10 | test("doesn't throw if direction is valid", () => { 11 | const validDirections = RELATIVE_DIRECTIONS; 12 | validDirections.forEach(validDirection => 13 | verifyRelativeDirection(validDirection), 14 | ); 15 | }); 16 | 17 | test('throws an error if direction is not valid', () => { 18 | const invalidDirections = ['', 'foo', 'forward\n', 'Forward', 'backwards']; 19 | invalidDirections.forEach(invalidDirection => { 20 | expect(() => { 21 | verifyRelativeDirection(invalidDirection); 22 | }).toThrow( 23 | `Unknown direction: '${invalidDirection}'. Should be one of: '${FORWARD}', '${RIGHT}', '${BACKWARD}' or '${LEFT}'.`, 24 | ); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-grade-letter/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/helper-get-grade-letter 2 | 3 | > WarriorJS helper to get the letter for a grade. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install @warriorjs/helper-get-grade-letter 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import getGradeLetter from '@warriorjs/helper-get-grade-letter'; 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-grade-letter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/helper-get-grade-letter", 3 | "version": "0.11.1", 4 | "description": "WarriorJS helper to get the letter for a grade", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-helper-get-grade-letter", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-grade-letter/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the letter for the given grade. 3 | * 4 | * @param {number} grade The grade. 5 | * 6 | * @returns {string} The grade letter. 7 | */ 8 | function getGradeLetter(grade) { 9 | if (grade >= 1.0) { 10 | return 'S'; 11 | } 12 | 13 | if (grade >= 0.9) { 14 | return 'A'; 15 | } 16 | 17 | if (grade >= 0.8) { 18 | return 'B'; 19 | } 20 | 21 | if (grade >= 0.7) { 22 | return 'C'; 23 | } 24 | 25 | if (grade >= 0.6) { 26 | return 'D'; 27 | } 28 | 29 | return 'F'; 30 | } 31 | 32 | export default getGradeLetter; 33 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-grade-letter/src/index.test.js: -------------------------------------------------------------------------------- 1 | import getGradeLetter from '.'; 2 | 3 | test('returns letter based on grade', () => { 4 | expect(getGradeLetter(1.0)).toBe('S'); 5 | expect(getGradeLetter(0.99)).toBe('A'); 6 | expect(getGradeLetter(0.9)).toBe('A'); 7 | expect(getGradeLetter(0.89)).toBe('B'); 8 | expect(getGradeLetter(0.8)).toBe('B'); 9 | expect(getGradeLetter(0.79)).toBe('C'); 10 | expect(getGradeLetter(0.7)).toBe('C'); 11 | expect(getGradeLetter(0.69)).toBe('D'); 12 | expect(getGradeLetter(0.6)).toBe('D'); 13 | expect(getGradeLetter(0.59)).toBe('F'); 14 | expect(getGradeLetter(0)).toBe('F'); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-config/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/helper-get-level-config 2 | 3 | > WarriorJS helper to get the config of a level. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install @warriorjs/helper-get-level-config 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import getLevelConfig from '@warriorjs/helper-get-level-config'; 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/helper-get-level-config", 3 | "version": "0.12.3", 4 | "description": "WarriorJS helper to get the config of a level", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-helper-get-level-config", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | }, 19 | "dependencies": { 20 | "lodash.clonedeep": "^4.5.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-config/src/index.js: -------------------------------------------------------------------------------- 1 | import cloneDeep from 'lodash.clonedeep'; 2 | 3 | /** 4 | * Returns the config for the level with the given number. 5 | * 6 | * @param {Object} tower The tower. 7 | * @param {number} levelNumber The number of the level. 8 | * @param {string} warriorName The name of the warrior. 9 | * @param {boolean} epic Whether the level is to be used in epic mode or not. 10 | * 11 | * @returns {Object} The level config. 12 | */ 13 | function getLevelConfig(tower, levelNumber, warriorName, epic) { 14 | const level = tower.levels[levelNumber - 1]; 15 | if (!level) { 16 | return null; 17 | } 18 | 19 | const levelConfig = cloneDeep(level); 20 | 21 | const levels = epic ? tower.levels : tower.levels.slice(0, levelNumber); 22 | const warriorAbilities = Object.assign( 23 | {}, 24 | ...levels.map( 25 | ({ 26 | floor: { 27 | warrior: { abilities }, 28 | }, 29 | }) => abilities || {}, 30 | ), 31 | ); 32 | 33 | levelConfig.number = levelNumber; 34 | levelConfig.floor.warrior.name = warriorName; 35 | levelConfig.floor.warrior.abilities = warriorAbilities; 36 | return levelConfig; 37 | } 38 | 39 | export default getLevelConfig; 40 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-config/src/index.test.js: -------------------------------------------------------------------------------- 1 | import getLevelConfig from '.'; 2 | 3 | const tower = { 4 | id: 'foo', 5 | name: 'Foo', 6 | levels: [ 7 | { floor: { warrior: { abilities: { a: 1 }, bar: 'baz' }, foo: 42 } }, 8 | { floor: { warrior: { abilities: { b: 2, c: 3 } } } }, 9 | { floor: { warrior: {} } }, 10 | { floor: { warrior: { abilities: { a: 4 } } } }, 11 | ], 12 | }; 13 | 14 | test('returns level config', () => { 15 | expect(getLevelConfig(tower, 1, 'Joe', false)).toEqual({ 16 | number: 1, 17 | floor: { 18 | foo: 42, 19 | warrior: { 20 | bar: 'baz', 21 | name: 'Joe', 22 | abilities: { a: 1 }, 23 | }, 24 | }, 25 | }); 26 | }); 27 | 28 | test('gets abilities from all levels if epic', () => { 29 | expect(getLevelConfig(tower, 1, 'Joe', true)).toEqual({ 30 | number: 1, 31 | floor: { 32 | foo: 42, 33 | warrior: { 34 | bar: 'baz', 35 | name: 'Joe', 36 | abilities: { a: 4, b: 2, c: 3 }, 37 | }, 38 | }, 39 | }); 40 | }); 41 | 42 | test('returns null for non-existent level', () => { 43 | expect(getLevelConfig(tower, 5, 'Joe', false)).toBeNull(); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/helper-get-level-score 2 | 3 | > WarriorJS helper to get the level score from the result of running player code 4 | > through that level. 5 | 6 | ## Install 7 | 8 | ```sh 9 | npm install @warriorjs/helper-get-level-score 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```js 15 | import getLevelScore from '@warriorjs/helper-get-level-score'; 16 | ``` 17 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/helper-get-level-score", 3 | "version": "0.14.0", 4 | "description": "WarriorJS helper to get the score for a play", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-helper-get-level-score", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getClearBonus.js: -------------------------------------------------------------------------------- 1 | import getLastEvent from './getLastEvent'; 2 | import isFloorClear from './isFloorClear'; 3 | 4 | /** 5 | * Returns the bonus for clearing the level. 6 | * 7 | * @param {Object[][]} events The events that happened during the play. 8 | * @param {number} warriorScore The score of the warrior. 9 | * @param {number} timeBonus The time bonus. 10 | * 11 | * @returns {number} The clear bonus. 12 | */ 13 | function getClearBonus(events, warriorScore, timeBonus) { 14 | const lastEvent = getLastEvent(events); 15 | if (!isFloorClear(lastEvent.floorMap)) { 16 | return 0; 17 | } 18 | 19 | return Math.round((warriorScore + timeBonus) * 0.2); 20 | } 21 | 22 | export default getClearBonus; 23 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getClearBonus.test.js: -------------------------------------------------------------------------------- 1 | import getClearBonus from './getClearBonus'; 2 | import getLastEvent from './getLastEvent'; 3 | import isFloorClear from './isFloorClear'; 4 | 5 | jest.mock('./getLastEvent'); 6 | jest.mock('./isFloorClear'); 7 | 8 | test('returns the 20% of the sum of the warrior score and the time bonus with clear level', () => { 9 | getLastEvent.mockReturnValue({ floorMap: 'map' }); 10 | isFloorClear.mockReturnValue(true); 11 | expect(getClearBonus('events', 3, 2)).toBe(1); 12 | expect(getLastEvent).toHaveBeenCalledWith('events'); 13 | expect(isFloorClear).toHaveBeenCalledWith('map'); 14 | }); 15 | 16 | test('returns zero if the level is not clear', () => { 17 | getLastEvent.mockReturnValue({ floorMap: 'map' }); 18 | isFloorClear.mockReturnValue(false); 19 | expect(getClearBonus('events', 3, 2)).toBe(0); 20 | expect(getLastEvent).toHaveBeenCalledWith('events'); 21 | expect(isFloorClear).toHaveBeenCalledWith('map'); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getLastEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the last event of the play. 3 | * 4 | * @param {Object[][]} events The events that happened during the play. 5 | * 6 | * @returns {Object} The last event. 7 | */ 8 | function getLastEvent(events) { 9 | const lastTurnEvents = events[events.length - 1]; 10 | return lastTurnEvents[lastTurnEvents.length - 1]; 11 | } 12 | 13 | export default getLastEvent; 14 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getLastEvent.test.js: -------------------------------------------------------------------------------- 1 | import getLastEvent from './getLastEvent'; 2 | 3 | test('returns the last event of the play', () => { 4 | const events = ['turn1', 'turn2', ['event1', 'event2', 'event3']]; 5 | expect(getLastEvent(events)).toBe('event3'); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getRemainingTimeBonus.js: -------------------------------------------------------------------------------- 1 | import getTurnCount from './getTurnCount'; 2 | 3 | /** 4 | * Returns the remaining time bonus. 5 | * 6 | * @param {Object[][]} events The events that happened during the play. 7 | * @param {number} timeBonus The initial time bonus. 8 | * 9 | * @returns {number} The time bonus. 10 | */ 11 | function getRemainingTimeBonus(events, timeBonus) { 12 | const turnCount = getTurnCount(events); 13 | const remainingTimeBonus = timeBonus - turnCount; 14 | return Math.max(remainingTimeBonus, 0); 15 | } 16 | 17 | export default getRemainingTimeBonus; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getRemainingTimeBonus.test.js: -------------------------------------------------------------------------------- 1 | import getRemainingTimeBonus from './getRemainingTimeBonus'; 2 | import getTurnCount from './getTurnCount'; 3 | 4 | jest.mock('./getTurnCount'); 5 | 6 | test('subtracts the number of turns played from the initial time bonus', () => { 7 | getTurnCount.mockReturnValue(3); 8 | expect(getRemainingTimeBonus('events', 10)).toBe(7); 9 | expect(getTurnCount).toHaveBeenCalledWith('events'); 10 | }); 11 | 12 | test("doesn't go below zero", () => { 13 | getTurnCount.mockReturnValue(11); 14 | expect(getRemainingTimeBonus('events', 10)).toBe(0); 15 | expect(getTurnCount).toHaveBeenCalledWith('events'); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getTurnCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the number of turns played. 3 | * 4 | * @param {Object[][]} events The events that happened during the play. 5 | * 6 | * @returns {number} The turn count. 7 | */ 8 | function getTurnCount(events) { 9 | return events.length; 10 | } 11 | 12 | export default getTurnCount; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getTurnCount.test.js: -------------------------------------------------------------------------------- 1 | import getTurnCount from './getTurnCount'; 2 | 3 | test('returns the number of turns played', () => { 4 | const events = ['turn1', 'turn2', 'turn3']; 5 | expect(getTurnCount(events)).toBe(3); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getWarriorScore.js: -------------------------------------------------------------------------------- 1 | import getLastEvent from './getLastEvent'; 2 | 3 | /** 4 | * Returns the score of the warrior. 5 | * 6 | * @param {Object[][]} events The events that happened during the play. 7 | * 8 | * @returns {number} The score of the warrior. 9 | */ 10 | function getWarriorScore(events) { 11 | const lastEvent = getLastEvent(events); 12 | return lastEvent.warriorStatus.score; 13 | } 14 | 15 | export default getWarriorScore; 16 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/getWarriorScore.test.js: -------------------------------------------------------------------------------- 1 | import getLastEvent from './getLastEvent'; 2 | import getWarriorScore from './getWarriorScore'; 3 | 4 | jest.mock('./getLastEvent'); 5 | 6 | test('returns the score of the warrior at the end of the play', () => { 7 | getLastEvent.mockReturnValue({ warriorStatus: { score: 42 } }); 8 | expect(getWarriorScore('events')).toBe(42); 9 | expect(getLastEvent).toHaveBeenCalledWith('events'); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/index.js: -------------------------------------------------------------------------------- 1 | import getClearBonus from './getClearBonus'; 2 | import getRemainingTimeBonus from './getRemainingTimeBonus'; 3 | import getWarriorScore from './getWarriorScore'; 4 | 5 | /** 6 | * Returns the score for the given level. 7 | * 8 | * @param {Object} result The level result. 9 | * @param {boolean} result.passed Whether the level was passed or not. 10 | * @param {Object[][]} result.events The events of the level. 11 | * @param {Object} levelConfig The level config. 12 | * @param {number} levelConfig.timeBonus The bonus for passing the level in time. 13 | * 14 | * @returns {Object} The score of the level, broken down into its components. 15 | */ 16 | function getLevelScore({ passed, events }, { timeBonus }) { 17 | if (!passed) { 18 | return null; 19 | } 20 | 21 | const warriorScore = getWarriorScore(events); 22 | const remainingTimeBonus = getRemainingTimeBonus(events, timeBonus); 23 | const clearBonus = getClearBonus(events, warriorScore, remainingTimeBonus); 24 | return { 25 | clearBonus, 26 | timeBonus: remainingTimeBonus, 27 | warrior: warriorScore, 28 | }; 29 | } 30 | 31 | export default getLevelScore; 32 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/index.test.js: -------------------------------------------------------------------------------- 1 | import getClearBonus from './getClearBonus'; 2 | import getLevelScore from '.'; 3 | import getRemainingTimeBonus from './getRemainingTimeBonus'; 4 | import getWarriorScore from './getWarriorScore'; 5 | 6 | jest.mock('./getClearBonus'); 7 | jest.mock('./getRemainingTimeBonus'); 8 | jest.mock('./getWarriorScore'); 9 | 10 | const levelConfig = { timeBonus: 16 }; 11 | 12 | test('returns null when level failed', () => { 13 | expect(getLevelScore({ passed: false }, levelConfig)).toBeNull(); 14 | }); 15 | 16 | describe('level passed', () => { 17 | let levelResult; 18 | 19 | beforeEach(() => { 20 | levelResult = { 21 | passed: true, 22 | events: 'events', 23 | }; 24 | }); 25 | 26 | test('has warrior score part', () => { 27 | getWarriorScore.mockReturnValue(8); 28 | expect(getLevelScore(levelResult, levelConfig).warrior).toBe(8); 29 | }); 30 | 31 | test('has time bonus part', () => { 32 | getRemainingTimeBonus.mockReturnValue(10); 33 | expect(getLevelScore(levelResult, levelConfig).timeBonus).toBe(10); 34 | expect(getRemainingTimeBonus).toHaveBeenCalledWith('events', 16); 35 | }); 36 | 37 | test('has clear bonus part', () => { 38 | getWarriorScore.mockReturnValue(8); 39 | getRemainingTimeBonus.mockReturnValue(12); 40 | getClearBonus.mockReturnValue(4); 41 | expect(getLevelScore(levelResult, levelConfig).clearBonus).toBe(4); 42 | expect(getClearBonus).toHaveBeenCalledWith('events', 8, 12); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/isFloorClear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the floor is clear. 3 | * 4 | * The floor is clear when there are no units other than the warrior. 5 | * 6 | * @returns {boolean} Whether the floor is clear or not. 7 | */ 8 | function isFloorClear(floorMap) { 9 | const spaces = floorMap.reduce((acc, val) => acc.concat(val), []); 10 | const unitCount = spaces.filter(space => !!space.unit).length; 11 | return unitCount <= 1; 12 | } 13 | 14 | export default isFloorClear; 15 | -------------------------------------------------------------------------------- /packages/warriorjs-helper-get-level-score/src/isFloorClear.test.js: -------------------------------------------------------------------------------- 1 | import isFloorClear from './isFloorClear'; 2 | 3 | test('considers clear when there are no units other than the warrior', () => { 4 | const floorMap = [ 5 | [{ character: 'a' }, { character: 'b' }], 6 | [{ character: '@', unit: 'warrior' }, { character: 'c' }], 7 | ]; 8 | expect(isFloorClear(floorMap)).toBe(true); 9 | }); 10 | 11 | test("doesn't consider clear when there are other units apart from the warrior", () => { 12 | const floorMap = [ 13 | [{ character: 'a' }, { character: 'b' }], 14 | [{ character: '@', unit: 'warrior' }, { character: 'f', unit: 'foo' }], 15 | ]; 16 | expect(isFloorClear(floorMap)).toBe(false); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/warriorjs-tower-baby-steps/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/tower-baby-steps 2 | 3 | > For players new to WarriorJS. 4 | 5 | ## Install 6 | 7 | `@warriorjs/cli` already ships with `@warriorjs/tower-baby-steps` built-in. 8 | 9 | If you still want to install it: 10 | 11 | ```sh 12 | npm install @warriorjs/tower-baby-steps 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```sh 18 | warriorjs 19 | ``` 20 | 21 | For more in depth documentation see: https://warrior.js.org/docs/player/towers. 22 | -------------------------------------------------------------------------------- /packages/warriorjs-tower-baby-steps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/tower-baby-steps", 3 | "version": "0.13.0", 4 | "description": "Baby Steps", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-tower-baby-steps", 9 | "keywords": [ 10 | "warriorjs-tower" 11 | ], 12 | "main": "lib/index.js", 13 | "files": [ 14 | "lib" 15 | ], 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "scripts": { 20 | "build": "babel src --out-dir lib --ignore test.js" 21 | }, 22 | "dependencies": { 23 | "@warriorjs/abilities": "^0.13.0", 24 | "@warriorjs/geography": "^0.7.0", 25 | "@warriorjs/units": "^0.13.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/warriorjs-tower-tick-tick-boom/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/tower-tick-tick-boom 2 | 3 | > Try not to blow the tower apart. 4 | 5 | ## Install 6 | 7 | If you have installed `@warriorjs/cli` globally, then run: 8 | 9 | ```sh 10 | npm install --global @warriorjs/tower-tick-tick-boom 11 | ``` 12 | 13 | If instead you have installed the game locally, then run in the same location: 14 | 15 | ```sh 16 | npm install @warriorjs/tower-tick-tick-boom 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```sh 22 | warriorjs 23 | ``` 24 | 25 | For more in depth documentation see: https://warrior.js.org/docs/player/towers. 26 | -------------------------------------------------------------------------------- /packages/warriorjs-tower-tick-tick-boom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/tower-tick-tick-boom", 3 | "version": "0.13.0", 4 | "description": "Tick, Tick... Boom!", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-tower-tick-tick-boom", 9 | "keywords": [ 10 | "warriorjs-tower" 11 | ], 12 | "main": "lib/index.js", 13 | "files": [ 14 | "lib" 15 | ], 16 | "publishConfig": { 17 | "access": "public" 18 | }, 19 | "scripts": { 20 | "build": "babel src --out-dir lib --ignore test.js" 21 | }, 22 | "dependencies": { 23 | "@warriorjs/abilities": "^0.13.0", 24 | "@warriorjs/effects": "^0.12.2", 25 | "@warriorjs/geography": "^0.7.0", 26 | "@warriorjs/units": "^0.13.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/warriorjs-units/README.md: -------------------------------------------------------------------------------- 1 | # @warriorjs/units 2 | 3 | > WarriorJS official units. 4 | 5 | ### Archer 6 | 7 | - **Character:** a 8 | - **Max Health:** 7 HP 9 | - **Abilities:** 10 | - look (3 range) 11 | - shoot (3 range, 3 power) 12 | - **AI:** Attack first enemy in line of sight in any direction. 13 | 14 | ### Captive 15 | 16 | - **Character:** C 17 | - **Max Health:** 1 HP 18 | - **Abilities:** None. 19 | - **AI:** Wait to be rescued. 20 | 21 | ### Sludge 22 | 23 | - **Character:** s 24 | - **Max Health:** 12 HP 25 | - **Abilities:** 26 | - feel 27 | - attack (3 power) 28 | - **AI:** Attack first adjacent enemy in any direction. 29 | 30 | ### Thick Sludge 31 | 32 | - **Character:** S 33 | - **Max Health:** 24 HP 34 | - **Abilities:** 35 | - feel 36 | - attack (3 power) 37 | - **AI:** Attack first adjacent enemy in any direction. 38 | 39 | ### Warrior 40 | 41 | - **Character:** @ 42 | - **Max Health:** 20 HP 43 | - **Abilities:** Determined by the level. 44 | - **AI:** Provided by the player. 45 | 46 | ### Wizard 47 | 48 | - **Character:** w 49 | - **Max Health:** 3 HP 50 | - **Abilities:** 51 | - look (3 range) 52 | - shoot (3 range, 11 power) 53 | - **AI:** Attack first enemy in line of sight in any direction. 54 | -------------------------------------------------------------------------------- /packages/warriorjs-units/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@warriorjs/units", 3 | "version": "0.13.0", 4 | "description": "WarriorJS base units", 5 | "author": "Matias Olivera ", 6 | "license": "MIT", 7 | "homepage": "https://warrior.js.org", 8 | "repository": "https://github.com/olistic/warriorjs/tree/master/packages/warriorjs-units", 9 | "main": "lib/index.js", 10 | "files": [ 11 | "lib" 12 | ], 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "babel src --out-dir lib --ignore test.js" 18 | }, 19 | "dependencies": { 20 | "@warriorjs/abilities": "^0.13.0", 21 | "@warriorjs/geography": "^0.7.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Archer.js: -------------------------------------------------------------------------------- 1 | import { RELATIVE_DIRECTIONS } from '@warriorjs/geography'; 2 | import { look, shoot } from '@warriorjs/abilities'; 3 | 4 | const Archer = { 5 | name: 'Archer', 6 | character: 'a', 7 | color: '#ebcb8b', 8 | maxHealth: 7, 9 | abilities: { 10 | look: look({ range: 3 }), 11 | shoot: shoot({ range: 3, power: 3 }), 12 | }, 13 | playTurn(archer) { 14 | const threatDirection = RELATIVE_DIRECTIONS.find(direction => { 15 | const spaceWithUnit = archer 16 | .look(direction) 17 | .find(space => space.isUnit()); 18 | return ( 19 | spaceWithUnit && 20 | spaceWithUnit.getUnit().isEnemy() && 21 | !spaceWithUnit.getUnit().isBound() 22 | ); 23 | }); 24 | if (threatDirection) { 25 | archer.shoot(threatDirection); 26 | } 27 | }, 28 | }; 29 | 30 | export default Archer; 31 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Captive.js: -------------------------------------------------------------------------------- 1 | const Captive = { 2 | name: 'Captive', 3 | character: 'C', 4 | color: '#81a1c1', 5 | maxHealth: 1, 6 | reward: 20, 7 | enemy: false, 8 | bound: true, 9 | playTurn() {}, 10 | }; 11 | 12 | export default Captive; 13 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Captive.test.js: -------------------------------------------------------------------------------- 1 | import Captive from './Captive'; 2 | 3 | describe('Captive', () => { 4 | test("appears as 'C' on map", () => { 5 | expect(Captive.character).toBe('C'); 6 | }); 7 | 8 | test('has #81a1c1 color', () => { 9 | expect(Captive.color).toBe('#81a1c1'); 10 | }); 11 | 12 | test('has 1 max health', () => { 13 | expect(Captive.maxHealth).toBe(1); 14 | }); 15 | 16 | test('has a reward of 20 points', () => { 17 | expect(Captive.reward).toBe(20); 18 | }); 19 | 20 | test('is not an enemy', () => { 21 | expect(Captive.enemy).toBe(false); 22 | }); 23 | 24 | test('is bound', () => { 25 | expect(Captive.bound).toBe(true); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Sludge.js: -------------------------------------------------------------------------------- 1 | import { RELATIVE_DIRECTIONS } from '@warriorjs/geography'; 2 | import { attack, feel } from '@warriorjs/abilities'; 3 | 4 | const Sludge = { 5 | name: 'Sludge', 6 | character: 's', 7 | color: '#d08770', 8 | maxHealth: 12, 9 | abilities: { 10 | attack: attack({ power: 3 }), 11 | feel: feel(), 12 | }, 13 | playTurn(sludge) { 14 | const threatDirection = RELATIVE_DIRECTIONS.find(direction => { 15 | const unit = sludge.feel(direction).getUnit(); 16 | return unit && unit.isEnemy() && !unit.isBound(); 17 | }); 18 | if (threatDirection) { 19 | sludge.attack(threatDirection); 20 | } 21 | }, 22 | }; 23 | 24 | export default Sludge; 25 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/ThickSludge.js: -------------------------------------------------------------------------------- 1 | import Sludge from './Sludge'; 2 | 3 | const ThickSludge = { 4 | ...Sludge, 5 | name: 'Thick Sludge', 6 | character: 'S', 7 | color: '#bf616a', 8 | maxHealth: 24, 9 | }; 10 | 11 | export default ThickSludge; 12 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Warrior.js: -------------------------------------------------------------------------------- 1 | const Warrior = { 2 | character: '@', 3 | color: '#8fbcbb', 4 | maxHealth: 20, 5 | }; 6 | 7 | export default Warrior; 8 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Warrior.test.js: -------------------------------------------------------------------------------- 1 | import Warrior from './Warrior'; 2 | 3 | describe('Warrior', () => { 4 | test("appears as '@' on map", () => { 5 | expect(Warrior.character).toBe('@'); 6 | }); 7 | 8 | test('has #8fbcbb color', () => { 9 | expect(Warrior.color).toBe('#8fbcbb'); 10 | }); 11 | 12 | test('has 20 max health', () => { 13 | expect(Warrior.maxHealth).toBe(20); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/Wizard.js: -------------------------------------------------------------------------------- 1 | import { look, shoot } from '@warriorjs/abilities'; 2 | 3 | import Archer from './Archer'; 4 | 5 | const Wizard = { 6 | ...Archer, 7 | name: 'Wizard', 8 | character: 'w', 9 | color: '#b48ead', 10 | maxHealth: 3, 11 | abilities: { 12 | look: look({ range: 3 }), 13 | shoot: shoot({ range: 3, power: 11 }), 14 | }, 15 | }; 16 | 17 | export default Wizard; 18 | -------------------------------------------------------------------------------- /packages/warriorjs-units/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as Archer } from './Archer'; 2 | export { default as Captive } from './Captive'; 3 | export { default as Sludge } from './Sludge'; 4 | export { default as ThickSludge } from './ThickSludge'; 5 | export { default as Warrior } from './Warrior'; 6 | export { default as Wizard } from './Wizard'; 7 | -------------------------------------------------------------------------------- /website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-dynamic-require": "off", 4 | "import/no-extraneous-dependencies": "off", 5 | "react/jsx-filename-extension": "off", 6 | "react/no-array-index-key": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/core/Footer.js: -------------------------------------------------------------------------------- 1 | const PropTypes = require('prop-types'); 2 | const React = require('react'); 3 | 4 | const GitHubButton = require('./GitHubButton'); 5 | const TwitterButton = require('./TwitterButton'); 6 | const getDocUrl = require('../utils/getDocUrl'); 7 | 8 | const Footer = ({ config, language }) => ( 9 | 56 | ); 57 | 58 | Footer.propTypes = { 59 | config: PropTypes.shape({ 60 | baseUrl: PropTypes.string.isRequired, 61 | footerIcon: PropTypes.string.isRequired, 62 | gitHubUrl: PropTypes.string.isRequired, 63 | title: PropTypes.string.isRequired, 64 | }).isRequired, 65 | language: PropTypes.string.isRequired, 66 | }; 67 | 68 | module.exports = Footer; 69 | -------------------------------------------------------------------------------- /website/core/GitHubButton.js: -------------------------------------------------------------------------------- 1 | const PropTypes = require('prop-types'); 2 | const React = require('react'); 3 | 4 | const GitHubButton = ({ username, repo }) => ( 5 | 12 | Star 13 | 14 | ); 15 | 16 | GitHubButton.propTypes = { 17 | username: PropTypes.string.isRequired, 18 | repo: PropTypes.string.isRequired, 19 | }; 20 | 21 | module.exports = GitHubButton; 22 | -------------------------------------------------------------------------------- /website/core/TwitterButton.js: -------------------------------------------------------------------------------- 1 | const PropTypes = require('prop-types'); 2 | const React = require('react'); 3 | 4 | const TwitterButton = ({ username }) => ( 5 | 10 | Follow WarriorJS on Twitter 14 | 15 | ); 16 | 17 | TwitterButton.propTypes = { 18 | username: PropTypes.string.isRequired, 19 | }; 20 | 21 | module.exports = TwitterButton; 22 | -------------------------------------------------------------------------------- /website/crowdin.yaml: -------------------------------------------------------------------------------- 1 | project_identifier_env: CROWDIN_WARRIORJS_PROJECT_ID 2 | api_key_env: CROWDIN_WARRIORJS_API_KEY 3 | base_path: "../" 4 | preserve_hierarchy: true 5 | 6 | files: 7 | - 8 | source: '/docs/**/*.md' 9 | translation: '/website/translated_docs/%locale%/**/%original_file_name%' 10 | languages_mapping: &anchor 11 | locale: 12 | 'af': 'af' 13 | 'ar': 'ar' 14 | 'bs-BA': 'bs-BA' 15 | 'ca': 'ca' 16 | 'cs': 'cs' 17 | 'da': 'da' 18 | 'de': 'de' 19 | 'el': 'el' 20 | 'es-ES': 'es-ES' 21 | 'fa': 'fa-IR' 22 | 'fi': 'fi' 23 | 'fr': 'fr' 24 | 'he': 'he' 25 | 'hu': 'hu' 26 | 'id': 'id-ID' 27 | 'it': 'it' 28 | 'ja': 'ja' 29 | 'ko': 'ko' 30 | 'mr': 'mr-IN' 31 | 'nl': 'nl' 32 | 'no': 'no-NO' 33 | 'pl': 'pl' 34 | 'pt-BR': 'pt-BR' 35 | 'pt-PT': 'pt-PT' 36 | 'ro': 'ro' 37 | 'ru': 'ru' 38 | 'sk': 'sk-SK' 39 | 'sr': 'sr' 40 | 'sv-SE': 'sv-SE' 41 | 'tr': 'tr' 42 | 'uk': 'uk' 43 | 'vi': 'vi' 44 | 'zh-CN': 'zh-CN' 45 | 'zh-TW': 'zh-TW' 46 | - 47 | source: '/website/i18n/en.json' 48 | translation: '/website/i18n/%locale%.json' 49 | languages_mapping: *anchor 50 | -------------------------------------------------------------------------------- /website/data/sponsors.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "warriorjs-website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "docusaurus-start", 7 | "build": "docusaurus-build", 8 | "publish-gh-pages": "docusaurus-publish", 9 | "write-translations": "docusaurus-write-translations", 10 | "version": "docusaurus-version", 11 | "rename-version": "docusaurus-rename-version", 12 | "crowdin-upload": "crowdin upload sources --auto-update -b master", 13 | "crowdin-download": "crowdin download -b master" 14 | }, 15 | "devDependencies": { 16 | "docusaurus": "^1.2.0" 17 | }, 18 | "dependencies": { 19 | "prop-types": "^15.6.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "play": { 3 | "Game": [ 4 | "player/overview", 5 | "player/object", 6 | "player/gameplay", 7 | "player/perspective", 8 | "player/scoring", 9 | "player/epic-mode", 10 | "player/towers" 11 | ], 12 | "Concepts": [ 13 | "player/units", 14 | "player/warrior", 15 | "player/abilities", 16 | "player/spaces" 17 | ], 18 | "Player API": ["player/space-api", "player/unit-api", "player/turn-api"], 19 | "Tips & Tricks": [ 20 | "player/general-tips", 21 | "player/js-tips", 22 | "player/ai-tips", 23 | "player/cli-tips" 24 | ], 25 | "CLI": ["player/install", "player/options"] 26 | }, 27 | "make": { 28 | "Guide": [ 29 | "maker/introduction", 30 | "maker/creating-tower", 31 | "maker/adding-levels", 32 | "maker/defining-abilities", 33 | "maker/defining-units", 34 | "maker/refactoring", 35 | "maker/testing", 36 | "maker/publishing" 37 | ], 38 | "Maker API": ["maker/space-api", "maker/unit-api"] 39 | }, 40 | "community": { 41 | "Community": [ 42 | "community/socialize", 43 | "community/ecosystem", 44 | "community/roadmap" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package'); 2 | const sponsors = require('./data/sponsors'); 3 | 4 | const gitHubUrl = pkg.repository; 5 | const twitterUsername = 'warrior_js'; 6 | 7 | const siteConfig = { 8 | gitHubUrl, 9 | twitterUsername, 10 | sponsors, 11 | title: 'WarriorJS Docs', 12 | tagline: 'An exciting game of programming and Artificial Intelligence', 13 | url: pkg.homepage, 14 | baseUrl: '/', 15 | projectName: 'warriorjs', 16 | organizationName: 'olistic', 17 | cname: 'warrior.js.org', 18 | noIndex: false, 19 | cleanUrl: true, 20 | editUrl: `${gitHubUrl}/edit/master/docs/`, 21 | translationRecruitingLink: 'https://crowdin.com/project/warriorjs', 22 | headerLinks: [ 23 | { doc: 'player/overview', label: 'Player' }, 24 | { doc: 'maker/introduction', label: 'Maker' }, 25 | { doc: 'community/socialize', label: 'Community' }, 26 | { languages: true }, 27 | { search: true }, 28 | { href: gitHubUrl, label: 'GitHub' }, 29 | ], 30 | headerIcon: 'img/warriorjs-text.svg', 31 | footerIcon: 'img/warriorjs-sword.svg', 32 | favicon: 'img/favicon.png', 33 | twitterImage: 'img/warriorjs.png', 34 | ogImage: 'img/warriorjs.png', 35 | colors: { 36 | primaryColor: '#2e3440', 37 | secondaryColor: '#3b4252', 38 | accentColor: '#8fbcbb', 39 | }, 40 | scripts: ['https://buttons.github.io/buttons.js'], 41 | disableHeaderTitle: true, 42 | disableTitleTagline: true, 43 | onPageNav: 'separate', 44 | gaTrackingId: 'UA-118632697-1', 45 | algolia: { 46 | apiKey: 'af0d3f56837aacc96ccd573d9208966c', 47 | indexName: 'warriorjs', 48 | }, 49 | }; 50 | 51 | module.exports = siteConfig; 52 | -------------------------------------------------------------------------------- /website/static/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # This config file will prevent tests from being run on the gh-pages branch. 2 | version: 2 3 | jobs: 4 | build: 5 | machine: true 6 | 7 | branches: 8 | ignore: gh-pages 9 | 10 | steps: 11 | - run: echo "Skipping tests on gh-pages branch" 12 | -------------------------------------------------------------------------------- /website/static/googlee0ff7b5bc8d30f78.html: -------------------------------------------------------------------------------- 1 | google-site-verification: googlee0ff7b5bc8d30f78.html -------------------------------------------------------------------------------- /website/static/img/code-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/website/static/img/code-preview.png -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/make-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/website/static/img/make-preview.png -------------------------------------------------------------------------------- /website/static/img/play-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/website/static/img/play-preview.png -------------------------------------------------------------------------------- /website/static/img/warriorjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olistic/warriorjs/ec531b4d4b6b7c3e51e5adbd03f575af56ebebc8/website/static/img/warriorjs.png -------------------------------------------------------------------------------- /website/utils/getDocUrl.js: -------------------------------------------------------------------------------- 1 | const siteConfig = require(`${process.cwd()}/siteConfig.js`); 2 | 3 | const docsUrl = `${siteConfig.baseUrl}docs`; 4 | 5 | function getDocUrl(doc, language) { 6 | if (language) { 7 | return `${docsUrl}/${language}/${doc}`; 8 | } 9 | 10 | return `${docsUrl}/${doc}`; 11 | } 12 | 13 | module.exports = getDocUrl; 14 | -------------------------------------------------------------------------------- /website/utils/getImgUrl.js: -------------------------------------------------------------------------------- 1 | const siteConfig = require(`${process.cwd()}/siteConfig.js`); 2 | 3 | const imgUrl = `${siteConfig.baseUrl}img`; 4 | 5 | function getImgUrl(img) { 6 | return `${imgUrl}/${img}`; 7 | } 8 | 9 | module.exports = getImgUrl; 10 | --------------------------------------------------------------------------------