diff --git a/client/src/Animation.ts b/client/src/Animation.ts index bd49ce4..76946ad 100644 --- a/client/src/Animation.ts +++ b/client/src/Animation.ts @@ -2,30 +2,41 @@ import type { Container, Sprite, Ticker, TickerCallback } from "pixi.js"; import { CardStatus, Hand } from "./Hand"; import { getSpriteSheet } from "./spritesheet"; import { CARD_HEIGHT, CARD_WIDTH } from "./constants"; +import { Card } from "./Card"; + +// I can't find an actual type for the promise resolution callback, but this is what +// it should look like. +type PromiseResolve = (value?: unknown) => void; const spritesheet = await getSpriteSheet(); export abstract class Animation { protected ticker: Ticker; protected boundCallback: TickerCallback; + #resolve: PromiseResolve; - constructor(ticker: Ticker) { - console.log("this got called", ticker); - if (ticker === undefined) throw new Error("got nothing"); - this.ticker = ticker; - this.boundCallback = () => {}; + constructor(hand: Hand) { + if (hand.ticker === null) + throw new Error("the hand does not have a ticker"); + + this.ticker = hand.ticker; + this.#resolve = Promise.resolve; + this.boundCallback = () => this.#resolve(); } abstract onTick(ticker: Ticker): void; - getCallback() { - this.boundCallback = this.onTick.bind(this); - return this.boundCallback; + start() { + return new Promise((resolve) => { + this.#resolve = resolve; + this.boundCallback = this.onTick.bind(this); + this.ticker.add(this.boundCallback); + }); } done() { - console.log("removing", this.ticker); this.ticker.remove(this.boundCallback); + this.#resolve(); } } @@ -34,27 +45,28 @@ export class FlipCard extends Animation { cardWidth: number; status: CardStatus; flipped: boolean; + to: Card; - constructor(ticker: Ticker, card: Sprite, status: CardStatus) { - super(ticker); + constructor(hand: Hand, index: number) { + super(hand); - this.boundCallback = () => {}; - this.card = card; + const { sprite, ...status } = hand.getCard(index); + this.card = sprite; this.status = status; this.flipped = false; - this.cardWidth = card.width; + this.cardWidth = sprite.width; + this.to = this.status.isFaceDown ? this.status.face : "unknown"; + this.card.pivot.set(this.cardWidth / 2, 0); + this.card.x += this.cardWidth / 2; // moving the pivot will shift the card } onTick(ticker: Ticker) { - const to = this.status.isFaceDown ? this.status.face : "unknown"; this.status.isFaceDown = !this.status.isFaceDown; - this.card.pivot.set(this.cardWidth / 2, 0); - // the card has just fipped if (!this.flipped && this.card.width <= 10) { this.flipped = true; - this.card.texture = spritesheet.textures[to]; + this.card.texture = spritesheet.textures[this.to]; } // the other side of the card is how showing @@ -88,8 +100,8 @@ export class ToHorizontal extends Animation { #containerSizeDelta: [number, number]; // change in size of container #containerFinalPosition: [number, number]; // final x and y of container - constructor(ticker: Ticker, hand: Hand) { - super(ticker); + constructor(hand: Hand) { + super(hand); if (hand.size() < 2) throw new Error("cannot spread fewer than two cards"); diff --git a/client/src/Hand.ts b/client/src/Hand.ts index a3c798d..4acb2c0 100644 --- a/client/src/Hand.ts +++ b/client/src/Hand.ts @@ -1,14 +1,7 @@ -import { - Assets, - Container, - Sprite, - Spritesheet, - Ticker, - TickerCallback, -} from "pixi.js"; +import { Assets, Container, Sprite, Spritesheet, Ticker } from "pixi.js"; import { Card } from "./Card"; import { CARD_HOVER_DIST, CARD_WIDTH } from "./constants"; -import { Animation, FlipCard, ToHorizontal } from "./Animation"; +import { FlipCard, ToHorizontal } from "./Animation"; type Pivot = "left" | "right" | "center"; type Layout = "horizontal" | "ascending" | "stacked"; @@ -16,12 +9,10 @@ type Layout = "horizontal" | "ascending" | "stacked"; const spritesheet = await Assets.load("/public/assets/cards.json"); export type CardStatus = { face: Card; - isAnimating: boolean; isFaceDown: boolean; }; export class Hand { - #animations: Animation[]; #cardSprites: Sprite[]; #statuses: CardStatus[]; #container: Container; @@ -29,7 +20,7 @@ export class Hand { #layout: Layout; #maxWidth: number; #pivot: Pivot; - #ticker: Ticker | null; + ticker: Ticker | null; #gap: number; constructor(maxWidth: number, cards: Card[] = []) { @@ -37,7 +28,6 @@ export class Hand { throw new Error("hand cannot be narrower than a single card"); } - this.#animations = []; this.#container = new Container(); this.#hover = false; this.#maxWidth = maxWidth; @@ -49,7 +39,7 @@ export class Hand { isAnimating: false, isFaceDown: false, })); - this.#ticker = null; + this.ticker = null; if (cards.length > 0) { const sprites: Sprite[] = []; @@ -66,12 +56,12 @@ export class Hand { } add(card: Card, faceDown = false) { - const sprite = new Sprite(spritesheet.textures[card]); + const spriteName = faceDown ? "unknown" : card; + const sprite = new Sprite(spritesheet.textures[spriteName]); this.#cardSprites.push(sprite); this.#statuses.push({ face: card, - isAnimating: false, isFaceDown: faceDown, }); this.#container.addChild(sprite); @@ -81,42 +71,34 @@ export class Hand { } animationTicker(ticker: Ticker) { - this.#ticker = ticker; - - ticker.add((ticker) => { - if (this.#animations.length) { - const animation = this.#animations.shift()!; - ticker.add(animation.getCallback()); - } - }); + this.ticker = ticker; } setGap(gap: number) { this.#gap = gap; } - flip(cardIdx: number) { + async flip(cardIdx: number) { if (cardIdx >= this.#cardSprites.length) throw new Error(`card index out of range: ${cardIdx}`); - if (!this.#ticker) + if (!this.ticker) throw new Error("can't animate hand before passing it a ticker"); - const card = this.#cardSprites[cardIdx]; - const status = this.#statuses[cardIdx]; - const ticker = this.#ticker; - this.#animations.push(new FlipCard(ticker, card, status)); + const animation = new FlipCard(this, cardIdx); + await animation.start(); } size(): number { return this.#cardSprites.length; } - spread() { - if (!this.#ticker) + async spread() { + if (!this.ticker) throw new Error("can't animate hand before passing it a ticker"); - this.#animations.push(new ToHorizontal(this.#ticker, this)); + const animation = new ToHorizontal(this); + await animation.start(); } #setCardHover(sprite: Sprite) { @@ -141,6 +123,13 @@ export class Hand { return this.#cardSprites; } + getCard(index: number) { + if (this.#cardSprites.length <= index) + throw new Error(`card index ${index} out of bounds`); + + return { sprite: this.#cardSprites[index], ...this.#statuses[index] }; + } + getLayout() { return this.#layout; } diff --git a/client/src/main.ts b/client/src/main.ts index 0745892..c29c220 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -10,7 +10,7 @@ const playerCards: Card[] = [ "nineOfDiamonds", ]; -const dealerCards: Card[] = ["unknown", "aceOfHearts"]; +const dealerCards: Card[] = ["aceOfClubs", "aceOfHearts"]; (async () => { try { @@ -113,11 +113,12 @@ const dealerCards: Card[] = ["unknown", "aceOfHearts"]; dealerHand.setGap(CARD_WIDTH * 0.1); dealerHand.animationTicker(app.ticker); - dealerHand.spread(); playerHand.setGap(CARD_WIDTH * 0.1); playerHand.animationTicker(app.ticker); - playerHand.spread(); + + await dealerHand.spread(); + await dealerHand.flip(0); // let ms = 0; // let elapsed = 0; @@ -141,7 +142,10 @@ const dealerCards: Card[] = ["unknown", "aceOfHearts"]; function positionPlayer(app: Application, h: Hand) { h.setPivot("center"); - h.setPosition(app.screen.width / 2, CARD_HEIGHT * 3); + h.setPosition( + app.screen.width / 2, + CARD_HEIGHT * 2.15 + 4 + CARD_HEIGHT / 2.5 + CARD_WIDTH, + ); h.setCardLayout("ascending"); } @@ -180,6 +184,23 @@ function markTable(app: Application, g: Graphics) { app.screen.width - CARD_WIDTH, CARD_HEIGHT / 2.5, ); + + g.stroke({ + color: "#ffffff", + width: 8, + }); + + g.circle( + app.screen.width / 2, + CARD_HEIGHT * 2.15 + + 4 + + CARD_HEIGHT / 2.5 + + CARD_WIDTH + + CARD_HEIGHT + + CARD_WIDTH, + CARD_HEIGHT / 2, + ); + g.stroke({ color: "#ffffff", width: 8,