diff --git a/public/src/games/phase10/Phase10Game.js b/public/src/games/phase10/Phase10Game.js index 09074ee..9a062cd 100644 --- a/public/src/games/phase10/Phase10Game.js +++ b/public/src/games/phase10/Phase10Game.js @@ -476,9 +476,12 @@ export default class Phase10Game extends Phaser.Scene { g.strokeRoundedRect(x, y, CARD_W, CARD_H, CARD_R); g.lineStyle(2, WILD_STRIPE, 0.5); for (let s = -CARD_H; s < CARD_W; s += 14) { + const t0 = Math.max(0, -s / CARD_H); + const t1 = Math.min(1, (CARD_W - s) / CARD_H); + if (t0 >= t1) continue; g.beginPath(); - g.moveTo(x + s, y + CARD_H); - g.lineTo(x + s + CARD_H, y); + g.moveTo(x + s + t0 * CARD_H, y + CARD_H * (1 - t0)); + g.lineTo(x + s + t1 * CARD_H, y + CARD_H * (1 - t1)); g.strokePath(); } container.add(g); diff --git a/public/src/games/skipbo/SkipBoGame.js b/public/src/games/skipbo/SkipBoGame.js index 55c66d3..55b9688 100644 --- a/public/src/games/skipbo/SkipBoGame.js +++ b/public/src/games/skipbo/SkipBoGame.js @@ -67,52 +67,52 @@ function slotLayout(slot) { case 'bottom': return { rotation: 0, - stock: { x: 460, y: 950 }, - discards: [{x:630,y:950},{x:750,y:950},{x:870,y:950},{x:990,y:950}], - handStart:{ x: 1110, y: 950 }, + stock: { x: 460, y: 950 }, + discards: [{ x: 630, y: 950 }, { x: 750, y: 950 }, { x: 870, y: 950 }, { x: 990, y: 950 }], + handStart: { x: 1110, y: 975 }, handAxis: 'x', portrait: { x: 320, y: 950, r: 60 }, - nameLabel:{ x: 320, y: 1030 }, - stockCntPos: { x: 460, y: 880 }, + nameLabel: { x: 320, y: 1030 }, + stockCntPos: { x: 460, y: 855 }, rotateCards: 0, outlineRotation: 0, }; case 'top': return { rotation: 180, - stock: { x: 560, y: 130 }, - discards: [{x:730,y:130},{x:850,y:130},{x:970,y:130},{x:1090,y:130}], - handStart:{ x: 1000, y: 80 }, + stock: { x: 560, y: 130 }, + discards: [{ x: 730, y: 130 }, { x: 850, y: 130 }, { x: 970, y: 130 }, { x: 1090, y: 130 }], + handStart: { x: 1000, y: 80 }, handAxis: 'x', portrait: { x: 420, y: 130, r: 60 }, - nameLabel:{ x: 420, y: 210 }, - stockCntPos: { x: 560, y: 60 }, + nameLabel: { x: 420, y: 210 }, + stockCntPos: { x: 560, y: 225 }, rotateCards: 180, outlineRotation: 0, }; case 'left': return { rotation: 90, - stock: { x: 100, y: 260 }, - discards: [{x:100,y:430},{x:100,y:550},{x:100,y:670},{x:100,y:790}], - handStart:{ x: 40, y: 920 }, + stock: { x: 100, y: 260 }, + discards: [{ x: 100, y: 430 }, { x: 100, y: 550 }, { x: 100, y: 670 }, { x: 100, y: 790 }], + handStart: { x: 40, y: 920 }, handAxis: 'y-up', // hand fans upward toward 920..520 portrait: { x: 100, y: 130, r: 56 }, - nameLabel:{ x: 100, y: 50 }, - stockCntPos:{ x: 180, y: 260 }, + nameLabel: { x: 100, y: 50 }, + stockCntPos: { x: 205, y: 260 }, rotateCards: 90, outlineRotation: 90, }; case 'right': return { rotation: 270, - stock: { x: 1820, y: 260 }, - discards: [{x:1820,y:430},{x:1820,y:550},{x:1820,y:670},{x:1820,y:790}], - handStart:{ x: 1880,y: 920 }, + stock: { x: 1820, y: 260 }, + discards: [{ x: 1820, y: 430 }, { x: 1820, y: 550 }, { x: 1820, y: 670 }, { x: 1820, y: 790 }], + handStart: { x: 1880, y: 920 }, handAxis: 'y-up', portrait: { x: 1820, y: 130, r: 56 }, - nameLabel:{ x: 1820, y: 50 }, - stockCntPos:{ x: 1740,y: 260 }, + nameLabel: { x: 1820, y: 50 }, + stockCntPos: { x: 1715, y: 260 }, rotateCards: 270, outlineRotation: 90, }; @@ -123,7 +123,7 @@ function slotLayout(slot) { // Center build piles const BUILD_X0 = CX - (CARD_W + 28) * 1.5; -const BUILD_Y = CY + 30; +const BUILD_Y = CY + 30; function buildPilePos(idx) { return { x: BUILD_X0 + idx * (CARD_W + 28), y: BUILD_Y }; } @@ -137,7 +137,7 @@ export default class SkipBoGame extends Phaser.Scene { this.gameDef = data.game; this.opponents = data.opponents ?? []; this.playfield = data.playfield ?? null; - this.cardBack = data.cardBack ?? null; + this.cardBack = data.cardBack ?? null; // Runtime state this.gs = null; @@ -203,9 +203,11 @@ export default class SkipBoGame extends Phaser.Scene { const sRect = this.add.rectangle(layout.stock.x, layout.stock.y, CARD_W + 6, CARD_H + 6, 0x000000, 0.35) .setStrokeStyle(2, COLORS.muted).setAngle(layout.outlineRotation).setDepth(D.pile); // Stock count badge + this.add.rectangle(layout.stockCntPos.x, layout.stockCntPos.y, 52, 34, 0x000000, 0.55) + .setDepth(D.ui); const sCnt = this.add.text(layout.stockCntPos.x, layout.stockCntPos.y, '0', { fontFamily: '"Julius Sans One"', fontSize: '26px', color: COLORS.accentHex, fontStyle: 'bold', - }).setOrigin(0.5).setDepth(D.ui); + }).setOrigin(0.5).setDepth(D.ui + 1); this.stockPileObjs[seat] = { rect: sRect, count: sCnt, x: layout.stock.x, y: layout.stock.y }; if (seat === 0) { @@ -249,7 +251,7 @@ export default class SkipBoGame extends Phaser.Scene { // Draw pile placeholder const drawRect = this.add.rectangle(DRAW_POS.x, DRAW_POS.y, CARD_W + 8, CARD_H + 8, 0x000000, 0.4) .setStrokeStyle(2, COLORS.accent).setDepth(D.pile); - this.add.text(DRAW_POS.x, DRAW_POS.y - CARD_H/2 - 18, 'DRAW', { + this.add.text(DRAW_POS.x, DRAW_POS.y - CARD_H / 2 - 18, 'DRAW', { fontFamily: '"Julius Sans One"', fontSize: '16px', color: COLORS.mutedHex, }).setOrigin(0.5).setDepth(D.ui); this.drawPileText = this.add.text(DRAW_POS.x, DRAW_POS.y, '', { @@ -264,10 +266,10 @@ export default class SkipBoGame extends Phaser.Scene { .setStrokeStyle(2, COLORS.muted).setDepth(D.pile) .setInteractive({ useHandCursor: true }); r.on('pointerdown', () => this.onBuildPileClick(i)); - const label = this.add.text(p.x, p.y - CARD_H/2 - 18, `BUILD ${i + 1}`, { + const label = this.add.text(p.x, p.y - CARD_H / 2 - 18, `BUILD ${i + 1}`, { fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex, }).setOrigin(0.5).setDepth(D.ui); - const needText = this.add.text(p.x, p.y + CARD_H/2 + 18, 'next: 1', { + const needText = this.add.text(p.x, p.y + CARD_H / 2 + 18, 'next: 1', { fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.accentHex, }).setOrigin(0.5).setDepth(D.ui); this.buildPileObjs[i] = { rect: r, label, needText, x: p.x, y: p.y }; @@ -344,12 +346,15 @@ export default class SkipBoGame extends Phaser.Scene { g.fillRoundedRect(x, y, CARD_W, CARD_H, CARD_R); g.lineStyle(3, WILD_STRIPE, 1); g.strokeRoundedRect(x, y, CARD_W, CARD_H, CARD_R); - // Diagonal stripes + // Diagonal stripes — clipped to card bounds g.lineStyle(2, WILD_STRIPE, 0.5); for (let s = -CARD_H; s < CARD_W; s += 14) { + const t0 = Math.max(0, -s / CARD_H); + const t1 = Math.min(1, (CARD_W - s) / CARD_H); + if (t0 >= t1) continue; g.beginPath(); - g.moveTo(x + s, y + CARD_H); - g.lineTo(x + s + CARD_H, y); + g.moveTo(x + s + t0 * CARD_H, y + CARD_H * (1 - t0)); + g.lineTo(x + s + t1 * CARD_H, y + CARD_H * (1 - t1)); g.strokePath(); } container.add(g); @@ -462,7 +467,7 @@ export default class SkipBoGame extends Phaser.Scene { }); this.cardObjs.set(`stock-${seat}-${top.id}`, c); if (seat === 0) { - c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W/2, -CARD_H/2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); + c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); c.input.cursor = 'pointer'; c.on('pointerdown', () => this.onStockClick()); } @@ -479,13 +484,13 @@ export default class SkipBoGame extends Phaser.Scene { }); this.cardObjs.set(`discard-${seat}-${d}-${t.id}`, c); if (seat === 0) { - c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W/2, -CARD_H/2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); + c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); c.input.cursor = 'pointer'; c.on('pointerdown', () => this.onDiscardClick(d)); } // Pile-depth chip if more than one card if (pile.length > 1) { - const chip = this.add.text(pos.x + CARD_W/2 - 4, pos.y + CARD_H/2 - 4, `×${pile.length}`, { + const chip = this.add.text(pos.x + CARD_W / 2 - 4, pos.y + CARD_H / 2 - 4, `×${pile.length}`, { fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.textDarkHex, backgroundColor: COLORS.accentHex, padding: { x: 4, y: 2 }, @@ -519,7 +524,7 @@ export default class SkipBoGame extends Phaser.Scene { }); this.cardObjs.set(`hand-${seat}-${card.id}`, c); if (isLocal) { - c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W/2, -CARD_H/2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); + c.setInteractive(new Phaser.Geom.Rectangle(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains); c.input.cursor = 'pointer'; c.on('pointerdown', () => this.onHandClick(i)); } @@ -597,8 +602,8 @@ export default class SkipBoGame extends Phaser.Scene { } cardForSelection(sel) { - if (sel.kind === 'hand') return this.gs.players[0].hand[sel.idx]; - if (sel.kind === 'stock') return stockTop(this.gs, 0); + if (sel.kind === 'hand') return this.gs.players[0].hand[sel.idx]; + if (sel.kind === 'stock') return stockTop(this.gs, 0); if (sel.kind === 'discard') return discardTop(this.gs, 0, sel.idx); return null; } @@ -796,7 +801,7 @@ export default class SkipBoGame extends Phaser.Scene { const p = this.opponentPortraits[s]; if (!p) continue; if (winnerSeat === s) p.playEmotion?.('win'); - else p.playEmotion?.('loss'); + else p.playEmotion?.('loss'); } this.recordHistory(youWon);