UNPKG

deckbuilder

Version:

A deck building and management tool any card based games in the browser or Node.

359 lines (246 loc) 9.46 kB
'use strict' import Card from './interfaces/Card'; import Deal from './interfaces/Deal'; import Options from './options/Options'; import { deepCopy } from './utils/utils'; import { fisherYates, strip } from './utils/shuffle'; /** * Deckbuilder helps you create and manage car decks for any type of card game. */ export default class Deckbuilder { /** * A reference to the options for this instance. * * @property {Options} */ options: Options; /** * A reference to the current deck of cards. * * @property {Array<Card>} */ deck: Array<Card> = []; /** * The total number of cards in this deck. * * @property {number} */ count: number = 0; /** * A reference to the cards that are currently drawn out and not a part of the deck. * * @property {Array<Card>} */ drawn: Array<Card> = []; /** * A reference to the cards that are currently discarded and not part of the deck. * * @property {Array<Card>} */ discarded: Array<Card> = []; /** * The shuffle methods available for use. * * @property {any} */ SHUFFLE_METHODS: any = { STRIP: strip, FISHERYATES: fisherYates, }; /** * @param {Object} [options] * @param {number} [options.maxCardCount=Infinity] The maximum number of cards that can be in this deck. */ constructor(options?: Object) { this.options = new Options(options); } /** * Adds one or more cards to the deck. * * A card must be an object and it can have any properties that suit your needs but at the very least it needs an id that Deckbuilder * can use to keep track of the card. * * @param {Card | Array<Card>} cards One or more cards to add to the deck. * @param {boolean} [shuffleIn=false] If this is set to true, the card will be inserted into a random position in the deck instead of being added to the bottom. * * @returns {Deckbuilder} Returns this for chaining. */ add(cards: (Card | Array<Card>), shuffleIn: boolean = false): Deckbuilder { if (!Array.isArray(cards)) cards = [cards]; for (const card of cards) { if (!card.id) { console.warn('Card is missing the id property, skipping...'); continue; } if (this.count === this.options.maxCardCount) { console.warn('The maximum amount of cards for this deck has been reached'); return this; } if (shuffleIn) { const randomPosition: number = Math.floor(Math.random() * this.deck.length); this.deck.splice(randomPosition, 0, card); } else this.deck.push(card); this.count++; } return this; } /** * Remove one or more cards from the deck. * * The card or cards to be removed need to be referenced by their id property. * * @param {string|Array<string>} ids The id or ids of the cards to remove from the deck. * * @returns {Deckbuilder} Returns this for chaining. */ remove(ids: (string | Array<string>)): Deckbuilder { if (!Array.isArray(ids)) ids = [ids]; this.deck = this.deck.filter(card => !ids.includes(card.id)); return this; } /** * Edits a card's properties from the deck. * * To define a new property, just set a new key and value. To edit a property, define the key to edit and the new value to set for that key. To * remove a property, define the key to delete. * * @param {string} id The id of the card to edit. * @param {string} key The name of the property to add, edit, or remove. * @param {any} [value=null] The value to add to the key. If removing a key just leave this blank. * * @returns {Deckbuilder} Returns this for chaining. */ edit(id: string, key: string, value: any = null): Deckbuilder { for (const card of this.deck) { if (card.id === id) { value ? card[key] = value : delete card[key]; break; } } return this; } /** * Shuffles the deck using one of the available shuffle methods any number of times. * * @param {number} [times=1] The number of times to shuffle the deck. * @param {string} [method='fisherYates'] The method to use for shuffling the deck. * * @returns {Deckbuilder} Returns this for chaining. */ shuffle(times: number = 1, method: Function = this.SHUFFLE_METHODS.FISHERYATES): Deckbuilder { this.deck = method(this.deck, times); return this; } /** * Deals a specified number of cards from the deck to a specified number of players. * * The cards can be dealt one at a time or all at once for each player. * * @param {number} players The number of players to deal cards to. * @param {number} cards The amount of cards to deal to each player. * @param {boolean} [allAtOnce=false] If set to true, all the cards will be dealt to each player instead of just one at a time. * * @returns {Deal} Returns a Deal object containing the players and the cards they have been dealt. */ deal(players: number, cards: number, allAtOnce: boolean = false): Deal { const deal: Deal = {}; let _deck: Array<Card> = deepCopy(this.deck); const totalCards: number = players * cards; for (let i: number = 0, player: number = 0; i < totalCards; ++i, ++player) { if (player >= players) player = 0; const currentPlayer: number = player + 1; if (!deal[currentPlayer]) deal[currentPlayer] = []; deal[currentPlayer].push(this.deck[i]); this.drawn.push(this.deck[i]); _deck.filter(card => { if (card.id == this.deck[i].id) _deck.splice(_deck.indexOf(card), 1); }); } this.deck = _deck; return deal; } /** * Draw any number of cards from the top of the deck. * * @param {number} cards The number of cards to draw. * * @returns {Array<Card>} The cards that have been drawn. */ draw(cards: number): Array<Card> { const drawn: Array<Card> = []; const _deck: Array<Card> = deepCopy(this.deck); for (let i: number = 0; i < cards; ++i) { this.drawn.push(this.deck[i]); _deck.filter(card => { if (card.id == this.deck[i].id) _deck.splice(_deck.indexOf(card), 1); }); } this.deck = _deck; return drawn; } /** * Pick one or more cards from the deck by their id/ids. * * @param {string|Array<string>} ids The id or ids of the cards to pick. * * @returns {Array<Card>} Returns the picked cards. */ pick(ids: (string | Array<string>)): Array<Card> { if (!Array.isArray(ids)) ids = [ids]; return this.deck.filter(card => ids.includes(card.id)); } /** * Discards any number of cards from the draw pile and optionally from the deck. * * @param {string|Array<string>} ids The id or ids of the cards to discard. * @param {boolean} [checkDeck=false] If set to true, it will also check the deck for cards it can discard and not just the drawn pile. * * @returns {Deckbuilder} Returns this for chaining. */ discard(ids: (string | Array<string>), checkDeck: boolean = false): Deckbuilder { if (!Array.isArray(ids)) ids = [ids]; this.discarded = this.drawn.filter(card => ids.includes(card.id)); this.drawn = this.drawn.filter(card => !ids.includes(card.id)); if (checkDeck) { this.discarded = this.discarded.concat(this.deck.filter(card => ids.includes(card.id))); this.deck = this.deck.filter(card => !ids.includes(card.id)); } return this; } /** * Returns cards from the drawn pile back to the deck. * * @param {string|Array<string>} [cards] By default all cards from the draw pile will be returned, this option can be used to return only certain cards from the drawn pile. * * @returns {Deckbuilder} Returns this for chaining. */ returnDrawn(cards: (any | Array<string>) = []): Deckbuilder { if (!Array.isArray(cards)) cards = [cards]; if (cards.length === 0) { this.deck = this.deck.concat(this.drawn); this.drawn = []; return this; } const cardsToReturn = this.drawn.filter(card => cards.includes(card.id)); this.deck = this.deck.concat(cardsToReturn); this.drawn = this.drawn.filter(card => !cards.includes(card.id)); return this; } /** * Returns cards from the discarded pile back to the deck. * * @param {string|Array<string>} [cards] By default all cards from the discarded pile will be returned, this option can be used to return only certain cards from the discarded pile. * * @returns {Deckbuilder} Returns this for chaining. */ returnDiscarded(cards: (any | Array<string>) = []): Deckbuilder { if (!Array.isArray(cards)) cards = [cards]; if (cards.length === 0) { this.deck = this.deck.concat(this.discarded); this.discarded = []; return this; } const cardsToReturn = this.discarded.filter(card => cards.includes(card.id)); this.deck = this.deck.concat(cardsToReturn); this.discarded = this.discarded.filter(card => !cards.includes(card.id)); return this; } }