diff --git a/source/package-lock.json b/source/package-lock.json index 9a0347b..115268d 100644 --- a/source/package-lock.json +++ b/source/package-lock.json @@ -1,12 +1,12 @@ { "name": "@funky-flask-test/funky-flask-test", - "version": "1.3.3", + "version": "1.3.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@funky-flask-test/funky-flask-test", - "version": "1.3.3", + "version": "1.3.4", "devDependencies": { "@types/node": "^24.0.15", "typescript": "^5.8.3", diff --git a/source/package.json b/source/package.json index e37da95..98fb089 100644 --- a/source/package.json +++ b/source/package.json @@ -1,6 +1,6 @@ { "name": "@funky-flask-test/funky-flask-test", - "version": "1.3.3", + "version": "1.3.4", "type": "module", "main": "./dist/index.jsx", "exports": { diff --git a/source/src/index.tsx b/source/src/index.tsx index 5e6a46f..f0a5792 100644 --- a/source/src/index.tsx +++ b/source/src/index.tsx @@ -1,238 +1,168 @@ +const ASSETS = { + squirrel: 'https://assets.vecteezy.net/png/27517308-pixel-art-squirrel-character.png', + dogWalk: 'https://assets.vecteezy.net/png/41440459-dog-walking-sprite.png', + nut: 'https://toppng.com/uploads/preview/ower-ups-animation-game-power-up-sprites.png' +} + export class RickGamePanel { - private canvas!: HTMLCanvasElement - private ctx!: CanvasRenderingContext2D - private player = { x: 20, y: 180, vy: 0, width: 10, height: 10, onGround: false } - private gravity = 0.7 - private jumpPower = -12 - - private isGameOver = false - private isStarted = false - private startupAlpha = 0 - private frameCount = 0 - private score = 0 - - private enemies: { x: number, y: number, width: number, height: number, speed: number }[] = [] - private spawnCooldown = 0 + private canvas!: HTMLCanvasElement; + private ctx!: CanvasRenderingContext2D; + private isStarted = false; + private isGameOver = false; + private score = 0; + private player = { x: 20, y: 180, vy: 0, width: 32, height: 32, onGround: false }; + private enemies: any[] = []; + private powerups: any[] = []; + private spawnTimer = 0; + private assetImgs: any = {}; constructor(private container: HTMLElement) { - this.createUI() - this.ctx = this.canvas.getContext('2d')! - this.attachListeners() - this.drawStartup() + this.loadAssets(ASSETS).then(imgs => { + this.assetImgs = imgs; + this.createUI(); + this.ctx = this.canvas.getContext('2d')!; + this.attachListeners(); + this.drawStartup(); + }); + } + + private async loadAssets(assets: Record) { + const entries = Object.entries(assets); + const result: any = {}; + await Promise.all(entries.map(([key, url]) => + new Promise(res => { + const img = new Image(); + img.src = url as string; + img.onload = () => { + result[key] = img; + res(); + }; + }) + )); + return result; } private createUI() { - const panel = document.createElement('div') - panel.style.cssText = ` - background: #111; - color: #eee; - font-family: monospace; - padding: 16px; - border-radius: 10px; - width: fit-content; - box-shadow: 0 0 10px rgba(255,255,255,0.2); - ` - - const title = document.createElement('div') - title.innerHTML = `🎮 Rickjump Shadered` - title.style.marginBottom = '10px' - panel.appendChild(title) - - this.canvas = document.createElement('canvas') - this.canvas.width = 300 - this.canvas.height = 200 - this.canvas.style.cssText = ` - border: 2px solid #333; - background: #000; - image-rendering: pixelated; - cursor: pointer; - ` - panel.appendChild(this.canvas) - - const footer = document.createElement('div') - footer.textContent = 'click to begin | dodge to score' - footer.style.cssText = ` - margin-top: 10px; - font-size: 0.7em; - color: #aaa; - ` - panel.appendChild(footer) - - this.container.appendChild(panel) + const panel = document.createElement('div'); + panel.style.cssText = `position: relative; width: 100%; max-width: 400px;`; + this.canvas = document.createElement('canvas'); + this.canvas.width = 300; + this.canvas.height = 200; + this.canvas.style.cssText = `width: 100%; border: 2px solid #333; background: #000; cursor: pointer;`; + panel.appendChild(this.canvas); + this.container.appendChild(panel); } private attachListeners() { + window.addEventListener('keydown', e => { + if ((e.code === 'Space' || e.code === 'Enter') && this.player.onGround && this.isStarted && !this.isGameOver) { + this.player.vy = -12; + this.player.onGround = false; + } + }); this.canvas.addEventListener('click', () => { if (!this.isStarted) { - this.isStarted = true - this.frame() + this.isStarted = true; + this.frame(); } else if (this.isGameOver) { - this.resetGame() + this.reset(); } else if (this.player.onGround) { - this.player.vy = this.jumpPower - this.player.onGround = false + this.player.vy = -12; + this.player.onGround = false; } - }) + }); } private frame = () => { - if (!this.isStarted) return - this.frameCount++ - - if (!this.isGameOver) { - this.player.vy += this.gravity - this.player.y += this.player.vy - - if (this.player.y >= 180) { - this.player.y = 180 - this.player.vy = 0 - this.player.onGround = true - } - - for (const enemy of this.enemies) { - enemy.x -= enemy.speed - } - - // Remove passed enemies + add score - const remainingEnemies = [] - for (const enemy of this.enemies) { - if (enemy.x + enemy.width > 0) { - remainingEnemies.push(enemy) - } else { - this.score++ - } - } - this.enemies = remainingEnemies - - for (const enemy of this.enemies) { - const p = this.player - const collide = - p.x < enemy.x + enemy.width && - p.x + p.width > enemy.x && - p.y < enemy.y + enemy.height && - p.y + p.height > enemy.y - - if (collide) { - this.isGameOver = true - setTimeout(() => { - window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '_blank') - }, 300) - return - } - } - - if (this.spawnCooldown-- <= 0) { - const last = this.enemies[this.enemies.length - 1] - if (!last || last.x < 180) { - this.spawnEnemy() - this.spawnCooldown = 40 + Math.floor(Math.random() * 50) - } - } + if (!this.isStarted) return; + if (this.isGameOver) { + this.draw(); + return; } - - this.draw() - requestAnimationFrame(this.frame) - } - - private resetGame() { - this.player.y = 180 - this.player.vy = 0 - this.enemies = [] - this.spawnCooldown = 0 - this.isGameOver = false - this.score = 0 - this.frame() + this.player.vy += 0.7; + this.player.y += this.player.vy; + if (this.player.y >= 180) { this.player.y = 180; this.player.vy = 0; this.player.onGround = true; } + this.spawnTimer--; + if (this.spawnTimer <= 0) { + this.spawnEnemy(); + if (Math.random() < 0.3) this.spawnPowerup(); + this.spawnTimer = 80; + } + this.enemies.forEach(e => e.x -= 1.5); + this.powerups.forEach(p => p.x -= 1.5); + this.enemies = this.enemies.filter(e => e.x + e.size > 0); + this.powerups = this.powerups.filter(p => p.x + p.size > 0); + this.enemies.forEach(e => { + if (this.collide(this.player, e)) this.gameOver(); + }); + this.powerups.forEach((p, idx) => { + if (this.collide(this.player, p)) { + this.score += 5; + this.powerups.splice(idx, 1); + } + }); + this.draw(); + requestAnimationFrame(this.frame); } private spawnEnemy() { - const height = 10 + Math.floor(Math.random() * 20) - const y = 190 - height - const speed = 1 + Math.random() * 1.5 - this.enemies.push({ x: 300, y, width: 10, height, speed }) + const variant = Math.random() < 0.5 ? 'walk' : 'fly'; + const y = variant === 'fly' ? 120 : 180 - 32; + this.enemies.push({ x: 300, y, size: 32, variant }); + } + + private spawnPowerup() { + this.powerups.push({ x: 300, y: 160, size: 24 }); + } + + private collide(a: any, b: any) { + return a.x < b.x + b.size && a.x + a.width > b.x && + a.y < b.y + b.size && a.y + a.height > b.y; + } + + private gameOver() { + this.isGameOver = true; + window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '_blank'); + } + + private reset() { + this.score = 0; + this.enemies = []; + this.powerups = []; + this.player.y = 180; + this.player.vy = 0; + this.isGameOver = false; + this.frame(); } private drawStartup() { - const draw = () => { - if (this.isStarted) return - - this.startupAlpha += 0.02 - if (this.startupAlpha > 1) this.startupAlpha = 1 - - this.draw(true) - requestAnimationFrame(draw) - } - - draw() + this.ctx = this.canvas.getContext('2d')!; + this.ctx.font = 'bold 16px monospace'; + this.ctx.fillStyle = '#fff'; + this.ctx.fillText('Click To Start', 80, 90); } - private draw(startup = false) { - const ctx = this.ctx - ctx.clearRect(0, 0, 300, 200) + private draw() { + const ctx = this.ctx; + ctx.clearRect(0, 0, 300, 200); + ctx.fillStyle = '#222'; ctx.fillRect(0, 0, 300, 200); + ctx.fillStyle = '#444'; ctx.fillRect(0, 190, 300, 10); + this.enemies.forEach(e => { + const img = this.assetImgs.dogWalk; + ctx.drawImage(img, e.x, e.y, e.size, e.size); + }); + this.powerups.forEach(p => { + const img = this.assetImgs.nut; + ctx.drawImage(img, p.x, p.y, p.size, p.size); + }); + const pl = this.assetImgs.squirrel; + ctx.drawImage(pl, this.player.x, this.player.y, this.player.width, this.player.height); + ctx.fillStyle = '#fff'; ctx.font = '12px monospace'; + ctx.fillText(`Score: ${this.score}`, 200, 20); - const grad = ctx.createLinearGradient(0, 0, 0, 200) - grad.addColorStop(0, '#000') - grad.addColorStop(1, '#222') - ctx.fillStyle = grad - ctx.fillRect(0, 0, 300, 200) - - ctx.fillStyle = '#444' - ctx.fillRect(0, 190, 300, 10) - - // Enemies - for (const e of this.enemies) { - ctx.fillStyle = '#f33' - ctx.fillRect(e.x, e.y, e.width, e.height) - } - - // Player - ctx.fillStyle = '#0af' - ctx.fillRect(this.player.x, this.player.y, this.player.width, this.player.height) - - // Shader lines - ctx.fillStyle = 'rgba(255,255,255,0.03)' - for (let y = 0; y < 200; y += 2) { - ctx.fillRect(0, y, 300, 1) - } - - // Vignette - ctx.fillStyle = 'rgba(0,0,0,0.2)' - ctx.beginPath() - ctx.ellipse(150, 100, 160, 100, 0, 0, Math.PI * 2) - ctx.rect(300, 0, -300, 200) - ctx.fill() - - // Score - if (this.isStarted) { - ctx.fillStyle = '#fff' - ctx.font = 'bold 12px monospace' - ctx.fillText(`Score: ${this.score}`, 210, 20) - } - - // Intro screen - if (!this.isStarted) { - ctx.fillStyle = `rgba(255,255,255,${this.startupAlpha * 0.9})` - ctx.font = 'bold 16px monospace' - ctx.fillText('Click to Start', 90, 100) - } - - // Game over if (this.isGameOver) { - ctx.fillStyle = '#fff' - ctx.font = 'bold 16px monospace' - ctx.fillText('💀 Rickrolled!', 95, 80) - ctx.font = '12px monospace' - ctx.fillText(`Final Score: ${this.score}`, 95, 100) - ctx.fillText(`Click to Restart`, 95, 120) + ctx.font = 'bold 16px monospace'; + ctx.fillText('Game Over! Click to restart', 50, 100); } } } - -export function insertRickPanel(target: HTMLElement | string = document.body): void { - const container = typeof target === 'string' - ? document.querySelector(target) - : target - - if (container instanceof HTMLElement) { - new RickGamePanel(container) - } -}