├── .gitignore ├── README.md ├── back ├── .dockerignore ├── Dockerfile ├── README.md ├── nodemon.json ├── package.json ├── src │ ├── Game.ts │ ├── collision-worker.js │ ├── config.ts │ ├── entities │ │ ├── Food.ts │ │ ├── GameEntity.ts │ │ └── Snake.ts │ ├── server.ts │ ├── simplify-worker.js │ └── webworker-threads.d.ts ├── tsconfig.json ├── tslint.json └── yarn.lock ├── docker-compose.yml ├── front ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── README.md ├── middleware │ └── websocket.js ├── package.json ├── public │ ├── app.css │ ├── favicon.svg │ ├── index.html │ └── manifest.json ├── src │ ├── App.js │ ├── Test.js │ ├── actions │ │ ├── canvas.js │ │ ├── mousemove.js │ │ ├── player.js │ │ └── websocket.js │ ├── components │ │ ├── Background.js │ │ ├── Canvas.js │ │ ├── Food.js │ │ ├── GameMap.js │ │ ├── Home.js │ │ ├── LinearBackground.js │ │ ├── MiniMap.js │ │ └── Snake.js │ ├── index.js │ ├── logo.svg │ ├── reducers │ │ ├── canvas.js │ │ └── index.js │ ├── registerServiceWorker.js │ ├── sagas │ │ ├── index.js │ │ ├── mousemove.js │ │ ├── playermove.js │ │ └── websockets.js │ └── store.js └── yarn.lock ├── orchestrator ├── Dockerfile ├── README.md ├── nodemon.json ├── package.json ├── src │ ├── env.ts │ ├── middlewares │ │ ├── auth.ts │ │ └── cors.ts │ ├── models │ │ └── server.ts │ ├── routes │ │ ├── admin.ts │ │ ├── confirm.ts │ │ ├── connect.ts │ │ └── disconnect.ts │ ├── server.ts │ └── spawn.ts ├── tsconfig.json ├── tslint.json ├── views │ └── admin.ejs └── yarn.lock ├── screenshots ├── game.png └── home.png └── scripts ├── deploy.sh ├── kill-servers.sh └── stop.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # next.js build output 58 | .next 59 | 60 | dist 61 | bin 62 | .env 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Effective Barnacle 2 | 3 | > A super scalable multiplayer game made with a ton of awesome technologies. 4 | 5 | Curious about how we built it and how does it work? Read our [blog post](medium.com)! 6 | 7 | Client: 8 | * React, Redux, [Sagas](https://github.com/redux-saga/redux-saga) 9 | * HTML5 Canvas 10 | * WebSocket 11 | 12 | Server: 13 | * Node 14 | * Docker 15 | * [Traefik](https://traefik.io) 16 | 17 | ## Preview 18 | 19 | | ![Screenshot](screenshots/home.png) | ![Screenshot](screenshots/game.png) 20 | |:---:|:---:| 21 | | Home page | The game 22 | 23 | ## Run it 24 | 25 | You just need to run: 26 | 27 | `./scripts/deploy.sh` 28 | 29 | This script will: 30 | 31 | * Create a `traefik_default` docker network if it doesn't exist yet 32 | * Run `docker-compose build` to build docker images 33 | * Run `docker-compose up -d` to start services 34 | 35 | Services are listed below: 36 | * 5 instances of the [Effective Barnacle server](https://github.com/feedthejim/effective-barnacle/tree/master/back) on random unused ports (`localhost:XXXX`) 37 | * [Effective Barnacle client](https://github.com/feedthejim/effective-barnacle/tree/master/front) (`localhost:3000`) 38 | * [Effective Barnacle orchestrator](https://github.com/feedthejim/effective-barnacle/tree/master/orchestrator) (`localhost:9000`) 39 | * Traefik reverse proxy (`localhost:80`) 40 | * MongoDB (`localhost:27017`) 41 | 42 | You can also customize ports and other variables with the following environment variables: 43 | 44 | * `EB_TRAEFIK_PORT` (`80`) 45 | * `EB_DOMAIN_NAME` (`localhost`) 46 | * `EB_ORCHESTRATOR_PORT` (`9000`) 47 | * `EB_ORCHESTRATOR_SECRET` (`supersecret`) 48 | * `EB_SERVER_DOCKER_IMAGE` (`effective-barnacle_backend`) 49 | * `EB_MAX_CLIENTS_PER_SERVER` (`5`) 50 | * `EB_DEFAULT_SERVERS_NB` (`5`) 51 | * `EB_MONGO_HOST` (`mongodb`) 52 | 53 | ## Shut it down 54 | 55 | Just run: 56 | 57 | `./scripts/stop.sh` 58 | 59 | > To make sure that Effective Barnacle is shutting down with this command, your server Docker image should be named **effective-barnacle_backend**. 60 | 61 | ## Contributing 62 | 63 | Pull requests are very welcomed! 64 | 65 | To get started in your contribution, you just need to either use the `./scripts/deploy.sh` script or install/run services in dev: 66 | 67 | ``` 68 | $ yarn install 69 | $ yarn dev 70 | ``` 71 | 72 | ## Authors 73 | 74 | * Jimmy Lai 75 | * Yannick Utard 76 | -------------------------------------------------------------------------------- /back/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /back/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN curl -o- -L https://yarnpkg.com/install.sh | bash 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN yarn install 10 | -------------------------------------------------------------------------------- /back/README.md: -------------------------------------------------------------------------------- 1 | # Effective Barnacle Server 2 | 3 | # Environment variables 4 | 5 | * `EB_SERVER_ID` (ID of the current server given by the orchestrator) 6 | * `EB_SERVER_PORT` (default `4242`) 7 | * `EB_ORCHESTRATOR_SECRET` (default `secret`) 8 | * `EB_ORCHESTRATOR_URL` (default `localhost:9000`) 9 | -------------------------------------------------------------------------------- /back/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node ./src/server.ts" 5 | } -------------------------------------------------------------------------------- /back/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "back", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "tsc && node dist/server.js", 8 | "dev": "nodemon" 9 | }, 10 | "dependencies": { 11 | "@types/randomcolor": "^0.4.3", 12 | "@types/shortid": "^0.0.29", 13 | "axios": "^0.18.0", 14 | "nodemon": "^1.17.5", 15 | "randomcolor": "^0.5.3", 16 | "schemapack": "^1.4.2", 17 | "shortid": "^2.2.8", 18 | "simplify-js": "^1.2.3", 19 | "socket.io": "^2.1.1", 20 | "ts-node": "^6.0.4", 21 | "typescript": "^2.8.3", 22 | "webworker-threads": "^0.7.15" 23 | }, 24 | "devDependencies": { 25 | "@types/socket.io": "^1.4.33", 26 | "nodemon": "^1.17.5", 27 | "ts-node": "^6.0.4", 28 | "tslint": "^5.10.0", 29 | "tslint-config-airbnb": "^5.9.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /back/src/Game.ts: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | import Snake from './entities/Snake'; 3 | import { Server, Socket } from 'socket.io'; 4 | import axios from 'axios'; 5 | import Food from './entities/Food'; 6 | import GameEntity from './entities/GameEntity'; 7 | import * as shortid from 'shortid'; 8 | 9 | const schemapack = require('schemapack'); 10 | const simplify = require('simplify-js'); 11 | // const collisionWorker = require('./collision-worker').default; 12 | 13 | const gameUpdate = schemapack.build({ 14 | snakes: [ 15 | { 16 | id: 'string', 17 | x: 'int16', 18 | y: 'int16', 19 | isBlinking: 'bool', 20 | isSpeedUp: 'bool', 21 | length: 'uint16', 22 | scale: 'float32', 23 | fillColor: 'string', 24 | username: 'string', 25 | angle: 'float32', 26 | points: [ 27 | { 28 | x: 'int16', 29 | y: 'int16', 30 | }, 31 | ], 32 | collisionRect: { 33 | minX: 'int16', 34 | minY: 'int16', 35 | maxX: 'int16', 36 | maxY: 'int16', 37 | }, 38 | score: 'uint16', 39 | width: 'float32', 40 | }, 41 | ], 42 | foods: [ 43 | { 44 | id: 'string', 45 | x: 'int16', 46 | y: 'int16', 47 | width: 'float32', 48 | height: 'float32', 49 | }, 50 | ], 51 | }); 52 | 53 | const { 54 | MAP_HEIGHT, 55 | MAP_RECT_HEIGHT, 56 | MAP_RECT_WIDTH, 57 | MAP_WIDTH, 58 | SOCKET_PORT, 59 | GAMELOOP_RATE, 60 | INITIAL_FOOD_COUNT, 61 | INITIAL_FOOD_VALUE, 62 | ORCHESTRATOR_URL, 63 | ORCHESTRATOR_SECRET, 64 | } = config; 65 | 66 | const collision = ( 67 | dom: { 68 | x: number; 69 | y: number; 70 | width?: number; 71 | height?: number; 72 | }, 73 | dom2: { 74 | x: number; 75 | y: number; 76 | width?: number; 77 | height?: number; 78 | }, 79 | isRect?: boolean, 80 | ): boolean => { 81 | const disX = dom.x - dom2.x; 82 | const disY = dom.y - dom2.y; 83 | const dw = dom.width + dom2.width; 84 | 85 | if (Math.abs(disX) > dw || Math.abs(disY) > dom.height + dom2.height) { 86 | return false; 87 | } 88 | 89 | return isRect ? true : Math.hypot(disX, disY) < dw / 2; 90 | }; 91 | 92 | const rectCollision = ( 93 | { collisionRect: rect1 }: Snake, 94 | { collisionRect: rect2 }: Snake, 95 | ) => 96 | !( 97 | rect2.minX > rect1.maxX || 98 | rect2.maxX < rect1.minX || 99 | rect2.maxY < rect1.minY || 100 | rect2.minY > rect1.maxY 101 | ); 102 | 103 | // Map class 104 | export class Game { 105 | public snakes: Snake[]; 106 | public count: number; 107 | public foods: Food[]; 108 | 109 | constructor( 110 | private wss: Server, 111 | public width: number = MAP_WIDTH, 112 | public height = MAP_HEIGHT, 113 | ) { 114 | this.snakes = []; 115 | this.count = 0; 116 | this.foods = []; 117 | for (let i = 0; i < INITIAL_FOOD_COUNT; i += 1) { 118 | this.foods.push( 119 | new Food({ 120 | id: shortid.generate(), 121 | x: ~~(Math.random() * (MAP_WIDTH - 100) + 100 / 2), 122 | y: ~~(Math.random() * (MAP_HEIGHT - 100) + 100 / 2), 123 | size: 10, 124 | value: INITIAL_FOOD_VALUE, 125 | }), 126 | ); 127 | } 128 | } 129 | 130 | private confirmConnection() { 131 | axios.get( 132 | `http://${ORCHESTRATOR_URL}/confirm/${process.env.EB_SERVER_ID}`, 133 | { 134 | auth: { 135 | username: '', 136 | password: ORCHESTRATOR_SECRET, 137 | }, 138 | withCredentials: true, 139 | }, 140 | ); 141 | } 142 | 143 | private disconnect() { 144 | axios.get( 145 | `http://${ORCHESTRATOR_URL}/disconnect/${process.env.EB_SERVER_ID}`, 146 | ); 147 | } 148 | 149 | public init() { 150 | this.wss.listen(SOCKET_PORT); 151 | setInterval(() => this.run(), GAMELOOP_RATE); 152 | // setInterval( 153 | // () => collisionWorker.postMessage(this.snakes), 154 | // GAMELOOP_RATE * 5, 155 | // ); 156 | 157 | this.wss.on('connection', (ws: Socket) => { 158 | this.confirmConnection(); 159 | 160 | let currentPlayer: Snake; 161 | 162 | ws.on('register', (username: string) => { 163 | currentPlayer = new Snake({ 164 | username, 165 | id: shortid.generate(), 166 | size: 30, 167 | length: 280, 168 | angle: Math.random() * 2 * Math.PI, 169 | x: ~~(Math.random() * (MAP_WIDTH - 100) + 100 / 2), 170 | y: ~~(Math.random() * (MAP_HEIGHT - 100) + 100 / 2), 171 | }); 172 | this.snakes.push(currentPlayer); 173 | ws.emit('register-success', currentPlayer); 174 | }); 175 | 176 | ws.on('move', (orientation: any) => { 177 | if (currentPlayer) currentPlayer.moveTo(orientation.x, orientation.y); 178 | }); 179 | 180 | ws.on('player-speed-up', () => { 181 | currentPlayer.speedUp(); 182 | }); 183 | 184 | ws.on('player-speed-down', () => { 185 | currentPlayer.speedDown(); 186 | }); 187 | 188 | ws.on('disconnect', () => { 189 | if (currentPlayer) { 190 | const idx = this.snakes.findIndex( 191 | snake => snake.id === currentPlayer.id, 192 | ); 193 | if (idx !== -1) { 194 | // handle the ctrl+R refresh case 195 | this.snakes.splice(idx, 1); 196 | currentPlayer = undefined; 197 | } 198 | } 199 | this.disconnect(); 200 | }); 201 | }); 202 | 203 | // collisionWorker.onmessage = (event: any) => { 204 | // (Object) 205 | // .values(event.data) 206 | // .forEach((snake: { id: string; points: any }) => { 207 | // this.snakes.splice(this.snakes.findIndex(s => s.id === snake.id), 1); 208 | // snake.points.forEach( 209 | // (point: any, index: number) => 210 | // index % 10 === 0 && 211 | // this.foods.push( 212 | // new Food({ 213 | // id: shortid.generate(), 214 | // x: point.x, 215 | // y: point.y, 216 | // size: 10, 217 | // value: INITIAL_FOOD_VALUE, 218 | // }), 219 | // ), 220 | // ); 221 | // }); 222 | // }; 223 | } 224 | 225 | private run() { 226 | const snakeDeleted: Map = new Map(); 227 | 228 | this.snakes.forEach((snake: Snake) => { 229 | snake.update(); 230 | 231 | if (snake.length <= 0) { 232 | snakeDeleted.set(snake.id, snake); 233 | } 234 | 235 | if ( 236 | this.snakes.some( 237 | (snake2: Snake) => 238 | snake2.id !== snake.id && 239 | rectCollision(snake, snake2) && 240 | snake2.points.some(point => 241 | collision(snake, { 242 | ...point, 243 | width: snake2.width, 244 | height: snake2.height, 245 | }), 246 | ), 247 | ) && 248 | !snakeDeleted.has(snake.id) 249 | ) { 250 | snakeDeleted.set(snake.id, snake); 251 | } 252 | 253 | this.foods.forEach((food: Food) => { 254 | food.update(); 255 | if (!collision(snake, food)) { 256 | return; 257 | } 258 | const added = snake.eat(food); 259 | this.foods.splice(this.foods.indexOf(food), 1); 260 | 261 | const newScale = snake.scale + added / (snake.width * 30); 262 | if (newScale < 1.4) { 263 | snake.scale = newScale; 264 | } 265 | }); 266 | 267 | const diff = INITIAL_FOOD_COUNT / 2 - this.foods.length; 268 | 269 | for (let i = 0; i < diff; i += 1) { 270 | this.foods.push( 271 | new Food({ 272 | id: shortid.generate(), 273 | x: ~~(Math.random() * (MAP_WIDTH - 100) + 100 / 2), 274 | y: ~~(Math.random() * (MAP_HEIGHT - 100) + 100 / 2), 275 | size: 10, 276 | value: INITIAL_FOOD_VALUE, 277 | }), 278 | ); 279 | } 280 | snakeDeleted.forEach((snake: { id: string; points: any }) => { 281 | this.snakes.splice(this.snakes.findIndex(s => s.id === snake.id), 1); 282 | snake.points.forEach( 283 | (point: any, index: number) => 284 | index % 10 === 0 && 285 | this.foods.push( 286 | new Food({ 287 | id: shortid.generate(), 288 | x: point.x, 289 | y: point.y, 290 | size: 10, 291 | value: INITIAL_FOOD_VALUE, 292 | }), 293 | ), 294 | ); 295 | }); 296 | this.limit(snake); 297 | }); 298 | 299 | this.wss.emit( 300 | 'game-update', 301 | gameUpdate.encode({ 302 | // snakes: this.snakes.map((snake: Snake) => { 303 | // return { 304 | // ...snake, 305 | // points: 306 | // snake.points.length < 20 307 | // ? snake.points 308 | // : snake.points.filter((point, index) => index % 10 === 0), 309 | // }; 310 | // }), 311 | snakes: this.snakes.map((snake: Snake) => { 312 | return { 313 | ...snake, 314 | score: snake.points.length, 315 | points: snake.simplifiedPoints, 316 | // points: simplify(snake.points, 5), 317 | }; 318 | }), 319 | foods: this.foods, 320 | }), 321 | ); 322 | } 323 | 324 | // limit element, prevent it moving to outside 325 | public limit(element: { 326 | x: number; 327 | y: number; 328 | width?: number; 329 | height?: number; 330 | }): void { 331 | const whalf: number = (element.width || 1) / 2; 332 | if (element.x < whalf) { 333 | element.x = whalf; 334 | } else if (element.x + whalf > this.width) { 335 | element.x = this.width - whalf; 336 | } 337 | 338 | const hhalf: number = (element.height || 1) / 2; 339 | if (element.y < hhalf) { 340 | element.y = hhalf; 341 | } else if (element.y + hhalf > this.height) { 342 | element.y = this.height - hhalf; 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /back/src/collision-worker.js: -------------------------------------------------------------------------------- 1 | var Worker = require('webworker-threads').Worker; 2 | 3 | var worker = new Worker(function() { 4 | const collision = (dom, dom2, isRect) => { 5 | const disX = dom.x - dom2.x; 6 | const disY = dom.y - dom2.y; 7 | const dw = dom.width + dom2.width; 8 | 9 | if (Math.abs(disX) > dw || Math.abs(disY) > dom.height + dom2.height) { 10 | return false; 11 | } 12 | 13 | return isRect ? true : Math.hypot(disX, disY) < dw / 2; 14 | }; 15 | 16 | const rectCollision = ({ collisionRect: rect1 }, { collisionRect: rect2 }) => 17 | !( 18 | rect2.minX > rect1.maxX || 19 | rect2.maxX < rect1.minX || 20 | rect2.maxY < rect1.minY || 21 | rect2.minY > rect1.maxY 22 | ); 23 | 24 | this.onmessage = function({ data }) { 25 | try { 26 | const snakes = data; 27 | const snakeDeleted = {}; 28 | snakes.forEach(snake => { 29 | // if (snake.length < 0) { 30 | // snakeDeleted.set(snake.id, snake); 31 | // return; 32 | // } 33 | if ( 34 | snakes.some( 35 | snake2 => 36 | snake2.id !== snake.id && 37 | rectCollision(snake, snake2) && 38 | snake2.points.some(point => 39 | collision(snake, { 40 | ...point, 41 | width: snake2.width, 42 | height: snake2.height, 43 | }), 44 | ), 45 | ) && 46 | !snakeDeleted[snake.id] 47 | ) { 48 | snakeDeleted[snake.id] = { id: snake.id, points: snake.points }; 49 | } 50 | }); 51 | 52 | postMessage(snakeDeleted); 53 | } catch (e) { 54 | postMessage('error: ' + e.message); 55 | } 56 | }; 57 | }); 58 | 59 | exports.default = worker; 60 | 61 | // worker.postMessage([{ x: 0, y: 0 }, { x: 1, y: 1 }]); 62 | 63 | // const worker = require('./worker').default; 64 | -------------------------------------------------------------------------------- /back/src/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | MAP_WIDTH: 4000, 3 | MAP_HEIGHT: 4000, 4 | 5 | SOCKET_PORT: process.env.EB_SERVER_PORT || 4242, 6 | SPEED: 5, 7 | BASE_ANGLE: Math.PI * 200, 8 | 9 | GAMELOOP_RATE: 16, 10 | MAP_RECT_WIDTH: 150, 11 | MAP_RECT_HEIGHT: 150, 12 | 13 | INITIAL_FOOD_COUNT: 700, 14 | INITIAL_FOOD_VALUE: 500, 15 | INITIAL_SCALE: 1, 16 | 17 | MAX_SIZE: 300, 18 | MAX_LEN: 300, 19 | ORCHESTRATOR_URL: process.env.EB_ORCHESTRATOR_URL || 'localhost:9000', 20 | ORCHESTRATOR_SECRET: process.env.EB_ORCHESTRATOR_SECRET || 'secret', 21 | }; 22 | -------------------------------------------------------------------------------- /back/src/entities/Food.ts: -------------------------------------------------------------------------------- 1 | import GameEntity, { GameEntityOptions } from './GameEntity'; 2 | 3 | interface FoodOptions extends GameEntityOptions { 4 | value: number; 5 | id: string; 6 | } 7 | 8 | export default class Food extends GameEntity { 9 | public value: number; 10 | public lightSize: number; 11 | public lightDirection: boolean = true; 12 | public id: string; 13 | 14 | constructor(options: FoodOptions) { 15 | super(options); 16 | this.id = options.id; 17 | this.value = options.value; 18 | this.lightSize = this.width / 2; 19 | } 20 | 21 | public action() { 22 | const lightSpeed = 1; 23 | 24 | this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed; 25 | 26 | // light animate 27 | if (this.lightSize > this.width || this.lightSize < this.width / 2) { 28 | this.lightDirection = !this.lightDirection; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /back/src/entities/GameEntity.ts: -------------------------------------------------------------------------------- 1 | export interface GameEntityOptions { 2 | x: number; 3 | y: number; 4 | size: number; 5 | width?: number; 6 | height?: number; 7 | } 8 | 9 | export default abstract class GameEntity { 10 | public x: number; 11 | public y: number; 12 | public width: number; 13 | public height: number; 14 | // public paintX: number; 15 | // public paintY: number; 16 | // public paintWidth: number; 17 | // public paintHeight: number; 18 | // public visible: boolean; 19 | 20 | constructor(options: GameEntityOptions) { 21 | this.x = +(options.x || 0); 22 | this.y = +(options.y || 0); 23 | this.width = options.size || options.width; 24 | this.height = options.size || options.height; 25 | 26 | if (!this.width || !this.height) { 27 | throw new Error('element size can not be undefined'); 28 | } 29 | } 30 | 31 | /** 32 | * update status 33 | */ 34 | public update(): void { 35 | // this.prepare(); 36 | this.action(); 37 | // this.render(); 38 | } 39 | 40 | public abstract action(): void; 41 | 42 | // public abstract render(): void; 43 | 44 | // private prepare(): void { 45 | // this.paintX = gameMap.view.relativeX(this.x); 46 | // this.paintY = gameMap.view.relativeY(this.y); 47 | // this.paintWidth = gameMap.view.relativeW(this.width); 48 | // this.paintHeight = gameMap.view.relativeH(this.height); 49 | // const halfWidth = this.paintWidth / 2; 50 | // const halfHeight = this.paintHeight / 2; 51 | // this.visible = (this.paintX + halfWidth > 0) 52 | // && (this.paintX - halfWidth < gameMap.view.width) 53 | // && (this.paintY + halfHeight > 0) 54 | // && (this.paintY - halfHeight < gameMap.view.height); 55 | // } 56 | } 57 | -------------------------------------------------------------------------------- /back/src/entities/Snake.ts: -------------------------------------------------------------------------------- 1 | import GameEntity, { GameEntityOptions } from './GameEntity'; 2 | import Food from './Food'; 3 | import config from '../config'; 4 | import randomcolor = require('randomcolor'); 5 | const simplify = require('simplify-js'); 6 | 7 | const { SPEED, BASE_ANGLE, INITIAL_SCALE, MAX_SIZE, MAX_LEN } = config; 8 | 9 | interface SnakeOptions extends GameEntityOptions { 10 | id: string; 11 | length?: number; 12 | angle?: number; 13 | username: string; 14 | fillColor?: string; 15 | strokeColor?: string; 16 | } 17 | 18 | export class Movement { 19 | constructor( 20 | public x: number, 21 | public y: number, 22 | public speed: number, 23 | public angle: number, 24 | ) {} 25 | } 26 | 27 | export default class Snake extends GameEntity { 28 | public score: number = 0; 29 | public isSpeedUp: boolean = false; 30 | public fillColor: string = ''; 31 | public angle: number; 32 | public stopped: boolean = false; 33 | public id: string; 34 | public scale: number = INITIAL_SCALE; 35 | public isBlinking: boolean = false; 36 | public collisionRect: { 37 | minX: number; 38 | minY: number; 39 | maxX: number; 40 | maxY: number; 41 | }; 42 | 43 | public frameCounter: number = 0; 44 | 45 | // save snake's movement 46 | public movementQueue: Movement[] = []; 47 | public username: string; 48 | // max length of queue 49 | public movementQueueLen: number; 50 | public speed: number = SPEED; 51 | public oldSpeed: number = SPEED; 52 | public length: number; 53 | public toAngle: number; 54 | private turnSpeed: number = 0.15; 55 | private vx: number = 0; 56 | private vy: number = 0; 57 | 58 | public points: { 59 | x: number; 60 | y: number; 61 | }[] = []; 62 | 63 | public simplifiedPoints: { 64 | x: number; 65 | y: number; 66 | }[] = []; 67 | 68 | constructor(options?: SnakeOptions) { 69 | super(options); 70 | this.id = options.id; 71 | const strokeColor: string = options.strokeColor || '#000'; 72 | this.fillColor = randomcolor(); 73 | this.toAngle = this.angle = (options.angle || 0) + BASE_ANGLE; 74 | this.length = options.length; 75 | this.collisionRect = { 76 | minX: 30000, 77 | maxX: -30000, 78 | minY: 30000, 79 | maxY: -30000, 80 | }; 81 | this.username = options.username; 82 | this.updateSize(); 83 | this.velocity(); 84 | } 85 | 86 | public updateSize(added: number = 0): void { 87 | this.width = Math.min(this.width + added, MAX_SIZE); 88 | this.height = Math.min(this.height + added, MAX_SIZE); 89 | this.length += Math.min(added * 50, MAX_LEN); 90 | this.turnSpeed -= added / 1000; 91 | this.turnSpeed = Math.max(0.08, this.turnSpeed); 92 | this.movementQueueLen = Math.ceil(this.length / this.oldSpeed); 93 | } 94 | 95 | // move to new position 96 | public moveTo(nx: number, ny: number): void { 97 | const x: number = nx - this.x; 98 | const y: number = this.y - ny; 99 | let angle: number = Math.atan(Math.abs(x / y)); 100 | 101 | // calculate angle, value is 0-360 102 | if (x > 0 && y < 0) { 103 | angle = Math.PI - angle; 104 | } else if (x < 0 && y < 0) { 105 | angle = Math.PI + angle; 106 | } else if (x < 0 && y > 0) { 107 | angle = Math.PI * 2 - angle; 108 | } 109 | 110 | const oldAngle: number = Math.abs(this.toAngle % (Math.PI * 2)); 111 | 112 | // number of turns 113 | let rounds: number = ~~(this.toAngle / (Math.PI * 2)); 114 | 115 | this.toAngle = angle; 116 | 117 | if (oldAngle >= (Math.PI * 3) / 2 && this.toAngle <= Math.PI / 2) { 118 | // move from fourth quadrant to first quadrant 119 | rounds += 1; 120 | } else if (oldAngle <= Math.PI / 2 && this.toAngle >= (Math.PI * 3) / 2) { 121 | // move from first quadrant to fourth quadrant 122 | rounds -= 1; 123 | } 124 | 125 | // calculate the real angle by rounds 126 | this.toAngle += rounds * Math.PI * 2; 127 | } 128 | 129 | // calculate horizontal speed and vertical speed by angle of snake header 130 | public velocity(): void { 131 | const angle: number = this.angle % (Math.PI * 2); 132 | const vx: number = Math.abs(this.speed * Math.sin(angle)); 133 | const vy: number = Math.abs(this.speed * Math.cos(angle)); 134 | 135 | if (angle < Math.PI / 2) { 136 | this.vx = vx; 137 | this.vy = -vy; 138 | } else if (angle < Math.PI) { 139 | this.vx = vx; 140 | this.vy = vy; 141 | } else if (angle < (Math.PI * 3) / 2) { 142 | this.vx = -vx; 143 | this.vy = vy; 144 | } else { 145 | this.vx = -vx; 146 | this.vy = -vy; 147 | } 148 | } 149 | 150 | // turn around 151 | public turnAround(): void { 152 | const angleDistance: number = this.toAngle - this.angle; 153 | 154 | if (Math.abs(angleDistance) <= this.turnSpeed) { 155 | // reset angle 156 | this.toAngle = this.angle = BASE_ANGLE + (this.toAngle % (Math.PI * 2)); 157 | } else { 158 | this.angle += Math.sign(angleDistance) * this.turnSpeed; 159 | } 160 | } 161 | 162 | public speedUp(): void { 163 | if (this.isSpeedUp) { 164 | return; 165 | } 166 | 167 | this.isSpeedUp = true; 168 | this.oldSpeed = this.speed; 169 | this.speed *= 2; 170 | } 171 | 172 | public speedDown(): void { 173 | if (!this.isSpeedUp) { 174 | return; 175 | } 176 | 177 | this.isSpeedUp = false; 178 | this.speed = this.oldSpeed; 179 | } 180 | 181 | // eat food 182 | public eat(food: Food): number { 183 | this.score += food.value; 184 | 185 | this.frameCounter = 10; 186 | 187 | // add points 188 | const added = food.value / 200; 189 | this.updateSize(added); 190 | return added; 191 | } 192 | 193 | public blink(): void { 194 | this.isBlinking = !this.isBlinking; 195 | } 196 | 197 | public updateCollisionRect(): void { 198 | this.simplifiedPoints = simplify(this.points, 5); 199 | // this.simplifiedPoints = this.points.slice(9, this.points.len); 200 | this.collisionRect = { 201 | minX: 3000, 202 | maxX: -3000, 203 | minY: 3000, 204 | maxY: -3000, 205 | }; 206 | 207 | this.simplifiedPoints.forEach((point: any) => { 208 | if (point.x < this.collisionRect.minX) { 209 | this.collisionRect.minX = point.x; 210 | } 211 | if (point.x > this.collisionRect.maxX) { 212 | this.collisionRect.maxX = point.x; 213 | } 214 | if (point.y < this.collisionRect.minY) { 215 | this.collisionRect.minY = point.y; 216 | } 217 | if (point.y > this.collisionRect.maxY) { 218 | this.collisionRect.maxY = point.y; 219 | } 220 | }); 221 | 222 | this.collisionRect.minX -= this.width; 223 | 224 | this.collisionRect.minY -= this.width; 225 | 226 | this.collisionRect.maxX += this.width; 227 | 228 | this.collisionRect.maxY += this.width; 229 | } 230 | 231 | // snake action 232 | public action() { 233 | if (this.stopped) { 234 | return; 235 | } 236 | 237 | if (this.frameCounter > 0) { 238 | this.blink(); 239 | this.frameCounter -= 1; 240 | } else { 241 | this.isBlinking = false; 242 | } 243 | 244 | if (this.isSpeedUp) { 245 | this.length = Math.max(this.length - 2, 0); 246 | this.movementQueueLen = Math.max(this.movementQueueLen - 2, 0); 247 | } 248 | 249 | // save movement 250 | this.movementQueue.push( 251 | new Movement(this.x, this.y, this.speed, this.angle), 252 | ); 253 | 254 | if (this.movementQueue.length > this.movementQueueLen) { 255 | this.movementQueue.shift(); 256 | } 257 | 258 | this.turnAround(); 259 | this.velocity(); 260 | this.x += this.vx; 261 | this.y += this.vy; 262 | 263 | this.points = []; 264 | let wholeLength = this.length; 265 | if (this.movementQueue.length) { 266 | let i = this.movementQueue.length - 1; 267 | while (i > 0) { 268 | const movement = this.movementQueue[i]; 269 | let x = movement.x; 270 | let y = movement.y; 271 | if (wholeLength > 0 && wholeLength < movement.speed) { 272 | const lm = this.movementQueue[i + 1] || this; 273 | const ratio = wholeLength / movement.speed; 274 | x = lm.x - (lm.x - x) * ratio; 275 | y = lm.y - (lm.y - y) * ratio; 276 | } else if (wholeLength < 0) { 277 | break; 278 | } 279 | 280 | i -= 1; 281 | wholeLength -= movement.speed; 282 | this.points.push({ x, y }); 283 | } 284 | } 285 | this.updateCollisionRect(); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /back/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as io from 'socket.io'; 2 | import { Game } from './Game'; 3 | 4 | const wss = io(); 5 | const game = new Game(wss); 6 | game.init(); 7 | -------------------------------------------------------------------------------- /back/src/simplify-worker.js: -------------------------------------------------------------------------------- 1 | var Worker = require('webworker-threads').Worker; 2 | 3 | var worker = new Worker(function() { 4 | function getSqDist(p1, p2) { 5 | var dx = p1.x - p2.x, 6 | dy = p1.y - p2.y; 7 | 8 | return dx * dx + dy * dy; 9 | } 10 | 11 | // square distance from a point to a segment 12 | function getSqSegDist(p, p1, p2) { 13 | var x = p1.x, 14 | y = p1.y, 15 | dx = p2.x - x, 16 | dy = p2.y - y; 17 | 18 | if (dx !== 0 || dy !== 0) { 19 | var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); 20 | 21 | if (t > 1) { 22 | x = p2.x; 23 | y = p2.y; 24 | } else if (t > 0) { 25 | x += dx * t; 26 | y += dy * t; 27 | } 28 | } 29 | 30 | dx = p.x - x; 31 | dy = p.y - y; 32 | 33 | return dx * dx + dy * dy; 34 | } 35 | // rest of the code doesn't care about point format 36 | 37 | // basic distance-based simplification 38 | function simplifyRadialDist(points, sqTolerance) { 39 | var prevPoint = points[0], 40 | newPoints = [prevPoint], 41 | point; 42 | 43 | for (var i = 1, len = points.length; i < len; i++) { 44 | point = points[i]; 45 | 46 | if (getSqDist(point, prevPoint) > sqTolerance) { 47 | newPoints.push(point); 48 | prevPoint = point; 49 | } 50 | } 51 | 52 | if (prevPoint !== point) newPoints.push(point); 53 | 54 | return newPoints; 55 | } 56 | 57 | function simplifyDPStep(points, first, last, sqTolerance, simplified) { 58 | var maxSqDist = sqTolerance, 59 | index; 60 | 61 | for (var i = first + 1; i < last; i++) { 62 | var sqDist = getSqSegDist(points[i], points[first], points[last]); 63 | 64 | if (sqDist > maxSqDist) { 65 | index = i; 66 | maxSqDist = sqDist; 67 | } 68 | } 69 | 70 | if (maxSqDist > sqTolerance) { 71 | if (index - first > 1) 72 | simplifyDPStep(points, first, index, sqTolerance, simplified); 73 | simplified.push(points[index]); 74 | if (last - index > 1) 75 | simplifyDPStep(points, index, last, sqTolerance, simplified); 76 | } 77 | } 78 | 79 | // simplification using Ramer-Douglas-Peucker algorithm 80 | function simplifyDouglasPeucker(points, sqTolerance) { 81 | var last = points.length - 1; 82 | 83 | var simplified = [points[0]]; 84 | simplifyDPStep(points, 0, last, sqTolerance, simplified); 85 | simplified.push(points[last]); 86 | 87 | return simplified; 88 | } 89 | 90 | // both algorithms combined for awesome performance 91 | function simplify(points, tolerance, highestQuality) { 92 | if (points.length <= 2) return points; 93 | 94 | var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1; 95 | 96 | points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); 97 | points = simplifyDouglasPeucker(points, sqTolerance); 98 | 99 | return points; 100 | } 101 | 102 | this.onmessage = function(event) { 103 | postMessage(simplify(event.data, 5)); 104 | }; 105 | }); 106 | worker.onmessage = function(event) { 107 | console.log('Worker said : ' + event.data); 108 | }; 109 | 110 | exports.default = worker; 111 | worker.postMessage([{ x: 0, y: 0 }, { x: 1, y: 1 }]); 112 | const worker = require('./worker').default; 113 | -------------------------------------------------------------------------------- /back/src/webworker-threads.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'webworker-threads' { 2 | export interface Event { 3 | data: any; 4 | } 5 | 6 | export type CallBack = (...args: any[]) => any; 7 | 8 | export interface WorkerConstructor { 9 | new (jsfilepath: string): Worker; 10 | new (fun: (this: Worker) => void): Worker; 11 | new (): Worker; 12 | } 13 | 14 | export interface Worker { 15 | // Returns the underlying thread object; see the next section for details. Note that this attribute is implementation-specific, and not part of W3C Web Worker API. 16 | readonly thread: Thread; 17 | // worker.postMessage({ x: 1, y: 2 }) sends a data structure into the worker. The worker can receive it using the onmessage handler. 18 | postMessage(data: any): void; 19 | // worker.onmessage = function (event) { console.log(event.data) }; receives data from the worker's postMessage calls. 20 | onmessage(event: Event): void; 21 | // terminates the worker thread. 22 | terminate(): void; 23 | // worker.addEventListener('message', callback) is equivalent to setting worker.onmesssage = callback. 24 | addEventListener(type: string, cb: CallBack): void; 25 | // Currently unimplemented. 26 | dispatchEvent(type: string): any; 27 | // Currently unimplemented. 28 | removeEventListener(type: string): any; 29 | } 30 | 31 | export interface Thread { 32 | readonly id: number; 33 | // thread.load( absolutePath [, cb] ) reads the file at absolutePath and thread.eval(fileContents, cb). 34 | load( 35 | absolutePath: string, 36 | cb?: (this: Thread, err: any, data: any) => void, 37 | ): void; 38 | 39 | // thread.eval( program [, cb]) converts program.toString() and eval()s it in the thread's global context, and (if provided) returns the completion value to cb(err, completionValue). 40 | eval( 41 | program: T, 42 | cb?: (this: Thread, err: any, data: any) => void, 43 | ): this; 44 | 45 | // thread.on( eventType, listener ) registers the listener listener(data) for any events of eventType that the thread thread may emit. 46 | on(eventType: string, listener: (data: any) => void): this; 47 | 48 | // thread.once(eventType, listener) is like thread.on(), but the listener will only be called once. 49 | once(eventType: string, listener: (data: any) => void): this; 50 | 51 | // thread.removeAllListeners([eventType]) deletes all listeners for all eventTypes.If eventType is provided, deletes all listeners only for the event type eventType. 52 | removeAllListeners(eventType?: string): this; 53 | 54 | // thread.emit(eventType, eventData[, eventData ... ]) emits an event of eventType with eventData inside the thread thread.All its arguments are.toString()ed. 55 | emit(eventType: string, ...eventDatas: any[]): this; 56 | 57 | // thread.destroy( /* no arguments */) destroys the thread. 58 | destroy(): this; 59 | } 60 | 61 | export interface ThreadPool { 62 | // threadPool.load( absolutePath [, cb] ) runs thread.load( absolutePath [, cb] ) in all the pool's threads. 63 | load( 64 | absolutePath: string, 65 | cb?: (this: Thread, err: any, data: any) => void, 66 | ): this; 67 | 68 | readonly any: { 69 | // threadPool.any.eval( program, cb ) is like thread.eval(), but in any of the pool's threads. 70 | eval( 71 | program: any, 72 | cb?: (this: Thread, err: any, data: any) => void, 73 | ): ThreadPool; 74 | 75 | // threadPool.any.emit( eventType, eventData [, eventData ... ] ) is like thread.emit(), but in any of the pool's threads. 76 | emit(eventType: string, ...eventData: any[]): ThreadPool; 77 | }; 78 | 79 | readonly all: { 80 | // threadPool.all.eval( program, cb ) is like thread.eval(), but in all the pool's threads. 81 | eval( 82 | program: any, 83 | cb?: (this: Thread, err: any, data: any) => void, 84 | ): ThreadPool; 85 | 86 | // threadPool.all.emit( eventType, eventData [, eventData ... ] ) is like thread.emit(), but in all the pool's threads. 87 | emit(eventType: string, ...eventData: any[]): ThreadPool; 88 | }; 89 | 90 | // threadPool.on( eventType, listener ) is like thread.on(), but in all of the pool's threads. 91 | on(eventType: string, listener: CallBack): this; 92 | 93 | // threadPool.totalThreads() returns the number of threads in this pool: as supplied in .createPool( number ) 94 | totalThreads(): number; 95 | 96 | // threadPool.idleThreads() returns the number of threads in this pool that are currently idle (sleeping) 97 | idleThreads(): number; 98 | 99 | // threadPool.pendingJobs() returns the number of jobs pending. 100 | pendingJobs(): number; 101 | 102 | // threadPool.destroy( [ rudely ] ) waits until pendingJobs() is zero and then destroys the pool. If rudely is truthy, then it doesn't wait for pendingJobs === 0. 103 | destroy(rudely?: boolean): void; 104 | } 105 | 106 | export const Worker: WorkerConstructor; 107 | 108 | export function createPool(numThreads: number): ThreadPool; 109 | 110 | export function create(): Thread; 111 | } 112 | -------------------------------------------------------------------------------- /back/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "declaration": false, 9 | "noImplicitReturns": true, 10 | "noImplicitThis": true, 11 | "noImplicitAny": true, 12 | "sourceMap": true, 13 | "noLib": false, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "typeRoots": [ 18 | "node_modules/@types" 19 | ] 20 | }, 21 | "compileOnSave": false, 22 | "exclude": [ 23 | "dist", 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /back/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/tslint-config-airbnb/tslint" 3 | } 4 | -------------------------------------------------------------------------------- /back/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@fimbul/bifrost@^0.9.0": 6 | version "0.9.0" 7 | resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.9.0.tgz#75c7d7ad0c5464f22603b715324ec89c1f50138f" 8 | dependencies: 9 | "@fimbul/ymir" "^0.9.0" 10 | get-caller-file "^1.0.2" 11 | tslib "^1.8.1" 12 | tsutils "^2.24.0" 13 | 14 | "@fimbul/ymir@^0.9.0": 15 | version "0.9.0" 16 | resolved "https://registry.yarnpkg.com/@fimbul/ymir/-/ymir-0.9.0.tgz#64bb9c658a48b584c070e830911c3675dc8e81da" 17 | dependencies: 18 | inversify "^4.10.0" 19 | reflect-metadata "^0.1.12" 20 | tslib "^1.8.1" 21 | 22 | "@types/node@*": 23 | version "10.3.1" 24 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.1.tgz#51092fbacaed768a122a293814474fbf6e5e8b6d" 25 | 26 | "@types/randomcolor@^0.4.3": 27 | version "0.4.3" 28 | resolved "https://registry.npmjs.org/@types/randomcolor/-/randomcolor-0.4.3.tgz#66bf69fdf43ea7b73ae4c378f660442c8e236e93" 29 | 30 | "@types/shortid@^0.0.29": 31 | version "0.0.29" 32 | resolved "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b" 33 | 34 | "@types/socket.io@^1.4.33": 35 | version "1.4.34" 36 | resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-1.4.34.tgz#858860c7e6dd7eb55abc39da55f958c2172ccf41" 37 | dependencies: 38 | "@types/node" "*" 39 | 40 | abbrev@1: 41 | version "1.1.1" 42 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 43 | 44 | accepts@~1.3.4: 45 | version "1.3.5" 46 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 47 | dependencies: 48 | mime-types "~2.1.18" 49 | negotiator "0.6.1" 50 | 51 | after@0.8.2: 52 | version "0.8.2" 53 | resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" 54 | 55 | ansi-align@^2.0.0: 56 | version "2.0.0" 57 | resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" 58 | dependencies: 59 | string-width "^2.0.0" 60 | 61 | ansi-regex@^2.0.0: 62 | version "2.1.1" 63 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 64 | 65 | ansi-regex@^3.0.0: 66 | version "3.0.0" 67 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 68 | 69 | ansi-styles@^2.2.1: 70 | version "2.2.1" 71 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 72 | 73 | ansi-styles@^3.2.1: 74 | version "3.2.1" 75 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 76 | dependencies: 77 | color-convert "^1.9.0" 78 | 79 | anymatch@^2.0.0: 80 | version "2.0.0" 81 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" 82 | dependencies: 83 | micromatch "^3.1.4" 84 | normalize-path "^2.1.1" 85 | 86 | aproba@^1.0.3: 87 | version "1.2.0" 88 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" 89 | 90 | are-we-there-yet@~1.1.2: 91 | version "1.1.5" 92 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" 93 | dependencies: 94 | delegates "^1.0.0" 95 | readable-stream "^2.0.6" 96 | 97 | argparse@^1.0.7: 98 | version "1.0.10" 99 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 100 | dependencies: 101 | sprintf-js "~1.0.2" 102 | 103 | arr-diff@^4.0.0: 104 | version "4.0.0" 105 | resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" 106 | 107 | arr-flatten@^1.1.0: 108 | version "1.1.0" 109 | resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" 110 | 111 | arr-union@^3.1.0: 112 | version "3.1.0" 113 | resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" 114 | 115 | array-unique@^0.3.2: 116 | version "0.3.2" 117 | resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" 118 | 119 | arraybuffer.slice@~0.0.7: 120 | version "0.0.7" 121 | resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" 122 | 123 | arrify@^1.0.0: 124 | version "1.0.1" 125 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 126 | 127 | assign-symbols@^1.0.0: 128 | version "1.0.0" 129 | resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" 130 | 131 | async-each@^1.0.0: 132 | version "1.0.1" 133 | resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" 134 | 135 | async-limiter@~1.0.0: 136 | version "1.0.0" 137 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 138 | 139 | atob@^2.1.1: 140 | version "2.1.1" 141 | resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" 142 | 143 | axios@^0.18.0: 144 | version "0.18.0" 145 | resolved "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" 146 | dependencies: 147 | follow-redirects "^1.3.0" 148 | is-buffer "^1.1.5" 149 | 150 | babel-code-frame@^6.22.0: 151 | version "6.26.0" 152 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" 153 | dependencies: 154 | chalk "^1.1.3" 155 | esutils "^2.0.2" 156 | js-tokens "^3.0.2" 157 | 158 | backo2@1.0.2: 159 | version "1.0.2" 160 | resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 161 | 162 | balanced-match@^1.0.0: 163 | version "1.0.0" 164 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 165 | 166 | base64-arraybuffer@0.1.5: 167 | version "0.1.5" 168 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" 169 | 170 | base64id@1.0.0: 171 | version "1.0.0" 172 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" 173 | 174 | base@^0.11.1: 175 | version "0.11.2" 176 | resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" 177 | dependencies: 178 | cache-base "^1.0.1" 179 | class-utils "^0.3.5" 180 | component-emitter "^1.2.1" 181 | define-property "^1.0.0" 182 | isobject "^3.0.1" 183 | mixin-deep "^1.2.0" 184 | pascalcase "^0.1.1" 185 | 186 | better-assert@~1.0.0: 187 | version "1.0.2" 188 | resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" 189 | dependencies: 190 | callsite "1.0.0" 191 | 192 | binary-extensions@^1.0.0: 193 | version "1.11.0" 194 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" 195 | 196 | bindings@^1.3.0: 197 | version "1.3.0" 198 | resolved "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" 199 | 200 | blob@0.0.4: 201 | version "0.0.4" 202 | resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" 203 | 204 | boxen@^1.2.1: 205 | version "1.3.0" 206 | resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" 207 | dependencies: 208 | ansi-align "^2.0.0" 209 | camelcase "^4.0.0" 210 | chalk "^2.0.1" 211 | cli-boxes "^1.0.0" 212 | string-width "^2.0.0" 213 | term-size "^1.2.0" 214 | widest-line "^2.0.0" 215 | 216 | brace-expansion@^1.1.7: 217 | version "1.1.11" 218 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 219 | dependencies: 220 | balanced-match "^1.0.0" 221 | concat-map "0.0.1" 222 | 223 | braces@^2.3.0, braces@^2.3.1: 224 | version "2.3.2" 225 | resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" 226 | dependencies: 227 | arr-flatten "^1.1.0" 228 | array-unique "^0.3.2" 229 | extend-shallow "^2.0.1" 230 | fill-range "^4.0.0" 231 | isobject "^3.0.1" 232 | repeat-element "^1.1.2" 233 | snapdragon "^0.8.1" 234 | snapdragon-node "^2.0.1" 235 | split-string "^3.0.2" 236 | to-regex "^3.0.1" 237 | 238 | buffer-from@^1.0.0: 239 | version "1.1.0" 240 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" 241 | 242 | builtin-modules@^1.1.1: 243 | version "1.1.1" 244 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 245 | 246 | cache-base@^1.0.1: 247 | version "1.0.1" 248 | resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" 249 | dependencies: 250 | collection-visit "^1.0.0" 251 | component-emitter "^1.2.1" 252 | get-value "^2.0.6" 253 | has-value "^1.0.0" 254 | isobject "^3.0.1" 255 | set-value "^2.0.0" 256 | to-object-path "^0.3.0" 257 | union-value "^1.0.0" 258 | unset-value "^1.0.0" 259 | 260 | callsite@1.0.0: 261 | version "1.0.0" 262 | resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" 263 | 264 | camelcase@^4.0.0: 265 | version "4.1.0" 266 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" 267 | 268 | capture-stack-trace@^1.0.0: 269 | version "1.0.0" 270 | resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" 271 | 272 | chalk@^1.1.3: 273 | version "1.1.3" 274 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 275 | dependencies: 276 | ansi-styles "^2.2.1" 277 | escape-string-regexp "^1.0.2" 278 | has-ansi "^2.0.0" 279 | strip-ansi "^3.0.0" 280 | supports-color "^2.0.0" 281 | 282 | chalk@^2.0.1, chalk@^2.3.0: 283 | version "2.4.1" 284 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" 285 | dependencies: 286 | ansi-styles "^3.2.1" 287 | escape-string-regexp "^1.0.5" 288 | supports-color "^5.3.0" 289 | 290 | chokidar@^2.0.2: 291 | version "2.0.3" 292 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" 293 | dependencies: 294 | anymatch "^2.0.0" 295 | async-each "^1.0.0" 296 | braces "^2.3.0" 297 | glob-parent "^3.1.0" 298 | inherits "^2.0.1" 299 | is-binary-path "^1.0.0" 300 | is-glob "^4.0.0" 301 | normalize-path "^2.1.1" 302 | path-is-absolute "^1.0.0" 303 | readdirp "^2.0.0" 304 | upath "^1.0.0" 305 | optionalDependencies: 306 | fsevents "^1.1.2" 307 | 308 | chownr@^1.0.1: 309 | version "1.0.1" 310 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" 311 | 312 | ci-info@^1.0.0: 313 | version "1.1.3" 314 | resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" 315 | 316 | class-utils@^0.3.5: 317 | version "0.3.6" 318 | resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" 319 | dependencies: 320 | arr-union "^3.1.0" 321 | define-property "^0.2.5" 322 | isobject "^3.0.0" 323 | static-extend "^0.1.1" 324 | 325 | cli-boxes@^1.0.0: 326 | version "1.0.0" 327 | resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" 328 | 329 | code-point-at@^1.0.0: 330 | version "1.1.0" 331 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 332 | 333 | collection-visit@^1.0.0: 334 | version "1.0.0" 335 | resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" 336 | dependencies: 337 | map-visit "^1.0.0" 338 | object-visit "^1.0.0" 339 | 340 | color-convert@^1.9.0: 341 | version "1.9.1" 342 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 343 | dependencies: 344 | color-name "^1.1.1" 345 | 346 | color-name@^1.1.1: 347 | version "1.1.3" 348 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 349 | 350 | commander@^2.12.1: 351 | version "2.15.1" 352 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 353 | 354 | component-bind@1.0.0: 355 | version "1.0.0" 356 | resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" 357 | 358 | component-emitter@1.2.1, component-emitter@^1.2.1: 359 | version "1.2.1" 360 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" 361 | 362 | component-inherit@0.0.3: 363 | version "0.0.3" 364 | resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" 365 | 366 | concat-map@0.0.1: 367 | version "0.0.1" 368 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 369 | 370 | configstore@^3.0.0: 371 | version "3.1.2" 372 | resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" 373 | dependencies: 374 | dot-prop "^4.1.0" 375 | graceful-fs "^4.1.2" 376 | make-dir "^1.0.0" 377 | unique-string "^1.0.0" 378 | write-file-atomic "^2.0.0" 379 | xdg-basedir "^3.0.0" 380 | 381 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: 382 | version "1.1.0" 383 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 384 | 385 | cookie@0.3.1: 386 | version "0.3.1" 387 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 388 | 389 | copy-descriptor@^0.1.0: 390 | version "0.1.1" 391 | resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" 392 | 393 | core-util-is@~1.0.0: 394 | version "1.0.2" 395 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 396 | 397 | create-error-class@^3.0.0: 398 | version "3.0.2" 399 | resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" 400 | dependencies: 401 | capture-stack-trace "^1.0.0" 402 | 403 | cross-spawn@^5.0.1: 404 | version "5.1.0" 405 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 406 | dependencies: 407 | lru-cache "^4.0.1" 408 | shebang-command "^1.2.0" 409 | which "^1.2.9" 410 | 411 | crypto-random-string@^1.0.0: 412 | version "1.0.0" 413 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" 414 | 415 | debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: 416 | version "2.6.9" 417 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 418 | dependencies: 419 | ms "2.0.0" 420 | 421 | debug@^3.1.0, debug@~3.1.0: 422 | version "3.1.0" 423 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 424 | dependencies: 425 | ms "2.0.0" 426 | 427 | decode-uri-component@^0.2.0: 428 | version "0.2.0" 429 | resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" 430 | 431 | deep-extend@^0.6.0: 432 | version "0.6.0" 433 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 434 | 435 | define-property@^0.2.5: 436 | version "0.2.5" 437 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" 438 | dependencies: 439 | is-descriptor "^0.1.0" 440 | 441 | define-property@^1.0.0: 442 | version "1.0.0" 443 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" 444 | dependencies: 445 | is-descriptor "^1.0.0" 446 | 447 | define-property@^2.0.2: 448 | version "2.0.2" 449 | resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" 450 | dependencies: 451 | is-descriptor "^1.0.2" 452 | isobject "^3.0.1" 453 | 454 | delegates@^1.0.0: 455 | version "1.0.0" 456 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 457 | 458 | detect-libc@^1.0.2: 459 | version "1.0.3" 460 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 461 | 462 | diff@^3.1.0, diff@^3.2.0: 463 | version "3.5.0" 464 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 465 | 466 | doctrine@0.7.2: 467 | version "0.7.2" 468 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" 469 | dependencies: 470 | esutils "^1.1.6" 471 | isarray "0.0.1" 472 | 473 | dot-prop@^4.1.0: 474 | version "4.2.0" 475 | resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" 476 | dependencies: 477 | is-obj "^1.0.0" 478 | 479 | duplexer3@^0.1.4: 480 | version "0.1.4" 481 | resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 482 | 483 | duplexer@~0.1.1: 484 | version "0.1.1" 485 | resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" 486 | 487 | engine.io-client@~3.2.0: 488 | version "3.2.1" 489 | resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36" 490 | dependencies: 491 | component-emitter "1.2.1" 492 | component-inherit "0.0.3" 493 | debug "~3.1.0" 494 | engine.io-parser "~2.1.1" 495 | has-cors "1.1.0" 496 | indexof "0.0.1" 497 | parseqs "0.0.5" 498 | parseuri "0.0.5" 499 | ws "~3.3.1" 500 | xmlhttprequest-ssl "~1.5.4" 501 | yeast "0.1.2" 502 | 503 | engine.io-parser@~2.1.0, engine.io-parser@~2.1.1: 504 | version "2.1.2" 505 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.2.tgz#4c0f4cff79aaeecbbdcfdea66a823c6085409196" 506 | dependencies: 507 | after "0.8.2" 508 | arraybuffer.slice "~0.0.7" 509 | base64-arraybuffer "0.1.5" 510 | blob "0.0.4" 511 | has-binary2 "~1.0.2" 512 | 513 | engine.io@~3.2.0: 514 | version "3.2.0" 515 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.0.tgz#54332506f42f2edc71690d2f2a42349359f3bf7d" 516 | dependencies: 517 | accepts "~1.3.4" 518 | base64id "1.0.0" 519 | cookie "0.3.1" 520 | debug "~3.1.0" 521 | engine.io-parser "~2.1.0" 522 | ws "~3.3.1" 523 | 524 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 525 | version "1.0.5" 526 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 527 | 528 | esprima@^4.0.0: 529 | version "4.0.0" 530 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" 531 | 532 | esutils@^1.1.6: 533 | version "1.1.6" 534 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375" 535 | 536 | esutils@^2.0.2: 537 | version "2.0.2" 538 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 539 | 540 | event-stream@~3.3.0: 541 | version "3.3.4" 542 | resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" 543 | dependencies: 544 | duplexer "~0.1.1" 545 | from "~0" 546 | map-stream "~0.1.0" 547 | pause-stream "0.0.11" 548 | split "0.3" 549 | stream-combiner "~0.0.4" 550 | through "~2.3.1" 551 | 552 | execa@^0.7.0: 553 | version "0.7.0" 554 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" 555 | dependencies: 556 | cross-spawn "^5.0.1" 557 | get-stream "^3.0.0" 558 | is-stream "^1.1.0" 559 | npm-run-path "^2.0.0" 560 | p-finally "^1.0.0" 561 | signal-exit "^3.0.0" 562 | strip-eof "^1.0.0" 563 | 564 | expand-brackets@^2.1.4: 565 | version "2.1.4" 566 | resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" 567 | dependencies: 568 | debug "^2.3.3" 569 | define-property "^0.2.5" 570 | extend-shallow "^2.0.1" 571 | posix-character-classes "^0.1.0" 572 | regex-not "^1.0.0" 573 | snapdragon "^0.8.1" 574 | to-regex "^3.0.1" 575 | 576 | extend-shallow@^2.0.1: 577 | version "2.0.1" 578 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" 579 | dependencies: 580 | is-extendable "^0.1.0" 581 | 582 | extend-shallow@^3.0.0, extend-shallow@^3.0.2: 583 | version "3.0.2" 584 | resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" 585 | dependencies: 586 | assign-symbols "^1.0.0" 587 | is-extendable "^1.0.1" 588 | 589 | extglob@^2.0.4: 590 | version "2.0.4" 591 | resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" 592 | dependencies: 593 | array-unique "^0.3.2" 594 | define-property "^1.0.0" 595 | expand-brackets "^2.1.4" 596 | extend-shallow "^2.0.1" 597 | fragment-cache "^0.2.1" 598 | regex-not "^1.0.0" 599 | snapdragon "^0.8.1" 600 | to-regex "^3.0.1" 601 | 602 | fill-range@^4.0.0: 603 | version "4.0.0" 604 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" 605 | dependencies: 606 | extend-shallow "^2.0.1" 607 | is-number "^3.0.0" 608 | repeat-string "^1.6.1" 609 | to-regex-range "^2.1.0" 610 | 611 | follow-redirects@^1.3.0: 612 | version "1.5.0" 613 | resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77" 614 | dependencies: 615 | debug "^3.1.0" 616 | 617 | for-in@^1.0.2: 618 | version "1.0.2" 619 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" 620 | 621 | fragment-cache@^0.2.1: 622 | version "0.2.1" 623 | resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" 624 | dependencies: 625 | map-cache "^0.2.2" 626 | 627 | from@~0: 628 | version "0.1.7" 629 | resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" 630 | 631 | fs-minipass@^1.2.5: 632 | version "1.2.5" 633 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" 634 | dependencies: 635 | minipass "^2.2.1" 636 | 637 | fs.realpath@^1.0.0: 638 | version "1.0.0" 639 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 640 | 641 | fsevents@^1.1.2: 642 | version "1.2.4" 643 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" 644 | dependencies: 645 | nan "^2.9.2" 646 | node-pre-gyp "^0.10.0" 647 | 648 | gauge@~2.7.3: 649 | version "2.7.4" 650 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" 651 | dependencies: 652 | aproba "^1.0.3" 653 | console-control-strings "^1.0.0" 654 | has-unicode "^2.0.0" 655 | object-assign "^4.1.0" 656 | signal-exit "^3.0.0" 657 | string-width "^1.0.1" 658 | strip-ansi "^3.0.1" 659 | wide-align "^1.1.0" 660 | 661 | get-caller-file@^1.0.2: 662 | version "1.0.2" 663 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" 664 | 665 | get-stream@^3.0.0: 666 | version "3.0.0" 667 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 668 | 669 | get-value@^2.0.3, get-value@^2.0.6: 670 | version "2.0.6" 671 | resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" 672 | 673 | glob-parent@^3.1.0: 674 | version "3.1.0" 675 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" 676 | dependencies: 677 | is-glob "^3.1.0" 678 | path-dirname "^1.0.0" 679 | 680 | glob@^7.0.5, glob@^7.1.1: 681 | version "7.1.2" 682 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 683 | dependencies: 684 | fs.realpath "^1.0.0" 685 | inflight "^1.0.4" 686 | inherits "2" 687 | minimatch "^3.0.4" 688 | once "^1.3.0" 689 | path-is-absolute "^1.0.0" 690 | 691 | global-dirs@^0.1.0: 692 | version "0.1.1" 693 | resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" 694 | dependencies: 695 | ini "^1.3.4" 696 | 697 | got@^6.7.1: 698 | version "6.7.1" 699 | resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" 700 | dependencies: 701 | create-error-class "^3.0.0" 702 | duplexer3 "^0.1.4" 703 | get-stream "^3.0.0" 704 | is-redirect "^1.0.0" 705 | is-retry-allowed "^1.0.0" 706 | is-stream "^1.0.0" 707 | lowercase-keys "^1.0.0" 708 | safe-buffer "^5.0.1" 709 | timed-out "^4.0.0" 710 | unzip-response "^2.0.1" 711 | url-parse-lax "^1.0.0" 712 | 713 | graceful-fs@^4.1.11, graceful-fs@^4.1.2: 714 | version "4.1.11" 715 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 716 | 717 | has-ansi@^2.0.0: 718 | version "2.0.0" 719 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 720 | dependencies: 721 | ansi-regex "^2.0.0" 722 | 723 | has-binary2@~1.0.2: 724 | version "1.0.3" 725 | resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" 726 | dependencies: 727 | isarray "2.0.1" 728 | 729 | has-cors@1.1.0: 730 | version "1.1.0" 731 | resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" 732 | 733 | has-flag@^3.0.0: 734 | version "3.0.0" 735 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 736 | 737 | has-unicode@^2.0.0: 738 | version "2.0.1" 739 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 740 | 741 | has-value@^0.3.1: 742 | version "0.3.1" 743 | resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" 744 | dependencies: 745 | get-value "^2.0.3" 746 | has-values "^0.1.4" 747 | isobject "^2.0.0" 748 | 749 | has-value@^1.0.0: 750 | version "1.0.0" 751 | resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" 752 | dependencies: 753 | get-value "^2.0.6" 754 | has-values "^1.0.0" 755 | isobject "^3.0.0" 756 | 757 | has-values@^0.1.4: 758 | version "0.1.4" 759 | resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" 760 | 761 | has-values@^1.0.0: 762 | version "1.0.0" 763 | resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" 764 | dependencies: 765 | is-number "^3.0.0" 766 | kind-of "^4.0.0" 767 | 768 | iconv-lite@^0.4.4: 769 | version "0.4.23" 770 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 771 | dependencies: 772 | safer-buffer ">= 2.1.2 < 3" 773 | 774 | ignore-by-default@^1.0.1: 775 | version "1.0.1" 776 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 777 | 778 | ignore-walk@^3.0.1: 779 | version "3.0.1" 780 | resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" 781 | dependencies: 782 | minimatch "^3.0.4" 783 | 784 | import-lazy@^2.1.0: 785 | version "2.1.0" 786 | resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 787 | 788 | imurmurhash@^0.1.4: 789 | version "0.1.4" 790 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 791 | 792 | indexof@0.0.1: 793 | version "0.0.1" 794 | resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" 795 | 796 | inflight@^1.0.4: 797 | version "1.0.6" 798 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 799 | dependencies: 800 | once "^1.3.0" 801 | wrappy "1" 802 | 803 | inherits@2, inherits@^2.0.1, inherits@~2.0.3: 804 | version "2.0.3" 805 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 806 | 807 | ini@^1.3.4, ini@~1.3.0: 808 | version "1.3.5" 809 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 810 | 811 | inversify@^4.10.0: 812 | version "4.13.0" 813 | resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.13.0.tgz#0ab40570bfa4474b04d5b919bbab3a4f682a72f5" 814 | 815 | is-accessor-descriptor@^0.1.6: 816 | version "0.1.6" 817 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" 818 | dependencies: 819 | kind-of "^3.0.2" 820 | 821 | is-accessor-descriptor@^1.0.0: 822 | version "1.0.0" 823 | resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" 824 | dependencies: 825 | kind-of "^6.0.0" 826 | 827 | is-binary-path@^1.0.0: 828 | version "1.0.1" 829 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" 830 | dependencies: 831 | binary-extensions "^1.0.0" 832 | 833 | is-buffer@^1.1.5: 834 | version "1.1.6" 835 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 836 | 837 | is-ci@^1.0.10: 838 | version "1.1.0" 839 | resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" 840 | dependencies: 841 | ci-info "^1.0.0" 842 | 843 | is-data-descriptor@^0.1.4: 844 | version "0.1.4" 845 | resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" 846 | dependencies: 847 | kind-of "^3.0.2" 848 | 849 | is-data-descriptor@^1.0.0: 850 | version "1.0.0" 851 | resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" 852 | dependencies: 853 | kind-of "^6.0.0" 854 | 855 | is-descriptor@^0.1.0: 856 | version "0.1.6" 857 | resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" 858 | dependencies: 859 | is-accessor-descriptor "^0.1.6" 860 | is-data-descriptor "^0.1.4" 861 | kind-of "^5.0.0" 862 | 863 | is-descriptor@^1.0.0, is-descriptor@^1.0.2: 864 | version "1.0.2" 865 | resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" 866 | dependencies: 867 | is-accessor-descriptor "^1.0.0" 868 | is-data-descriptor "^1.0.0" 869 | kind-of "^6.0.2" 870 | 871 | is-extendable@^0.1.0, is-extendable@^0.1.1: 872 | version "0.1.1" 873 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" 874 | 875 | is-extendable@^1.0.1: 876 | version "1.0.1" 877 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" 878 | dependencies: 879 | is-plain-object "^2.0.4" 880 | 881 | is-extglob@^2.1.0, is-extglob@^2.1.1: 882 | version "2.1.1" 883 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 884 | 885 | is-fullwidth-code-point@^1.0.0: 886 | version "1.0.0" 887 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 888 | dependencies: 889 | number-is-nan "^1.0.0" 890 | 891 | is-fullwidth-code-point@^2.0.0: 892 | version "2.0.0" 893 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 894 | 895 | is-glob@^3.1.0: 896 | version "3.1.0" 897 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" 898 | dependencies: 899 | is-extglob "^2.1.0" 900 | 901 | is-glob@^4.0.0: 902 | version "4.0.0" 903 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" 904 | dependencies: 905 | is-extglob "^2.1.1" 906 | 907 | is-installed-globally@^0.1.0: 908 | version "0.1.0" 909 | resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" 910 | dependencies: 911 | global-dirs "^0.1.0" 912 | is-path-inside "^1.0.0" 913 | 914 | is-npm@^1.0.0: 915 | version "1.0.0" 916 | resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" 917 | 918 | is-number@^3.0.0: 919 | version "3.0.0" 920 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" 921 | dependencies: 922 | kind-of "^3.0.2" 923 | 924 | is-number@^4.0.0: 925 | version "4.0.0" 926 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" 927 | 928 | is-obj@^1.0.0: 929 | version "1.0.1" 930 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" 931 | 932 | is-odd@^2.0.0: 933 | version "2.0.0" 934 | resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" 935 | dependencies: 936 | is-number "^4.0.0" 937 | 938 | is-path-inside@^1.0.0: 939 | version "1.0.1" 940 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 941 | dependencies: 942 | path-is-inside "^1.0.1" 943 | 944 | is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: 945 | version "2.0.4" 946 | resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" 947 | dependencies: 948 | isobject "^3.0.1" 949 | 950 | is-redirect@^1.0.0: 951 | version "1.0.0" 952 | resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" 953 | 954 | is-retry-allowed@^1.0.0: 955 | version "1.1.0" 956 | resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" 957 | 958 | is-stream@^1.0.0, is-stream@^1.1.0: 959 | version "1.1.0" 960 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 961 | 962 | is-windows@^1.0.2: 963 | version "1.0.2" 964 | resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" 965 | 966 | isarray@0.0.1: 967 | version "0.0.1" 968 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 969 | 970 | isarray@1.0.0, isarray@~1.0.0: 971 | version "1.0.0" 972 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 973 | 974 | isarray@2.0.1: 975 | version "2.0.1" 976 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" 977 | 978 | isexe@^2.0.0: 979 | version "2.0.0" 980 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 981 | 982 | isobject@^2.0.0: 983 | version "2.1.0" 984 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" 985 | dependencies: 986 | isarray "1.0.0" 987 | 988 | isobject@^3.0.0, isobject@^3.0.1: 989 | version "3.0.1" 990 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" 991 | 992 | js-tokens@^3.0.2: 993 | version "3.0.2" 994 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 995 | 996 | js-yaml@^3.7.0: 997 | version "3.12.0" 998 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" 999 | dependencies: 1000 | argparse "^1.0.7" 1001 | esprima "^4.0.0" 1002 | 1003 | kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: 1004 | version "3.2.2" 1005 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" 1006 | dependencies: 1007 | is-buffer "^1.1.5" 1008 | 1009 | kind-of@^4.0.0: 1010 | version "4.0.0" 1011 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" 1012 | dependencies: 1013 | is-buffer "^1.1.5" 1014 | 1015 | kind-of@^5.0.0: 1016 | version "5.1.0" 1017 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" 1018 | 1019 | kind-of@^6.0.0, kind-of@^6.0.2: 1020 | version "6.0.2" 1021 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" 1022 | 1023 | latest-version@^3.0.0: 1024 | version "3.1.0" 1025 | resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" 1026 | dependencies: 1027 | package-json "^4.0.0" 1028 | 1029 | lowercase-keys@^1.0.0: 1030 | version "1.0.1" 1031 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 1032 | 1033 | lru-cache@^4.0.1: 1034 | version "4.1.3" 1035 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" 1036 | dependencies: 1037 | pseudomap "^1.0.2" 1038 | yallist "^2.1.2" 1039 | 1040 | make-dir@^1.0.0: 1041 | version "1.3.0" 1042 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" 1043 | dependencies: 1044 | pify "^3.0.0" 1045 | 1046 | make-error@^1.1.1: 1047 | version "1.3.4" 1048 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535" 1049 | 1050 | map-cache@^0.2.2: 1051 | version "0.2.2" 1052 | resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" 1053 | 1054 | map-stream@~0.1.0: 1055 | version "0.1.0" 1056 | resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" 1057 | 1058 | map-visit@^1.0.0: 1059 | version "1.0.0" 1060 | resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" 1061 | dependencies: 1062 | object-visit "^1.0.0" 1063 | 1064 | micromatch@^3.1.4: 1065 | version "3.1.10" 1066 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" 1067 | dependencies: 1068 | arr-diff "^4.0.0" 1069 | array-unique "^0.3.2" 1070 | braces "^2.3.1" 1071 | define-property "^2.0.2" 1072 | extend-shallow "^3.0.2" 1073 | extglob "^2.0.4" 1074 | fragment-cache "^0.2.1" 1075 | kind-of "^6.0.2" 1076 | nanomatch "^1.2.9" 1077 | object.pick "^1.3.0" 1078 | regex-not "^1.0.0" 1079 | snapdragon "^0.8.1" 1080 | to-regex "^3.0.2" 1081 | 1082 | mime-db@~1.33.0: 1083 | version "1.33.0" 1084 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" 1085 | 1086 | mime-types@~2.1.18: 1087 | version "2.1.18" 1088 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" 1089 | dependencies: 1090 | mime-db "~1.33.0" 1091 | 1092 | minimatch@^3.0.2, minimatch@^3.0.4: 1093 | version "3.0.4" 1094 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 1095 | dependencies: 1096 | brace-expansion "^1.1.7" 1097 | 1098 | minimist@0.0.8: 1099 | version "0.0.8" 1100 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 1101 | 1102 | minimist@^1.2.0: 1103 | version "1.2.0" 1104 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 1105 | 1106 | minipass@^2.2.1, minipass@^2.3.3: 1107 | version "2.3.3" 1108 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" 1109 | dependencies: 1110 | safe-buffer "^5.1.2" 1111 | yallist "^3.0.0" 1112 | 1113 | minizlib@^1.1.0: 1114 | version "1.1.0" 1115 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" 1116 | dependencies: 1117 | minipass "^2.2.1" 1118 | 1119 | mixin-deep@^1.2.0: 1120 | version "1.3.1" 1121 | resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" 1122 | dependencies: 1123 | for-in "^1.0.2" 1124 | is-extendable "^1.0.1" 1125 | 1126 | mkdirp@^0.5.0, mkdirp@^0.5.1: 1127 | version "0.5.1" 1128 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 1129 | dependencies: 1130 | minimist "0.0.8" 1131 | 1132 | ms@2.0.0: 1133 | version "2.0.0" 1134 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1135 | 1136 | nan@^2.8.0, nan@^2.9.2: 1137 | version "2.10.0" 1138 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" 1139 | 1140 | nanomatch@^1.2.9: 1141 | version "1.2.9" 1142 | resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" 1143 | dependencies: 1144 | arr-diff "^4.0.0" 1145 | array-unique "^0.3.2" 1146 | define-property "^2.0.2" 1147 | extend-shallow "^3.0.2" 1148 | fragment-cache "^0.2.1" 1149 | is-odd "^2.0.0" 1150 | is-windows "^1.0.2" 1151 | kind-of "^6.0.2" 1152 | object.pick "^1.3.0" 1153 | regex-not "^1.0.0" 1154 | snapdragon "^0.8.1" 1155 | to-regex "^3.0.1" 1156 | 1157 | needle@^2.2.0: 1158 | version "2.2.1" 1159 | resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" 1160 | dependencies: 1161 | debug "^2.1.2" 1162 | iconv-lite "^0.4.4" 1163 | sax "^1.2.4" 1164 | 1165 | negotiator@0.6.1: 1166 | version "0.6.1" 1167 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 1168 | 1169 | node-pre-gyp@^0.10.0: 1170 | version "0.10.0" 1171 | resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" 1172 | dependencies: 1173 | detect-libc "^1.0.2" 1174 | mkdirp "^0.5.1" 1175 | needle "^2.2.0" 1176 | nopt "^4.0.1" 1177 | npm-packlist "^1.1.6" 1178 | npmlog "^4.0.2" 1179 | rc "^1.1.7" 1180 | rimraf "^2.6.1" 1181 | semver "^5.3.0" 1182 | tar "^4" 1183 | 1184 | nodemon@^1.17.5: 1185 | version "1.17.5" 1186 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.17.5.tgz#e6a665c872fdf09d48bf2a81f3e85f8cfb39322a" 1187 | dependencies: 1188 | chokidar "^2.0.2" 1189 | debug "^3.1.0" 1190 | ignore-by-default "^1.0.1" 1191 | minimatch "^3.0.4" 1192 | pstree.remy "^1.1.0" 1193 | semver "^5.5.0" 1194 | supports-color "^5.2.0" 1195 | touch "^3.1.0" 1196 | undefsafe "^2.0.2" 1197 | update-notifier "^2.3.0" 1198 | 1199 | nopt@^4.0.1: 1200 | version "4.0.1" 1201 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" 1202 | dependencies: 1203 | abbrev "1" 1204 | osenv "^0.1.4" 1205 | 1206 | nopt@~1.0.10: 1207 | version "1.0.10" 1208 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 1209 | dependencies: 1210 | abbrev "1" 1211 | 1212 | normalize-path@^2.1.1: 1213 | version "2.1.1" 1214 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" 1215 | dependencies: 1216 | remove-trailing-separator "^1.0.1" 1217 | 1218 | npm-bundled@^1.0.1: 1219 | version "1.0.3" 1220 | resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" 1221 | 1222 | npm-packlist@^1.1.6: 1223 | version "1.1.10" 1224 | resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" 1225 | dependencies: 1226 | ignore-walk "^3.0.1" 1227 | npm-bundled "^1.0.1" 1228 | 1229 | npm-run-path@^2.0.0: 1230 | version "2.0.2" 1231 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 1232 | dependencies: 1233 | path-key "^2.0.0" 1234 | 1235 | npmlog@^4.0.2: 1236 | version "4.1.2" 1237 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 1238 | dependencies: 1239 | are-we-there-yet "~1.1.2" 1240 | console-control-strings "~1.1.0" 1241 | gauge "~2.7.3" 1242 | set-blocking "~2.0.0" 1243 | 1244 | number-is-nan@^1.0.0: 1245 | version "1.0.1" 1246 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 1247 | 1248 | object-assign@^4.1.0: 1249 | version "4.1.1" 1250 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 1251 | 1252 | object-component@0.0.3: 1253 | version "0.0.3" 1254 | resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" 1255 | 1256 | object-copy@^0.1.0: 1257 | version "0.1.0" 1258 | resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" 1259 | dependencies: 1260 | copy-descriptor "^0.1.0" 1261 | define-property "^0.2.5" 1262 | kind-of "^3.0.3" 1263 | 1264 | object-visit@^1.0.0: 1265 | version "1.0.1" 1266 | resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" 1267 | dependencies: 1268 | isobject "^3.0.0" 1269 | 1270 | object.pick@^1.3.0: 1271 | version "1.3.0" 1272 | resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" 1273 | dependencies: 1274 | isobject "^3.0.1" 1275 | 1276 | once@^1.3.0: 1277 | version "1.4.0" 1278 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1279 | dependencies: 1280 | wrappy "1" 1281 | 1282 | os-homedir@^1.0.0: 1283 | version "1.0.2" 1284 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 1285 | 1286 | os-tmpdir@^1.0.0: 1287 | version "1.0.2" 1288 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 1289 | 1290 | osenv@^0.1.4: 1291 | version "0.1.5" 1292 | resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" 1293 | dependencies: 1294 | os-homedir "^1.0.0" 1295 | os-tmpdir "^1.0.0" 1296 | 1297 | p-finally@^1.0.0: 1298 | version "1.0.0" 1299 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 1300 | 1301 | package-json@^4.0.0: 1302 | version "4.0.1" 1303 | resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" 1304 | dependencies: 1305 | got "^6.7.1" 1306 | registry-auth-token "^3.0.1" 1307 | registry-url "^3.0.3" 1308 | semver "^5.1.0" 1309 | 1310 | parseqs@0.0.5: 1311 | version "0.0.5" 1312 | resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" 1313 | dependencies: 1314 | better-assert "~1.0.0" 1315 | 1316 | parseuri@0.0.5: 1317 | version "0.0.5" 1318 | resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" 1319 | dependencies: 1320 | better-assert "~1.0.0" 1321 | 1322 | pascalcase@^0.1.1: 1323 | version "0.1.1" 1324 | resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" 1325 | 1326 | path-dirname@^1.0.0: 1327 | version "1.0.2" 1328 | resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" 1329 | 1330 | path-is-absolute@^1.0.0: 1331 | version "1.0.1" 1332 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1333 | 1334 | path-is-inside@^1.0.1: 1335 | version "1.0.2" 1336 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 1337 | 1338 | path-key@^2.0.0: 1339 | version "2.0.1" 1340 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 1341 | 1342 | path-parse@^1.0.5: 1343 | version "1.0.5" 1344 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" 1345 | 1346 | pause-stream@0.0.11: 1347 | version "0.0.11" 1348 | resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" 1349 | dependencies: 1350 | through "~2.3" 1351 | 1352 | pify@^3.0.0: 1353 | version "3.0.0" 1354 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 1355 | 1356 | posix-character-classes@^0.1.0: 1357 | version "0.1.1" 1358 | resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" 1359 | 1360 | prepend-http@^1.0.1: 1361 | version "1.0.4" 1362 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" 1363 | 1364 | process-nextick-args@~2.0.0: 1365 | version "2.0.0" 1366 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 1367 | 1368 | ps-tree@^1.1.0: 1369 | version "1.1.0" 1370 | resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014" 1371 | dependencies: 1372 | event-stream "~3.3.0" 1373 | 1374 | pseudomap@^1.0.2: 1375 | version "1.0.2" 1376 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 1377 | 1378 | pstree.remy@^1.1.0: 1379 | version "1.1.0" 1380 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.0.tgz#f2af27265bd3e5b32bbfcc10e80bac55ba78688b" 1381 | dependencies: 1382 | ps-tree "^1.1.0" 1383 | 1384 | randomcolor@^0.5.3: 1385 | version "0.5.3" 1386 | resolved "https://registry.npmjs.org/randomcolor/-/randomcolor-0.5.3.tgz#7f90f2f2a7f6d5a52232161eeaeeaea9ac3b5815" 1387 | 1388 | rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: 1389 | version "1.2.8" 1390 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 1391 | dependencies: 1392 | deep-extend "^0.6.0" 1393 | ini "~1.3.0" 1394 | minimist "^1.2.0" 1395 | strip-json-comments "~2.0.1" 1396 | 1397 | readable-stream@^2.0.2, readable-stream@^2.0.6: 1398 | version "2.3.6" 1399 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 1400 | dependencies: 1401 | core-util-is "~1.0.0" 1402 | inherits "~2.0.3" 1403 | isarray "~1.0.0" 1404 | process-nextick-args "~2.0.0" 1405 | safe-buffer "~5.1.1" 1406 | string_decoder "~1.1.1" 1407 | util-deprecate "~1.0.1" 1408 | 1409 | readdirp@^2.0.0: 1410 | version "2.1.0" 1411 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" 1412 | dependencies: 1413 | graceful-fs "^4.1.2" 1414 | minimatch "^3.0.2" 1415 | readable-stream "^2.0.2" 1416 | set-immediate-shim "^1.0.1" 1417 | 1418 | reflect-metadata@^0.1.12: 1419 | version "0.1.12" 1420 | resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" 1421 | 1422 | regex-not@^1.0.0, regex-not@^1.0.2: 1423 | version "1.0.2" 1424 | resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" 1425 | dependencies: 1426 | extend-shallow "^3.0.2" 1427 | safe-regex "^1.1.0" 1428 | 1429 | registry-auth-token@^3.0.1: 1430 | version "3.3.2" 1431 | resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" 1432 | dependencies: 1433 | rc "^1.1.6" 1434 | safe-buffer "^5.0.1" 1435 | 1436 | registry-url@^3.0.3: 1437 | version "3.1.0" 1438 | resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" 1439 | dependencies: 1440 | rc "^1.0.1" 1441 | 1442 | remove-trailing-separator@^1.0.1: 1443 | version "1.1.0" 1444 | resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" 1445 | 1446 | repeat-element@^1.1.2: 1447 | version "1.1.2" 1448 | resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" 1449 | 1450 | repeat-string@^1.6.1: 1451 | version "1.6.1" 1452 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 1453 | 1454 | resolve-url@^0.2.1: 1455 | version "0.2.1" 1456 | resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" 1457 | 1458 | resolve@^1.3.2: 1459 | version "1.7.1" 1460 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" 1461 | dependencies: 1462 | path-parse "^1.0.5" 1463 | 1464 | ret@~0.1.10: 1465 | version "0.1.15" 1466 | resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" 1467 | 1468 | rimraf@^2.6.1: 1469 | version "2.6.2" 1470 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" 1471 | dependencies: 1472 | glob "^7.0.5" 1473 | 1474 | safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 1475 | version "5.1.2" 1476 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 1477 | 1478 | safe-regex@^1.1.0: 1479 | version "1.1.0" 1480 | resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" 1481 | dependencies: 1482 | ret "~0.1.10" 1483 | 1484 | "safer-buffer@>= 2.1.2 < 3": 1485 | version "2.1.2" 1486 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1487 | 1488 | sax@^1.2.4: 1489 | version "1.2.4" 1490 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 1491 | 1492 | schemapack@^1.4.2: 1493 | version "1.4.2" 1494 | resolved "https://registry.npmjs.org/schemapack/-/schemapack-1.4.2.tgz#8b582a55e128e345854ce3fb380371c58264f349" 1495 | 1496 | semver-diff@^2.0.0: 1497 | version "2.1.0" 1498 | resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 1499 | dependencies: 1500 | semver "^5.0.3" 1501 | 1502 | semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: 1503 | version "5.5.0" 1504 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" 1505 | 1506 | set-blocking@~2.0.0: 1507 | version "2.0.0" 1508 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1509 | 1510 | set-immediate-shim@^1.0.1: 1511 | version "1.0.1" 1512 | resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" 1513 | 1514 | set-value@^0.4.3: 1515 | version "0.4.3" 1516 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" 1517 | dependencies: 1518 | extend-shallow "^2.0.1" 1519 | is-extendable "^0.1.1" 1520 | is-plain-object "^2.0.1" 1521 | to-object-path "^0.3.0" 1522 | 1523 | set-value@^2.0.0: 1524 | version "2.0.0" 1525 | resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" 1526 | dependencies: 1527 | extend-shallow "^2.0.1" 1528 | is-extendable "^0.1.1" 1529 | is-plain-object "^2.0.3" 1530 | split-string "^3.0.1" 1531 | 1532 | shebang-command@^1.2.0: 1533 | version "1.2.0" 1534 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 1535 | dependencies: 1536 | shebang-regex "^1.0.0" 1537 | 1538 | shebang-regex@^1.0.0: 1539 | version "1.0.0" 1540 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 1541 | 1542 | shortid@^2.2.8: 1543 | version "2.2.8" 1544 | resolved "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" 1545 | 1546 | signal-exit@^3.0.0, signal-exit@^3.0.2: 1547 | version "3.0.2" 1548 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1549 | 1550 | simplify-js@^1.2.3: 1551 | version "1.2.3" 1552 | resolved "https://registry.npmjs.org/simplify-js/-/simplify-js-1.2.3.tgz#a3422c1b9884d60421345eb44d2b872662df27f5" 1553 | 1554 | snapdragon-node@^2.0.1: 1555 | version "2.1.1" 1556 | resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" 1557 | dependencies: 1558 | define-property "^1.0.0" 1559 | isobject "^3.0.0" 1560 | snapdragon-util "^3.0.1" 1561 | 1562 | snapdragon-util@^3.0.1: 1563 | version "3.0.1" 1564 | resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" 1565 | dependencies: 1566 | kind-of "^3.2.0" 1567 | 1568 | snapdragon@^0.8.1: 1569 | version "0.8.2" 1570 | resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" 1571 | dependencies: 1572 | base "^0.11.1" 1573 | debug "^2.2.0" 1574 | define-property "^0.2.5" 1575 | extend-shallow "^2.0.1" 1576 | map-cache "^0.2.2" 1577 | source-map "^0.5.6" 1578 | source-map-resolve "^0.5.0" 1579 | use "^3.1.0" 1580 | 1581 | socket.io-adapter@~1.1.0: 1582 | version "1.1.1" 1583 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" 1584 | 1585 | socket.io-client@2.1.1: 1586 | version "2.1.1" 1587 | resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f" 1588 | dependencies: 1589 | backo2 "1.0.2" 1590 | base64-arraybuffer "0.1.5" 1591 | component-bind "1.0.0" 1592 | component-emitter "1.2.1" 1593 | debug "~3.1.0" 1594 | engine.io-client "~3.2.0" 1595 | has-binary2 "~1.0.2" 1596 | has-cors "1.1.0" 1597 | indexof "0.0.1" 1598 | object-component "0.0.3" 1599 | parseqs "0.0.5" 1600 | parseuri "0.0.5" 1601 | socket.io-parser "~3.2.0" 1602 | to-array "0.1.4" 1603 | 1604 | socket.io-parser@~3.2.0: 1605 | version "3.2.0" 1606 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077" 1607 | dependencies: 1608 | component-emitter "1.2.1" 1609 | debug "~3.1.0" 1610 | isarray "2.0.1" 1611 | 1612 | socket.io@^2.1.1: 1613 | version "2.1.1" 1614 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980" 1615 | dependencies: 1616 | debug "~3.1.0" 1617 | engine.io "~3.2.0" 1618 | has-binary2 "~1.0.2" 1619 | socket.io-adapter "~1.1.0" 1620 | socket.io-client "2.1.1" 1621 | socket.io-parser "~3.2.0" 1622 | 1623 | source-map-resolve@^0.5.0: 1624 | version "0.5.2" 1625 | resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" 1626 | dependencies: 1627 | atob "^2.1.1" 1628 | decode-uri-component "^0.2.0" 1629 | resolve-url "^0.2.1" 1630 | source-map-url "^0.4.0" 1631 | urix "^0.1.0" 1632 | 1633 | source-map-support@^0.5.6: 1634 | version "0.5.6" 1635 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" 1636 | dependencies: 1637 | buffer-from "^1.0.0" 1638 | source-map "^0.6.0" 1639 | 1640 | source-map-url@^0.4.0: 1641 | version "0.4.0" 1642 | resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" 1643 | 1644 | source-map@^0.5.6: 1645 | version "0.5.7" 1646 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1647 | 1648 | source-map@^0.6.0: 1649 | version "0.6.1" 1650 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1651 | 1652 | split-string@^3.0.1, split-string@^3.0.2: 1653 | version "3.1.0" 1654 | resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" 1655 | dependencies: 1656 | extend-shallow "^3.0.0" 1657 | 1658 | split@0.3: 1659 | version "0.3.3" 1660 | resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" 1661 | dependencies: 1662 | through "2" 1663 | 1664 | sprintf-js@~1.0.2: 1665 | version "1.0.3" 1666 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1667 | 1668 | static-extend@^0.1.1: 1669 | version "0.1.2" 1670 | resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" 1671 | dependencies: 1672 | define-property "^0.2.5" 1673 | object-copy "^0.1.0" 1674 | 1675 | stream-combiner@~0.0.4: 1676 | version "0.0.4" 1677 | resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" 1678 | dependencies: 1679 | duplexer "~0.1.1" 1680 | 1681 | string-width@^1.0.1: 1682 | version "1.0.2" 1683 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 1684 | dependencies: 1685 | code-point-at "^1.0.0" 1686 | is-fullwidth-code-point "^1.0.0" 1687 | strip-ansi "^3.0.0" 1688 | 1689 | "string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: 1690 | version "2.1.1" 1691 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1692 | dependencies: 1693 | is-fullwidth-code-point "^2.0.0" 1694 | strip-ansi "^4.0.0" 1695 | 1696 | string_decoder@~1.1.1: 1697 | version "1.1.1" 1698 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 1699 | dependencies: 1700 | safe-buffer "~5.1.0" 1701 | 1702 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 1703 | version "3.0.1" 1704 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 1705 | dependencies: 1706 | ansi-regex "^2.0.0" 1707 | 1708 | strip-ansi@^4.0.0: 1709 | version "4.0.0" 1710 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1711 | dependencies: 1712 | ansi-regex "^3.0.0" 1713 | 1714 | strip-eof@^1.0.0: 1715 | version "1.0.0" 1716 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 1717 | 1718 | strip-json-comments@~2.0.1: 1719 | version "2.0.1" 1720 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1721 | 1722 | supports-color@^2.0.0: 1723 | version "2.0.0" 1724 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 1725 | 1726 | supports-color@^5.2.0, supports-color@^5.3.0: 1727 | version "5.4.0" 1728 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" 1729 | dependencies: 1730 | has-flag "^3.0.0" 1731 | 1732 | tar@^4: 1733 | version "4.4.4" 1734 | resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" 1735 | dependencies: 1736 | chownr "^1.0.1" 1737 | fs-minipass "^1.2.5" 1738 | minipass "^2.3.3" 1739 | minizlib "^1.1.0" 1740 | mkdirp "^0.5.0" 1741 | safe-buffer "^5.1.2" 1742 | yallist "^3.0.2" 1743 | 1744 | term-size@^1.2.0: 1745 | version "1.2.0" 1746 | resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" 1747 | dependencies: 1748 | execa "^0.7.0" 1749 | 1750 | through@2, through@~2.3, through@~2.3.1: 1751 | version "2.3.8" 1752 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1753 | 1754 | timed-out@^4.0.0: 1755 | version "4.0.1" 1756 | resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 1757 | 1758 | to-array@0.1.4: 1759 | version "0.1.4" 1760 | resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" 1761 | 1762 | to-object-path@^0.3.0: 1763 | version "0.3.0" 1764 | resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" 1765 | dependencies: 1766 | kind-of "^3.0.2" 1767 | 1768 | to-regex-range@^2.1.0: 1769 | version "2.1.1" 1770 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" 1771 | dependencies: 1772 | is-number "^3.0.0" 1773 | repeat-string "^1.6.1" 1774 | 1775 | to-regex@^3.0.1, to-regex@^3.0.2: 1776 | version "3.0.2" 1777 | resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" 1778 | dependencies: 1779 | define-property "^2.0.2" 1780 | extend-shallow "^3.0.2" 1781 | regex-not "^1.0.2" 1782 | safe-regex "^1.1.0" 1783 | 1784 | touch@^3.1.0: 1785 | version "3.1.0" 1786 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 1787 | dependencies: 1788 | nopt "~1.0.10" 1789 | 1790 | ts-node@^6.0.4: 1791 | version "6.1.0" 1792 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.1.0.tgz#a2c37a11fdb58e60eca887a1269b025cf4d2f8b8" 1793 | dependencies: 1794 | arrify "^1.0.0" 1795 | diff "^3.1.0" 1796 | make-error "^1.1.1" 1797 | minimist "^1.2.0" 1798 | mkdirp "^0.5.1" 1799 | source-map-support "^0.5.6" 1800 | yn "^2.0.0" 1801 | 1802 | tslib@1.9.0: 1803 | version "1.9.0" 1804 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" 1805 | 1806 | tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: 1807 | version "1.9.2" 1808 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.2.tgz#8be0cc9a1f6dc7727c38deb16c2ebd1a2892988e" 1809 | 1810 | tslint-config-airbnb@^5.9.2: 1811 | version "5.9.2" 1812 | resolved "https://registry.yarnpkg.com/tslint-config-airbnb/-/tslint-config-airbnb-5.9.2.tgz#fdb37a695ec09b663d5ee92e7852a7a9337ec44c" 1813 | dependencies: 1814 | tslint-consistent-codestyle "^1.10.0" 1815 | tslint-eslint-rules "^5.3.1" 1816 | tslint-microsoft-contrib "~5.0.1" 1817 | 1818 | tslint-consistent-codestyle@^1.10.0: 1819 | version "1.13.1" 1820 | resolved "https://registry.yarnpkg.com/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.13.1.tgz#7996913453f369f7321c5b49776c2bcf837461aa" 1821 | dependencies: 1822 | "@fimbul/bifrost" "^0.9.0" 1823 | tslib "^1.7.1" 1824 | tsutils "^2.27.0" 1825 | 1826 | tslint-eslint-rules@^5.3.1: 1827 | version "5.3.1" 1828 | resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.3.1.tgz#10dec4361df0b3e4385d91ff8e0226bda4ec2ad4" 1829 | dependencies: 1830 | doctrine "0.7.2" 1831 | tslib "1.9.0" 1832 | tsutils "2.8.0" 1833 | 1834 | tslint-microsoft-contrib@~5.0.1: 1835 | version "5.0.3" 1836 | resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.0.3.tgz#6fc3e238179cd72045c2b422e4d655f4183a8d5c" 1837 | dependencies: 1838 | tsutils "^2.12.1" 1839 | 1840 | tslint@^5.10.0: 1841 | version "5.10.0" 1842 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.10.0.tgz#11e26bccb88afa02dd0d9956cae3d4540b5f54c3" 1843 | dependencies: 1844 | babel-code-frame "^6.22.0" 1845 | builtin-modules "^1.1.1" 1846 | chalk "^2.3.0" 1847 | commander "^2.12.1" 1848 | diff "^3.2.0" 1849 | glob "^7.1.1" 1850 | js-yaml "^3.7.0" 1851 | minimatch "^3.0.4" 1852 | resolve "^1.3.2" 1853 | semver "^5.3.0" 1854 | tslib "^1.8.0" 1855 | tsutils "^2.12.1" 1856 | 1857 | tsutils@2.8.0: 1858 | version "2.8.0" 1859 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.0.tgz#0160173729b3bf138628dd14a1537e00851d814a" 1860 | dependencies: 1861 | tslib "^1.7.1" 1862 | 1863 | tsutils@^2.12.1, tsutils@^2.24.0, tsutils@^2.27.0: 1864 | version "2.27.1" 1865 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.1.tgz#ab0276ac23664f36ce8fd4414daec4aebf4373ee" 1866 | dependencies: 1867 | tslib "^1.8.1" 1868 | 1869 | typescript@^2.8.3: 1870 | version "2.9.1" 1871 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961" 1872 | 1873 | ultron@~1.1.0: 1874 | version "1.1.1" 1875 | resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" 1876 | 1877 | undefsafe@^2.0.2: 1878 | version "2.0.2" 1879 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" 1880 | dependencies: 1881 | debug "^2.2.0" 1882 | 1883 | union-value@^1.0.0: 1884 | version "1.0.0" 1885 | resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" 1886 | dependencies: 1887 | arr-union "^3.1.0" 1888 | get-value "^2.0.6" 1889 | is-extendable "^0.1.1" 1890 | set-value "^0.4.3" 1891 | 1892 | unique-string@^1.0.0: 1893 | version "1.0.0" 1894 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" 1895 | dependencies: 1896 | crypto-random-string "^1.0.0" 1897 | 1898 | unset-value@^1.0.0: 1899 | version "1.0.0" 1900 | resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" 1901 | dependencies: 1902 | has-value "^0.3.1" 1903 | isobject "^3.0.0" 1904 | 1905 | unzip-response@^2.0.1: 1906 | version "2.0.1" 1907 | resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" 1908 | 1909 | upath@^1.0.0: 1910 | version "1.1.0" 1911 | resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" 1912 | 1913 | update-notifier@^2.3.0: 1914 | version "2.5.0" 1915 | resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" 1916 | dependencies: 1917 | boxen "^1.2.1" 1918 | chalk "^2.0.1" 1919 | configstore "^3.0.0" 1920 | import-lazy "^2.1.0" 1921 | is-ci "^1.0.10" 1922 | is-installed-globally "^0.1.0" 1923 | is-npm "^1.0.0" 1924 | latest-version "^3.0.0" 1925 | semver-diff "^2.0.0" 1926 | xdg-basedir "^3.0.0" 1927 | 1928 | urix@^0.1.0: 1929 | version "0.1.0" 1930 | resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" 1931 | 1932 | url-parse-lax@^1.0.0: 1933 | version "1.0.0" 1934 | resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" 1935 | dependencies: 1936 | prepend-http "^1.0.1" 1937 | 1938 | use@^3.1.0: 1939 | version "3.1.0" 1940 | resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" 1941 | dependencies: 1942 | kind-of "^6.0.2" 1943 | 1944 | util-deprecate@~1.0.1: 1945 | version "1.0.2" 1946 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1947 | 1948 | webworker-threads@^0.7.15: 1949 | version "0.7.15" 1950 | resolved "https://registry.npmjs.org/webworker-threads/-/webworker-threads-0.7.15.tgz#b7540a9d417f85debd1b2a86a46c26e93b6c6568" 1951 | dependencies: 1952 | bindings "^1.3.0" 1953 | nan "^2.8.0" 1954 | 1955 | which@^1.2.9: 1956 | version "1.3.1" 1957 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1958 | dependencies: 1959 | isexe "^2.0.0" 1960 | 1961 | wide-align@^1.1.0: 1962 | version "1.1.3" 1963 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 1964 | dependencies: 1965 | string-width "^1.0.2 || 2" 1966 | 1967 | widest-line@^2.0.0: 1968 | version "2.0.0" 1969 | resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" 1970 | dependencies: 1971 | string-width "^2.1.1" 1972 | 1973 | wrappy@1: 1974 | version "1.0.2" 1975 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1976 | 1977 | write-file-atomic@^2.0.0: 1978 | version "2.3.0" 1979 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" 1980 | dependencies: 1981 | graceful-fs "^4.1.11" 1982 | imurmurhash "^0.1.4" 1983 | signal-exit "^3.0.2" 1984 | 1985 | ws@~3.3.1: 1986 | version "3.3.3" 1987 | resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" 1988 | dependencies: 1989 | async-limiter "~1.0.0" 1990 | safe-buffer "~5.1.0" 1991 | ultron "~1.1.0" 1992 | 1993 | xdg-basedir@^3.0.0: 1994 | version "3.0.0" 1995 | resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" 1996 | 1997 | xmlhttprequest-ssl@~1.5.4: 1998 | version "1.5.5" 1999 | resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" 2000 | 2001 | yallist@^2.1.2: 2002 | version "2.1.2" 2003 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 2004 | 2005 | yallist@^3.0.0, yallist@^3.0.2: 2006 | version "3.0.2" 2007 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" 2008 | 2009 | yeast@0.1.2: 2010 | version "0.1.2" 2011 | resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" 2012 | 2013 | yn@^2.0.0: 2014 | version "2.0.0" 2015 | resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" 2016 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | reverse-proxy: 5 | restart: always 6 | image: traefik:latest 7 | command: --api --docker 8 | networks: 9 | - web 10 | ports: 11 | - "${EB_TRAEFIK_PORT}:80" 12 | - "8080:8080" 13 | volumes: 14 | - /var/run/docker.sock:/var/run/docker.sock 15 | 16 | mongodb: 17 | image: mongo:latest 18 | networks: 19 | - web 20 | ports: 21 | - "27017:27017" 22 | volumes: 23 | - ./mongodb:/var/lib/mongodb/data 24 | 25 | orchestrator: 26 | build: ./orchestrator 27 | networks: 28 | - web 29 | ports: 30 | - "9000:9000" 31 | labels: 32 | - "traefik.frontend.rule=Host:orchestrator.${EB_DOMAIN_NAME}" 33 | environment: 34 | - EB_DOMAIN_NAME=${EB_DOMAIN_NAME} 35 | - EB_ORCHESTRATOR_PORT=${EB_ORCHESTRATOR_PORT} 36 | - EB_ORCHESTRATOR_SECRET=${EB_ORCHESTRATOR_SECRET} 37 | - EB_SERVER_DOCKER_IMAGE=${EB_SERVER_DOCKER_IMAGE} 38 | - EB_MAX_CLIENTS_PER_SERVER=${EB_MAX_CLIENTS_PER_SERVER} 39 | - EB_DEFAULT_SERVERS_NB=${EB_DEFAULT_SERVERS_NB} 40 | - EB_MONGO_HOST=${EB_MONGO_HOST} 41 | volumes: 42 | - /var/run/docker.sock:/var/run/docker.sock 43 | depends_on: 44 | - reverse-proxy 45 | - mongodb 46 | 47 | backend: 48 | build: ./back 49 | 50 | frontend: 51 | build: ./front 52 | 53 | networks: 54 | web: 55 | external: 56 | name: traefik_default 57 | -------------------------------------------------------------------------------- /front/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:react/recommended"], 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "experimentalObjectRestSpread": true, 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["react"], 15 | "rules": { 16 | "comma-dangle": ["error", "always-multiline"], 17 | "react/prop-types": 0, 18 | "no-var": "error", 19 | "indent": [ 20 | "error", 21 | 2, 22 | { 23 | "SwitchCase": 1 24 | } 25 | ], 26 | "linebreak-style": ["error", "unix"], 27 | "quotes": ["error", "single"], 28 | "semi": ["error", "always"], 29 | "no-console": "warn" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /front/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN curl -o- -L https://yarnpkg.com/install.sh | bash 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN yarn install 10 | 11 | RUN yarn build 12 | -------------------------------------------------------------------------------- /front/README.md: -------------------------------------------------------------------------------- 1 | # Effective Barnacle Client 2 | 3 | ... 4 | -------------------------------------------------------------------------------- /front/middleware/websocket.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedthejim/effective-barnacle/71d50328be501e9383631cb868913f257e7bdb2b/front/middleware/websocket.js -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "bufferutil": "^3.0.5", 8 | "konva": "^2.1.3", 9 | "ramda": "^0.25.0", 10 | "random-username-generator": "^1.0.4", 11 | "react": "^16.3.2", 12 | "react-countdown-now": "^1.3.0", 13 | "react-dom": "^16.3.2", 14 | "react-konva": "^1.7.3", 15 | "react-redux": "^5.0.7", 16 | "react-scripts": "1.1.4", 17 | "redux": "^4.0.0", 18 | "redux-devtools-extension": "^2.13.2", 19 | "redux-saga": "^0.16.0", 20 | "schemapack": "^1.4.2", 21 | "socket.io-client": "^2.1.1", 22 | "socket.io-msgpack-parser": "^2.2.0", 23 | "utf-8-validate": "^4.0.2" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test --env=jsdom", 29 | "eject": "react-scripts eject" 30 | }, 31 | "devDependencies": { 32 | "eslint": "^4.19.1", 33 | "eslint-plugin-react": "^7.8.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /front/public/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Avenir", Helvetica, Arial, sans-serif; 3 | } 4 | 5 | * { 6 | margin:0; 7 | } 8 | 9 | #footer { 10 | text-align: center; 11 | } 12 | 13 | #footer a { 14 | color: #3897F0; 15 | text-decoration: none; 16 | } 17 | 18 | .panels { 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | height: 90vh; 23 | text-align: center; 24 | z-index: 5; 25 | position: relative; 26 | } 27 | 28 | .panels-wrapper:before { 29 | content: ''; 30 | position: absolute; 31 | width: 100%; 32 | height: 70%; 33 | background-image: linear-gradient(to top, #f5f4f3, #fff); 34 | left: 0; 35 | top: -80px; 36 | z-index: 0; 37 | } 38 | 39 | .panels-wrapper:after { 40 | content: ''; 41 | display: block; 42 | position: absolute; 43 | width: 100%; 44 | height: 160px; 45 | background: white; 46 | -ms-transform: skewY(-5deg); 47 | transform: skewY(-5deg); 48 | z-index: 0; 49 | top: 70%; 50 | margin-top: -160px; 51 | left: 0; 52 | z-index: 0; 53 | } 54 | 55 | .panel { 56 | flex-basis: 20%; 57 | border-radius: 6px; 58 | box-shadow: 0 5px 15px rgba(112,128,175,0.7); 59 | padding: 40px 10px; 60 | position: relative; 61 | background-color: white; 62 | } 63 | 64 | .panel-button { 65 | background-color: #3897F0; 66 | padding: 10px; 67 | width: 70%; 68 | color: white; 69 | border-radius: 6px; 70 | border: none; 71 | cursor: pointer; 72 | margin: 20px 0; 73 | } 74 | 75 | .panel-button:hover { 76 | background-color: rgb(15, 79, 139); 77 | } 78 | 79 | .panel input { 80 | display: block; 81 | margin-top: 10px !important; 82 | padding: 10px; 83 | border: 2px solid #eeee; 84 | border-radius: 4px; 85 | margin: 0 auto; 86 | } 87 | 88 | .panel-respawn { 89 | margin: 15px 0; 90 | } 91 | 92 | .panel-coutdown { 93 | font-size: 60px; 94 | } 95 | -------------------------------------------------------------------------------- /front/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fill 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /front/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Effective Barnacle 12 | 13 | 14 | 15 | 18 | 19 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /front/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /front/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Canvas from './components/Canvas'; 3 | import Home from './components/Home'; 4 | import { connect } from 'react-redux'; 5 | import Countdown from 'react-countdown-now'; 6 | import { WEBSOCKET_CONNECT, WEBSOCKET_CANCEL } from './actions/websocket'; 7 | import rug from 'random-username-generator'; 8 | 9 | const mapStateToProps = state => { 10 | return { 11 | isGameRunning: state.canvas.isGameRunning, 12 | gameOver: state.canvas.gameOver, 13 | }; 14 | }; 15 | 16 | const mapDispatchToProps = dispatch => { 17 | return { 18 | disconnect: () => 19 | dispatch({ 20 | type: WEBSOCKET_CANCEL, 21 | }), 22 | connect: username => 23 | dispatch({ 24 | type: WEBSOCKET_CONNECT, 25 | username, 26 | }), 27 | }; 28 | }; 29 | 30 | class App extends React.Component { 31 | constructor() { 32 | super(); 33 | this.state = { 34 | value: rug.generate(), 35 | }; 36 | } 37 | 38 | handleChange(event) { 39 | this.setState({ value: event.target.value }); 40 | } 41 | 42 | click() { 43 | this.props.disconnect(); 44 | this.props.connect(this.state.value.substring(0, 40) || rug.generate()); 45 | } 46 | 47 | render() { 48 | return this.props.isGameRunning ? ( 49 | this.props.gameOver ? ( 50 | 51 |

