Skip to content

Commit 454e7a7

Browse files
committed
pony-escape
0 parents  commit 454e7a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+7895
-0
lines changed

.editorconfig

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
charset = utf-8
6+
trim_trailing_whitespace = true
7+
insert_final_newline = true
8+
indent_style = space
9+
indent_size = 3
10+
11+
[*.{md,yml,yaml}]
12+
indent_size = 2

.gitignore

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Folders
2+
/*/
3+
tmp/
4+
5+
!/src/
6+
!/asset/
7+
!/cypress/
8+
9+
# Files
10+
/.*
11+
*.log
12+
*.js
13+
14+
!/.editorconfig
15+
!/.env
16+
!/.gitignore
17+
!/.now.json
18+
!/.prettier*
19+
!/.yarnrc

.now.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "ponyescape",
3+
}

.prettierrc.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
arrowParens: 'always',
3+
bracketSpacing: true,
4+
endOfLine: 'lf',
5+
printWidth: 80,
6+
quoteProps: 'consistent',
7+
semi: false,
8+
singleQuote: true,
9+
tabWidth: 3,
10+
trailingComma: 'all',
11+
useTabs: false,
12+
overrides: [
13+
{
14+
files: '*.md',
15+
options: {
16+
tabWidth: 2,
17+
},
18+
},
19+
],
20+
}

.yarnrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
network-timeout 300000

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Pony Run Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]

LICENSE

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ISC License
2+
3+
Copyright (c) 2020, Mathieu CAROFF
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Pony Escape
2+
3+
A simple like HTML5-canvas TypeScript side-scroller game.
4+
5+
It's a clone of the ["T-Rex Chrome Dino Game"](https://chromedino.com/)
6+
that can be played in Chrome when internet isn't available or at the address
7+
chrome://dino/.
8+
9+
## Contributing
10+
11+
Use `./tsc.cmd` to run typescript type checking.
12+
13+
Use `./parcel.cmd` to run the development server.
14+
15+
The project is built by now.sh using `npm run build` (on a linux machine).

asset/monster/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Monster
2+
3+
## Sources
4+
5+
- `monster-smooze-A.png` from https://www.deviantart.com/masemj/art/Here-Comes-The-Smooze-534130550 (`id: 900184` on derpibooru)

asset/monster/monster-smooze-A.png

204 KB
Loading

asset/player/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Monster
2+
3+
## Sources
4+
5+
- `rarity-930-1.png` from https://derpibooru.org/images/930, first frame, clipping path improved

asset/player/rarity-930-1.png

65.3 KB
Loading

ava.cmd

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/bash
2+
yarn run ava --watch

package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "pony-run",
3+
"description": "A simple dino-like HTML5-canvas TS game with ponies",
4+
"repository": "[email protected]:ponydevs/ponyrun.git",
5+
"license": "ISC",
6+
"scripts": {
7+
"build": "parcel build src/page.html -d public -o index.html"
8+
},
9+
"ava": {
10+
"extensions": [
11+
"ts"
12+
],
13+
"require": [
14+
"ts-node/register"
15+
],
16+
"files": [
17+
"src/**/*.test.ts"
18+
]
19+
},
20+
"devDependencies": {
21+
"@types/node": "^13.9.1",
22+
"ava": "^3.8.1",
23+
"prettier": "^1.19.1",
24+
"ts-node": "^8.6.2",
25+
"typescript": "^3.8.3"
26+
},
27+
"dependencies": {
28+
"parcel": "^1.12.4",
29+
"rxjs": "^7.0.0-beta.0"
30+
}
31+
}

parcel.cmd

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/bash
2+
yarn parcel ./src/page.html

src/core/core.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
PonyDisplay,
3+
PonyInput,
4+
Square,
5+
Player,
6+
Pair,
7+
} from '../type/ponyEscape'
8+
import { generateLabyrinth } from './labyrinth/generateLabyrinth'
9+
import { w } from '../util/window'
10+
11+
export interface LoadProp {
12+
display: PonyDisplay
13+
input: PonyInput
14+
size: Pair
15+
}
16+
17+
export let core = (prop: LoadProp) => {
18+
let { display, input, size } = prop
19+
20+
let grid = generateLabyrinth(size)
21+
22+
w.grid = grid
23+
24+
let player: Player = {
25+
x: 1,
26+
y: Math.floor(size.y / 2) * 2 - 1,
27+
}
28+
29+
let score = 0
30+
31+
input.left.subscribe(() => {
32+
player.x -= 2
33+
render()
34+
})
35+
input.right.subscribe(() => {
36+
player.x += 2
37+
render()
38+
})
39+
input.up.subscribe(() => {
40+
player.y -= 2
41+
render()
42+
})
43+
input.down.subscribe(() => {
44+
player.y += 2
45+
render()
46+
})
47+
48+
let render = () => {
49+
display.render({
50+
grid,
51+
monster: undefined,
52+
player,
53+
score,
54+
screen: 'play',
55+
})
56+
}
57+
58+
render()
59+
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createArray2d } from '../../util/array2d'
2+
import { Square, Pair, WallSquare } from '../../type/ponyEscape'
3+
4+
export let generateLabyrinth = (size: Pair) => {
5+
let twiceSize = { x: size.x * 2, y: size.y * 2 }
6+
7+
let grid = createArray2d<Square>(twiceSize, ({ y, x }) => {
8+
let isBorder = () => (x + y) % 2 === 1
9+
let isGround = () => x % 2 === 1 && y % 2 === 1
10+
11+
if (x >= size.x * 2 - 1 || y >= size.y * 2 - 1) {
12+
return {
13+
type: 'exterior',
14+
}
15+
}
16+
17+
if (isGround()) {
18+
return {
19+
type: 'ground',
20+
}
21+
}
22+
23+
let filled: WallSquare['filled'] = 'filled'
24+
if (isBorder() && Math.random() > 0.4) {
25+
filled = 'empty'
26+
}
27+
28+
return {
29+
type: 'wall',
30+
filled,
31+
visibility: 'visible',
32+
}
33+
})
34+
35+
return grid
36+
}

src/display/asset.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @ts-ignore
2+
import monsterUrl from '../../asset/monster/monster-smooze-A.png'
3+
// @ts-ignore
4+
import playerUrl from '../../asset/player/rarity-930-1.png'
5+
import { promiseObjectAll } from '../util/promise'
6+
import { loadImage } from './loadImage'
7+
8+
export interface Asset {
9+
monster: HTMLImageElement
10+
player: HTMLImageElement
11+
}
12+
13+
export let getAsset = async (): Promise<Asset> => {
14+
return promiseObjectAll<Asset>({
15+
monster: loadImage(monsterUrl),
16+
player: loadImage(playerUrl),
17+
})
18+
}

src/display/color.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export let white = [255, 255, 255]
2+
export let coal = [40, 40, 40]
3+
export let darkCoal = [20, 20, 20]
4+
export let black = [0, 0, 0]

src/display/display.ts

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { PonyDisplay, PonyRenderProp, Pair, WHPair } from '../type/ponyEscape'
2+
import { Asset } from './asset'
3+
import { getContext2d } from '../util/getContext2d'
4+
import { white, darkCoal, black, coal } from './color'
5+
import { scaleToFitIn } from '../util/scaleToFitIn'
6+
7+
export interface PonyDisplayProp {
8+
asset: Asset
9+
canvas: HTMLCanvasElement
10+
}
11+
12+
export interface ScorePosition {
13+
x: number
14+
y: number
15+
scale: number
16+
}
17+
18+
export let createDisplay = (prop: PonyDisplayProp): PonyDisplay => {
19+
let { asset, canvas } = prop
20+
let ctx = getContext2d(canvas)
21+
22+
let getGridSize = (grid: PonyRenderProp['grid']): Pair => {
23+
if (!(grid.length > 0)) throw new Error()
24+
25+
return {
26+
x: grid[0].length,
27+
y: grid.length,
28+
}
29+
}
30+
31+
let getSquareSize = (gridSize: Pair): Pair => {
32+
return {
33+
x: 800 / gridSize.x,
34+
y: 600 / gridSize.y,
35+
}
36+
}
37+
38+
let renderGrid = async (grid: PonyRenderProp['grid'], gridSize: Pair) => {
39+
let image = new ImageData(gridSize.x, gridSize.y)
40+
let { data } = image
41+
42+
grid.forEach((line, ky) => {
43+
let kky = 4 * line.length * ky
44+
line.forEach((square, kx) => {
45+
let kk = kky + 4 * kx
46+
let color: number[]
47+
if (square.type === 'exterior') {
48+
color = black
49+
} else if (square.type === 'ground') {
50+
color = coal
51+
} else if (square.visibility === 'invisible') {
52+
color = darkCoal
53+
} else if (square.filled === 'filled') {
54+
color = white
55+
} else {
56+
color = coal
57+
}
58+
;[data[kk + 0], data[kk + 1], data[kk + 2]] = color
59+
data[kk + 3] = 255
60+
})
61+
})
62+
63+
let bitmap = await createImageBitmap(image)
64+
ctx.imageSmoothingEnabled = false
65+
ctx.drawImage(bitmap, 0, 0, 800, 600)
66+
}
67+
68+
let renderCharacter = (
69+
character: Pair,
70+
squareSize: Pair,
71+
which: 'player' | 'monster',
72+
) => {
73+
let image = asset[which]
74+
75+
let fit = scaleToFitIn(image, squareSize)
76+
77+
let dx = character.x * squareSize.x
78+
let dy = character.y * squareSize.y
79+
ctx.strokeStyle = 'red'
80+
ctx.strokeRect(dx, dy, squareSize.x, squareSize.y)
81+
ctx.drawImage(image, dx + fit.x, dy + fit.y, fit.w, fit.h)
82+
}
83+
84+
let renderScore = (score: number, scorePos: ScorePosition) => {
85+
let { x, y, scale } = scorePos
86+
87+
ctx.font = `${32 * scale}px Impact`
88+
ctx.fillStyle = 'white'
89+
ctx.strokeStyle = 'black'
90+
91+
let text = `${score}`
92+
ctx.fillText(text, x, y)
93+
ctx.strokeText(text, x, y)
94+
}
95+
96+
let render = async (prop: PonyRenderProp): Promise<undefined> => {
97+
if (prop.screen === 'score') {
98+
renderScore(prop.score, { x: 350, y: 250, scale: 2 })
99+
return
100+
}
101+
102+
let gridSize = getGridSize(prop.grid)
103+
let squareSize = getSquareSize(gridSize)
104+
105+
await renderGrid(prop.grid, gridSize)
106+
renderCharacter(prop.player, squareSize, 'player')
107+
if (prop.monster !== undefined) {
108+
renderCharacter(prop.monster, squareSize, 'monster')
109+
}
110+
renderScore(prop.score, { x: 50, y: 540, scale: 1 })
111+
}
112+
113+
let me: PonyDisplay = {
114+
render,
115+
}
116+
117+
return me
118+
}

0 commit comments

Comments
 (0)