From d903dd57ca631441058b85b57e5b60b880903441 Mon Sep 17 00:00:00 2001 From: type-two Date: Thu, 16 Jul 2026 16:58:25 +1000 Subject: [PATCH] Tier 1: classes, themes, pirates/ghouls, score, vibe states, touch - 4 Gauntlet classes (stat tweaks + tint) with title-screen select (keys 1-4 or tap) - Level themes cycle shop/fair/club: per-theme tilesets (9 idle sprites wired) and weighted enemy spawn tables - Record Pirate (steals pickups, flees to its bin; kill = loot back + gold bonus) and Crate Ghoul (3 HP tank) - Gold records ('R' tiles + pirate drops), score (kills/gold/clears), local top-5 high-score table with initials on the title screen - Vibe states: peak time = faster vinyl, burning out = slower + red vignette - "Selector shot the espresso" (vinyl destroys food) + new announcer lines - Mobile: Scale.FIT canvas, twin-stick touch (left = move, right = fire), HORN button All mechanics regression-tested in browser via engine stepping. Co-Authored-By: Claude Fable 5 --- assets/process_sprites.py | 7 + public/sprites/floor_carpet.png | Bin 0 -> 16213 bytes public/sprites/floor_checker.png | Bin 0 -> 4950 bytes public/sprites/ghoul.png | Bin 0 -> 3867 bytes public/sprites/gold.png | Bin 0 -> 3289 bytes public/sprites/pirate.png | Bin 0 -> 3322 bytes public/sprites/wall_poster.png | Bin 0 -> 6484 bytes public/sprites/wall_shelf.png | Bin 0 -> 5726 bytes src/GameScene.js | 335 ++++++++++++++++++++++++++----- src/announcer.js | 3 + src/levels.js | 12 +- src/main.js | 1 + 12 files changed, 304 insertions(+), 54 deletions(-) create mode 100644 public/sprites/floor_carpet.png create mode 100644 public/sprites/floor_checker.png create mode 100644 public/sprites/ghoul.png create mode 100644 public/sprites/gold.png create mode 100644 public/sprites/pirate.png create mode 100644 public/sprites/wall_poster.png create mode 100644 public/sprites/wall_shelf.png diff --git a/assets/process_sprites.py b/assets/process_sprites.py index b68d026..352981d 100644 --- a/assets/process_sprites.py +++ b/assets/process_sprites.py @@ -19,7 +19,14 @@ OUT.mkdir(parents=True, exist_ok=True) # game texture key -> (source file, size px, is_tile) TABLE = { "floor": ("floor_parquet.png", 96, True), + "floor_carpet": ("floor_carpet.png", 96, True), + "floor_checker": ("floor_checker.png", 96, True), "wall": ("wall_crate.png", 48, True), + "wall_shelf": ("wall_shelf.png", 48, True), + "wall_poster": ("wall_poster.png", 48, True), + "pirate": ("enemy_record_pirate.png", 48, False), + "ghoul": ("enemy_crate_ghoul.png", 48, False), + "gold": ("pickup_goldrecord.png", 38, False), "player": ("player_digger.png", 48, False), "poser": ("enemy_poser.png", 48, False), "nerd": ("enemy_gearnerd.png", 48, False), diff --git a/public/sprites/floor_carpet.png b/public/sprites/floor_carpet.png new file mode 100644 index 0000000000000000000000000000000000000000..8d57136df68b797176cf0030722a97e41d612baa GIT binary patch literal 16213 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