Game over!

52 |

Respawn in

53 | this.click()} 55 | date={Date.now() + 5000} 56 | zeroPadLength={1} 57 | renderer={({ seconds }) => { 58 | return

{seconds}

; 59 | }} 60 | /> 61 |
62 | ) : ( 63 | 64 | ) 65 | ) : ( 66 |
{ 68 | if (event.key === 'Enter') { 69 | this.click(); 70 | } 71 | }} 72 | className="panels-wrapper" 73 | > 74 | 75 |

Enter your nickname & play !

76 | 77 | this.handleChange(event)} 82 | autoFocus 83 | /> 84 | 85 | 91 |
92 |
93 | ); 94 | } 95 | } 96 | 97 | export default connect( 98 | mapStateToProps, 99 | mapDispatchToProps 100 | )(App); 101 | -------------------------------------------------------------------------------- /front/src/Test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { createStore, combineReducers } from 'redux'; 4 | import * as R from 'ramda'; 5 | 6 | // composition helper 7 | const combine = R.curry((c, o) => x => (
{c(x)} {o(x)}
)); 8 | const combineComponents = (...args) => { 9 | const [first, ...rest] = args; 10 | return R.reduce((acc, c) => combine(acc, c), first, rest); 11 | }; 12 | 13 | // helpers 14 | const targetValue = e => e.target.value; 15 | const getTodos = R.prop('todos'); 16 | 17 | // redux utils 18 | const createReducer = (init, handlers) => 19 | (state = init, action) => 20 | R.propOr(R.identity, R.prop('type', action), handlers)(state, action); 21 | 22 | const addOne = R.add(1); 23 | const getAllIds = R.pluck('id'); 24 | const getMax = R.reduce(R.max, 0); 25 | const getNextId = R.compose(addOne, getMax, getAllIds); 26 | 27 | // constants 28 | const ADD_TODO = 'ADD_TODO'; 29 | const DELETE_TODO = 'DELETE_TODO'; 30 | 31 | // actions 32 | const addTodo = text => ({ type: ADD_TODO, text }); 33 | const deleteTodo = id => ({ type: DELETE_TODO, id }); 34 | 35 | // reducers 36 | const todos = createReducer([], { 37 | [ADD_TODO]: (state, action) => [ 38 | { id: getNextId(state), completed: false, text: action.text }, 39 | ...state 40 | ], 41 | [DELETE_TODO]: (state, action) => R.reject(R.propEq('id', action.id), state), 42 | }); 43 | 44 | const year = createReducer('', {}); 45 | const title = createReducer('', {}); 46 | 47 | // combine reducer and create store 48 | const reducers = combineReducers({ todos, year, title }); 49 | 50 | const initialState = { 51 | year: '2016', 52 | title: 'Random stuff', 53 | todos: [{ id: 1, text: 'foo' }, { id: 2, text: 'bar' }] 54 | }; 55 | 56 | const store = createStore(reducers, initialState); 57 | 58 | // components 59 | const Header = title =>

