Compare commits

..

No commits in common. "2f7b2e183e0886f6380a2abb1970d752073453d1" and "6a33bf500b98ccb3ec099a3e29fd92e7a6c6b21a" have entirely different histories.

1 changed files with 23 additions and 104 deletions

View File

@ -5,7 +5,6 @@ import { auth } from '../../services/auth.js';
import { api } from '../../services/api.js';
import { playSound, SFX } from '../../ui/Sounds.js';
import { MusicPlayer } from '../../ui/MusicPlayer.js';
import { createOpponentPortrait, createPlayerPortrait } from '../../ui/Portrait.js';
import {
SIZE, CELL, BOARD_PX, GRID_X, GRID_Y, cellToWorld, worldToCell,
COLOR_PALETTE, cornersFor, ALL_PIECE_IDS, PIECE_SIZE, ORIENTATIONS,
@ -45,7 +44,6 @@ export default class BlokusGame extends Phaser.Scene {
this.hoverMove = null;
this.trayObjs = [];
this.scoreObjs = [];
this.opponentPortraits = [];
}
create() {
@ -257,31 +255,18 @@ export default class BlokusGame extends Phaser.Scene {
// ── Score panels + controls ──────────────────────────────────────────────────
buildScorePanels() {
this.scorePanels = [];
const panelH = 192; // 3× original height
const portraitR = 60;
const portraitY = -panelH / 2 + portraitR + 14; // centered in upper space
for (let seat = 0; seat < this.gs.players.length; seat++) {
const y = 221 + seat * (panelH + 10);
const y = 146 + seat * 74;
const container = this.add.container(SIDE_X, y).setDepth(DEPTH.hud);
const bg = this.add.graphics();
container.add(bg);
const swatch = this.add.graphics();
container.add(swatch);
const portraitStroke = this.add.graphics().setDepth(DEPTH.hud + 1);
container.add(portraitStroke);
const name = this.add.text(-120, 60, '', { fontFamily: 'Righteous', fontSize: '20px', color: COLORS.textHex }).setOrigin(0, 0.5);
const detail = this.add.text(-120, 85, '', { fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex }).setOrigin(0, 0.5);
const score = this.add.text(150, 60, '', { fontFamily: 'Righteous', fontSize: '26px', color: COLORS.accentHex }).setOrigin(1, 0.5);
const name = this.add.text(-120, -18, '', { fontFamily: 'Righteous', fontSize: '20px', color: COLORS.textHex }).setOrigin(0, 0.5);
const detail = this.add.text(-120, 14, '', { fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex }).setOrigin(0, 0.5);
const score = this.add.text(150, 0, '', { fontFamily: 'Righteous', fontSize: '26px', color: COLORS.accentHex }).setOrigin(1, 0.5);
container.add([name, detail, score]);
this.scorePanels.push({ container, bg, swatch, portraitStroke, name, detail, score, panelH });
// Create portrait
if (seat === 0) {
createPlayerPortrait(this, SIDE_X, y + portraitY, portraitR, DEPTH.hud + 2, 'BlokusGame');
} else {
const opp = this.opponents[seat - 1];
this.opponentPortraits[seat - 1] = createOpponentPortrait(this, opp, SIDE_X, y + portraitY, portraitR, DEPTH.hud + 2);
}
this.scorePanels.push({ container, bg, swatch, name, detail, score });
}
}
@ -298,17 +283,12 @@ export default class BlokusGame extends Phaser.Scene {
const isTurn = !this.gameOver && this.gs.current === seat;
panel.bg.clear();
panel.bg.fillStyle(COLORS.panel, isTurn ? 0.95 : 0.6);
panel.bg.fillRoundedRect(-180, -panel.panelH / 2, 360, panel.panelH, 10);
panel.bg.fillRoundedRect(-180, -32, 360, 64, 10);
panel.bg.lineStyle(isTurn ? 3 : 1, isTurn ? COLORS.accent : col.hex, isTurn ? 1 : 0.5);
panel.bg.strokeRoundedRect(-180, -panel.panelH / 2, 360, panel.panelH, 10);
panel.bg.strokeRoundedRect(-180, -32, 360, 64, 10);
panel.swatch.clear();
panel.swatch.fillStyle(col.hex, 1);
panel.swatch.fillRoundedRect(-172, panel.panelH / 2 - 44, 28, 32, 5);
// Portrait stroke — colored circle around the portrait
const portraitY = -panel.panelH / 2 + 60 + 14;
panel.portraitStroke.clear();
panel.portraitStroke.lineStyle(4, col.hex, 1);
panel.portraitStroke.strokeCircle(0, portraitY, 63);
panel.swatch.fillRoundedRect(-172, -16, 28, 32, 5);
panel.name.setText(this.playerName(seat));
panel.detail.setText(p.out ? 'done' : `${p.remaining.size} pieces left`);
panel.score.setText(`${scoreFor(p)}`);
@ -316,15 +296,11 @@ export default class BlokusGame extends Phaser.Scene {
}
buildControls() {
const trayCenter = TRAY_X + (TRAY_COLS * TRAY_SLOT_W) / 2;
this.passBtn = new Button(this, trayCenter, 898, 'Pass', () => this.humanPass(), { variant: 'ghost', width: 360, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
this.rotateBtn = new Button(this, trayCenter - 95, 838, '↻ Rotate', () => this.rotateSelection(), { width: 170, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
this.flipBtn = new Button(this, trayCenter + 95, 838, '↔ Flip', () => this.flipSelection(), { width: 170, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
const rightPad = 20;
const gap = 20;
const leaveX = GAME_WIDTH - rightPad - 85;
new Button(this, leaveX - 170 - gap, 1030, 'New', () => this.scene.restart(), { variant: 'ghost', width: 170, height: 44, fontSize: 18 }).setDepth(DEPTH.button);
new Button(this, leaveX, 1030, 'Leave', () => this.scene.start('GameMenu'), { variant: 'ghost', width: 170, height: 44, fontSize: 18 }).setDepth(DEPTH.button);
this.rotateBtn = new Button(this, SIDE_X - 95, 610, '↻ Rotate', () => this.rotateSelection(), { width: 170, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
this.flipBtn = new Button(this, SIDE_X + 95, 610, '↔ Flip', () => this.flipSelection(), { width: 170, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
this.passBtn = new Button(this, SIDE_X, 674, 'Pass', () => this.humanPass(), { variant: 'ghost', width: 360, height: 48, fontSize: 20 }).setDepth(DEPTH.button);
new Button(this, SIDE_X - 95, 750, 'New', () => this.scene.restart(), { variant: 'ghost', width: 170, height: 44, fontSize: 18 }).setDepth(DEPTH.button);
new Button(this, SIDE_X + 95, 750, 'Leave', () => this.scene.start('GameMenu'), { variant: 'ghost', width: 170, height: 44, fontSize: 18 }).setDepth(DEPTH.button);
}
buildStatus() {
@ -392,7 +368,7 @@ export default class BlokusGame extends Phaser.Scene {
if (this.gameOver) return;
const mv = chooseMove(this.gs, seat, this.skillForSeat(seat));
if (!mv) { this.gs = passTurn(this.gs, seat); this.animating = false; this.driveTurn(); return; }
this.placePiece(seat, mv, () => { this.animating = false; this.driveTurn(); }, true);
this.placePiece(seat, mv, () => { this.animating = false; this.driveTurn(); });
}
humanPass() {
@ -402,79 +378,22 @@ export default class BlokusGame extends Phaser.Scene {
this.driveTurn();
}
placePiece(seat, move, done, animateFromPortrait = false) {
placePiece(seat, move, done) {
const next = applyPlacement(this.gs, seat, move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
if (next === this.gs) { done(); return; } // illegal — should not happen
this.gs = next;
playSound(this, SFX.CARD_PLACE);
this.renderPieces();
this.renderTray();
this.renderScores();
if (animateFromPortrait && seat > 0) {
this.animateAIPiece(seat, move, done);
} else {
this.renderPieces();
this.renderTray();
this.renderScores();
// Brief pop on the freshly placed cells.
const cells = placementCells(move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
const flash = this.add.graphics().setDepth(DEPTH.ghost);
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
this.time.delayedCall(180, done);
}
}
animateAIPiece(seat, move, done) {
// Brief pop on the freshly placed cells.
const cells = placementCells(move.pieceId, move.oriIdx, move.anchorR, move.anchorC);
const col = COLOR_PALETTE[seat];
const flash = this.add.graphics().setDepth(DEPTH.ghost);
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
// Calculate board center of the piece
let avgR = 0, avgC = 0;
for (const [r, c] of cells) { avgR += r; avgC += c; }
avgR /= cells.length;
avgC /= cells.length;
const destX = GRID_X + avgC * CELL;
const destY = GRID_Y + avgR * CELL;
// Portrait position for this opponent
const panelH = 192;
const portraitYOffset = -panelH / 2 + 60 + 14;
const panelY = 221 + (seat - 1) * (panelH + 10);
const startX = SIDE_X;
const startY = panelY + portraitYOffset + 200;
// Create flying piece graphics — draw centered at (0,0) so we can tween position
const flyGfx = this.add.graphics().setDepth(DEPTH.piece + 1);
flyGfx.fillStyle(col.hex, 1);
flyGfx.lineStyle(2, col.dark, 1);
for (const [r, c] of cells) {
const cx = (c - avgC) * CELL;
const cy = (r - avgR) * CELL;
flyGfx.fillRoundedRect(cx - CELL / 2 + 2, cy - CELL / 2 + 2, CELL - 4, CELL - 4, 6);
flyGfx.strokeRoundedRect(cx - CELL / 2 + 2, cy - CELL / 2 + 2, CELL - 4, CELL - 4, 6);
}
flyGfx.x = startX;
flyGfx.y = startY;
// Animate from portrait to board
const duration = 400;
this.tweens.add({
targets: flyGfx,
x: destX,
y: destY,
duration,
ease: 'Quad.easeInOut',
onComplete: () => {
flyGfx.destroy();
this.renderPieces();
this.renderTray();
this.renderScores();
// Brief pop on the freshly placed cells.
const flash = this.add.graphics().setDepth(DEPTH.ghost);
for (const [r, c] of cells) this.fillCell(flash, r, c, 0xffffff, 0.5);
this.tweens.add({ targets: flash, alpha: 0, duration: 280, ease: 'Quad.easeOut', onComplete: () => flash.destroy() });
this.time.delayedCall(180, done);
},
});
this.time.delayedCall(180, done);
}
updateButtons() {