diff --git a/package.json b/package.json index da9fefb4..9f390a98 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build-ts": "tsc && npm pack", "test0": "mocha -r ts-node/register test/games/rincala.test.ts", "test": "mocha -r ts-node/register test/**/*.test.ts", + "testc": "mocha -r ts-node/register test/common/*.test.ts", "lint": "npx eslint .", "dist-dev": "rimraf dist && webpack", "dist-prod": "rimraf dist && webpack --mode=production", diff --git a/src/common/decktet/Card.ts b/src/common/decktet/Card.ts index 1066d1a4..f233e679 100644 --- a/src/common/decktet/Card.ts +++ b/src/common/decktet/Card.ts @@ -8,6 +8,7 @@ type Params = { personality?: boolean; event?: boolean; location?: boolean; + deck?: number; }; export const cardSortAsc = (a: Card, b: Card): number => { @@ -51,6 +52,7 @@ export class Card { private readonly _personality: boolean = false; private readonly _event: boolean = false; private readonly _location: boolean = false; + private readonly _deck: number = 0; private _plain: string|undefined; constructor(params: Params) { @@ -66,6 +68,9 @@ export class Card { if (params.location !== undefined) { this._location = params.location; } + if (params.deck !== undefined) { + this._deck = params.deck; + } } public get name(): string { @@ -86,9 +91,12 @@ export class Card { public get location(): boolean { return this._location; } + public get deck(): number { + return this._deck; + } public get uid(): string { - return [this.rank.uid, ...this.suits.map(s => s.uid)].join(""); + return [this.rank.uid, ...this.suits.map(s => s.uid), (this._deck > 0 ? this._deck : "")].join(""); } public setPlain(plain: string|undefined): Card { @@ -145,7 +153,8 @@ export class Card { nudge: { dx: 250, dy: -250, - } + }, + orientation: "vertical", }); } const nudges: [number,number][] = [[-250, -250], [-250, 250], [250, 250]]; @@ -158,31 +167,49 @@ export class Card { nudge: { dx: nudge[0], dy: nudge[1], - } + }, + orientation: "vertical", }); } return glyph; } public clone(): Card { - return new Card({name: this.name, rank: this.rank, suits: [...this.suits.map(s => s.clone())], personality: this.personality, location: this.location, event: this.event}); + return new Card({name: this.name, rank: this.rank, suits: [...this.suits.map(s => s.clone())], personality: this.personality, location: this.location, event: this.event, deck: this.deck}); + } + + public cloneForDeck(deck: number): Card { + return new Card({name: this.name, rank: this.rank, suits: [...this.suits.map(s => s.clone())], personality: this.personality, location: this.location, event: this.event, deck: deck}); } public static deserialize(card: Card|string, allowCustom = false): Card|undefined { + let strDeck: number = 0; if (typeof card === "string") { - const found = [...cardsBasic, ...cardsExtended].find(c => c.uid === card.toUpperCase()); + let found: Card|undefined; + if (card.length > 1 && card.charAt(card.length - 1).match(/\d/)) { + strDeck = parseInt(card.charAt(card.length - 1),10); + found = [...cardsBasic, ...cardsExtended].find(c => c.uid === card.toUpperCase().substring(0,card.length - 1)); + if (found) + return new Card({name: found._name, rank: Component.deserialize(found._rank)!, suits: [...found._suits.map(s => Component.deserialize(s)!)], personality: found._personality, location: found._location, event: found.event, deck: strDeck}); + } else { + found = [...cardsBasic, ...cardsExtended].find(c => c.uid === card.toUpperCase()); + } if (allowCustom && found === undefined) { - const [strRank, ...strSuits] = card.split(""); + let [strRank, ...strSuits] = card.split(""); + if (card.length > 1 && card.charAt(card.length - 1).match(/\d/)) { + strDeck = parseInt(card.charAt(card.length - 1),10); + [strRank, ...strSuits] = card.substring(0,card.length - 1).split(""); + } const rank = Component.deserialize(strRank); const suits = strSuits.map(s => Component.deserialize(s)); if (rank === undefined || suits.includes(undefined)) { return undefined; } - return new Card({name: "_custom", rank, suits: (suits as Component[]).sort((a,b) => a.seq - b.seq)}); + return new Card({name: "_custom", rank, suits: (suits as Component[]).sort((a,b) => a.seq - b.seq), deck: strDeck}); } return found; } - return new Card({name: card._name, rank: Component.deserialize(card._rank)!, suits: [...card._suits.map(s => Component.deserialize(s)!)], personality: card._personality, location: card._location, event: card._event}); + return new Card({name: card._name, rank: Component.deserialize(card._rank)!, suits: [...card._suits.map(s => Component.deserialize(s)!)], personality: card._personality, location: card._location, event: card._event, deck: strDeck}); } } diff --git a/src/common/decktet/Deck.ts b/src/common/decktet/Deck.ts index 081ec8cd..6161f934 100644 --- a/src/common/decktet/Deck.ts +++ b/src/common/decktet/Deck.ts @@ -3,9 +3,23 @@ import { Card, cardsBasic, cardsExtended } from "./Card"; export class Deck { private _cards: Card[]; + private _decks: number | undefined; - constructor(cards: Card[]) { - this._cards = cards.map(c => new Card(c)); + constructor(cards: Card[], decks?: number) { + if (decks === undefined) { + this._cards = cards.map(c => new Card(c)); + } else if (decks < 1 || decks > 9) { + throw new Error("Only one to nine decktet decks are supported."); + } else { + const newCards: Card[] = []; + for (let d=1; d <= decks; d++) { + cards.forEach(c => { + newCards.push(c.cloneForDeck(d)); + }); + } + this._cards = newCards.map(c => new Card(c)); + this._decks = decks; + } } public get cards(): Card[] { @@ -31,6 +45,20 @@ export class Deck { return this; } + public addAll(uid: string): Deck { + if (this._decks === undefined) { + throw new Error("Use add() to add cards to a single deck."); + } + let card = uid; + if (card.length > 1 && card.charAt(card.length - 1).match(/\d/)) { + card = card.substring(0,card.length - 2); + } + for (let d=1; d <= this._decks; d++) { + this.add(card + d); + } + return this; + } + public remove(uid: string): Deck { const idx = this._cards.findIndex(c => c.uid === uid); if (idx < 0) { @@ -40,6 +68,20 @@ export class Deck { return this; } + public removeAll(uid: string): Deck { + if (this._decks === undefined) { + throw new Error("Use remove() to remove cards from a single deck."); + } + let card = uid; + if (card.length > 1 && card.charAt(card.length - 1).match(/\d/)) { + card = card.substring(0,card.length - 2); + } + for (let d=1; d <= this._decks; d++) { + this.remove(card + d); + } + return this; + } + public draw(count = 1): Card[] { const drawn: Card[] = []; const limit = Math.min(count, this._cards.length); @@ -61,4 +103,4 @@ export class Deck { public static deserialize(deck: Deck): Deck { return new Deck(deck.cards); } -} \ No newline at end of file +} diff --git a/test/common/decktet.test.ts b/test/common/decktet.test.ts new file mode 100644 index 00000000..7aeb7d0b --- /dev/null +++ b/test/common/decktet.test.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ + +import "mocha"; +import { expect } from "chai"; +//import { Card, Deck, cardSortAsc, cardSortDesc, cardsBasic, cardsExtended } from "../../src/common/decktet"; +import { Deck, cardsBasic, cardsExtended } from "../../src/common/decktet"; + +describe("Decktets", () => { + it ("Still makes a single decktet", () => { + const mydeck = new Deck([...cardsBasic, ...cardsExtended]); + expect(mydeck.size).eq(45); + const card = mydeck.draw(1).map(c => c.uid)[0]; + expect(card).eq("1M"); + expect(mydeck.size).eq(44); + + mydeck.shuffle(); + expect(mydeck.size).eq(44); + expect(mydeck.remove("1K")).to.have.deep.property("size", 43); + }); + + it ("Now makes a double decktet", () => { + const mydeck = new Deck([...cardsBasic, ...cardsExtended],2); + expect(mydeck.size).eq(90); + expect(mydeck.draw(1).map(c => c.uid)[0]).eq("1M1"); + expect(mydeck.size).eq(89); + expect(mydeck.draw(1).map(c => c.plain)[0]).eq("Ace Suns"); + expect(mydeck.size).eq(88); + + mydeck.shuffle(); + expect(mydeck.size).eq(88); + expect(mydeck.remove("1K1")).to.have.deep.property("size", 87); + expect(mydeck.remove("1K2")).to.have.deep.property("size", 86); + expect(mydeck.removeAll("1Y")).to.have.deep.property("size", 84); + + }); + + it ("Doesn't blow up when the deck is out", () => { + const mydeck = new Deck([...cardsBasic, ...cardsExtended],3); + expect(mydeck.size).eq(135); + mydeck.draw(134); + expect(mydeck.size).eq(1); + expect(mydeck.draw(1).map(c => c.plain)[0]).eq("Court Suns Waves Wyrms"); + expect(mydeck.size).eq(0); + const [card] = mydeck.draw(1); + expect(card).eq(undefined); + + }); +});