feat: upgrade Ticket to Ride UI with card sprites and improved layout
- Replace procedural card drawing with spritesheet images for better visuals - Rotate cards to landscape orientation and adjust right-panel dimensions - Reorganize opponent panels into a two-column grid for better screen utilization - Add circular score badges above player and opponent portraits - Display actual top card on discard pile instead of generic face-down graphic - Move game log to bottom-right corner - Update PreloadScene to load new card assets
This commit is contained in:
parent
975d40b4b0
commit
8222b61f0a
|
|
@ -21,11 +21,16 @@ const D = {
|
||||||
|
|
||||||
const HAND_ORDER = [...TRAIN_COLORS, 'locomotive'];
|
const HAND_ORDER = [...TRAIN_COLORS, 'locomotive'];
|
||||||
|
|
||||||
|
const TTR_CARD_FRAME = {
|
||||||
|
red: 0, orange: 1, yellow: 2, green: 3, blue: 4,
|
||||||
|
purple: 5, black: 6, white: 7, locomotive: 8, back: 9,
|
||||||
|
};
|
||||||
|
|
||||||
// Right-band + bottom-strip layout (1920×1080). The map occupies x<1400.
|
// Right-band + bottom-strip layout (1920×1080). The map occupies x<1400.
|
||||||
const RB = { x0: 1408, cx: 1660, w: 512 };
|
const RB = { x0: 1408, cx: 1660, w: 512 };
|
||||||
const OPP_X = 1452, OPP_Y0 = 70, OPP_STEP = 96, OPP_R = 32;
|
const OPP_COL1_X = 1450, OPP_COL2_X = 1706, OPP_Y0 = 150, OPP_STEP = 100, OPP_R = 32;
|
||||||
const MK_X = 1470, MK_Y0 = 392, MK_STEP = 86, CARD_W = 78, CARD_H = 74;
|
const MK_X = 1476, MK_Y0 = 507, MK_STEP = 104, CARD_W = 78, CARD_H = 112;
|
||||||
const PILE_X = 1640;
|
const PILE_X = 1646;
|
||||||
const BOT_Y = 980;
|
const BOT_Y = 980;
|
||||||
|
|
||||||
export default class TicketToRideGame extends Phaser.Scene {
|
export default class TicketToRideGame extends Phaser.Scene {
|
||||||
|
|
@ -43,6 +48,9 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
this.handObjs = [];
|
this.handObjs = [];
|
||||||
this.hoverRouteId = null;
|
this.hoverRouteId = null;
|
||||||
this.bannerShownFor = -1;
|
this.bannerShownFor = -1;
|
||||||
|
this.playerScoreText = null;
|
||||||
|
this.scoreBadgeContainers = [];
|
||||||
|
this.oppScoreTexts = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
@ -133,25 +141,25 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
fontFamily: 'Righteous', fontSize: '20px', color: COLORS.accentHex,
|
fontFamily: 'Righteous', fontSize: '20px', color: COLORS.accentHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.hud);
|
}).setOrigin(0, 0.5).setDepth(D.hud);
|
||||||
|
|
||||||
// Draw pile (face-down) — clickable.
|
// Draw pile (face-down, rotated landscape) — clickable.
|
||||||
this.deckCard = this.add.graphics().setDepth(D.hud);
|
this.deckCard = this.add.image(PILE_X, MK_Y0, 'ttr-cards', TTR_CARD_FRAME.back)
|
||||||
this.drawPileFace(this.deckCard, PILE_X, MK_Y0, 'Deck');
|
.setDisplaySize(CARD_W, CARD_H).setAngle(-90).setDepth(D.hud);
|
||||||
this.deckCount = this.add.text(PILE_X, MK_Y0 + 4, '', {
|
this.deckCount = this.add.text(PILE_X, MK_Y0 + 4, '', {
|
||||||
fontFamily: 'Righteous', fontSize: '24px', color: '#fdf3d8',
|
fontFamily: 'Righteous', fontSize: '24px', color: '#fdf3d8',
|
||||||
}).setOrigin(0.5).setDepth(D.hud + 1);
|
}).setOrigin(0.5).setDepth(D.hud + 1);
|
||||||
this.add.text(PILE_X, MK_Y0 - CARD_H / 2 - 14, 'Draw', {
|
this.add.text(PILE_X, MK_Y0 - CARD_W / 2 - 14, 'Draw', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.hud);
|
}).setOrigin(0.5).setDepth(D.hud);
|
||||||
this.makeZone(PILE_X, MK_Y0, CARD_W, CARD_H, () => this.onDeckClick());
|
this.makeZone(PILE_X, MK_Y0, CARD_H, CARD_W, () => this.onDeckClick());
|
||||||
|
|
||||||
// Discard pile (display only).
|
// Discard pile (display only, rotated landscape).
|
||||||
const discY = MK_Y0 + MK_STEP * 1.5;
|
const discY = MK_Y0 + MK_STEP * 1.5;
|
||||||
this.discardCard = this.add.graphics().setDepth(D.hud);
|
this.discardCard = this.add.image(PILE_X, discY, 'ttr-cards', TTR_CARD_FRAME.back)
|
||||||
this.drawPileFace(this.discardCard, PILE_X, discY, 'Discard', 0x3a2f22);
|
.setDisplaySize(CARD_W, CARD_H).setAngle(-90).setAlpha(0.3).setDepth(D.hud);
|
||||||
this.discardCount = this.add.text(PILE_X, discY + 4, '', {
|
this.discardCount = this.add.text(PILE_X, discY + 4, '', {
|
||||||
fontFamily: 'Righteous', fontSize: '20px', color: COLORS.mutedHex,
|
fontFamily: 'Righteous', fontSize: '20px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.hud + 1);
|
}).setOrigin(0.5).setDepth(D.hud + 1);
|
||||||
this.add.text(PILE_X, discY - CARD_H / 2 - 14, 'Discard', {
|
this.add.text(PILE_X, discY - CARD_W / 2 - 14, 'Discard', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.hud);
|
}).setOrigin(0.5).setDepth(D.hud);
|
||||||
|
|
||||||
|
|
@ -172,13 +180,6 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
this.makeZone(PILE_X, tY, CARD_W, CARD_H, () => this.onTicketDeckClick());
|
this.makeZone(PILE_X, tY, CARD_W, CARD_H, () => this.onTicketDeckClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPileFace(g, x, y, _label, fill = 0x243b52) {
|
|
||||||
g.fillStyle(fill, 1);
|
|
||||||
g.fillRoundedRect(x - CARD_W / 2, y - CARD_H / 2, CARD_W, CARD_H, 8);
|
|
||||||
g.lineStyle(3, 0xfdf3d8, 0.85);
|
|
||||||
g.strokeRoundedRect(x - CARD_W / 2, y - CARD_H / 2, CARD_W, CARD_H, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeZone(x, y, w, h, fn) {
|
makeZone(x, y, w, h, fn) {
|
||||||
const z = this.add.zone(x, y, w, h).setInteractive({ useHandCursor: true }).setDepth(D.hud + 4);
|
const z = this.add.zone(x, y, w, h).setInteractive({ useHandCursor: true }).setDepth(D.hud + 4);
|
||||||
z.on('pointerdown', fn);
|
z.on('pointerdown', fn);
|
||||||
|
|
@ -188,28 +189,61 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
buildOpponentPanels() {
|
buildOpponentPanels() {
|
||||||
this.opponentPortraits.forEach((p) => p.destroy?.());
|
this.opponentPortraits.forEach((p) => p.destroy?.());
|
||||||
this.opponentPortraits = [];
|
this.opponentPortraits = [];
|
||||||
this.oppPanelText.forEach((t) => t.destroy());
|
this.oppPanelText.forEach((t) => t?.destroy());
|
||||||
this.oppPanelText = [];
|
this.oppPanelText = [];
|
||||||
this.oppRingGfx?.destroy();
|
this.oppRingGfx?.destroy();
|
||||||
this.oppRingGfx = this.add.graphics().setDepth(D.hud + 3);
|
this.oppRingGfx = this.add.graphics().setDepth(D.hud + 3);
|
||||||
|
this.scoreBadgeContainers.forEach((c) => c.destroy());
|
||||||
|
this.scoreBadgeContainers = [];
|
||||||
|
this.oppScoreTexts = {};
|
||||||
|
|
||||||
|
// Player score badge (above human portrait in the bottom HUD)
|
||||||
|
const pb = this._makeScoreBadge(80, BOT_Y - 64, this.playerColor(0).hex, 16);
|
||||||
|
this.playerScoreText = pb.text;
|
||||||
|
this.scoreBadgeContainers.push(pb.container);
|
||||||
|
|
||||||
for (let seat = 1; seat < this.gs.playerCount; seat++) {
|
for (let seat = 1; seat < this.gs.playerCount; seat++) {
|
||||||
const y = OPP_Y0 + (seat - 1) * OPP_STEP;
|
const { x, y } = this.oppPos(seat);
|
||||||
const opp = this.opponents[seat - 1];
|
const opp = this.opponents[seat - 1];
|
||||||
const portrait = createOpponentPortrait(this, opp, OPP_X, y, OPP_R, D.hud, { playIntro: seat === 1 });
|
const portrait = createOpponentPortrait(this, opp, x, y, OPP_R, D.hud, { playIntro: seat === 1 });
|
||||||
this.opponentPortraits.push(portrait);
|
this.opponentPortraits.push(portrait);
|
||||||
const col = PLAYER_COLORS[this.gs.players[seat].colorIndex];
|
const col = PLAYER_COLORS[this.gs.players[seat].colorIndex];
|
||||||
this.add.circle(OPP_X, y, OPP_R + 4, col.hex, 0).setStrokeStyle(3, col.hex, 0.95).setDepth(D.hud + 2);
|
this.add.circle(x, y, OPP_R + 4, col.hex, 0).setStrokeStyle(3, col.hex, 0.95).setDepth(D.hud + 2);
|
||||||
this.add.text(OPP_X + OPP_R + 16, y - 20, this.pname(seat), {
|
this.add.text(x + OPP_R + 12, y - 18, this.pname(seat), {
|
||||||
fontFamily: 'Righteous', fontSize: '17px', color: COLORS.textHex,
|
fontFamily: 'Righteous', fontSize: '15px', color: COLORS.textHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.hud + 1);
|
}).setOrigin(0, 0.5).setDepth(D.hud + 1);
|
||||||
const info = this.add.text(OPP_X + OPP_R + 16, y + 6, '', {
|
const info = this.add.text(x + OPP_R + 12, y + 6, '', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '13px', color: COLORS.mutedHex, lineSpacing: 2,
|
fontFamily: '"Julius Sans One"', fontSize: '12px', color: COLORS.mutedHex, lineSpacing: 2,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.hud + 1);
|
}).setOrigin(0, 0.5).setDepth(D.hud + 1);
|
||||||
this.oppPanelText[seat] = info;
|
this.oppPanelText[seat] = info;
|
||||||
|
|
||||||
|
// Score badge above opponent portrait
|
||||||
|
const ob = this._makeScoreBadge(x, y - OPP_R - 14, col.hex, 13);
|
||||||
|
this.oppScoreTexts[seat] = ob.text;
|
||||||
|
this.scoreBadgeContainers.push(ob.container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_makeScoreBadge(x, y, color, radius = 16) {
|
||||||
|
const c = this.add.container(x, y).setDepth(D.hud + 3);
|
||||||
|
const g = this.add.graphics();
|
||||||
|
g.fillStyle(color, 1);
|
||||||
|
g.fillCircle(0, 0, radius);
|
||||||
|
g.lineStyle(2, 0xffffff, 0.35);
|
||||||
|
g.strokeCircle(0, 0, radius);
|
||||||
|
const t = this.add.text(0, 0, '0', {
|
||||||
|
fontFamily: 'Righteous', fontSize: radius > 14 ? '13px' : '11px', color: '#ffffff',
|
||||||
|
}).setOrigin(0.5);
|
||||||
|
c.add([g, t]);
|
||||||
|
return { container: c, text: t };
|
||||||
|
}
|
||||||
|
|
||||||
|
oppPos(seat) {
|
||||||
|
const row = Math.floor((seat - 1) / 2);
|
||||||
|
const col = (seat - 1) % 2;
|
||||||
|
return { x: col === 0 ? OPP_COL1_X : OPP_COL2_X, y: OPP_Y0 + row * OPP_STEP };
|
||||||
|
}
|
||||||
|
|
||||||
// ── bottom HUD: human portrait, hand, status, leave ───────────────────────────
|
// ── bottom HUD: human portrait, hand, status, leave ───────────────────────────
|
||||||
buildHUD() {
|
buildHUD() {
|
||||||
this.add.rectangle(GAME_WIDTH / 2, BOT_Y + 30, GAME_WIDTH, 200, COLORS.panel, 0.92).setDepth(D.hud - 1);
|
this.add.rectangle(GAME_WIDTH / 2, BOT_Y + 30, GAME_WIDTH, 200, COLORS.panel, 0.92).setDepth(D.hud - 1);
|
||||||
|
|
@ -219,13 +253,6 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
this.add.text(80, BOT_Y + 60, auth.user?.username ?? 'You', {
|
this.add.text(80, BOT_Y + 60, auth.user?.username ?? 'You', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.textHex,
|
fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5).setDepth(D.hud);
|
}).setOrigin(0.5).setDepth(D.hud);
|
||||||
this.playerScoreText = this.add.text(80, BOT_Y - 64, '0', {
|
|
||||||
fontFamily: 'Righteous', fontSize: '22px', color: COLORS.goldHex,
|
|
||||||
}).setOrigin(0.5).setDepth(D.hud + 1);
|
|
||||||
this.add.text(80, BOT_Y - 90, 'SCORE', {
|
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '11px', color: COLORS.mutedHex,
|
|
||||||
}).setOrigin(0.5).setDepth(D.hud + 1);
|
|
||||||
|
|
||||||
this.add.text(180, BOT_Y - 70, 'Your Trains', {
|
this.add.text(180, BOT_Y - 70, 'Your Trains', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.hud);
|
}).setOrigin(0, 0.5).setDepth(D.hud);
|
||||||
|
|
@ -241,9 +268,10 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
backgroundColor: '#111923cc', padding: { x: 18, y: 8 },
|
backgroundColor: '#111923cc', padding: { x: 18, y: 8 },
|
||||||
}).setOrigin(0.5).setDepth(D.banner);
|
}).setOrigin(0.5).setDepth(D.banner);
|
||||||
|
|
||||||
this.logText = this.add.text(GAME_WIDTH / 2, 1068, '', {
|
this.logText = this.add.text(1760, BOT_Y + 32, '', {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0.5, 1).setDepth(D.hud);
|
wordWrap: { width: 300 }, align: 'center',
|
||||||
|
}).setOrigin(0.5, 0).setDepth(D.hud);
|
||||||
|
|
||||||
new Button(this, 80, 40, 'Leave', () => this.scene.start('GameMenu'),
|
new Button(this, 80, 40, 'Leave', () => this.scene.start('GameMenu'),
|
||||||
{ variant: 'ghost', width: 120, height: 40, fontSize: 17 }).setDepth(D.banner);
|
{ variant: 'ghost', width: 120, height: 40, fontSize: 17 }).setDepth(D.banner);
|
||||||
|
|
@ -442,61 +470,44 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
this.marketObjs.forEach((o) => o.destroy());
|
this.marketObjs.forEach((o) => o.destroy());
|
||||||
this.marketObjs = [];
|
this.marketObjs = [];
|
||||||
const myTurn = this.canHumanAct();
|
const myTurn = this.canHumanAct();
|
||||||
|
const mkStep = CARD_W + 10; // rotated visual height + padding
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const y = MK_Y0 + i * MK_STEP;
|
const y = MK_Y0 + i * mkStep;
|
||||||
const color = this.gs.faceUp[i];
|
const color = this.gs.faceUp[i];
|
||||||
if (color == null) {
|
if (color == null) {
|
||||||
const g = this.add.graphics().setDepth(D.market);
|
const g = this.add.graphics().setDepth(D.market);
|
||||||
g.lineStyle(2, COLORS.mutedHex, 0.5);
|
g.lineStyle(2, COLORS.mutedHex, 0.5);
|
||||||
g.strokeRoundedRect(MK_X - CARD_W / 2, y - CARD_H / 2, CARD_W, CARD_H, 8);
|
g.strokeRoundedRect(MK_X - CARD_H / 2, y - CARD_W / 2, CARD_H, CARD_W, 8);
|
||||||
this.marketObjs.push(g);
|
this.marketObjs.push(g);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.marketObjs.push(...this.makeCardFace(MK_X, y, CARD_W, CARD_H, color, D.market, ''));
|
const objs = this.makeCardFace(MK_X, y, CARD_W, CARD_H, color, D.market, '');
|
||||||
const z = this.add.zone(MK_X, y, CARD_W, CARD_H).setDepth(D.market + 2);
|
objs.forEach((o) => o.setAngle(-90));
|
||||||
|
this.marketObjs.push(...objs);
|
||||||
|
const z = this.add.zone(MK_X, y, CARD_H, CARD_W).setDepth(D.market + 2);
|
||||||
if (myTurn) z.setInteractive({ useHandCursor: true });
|
if (myTurn) z.setInteractive({ useHandCursor: true });
|
||||||
z.on('pointerdown', () => this.onMarketClick(i));
|
z.on('pointerdown', () => this.onMarketClick(i));
|
||||||
this.marketObjs.push(z);
|
this.marketObjs.push(z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the display objects for a card face (graphics + label text).
|
makeCardFace(x, y, w, h, colorKey, depth, _label) {
|
||||||
makeCardFace(x, y, w, h, colorKey, depth, label) {
|
const frame = TTR_CARD_FRAME[colorKey] ?? TTR_CARD_FRAME.back;
|
||||||
const objs = [];
|
return [this.add.image(x, y, 'ttr-cards', frame).setDisplaySize(w, h).setDepth(depth)];
|
||||||
const g = this.add.graphics().setDepth(depth);
|
|
||||||
const fill = CARD_COLOR_HEX[colorKey] ?? 0x888888;
|
|
||||||
g.fillStyle(fill, 1);
|
|
||||||
g.fillRoundedRect(x - w / 2, y - h / 2, w, h, 8);
|
|
||||||
if (colorKey === 'locomotive') {
|
|
||||||
// rainbow accent stripe for the wild card
|
|
||||||
const cols = [0xd23b3b, 0xe0b000, 0x2e7d32, 0x2d6cdf, 0x8e44ad];
|
|
||||||
cols.forEach((c, i) => {
|
|
||||||
g.fillStyle(c, 0.9);
|
|
||||||
g.fillRect(x - w / 2 + 6 + i * ((w - 12) / cols.length), y - 8, (w - 12) / cols.length, 16);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
g.lineStyle(2.5, 0xfdf3d8, 0.9);
|
|
||||||
g.strokeRoundedRect(x - w / 2, y - h / 2, w, h, 8);
|
|
||||||
objs.push(g);
|
|
||||||
const txt = colorKey === 'locomotive' ? 'LOCO' : CARD_LABEL[colorKey];
|
|
||||||
objs.push(this.add.text(x, y + h / 2 - 12, label || txt, {
|
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '13px', color: this.textColorFor(colorKey),
|
|
||||||
}).setOrigin(0.5).setDepth(depth + 1));
|
|
||||||
return objs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHand() {
|
renderHand() {
|
||||||
this.handObjs.forEach((o) => o.destroy());
|
this.handObjs.forEach((o) => o.destroy());
|
||||||
this.handObjs = [];
|
this.handObjs = [];
|
||||||
const hand = this.gs.players[0].hand;
|
const hand = this.gs.players[0].hand;
|
||||||
const w = 66, h = 84, step = 88, x0 = 400;
|
const h = 140, w = 97, step = 105, x0 = 400, cy = 1000;
|
||||||
HAND_ORDER.forEach((colorKey, i) => {
|
HAND_ORDER.forEach((colorKey, i) => {
|
||||||
const x = x0 + i * step;
|
const x = x0 + i * step;
|
||||||
const count = hand[colorKey];
|
const count = hand[colorKey];
|
||||||
const objs = this.makeCardFace(x, BOT_Y + 2, w, h, colorKey, D.hud, '');
|
const objs = this.makeCardFace(x, cy, w, h, colorKey, D.hud, '');
|
||||||
if (count === 0) objs.forEach((o) => o.setAlpha(0.3));
|
if (count === 0) objs.forEach((o) => o.setAlpha(0.3));
|
||||||
this.handObjs.push(...objs);
|
this.handObjs.push(...objs);
|
||||||
this.handObjs.push(this.add.text(x, BOT_Y - h / 2 - 8, `×${count}`, {
|
this.handObjs.push(this.add.text(x, cy - h / 2 - 8, `×${count}`, {
|
||||||
fontFamily: 'Righteous', fontSize: '18px', color: count > 0 ? COLORS.textHex : COLORS.mutedHex,
|
fontFamily: 'Righteous', fontSize: '18px', color: count > 0 ? COLORS.textHex : COLORS.mutedHex,
|
||||||
}).setOrigin(0.5).setDepth(D.hud + 1));
|
}).setOrigin(0.5).setDepth(D.hud + 1));
|
||||||
});
|
});
|
||||||
|
|
@ -504,12 +515,15 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
|
|
||||||
renderPiles() {
|
renderPiles() {
|
||||||
this.deckCount.setText(String(this.gs.trainDeck.length));
|
this.deckCount.setText(String(this.gs.trainDeck.length));
|
||||||
|
const topCard = this.gs.discard.at(-1) ?? null;
|
||||||
|
this.discardCard.setFrame(topCard ? (TTR_CARD_FRAME[topCard] ?? TTR_CARD_FRAME.back) : TTR_CARD_FRAME.back);
|
||||||
|
this.discardCard.setAlpha(topCard ? 1 : 0.3);
|
||||||
this.discardCount.setText(String(this.gs.discard.length));
|
this.discardCount.setText(String(this.gs.discard.length));
|
||||||
this.ticketCount.setText(`${this.gs.ticketDeck.length} left`);
|
this.ticketCount.setText(`${this.gs.ticketDeck.length} left`);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPanels() {
|
renderPanels() {
|
||||||
this.playerScoreText.setText(String(L.publicScore(this.gs, 0)));
|
this.playerScoreText?.setText(String(L.publicScore(this.gs, 0)));
|
||||||
this.trainsText.setText(String(this.gs.players[0].trainsLeft));
|
this.trainsText.setText(String(this.gs.players[0].trainsLeft));
|
||||||
|
|
||||||
this.oppRingGfx.clear();
|
this.oppRingGfx.clear();
|
||||||
|
|
@ -517,12 +531,13 @@ export default class TicketToRideGame extends Phaser.Scene {
|
||||||
const p = this.gs.players[seat];
|
const p = this.gs.players[seat];
|
||||||
const info = this.oppPanelText[seat];
|
const info = this.oppPanelText[seat];
|
||||||
if (info) {
|
if (info) {
|
||||||
info.setText(`Trains ${p.trainsLeft} Cards ${L.handCount(p)}\nTickets ${p.tickets.length} Score ${L.publicScore(this.gs, seat)}`);
|
info.setText(`Trains ${p.trainsLeft} Cards ${L.handCount(p)}\nTickets ${p.tickets.length}`);
|
||||||
}
|
}
|
||||||
|
this.oppScoreTexts[seat]?.setText(String(L.publicScore(this.gs, seat)));
|
||||||
if (this.gs.currentPlayer === seat) {
|
if (this.gs.currentPlayer === seat) {
|
||||||
const y = OPP_Y0 + (seat - 1) * OPP_STEP;
|
const { x, y } = this.oppPos(seat);
|
||||||
this.oppRingGfx.lineStyle(4, COLORS.goldHex, 1);
|
this.oppRingGfx.lineStyle(4, COLORS.goldHex, 1);
|
||||||
this.oppRingGfx.strokeCircle(OPP_X, y, OPP_R + 8);
|
this.oppRingGfx.strokeCircle(x, y, OPP_R + 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ export default class PreloadScene extends Phaser.Scene {
|
||||||
// Prosperity expansion art (frame order documented in expansions/prosperity.js).
|
// Prosperity expansion art (frame order documented in expansions/prosperity.js).
|
||||||
// Optional — same procedural fallback applies when the sheet is absent.
|
// Optional — same procedural fallback applies when the sheet is absent.
|
||||||
this.load.spritesheet('dominion-prosperity', '/assets/images/dominion-prosperity.png', { frameWidth: 270, frameHeight: 390 });
|
this.load.spritesheet('dominion-prosperity', '/assets/images/dominion-prosperity.png', { frameWidth: 270, frameHeight: 390 });
|
||||||
|
this.load.spritesheet('ttr-cards', '/assets/images/tickettoride-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue