
14 changed files with 614 additions and 465 deletions
@ -0,0 +1,6 @@
|
||||
node_modules |
||||
.DS_Store |
||||
dist |
||||
dist-ssr |
||||
*.local |
||||
pnpm-lock.yaml |
@ -1,38 +1,42 @@
|
||||
import botonJugarImg from "./assets/boton_jugar.png" |
||||
import larretaImg from "./assets/Millonario Malo.png" |
||||
import fondoImg from "./assets/CieloRioCalle.png" |
||||
import edificiosImg from "./assets/Edificios.png" |
||||
import jugadorxImg from "./assets/ProtagonistaCorriendo_1.png" |
||||
import baldosaImg from "./assets/Baldosa.png" |
||||
import semillaImg from "./assets/Semilla.png" |
||||
import arbol1Img from "./assets/Árbol 1.png" |
||||
import arbol2Img from "./assets/Árbol 2.png" |
||||
import botonJugarImg from "./assets/boton_jugar.png"; |
||||
import larretaImg from "./assets/Millonario Malo.png"; |
||||
import fondoImg from "./assets/CieloRioCalle.png"; |
||||
import edificiosImg from "./assets/Edificios.png"; |
||||
import jugadorxImg from "./assets/ProtagonistaCorriendo_1.png"; |
||||
import baldosaImg from "./assets/Baldosa.png"; |
||||
import semillaImg from "./assets/Semilla.png"; |
||||
import arbol1Img from "./assets/Árbol 1.png"; |
||||
import arbol2Img from "./assets/Árbol 2.png"; |
||||
|
||||
function loadImage(url: string): Promise<HTMLImageElement> { |
||||
return new Promise((resolve, reject) => { |
||||
let img = new Image() |
||||
img.onload = () => resolve(img) |
||||
img.onerror = e => reject(e) |
||||
img.src = url |
||||
}) |
||||
return new Promise((resolve, reject) => { |
||||
let img = new Image(); |
||||
img.onload = () => resolve(img); |
||||
img.onerror = (e) => reject(e); |
||||
img.src = url; |
||||
}); |
||||
} |
||||
|
||||
export const assetUrls = { |
||||
botonJugar: botonJugarImg, |
||||
larreta: larretaImg, |
||||
fondo: fondoImg, |
||||
edificios: edificiosImg, |
||||
jugadorx: jugadorxImg, |
||||
baldosa: baldosaImg, |
||||
semilla: semillaImg, |
||||
arbol1: arbol1Img, |
||||
arbol2: arbol2Img, |
||||
} |
||||
export type Assets = { [key in keyof typeof assetUrls]: HTMLImageElement } |
||||
botonJugar: botonJugarImg, |
||||
larreta: larretaImg, |
||||
fondo: fondoImg, |
||||
edificios: edificiosImg, |
||||
jugadorx: jugadorxImg, |
||||
baldosa: baldosaImg, |
||||
semilla: semillaImg, |
||||
arbol1: arbol1Img, |
||||
arbol2: arbol2Img, |
||||
}; |
||||
export type Assets = { [key in keyof typeof assetUrls]: HTMLImageElement }; |
||||
|
||||
const assets = Object.fromEntries(Object.entries(assetUrls).map(([name, url]) => [name, loadImage(url)])) |
||||
const assets = Object.fromEntries( |
||||
Object.entries(assetUrls).map(([name, url]) => [name, loadImage(url)]) |
||||
); |
||||
|
||||
export async function loadAssets() { |
||||
const imgs = await Promise.all(Object.values(assets)) |
||||
return Object.fromEntries(imgs.map((img, i) => [Object.keys(assetUrls)[i], img])) as Assets |
||||
const imgs = await Promise.all(Object.values(assets)); |
||||
return Object.fromEntries( |
||||
imgs.map((img, i) => [Object.keys(assetUrls)[i], img]) |
||||
) as Assets; |
||||
} |
||||
|
@ -1,221 +1,255 @@
|
||||
import { Juego } from './main' |
||||
import { Sprite } from './sprite' |
||||
import { Box, boxCollision, drawText, randomFromArray } from './utils' |
||||
|
||||
const ENEMIES_NUM = 12 |
||||
const SEED_COOLDOWN = 300 |
||||
const MAP_MIN = 1000 |
||||
const MAP_MAX = 5000 |
||||
const MAP_SIZE = MAP_MAX - MAP_MIN |
||||
const TREES_TO_WIN = 30 |
||||
const TIME = 2 * 60 * 1000 |
||||
import { Juego } from "./main"; |
||||
import { Sprite } from "./sprite"; |
||||
import { Box, boxCollision, drawText, randomFromArray } from "./utils"; |
||||
|
||||
const ENEMIES_NUM = 12; |
||||
const SEED_COOLDOWN = 300; |
||||
const MAP_MIN = 1000; |
||||
const MAP_MAX = 5000; |
||||
const MAP_SIZE = MAP_MAX - MAP_MIN; |
||||
const TREES_TO_WIN = 30; |
||||
const TIME = 2 * 60 * 1000; |
||||
|
||||
export type State = { |
||||
current: "jugando" |
||||
pos: { x: number } |
||||
view: { x: number } |
||||
side: "left" | "right" |
||||
enemies: { x: number }[] |
||||
seeds: { x: number; velocity: { x: number } }[] |
||||
trees: { x: number, sprite: Sprite }[] |
||||
time: number, |
||||
seedCooldown: number, |
||||
} |
||||
current: "jugando"; |
||||
pos: { x: number }; |
||||
view: { x: number }; |
||||
side: "left" | "right"; |
||||
enemies: { x: number }[]; |
||||
seeds: { x: number; velocity: { x: number } }[]; |
||||
trees: { x: number; sprite: Sprite }[]; |
||||
time: number; |
||||
seedCooldown: number; |
||||
}; |
||||
|
||||
export function createJugandoState(): State { |
||||
return { |
||||
current: "jugando", |
||||
pos: { x: MAP_MIN + MAP_SIZE / 2 }, |
||||
view: { x: 0 }, |
||||
side: "right", |
||||
enemies: [], |
||||
seeds: [], |
||||
trees: [], |
||||
time: TIME, |
||||
seedCooldown: 0, |
||||
} |
||||
return { |
||||
current: "jugando", |
||||
pos: { x: MAP_MIN + MAP_SIZE / 2 }, |
||||
view: { x: 0 }, |
||||
side: "right", |
||||
enemies: [], |
||||
seeds: [], |
||||
trees: [], |
||||
time: TIME, |
||||
seedCooldown: 0, |
||||
}; |
||||
} |
||||
|
||||
export function update(juego: Juego<State>, dt: number) { |
||||
juego.state.time -= dt |
||||
if (juego.state.time < 0) { |
||||
(juego as Juego<any>).state = { current: "lose" } |
||||
return |
||||
} |
||||
|
||||
const playerSpeed = juego.canvas.width * 0.15 |
||||
const enemySpeed = juego.canvas.width * 0.05 |
||||
if (juego.keyboard.keys.d || juego.keyboard.keys.ArrowRight) { |
||||
juego.state.side = "right" |
||||
juego.state.pos.x += (dt / 1000) * playerSpeed |
||||
} |
||||
if (juego.keyboard.keys.a || juego.keyboard.keys.ArrowLeft) { |
||||
juego.state.side = "left" |
||||
juego.state.pos.x -= (dt / 1000) * playerSpeed |
||||
} |
||||
|
||||
if (juego.state.pos.x < MAP_MIN) juego.state.pos.x = MAP_MIN |
||||
if (juego.state.pos.x > MAP_MAX) juego.state.pos.x = MAP_MAX |
||||
|
||||
juego.state.seedCooldown -= dt |
||||
if (juego.keyboard.keys[' '] && juego.state.seedCooldown < 0) { |
||||
const seedSpeed = juego.canvas.width * 0.7 |
||||
juego.state.seeds.push({ |
||||
x: juego.state.pos.x, |
||||
velocity: { x: juego.state.side === "left" ? -seedSpeed : seedSpeed }, |
||||
}) |
||||
juego.state.seedCooldown = SEED_COOLDOWN |
||||
} |
||||
for (const seed of juego.state.seeds) { |
||||
seed.x += (dt / 1000) * seed.velocity.x |
||||
seed.velocity.x *= 0.97 |
||||
for (const enemy of juego.state.enemies) { |
||||
if (boxCollision({ |
||||
x: seed.x, |
||||
y: getFloorY(juego) - juego.sprites.semilla.getHeight(juego), |
||||
width: juego.sprites.semilla.getWidth(juego), |
||||
height: juego.sprites.semilla.getHeight(juego), |
||||
}, { |
||||
x: enemy.x, |
||||
y: getFloorY(juego) - juego.sprites.larreta.getHeight(juego), |
||||
width: juego.sprites.larreta.getWidth(juego), |
||||
height: juego.sprites.larreta.getHeight(juego), |
||||
})) { |
||||
juego.state.seeds = juego.state.seeds.filter(s => s.x !== seed.x) |
||||
juego.state.enemies = juego.state.enemies.filter(e => e.x !== enemy.x) |
||||
juego.state.trees.push({ |
||||
x: enemy.x, |
||||
sprite: randomFromArray([juego.sprites.arbol1, juego.sprites.arbol2]), |
||||
}) |
||||
} |
||||
} |
||||
if (Math.abs(seed.velocity.x) < 100) |
||||
juego.state.seeds = juego.state.seeds.filter(s => s.velocity.x !== seed.velocity.x) |
||||
} |
||||
|
||||
if (juego.state.trees.length >= TREES_TO_WIN) { |
||||
(juego as Juego<any>).state = { current: "win" } |
||||
return |
||||
} |
||||
|
||||
while (juego.state.enemies.length < ENEMIES_NUM) { |
||||
const x = Math.random() * MAP_SIZE + MAP_MIN |
||||
// Don't spawn enemies too close
|
||||
if (Math.abs(juego.state.pos.x - x) < 300) continue |
||||
juego.state.enemies.push({ x }) |
||||
} |
||||
|
||||
juego.state.time -= dt; |
||||
if (juego.state.time < 0) { |
||||
(juego as Juego<any>).state = { current: "lose" }; |
||||
return; |
||||
} |
||||
|
||||
const playerSpeed = juego.canvas.width * 0.15; |
||||
const enemySpeed = juego.canvas.width * 0.05; |
||||
if (juego.keyboard.keys.d || juego.keyboard.keys.ArrowRight) { |
||||
juego.state.side = "right"; |
||||
juego.state.pos.x += (dt / 1000) * playerSpeed; |
||||
} |
||||
if (juego.keyboard.keys.a || juego.keyboard.keys.ArrowLeft) { |
||||
juego.state.side = "left"; |
||||
juego.state.pos.x -= (dt / 1000) * playerSpeed; |
||||
} |
||||
|
||||
if (juego.state.pos.x < MAP_MIN) juego.state.pos.x = MAP_MIN; |
||||
if (juego.state.pos.x > MAP_MAX) juego.state.pos.x = MAP_MAX; |
||||
|
||||
juego.state.seedCooldown -= dt; |
||||
if (juego.keyboard.keys[" "] && juego.state.seedCooldown < 0) { |
||||
const seedSpeed = juego.canvas.width * 0.7; |
||||
juego.state.seeds.push({ |
||||
x: juego.state.pos.x, |
||||
velocity: { x: juego.state.side === "left" ? -seedSpeed : seedSpeed }, |
||||
}); |
||||
juego.state.seedCooldown = SEED_COOLDOWN; |
||||
} |
||||
for (const seed of juego.state.seeds) { |
||||
seed.x += (dt / 1000) * seed.velocity.x; |
||||
seed.velocity.x *= 0.97; |
||||
for (const enemy of juego.state.enemies) { |
||||
const distance = enemy.x - juego.state.pos.x |
||||
if (distance < 0) { |
||||
enemy.x += (dt / 1000) * enemySpeed |
||||
} else { |
||||
enemy.x -= (dt / 1000) * enemySpeed |
||||
} |
||||
if ( |
||||
boxCollision( |
||||
{ |
||||
x: seed.x, |
||||
y: getFloorY(juego) - juego.sprites.semilla.getHeight(juego), |
||||
width: juego.sprites.semilla.getWidth(juego), |
||||
height: juego.sprites.semilla.getHeight(juego), |
||||
}, |
||||
{ |
||||
x: enemy.x, |
||||
y: getFloorY(juego) - juego.sprites.larreta.getHeight(juego), |
||||
width: juego.sprites.larreta.getWidth(juego), |
||||
height: juego.sprites.larreta.getHeight(juego), |
||||
} |
||||
) |
||||
) { |
||||
juego.state.seeds = juego.state.seeds.filter((s) => s.x !== seed.x); |
||||
juego.state.enemies = juego.state.enemies.filter( |
||||
(e) => e.x !== enemy.x |
||||
); |
||||
juego.state.trees.push({ |
||||
x: enemy.x, |
||||
sprite: randomFromArray([juego.sprites.arbol1, juego.sprites.arbol2]), |
||||
}); |
||||
} |
||||
} |
||||
if (Math.abs(seed.velocity.x) < 100) |
||||
juego.state.seeds = juego.state.seeds.filter( |
||||
(s) => s.velocity.x !== seed.velocity.x |
||||
); |
||||
} |
||||
|
||||
if (juego.state.trees.length >= TREES_TO_WIN) { |
||||
(juego as Juego<any>).state = { current: "win" }; |
||||
return; |
||||
} |
||||
|
||||
while (juego.state.enemies.length < ENEMIES_NUM) { |
||||
const x = Math.random() * MAP_SIZE + MAP_MIN; |
||||
// Don't spawn enemies too close
|
||||
if (Math.abs(juego.state.pos.x - x) < 300) continue; |
||||
juego.state.enemies.push({ x }); |
||||
} |
||||
|
||||
for (const enemy of juego.state.enemies) { |
||||
const distance = enemy.x - juego.state.pos.x; |
||||
if (distance < 0) { |
||||
enemy.x += (dt / 1000) * enemySpeed; |
||||
} else { |
||||
enemy.x -= (dt / 1000) * enemySpeed; |
||||
} |
||||
} |
||||
|
||||
juego.state.view.x = -juego.state.pos.x + juego.canvas.width / 2 - juego.sprites.jugadorx.getWidth(juego) / 2 |
||||
juego.state.view.x = |
||||
-juego.state.pos.x + |
||||
juego.canvas.width / 2 - |
||||
juego.sprites.jugadorx.getWidth(juego) / 2; |
||||
} |
||||
|
||||
function drawJugadorx(juego: Juego<State>) { |
||||
const floorY = getFloorY(juego) |
||||
juego.sprites.jugadorx.draw( |
||||
juego, |
||||
juego.state.pos.x + juego.state.view.x, |
||||
floorY - juego.sprites.jugadorx.getHeight(juego), |
||||
0, |
||||
juego.state.side === "left", |
||||
) |
||||
const floorY = getFloorY(juego); |
||||
juego.sprites.jugadorx.draw( |
||||
juego, |
||||
juego.state.pos.x + juego.state.view.x, |
||||
floorY - juego.sprites.jugadorx.getHeight(juego), |
||||
0, |
||||
juego.state.side === "left" |
||||
); |
||||
} |
||||
|
||||
function drawTrees(juego: Juego<State>) { |
||||
const floorY = getFloorY(juego) |
||||
for (const tree of juego.state.trees) { |
||||
const height = tree.sprite.getHeight(juego) |
||||
tree.sprite.draw( |
||||
juego, |
||||
tree.x + juego.state.view.x, |
||||
floorY - height, |
||||
) |
||||
} |
||||
const floorY = getFloorY(juego); |
||||
for (const tree of juego.state.trees) { |
||||
const height = tree.sprite.getHeight(juego); |
||||
tree.sprite.draw(juego, tree.x + juego.state.view.x, floorY - height); |
||||
} |
||||
} |
||||
function drawEnemies(juego: Juego<State>) { |
||||
const height = juego.sprites.larreta.getHeight(juego) |
||||
const floorY = getFloorY(juego) |
||||
for (const enemy of juego.state.enemies) { |
||||
juego.sprites.larreta.draw( |
||||
juego, |
||||
enemy.x + juego.state.view.x, |
||||
floorY - height, |
||||
) |
||||
} |
||||
const height = juego.sprites.larreta.getHeight(juego); |
||||
const floorY = getFloorY(juego); |
||||
for (const enemy of juego.state.enemies) { |
||||
juego.sprites.larreta.draw( |
||||
juego, |
||||
enemy.x + juego.state.view.x, |
||||
floorY - height |
||||
); |
||||
} |
||||
} |
||||
|
||||
function drawSeeds(juego: Juego<State>) { |
||||
const height = juego.sprites.semilla.getHeight(juego) |
||||
const floorY = getFloorY(juego) |
||||
for (const seed of juego.state.seeds) { |
||||
juego.sprites.semilla.draw( |
||||
juego, |
||||
seed.x + juego.state.view.x, |
||||
floorY - height, |
||||
) |
||||
} |
||||
const height = juego.sprites.semilla.getHeight(juego); |
||||
const floorY = getFloorY(juego); |
||||
for (const seed of juego.state.seeds) { |
||||
juego.sprites.semilla.draw( |
||||
juego, |
||||
seed.x + juego.state.view.x, |
||||
floorY - height |
||||
); |
||||
} |
||||
} |
||||
|
||||
function getFloorY(juego: Juego<State>): number { |
||||
return juego.canvas.height * 65 / 96 |
||||
return (juego.canvas.height * 65) / 96; |
||||
} |
||||
|
||||
function drawBackground( |
||||
juego: Juego<State>, |
||||
y: number, |
||||
height: number, |
||||
img: HTMLImageElement, |
||||
sourceBox?: Box, |
||||
juego: Juego<State>, |
||||
y: number, |
||||
height: number, |
||||
img: HTMLImageElement, |
||||
sourceBox?: Box |
||||
) { |
||||
const aspect = img.width / img.height |
||||
const width = height * aspect |
||||
for (let i = 0; i < 10; i++) { |
||||
if (sourceBox) { |
||||
juego.ctx.drawImage( |
||||
img, |
||||
sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height, |
||||
i * (width - 1) + juego.state.view.x, y, width, height, |
||||
) |
||||
} else { |
||||
juego.ctx.drawImage(img, i * (width - 1) + juego.state.view.x, y, width, height) |
||||
} |
||||
const aspect = img.width / img.height; |
||||
const width = height * aspect; |
||||
for (let i = 0; i < 10; i++) { |
||||
if (sourceBox) { |
||||
juego.ctx.drawImage( |
||||
img, |
||||
sourceBox.x, |
||||
sourceBox.y, |
||||
sourceBox.width, |
||||
sourceBox.height, |
||||
i * (width - 1) + juego.state.view.x, |
||||
y, |
||||
width, |
||||
height |
||||
); |
||||
} else { |
||||
juego.ctx.drawImage( |
||||
img, |
||||
i * (width - 1) + juego.state.view.x, |
||||
y, |
||||
width, |
||||
height |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
export function draw(juego: Juego<State>, timestamp: number) { |
||||
drawBackground(juego, 0, juego.canvas.height, juego.assets.fondo) |
||||
drawBackground( |
||||
juego, 0, getFloorY(juego), juego.assets.edificios,
|
||||
{ x: 0, y: 0, width: 89, height: 48 }) |
||||
|
||||
drawTrees(juego) |
||||
drawEnemies(juego) |
||||
|
||||
drawJugadorx(juego) |
||||
drawSeeds(juego) |
||||
|
||||
juego.ctx.fillStyle = "white" |
||||
drawText(juego, 'Usá las flechitas para moverte, y espacio para "disparar" semillas.', { x: 0, y: 100 }, {}) |
||||
|
||||
const arbolesBox = drawText( |
||||
juego, `Arboles: ${juego.state.trees.length}/${TREES_TO_WIN}`, |
||||
{ x: juego.canvas.width - 10, y: 10 }, |
||||
{ bold: true, align: 'right' }) |
||||
|
||||
const tiempoBox = drawText( |
||||
juego, `Tiempo restante`, |
||||
{ x: 10, y: 10 }, |
||||
{ bold: false, align: 'left' }) |
||||
|
||||
juego.ctx.fillRect( |
||||
10, tiempoBox.y + tiempoBox.height, |
||||
(juego.canvas.width - arbolesBox.width - 20) * (juego.state.time / TIME), |
||||
30) |
||||
drawBackground(juego, 0, juego.canvas.height, juego.assets.fondo); |
||||
drawBackground(juego, 0, getFloorY(juego), juego.assets.edificios, { |
||||
x: 0, |
||||
y: 0, |
||||
width: 89, |
||||
height: 48, |
||||
}); |
||||
|
||||
drawTrees(juego); |
||||
drawEnemies(juego); |
||||
|
||||
drawJugadorx(juego); |
||||
drawSeeds(juego); |
||||
|
||||
juego.ctx.fillStyle = "white"; |
||||
drawText( |
||||
juego, |
||||
'Usá las flechitas para moverte, y espacio para "disparar" semillas.', |
||||
{ x: 0, y: 100 }, |
||||
{} |
||||
); |
||||
|
||||
const arbolesBox = drawText( |
||||
juego, |
||||
`Arboles: ${juego.state.trees.length}/${TREES_TO_WIN}`, |
||||
{ x: juego.canvas.width - 10, y: 10 }, |
||||
{ bold: true, align: "right" } |
||||
); |
||||
|
||||
const tiempoBox = drawText( |
||||
juego, |
||||
`Tiempo restante`, |
||||
{ x: 10, y: 10 }, |
||||
{ bold: false, align: "left" } |
||||
); |
||||
|
||||
juego.ctx.fillRect( |
||||
10, |
||||
tiempoBox.y + tiempoBox.height, |
||||
(juego.canvas.width - arbolesBox.width - 20) * (juego.state.time / TIME), |
||||
30 |
||||
); |
||||
} |
||||
|
@ -1,18 +1,20 @@
|
||||
import { Juego } from './main' |
||||
import { drawText } from './utils' |
||||
import { Juego } from "./main"; |
||||
import { drawText } from "./utils"; |
||||
|
||||
export type State = { |
||||
current: "lose" |
||||
} |
||||
current: "lose"; |
||||
}; |
||||
|
||||
export function update(juego: Juego<State>, dt: number) { |
||||
} |
||||
export function update(juego: Juego<State>, dt: number) {} |
||||
|
||||
export function draw(juego: Juego<State>, timestamp: number) { |
||||
drawText(juego, '¡Te convirtieron en baldosa!', |
||||
{ x: juego.canvas.width / 2, y: 100 }, |
||||
{ bold: true, size: 3, align: 'center' }) |
||||
drawText( |
||||
juego, |
||||
"¡Te convirtieron en baldosa!", |
||||
{ x: juego.canvas.width / 2, y: 100 }, |
||||
{ bold: true, size: 3, align: "center" } |
||||
); |
||||
|
||||
const width = juego.sprites.baldosa.getWidth(juego) |
||||
juego.sprites.baldosa.draw(juego, juego.canvas.width / 2 - width / 2, 100) |
||||
const width = juego.sprites.baldosa.getWidth(juego); |
||||
juego.sprites.baldosa.draw(juego, juego.canvas.width / 2 - width / 2, 100); |
||||
} |
||||
|
@ -1,131 +1,181 @@
|
||||
import './style.css' |
||||
import "./style.css"; |
||||
|
||||
import * as welcome from "./welcome" |
||||
import * as jugando from "./jugando" |
||||
import * as win from "./win" |
||||
import * as lose from "./lose" |
||||
import { Assets, loadAssets } from './assets' |
||||
import { loadSprite, Sprite } from './sprite' |
||||
import * as welcome from "./welcome"; |
||||
import * as jugando from "./jugando"; |
||||
import * as win from "./win"; |
||||
import * as lose from "./lose"; |
||||
import { Assets, loadAssets } from "./assets"; |
||||
import { loadSprite, Sprite } from "./sprite"; |
||||
|
||||
export type State = welcome.State | jugando.State | win.State | lose.State |
||||
export type State = welcome.State | jugando.State | win.State | lose.State; |
||||
|
||||
export type Juego<T extends State> = { |
||||
canvas: HTMLCanvasElement |
||||
ctx: CanvasRenderingContext2D |
||||
assets: Assets |
||||
sprites: { [key in "jugadorx" | "baldosa" | "larreta" | "semilla" | "arbol1" | "arbol2"]: Sprite } |
||||
mouse: { x: number; y: number, down: boolean } |
||||
keyboard: { keys: { [key: string]: boolean } } |
||||
state: T |
||||
} |
||||
canvas: HTMLCanvasElement; |
||||
ctx: CanvasRenderingContext2D; |
||||
assets: Assets; |
||||
sprites: { |
||||
[key in |
||||
| "jugadorx" |
||||
| "baldosa" |
||||
| "larreta" |
||||
| "semilla" |
||||
| "arbol1" |
||||
| "arbol2"]: Sprite; |
||||
}; |
||||
mouse: { x: number; y: number; down: boolean }; |
||||
keyboard: { keys: { [key: string]: boolean } }; |
||||
state: T; |
||||
}; |
||||
|
||||
function update(juego: Juego<any>, dt: number) { |
||||
switch (juego.state.current) { |
||||
case "welcome": |
||||
welcome.update(juego, dt) |
||||
break |
||||
case "jugando": |
||||
jugando.update(juego, dt) |
||||
break |
||||
case "win": |
||||
win.update(juego, dt) |
||||
break |
||||
case "lose": |
||||
lose.update(juego, dt) |
||||
break |
||||
} |
||||
switch (juego.state.current) { |
||||
case "welcome": |
||||
welcome.update(juego, dt); |
||||
break; |
||||
case "jugando": |
||||
jugando.update(juego, dt); |
||||
break; |
||||
case "win": |
||||
win.update(juego, dt); |
||||
break; |
||||
case "lose": |
||||
lose.update(juego, dt); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
function draw(juego: Juego<any>, timestamp: number) { |
||||
const { width, height } = juego.canvas |
||||
|
||||
juego.ctx.fillStyle = "white" |
||||
juego.ctx.fillRect(0, 0, width, height) |
||||
juego.ctx.fillStyle = "black" |
||||
switch (juego.state.current) { |
||||
case "welcome": |
||||
welcome.draw(juego, timestamp) |
||||
break |
||||
case "jugando": |
||||
jugando.draw(juego, timestamp) |
||||
break |
||||
case "win": |
||||
win.draw(juego, timestamp) |
||||
break |
||||
case "lose": |
||||
lose.draw(juego, timestamp) |
||||
break |
||||
} |
||||
const { width, height } = juego.canvas; |
||||
|
||||
juego.ctx.fillStyle = "white"; |
||||
juego.ctx.fillRect(0, 0, width, height); |
||||
juego.ctx.fillStyle = "black"; |
||||
switch (juego.state.current) { |
||||
case "welcome": |
||||
welcome.draw(juego, timestamp); |
||||
break; |
||||
case "jugando": |
||||
jugando.draw(juego, timestamp); |
||||
break; |
||||
case "win": |
||||
win.draw(juego, timestamp); |
||||
break; |
||||
case "lose": |
||||
lose.draw(juego, timestamp); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
function resizeCanvas(canvas: HTMLCanvasElement) { |
||||
canvas.width = canvas.clientWidth |
||||
canvas.height = canvas.clientHeight |
||||
canvas.width = canvas.clientWidth; |
||||
canvas.height = canvas.clientHeight; |
||||
} |
||||
|
||||
async function initJuego() { |
||||
const canvas = document.querySelector<HTMLCanvasElement>("#juego")! |
||||
const ctx = canvas.getContext("2d", { |
||||
alpha: false, |
||||
desynchronized: true, |
||||
})! |
||||
|
||||
window.addEventListener("resize", () => resizeCanvas(canvas), false) |
||||
resizeCanvas(canvas) |
||||
|
||||
const assets = await loadAssets() |
||||
|
||||
const sprites = { |
||||
jugadorx: loadSprite(assets.jugadorx, 65, 94, juego => juego.canvas.height / 4), |
||||
baldosa: loadSprite(assets.baldosa, 72, 72, juego => juego.canvas.height * 0.8), |
||||
larreta: loadSprite(assets.larreta, 50, 71, juego => juego.canvas.height / 4), |
||||
semilla: loadSprite(assets.semilla, 72, 73, juego => juego.canvas.height / 8), |
||||
arbol1: loadSprite(assets.arbol1, 72, 68, juego => juego.canvas.height / 3), |
||||
arbol2: loadSprite(assets.arbol2, 72, 56, juego => juego.canvas.height / 4), |
||||
} |
||||
|
||||
let juego: Juego<welcome.State> = { |
||||
canvas, |
||||
ctx, |
||||
assets, |
||||
sprites, |
||||
mouse: { x: 0, y: 0, down: false }, |
||||
keyboard: { keys: {} }, |
||||
state: { |
||||
current: "welcome", |
||||
}, |
||||
}; |
||||
(window as any).juego = juego |
||||
juego.ctx.imageSmoothingEnabled = false |
||||
|
||||
canvas.addEventListener("mousemove", e => { |
||||
juego.mouse.x = e.clientX |
||||
juego.mouse.y = e.clientY |
||||
}, false) |
||||
canvas.addEventListener("mousedown", () => { |
||||
juego.mouse.down = true |
||||
}, false) |
||||
canvas.addEventListener("mouseup", () => { |
||||
juego.mouse.down = false |
||||
}, false) |
||||
window.addEventListener("keydown", e => { |
||||
juego.keyboard.keys[e.key] = true |
||||
}) |
||||
window.addEventListener("keyup", e => { |
||||
juego.keyboard.keys[e.key] = false |
||||
}) |
||||
|
||||
let lastRender = 0 |
||||
const loop: FrameRequestCallback = timestamp => { |
||||
const progress = timestamp - lastRender |
||||
|
||||
update(juego, progress) |
||||
draw(juego, timestamp) |
||||
|
||||
lastRender = timestamp |
||||
window.requestAnimationFrame(loop) |
||||
} |
||||
|
||||
window.requestAnimationFrame(loop) |
||||
const canvas = document.querySelector<HTMLCanvasElement>("#juego")!; |
||||
const ctx = canvas.getContext("2d", { |
||||
alpha: false, |
||||
desynchronized: true, |
||||
})!; |
||||
|
||||
window.addEventListener("resize", () => resizeCanvas(canvas), false); |
||||
resizeCanvas(canvas); |
||||
|
||||
const assets = await loadAssets(); |
||||
|
||||
const sprites = { |
||||
jugadorx: loadSprite( |
||||
assets.jugadorx, |
||||
65, |
||||
94, |
||||
(juego) => juego.canvas.height / 4 |
||||
), |
||||
baldosa: loadSprite( |
||||
assets.baldosa, |
||||
72, |
||||
72, |
||||
(juego) => juego.canvas.height * 0.8 |
||||
), |
||||
larreta: loadSprite( |
||||
assets.larreta, |
||||
50, |
||||
71, |
||||
(juego) => juego.canvas.height / 4 |
||||
), |
||||
semilla: loadSprite( |
||||
assets.semilla, |
||||
72, |
||||
73, |
||||
(juego) => juego.canvas.height / 8 |
||||
), |
||||
arbol1: loadSprite( |
||||
assets.arbol1, |
||||
72, |
||||
68, |
||||
(juego) => juego.canvas.height / 3 |
||||
), |
||||
arbol2: loadSprite( |
||||
assets.arbol2, |
||||
72, |
||||
56, |
||||
(juego) => juego.canvas.height / 4 |
||||
), |
||||
}; |
||||
|
||||
let juego: Juego<welcome.State> = { |
||||
canvas, |
||||
ctx, |
||||
assets, |
||||
sprites, |
||||
mouse: { x: 0, y: 0, down: false }, |
||||
keyboard: { keys: {} }, |
||||
state: { |
||||
current: "welcome", |
||||
}, |
||||
}; |
||||
(window as any).juego = juego; |
||||
juego.ctx.imageSmoothingEnabled = false; |
||||
|
||||
canvas.addEventListener( |
||||
"mousemove", |
||||
(e) => { |
||||
juego.mouse.x = e.clientX; |
||||
juego.mouse.y = e.clientY; |
||||
}, |
||||
false |
||||
); |
||||
canvas.addEventListener( |
||||
"mousedown", |
||||
() => { |
||||
juego.mouse.down = true; |
||||
}, |
||||
false |
||||
); |
||||
canvas.addEventListener( |
||||
"mouseup", |
||||
() => { |
||||
juego.mouse.down = false; |
||||
}, |
||||
false |
||||
); |
||||
window.addEventListener("keydown", (e) => { |
||||
juego.keyboard.keys[e.key] = true; |
||||
}); |
||||
window.addEventListener("keyup", (e) => { |
||||
juego.keyboard.keys[e.key] = false; |
||||
}); |
||||
|
||||
let lastRender = 0; |
||||
const loop: FrameRequestCallback = (timestamp) => { |
||||
const progress = timestamp - lastRender; |
||||
|
||||
update(juego, progress); |
||||
draw(juego, timestamp); |
||||
|
||||
lastRender = timestamp; |
||||
window.requestAnimationFrame(loop); |
||||
}; |
||||
|
||||
window.requestAnimationFrame(loop); |
||||
} |
||||
initJuego() |
||||
initJuego(); |
||||
|
@ -1,65 +1,80 @@
|
||||
import { Juego } from "./main" |
||||
import { Juego } from "./main"; |
||||
|
||||
export type Sprite = { |
||||
getWidth: (juego: Juego<any>) => number; |
||||
getHeight: (juego: Juego<any>) => number; |
||||
draw: (juego: Juego<any>, x: number, y: number, spriteIndex?: number, flipped?: boolean) => void; |
||||
width: number; |
||||
height: number; |
||||
} |
||||
getWidth: (juego: Juego<any>) => number; |
||||
getHeight: (juego: Juego<any>) => number; |
||||
draw: ( |
||||
juego: Juego<any>, |
||||
x: number, |
||||
y: number, |
||||
spriteIndex?: number, |
||||
flipped?: boolean |
||||
) => void; |
||||
width: number; |
||||
height: number; |
||||
}; |
||||
|
||||
// Sprites are image assets that have a grid of tiles to animate
|
||||
export function loadSprite( |
||||
img: HTMLImageElement, |
||||
// The size of each tile
|
||||
width: number, height: number, |
||||
// Calculates the size of the sprite when rendering
|
||||
getHeight: (juego: Juego<any>) => number, |
||||
img: HTMLImageElement, |
||||
// The size of each tile
|
||||
width: number, |
||||
height: number, |
||||
// Calculates the size of the sprite when rendering
|
||||
getHeight: (juego: Juego<any>) => number |
||||
): Sprite { |
||||
const aspect = width / height |
||||
const rowSize = Math.floor(img.width / width) |
||||
if (img.width / width !== rowSize) { |
||||
console.warn(`The sprite grid for ${img.src} has extra space after rows, are you sure the width/height of each tile is right?`) |
||||
} |
||||
const aspect = width / height; |
||||
const rowSize = Math.floor(img.width / width); |
||||
if (img.width / width !== rowSize) { |
||||
console.warn( |
||||
`The sprite grid for ${img.src} has extra space after rows, are you sure the width/height of each tile is right?` |
||||
); |
||||
} |
||||
|
||||
function getWidth(juego: Juego<any>) { |
||||
return getHeight(juego) * aspect |
||||
} |
||||
function getWidth(juego: Juego<any>) { |
||||
return getHeight(juego) * aspect; |
||||
} |
||||
|
||||
function draw(juego: Juego<any>, x: number, y: number, spriteIndex = 0, flipped = false) { |
||||
const drawHeight = getHeight(juego) |
||||
const drawWidth = getWidth(juego) |
||||
const tileX = spriteIndex % rowSize |
||||
const tileY = Math.floor(spriteIndex / rowSize) |
||||
if (flipped) { |
||||
juego.ctx.save() |
||||
juego.ctx.scale(-1, 1) |
||||
juego.ctx.drawImage( |
||||
img, |
||||
tileX * width, |
||||
tileY * height, |
||||
width, |
||||
height, |
||||
-x - drawWidth, |
||||
y, |
||||
drawWidth, |
||||
drawHeight, |
||||
) |
||||
juego.ctx.restore() |
||||
} else { |
||||
juego.ctx.drawImage( |
||||
img, |
||||
tileX * width, |
||||
tileY * height, |
||||
width, |
||||
height, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight, |
||||
) |
||||
} |
||||
function draw( |
||||
juego: Juego<any>, |
||||
x: number, |
||||
y: number, |
||||
spriteIndex = 0, |
||||
flipped = false |
||||
) { |
||||
const drawHeight = getHeight(juego); |
||||
const drawWidth = getWidth(juego); |
||||
const tileX = spriteIndex % rowSize; |
||||
const tileY = Math.floor(spriteIndex / rowSize); |
||||
if (flipped) { |
||||
juego.ctx.save(); |
||||
juego.ctx.scale(-1, 1); |
||||
juego.ctx.drawImage( |
||||
img, |
||||
tileX * width, |
||||
tileY * height, |
||||
width, |
||||
height, |
||||
-x - drawWidth, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
juego.ctx.restore(); |
||||
} else { |
||||
juego.ctx.drawImage( |
||||
img, |
||||
tileX * width, |
||||
tileY * height, |
||||
width, |
||||
height, |
||||
x, |
||||
y, |
||||
drawWidth, |
||||
drawHeight |
||||
); |
||||
} |
||||
} |
||||
|
||||
return { getWidth, getHeight, draw, width, height } |
||||
return { getWidth, getHeight, draw, width, height }; |
||||
} |
||||
|
@ -1,47 +1,62 @@
|
||||
import { Juego } from "./main"; |
||||
|
||||
export type Pos = { |
||||
x: number, y: number, |
||||
x: number; |
||||
y: number; |
||||
}; |
||||
export type Box = Pos & { |
||||
width: number, height: number, |
||||
} |
||||
width: number; |
||||
height: number; |
||||
}; |
||||
|
||||
export function posInBox(box: Box, pos: Pos) { |
||||
return pos.x > box.x && pos.x < box.x + box.width |
||||
&& pos.y > box.y && pos.y < box.y + box.height |
||||
return ( |
||||
pos.x > box.x && |
||||
pos.x < box.x + box.width && |
||||
pos.y > box.y && |
||||
pos.y < box.y + box.height |
||||
); |
||||
} |
||||
export function boxCollision(box1: Box, box2: Box) { |
||||
// http://stackoverflow.com/questions/2440377/ddg#7301852
|
||||
return !( |
||||
((box1.y + box1.height) < (box2.y)) || |
||||
(box1.y > (box2.y + box2.height)) || |
||||
((box1.x + box1.width) < box2.x) || |
||||
(box1.x > (box2.x + box2.width)) |
||||
) |
||||
// http://stackoverflow.com/questions/2440377/ddg#7301852
|
||||
return !( |
||||
box1.y + box1.height < box2.y || |
||||
box1.y > box2.y + box2.height || |
||||
box1.x + box1.width < box2.x || |
||||
box1.x > box2.x + box2.width |
||||
); |
||||
} |
||||
|
||||
export function drawText(juego: Juego<any>, text: string, pos: Pos, { |
||||
export function drawText( |
||||
juego: Juego<any>, |
||||
text: string, |
||||
pos: Pos, |
||||
{ |
||||
bold = false, |
||||
size = 2, |
||||
align = "left", |
||||
baseline = "top", |
||||
}: { |
||||
align?: CanvasTextAlign, |
||||
baseline?: CanvasTextBaseline, |
||||
bold?: boolean, |
||||
}: { |
||||
align?: CanvasTextAlign; |
||||
baseline?: CanvasTextBaseline; |
||||
bold?: boolean; |
||||
// in rem
|
||||
size?: number, |
||||
}): Box { |
||||
juego.ctx.font = `${bold ? 'bold ' : ''}${size}rem sans-serif` |
||||
juego.ctx.textAlign = align |
||||
juego.ctx.textBaseline = baseline |
||||
juego.ctx.fillText(text, pos.x, pos.y) |
||||
size?: number; |
||||
} |
||||
): Box { |
||||
juego.ctx.font = `${bold ? "bold " : ""}${size}rem sans-serif`; |
||||
juego.ctx.textAlign = align; |
||||
juego.ctx.textBaseline = baseline; |
||||
juego.ctx.fillText(text, pos.x, pos.y); |
||||
|
||||
const measure = juego.ctx.measureText(text) |
||||
return { ...pos, width: measure.width, height: measure.actualBoundingBoxDescent } |
||||
const measure = juego.ctx.measureText(text); |
||||
return { |
||||
...pos, |
||||
width: measure.width, |
||||
height: measure.actualBoundingBoxDescent, |
||||
}; |
||||
} |
||||
|
||||
export function randomFromArray<T>(array: T[]): T { |
||||
return array[Math.floor(Math.random() * array.length)] |
||||
return array[Math.floor(Math.random() * array.length)]; |
||||
} |
||||
|
@ -1,29 +1,38 @@
|
||||
import { posInBox } from './utils' |
||||
import { createJugandoState } from './jugando' |
||||
import { Juego } from './main' |
||||
import { posInBox } from "./utils"; |
||||
import { createJugandoState } from "./jugando"; |
||||
import { Juego } from "./main"; |
||||
|
||||
export type State = { |
||||
current: "welcome" |
||||
} |
||||
current: "welcome"; |
||||
}; |
||||
|
||||
function startButton(juego: Juego<State>) { |
||||
const [width, height] = [juego.assets.botonJugar.width / 4, juego.assets.botonJugar.height / 4] |
||||
return { |
||||
x: juego.canvas.width - width - 30, |
||||
y: juego.canvas.height - height - 30, |
||||
width, |
||||
height, |
||||
} |
||||
const [width, height] = [ |
||||
juego.assets.botonJugar.width / 4, |
||||
juego.assets.botonJugar.height / 4, |
||||
]; |
||||
return { |
||||
x: juego.canvas.width - width - 30, |
||||
y: juego.canvas.height - height - 30, |
||||
width, |
||||
height, |
||||
}; |
||||
} |
||||
|
||||
export function update(juego: Juego<State>, dt: number) { |
||||
const btn = startButton(juego) |
||||
if (juego.mouse.down && posInBox(btn, juego.mouse)) { |
||||
(juego as Juego<any>).state = createJugandoState() |
||||
} |
||||
const btn = startButton(juego); |
||||
if (juego.mouse.down && posInBox(btn, juego.mouse)) { |
||||
(juego as Juego<any>).state = createJugandoState(); |
||||
} |
||||
} |
||||
|
||||
export function draw(juego: Juego<State>, timestamp: number) { |
||||
const btn = startButton(juego) |
||||
juego.ctx.drawImage(juego.assets.botonJugar, btn.x, btn.y, btn.width, btn.height) |
||||
const btn = startButton(juego); |
||||
juego.ctx.drawImage( |
||||
juego.assets.botonJugar, |
||||
btn.x, |
||||
btn.y, |
||||
btn.width, |
||||
btn.height |
||||
); |
||||
} |
||||
|
@ -1,15 +1,17 @@
|
||||
import { Juego } from './main' |
||||
import { drawText } from './utils' |
||||
import { Juego } from "./main"; |
||||
import { drawText } from "./utils"; |
||||
|
||||
export type State = { |
||||
current: "win" |
||||
} |
||||
current: "win"; |
||||
}; |
||||
|
||||
export function update(juego: Juego<State>, dt: number) { |
||||
} |
||||
export function update(juego: Juego<State>, dt: number) {} |
||||
|
||||
export function draw(juego: Juego<State>, timestamp: number) { |
||||
drawText(juego, '¡Salvaste la costanera!', |
||||
{ x: juego.canvas.width / 2, y: 100 }, |
||||
{ bold: true, size: 3, align: 'center' }) |
||||
drawText( |
||||
juego, |
||||
"¡Salvaste la costanera!", |
||||
{ x: juego.canvas.width / 2, y: 100 }, |
||||
{ bold: true, size: 3, align: "center" } |
||||
); |
||||
} |
||||
|
@ -1,15 +1,15 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "ESNext", |
||||
"module": "ESNext", |
||||
"lib": ["ESNext", "DOM"], |
||||
"moduleResolution": "Node", |
||||
"strict": true, |
||||
"sourceMap": true, |
||||
"resolveJsonModule": true, |
||||
"esModuleInterop": true, |
||||
"noEmit": true, |
||||
"noImplicitReturns": true |
||||
}, |
||||
"include": ["./src"] |
||||
"compilerOptions": { |
||||
"target": "ESNext", |
||||
"module": "ESNext", |
||||
"lib": ["ESNext", "DOM"], |
||||
"moduleResolution": "Node", |
||||
"strict": true, |
||||
"sourceMap": true, |
||||
"resolveJsonModule": true, |
||||
"esModuleInterop": true, |
||||
"noEmit": true, |
||||
"noImplicitReturns": true |
||||
}, |
||||
"include": ["./src"] |
||||
} |
||||
|
Loading…
Reference in new issue