refactor animations to be promise based
This commit is contained in:
@ -2,30 +2,41 @@ import type { Container, Sprite, Ticker, TickerCallback } from "pixi.js";
|
|||||||
import { CardStatus, Hand } from "./Hand";
|
import { CardStatus, Hand } from "./Hand";
|
||||||
import { getSpriteSheet } from "./spritesheet";
|
import { getSpriteSheet } from "./spritesheet";
|
||||||
import { CARD_HEIGHT, CARD_WIDTH } from "./constants";
|
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();
|
const spritesheet = await getSpriteSheet();
|
||||||
|
|
||||||
export abstract class Animation {
|
export abstract class Animation {
|
||||||
protected ticker: Ticker;
|
protected ticker: Ticker;
|
||||||
protected boundCallback: TickerCallback<any>;
|
protected boundCallback: TickerCallback<any>;
|
||||||
|
#resolve: PromiseResolve;
|
||||||
|
|
||||||
constructor(ticker: Ticker) {
|
constructor(hand: Hand) {
|
||||||
console.log("this got called", ticker);
|
if (hand.ticker === null)
|
||||||
if (ticker === undefined) throw new Error("got nothing");
|
throw new Error("the hand does not have a ticker");
|
||||||
this.ticker = ticker;
|
|
||||||
this.boundCallback = () => {};
|
this.ticker = hand.ticker;
|
||||||
|
this.#resolve = Promise.resolve;
|
||||||
|
this.boundCallback = () => this.#resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract onTick(ticker: Ticker): void;
|
abstract onTick(ticker: Ticker): void;
|
||||||
|
|
||||||
getCallback() {
|
start() {
|
||||||
this.boundCallback = this.onTick.bind(this);
|
return new Promise((resolve) => {
|
||||||
return this.boundCallback;
|
this.#resolve = resolve;
|
||||||
|
this.boundCallback = this.onTick.bind(this);
|
||||||
|
this.ticker.add(this.boundCallback);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
done() {
|
done() {
|
||||||
console.log("removing", this.ticker);
|
|
||||||
this.ticker.remove(this.boundCallback);
|
this.ticker.remove(this.boundCallback);
|
||||||
|
this.#resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,27 +45,28 @@ export class FlipCard extends Animation {
|
|||||||
cardWidth: number;
|
cardWidth: number;
|
||||||
status: CardStatus;
|
status: CardStatus;
|
||||||
flipped: boolean;
|
flipped: boolean;
|
||||||
|
to: Card;
|
||||||
|
|
||||||
constructor(ticker: Ticker, card: Sprite, status: CardStatus) {
|
constructor(hand: Hand, index: number) {
|
||||||
super(ticker);
|
super(hand);
|
||||||
|
|
||||||
this.boundCallback = () => {};
|
const { sprite, ...status } = hand.getCard(index);
|
||||||
this.card = card;
|
this.card = sprite;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.flipped = false;
|
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) {
|
onTick(ticker: Ticker) {
|
||||||
const to = this.status.isFaceDown ? this.status.face : "unknown";
|
|
||||||
this.status.isFaceDown = !this.status.isFaceDown;
|
this.status.isFaceDown = !this.status.isFaceDown;
|
||||||
|
|
||||||
this.card.pivot.set(this.cardWidth / 2, 0);
|
|
||||||
|
|
||||||
// the card has just fipped
|
// the card has just fipped
|
||||||
if (!this.flipped && this.card.width <= 10) {
|
if (!this.flipped && this.card.width <= 10) {
|
||||||
this.flipped = true;
|
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
|
// 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
|
#containerSizeDelta: [number, number]; // change in size of container
|
||||||
#containerFinalPosition: [number, number]; // final x and y of container
|
#containerFinalPosition: [number, number]; // final x and y of container
|
||||||
|
|
||||||
constructor(ticker: Ticker, hand: Hand) {
|
constructor(hand: Hand) {
|
||||||
super(ticker);
|
super(hand);
|
||||||
|
|
||||||
if (hand.size() < 2) throw new Error("cannot spread fewer than two cards");
|
if (hand.size() < 2) throw new Error("cannot spread fewer than two cards");
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
import {
|
import { Assets, Container, Sprite, Spritesheet, Ticker } from "pixi.js";
|
||||||
Assets,
|
|
||||||
Container,
|
|
||||||
Sprite,
|
|
||||||
Spritesheet,
|
|
||||||
Ticker,
|
|
||||||
TickerCallback,
|
|
||||||
} from "pixi.js";
|
|
||||||
import { Card } from "./Card";
|
import { Card } from "./Card";
|
||||||
import { CARD_HOVER_DIST, CARD_WIDTH } from "./constants";
|
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 Pivot = "left" | "right" | "center";
|
||||||
type Layout = "horizontal" | "ascending" | "stacked";
|
type Layout = "horizontal" | "ascending" | "stacked";
|
||||||
@ -16,12 +9,10 @@ type Layout = "horizontal" | "ascending" | "stacked";
|
|||||||
const spritesheet = await Assets.load<Spritesheet>("/public/assets/cards.json");
|
const spritesheet = await Assets.load<Spritesheet>("/public/assets/cards.json");
|
||||||
export type CardStatus = {
|
export type CardStatus = {
|
||||||
face: Card;
|
face: Card;
|
||||||
isAnimating: boolean;
|
|
||||||
isFaceDown: boolean;
|
isFaceDown: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Hand {
|
export class Hand {
|
||||||
#animations: Animation[];
|
|
||||||
#cardSprites: Sprite[];
|
#cardSprites: Sprite[];
|
||||||
#statuses: CardStatus[];
|
#statuses: CardStatus[];
|
||||||
#container: Container;
|
#container: Container;
|
||||||
@ -29,7 +20,7 @@ export class Hand {
|
|||||||
#layout: Layout;
|
#layout: Layout;
|
||||||
#maxWidth: number;
|
#maxWidth: number;
|
||||||
#pivot: Pivot;
|
#pivot: Pivot;
|
||||||
#ticker: Ticker | null;
|
ticker: Ticker | null;
|
||||||
#gap: number;
|
#gap: number;
|
||||||
|
|
||||||
constructor(maxWidth: number, cards: Card[] = []) {
|
constructor(maxWidth: number, cards: Card[] = []) {
|
||||||
@ -37,7 +28,6 @@ export class Hand {
|
|||||||
throw new Error("hand cannot be narrower than a single card");
|
throw new Error("hand cannot be narrower than a single card");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#animations = [];
|
|
||||||
this.#container = new Container();
|
this.#container = new Container();
|
||||||
this.#hover = false;
|
this.#hover = false;
|
||||||
this.#maxWidth = maxWidth;
|
this.#maxWidth = maxWidth;
|
||||||
@ -49,7 +39,7 @@ export class Hand {
|
|||||||
isAnimating: false,
|
isAnimating: false,
|
||||||
isFaceDown: false,
|
isFaceDown: false,
|
||||||
}));
|
}));
|
||||||
this.#ticker = null;
|
this.ticker = null;
|
||||||
|
|
||||||
if (cards.length > 0) {
|
if (cards.length > 0) {
|
||||||
const sprites: Sprite[] = [];
|
const sprites: Sprite[] = [];
|
||||||
@ -66,12 +56,12 @@ export class Hand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add(card: Card, faceDown = false) {
|
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.#cardSprites.push(sprite);
|
||||||
this.#statuses.push({
|
this.#statuses.push({
|
||||||
face: card,
|
face: card,
|
||||||
isAnimating: false,
|
|
||||||
isFaceDown: faceDown,
|
isFaceDown: faceDown,
|
||||||
});
|
});
|
||||||
this.#container.addChild(sprite);
|
this.#container.addChild(sprite);
|
||||||
@ -81,42 +71,34 @@ export class Hand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
animationTicker(ticker: Ticker) {
|
animationTicker(ticker: Ticker) {
|
||||||
this.#ticker = ticker;
|
this.ticker = ticker;
|
||||||
|
|
||||||
ticker.add((ticker) => {
|
|
||||||
if (this.#animations.length) {
|
|
||||||
const animation = this.#animations.shift()!;
|
|
||||||
ticker.add(animation.getCallback());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setGap(gap: number) {
|
setGap(gap: number) {
|
||||||
this.#gap = gap;
|
this.#gap = gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
flip(cardIdx: number) {
|
async flip(cardIdx: number) {
|
||||||
if (cardIdx >= this.#cardSprites.length)
|
if (cardIdx >= this.#cardSprites.length)
|
||||||
throw new Error(`card index out of range: ${cardIdx}`);
|
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");
|
throw new Error("can't animate hand before passing it a ticker");
|
||||||
|
|
||||||
const card = this.#cardSprites[cardIdx];
|
const animation = new FlipCard(this, cardIdx);
|
||||||
const status = this.#statuses[cardIdx];
|
await animation.start();
|
||||||
const ticker = this.#ticker;
|
|
||||||
this.#animations.push(new FlipCard(ticker, card, status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size(): number {
|
size(): number {
|
||||||
return this.#cardSprites.length;
|
return this.#cardSprites.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
spread() {
|
async spread() {
|
||||||
if (!this.#ticker)
|
if (!this.ticker)
|
||||||
throw new Error("can't animate hand before passing it a 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) {
|
#setCardHover(sprite: Sprite) {
|
||||||
@ -141,6 +123,13 @@ export class Hand {
|
|||||||
return this.#cardSprites;
|
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() {
|
getLayout() {
|
||||||
return this.#layout;
|
return this.#layout;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const playerCards: Card[] = [
|
|||||||
"nineOfDiamonds",
|
"nineOfDiamonds",
|
||||||
];
|
];
|
||||||
|
|
||||||
const dealerCards: Card[] = ["unknown", "aceOfHearts"];
|
const dealerCards: Card[] = ["aceOfClubs", "aceOfHearts"];
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@ -113,11 +113,12 @@ const dealerCards: Card[] = ["unknown", "aceOfHearts"];
|
|||||||
|
|
||||||
dealerHand.setGap(CARD_WIDTH * 0.1);
|
dealerHand.setGap(CARD_WIDTH * 0.1);
|
||||||
dealerHand.animationTicker(app.ticker);
|
dealerHand.animationTicker(app.ticker);
|
||||||
dealerHand.spread();
|
|
||||||
|
|
||||||
playerHand.setGap(CARD_WIDTH * 0.1);
|
playerHand.setGap(CARD_WIDTH * 0.1);
|
||||||
playerHand.animationTicker(app.ticker);
|
playerHand.animationTicker(app.ticker);
|
||||||
playerHand.spread();
|
|
||||||
|
await dealerHand.spread();
|
||||||
|
await dealerHand.flip(0);
|
||||||
|
|
||||||
// let ms = 0;
|
// let ms = 0;
|
||||||
// let elapsed = 0;
|
// let elapsed = 0;
|
||||||
@ -141,7 +142,10 @@ const dealerCards: Card[] = ["unknown", "aceOfHearts"];
|
|||||||
|
|
||||||
function positionPlayer(app: Application, h: Hand) {
|
function positionPlayer(app: Application, h: Hand) {
|
||||||
h.setPivot("center");
|
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");
|
h.setCardLayout("ascending");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +184,23 @@ function markTable(app: Application, g: Graphics) {
|
|||||||
app.screen.width - CARD_WIDTH,
|
app.screen.width - CARD_WIDTH,
|
||||||
CARD_HEIGHT / 2.5,
|
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({
|
g.stroke({
|
||||||
color: "#ffffff",
|
color: "#ffffff",
|
||||||
width: 8,
|
width: 8,
|
||||||
|
|||||||
Reference in New Issue
Block a user