!`a@3=8;!9rpvXgo{ zhp0nn#`7p>LzRyT3a#|w-a%)|A#cUT>qHMycK0;MgIB>4!`FM#Q_BLUz>9FJ-oqGp zhmj&fq#%^CTsFodWX$!bAx#R=t8Qm4U!%|R9{{C z!+NkaYLMF<)`(d-9tHO*6`I0bI2bhou{i+jAPCDrM;8&GZpMUNbUpA9mGk55q{^rQ zA2|f1dc531K1JqUut)GO--SU`QKAM9+o}Ht`b7!V%%b}900000NkvXXu0mjfEUA~; literal 0 HcmV?d00001 diff --git a/public/sprites/floor_checker.png b/public/sprites/floor_checker.png new file mode 100644 index 0000000000000000000000000000000000000000..fb28ef65b1db487bdd100ec60167fbf77cdbfbce GIT binary patch literal 4950 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 U&(#FNqW}N^07*qoM6N<$g2w=ki~s-t literal 0 HcmV?d00001 diff --git a/public/sprites/ghoul.png b/public/sprites/ghoul.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad8397945bf8577407a670915ba78eb430aeef1 GIT binary patch literal 3867 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 literal 0 HcmV?d00001 diff --git a/public/sprites/gold.png b/public/sprites/gold.png new file mode 100644 index 0000000000000000000000000000000000000000..9e243475c58c7c30a25381ba76cb2da1a37ce0e8 GIT binary patch literal 3289 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 Xvk5;TosB^Z00000NkvXXu0mjf7Q8uR literal 0 HcmV?d00001 diff --git a/public/sprites/pirate.png b/public/sprites/pirate.png new file mode 100644 index 0000000000000000000000000000000000000000..28e86d21e9bd435892d4b451c84a6f285581ae2a GIT binary patch literal 3322 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 Ef@RcW&;S4c literal 0 HcmV?d00001 diff --git a/public/sprites/wall_poster.png b/public/sprites/wall_poster.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce4388ffa5e93f4144e0abd30b0e54b3966694f GIT binary patch literal 6484 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 literal 0 HcmV?d00001 diff --git a/public/sprites/wall_shelf.png b/public/sprites/wall_shelf.png new file mode 100644 index 0000000000000000000000000000000000000000..c583159bbf2439c5044fee9f80607081845bd50e GIT binary patch literal 5726 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 QWdHyG07*qoM6N<$f.png (see assets/process_sprites.py) const TEXTURE_KEYS = [ - 'floor', 'wall', 'player', 'poser', 'nerd', 'soundguy', 'generator', - 'exit', 'door', 'espresso', 'lanyard', 'airhorn', 'vinyl', + 'floor', 'floor_carpet', 'floor_checker', + 'wall', 'wall_shelf', 'wall_poster', + 'player', 'poser', 'nerd', 'pirate', 'ghoul', 'soundguy', 'generator', + 'exit', 'door', 'espresso', 'lanyard', 'airhorn', 'vinyl', 'gold', +]; + +// Gauntlet II classes: stat tweaks only, no new mechanics. +const CLASSES = { + digger: { name: 'THE DIGGER', desc: '+25% speed', speed: 1.25, dmg: 1, lanyards: 0, airhorns: 1, sgDrain: 1, tint: 0xffffff }, + selector: { name: 'THE SELECTOR', desc: 'double damage', speed: 1, dmg: 2, lanyards: 0, airhorns: 1, sgDrain: 1, tint: 0xffe080 }, + promoter: { name: 'THE PROMOTER', desc: 'starts loaded', speed: 1, dmg: 1, lanyards: 1, airhorns: 2, sgDrain: 1, tint: 0xffa0ff }, + soundtech: { name: 'THE SOUND TECH', desc: 'sound guy resistant', speed: 1, dmg: 1, lanyards: 0, airhorns: 1, sgDrain: 0.4, tint: 0xa0ffa0 }, +}; + +const ENEMY_TYPES = { + poser: { tex: 'poser', speed: 180, touch: 30, hp: 1 }, + nerd: { tex: 'nerd', speed: 105, touch: 80, hp: 1 }, + pirate: { tex: 'pirate', speed: 210, touch: 20, hp: 1 }, // steals pickups, flees home + ghoul: { tex: 'ghoul', speed: 70, touch: 120, hp: 3 }, // slow tank +}; + +// Level themes cycle with level index; mix = weighted generator spawn table. +const THEMES = [ + { name: 'RECORD SHOP', floor: 'floor', wall: 'wall', mix: [['poser', 0.7], ['nerd', 0.3]] }, + { name: 'RECORD FAIR', floor: 'floor_carpet', wall: 'wall_shelf', mix: [['poser', 0.5], ['nerd', 0.25], ['pirate', 0.25]] }, + { name: 'CLUB NIGHT', floor: 'floor_checker', wall: 'wall_poster', mix: [['poser', 0.4], ['nerd', 0.2], ['pirate', 0.2], ['ghoul', 0.2]] }, ]; export default class GameScene extends Phaser.Scene { @@ -40,6 +63,7 @@ export default class GameScene extends Phaser.Scene { create() { this.cameras.main.setBackgroundColor(0x1a1a1a); + this.cls = CLASSES.digger; // Persistent groups — created once, refilled each level so colliders survive. this.walls = this.physics.add.staticGroup(); @@ -62,15 +86,23 @@ export default class GameScene extends Phaser.Scene { this.physics.add.collider(this.vinyl, this.walls, this.killVinyl, null, this); this.physics.add.overlap(this.vinyl, this.enemies, this.hitEnemy, null, this); this.physics.add.overlap(this.vinyl, this.generators, this.hitGenerator, null, this); + this.physics.add.overlap(this.vinyl, this.pickups, this.vinylHitsPickup, null, this); this.physics.add.overlap(this.player, this.enemies, this.touchEnemy, null, this); this.physics.add.overlap(this.player, this.soundGuys, this.touchSoundGuy, null, this); this.physics.add.overlap(this.player, this.pickups, this.grabPickup, null, this); + this.physics.add.overlap(this.enemies, this.pickups, this.pirateGrab, null, this); // Input. this.cursors = this.input.keyboard.createCursorKeys(); this.wasd = this.input.keyboard.addKeys('W,A,S,D'); this.keySpace = this.input.keyboard.addKey('SPACE'); this.keyR = this.input.keyboard.addKey('R'); + this.input.keyboard.on('keydown', (ev) => { + if (this.state !== 'TITLE') return; + const i = ['1', '2', '3', '4'].indexOf(ev.key); + if (i >= 0) this.startGame(Object.keys(CLASSES)[i]); + }); + this.setupTouch(); // HUD + overlays (fixed to camera). this.hud = this.add @@ -78,7 +110,7 @@ export default class GameScene extends Phaser.Scene { .setScrollFactor(0) .setDepth(100); this.overlay = this.add - .text(480, 340, '', { + .text(480, 190, '', { fontFamily: 'monospace', fontSize: '40px', color: '#ff00ff', @@ -87,6 +119,32 @@ export default class GameScene extends Phaser.Scene { .setOrigin(0.5) .setScrollFactor(0) .setDepth(100); + this.sub = this.add + .text(480, 330, '', { + fontFamily: 'monospace', + fontSize: '20px', + color: '#ffffff', + align: 'center', + }) + .setOrigin(0.5) + .setScrollFactor(0) + .setDepth(100); + this.classButtons = Object.entries(CLASSES).map(([key, c], i) => { + const b = this.add + .text(480, 470 + i * 45, `${i + 1} ${c.name} — ${c.desc}`, { + fontFamily: 'monospace', + fontSize: '22px', + color: '#00e5ff', + backgroundColor: '#00000080', + padding: { x: 12, y: 4 }, + }) + .setOrigin(0.5) + .setScrollFactor(0) + .setDepth(100) + .setInteractive({ useHandCursor: true }); + b.on('pointerdown', () => this.startGame(key)); + return b; + }); this.barBg = this.add .rectangle(480, 680, 306, 26, 0x000000) .setScrollFactor(0) @@ -98,6 +156,12 @@ export default class GameScene extends Phaser.Scene { .setScrollFactor(0) .setDepth(100) .setVisible(false); + // burning-out vignette (vibe < 25%) + this.vignette = this.add + .rectangle(480, 360, 960, 720, 0xff0000, 1) + .setScrollFactor(0) + .setDepth(90) + .setAlpha(0); // Vibe heartbeat. this.time.addEvent({ @@ -111,20 +175,55 @@ export default class GameScene extends Phaser.Scene { }, }); - this.input.keyboard.on('keydown', () => { - if (this.state === 'TITLE') this.startGame(); - }); - - this.state = 'TITLE'; this.resetStats(); this.buildLevel(0); - this.showOverlay('VINYL GAUNTLET\n\npress any key'); + this.showTitle(); + } + + setupTouch() { + this.input.addPointer(2); + this.touchMove = null; + this.touchFire = null; + const overHorn = (p) => this.hornBtn && Phaser.Math.Distance.Between(p.x, p.y, 870, 630) < 60; + this.input.on('pointerdown', (p) => { + if (this.state !== 'PLAYING' || overHorn(p)) return; + const stick = { id: p.id, ox: p.x, oy: p.y, dx: 0, dy: 0 }; + if (p.x < 480) this.touchMove = stick; + else this.touchFire = stick; + }); + this.input.on('pointermove', (p) => { + for (const s of [this.touchMove, this.touchFire]) { + if (s && s.id === p.id) { + s.dx = p.x - s.ox; + s.dy = p.y - s.oy; + } + } + }); + this.input.on('pointerup', (p) => { + if (this.touchMove && this.touchMove.id === p.id) this.touchMove = null; + if (this.touchFire && this.touchFire.id === p.id) this.touchFire = null; + }); + if (this.sys.game.device.input.touch) { + this.hornBtn = this.add + .circle(870, 630, 42, 0xffffff, 0.25) + .setScrollFactor(0) + .setDepth(100) + .setStrokeStyle(2, 0xffffff) + .setInteractive(); + this.add + .text(870, 630, 'HORN', { fontFamily: 'monospace', fontSize: '14px', color: '#ffffff' }) + .setOrigin(0.5) + .setScrollFactor(0) + .setDepth(101); + this.hornBtn.on('pointerdown', () => this.doAirhorn()); + } } resetStats() { this.vibe = VIBE_MAX; - this.lanyards = 0; - this.airhorns = 1; + this.score = 0; + this.lanyards = this.cls.lanyards; + this.airhorns = this.cls.airhorns; this.densityBase = 1; this.nextFire = 0; this.soundGuyTick = 0; @@ -138,6 +237,7 @@ export default class GameScene extends Phaser.Scene { if (row.length !== w0) throw new Error(`level ${index}: ragged row (${row.length} vs ${w0})`); this.levelIndex = index; + this.theme = THEMES[index % THEMES.length]; this.walls.clear(true, true); this.doors.clear(true, true); this.pickups.clear(true, true); @@ -158,10 +258,10 @@ export default class GameScene extends Phaser.Scene { // Tiled floor behind everything. if (this.floor) this.floor.destroy(); this.floor = this.add - .tileSprite(0, 0, worldW, worldH, 'floor') + .tileSprite(0, 0, worldW, worldH, this.theme.floor) .setOrigin(0) .setDepth(-10) - .setTint(0x555555); // darken so the brown crate walls read as raised + .setTint(0x555555); // darken so walls read as raised for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { @@ -170,7 +270,7 @@ export default class GameScene extends Phaser.Scene { const y = r * TILE + TILE / 2; switch (ch) { case 'W': - this.walls.create(x, y, 'wall'); + this.walls.create(x, y, this.theme.wall); break; case 'D': this.doors.create(x, y, 'door'); @@ -187,6 +287,9 @@ export default class GameScene extends Phaser.Scene { case 'A': this.pickups.create(x, y, 'airhorn').setData('type', 'airhorn'); break; + case 'R': + this.pickups.create(x, y, 'gold').setData('type', 'gold'); + break; case 'G': { const g = this.generators.create(x, y, 'generator'); g.setData('hp', GEN_HP); @@ -221,7 +324,7 @@ export default class GameScene extends Phaser.Scene { update(time, delta) { if (this.state === 'TITLE') return; if (this.state === 'GAME_OVER') { - if (Phaser.Input.Keyboard.JustDown(this.keyR)) this.startGame(); + if (Phaser.Input.Keyboard.JustDown(this.keyR)) this.showTitle(); return; } if (this.state === 'LEVEL_CLEAR') return; @@ -233,7 +336,7 @@ export default class GameScene extends Phaser.Scene { this.updateGenerators(delta); this.updateSoundGuys(); this.density = this.densityBase + 0.05 * Math.floor((time - this.levelStartTime) / 10000); - this.handleAirhorn(); + if (Phaser.Input.Keyboard.JustDown(this.keySpace)) this.doAirhorn(); this.handleTrainspot(delta); this.updateHud(); } @@ -247,7 +350,16 @@ export default class GameScene extends Phaser.Scene { vx *= inv; vy *= inv; } - this.player.setVelocity(vx * PLAYER_SPEED, vy * PLAYER_SPEED); + if (vx === 0 && vy === 0 && this.touchMove) { + const m = Math.hypot(this.touchMove.dx, this.touchMove.dy); + if (m > 12) { + vx = this.touchMove.dx / m; + vy = this.touchMove.dy / m; + } + } + // burning out (< 25% vibe) = sluggish + const spd = PLAYER_SPEED * this.cls.speed * (this.vibe < VIBE_MAX * 0.25 ? 0.85 : 1); + this.player.setVelocity(vx * spd, vy * spd); } handleFiring(time) { @@ -255,18 +367,27 @@ export default class GameScene extends Phaser.Scene { const c = this.cursors; let dx = (c.right.isDown ? 1 : 0) - (c.left.isDown ? 1 : 0); let dy = (c.down.isDown ? 1 : 0) - (c.up.isDown ? 1 : 0); - if (dx === 0 && dy === 0) return; if (dx !== 0 && dy !== 0) { const inv = 1 / Math.sqrt(2); dx *= inv; dy *= inv; } + if (dx === 0 && dy === 0 && this.touchFire) { + const m = Math.hypot(this.touchFire.dx, this.touchFire.dy); + if (m > 15) { + dx = this.touchFire.dx / m; + dy = this.touchFire.dy / m; + } + } + if (dx === 0 && dy === 0) return; const v = this.vinyl.get(this.player.x, this.player.y, 'vinyl'); if (!v) return; v.setActive(true).setVisible(true); v.body.enable = true; v.setPosition(this.player.x, this.player.y); - v.setVelocity(dx * VINYL_SPEED, dy * VINYL_SPEED); + // peak time (> 75% vibe) = hotter throws + const vs = VINYL_SPEED * (this.vibe > VIBE_MAX * 0.75 ? 1.2 : 1); + v.setVelocity(dx * vs, dy * vs); v.setData('dieAt', time + VINYL_LIFE_MS); this.nextFire = time + FIRE_COOLDOWN_MS; } @@ -277,9 +398,36 @@ export default class GameScene extends Phaser.Scene { }); } + nearestPickup(from) { + let best = null; + let bestD = Infinity; + this.pickups.getChildren().forEach((p) => { + const d = Phaser.Math.Distance.Between(from.x, from.y, p.x, p.y); + if (d < bestD) { + bestD = d; + best = p; + } + }); + return best; + } + updateEnemies() { this.enemies.getChildren().forEach((e) => { - if (e.active) this.physics.moveToObject(e, this.player, e.getData('speed')); + if (!e.active) return; + let target = this.player; + if (e.getData('type') === 'pirate') { + if (e.getData('carry')) { + const home = e.getData('home'); + if (Phaser.Math.Distance.Between(e.x, e.y, home.x, home.y) < 24) { + this.kill(e); // escaped with the loot + return; + } + target = home; + } else { + target = this.nearestPickup(e) || this.player; + } + } + this.physics.moveToObject(e, target, e.getData('speed')); }); } @@ -296,17 +444,32 @@ export default class GameScene extends Phaser.Scene { }); } - spawnEnemy(g) { - const poser = Math.random() < 0.7; - const e = this.enemies.get(g.x, g.y, poser ? 'poser' : 'nerd'); + spawnEnemy(g, forceType) { + let type = forceType; + if (!type) { + let roll = Math.random(); + for (const [t, w] of this.theme.mix) { + roll -= w; + type = t; + if (roll <= 0) break; + } + } + const def = ENEMY_TYPES[type]; + const e = this.enemies.get(g.x, g.y, def.tex); if (!e) return; // pool full — skip - e.setTexture(poser ? 'poser' : 'nerd'); + e.setTexture(def.tex); e.setActive(true).setVisible(true); + e.clearTint(); e.body.enable = true; e.body.setSize(34, 34, true); // body smaller than 48px sprite e.setPosition(g.x, g.y); - e.setData('touch', poser ? POSER_TOUCH_DAMAGE : NERD_TOUCH_DAMAGE); - e.setData('speed', poser ? POSER_SPEED : NERD_SPEED); + e.setData('type', type); + e.setData('touch', def.touch); + e.setData('speed', def.speed); + e.setData('hp', def.hp); + e.setData('carry', null); + e.setData('home', { x: g.x, y: g.y }); + if (type === 'pirate') this.say(LINES.pirate); } updateSoundGuys() { @@ -315,14 +478,13 @@ export default class GameScene extends Phaser.Scene { }); } - handleAirhorn() { - if (!Phaser.Input.Keyboard.JustDown(this.keySpace)) return; - if (this.airhorns <= 0) return; + doAirhorn() { + if (this.state !== 'PLAYING' || this.airhorns <= 0) return; this.airhorns -= 1; this.cameras.main.flash(200, 255, 255, 255); this.cameras.main.shake(200, 0.01); this.enemies.getChildren().forEach((e) => { - if (e.active) this.kill(e); + if (e.active) this.dropLoot(e) || this.kill(e); }); this.soundGuys.clear(true, true); this.airhornBlast(); @@ -346,9 +508,10 @@ export default class GameScene extends Phaser.Scene { updateHud() { const v = Math.max(0, Math.ceil(this.vibe)); this.hud.setText( - `VIBE ${v} LANYARDS ${this.lanyards} AIRHORNS ${this.airhorns} LEVEL ${this.levelIndex + 1}`, + `SCORE ${this.score} VIBE ${v} LANYARDS ${this.lanyards} AIRHORNS ${this.airhorns} LEVEL ${this.levelIndex + 1}`, ); this.hud.setColor(this.vibe < 200 ? '#ff4040' : this.vibe < 500 ? '#ffd11a' : '#ffffff'); + this.vignette.setAlpha(this.vibe < VIBE_MAX * 0.25 ? 0.12 : 0); } // --- collision callbacks --- @@ -357,16 +520,35 @@ export default class GameScene extends Phaser.Scene { this.kill(v); } + // pirate killed while carrying: put the pickup back + a gold record bonus + dropLoot(e) { + const carry = e.getData('carry'); + if (!carry) return false; + this.pickups.create(e.x, e.y, carry.tex).setData('type', carry.type); + this.pickups.create(e.x + TILE, e.y, 'gold').setData('type', 'gold'); + e.setData('carry', null); + this.kill(e); + return true; + } + hitEnemy(v, e) { if (!v.active || !e.active) return; this.kill(v); - this.kill(e); + const hp = e.getData('hp') - this.cls.dmg; + if (hp > 0) { + e.setData('hp', hp); + e.setTintFill(0xffffff); + this.time.delayedCall(60, () => e.active && e.clearTint()); + return; + } + this.score += KILL_SCORE; + if (!this.dropLoot(e)) this.kill(e); } hitGenerator(v, g) { if (!v.active || !g.active) return; this.kill(v); - const hp = g.getData('hp') - 1; + const hp = g.getData('hp') - this.cls.dmg; g.setData('hp', hp); g.setTintFill(0xffffff); this.time.delayedCall(60, () => g.active && g.clearTint()); @@ -376,17 +558,31 @@ export default class GameScene extends Phaser.Scene { } } + vinylHitsPickup(v, item) { + // Gauntlet's "Elf shot the food!" — only food is destructible + if (!v.active || item.getData('type') !== 'espresso') return; + this.kill(v); + item.destroy(); + this.say(LINES.shotEspresso); + } + + pirateGrab(e, item) { + if (!e.active || e.getData('type') !== 'pirate' || e.getData('carry')) return; + e.setData('carry', { type: item.getData('type'), tex: item.texture.key }); + item.destroy(); + } + touchEnemy(p, e) { if (!e.active) return; const dmg = e.getData('touch'); - this.kill(e); + if (!this.dropLoot(e)) this.kill(e); this.takeDamage(dmg); } touchSoundGuy() { if (this.time.now - this.soundGuyTick > 250) { this.soundGuyTick = this.time.now; - this.takeDamage(SOUNDGUY_DRAIN); + this.takeDamage(SOUNDGUY_DRAIN * this.cls.sgDrain); } } @@ -395,6 +591,10 @@ export default class GameScene extends Phaser.Scene { if (type === 'espresso') this.vibe = Math.min(VIBE_MAX, this.vibe + ESPRESSO_VIBE); else if (type === 'lanyard') this.lanyards += 1; else if (type === 'airhorn') this.airhorns += 1; + else if (type === 'gold') { + this.score += GOLD_SCORE; + this.say(LINES.gold); + } item.destroy(); } @@ -419,8 +619,8 @@ export default class GameScene extends Phaser.Scene { takeDamage(amount) { if (this.state !== 'PLAYING') return; this.vibe -= amount; - this.player.setTint(0xff0000); - this.time.delayedCall(100, () => this.player.clearTint()); + this.player.setTintFill(0xff0000); + this.time.delayedCall(100, () => this.player.setTint(this.cls.tint)); if (this.trainspotProgress > 0) this.trainspotProgress = 0; // trainspot breaks on any hit this.checkThresholds(); if (this.vibe <= 0) this.gameOver(); @@ -434,37 +634,75 @@ export default class GameScene extends Phaser.Scene { levelClear() { if (this.state !== 'PLAYING') return; this.state = 'LEVEL_CLEAR'; + this.score += CLEAR_SCORE; this.physics.pause(); this.barBg.setVisible(false); this.barFill.setVisible(false); + const next = (this.levelIndex + 1) % LEVELS.length; this.showOverlay('TUNE IDENTIFIED'); + this.sub.setText(`next: ${THEMES[next % THEMES.length].name}`).setVisible(true); this.say(LINES.levelClear); this.time.delayedCall(1500, () => { this.physics.resume(); - let next = this.levelIndex + 1; - if (next >= LEVELS.length) { - next = 0; - this.densityBase += 0.5; // loop back harder - } + if (this.levelIndex + 1 >= LEVELS.length) this.densityBase += 0.5; // loop back harder this.hideOverlay(); this.state = 'PLAYING'; this.buildLevel(next); }); } + // --- high scores (localStorage) --- + + loadScores() { + try { + return JSON.parse(localStorage.getItem('vg_hiscores')) || []; + } catch { + return []; + } + } + + maybeSaveScore() { + if (this.score <= 0) return; + const scores = this.loadScores(); + if (scores.length >= 5 && this.score <= scores[scores.length - 1].score) return; + // ponytail: window.prompt beats building a text-entry widget; revisit if it grates + const raw = window.prompt(`High score: ${this.score}! Your initials:`, 'AAA'); + const initials = (raw || 'AAA').toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 3) || 'AAA'; + scores.push({ initials, score: this.score }); + scores.sort((a, b) => b.score - a.score); + localStorage.setItem('vg_hiscores', JSON.stringify(scores.slice(0, 5))); + } + gameOver() { if (this.state !== 'PLAYING') return; this.state = 'GAME_OVER'; this.player.setVelocity(0, 0); this.physics.pause(); this.say(LINES.gameOver); - this.showOverlay('TRAINWRECK\n\npress R'); + this.maybeSaveScore(); + this.showOverlay('TRAINWRECK'); + this.sub.setText(`score ${this.score}\n\npress R`).setVisible(true); } - startGame() { + showTitle() { + this.state = 'TITLE'; + this.physics.pause(); + this.buildLevel(0); + const hs = this.loadScores() + .map((s, i) => `${i + 1}. ${s.initials} ${s.score}`) + .join('\n'); + this.showOverlay('VINYL GAUNTLET'); + this.sub.setText(hs ? `HIGH SCORES\n${hs}` : 'dig deep. name every tune.').setVisible(true); + this.classButtons.forEach((b) => b.setVisible(true)); + } + + startGame(clsKey) { + this.cls = CLASSES[clsKey] || CLASSES.digger; + this.player.setTint(this.cls.tint); this.physics.resume(); this.resetStats(); this.hideOverlay(); + this.classButtons.forEach((b) => b.setVisible(false)); this.state = 'PLAYING'; this.buildLevel(0); } @@ -475,6 +713,7 @@ export default class GameScene extends Phaser.Scene { hideOverlay() { this.overlay.setVisible(false); + this.sub.setVisible(false); } say(line) { diff --git a/src/announcer.js b/src/announcer.js index 674284f..f6bd603 100644 --- a/src/announcer.js +++ b/src/announcer.js @@ -44,4 +44,7 @@ export const LINES = { doorOpen: ['doorOpen', 'Velvet rope unlocked.'], levelClear: ['levelClear', 'Tune identified. Selector levels up.'], trainspot: ['trainspot', 'Trainspotting.'], + shotEspresso: ['shotEspresso', 'Selector shot the espresso.'], + pirate: ['pirate', 'Record pirate. Protect the wax.'], + gold: ['gold', 'Wax acquired.'], }; diff --git a/src/levels.js b/src/levels.js index 2946e6d..f0d0736 100644 --- a/src/levels.js +++ b/src/levels.js @@ -33,8 +33,8 @@ const level1 = [ edge('..........WWWWW'), edge(''), edge(''), - edge('.....................E'), - edge('.........................X'), + edge('..............R......E'), + edge('.....R...................X'), edge(''), wall, ]; @@ -55,8 +55,8 @@ const level2 = [ edge('....WW....WWWW....WW'), edge('....WW....W..G....'), edge('....WW....W.......E'), - edge('..........WWWWWW..'), - edge('.E................'), + edge('..........WWWWWW..R'), + edge('.E......R.........'), edge('...................X'), wall, ]; @@ -77,8 +77,8 @@ const level3 = [ edge('.WWWW.WWWW.WWWW.WWWW'), edge('.W.G....E....G....E.'), edge('.WWWW.WWWW.WWWW.WWW'), - edge('.......S...........'), - edge('.E.................'), + edge('.......S.......R....'), + edge('.E....R............'), edge('.................X'), wall, ]; diff --git a/src/main.js b/src/main.js index 6fcee0c..8f43282 100644 --- a/src/main.js +++ b/src/main.js @@ -8,6 +8,7 @@ window.game = new Phaser.Game({ parent: 'game', backgroundColor: '#0a0a0a', pixelArt: true, + scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, // phones get a fitted canvas physics: { default: 'arcade', arcade: { gravity: { x: 0, y: 0 }, debug: false },