├── .editorconfig
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .vscode
├── extensions.json
└── settings.json
├── assets
├── PressStart2P-Regular.ttf
├── favicon.png
├── game-over.mp3
├── jump.mp3
├── level-up.mp3
├── preview.png
└── sprite.png
├── index.html
├── lib
├── actors
│ ├── Actor.js
│ ├── Bird.js
│ ├── Cactus.js
│ ├── Cloud.js
│ └── Dino.js
├── game
│ ├── DinoGame.js
│ └── GameRunner.js
├── index.js
├── sounds.js
├── sprites.js
└── utils.js
├── license
├── package.json
├── readme.md
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | node_modules
3 | index.html
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'always',
3 | semi: false,
4 | singleQuote: true,
5 | tabWidth: 2,
6 | trailingComma: 'es5',
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[javascript]": {
3 | "editor.defaultFormatter": "esbenp.prettier-vscode"
4 | },
5 | "editor.defaultFormatter": "esbenp.prettier-vscode",
6 | "editor.formatOnSave": true,
7 | "javascript.format.enable": true,
8 | }
9 |
--------------------------------------------------------------------------------
/assets/PressStart2P-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/PressStart2P-Regular.ttf
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/favicon.png
--------------------------------------------------------------------------------
/assets/game-over.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/game-over.mp3
--------------------------------------------------------------------------------
/assets/jump.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/jump.mp3
--------------------------------------------------------------------------------
/assets/level-up.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/level-up.mp3
--------------------------------------------------------------------------------
/assets/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/preview.png
--------------------------------------------------------------------------------
/assets/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisdothtml/chrome-dino/6761e29e9301c297d474309eafebb3bf2c2b7a8f/assets/sprite.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Chrome Dino
8 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/lib/actors/Actor.js:
--------------------------------------------------------------------------------
1 | import sprites from '../sprites.js'
2 |
3 | const cache = new Map()
4 |
5 | // analyze the pixels and create a map of the transparent
6 | // and non-transparent pixels for hit testing
7 | function getSpriteAlphaMap(imageData, name) {
8 | if (cache.has(name)) {
9 | return cache.get(name)
10 | }
11 |
12 | const sprite = sprites[name]
13 | const lines = []
14 | const initIVal = imageData.width * sprite.y * 4
15 |
16 | // for each line of pixels
17 | for (
18 | let i = initIVal;
19 | i < initIVal + sprite.h * imageData.width * 4;
20 | // (increments by 8 because it skips every other pixel due to pixel density)
21 | i += imageData.width * 8
22 | ) {
23 | const line = []
24 | const initJVal = i + sprite.x * 4
25 | // for each pixel in the line
26 | // (increments by 8 because it skips every other pixel due to pixel density)
27 | for (let j = initJVal; j < initJVal + sprite.w * 4; j += 8) {
28 | // 0 for transparent, 1 for not
29 | line.push(imageData.data[j + 3] === 0 ? 0 : 1)
30 | }
31 |
32 | lines.push(line)
33 | }
34 |
35 | cache.set(name, lines)
36 | return lines
37 | }
38 |
39 | export default class Actor {
40 | constructor(imageData) {
41 | this._sprite = null
42 | this.height = 0
43 | this.width = 0
44 | this.x = 0
45 | this.y = 0
46 |
47 | // the spriteImage should only be passed into actors that will
48 | // use hit detection; otherwise don't waste cpu on generating
49 | // the alpha map every time the sprite is set
50 | if (imageData) {
51 | this.imageData = imageData
52 | this.alphaMap = []
53 | }
54 | }
55 |
56 | set sprite(name) {
57 | this._sprite = name
58 | this.height = sprites[name].h / 2
59 | this.width = sprites[name].w / 2
60 |
61 | if (this.imageData) {
62 | this.alphaMap = getSpriteAlphaMap(this.imageData, name)
63 | }
64 | }
65 |
66 | get sprite() {
67 | return this._sprite
68 | }
69 |
70 | // the x value of the right side of it
71 | get rightX() {
72 | return this.width + this.x
73 | }
74 |
75 | // the y value of the bottom of it
76 | get bottomY() {
77 | return this.height + this.y
78 | }
79 |
80 | hits(actors) {
81 | return actors.some((actor) => {
82 | if (!actor) return false
83 |
84 | if (this.x >= actor.rightX || actor.x >= this.rightX) {
85 | return false
86 | }
87 |
88 | if (this.y >= actor.bottomY || actor.y >= this.bottomY) {
89 | return false
90 | }
91 |
92 | // actors' coords are intersecting, but they still might not be hitting
93 | // each other if they intersect at transparent pixels
94 | if (this.alphaMap && actor.alphaMap) {
95 | const startY = Math.round(Math.max(this.y, actor.y))
96 | const endY = Math.round(Math.min(this.bottomY, actor.bottomY))
97 | const startX = Math.round(Math.max(this.x, actor.x))
98 | const endX = Math.round(Math.min(this.rightX, actor.rightX))
99 | const thisY = Math.round(this.y)
100 | const actorY = Math.round(actor.y)
101 | const thisX = Math.round(this.x)
102 | const actorX = Math.round(actor.x)
103 |
104 | for (let y = startY; y < endY; y++) {
105 | for (let x = startX; x < endX; x++) {
106 | // doesn't hit if either are transparent at these coords
107 | if (this.alphaMap[y - thisY][x - thisX] === 0) continue
108 | if (actor.alphaMap[y - actorY][x - actorX] === 0) continue
109 |
110 | return true
111 | }
112 | }
113 |
114 | return false
115 | }
116 |
117 | return true
118 | })
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/actors/Bird.js:
--------------------------------------------------------------------------------
1 | import sprites from '../sprites.js'
2 | import Actor from './Actor.js'
3 |
4 | export default class Bird extends Actor {
5 | static maxBirdHeight = Math.max(sprites.birdUp.h, sprites.birdDown.h) / 2
6 |
7 | // pixels that are added/removed to `y` when switching between wings up and wings down
8 | static wingSpriteYShift = 6
9 |
10 | constructor(imageData) {
11 | super(imageData)
12 | this.wingFrames = 0
13 | this.wingDirection = 'Up'
14 | this.sprite = `bird${this.wingDirection}`
15 | // these are dynamically set by the game
16 | this.x = null
17 | this.y = null
18 | this.speed = null
19 | this.wingsRate = null
20 | }
21 |
22 | nextFrame() {
23 | this.x -= this.speed
24 | this.determineSprite()
25 | }
26 |
27 | determineSprite() {
28 | const oldHeight = this.height
29 |
30 | if (this.wingFrames >= this.wingsRate) {
31 | this.wingDirection = this.wingDirection === 'Up' ? 'Down' : 'Up'
32 | this.wingFrames = 0
33 | }
34 |
35 | this.sprite = `bird${this.wingDirection}`
36 | this.wingFrames++
37 |
38 | // if we're switching sprites, y needs to be
39 | // updated for the height difference
40 | if (this.height !== oldHeight) {
41 | let adjustment = Bird.wingSpriteYShift
42 | if (this.wingDirection === 'Up') {
43 | adjustment *= -1
44 | }
45 |
46 | this.y += adjustment
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/actors/Cactus.js:
--------------------------------------------------------------------------------
1 | import { randItem } from '../utils.js'
2 | import Actor from './Actor.js'
3 |
4 | const VARIANTS = ['cactus', 'cactusDouble', 'cactusDoubleB', 'cactusTriple']
5 |
6 | export default class Cactus extends Actor {
7 | constructor(imageData) {
8 | super(imageData)
9 | this.sprite = randItem(VARIANTS)
10 | // these are dynamically set by the game
11 | this.speed = null
12 | this.x = null
13 | this.y = null
14 | }
15 |
16 | nextFrame() {
17 | this.x -= this.speed
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/actors/Cloud.js:
--------------------------------------------------------------------------------
1 | import { randInteger } from '../utils.js'
2 | import Actor from './Actor.js'
3 |
4 | export default class Cloud extends Actor {
5 | constructor(canvasWidth) {
6 | super()
7 | this.sprite = 'cloud'
8 | this.speedMod = randInteger(6, 14) / 10
9 | // these are dynamically set by the game
10 | this.speed = null
11 | this.x = null
12 | this.y = null
13 | }
14 |
15 | nextFrame() {
16 | this.x -= this.speed * this.speedMod
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/actors/Dino.js:
--------------------------------------------------------------------------------
1 | import Actor from './Actor.js'
2 |
3 | export default class Dino extends Actor {
4 | constructor(imageData) {
5 | super(imageData)
6 | this.isDucking = false
7 | this.legFrames = 0
8 | this.legShowing = 'Left'
9 | this.sprite = `dino${this.legShowing}Leg`
10 | this.vVelocity = null
11 | this.baseY = 0
12 | this.relativeY = 0
13 | // these are dynamically set by the game
14 | this.legsRate = null
15 | this.lift = null
16 | this.gravity = null
17 | }
18 |
19 | get y() {
20 | return this.baseY - this.height + this.relativeY
21 | }
22 |
23 | set y(value) {
24 | this.baseY = value
25 | }
26 |
27 | reset() {
28 | this.isDucking = false
29 | this.legFrames = 0
30 | this.legShowing = 'Left'
31 | this.sprite = `dino${this.legShowing}Leg`
32 | this.vVelocity = null
33 | this.relativeY = 0
34 | }
35 |
36 | jump() {
37 | if (this.relativeY === 0) {
38 | this.vVelocity = -this.lift
39 | return true
40 | }
41 | return false
42 | }
43 |
44 | duck(value) {
45 | this.isDucking = Boolean(value)
46 | }
47 |
48 | nextFrame() {
49 | if (this.vVelocity !== null) {
50 | // use gravity to gradually decrease vVelocity
51 | this.vVelocity += this.gravity
52 | this.relativeY += this.vVelocity
53 | }
54 |
55 | // stop falling once back down to the ground
56 | if (this.relativeY > 0) {
57 | this.vVelocity = null
58 | this.relativeY = 0
59 | }
60 |
61 | this.determineSprite()
62 | }
63 |
64 | determineSprite() {
65 | if (this.relativeY < 0) {
66 | // in the air stiff
67 | this.sprite = 'dino'
68 | } else {
69 | // on the ground running
70 | if (this.legFrames >= this.legsRate) {
71 | this.legShowing = this.legShowing === 'Left' ? 'Right' : 'Left'
72 | this.legFrames = 0
73 | }
74 |
75 | if (this.isDucking) {
76 | this.sprite = `dinoDuck${this.legShowing}Leg`
77 | } else {
78 | this.sprite = `dino${this.legShowing}Leg`
79 | }
80 |
81 | this.legFrames++
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/game/DinoGame.js:
--------------------------------------------------------------------------------
1 | import Bird from '../actors/Bird.js'
2 | import Cactus from '../actors/Cactus.js'
3 | import Cloud from '../actors/Cloud.js'
4 | import Dino from '../actors/Dino.js'
5 | import sprites from '../sprites.js'
6 | import { playSound } from '../sounds.js'
7 | import {
8 | loadFont,
9 | loadImage,
10 | getImageData,
11 | randBoolean,
12 | randInteger,
13 | } from '../utils.js'
14 | import GameRunner from './GameRunner.js'
15 |
16 | export default class DinoGame extends GameRunner {
17 | constructor(width, height) {
18 | super()
19 |
20 | this.width = null
21 | this.height = null
22 | this.canvas = this.createCanvas(width, height)
23 | this.canvasCtx = this.canvas.getContext('2d')
24 | this.spriteImage = null
25 | this.spriteImageData = null
26 |
27 | /*
28 | * units
29 | * fpa: frames per action
30 | * ppf: pixels per frame
31 | * px: pixels
32 | */
33 | this.defaultSettings = {
34 | bgSpeed: 8, // ppf
35 | birdSpeed: 7.2, // ppf
36 | birdSpawnRate: 240, // fpa
37 | birdWingsRate: 15, // fpa
38 | cactiSpawnRate: 50, // fpa
39 | cloudSpawnRate: 200, // fpa
40 | cloudSpeed: 2, // ppf
41 | dinoGravity: 0.5, // ppf
42 | dinoGroundOffset: 4, // px
43 | dinoLegsRate: 6, // fpa
44 | dinoLift: 10, // ppf
45 | scoreBlinkRate: 20, // fpa
46 | scoreIncreaseRate: 6, // fpa
47 | }
48 |
49 | this.state = {
50 | settings: { ...this.defaultSettings },
51 | birds: [],
52 | cacti: [],
53 | clouds: [],
54 | dino: null,
55 | gameOver: false,
56 | groundX: 0,
57 | groundY: 0,
58 | isRunning: false,
59 | level: 0,
60 | score: {
61 | blinkFrames: 0,
62 | blinks: 0,
63 | isBlinking: false,
64 | value: 0,
65 | },
66 | }
67 | }
68 |
69 | // ref for canvas pixel density:
70 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#correcting_resolution_in_a_%3Ccanvas%3E
71 | createCanvas(width, height) {
72 | const canvas = document.createElement('canvas')
73 | const ctx = canvas.getContext('2d')
74 | const scale = window.devicePixelRatio
75 |
76 | this.width = width
77 | this.height = height
78 | canvas.style.width = width + 'px'
79 | canvas.style.height = height + 'px'
80 | canvas.width = Math.floor(width * scale)
81 | canvas.height = Math.floor(height * scale)
82 | ctx.scale(scale, scale)
83 |
84 | document.body.appendChild(canvas)
85 | return canvas
86 | }
87 |
88 | async preload() {
89 | const { settings } = this.state
90 | const [spriteImage] = await Promise.all([
91 | loadImage('./assets/sprite.png'),
92 | loadFont('./assets/PressStart2P-Regular.ttf', 'PressStart2P'),
93 | ])
94 | this.spriteImage = spriteImage
95 | this.spriteImageData = getImageData(spriteImage)
96 | const dino = new Dino(this.spriteImageData)
97 |
98 | dino.legsRate = settings.dinoLegsRate
99 | dino.lift = settings.dinoLift
100 | dino.gravity = settings.dinoGravity
101 | dino.x = 25
102 | dino.baseY = this.height - settings.dinoGroundOffset
103 | this.state.dino = dino
104 | this.state.groundY = this.height - sprites.ground.h / 2
105 | }
106 |
107 | onFrame() {
108 | const { state } = this
109 |
110 | this.drawBackground()
111 | // this.drawFPS()
112 | this.drawGround()
113 | this.drawClouds()
114 | this.drawDino()
115 | this.drawScore()
116 |
117 | if (state.isRunning) {
118 | this.drawCacti()
119 |
120 | if (state.level > 3) {
121 | this.drawBirds()
122 | }
123 |
124 | if (state.dino.hits([state.cacti[0], state.birds[0]])) {
125 | playSound('game-over')
126 | state.gameOver = true
127 | }
128 |
129 | if (state.gameOver) {
130 | this.endGame()
131 | } else {
132 | this.updateScore()
133 | }
134 | }
135 | }
136 |
137 | onInput(type) {
138 | const { state } = this
139 |
140 | switch (type) {
141 | case 'jump': {
142 | if (state.isRunning) {
143 | if (state.dino.jump()) {
144 | playSound('jump')
145 | }
146 | } else {
147 | this.resetGame()
148 | state.dino.jump()
149 | playSound('jump')
150 | }
151 | break
152 | }
153 |
154 | case 'duck': {
155 | if (state.isRunning) {
156 | state.dino.duck(true)
157 | }
158 | break
159 | }
160 |
161 | case 'stop-duck': {
162 | if (state.isRunning) {
163 | state.dino.duck(false)
164 | }
165 | break
166 | }
167 | }
168 | }
169 |
170 | resetGame() {
171 | this.state.dino.reset()
172 | Object.assign(this.state, {
173 | settings: { ...this.defaultSettings },
174 | birds: [],
175 | cacti: [],
176 | gameOver: false,
177 | isRunning: true,
178 | level: 0,
179 | score: {
180 | blinkFrames: 0,
181 | blinks: 0,
182 | isBlinking: false,
183 | value: 0,
184 | },
185 | })
186 |
187 | this.start()
188 | }
189 |
190 | endGame() {
191 | const iconSprite = sprites.replayIcon
192 | const padding = 15
193 |
194 | this.paintText(
195 | 'G A M E O V E R',
196 | this.width / 2,
197 | this.height / 2 - padding,
198 | {
199 | font: 'PressStart2P',
200 | size: '12px',
201 | align: 'center',
202 | baseline: 'bottom',
203 | color: '#535353',
204 | }
205 | )
206 |
207 | this.paintSprite(
208 | 'replayIcon',
209 | this.width / 2 - iconSprite.w / 4,
210 | this.height / 2 - iconSprite.h / 4 + padding
211 | )
212 |
213 | this.state.isRunning = false
214 | this.drawScore()
215 | this.stop()
216 | }
217 |
218 | increaseDifficulty() {
219 | const { birds, cacti, clouds, dino, settings } = this.state
220 | const { bgSpeed, cactiSpawnRate, dinoLegsRate } = settings
221 | const { level } = this.state
222 |
223 | if (level > 4 && level < 8) {
224 | settings.bgSpeed++
225 | settings.birdSpeed = settings.bgSpeed * 0.8
226 | } else if (level > 7) {
227 | settings.bgSpeed = Math.ceil(bgSpeed * 1.1)
228 | settings.birdSpeed = settings.bgSpeed * 0.9
229 | settings.cactiSpawnRate = Math.floor(cactiSpawnRate * 0.98)
230 |
231 | if (level > 7 && level % 2 === 0 && dinoLegsRate > 3) {
232 | settings.dinoLegsRate--
233 | }
234 | }
235 |
236 | for (const bird of birds) {
237 | bird.speed = settings.birdSpeed
238 | }
239 |
240 | for (const cactus of cacti) {
241 | cactus.speed = settings.bgSpeed
242 | }
243 |
244 | for (const cloud of clouds) {
245 | cloud.speed = settings.bgSpeed
246 | }
247 |
248 | dino.legsRate = settings.dinoLegsRate
249 | }
250 |
251 | updateScore() {
252 | const { state } = this
253 |
254 | if (this.frameCount % state.settings.scoreIncreaseRate === 0) {
255 | const oldLevel = state.level
256 |
257 | state.score.value++
258 | state.level = Math.floor(state.score.value / 100)
259 |
260 | if (state.level !== oldLevel) {
261 | playSound('level-up')
262 | this.increaseDifficulty()
263 | state.score.isBlinking = true
264 | }
265 | }
266 | }
267 |
268 | drawFPS() {
269 | this.paintText('fps: ' + Math.round(this.frameRate), 0, 0, {
270 | font: 'PressStart2P',
271 | size: '12px',
272 | baseline: 'top',
273 | align: 'left',
274 | color: '#535353',
275 | })
276 | }
277 |
278 | drawBackground() {
279 | this.canvasCtx.fillStyle = '#f7f7f7'
280 | this.canvasCtx.fillRect(0, 0, this.width, this.height)
281 | }
282 |
283 | drawGround() {
284 | const { state } = this
285 | const { bgSpeed } = state.settings
286 | const groundImgWidth = sprites.ground.w / 2
287 |
288 | this.paintSprite('ground', state.groundX, state.groundY)
289 | state.groundX -= bgSpeed
290 |
291 | // append second image until first is fully translated
292 | if (state.groundX <= -groundImgWidth + this.width) {
293 | this.paintSprite('ground', state.groundX + groundImgWidth, state.groundY)
294 |
295 | if (state.groundX <= -groundImgWidth) {
296 | state.groundX = -bgSpeed
297 | }
298 | }
299 | }
300 |
301 | drawClouds() {
302 | const { clouds, settings } = this.state
303 |
304 | this.progressInstances(clouds)
305 | if (this.frameCount % settings.cloudSpawnRate === 0) {
306 | const newCloud = new Cloud()
307 | newCloud.speed = settings.bgSpeed
308 | newCloud.x = this.width
309 | newCloud.y = randInteger(20, 80)
310 | clouds.push(newCloud)
311 | }
312 | this.paintInstances(clouds)
313 | }
314 |
315 | drawDino() {
316 | const { dino } = this.state
317 |
318 | dino.nextFrame()
319 | this.paintSprite(dino.sprite, dino.x, dino.y)
320 | }
321 |
322 | drawCacti() {
323 | const { state } = this
324 | const { cacti, settings } = state
325 |
326 | this.progressInstances(cacti)
327 | if (this.frameCount % settings.cactiSpawnRate === 0) {
328 | // randomly either do or don't add cactus
329 | if (!state.birds.length && randBoolean()) {
330 | const newCacti = new Cactus(this.spriteImageData)
331 | newCacti.speed = settings.bgSpeed
332 | newCacti.x = this.width
333 | newCacti.y = this.height - newCacti.height - 2
334 | cacti.push(newCacti)
335 | }
336 | }
337 | this.paintInstances(cacti)
338 | }
339 |
340 | drawBirds() {
341 | const { birds, settings } = this.state
342 |
343 | this.progressInstances(birds)
344 | if (this.frameCount % settings.birdSpawnRate === 0) {
345 | // randomly either do or don't add bird
346 | if (randBoolean()) {
347 | const newBird = new Bird(this.spriteImageData)
348 | newBird.speed = settings.birdSpeed
349 | newBird.wingsRate = settings.birdWingsRate
350 | newBird.x = this.width
351 | // ensure birds are always at least 5px higher than a ducking dino
352 | newBird.y =
353 | this.height -
354 | Bird.maxBirdHeight -
355 | Bird.wingSpriteYShift -
356 | 5 -
357 | sprites.dinoDuckLeftLeg.h / 2 -
358 | settings.dinoGroundOffset
359 | birds.push(newBird)
360 | }
361 | }
362 | this.paintInstances(birds)
363 | }
364 |
365 | drawScore() {
366 | const { canvasCtx, state } = this
367 | const { isRunning, score, settings } = state
368 | const fontSize = 12
369 | let shouldDraw = true
370 | let drawValue = score.value
371 |
372 | if (isRunning && score.isBlinking) {
373 | score.blinkFrames++
374 |
375 | if (score.blinkFrames % settings.scoreBlinkRate === 0) {
376 | score.blinks++
377 | }
378 |
379 | if (score.blinks > 7) {
380 | score.blinkFrames = 0
381 | score.blinks = 0
382 | score.isBlinking = false
383 | } else {
384 | if (score.blinks % 2 === 0) {
385 | drawValue = Math.floor(drawValue / 100) * 100
386 | } else {
387 | shouldDraw = false
388 | }
389 | }
390 | }
391 |
392 | if (shouldDraw) {
393 | // draw the background behind it in case this is called
394 | // at a time where the background isn't re-drawn (i.e. in `endGame`)
395 | canvasCtx.fillStyle = '#f7f7f7'
396 | canvasCtx.fillRect(this.width - fontSize * 5, 0, fontSize * 5, fontSize)
397 |
398 | this.paintText((drawValue + '').padStart(5, '0'), this.width, 0, {
399 | font: 'PressStart2P',
400 | size: `${fontSize}px`,
401 | align: 'right',
402 | baseline: 'top',
403 | color: '#535353',
404 | })
405 | }
406 | }
407 |
408 | /**
409 | * For each instance in the provided array, calculate the next
410 | * frame and remove any that are no longer visible
411 | * @param {Actor[]} instances
412 | */
413 | progressInstances(instances) {
414 | for (let i = instances.length - 1; i >= 0; i--) {
415 | const instance = instances[i]
416 |
417 | instance.nextFrame()
418 | if (instance.rightX <= 0) {
419 | // remove if off screen
420 | instances.splice(i, 1)
421 | }
422 | }
423 | }
424 |
425 | /**
426 | * @param {Actor[]} instances
427 | */
428 | paintInstances(instances) {
429 | for (const instance of instances) {
430 | this.paintSprite(instance.sprite, instance.x, instance.y)
431 | }
432 | }
433 |
434 | paintSprite(spriteName, dx, dy) {
435 | const { h, w, x, y } = sprites[spriteName]
436 | this.canvasCtx.drawImage(this.spriteImage, x, y, w, h, dx, dy, w / 2, h / 2)
437 | }
438 |
439 | paintText(text, x, y, opts) {
440 | const { font = 'serif', size = '12px' } = opts
441 | const { canvasCtx } = this
442 |
443 | canvasCtx.font = `${size} ${font}`
444 | if (opts.align) canvasCtx.textAlign = opts.align
445 | if (opts.baseline) canvasCtx.textBaseline = opts.baseline
446 | if (opts.color) canvasCtx.fillStyle = opts.color
447 | canvasCtx.fillText(text, x, y)
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/lib/game/GameRunner.js:
--------------------------------------------------------------------------------
1 | export default class GameRunner {
2 | constructor() {
3 | this.looping = false
4 | this.preloaded = false
5 | this.targetFrameRate = 60
6 | this.frameCount = 0
7 | this.frameRate = 0
8 | this.paused = false
9 | this.stepFrames = null
10 | this._lastFrameTime = window.performance.now()
11 |
12 | // store this bound function so we don't have to create
13 | // one every single time we call requestAnimationFrame
14 | this.__loop = this._loop.bind(this)
15 | }
16 |
17 | async start(paused = false) {
18 | if (!this.preloaded) {
19 | if (this.preload) {
20 | await this.preload()
21 | }
22 | this.preloaded = true
23 | }
24 |
25 | if (paused) {
26 | this.paused = paused
27 | }
28 |
29 | this.looping = true
30 |
31 | if (!paused) {
32 | window.requestAnimationFrame(this.__loop)
33 | }
34 | }
35 |
36 | stop() {
37 | this.looping = false
38 | }
39 |
40 | pause() {
41 | this.paused = true
42 | }
43 |
44 | unpause() {
45 | this.paused = false
46 | }
47 |
48 | step(frames = 1) {
49 | if (typeof this.stepFrames === 'number') {
50 | this.stepFrames += frames
51 | } else {
52 | this.stepFrames = frames
53 | }
54 |
55 | this.__loop(window.performance.now())
56 | }
57 |
58 | _loop(timestamp) {
59 | const now = window.performance.now()
60 | const timeSinceLast = now - this._lastFrameTime
61 | const targetTimeBetweenFrames = 1000 / this.targetFrameRate
62 |
63 | if (timeSinceLast >= targetTimeBetweenFrames - 5) {
64 | this.onFrame()
65 | this.frameRate = 1000 / (now - this._lastFrameTime)
66 | this._lastFrameTime = now
67 | this.frameCount++
68 | }
69 |
70 | if (this.looping) {
71 | let shouldLoop = true
72 |
73 | if (this.paused) {
74 | if (typeof this.stepFrames === 'number') {
75 | if (this.stepFrames === 0) {
76 | this.stepFrames = null
77 | shouldLoop = false
78 | } else {
79 | this.stepFrames--
80 | }
81 | } else {
82 | shouldLoop = false
83 | }
84 | }
85 |
86 | if (shouldLoop) {
87 | window.requestAnimationFrame(this.__loop)
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import DinoGame from './game/DinoGame.js'
2 |
3 | const game = new DinoGame(600, 150)
4 | const isTouchDevice =
5 | 'ontouchstart' in window ||
6 | navigator.maxTouchPoints > 0 ||
7 | navigator.msMaxTouchPoints > 0
8 |
9 | if (isTouchDevice) {
10 | document.addEventListener('touchstart', ({ touches }) => {
11 | if (touches.length === 1) {
12 | game.onInput('jump')
13 | } else if (touches.length === 2) {
14 | game.onInput('duck')
15 | }
16 | })
17 |
18 | document.addEventListener('touchend', ({ touches }) => {
19 | game.onInput('stop-duck')
20 | })
21 | } else {
22 | const keycodes = {
23 | // up, spacebar
24 | JUMP: { 38: 1, 32: 1 },
25 | // down
26 | DUCK: { 40: 1 },
27 | }
28 |
29 | document.addEventListener('keydown', ({ keyCode }) => {
30 | if (keycodes.JUMP[keyCode]) {
31 | game.onInput('jump')
32 | } else if (keycodes.DUCK[keyCode]) {
33 | game.onInput('duck')
34 | }
35 | })
36 |
37 | document.addEventListener('keyup', ({ keyCode }) => {
38 | if (keycodes.DUCK[keyCode]) {
39 | game.onInput('stop-duck')
40 | }
41 | })
42 | }
43 |
44 | game.start().catch(console.error)
45 |
--------------------------------------------------------------------------------
/lib/sounds.js:
--------------------------------------------------------------------------------
1 | const AudioContext = window.AudioContext || window.webkitAudioContext
2 | const audioContext = new AudioContext()
3 | const soundNames = ['game-over', 'jump', 'level-up']
4 | const soundBuffers = {}
5 | let SOUNDS_LOADED = false
6 |
7 | loadSounds().catch(console.error)
8 | export function playSound(name) {
9 | if (SOUNDS_LOADED) {
10 | audioContext.resume()
11 | playBuffer(soundBuffers[name])
12 | }
13 | }
14 |
15 | async function loadSounds() {
16 | await Promise.all(
17 | soundNames.map(async (soundName) => {
18 | soundBuffers[soundName] = await loadBuffer(`./assets/${soundName}.mp3`)
19 | })
20 | )
21 |
22 | SOUNDS_LOADED = true
23 | }
24 |
25 | function loadBuffer(filepath) {
26 | return new Promise((resolve, reject) => {
27 | const request = new XMLHttpRequest()
28 |
29 | request.open('GET', filepath)
30 | request.responseType = 'arraybuffer'
31 | request.onload = () =>
32 | audioContext.decodeAudioData(request.response, resolve)
33 | request.onerror = reject
34 | request.send()
35 | })
36 | }
37 |
38 | function playBuffer(buffer) {
39 | const source = audioContext.createBufferSource()
40 |
41 | source.buffer = buffer
42 | source.connect(audioContext.destination)
43 | source.start()
44 | }
45 |
--------------------------------------------------------------------------------
/lib/sprites.js:
--------------------------------------------------------------------------------
1 | export default {
2 | birdUp: { h: 52, w: 84, x: 708, y: 31 },
3 | birdDown: { h: 60, w: 84, x: 708, y: 85 },
4 | cactus: { h: 92, w: 46, x: 70, y: 31 },
5 | cactusDouble: { h: 66, w: 64, x: 118, y: 31 },
6 | cactusDoubleB: { h: 92, w: 80, x: 184, y: 31 },
7 | cactusTriple: { h: 66, w: 82, x: 266, y: 31 },
8 | cloud: { h: 28, w: 92, x: 794, y: 31 },
9 | dino: { h: 86, w: 80, x: 350, y: 31 },
10 | dinoDuckLeftLeg: { h: 52, w: 110, x: 596, y: 31 },
11 | dinoDuckRightLeg: { h: 52, w: 110, x: 596, y: 85 },
12 | dinoLeftLeg: { h: 86, w: 80, x: 432, y: 31 },
13 | dinoRightLeg: { h: 86, w: 80, x: 514, y: 31 },
14 | ground: { h: 28, w: 2400, x: 0, y: 2 },
15 | replayIcon: { h: 60, w: 68, x: 0, y: 31 },
16 | }
17 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | export function getImageData(image) {
2 | const { width, height } = image
3 | const tmpCanvas = document.createElement('canvas')
4 | const ctx = tmpCanvas.getContext('2d')
5 | let result
6 |
7 | tmpCanvas.width = width
8 | tmpCanvas.height = height
9 | ctx.drawImage(image, 0, 0)
10 |
11 | result = ctx.getImageData(0, 0, width, height)
12 | tmpCanvas.remove()
13 | return result
14 | }
15 |
16 | export async function loadImage(url) {
17 | return new Promise((resolve, reject) => {
18 | const image = new Image()
19 |
20 | image.onload = () => resolve(image)
21 | image.onerror = reject
22 | image.src = url
23 | })
24 | }
25 |
26 | function getFontName(url) {
27 | const ext = url.slice(url.lastIndexOf('.'))
28 | const pathParts = url.split('/')
29 |
30 | return pathParts[pathParts.length - 1].slice(0, -1 * ext.length)
31 | }
32 |
33 | export async function loadFont(url, fontName) {
34 | if (!fontName) fontName = getFontName(url)
35 | const styleEl = document.createElement('style')
36 |
37 | styleEl.innerHTML = `
38 | @font-face {
39 | font-family: ${fontName};
40 | src: url(${url});
41 | }
42 | `
43 | document.head.appendChild(styleEl)
44 | await document.fonts.load(`12px ${fontName}`)
45 | }
46 |
47 | export function randInteger(min, max) {
48 | return Math.floor(Math.random() * (max - min + 1)) + min
49 | }
50 |
51 | export function randBoolean() {
52 | return Boolean(randInteger(0, 1))
53 | }
54 |
55 | export function randItem(arr) {
56 | return arr[randInteger(0, arr.length - 1)]
57 | }
58 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Chris Deacy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "lint": "prettier --check .",
5 | "lint-fix": "prettier --write .",
6 | "start": "reload"
7 | },
8 | "devDependencies": {
9 | "prettier": "^2.6.2",
10 | "reload": "^3.2.0"
11 | },
12 | "volta": {
13 | "node": "16.15.0",
14 | "yarn": "1.22.18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | > Remaking the chrome offline dinosaur game
9 |
10 | Note that this was previously written with p5.js but has since been refactored into a dependency-free approach. You can still see the old p5.js implementation in [the p5js branch](https://github.com/chrisdothtml/chrome-dino/tree/p5js).
11 |
12 | ## Run locally (literally)
13 |
14 | I use [volta](https://volta.sh/) to automatically install/switch my node and yarn versions. Either install volta, or check the versions I'm using via the [package.json](package.json) `volta` field.
15 |
16 | ```bash
17 | # install deps
18 | $ yarn
19 | # start server
20 | $ yarn start
21 | ```
22 |
23 | ## License
24 |
25 | [MIT](license)
26 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | cli-color@~2.0.0:
6 | version "2.0.2"
7 | resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.2.tgz#e295addbae470800def0254183c648531cdf4e3f"
8 | integrity sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==
9 | dependencies:
10 | d "^1.0.1"
11 | es5-ext "^0.10.59"
12 | es6-iterator "^2.0.3"
13 | memoizee "^0.4.15"
14 | timers-ext "^0.1.7"
15 |
16 | commander@~7.2.0:
17 | version "7.2.0"
18 | resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
19 | integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
20 |
21 | d@1, d@^1.0.1:
22 | version "1.0.1"
23 | resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
24 | integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
25 | dependencies:
26 | es5-ext "^0.10.50"
27 | type "^1.0.1"
28 |
29 | debug@2.6.9:
30 | version "2.6.9"
31 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
32 | dependencies:
33 | ms "2.0.0"
34 |
35 | define-lazy-prop@^2.0.0:
36 | version "2.0.0"
37 | resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
38 | integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
39 |
40 | depd@~1.1.2:
41 | version "1.1.2"
42 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
43 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
44 |
45 | destroy@~1.0.4:
46 | version "1.0.4"
47 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
48 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
49 |
50 | ee-first@1.1.1:
51 | version "1.1.1"
52 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
53 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
54 |
55 | encodeurl@~1.0.2:
56 | version "1.0.2"
57 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
58 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
59 |
60 | es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.59, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
61 | version "0.10.61"
62 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.61.tgz#311de37949ef86b6b0dcea894d1ffedb909d3269"
63 | integrity sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==
64 | dependencies:
65 | es6-iterator "^2.0.3"
66 | es6-symbol "^3.1.3"
67 | next-tick "^1.1.0"
68 |
69 | es6-iterator@^2.0.3:
70 | version "2.0.3"
71 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
72 | integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
73 | dependencies:
74 | d "1"
75 | es5-ext "^0.10.35"
76 | es6-symbol "^3.1.1"
77 |
78 | es6-symbol@^3.1.1, es6-symbol@^3.1.3:
79 | version "3.1.3"
80 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
81 | integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
82 | dependencies:
83 | d "^1.0.1"
84 | ext "^1.1.2"
85 |
86 | es6-weak-map@^2.0.3:
87 | version "2.0.3"
88 | resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
89 | integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
90 | dependencies:
91 | d "1"
92 | es5-ext "^0.10.46"
93 | es6-iterator "^2.0.3"
94 | es6-symbol "^3.1.1"
95 |
96 | escape-html@~1.0.3:
97 | version "1.0.3"
98 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
99 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
100 |
101 | etag@~1.8.1:
102 | version "1.8.1"
103 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
104 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
105 |
106 | event-emitter@^0.3.5:
107 | version "0.3.5"
108 | resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
109 | integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
110 | dependencies:
111 | d "1"
112 | es5-ext "~0.10.14"
113 |
114 | ext@^1.1.2:
115 | version "1.6.0"
116 | resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52"
117 | integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==
118 | dependencies:
119 | type "^2.5.0"
120 |
121 | finalhandler@~1.1.1:
122 | version "1.1.2"
123 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
124 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
125 | dependencies:
126 | debug "2.6.9"
127 | encodeurl "~1.0.2"
128 | escape-html "~1.0.3"
129 | on-finished "~2.3.0"
130 | parseurl "~1.3.3"
131 | statuses "~1.5.0"
132 | unpipe "~1.0.0"
133 |
134 | fresh@0.5.2:
135 | version "0.5.2"
136 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
137 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
138 |
139 | http-errors@1.8.1:
140 | version "1.8.1"
141 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
142 | integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
143 | dependencies:
144 | depd "~1.1.2"
145 | inherits "2.0.4"
146 | setprototypeof "1.2.0"
147 | statuses ">= 1.5.0 < 2"
148 | toidentifier "1.0.1"
149 |
150 | inherits@2.0.4:
151 | version "2.0.4"
152 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
153 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
154 |
155 | is-docker@^2.0.0, is-docker@^2.1.1:
156 | version "2.2.1"
157 | resolved "https://unpm.uberinternal.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
158 | integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
159 |
160 | is-promise@^2.2.2:
161 | version "2.2.2"
162 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
163 | integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
164 |
165 | is-wsl@^2.2.0:
166 | version "2.2.0"
167 | resolved "https://unpm.uberinternal.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
168 | integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
169 | dependencies:
170 | is-docker "^2.0.0"
171 |
172 | lru-queue@^0.1.0:
173 | version "0.1.0"
174 | resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
175 | integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=
176 | dependencies:
177 | es5-ext "~0.10.2"
178 |
179 | memoizee@^0.4.15:
180 | version "0.4.15"
181 | resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72"
182 | integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==
183 | dependencies:
184 | d "^1.0.1"
185 | es5-ext "^0.10.53"
186 | es6-weak-map "^2.0.3"
187 | event-emitter "^0.3.5"
188 | is-promise "^2.2.2"
189 | lru-queue "^0.1.0"
190 | next-tick "^1.1.0"
191 | timers-ext "^0.1.7"
192 |
193 | mime@1.6.0:
194 | version "1.6.0"
195 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
196 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
197 |
198 | minimist@~1.2.0:
199 | version "1.2.6"
200 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
201 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
202 |
203 | ms@2.0.0:
204 | version "2.0.0"
205 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
206 |
207 | ms@2.1.3:
208 | version "2.1.3"
209 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
210 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
211 |
212 | next-tick@1, next-tick@^1.1.0:
213 | version "1.1.0"
214 | resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
215 | integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
216 |
217 | on-finished@~2.3.0:
218 | version "2.3.0"
219 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
220 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
221 | dependencies:
222 | ee-first "1.1.1"
223 |
224 | open@^8.0.0:
225 | version "8.4.0"
226 | resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
227 | integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
228 | dependencies:
229 | define-lazy-prop "^2.0.0"
230 | is-docker "^2.1.1"
231 | is-wsl "^2.2.0"
232 |
233 | parseurl@~1.3.3:
234 | version "1.3.3"
235 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
236 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
237 |
238 | prettier@^2.6.2:
239 | version "2.6.2"
240 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
241 | integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
242 |
243 | querystringify@^2.1.1:
244 | version "2.2.0"
245 | resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
246 | integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
247 |
248 | range-parser@~1.2.1:
249 | version "1.2.1"
250 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
251 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
252 |
253 | reload@^3.2.0:
254 | version "3.2.0"
255 | resolved "https://registry.yarnpkg.com/reload/-/reload-3.2.0.tgz#616239a2f909b8f605a53396c7ab56cae984b8e3"
256 | integrity sha512-30iJoDvFHGbfq6tT3Vag/4RV3wkpuCOqPSM3GyeuOSSo48wKfZT/iI19oeO0GCVX0XSr+44XJ6yBiRJWqOq+sw==
257 | dependencies:
258 | cli-color "~2.0.0"
259 | commander "~7.2.0"
260 | finalhandler "~1.1.1"
261 | minimist "~1.2.0"
262 | open "^8.0.0"
263 | serve-static "~1.14.0"
264 | supervisor "~0.12.0"
265 | url-parse "~1.5.0"
266 | ws "~7.4.0"
267 |
268 | requires-port@^1.0.0:
269 | version "1.0.0"
270 | resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
271 | integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
272 |
273 | send@0.17.2:
274 | version "0.17.2"
275 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
276 | integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==
277 | dependencies:
278 | debug "2.6.9"
279 | depd "~1.1.2"
280 | destroy "~1.0.4"
281 | encodeurl "~1.0.2"
282 | escape-html "~1.0.3"
283 | etag "~1.8.1"
284 | fresh "0.5.2"
285 | http-errors "1.8.1"
286 | mime "1.6.0"
287 | ms "2.1.3"
288 | on-finished "~2.3.0"
289 | range-parser "~1.2.1"
290 | statuses "~1.5.0"
291 |
292 | serve-static@~1.14.0:
293 | version "1.14.2"
294 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa"
295 | integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==
296 | dependencies:
297 | encodeurl "~1.0.2"
298 | escape-html "~1.0.3"
299 | parseurl "~1.3.3"
300 | send "0.17.2"
301 |
302 | setprototypeof@1.2.0:
303 | version "1.2.0"
304 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
305 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
306 |
307 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
308 | version "1.5.0"
309 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
310 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
311 |
312 | supervisor@~0.12.0:
313 | version "0.12.0"
314 | resolved "https://registry.yarnpkg.com/supervisor/-/supervisor-0.12.0.tgz#de7e6337015b291851c10f3538c4a7f04917ecc1"
315 | integrity sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME=
316 |
317 | timers-ext@^0.1.7:
318 | version "0.1.7"
319 | resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
320 | integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==
321 | dependencies:
322 | es5-ext "~0.10.46"
323 | next-tick "1"
324 |
325 | toidentifier@1.0.1:
326 | version "1.0.1"
327 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
328 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
329 |
330 | type@^1.0.1:
331 | version "1.2.0"
332 | resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
333 | integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
334 |
335 | type@^2.5.0:
336 | version "2.6.0"
337 | resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f"
338 | integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==
339 |
340 | unpipe@~1.0.0:
341 | version "1.0.0"
342 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
343 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
344 |
345 | url-parse@~1.5.0:
346 | version "1.5.10"
347 | resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
348 | integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
349 | dependencies:
350 | querystringify "^2.1.1"
351 | requires-port "^1.0.0"
352 |
353 | ws@~7.4.0:
354 | version "7.4.6"
355 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
356 | integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
357 |
--------------------------------------------------------------------------------