A Todo List: {title}

; 60 | const Add = ({ onSave }) => ( 61 |
62 | 63 |
64 | ); 65 | const List = items =>
    {items}
; 66 | const Item = ({ todo, onDelete }) => ( 67 |
  • 68 | {todo.text} 69 |
  • 70 | ); 71 | const Footer = text =>
    {text}
    ; 72 | 73 | // define a bindActionCreator 74 | const bindAction = R.curry((dispatch, actionCreator) => 75 | R.compose(dispatch, actionCreator)); 76 | 77 | const bindActionCreator = bindAction(store.dispatch); 78 | 79 | // map state to props 80 | const TodoHeader = R.pipe(props => props.title, Header); 81 | 82 | const TodoAdd = R.pipe(props => ({ onSave: props.dispatch(addTodo) }), Add); 83 | 84 | const mapItems = ({ todos, onDelete }) => 85 | R.map(todo => Item({ todo, onDelete }), todos); 86 | 87 | const TodoList = R.pipe(props => 88 | ({ todos: props.todos, onDelete: props.dispatch(deleteTodo) }), 89 | R.compose(List, mapItems) 90 | ); 91 | const TodoFooter = R.pipe(props => props.year, Footer); 92 | 93 | // combine all components 94 | const App = combineComponents(TodoHeader, TodoAdd, TodoList, TodoFooter); 95 | 96 | // we could also have used curry... 97 | const getRender = node => app => ReactDOM.render(app, node); 98 | const render = getRender(document.getElementById('root')); 99 | 100 | const run = store.subscribe(() => 101 | render()); 102 | 103 | // start 104 | const init = store.dispatch({ type: '@@INIT' }); -------------------------------------------------------------------------------- /front/src/actions/canvas.js: -------------------------------------------------------------------------------- 1 | export const ADD_COLORED_RECT = 'ADD_COLORED_RECT'; 2 | -------------------------------------------------------------------------------- /front/src/actions/mousemove.js: -------------------------------------------------------------------------------- 1 | export const MOUSE_MOVE = 'MOUSE_MOVE'; 2 | export const PLAYER_MOVE = 'PLAYER_MOVE'; 3 | export const PLAYER_SPEED_UP = 'PLAYER_SPEED_UP'; 4 | export const PLAYER_SPEED_DOWN = 'PLAYER_SPEED_DOWN'; 5 | 6 | export const mouseMove = (x, y) => { 7 | return { 8 | type: MOUSE_MOVE, 9 | x, 10 | y, 11 | }; 12 | }; 13 | 14 | export const playerMove = (x, y) => { 15 | return { 16 | type: PLAYER_MOVE, 17 | x, 18 | y, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /front/src/actions/player.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedthejim/effective-barnacle/71d50328be501e9383631cb868913f257e7bdb2b/front/src/actions/player.js -------------------------------------------------------------------------------- /front/src/actions/websocket.js: -------------------------------------------------------------------------------- 1 | export const WEBSOCKET_CONNECT = 'WEBSOCKET_CONNECT'; 2 | export const WEBSOCKET_CANCEL = 'WEBSOCKET_CANCEL'; 3 | export const WEBSOCKET_REGISTER_SUCCESS = 'WEBSOCKET_REGISTER_SUCCESS'; 4 | export const WEBSOCKET_GAME_UPDATE = 'WEBSOCKET_GAME_UPDATE'; 5 | 6 | export const connectToServer = (url, username) => { 7 | return { 8 | type: WEBSOCKET_CONNECT, 9 | username, 10 | }; 11 | }; 12 | 13 | export const registerSuccess = player => { 14 | return { 15 | type: WEBSOCKET_REGISTER_SUCCESS, 16 | player, 17 | }; 18 | }; 19 | 20 | export const updateGameState = data => { 21 | return { 22 | type: WEBSOCKET_GAME_UPDATE, 23 | players: data.snakes, 24 | foods: data.foods, 25 | }; 26 | }; 27 | 28 | export const webSocketActions = { 29 | connectToServer, 30 | registerSuccess, 31 | updateGameState, 32 | }; 33 | -------------------------------------------------------------------------------- /front/src/components/Background.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Rect, Group } from 'react-konva'; 3 | import { connect } from 'react-redux'; 4 | 5 | const mapStateToProps = state => { 6 | return { 7 | gameMap: state.canvas.gameMap, 8 | }; 9 | }; 10 | 11 | const mapDispatchToProps = (/* dispatch*/) => { 12 | return {}; 13 | }; 14 | 15 | const MAP_RECT_WIDTH = 150; 16 | const MAP_RECT_HEIGHT = 150; 17 | const TILE_RATIO = 1; 18 | 19 | class Tiles extends React.Component { 20 | shouldComponentUpdate() { 21 | return false; 22 | } 23 | render() { 24 | return this.props.tiles.map((shape, index) => ( 25 | 34 | )); 35 | } 36 | } 37 | 38 | class Background extends React.PureComponent { 39 | constructor(props) { 40 | super(props); 41 | 42 | const shapes = []; 43 | const width = MAP_RECT_WIDTH * 10 * TILE_RATIO; 44 | const height = MAP_RECT_HEIGHT * 10 * TILE_RATIO; 45 | const mrw = MAP_RECT_WIDTH / TILE_RATIO; 46 | const mrh = MAP_RECT_HEIGHT / TILE_RATIO; 47 | const colors = ['#ccc', '#999']; 48 | 49 | for (let x = 0, i = 0; x <= width; x += mrw, i++) { 50 | let color = colors[i % 2]; 51 | for (let y = 0; y <= height; y += mrh) { 52 | const cx = width - x; 53 | const cy = height - y; 54 | const w = cx < mrw ? cx : mrw; 55 | const h = cy < mrh ? cy : mrh; 56 | shapes.push({ x, y, h, w, color }); 57 | color = color === colors[0] ? colors[1] : colors[0]; 58 | } 59 | } 60 | this.state = { 61 | shapes, 62 | }; 63 | } 64 | 65 | relative(value) { 66 | return value / this.props.gameMap.scale; 67 | } 68 | 69 | render() { 70 | const gameMap = this.props.gameMap; 71 | const view = gameMap.view; 72 | 73 | const width = MAP_RECT_WIDTH * 10 * TILE_RATIO; 74 | const height = MAP_RECT_HEIGHT * 10 * TILE_RATIO; 75 | 76 | const tileWidth = this.relative(width); 77 | const tileHeigth = this.relative(height); 78 | 79 | const beginX = view.x < 0 ? -view.x : -view.x % tileWidth; 80 | const beginY = view.y < 0 ? -view.y : -view.y % tileHeigth; 81 | 82 | const endX = 83 | view.x + view.width > gameMap.paintWidth 84 | ? gameMap.paintWidth - view.x 85 | : beginX + view.width + tileWidth; 86 | 87 | const endY = 88 | view.y + view.height > gameMap.paintHeight 89 | ? gameMap.paintHeight - view.y 90 | : beginY + view.height + tileHeigth; 91 | 92 | const cx = endX - beginX; 93 | const cy = endY - beginY; 94 | const w = cx > tileWidth ? cx : tileWidth; 95 | const h = cy > tileHeigth ? cy : tileHeigth; 96 | 97 | const clippingParams = { 98 | clipX: beginX, 99 | clipY: beginY, 100 | clipHeight: h, 101 | clipWidth: w, 102 | }; 103 | 104 | // const clippingParams2 = { 105 | // clipX: beginX + w, 106 | // clipY: beginY, 107 | // clipHeight: h, 108 | // clipWidth: w, 109 | // }; 110 | 111 | // const clippingParams3 = { 112 | // clipX: beginX, 113 | // clipY: beginY + h, 114 | // clipHeight: h, 115 | // clipWidth: w, 116 | // }; 117 | 118 | // const clippingParams4 = { 119 | // clipX: beginX + w, 120 | // clipY: beginY + h, 121 | // clipHeight: h, 122 | // clipWidth: w, 123 | // }; 124 | return ( 125 | 126 | 127 | 134 | 135 | 136 | 137 | {/* 138 | 145 | 146 | 147 | 148 | 149 | 156 | 157 | 158 | 159 | 160 | 167 | 168 | 169 | */} 170 | 171 | ); 172 | } 173 | } 174 | 175 | export default connect( 176 | mapStateToProps, 177 | mapDispatchToProps 178 | )(Background); 179 | -------------------------------------------------------------------------------- /front/src/components/Canvas.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stage, Layer } from 'react-konva'; 3 | import GameMap from './GameMap'; 4 | // import Background from './Background'; 5 | import { connect } from 'react-redux'; 6 | import LinearBackground from './LinearBackground'; 7 | 8 | const mapStateToProps = state => { 9 | return { 10 | isGameRunning: state.canvas.isGameRunning, 11 | }; 12 | }; 13 | class Canvas extends React.PureComponent { 14 | constructor(props) { 15 | super(props); 16 | this.state = {}; 17 | } 18 | 19 | render() { 20 | return ( 21 | this.props.isGameRunning && ( 22 | 27 | 28 | 29 | 30 | 31 | 32 | ) 33 | ); 34 | } 35 | } 36 | 37 | export default connect(mapStateToProps)(Canvas); 38 | -------------------------------------------------------------------------------- /front/src/components/Food.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Circle } from 'react-konva'; 3 | import { connect } from 'react-redux'; 4 | import Konva from 'konva'; 5 | 6 | const mapStateToProps = state => { 7 | return { 8 | gameMap: state.canvas.gameMap, 9 | }; 10 | }; 11 | 12 | const relativeX = (x, gameMap) => { 13 | return x / gameMap.scale - gameMap.view.x; 14 | }; 15 | 16 | const relativeY = (y, gameMap) => { 17 | return y / gameMap.scale - gameMap.view.y; 18 | }; 19 | 20 | const relativeW = (width, gameMap) => { 21 | return width / gameMap.scale; 22 | }; 23 | 24 | const relativeH = (height, gameMap) => { 25 | return height / gameMap.scale; 26 | }; 27 | 28 | class Food extends React.PureComponent { 29 | constructor(props) { 30 | super(props); 31 | this.circle = React.createRef(); 32 | } 33 | 34 | componentDidMount() { 35 | // this.circle.current.cache(); 36 | // console.log(this.circle); 37 | } 38 | 39 | render() { 40 | const foodConfig = { 41 | x: relativeX(this.props.x, this.props.gameMap), 42 | y: relativeY(this.props.y, this.props.gameMap), 43 | radius: relativeW(this.props.width, this.props.gameMap), 44 | }; 45 | // public render() { 46 | // if (!this.visible) { 47 | // return; 48 | // } 49 | 50 | // gameMap.ctx.fillStyle = '#fff'; 51 | 52 | // // draw light 53 | // gameMap.ctx.globalAlpha = 0.2; 54 | // gameMap.ctx.beginPath(); 55 | // gameMap.ctx.arc( 56 | // this.paintX, 57 | // this.paintY, 58 | // this.lightSize * this.paintWidth / this.width, 59 | // 0, Math.PI * 2, 60 | // ); 61 | // gameMap.ctx.fill(); 62 | 63 | // gameMap.ctx.globalAlpha = 1; 64 | // gameMap.ctx.beginPath(); 65 | // gameMap.ctx.arc(this.paintX, this.paintY, this.paintWidth / 2, 0, Math.PI * 2); 66 | // gameMap.ctx.fill(); 67 | // } 68 | 69 | return ( 70 | 83 | ); 84 | } 85 | } 86 | 87 | export default connect(mapStateToProps)(Food); 88 | -------------------------------------------------------------------------------- /front/src/components/GameMap.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { ADD_COLORED_RECT } from '../actions/canvas'; 4 | import Snake from './Snake'; 5 | import { Circle, Label, Tag, Text } from 'react-konva'; 6 | 7 | // import Food from './Food'; 8 | 9 | const mapStateToProps = state => { 10 | return { 11 | players: state.canvas.players, 12 | leaderboard: state.canvas.leaderboard, 13 | gameMap: state.canvas.gameMap, 14 | player: state.canvas.player, 15 | foods: state.canvas.foods, 16 | }; 17 | }; 18 | 19 | const mapDispatchToProps = dispatch => { 20 | return { 21 | insertNewRectangle: () => dispatch({ type: ADD_COLORED_RECT }), 22 | }; 23 | }; 24 | 25 | const relativeX = (x, gameMap) => { 26 | return x / gameMap.scale - gameMap.view.x; 27 | }; 28 | 29 | const relativeY = (y, gameMap) => { 30 | return y / gameMap.scale - gameMap.view.y; 31 | }; 32 | 33 | const relativeW = (width, gameMap) => { 34 | return width / gameMap.scale; 35 | }; 36 | 37 | const relativeH = (height, gameMap) => { 38 | return height / gameMap.scale; 39 | }; 40 | 41 | const isVisible = (gameEntity, gameMap) => { 42 | const paintX = relativeX(gameEntity.x, gameMap); 43 | const paintY = relativeY(gameEntity.y, gameMap); 44 | const width = relativeW(gameEntity.width, gameMap); 45 | const height = relativeH(gameEntity.height, gameMap); 46 | return !( 47 | paintX > gameMap.view.width || 48 | paintX + width < 0 || 49 | paintY > gameMap.view.height || 50 | paintY + height < 0 51 | ); 52 | }; 53 | 54 | const snakeIsVisible = ({ collisionRect }, gameMap) => 55 | isVisible( 56 | { 57 | x: collisionRect.minX, 58 | y: collisionRect.minY, 59 | width: collisionRect.maxX - collisionRect.minX, 60 | height: collisionRect.maxY - collisionRect.minY, 61 | }, 62 | gameMap 63 | ); 64 | 65 | const Food = foodProps => ( 66 | 67 | ); 68 | 69 | class GameMap extends React.PureComponent { 70 | render() { 71 | return ( 72 | 73 | {this.props.foods.map( 74 | food => 75 | isVisible(food, this.props.gameMap) && 76 | Food({ 77 | key: food.id, 78 | x: relativeX(food.x, this.props.gameMap), 79 | y: relativeY(food.y, this.props.gameMap), 80 | radius: relativeW(food.width, this.props.gameMap), 81 | }) 82 | )} 83 | {this.props.player.points.length > 0 && ( 84 | 85 | )} 86 | {this.props.players.map( 87 | player => 88 | player.id !== this.props.player.id && 89 | snakeIsVisible(player, this.props.gameMap) && ( 90 | 91 | ) 92 | )} 93 | {this.props.leaderboard.map((player, index) => ( 94 | 109 | ))} 110 | 111 | ); 112 | } 113 | } 114 | 115 | export default connect( 116 | mapStateToProps, 117 | mapDispatchToProps 118 | )(GameMap); 119 | -------------------------------------------------------------------------------- /front/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Home extends React.Component { 4 | render() { 5 | return ( 6 |
    7 |
    8 |
    9 | {this.props.children} 10 |
    11 |
    12 | 13 | 17 |
    18 | ); 19 | } 20 | } 21 | 22 | export default Home; 23 | -------------------------------------------------------------------------------- /front/src/components/LinearBackground.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Rect } from 'react-konva'; 3 | import { connect } from 'react-redux'; 4 | 5 | const mapStateToProps = state => { 6 | return { 7 | gameMap: state.canvas.gameMap, 8 | }; 9 | }; 10 | 11 | const mapDispatchToProps = (/* dispatch*/) => { 12 | return {}; 13 | }; 14 | 15 | class LinearGradientBackground extends React.Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = {}; 19 | } 20 | render() { 21 | return ( 22 | 23 | 36 | 44 | 45 | ); 46 | } 47 | } 48 | 49 | export default connect( 50 | mapStateToProps, 51 | mapDispatchToProps 52 | )(LinearGradientBackground); 53 | -------------------------------------------------------------------------------- /front/src/components/MiniMap.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { connect } from 'react-redux'; 3 | // import { Arc, Group } from 'react-konva'; 4 | 5 | // const mapStateToProps = state => { 6 | // return { 7 | // gameMap: state.canvas.gameMap, 8 | // }; 9 | // }; 10 | 11 | // const MINIMAP_RADIUS = 30; 12 | // const MINIMAP_MARGIN = 50; 13 | 14 | // class MiniMapBackground extends React.Component {} 15 | 16 | // class MiniMap extends React.Component { 17 | // render() { 18 | 19 | // private smallMapWid: number; 20 | // // private smallMapHei: number; 21 | // // private x: number; 22 | // // private y: number; 23 | // // private mapX: number; 24 | // // private mapY: number; 25 | 26 | // // constructor( 27 | // // public gameMap: GameMap, 28 | // // private margin: number, 29 | // // private radius: number, 30 | 31 | // this.image.width = this.radius * 2; 32 | // this.image.height = this.radius * 2; 33 | // this.x = this.gameMap.view.width - this.radius * 2 - this.margin; 34 | // this.y = this.gameMap.view.height - this.radius * 2 - this.margin; 35 | // this.mapX = this.x + this.radius / 2; 36 | // this.mapY = this.y + this.radius / 2; 37 | // const ctx: CanvasRenderingContext2D = this.image.getContext('2d'); 38 | 39 | // this.smallMapWid = 40 | // this.gameMap.width > this.gameMap.height 41 | // ? this.radius 42 | // : (this.gameMap.width * this.radius) / this.gameMap.height; 43 | // this.smallMapHei = 44 | // this.gameMap.width > this.gameMap.height 45 | // ? (this.gameMap.height * this.radius) / this.gameMap.width 46 | // : this.radius; 47 | 48 | // const smallRectX = this.radius - this.smallMapWid / 2; 49 | // const smallRectY = this.radius - this.smallMapHei / 2; 50 | 51 | // // draw background 52 | // ctx.save(); 53 | // ctx.beginPath(); 54 | // ctx.arc(this.radius, this.radius, this.radius - 1, 0, Math.PI * 2); 55 | // ctx.fillStyle = '#000'; 56 | // ctx.fill(); 57 | // // draw map 58 | // ctx.fillStyle = '#ccc'; 59 | // ctx.fillRect(smallRectX, smallRectY, this.smallMapWid, this.smallMapHei); 60 | // ctx.restore(); 61 | 62 | // return ; 67 | // } 68 | // } 69 | 70 | // export default connect(mapStateToProps)(MiniMap); 71 | // // import { GameMap } from './GameMap'; 72 | 73 | // // export class SmallMap { 74 | // // private image: HTMLCanvasElement = document.createElement('canvas'); 75 | // // private smallMapWid: number; 76 | // // private smallMapHei: number; 77 | // // private x: number; 78 | // // private y: number; 79 | // // private mapX: number; 80 | // // private mapY: number; 81 | 82 | // // constructor( 83 | // // public gameMap: GameMap, 84 | // // private margin: number, 85 | // // private radius: number, 86 | // // ) { this.initImage(); } 87 | 88 | // // public initImage(): void { 89 | // // this.image.width = this.radius * 2; 90 | // // this.image.height = this.radius * 2; 91 | // // this.x = this.gameMap.view.width - this.radius * 2 - this.margin; 92 | // // this.y = this.gameMap.view.height - this.radius * 2 - this.margin; 93 | // // this.mapX = this.x + this.radius / 2; 94 | // // this.mapY = this.y + this.radius / 2; 95 | // // const ctx: CanvasRenderingContext2D = this.image.getContext('2d'); 96 | 97 | // // this.smallMapWid = this.gameMap.width > this.gameMap.height 98 | // // ? this.radius 99 | // // : (this.gameMap.width * this.radius / this.gameMap.height); 100 | // // this.smallMapHei = this.gameMap.width > this.gameMap.height 101 | // // ? (this.gameMap.height * this.radius / this.gameMap.width) 102 | // // : this.radius; 103 | 104 | // // const smallRectX = this.radius - this.smallMapWid / 2; 105 | // // const smallRectY = this.radius - this.smallMapHei / 2; 106 | 107 | // // // draw background 108 | // // ctx.save(); 109 | // // ctx.beginPath(); 110 | // // ctx.arc(this.radius, this.radius, this.radius - 1, 0, Math.PI * 2); 111 | // // ctx.fillStyle = '#000'; 112 | // // ctx.fill(); 113 | 114 | // // ctx.lineWidth = 2; 115 | // // ctx.strokeStyle = '#fff'; 116 | // // ctx.stroke(); 117 | 118 | // // // draw map 119 | // // ctx.fillStyle = '#ccc'; 120 | // // ctx.fillRect(smallRectX, smallRectY, this.smallMapWid, this.smallMapHei); 121 | // // ctx.restore(); 122 | // // } 123 | 124 | // // public render() { 125 | // // // relative ratio 126 | // // const radio = this.smallMapWid / this.gameMap.paintWidth; 127 | // // const ctx: CanvasRenderingContext2D = this.gameMap.ctx; 128 | 129 | // // // area and position of window 130 | // // const smallViewX = this.gameMap.view.x * radio + this.mapX; 131 | // // const smallViewY = this.gameMap.view.y * radio + this.mapY; 132 | // // const smallViewW = this.gameMap.view.width * radio; 133 | // // const smallViewH = this.gameMap.view.height * radio; 134 | 135 | // // ctx.save(); 136 | // // ctx.globalAlpha = 0.8; 137 | // // ctx.drawImage(this.image, this.x, this.y); 138 | 139 | // // // draw window 140 | // // // ctx.strokeStyle = '#fff'; 141 | // // // ctx.strokeRect(smallViewX, smallViewY, smallViewW, smallViewH); 142 | // // ctx.fillStyle = '#f00'; 143 | // // ctx.fillRect( 144 | // // smallViewX + smallViewW / 2 - 2, smallViewY + smallViewH / 2 - 2, 145 | // // 4, 4, 146 | // // ); 147 | 148 | // // ctx.restore(); 149 | // // } 150 | // // } 151 | -------------------------------------------------------------------------------- /front/src/components/Snake.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Line, Label, Tag, Text, Circle, Group, Rect } from 'react-konva'; 3 | import { connect } from 'react-redux'; 4 | 5 | const mapStateToProps = state => { 6 | return { 7 | gameMap: state.canvas.gameMap, 8 | }; 9 | }; 10 | 11 | const relativeX = (x, gameMap) => { 12 | return x / gameMap.scale - gameMap.view.x; 13 | }; 14 | 15 | const relativeY = (y, gameMap) => { 16 | return y / gameMap.scale - gameMap.view.y; 17 | }; 18 | 19 | const relativeW = (width, gameMap) => { 20 | return width / gameMap.scale; 21 | }; 22 | 23 | const relativeH = (height, gameMap) => { 24 | return height / gameMap.scale; 25 | }; 26 | 27 | class Snake extends React.PureComponent { 28 | constructor(props) { 29 | super(props); 30 | } 31 | render() { 32 | const points = []; 33 | this.props.points.forEach(({ x, y }) => { 34 | points.push( 35 | relativeX(x, this.props.gameMap), 36 | relativeY(y, this.props.gameMap) 37 | ); 38 | }); 39 | 40 | return ( 41 | 42 | 54 | 55 | 64 | 73 | 82 | 91 | 92 | 113 | {/* */} 127 | 128 | ); 129 | } 130 | } 131 | 132 | export default connect(mapStateToProps)(Snake); 133 | -------------------------------------------------------------------------------- /front/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import registerServiceWorker from './registerServiceWorker'; 5 | import store from './store'; 6 | import { Provider } from 'react-redux'; 7 | 8 | /* eslint-disable-next-line */ 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | 16 | /* eslint-disable-next-line */ 17 | registerServiceWorker(); 18 | -------------------------------------------------------------------------------- /front/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /front/src/reducers/canvas.js: -------------------------------------------------------------------------------- 1 | import Konva from 'konva'; 2 | import { ADD_COLORED_RECT } from '../actions/canvas'; 3 | import { 4 | WEBSOCKET_GAME_UPDATE, 5 | WEBSOCKET_CONNECT, 6 | WEBSOCKET_REGISTER_SUCCESS, 7 | } from '../actions/websocket'; 8 | 9 | const initialState = { 10 | gameOver: false, 11 | isGameRunning: false, 12 | gameMap: { 13 | width: 4000, 14 | height: 4000, 15 | scale: 1, 16 | paintWidth: 4000 / 1, 17 | paintHeight: 4000 / 1, 18 | view: { 19 | x: 0, 20 | y: 0, 21 | width: window.innerWidth, 22 | height: window.innerHeight, 23 | }, 24 | }, 25 | players: [], 26 | foods: [], 27 | leaderboard: [], 28 | player: { 29 | username: 'undefined', 30 | points: [], 31 | id: -1, 32 | x: window.innerWidth / 2, 33 | y: window.innerHeight / 2, 34 | fillColor: Konva.Util.getRandomColor(), 35 | }, 36 | }; 37 | 38 | export default (state = initialState, action) => { 39 | switch (action.type) { 40 | case WEBSOCKET_CONNECT: 41 | return { 42 | ...state, 43 | player: { 44 | ...state.player, 45 | username: action.username, 46 | }, 47 | }; 48 | 49 | case WEBSOCKET_REGISTER_SUCCESS: 50 | return { 51 | ...state, 52 | player: { 53 | ...state.player, 54 | ...action.player, 55 | }, 56 | isGameRunning: true, 57 | }; 58 | 59 | case WEBSOCKET_GAME_UPDATE: { 60 | const player = action.players.find(snake => snake.id === state.player.id); 61 | 62 | const newConf = player 63 | ? { 64 | x: player.x / state.gameMap.scale - state.gameMap.view.width / 2, 65 | y: player.y / state.gameMap.scale - state.gameMap.view.height / 2, 66 | } 67 | : { x: 0, y: 0 }; 68 | 69 | const newScale = player 70 | ? { 71 | scale: player.scale, 72 | paintWidth: 4000 / player.scale, 73 | paintHeight: 4000 / player.scale, 74 | } 75 | : {}; 76 | 77 | state.leaderboard.sli; 78 | 79 | return { 80 | ...state, 81 | gameOver: !player, 82 | gameMap: { 83 | ...state.gameMap, 84 | ...newScale, 85 | view: { 86 | ...state.gameMap.view, 87 | ...newConf, 88 | }, 89 | }, 90 | leaderboard: action.players 91 | .sort((p1, p2) => p1.score < p2.score) 92 | .slice(0, 10), 93 | foods: [...action.foods], 94 | players: [...action.players], 95 | player: { 96 | ...(player ? player : state.player), 97 | }, 98 | }; 99 | } 100 | 101 | case ADD_COLORED_RECT: 102 | return { 103 | ...state, 104 | entities: [ 105 | ...state.entities, 106 | { 107 | x: Math.random() * window.innerWidth, 108 | y: Math.random() * window.innerHeight, 109 | color: Konva.Util.getRandomColor(), 110 | }, 111 | ], 112 | }; 113 | 114 | default: 115 | return state; 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /front/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import canvas from './canvas'; 3 | 4 | export default combineReducers({canvas}); -------------------------------------------------------------------------------- /front/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /front/src/sagas/index.js: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects'; 2 | import mouseMoveSaga from './mousemove'; 3 | import webSocketSaga from './websockets'; 4 | import playerMoveSaga from './playermove'; 5 | 6 | const rootSaga = function*() { 7 | yield all([webSocketSaga(), mouseMoveSaga(), playerMoveSaga()]); 8 | }; 9 | 10 | // export const rootSaga = webSocketSaga; 11 | 12 | export default rootSaga; 13 | -------------------------------------------------------------------------------- /front/src/sagas/mousemove.js: -------------------------------------------------------------------------------- 1 | import { eventChannel } from 'redux-saga'; 2 | import { call, put, take } from 'redux-saga/effects'; 3 | import { 4 | MOUSE_MOVE, 5 | PLAYER_SPEED_DOWN, 6 | PLAYER_SPEED_UP, 7 | } from '../actions/mousemove'; 8 | import { WEBSOCKET_CONNECT } from '../actions/websocket'; 9 | 10 | const createMouseChannel = () => { 11 | return eventChannel(emitter => { 12 | // finger|mouse move event 13 | window.addEventListener('mousemove', mouseEvent => { 14 | mouseEvent.preventDefault(); 15 | emitter({ 16 | type: MOUSE_MOVE, 17 | x: mouseEvent.clientX, 18 | y: mouseEvent.clientY, 19 | }); 20 | }); 21 | 22 | window.addEventListener('mousedown', mouseEvent => { 23 | mouseEvent.preventDefault(); 24 | emitter({ 25 | type: PLAYER_SPEED_UP, 26 | }); 27 | }); 28 | 29 | window.addEventListener('mouseup', mouseEvent => { 30 | mouseEvent.preventDefault(); 31 | emitter({ 32 | type: PLAYER_SPEED_DOWN, 33 | }); 34 | }); 35 | 36 | const unsubscribe = () => { 37 | window.removeEventListener('mousemove'); 38 | window.removeEventListener('mouseup'); 39 | window.removeEventListener('mousedown'); 40 | }; 41 | 42 | return unsubscribe; 43 | }); 44 | }; 45 | 46 | const mouseMoveSaga = function*() { 47 | yield take(WEBSOCKET_CONNECT); 48 | const mouseMoveChannel = yield call(createMouseChannel); 49 | while (true) { 50 | const action = yield take(mouseMoveChannel); 51 | yield put(action); 52 | } 53 | }; 54 | 55 | export default mouseMoveSaga; 56 | -------------------------------------------------------------------------------- /front/src/sagas/playermove.js: -------------------------------------------------------------------------------- 1 | import { MOUSE_MOVE, playerMove } from '../actions/mousemove'; 2 | import { select, take, put } from 'redux-saga/effects'; 3 | 4 | export const getGameMap = state => state.canvas.gameMap; 5 | 6 | export default function* playerMoveSaga() { 7 | while (true) { 8 | const mouseMove = yield take(MOUSE_MOVE); 9 | const gameMap = yield select(getGameMap); 10 | yield put( 11 | playerMove( 12 | (mouseMove.x + gameMap.view.x) * gameMap.scale, 13 | (mouseMove.y + gameMap.view.y) * gameMap.scale 14 | ) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /front/src/sagas/websockets.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { eventChannel } from 'redux-saga'; 3 | import { 4 | WEBSOCKET_CONNECT, 5 | WEBSOCKET_CANCEL, 6 | webSocketActions, 7 | } from '../actions/websocket'; 8 | import { call, put, take, race, takeEvery } from 'redux-saga/effects'; 9 | import io from 'socket.io-client'; 10 | import { 11 | PLAYER_MOVE, 12 | PLAYER_SPEED_UP, 13 | PLAYER_SPEED_DOWN, 14 | } from '../actions/mousemove'; 15 | import sp from 'schemapack'; 16 | import axios from 'axios'; 17 | 18 | const gameUpdate = sp.build({ 19 | snakes: [ 20 | { 21 | id: 'string', 22 | x: 'int16', 23 | y: 'int16', 24 | isBlinking: 'bool', 25 | isSpeedUp: 'bool', 26 | length: 'uint16', 27 | scale: 'float32', 28 | fillColor: 'string', 29 | angle: 'float32', 30 | username: 'string', 31 | points: [ 32 | { 33 | x: 'int16', 34 | y: 'int16', 35 | }, 36 | ], 37 | 38 | collisionRect: { 39 | minX: 'int16', 40 | minY: 'int16', 41 | maxX: 'int16', 42 | maxY: 'int16', 43 | }, 44 | width: 'float32', 45 | score: 'uint16', 46 | }, 47 | ], 48 | foods: [ 49 | { 50 | x: 'int16', 51 | y: 'int16', 52 | id: 'string', 53 | width: 'float32', 54 | height: 'float32', 55 | }, 56 | ], 57 | }); 58 | 59 | function socketListener(ws, username) { 60 | return eventChannel(emitter => { 61 | const actions = bindActionCreators(webSocketActions, emitter); 62 | 63 | // ws.on('connect', () => { 64 | // console.log('Connected!'); 65 | console.log(username); 66 | ws.emit('register', username); 67 | // actions.connectToServer(url, username); 68 | // }); 69 | 70 | ws.on('register-success', actions.registerSuccess); 71 | 72 | ws.on('game-update', data => 73 | actions.updateGameState(gameUpdate.decode(data)) 74 | ); 75 | // ws.on('message', (e) => { 76 | // // console.log(e); 77 | // ws.emit('message', 'fdp'); 78 | // }); 79 | // /* eslint-disable-next-line */ 80 | 81 | // ws.onopen = e => { 82 | // //ws.send(JSON.stringify({ type: 'move', ...subscribeData })); 83 | // }; 84 | 85 | // ws.onclose = eventHandlers.onclose; 86 | 87 | // ws.onerror = eventHandlers.onerror; 88 | 89 | // ws.onmessage = eventHandlers.onmessage; 90 | // return ws.close; 91 | return ws.close; 92 | }); 93 | } 94 | 95 | function* externalListener(socketChannel) { 96 | while (true) { 97 | const action = yield take(socketChannel); 98 | yield put(action); 99 | } 100 | } 101 | 102 | function* internalListener(ws) { 103 | yield [ 104 | takeEvery(PLAYER_MOVE, action => ws.emit('move', action)), 105 | takeEvery(PLAYER_SPEED_UP, () => { 106 | ws.emit('player-speed-up'); 107 | }), 108 | takeEvery(PLAYER_SPEED_DOWN, () => ws.emit('player-speed-down')), 109 | ]; 110 | } 111 | 112 | function* webSocketSaga() { 113 | while (true) { 114 | const payload = yield take(WEBSOCKET_CONNECT); 115 | let data = undefined; 116 | try { 117 | const response = yield axios.get('http://localhost:9000/connect'); 118 | data = response.data.url; 119 | } catch (e) { 120 | data = 'localhost:4242'; 121 | } 122 | const ws = io(`ws://${data}`, { 123 | transports: ['websocket'], 124 | upgrade: false, 125 | }); 126 | 127 | const socketChannel = yield call(socketListener, ws, payload.username); 128 | 129 | const { cancel } = yield race({ 130 | task: [call(externalListener, socketChannel), call(internalListener, ws)], 131 | cancel: take(WEBSOCKET_CANCEL), 132 | }); 133 | 134 | if (cancel) { 135 | ws.close(); 136 | } 137 | } 138 | } 139 | 140 | export default webSocketSaga; 141 | -------------------------------------------------------------------------------- /front/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import reducers from './reducers'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | import createSagaMiddleware from 'redux-saga'; 5 | import rootSaga from './sagas'; 6 | 7 | const sagaMiddleware = createSagaMiddleware(); 8 | 9 | export default createStore( 10 | reducers, 11 | {}, 12 | composeWithDevTools(applyMiddleware(sagaMiddleware)) 13 | ); 14 | 15 | sagaMiddleware.run(rootSaga); 16 | -------------------------------------------------------------------------------- /orchestrator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN curl -o- -L https://yarnpkg.com/install.sh | bash 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN yarn install 10 | 11 | EXPOSE 9000 12 | 13 | CMD ["yarn", "start"] 14 | -------------------------------------------------------------------------------- /orchestrator/README.md: -------------------------------------------------------------------------------- 1 | # Effective Barnacle Orchestrator 2 | 3 | This project uses Traefik... 4 | 5 | # Environment variables 6 | 7 | * `EB_ORCHESTRATOR_PORT` (default `9000`) 8 | * `EB_ORCHESTRATOR_SECRET` (default `secret`) 9 | * `EB_SERVER_DOCKER_IMAGE` (default `effective-barnacle_backend`) 10 | * `EB_DOMAIN_NAME` (default `localhost`) 11 | * `EB_MAX_CLIENTS_PER_SERVER` (default `5`) 12 | * `EB_DEFAULT_SERVERS_NB` (default `5`) 13 | * `EB_MONGO_HOST` (default `localhost`) 14 | 15 | # Routes 16 | 17 | * `/connect` 18 | 19 | Returns the first free server. 20 | 21 | * `/confirm/:serverId` 22 | 23 | Secure way to register a user to a server. 24 | 25 | * `/disconnect/:serverId` 26 | 27 | Disconnect the user from the server. 28 | 29 | * `/admin` 30 | 31 | Admin page. 32 | -------------------------------------------------------------------------------- /orchestrator/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node ./src/server.ts" 5 | } -------------------------------------------------------------------------------- /orchestrator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "back", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "tsc && node dist/server.js", 8 | "dev": "nodemon" 9 | }, 10 | "dependencies": { 11 | "basic-auth": "^2.0.0", 12 | "dockerode": "^2.5.5", 13 | "ejs": "^2.6.1", 14 | "express": "^4.16.3", 15 | "mongoose": "^5.1.3", 16 | "shortid": "^2.2.8", 17 | "typescript": "^2.8.3" 18 | }, 19 | "devDependencies": { 20 | "@types/basic-auth": "^1.1.2", 21 | "@types/dockerode": "^2.5.4", 22 | "@types/express": "^4.11.1", 23 | "@types/mongoose": "^5.0.15", 24 | "@types/shortid": "^0.0.29", 25 | "nodemon": "^1.17.5", 26 | "ts-node": "^6.0.4", 27 | "tslint": "^5.10.0", 28 | "tslint-config-airbnb": "^5.9.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orchestrator/src/env.ts: -------------------------------------------------------------------------------- 1 | export const MONGO_HOST = process.env.EB_MONGO_HOST || 'localhost'; 2 | export const PORT = process.env.EB_ORCHESTRATOR_PORT || 9000; 3 | export const DEFAULT_SERVERS_NB = process.env.EB_DEFAULT_SERVERS_NB || 5; 4 | export const SERVER_IMAGE = process.env.EB_SERVER_DOCKER_IMAGE || 'effective-barnacle_backend'; 5 | export const DOMAIN_NAME = process.env.EB_DOMAIN_NAME || 'localhost'; 6 | export const MAX_CLIENTS_PER_SERVER = process.env.EB_MAX_CLIENTS_PER_SERVER || 5; 7 | export const SECRET = process.env.EB_ORCHESTRATOR_SECRET || 'secret'; 8 | -------------------------------------------------------------------------------- /orchestrator/src/middlewares/auth.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as auth from 'basic-auth'; 3 | 4 | import { SECRET } from '../env'; 5 | 6 | export default function basicAuth(req: express.Request, res: express.Response, 7 | next: express.NextFunction) { 8 | const user = auth(req); 9 | 10 | if (!user || user['pass'] !== SECRET) { 11 | res.statusCode = 401; 12 | res.setHeader('WWW-Authenticate', 'Basic realm="Node"'); 13 | res.end('Unauthorized'); 14 | } else { 15 | next(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /orchestrator/src/middlewares/cors.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | export default function cors(req: express.Request, res: express.Response, 4 | next: express.NextFunction) { 5 | res.header('Access-Control-Allow-Origin', '*'); 6 | res.header( 7 | 'Access-Control-Allow-Headers', 8 | 'Origin, X-Requested-With, Content-Type, Accept', 9 | ); 10 | next(); 11 | } 12 | -------------------------------------------------------------------------------- /orchestrator/src/models/server.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | 3 | export interface IServer extends mongoose.Document { 4 | _id: string; 5 | url: string; 6 | port: number; 7 | clients: number; 8 | } 9 | 10 | const serverSchema = new mongoose.Schema({ 11 | _id: String, 12 | url: String, 13 | port: Number, 14 | clients: Number, 15 | }); 16 | 17 | export const Server = mongoose.model('server', serverSchema); 18 | -------------------------------------------------------------------------------- /orchestrator/src/routes/admin.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | import { Server } from '../models/server'; 4 | import { MAX_CLIENTS_PER_SERVER } from '../env'; 5 | 6 | export default async function admin(req: express.Request, res: express.Response) { 7 | const servers = await Server.find({}).exec(); 8 | 9 | res.render('admin', { 10 | servers, 11 | maxClientsPerServer: MAX_CLIENTS_PER_SERVER, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /orchestrator/src/routes/confirm.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | import { Server } from '../models/server'; 4 | 5 | export default async function confirm(req: express.Request, res: express.Response) { 6 | const { serverId } = req.params; 7 | 8 | const server = await Server.findById(serverId).exec(); 9 | server.clients++; 10 | server.save(); 11 | 12 | res.json(server); 13 | } 14 | -------------------------------------------------------------------------------- /orchestrator/src/routes/connect.ts: -------------------------------------------------------------------------------- 1 | import * as Docker from 'dockerode'; 2 | import * as express from 'express'; 3 | import * as shortid from 'shortid'; 4 | 5 | import { Server, IServer } from '../models/server'; 6 | import spawnServer from '../spawn'; 7 | import { MAX_CLIENTS_PER_SERVER } from '../env'; 8 | 9 | function findFreeServer(servers: IServer[]): IServer { 10 | return servers.find((server: IServer) => server.clients < MAX_CLIENTS_PER_SERVER); 11 | } 12 | 13 | export default async function connect(req: express.Request, res: express.Response) { 14 | const servers = await Server.find({}).exec(); 15 | 16 | let server = await findFreeServer(servers); 17 | 18 | if (!server) { 19 | server = await spawnServer(servers); 20 | } 21 | 22 | res.json(server); 23 | } 24 | -------------------------------------------------------------------------------- /orchestrator/src/routes/disconnect.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as Docker from 'dockerode'; 3 | 4 | import { Server, IServer } from '../models/server'; 5 | import { DEFAULT_SERVERS_NB } from '../env'; 6 | 7 | const docker = new Docker({ socketPath: '/var/run/docker.sock' }); 8 | 9 | export default async function disconnect(req: express.Request, res: express.Response) { 10 | const { serverId } = req.params; 11 | 12 | const servers = await Server.find({}).exec(); 13 | const server = servers.find((server: IServer) => server._id === serverId); 14 | 15 | if (!server) { 16 | res.sendStatus(404); 17 | return; 18 | } 19 | 20 | if (servers.length > DEFAULT_SERVERS_NB && server.clients <= 1) { 21 | server.remove(); 22 | docker.getContainer(serverId).kill().then((container: Docker.Container) => { 23 | container.remove(); 24 | }); 25 | } else { 26 | server.clients--; 27 | server.save(); 28 | } 29 | 30 | res.json(server); 31 | } 32 | -------------------------------------------------------------------------------- /orchestrator/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as mongoose from 'mongoose'; 3 | 4 | import basicAuth from './middlewares/auth'; 5 | import cors from './middlewares/cors'; 6 | import connect from './routes/connect'; 7 | import disconnect from './routes/disconnect'; 8 | import admin from './routes/admin'; 9 | import confirm from './routes/confirm'; 10 | import { Server } from './models/server'; 11 | import spawnServer from './spawn'; 12 | import { MONGO_HOST, PORT, DEFAULT_SERVERS_NB } from './env'; 13 | 14 | mongoose.connect(`mongodb://${MONGO_HOST}/barnacle`); 15 | 16 | const app = express(); 17 | 18 | app.use(cors); 19 | 20 | app.set('view engine', 'ejs'); 21 | 22 | app.get('/connect', connect); 23 | app.get('/confirm/:serverId', basicAuth, confirm); 24 | app.get('/disconnect/:serverId', disconnect); 25 | app.get('/', basicAuth, admin); 26 | 27 | app.listen(PORT, async () => { 28 | for (let i = 0; i < DEFAULT_SERVERS_NB; ++i) { 29 | spawnServer(await Server.find({}).exec()); 30 | } 31 | console.log(`App serving on http://localhost:${PORT}`); 32 | }); 33 | -------------------------------------------------------------------------------- /orchestrator/src/spawn.ts: -------------------------------------------------------------------------------- 1 | import * as Docker from 'dockerode'; 2 | import * as shortid from 'shortid'; 3 | 4 | import { Server, IServer } from './models/server'; 5 | import { DOMAIN_NAME, SERVER_IMAGE, SECRET } from './env'; 6 | 7 | const docker = new Docker({ socketPath: '/var/run/docker.sock' }); 8 | 9 | function generateRandomPort(): number { 10 | return Math.round(Math.random() * 10000 + 7000); 11 | } 12 | 13 | function getUnusedPort(servers: IServer[]): number { 14 | let randomPort = generateRandomPort(); 15 | 16 | while (servers.some((server: IServer) => server.port === randomPort)) { 17 | randomPort = generateRandomPort(); 18 | } 19 | 20 | return randomPort; 21 | } 22 | 23 | export default async function spawnServer(servers: IServer[]): Promise { 24 | const _id = shortid.generate(); 25 | const url = `arena-${_id}.${DOMAIN_NAME}`; 26 | const port = await getUnusedPort(servers); 27 | 28 | docker.run(SERVER_IMAGE, ['yarn', 'start'], null, { 29 | Labels: { 30 | 'traefik.enable': 'true', 31 | 'traefik.frontend.rule': `Host:${url}`, 32 | 'traefik.docker.network': 'traefik_default', 33 | }, 34 | ExposedPorts: { [`${port}/tcp`]: {} }, 35 | NetworkMode: 'traefik_default', 36 | Env: [ 37 | `EB_SERVER_ID=${_id}`, 38 | `EB_SERVER_PORT=${port}`, 39 | `EB_SERVER_SECRET=${SECRET}`, 40 | `EB_ORCHESTRATOR_URL=orchestrator.${DOMAIN_NAME}`, 41 | 'NODE_ENV=production', 42 | ], 43 | name: _id, 44 | }); 45 | 46 | const server = new Server({ 47 | _id, 48 | url, 49 | port, 50 | clients: 0, 51 | }); 52 | 53 | server.save(); 54 | 55 | return server; 56 | } 57 | -------------------------------------------------------------------------------- /orchestrator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "./dist", 7 | "declaration": false, 8 | "noImplicitReturns": true, 9 | "noImplicitThis": true, 10 | "noImplicitAny": true, 11 | "sourceMap": true, 12 | "noLib": false, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "emitDecoratorMetadata": true, 15 | "experimentalDecorators": true, 16 | "typeRoots": [ 17 | "node_modules/@types" 18 | ] 19 | }, 20 | "compileOnSave": false, 21 | "exclude": [ 22 | "dist", 23 | "node_modules" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /orchestrator/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/tslint-config-airbnb/tslint", 3 | "rules": { 4 | "no-increment-decrement": false, 5 | "import-name": false, 6 | "variable-name": false 7 | } 8 | } -------------------------------------------------------------------------------- /orchestrator/views/admin.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Effective Barnacle Admin 7 | 8 | 10 | 11 | 12 | 13 |
    14 |

    Effective Barnacle Admin

    15 | 16 |

    17 | 18 | <%= servers.length %> 19 | servers up

    20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | <% servers.forEach(server => { %> 32 | 33 | 36 | 41 | 44 | 48 | 49 | <% }) %> 50 | 51 |
    IDURLPortClients
    34 | <%= server._id %> 35 | 37 | 38 | <%= server.url %> 39 | 40 | 42 | <%= server.port %> 43 | 45 | <%= server.clients %> / 46 | <%= maxClientsPerServer %> 47 |
    52 |
    53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /screenshots/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedthejim/effective-barnacle/71d50328be501e9383631cb868913f257e7bdb2b/screenshots/game.png -------------------------------------------------------------------------------- /screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feedthejim/effective-barnacle/71d50328be501e9383631cb868913f257e7bdb2b/screenshots/home.png -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! docker network ls | grep traefik_default; then 4 | docker network create traefik_default 5 | fi 6 | 7 | docker-compose build 8 | 9 | docker-compose up -d reverse-proxy mongodb orchestrator 10 | -------------------------------------------------------------------------------- /scripts/kill-servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker ps -q --filter ancestor="effective-barnacle_backend" | xargs -r docker kill 4 | -------------------------------------------------------------------------------- /scripts/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ./scripts/kill-servers.sh 4 | 5 | docker-compose down 6 | --------------------------------------------------------------------------------