import { inject , injectable } from "inversify" ; import { DefaultDeckPartConfig } from "./DeckPartConfig" ; import type { Card } from "../card/Card" ; import type { Deck } from "./Deck" ; import { TYPES } from "../types" ; import { CardService } from "../card/CardService" ; import type { Format } from "../card/format/Format" ; import { insertAt , pullFirst } from "lightdash" ; import { sampleSize , shuffle } from "lodash" ; import { SortingService , SortingStrategy } from "../card/SortingService" ; import { CardTypeCategory } from "../card/type/CardTypeCategory" ; import { BanlistService } from "../card/banlist/BanlistService" ; import { DECK_PART_ARR , DeckPart } from "./DeckPart" ; @ injectable ( ) export class DeckService { readonly #cardService : CardService ; readonly #sortingService : SortingService ; readonly #banlistService : BanlistService ; constructor ( @ inject ( TYPES . CardService ) cardService , @ inject ( TYPES . SortingService ) sortingService , @ inject ( TYPES . BanlistService ) banlistService ) { this . #cardService = cardService ; this . #sortingService = sortingService ; this . #banlistService = banlistService ; } /**
     * Checks if a given card can be moved in the deck.
     * Similar to  {@link #canAdd} , with the difference that in this case
     * the check is done against a copy of the deck with the card removed in order to correctly test the card count.
     *
     *  param deck param card param oldDeckPart param newDeckPart param format return canMove ( deck , card , oldDeckPart , newDeckPart , format ) { const deckWithoutCard = this . #cloneDeck ( deck ) ; this . removeCard ( deckWithoutCard , card , oldDeckPart ) ; return this . canAdd ( deckWithoutCard , card , newDeckPart , format ) ; } param deck param card param deckPart param format return canAdd ( deck , card , deckPart , format ) { if ( ! card . type . deckParts . has ( deckPart ) ) { return false ; } const deckPartSize = deck . parts [ deckPart ] . length ; if ( deckPartSize + 1 > DefaultDeckPartConfig [ deckPart ] . max ) { return false ; } if ( card . type . category === CardTypeCategory . SKILL && this . getAllCards ( deck ) . some ( ( existingCard ) => existingCard . type . category === CardTypeCategory . SKILL ) ) { return false ; } const count = this . getAllCards ( deck ) . filter ( ( existingCard ) => this . #cardService . isTreatedAsSame ( existingCard , card ) ) . length ; const banState = this . #banlistService . getBanStateByFormat ( card , format ) ; return count < banState . count ; } param deck param card param format return findAvailableDeckPart ( deck , card , format ) { return ( DECK_PART_ARR . find ( ( currentDeckPart ) => this . canAdd ( deck , card , currentDeckPart , format ) ) ?? null ) ; } /**
     * Adds a card.
     * Make sure to use  {@link #canAdd} before.
     *
     *  param deck param card param deckPart param newIndex addCard ( deck , card , deckPart , newIndex ? ) { const current = deck . parts [ deckPart ] ; if ( newIndex != null ) { insertAt ( current , newIndex , card ) ; } else { current . push ( card ) ; } } param deck param card param deckPart param oldIndex removeCard ( deck , card , deckPart , oldIndex ? ) { const cards = deck . parts [ deckPart ] ; if ( oldIndex != null ) { if ( cards [ oldIndex ] !== card ) { throw new TypeError ( "The given card does not exist at this index." ) ; } cards . splice ( oldIndex , 1 ) ; } else { pullFirst ( cards , card ) ; } } param deck param card param deckPart param oldIndex param newIndex reorderCard ( deck , card , deckPart , oldIndex , newIndex ) { const cards = deck . parts [ deckPart ] ; if ( cards [ oldIndex ] !== card ) { throw new TypeError ( "The given card does not exist at this index." ) ; } cards . splice ( oldIndex , 1 ) ; insertAt ( cards , newIndex , card ) ; } param deck return sort ( deck ) { const deckClone = this . #cloneDeck ( deck ) ; for ( const deckPart of DECK_PART_ARR ) { deckClone . parts [ deckPart ] = this . #sortingService . sort ( deckClone . parts [ deckPart ] , { strategy : SortingStrategy . DEFAULT , } ) ; } return deckClone ; } param deck return shuffle ( deck ) { const deckClone = this . #cloneDeck ( deck ) ; for ( const deckPart of DECK_PART_ARR ) { deckClone . parts [ deckPart ] = shuffle ( deckClone . parts [ deckPart ] ) ; } return deckClone ; } param deck param goingFirst return getSimulatedStartingHand ( deck , goingFirst ) { return sampleSize ( deck . parts [ DeckPart . MAIN ] , goingFirst ? 5 : 6 ) ; } param deck return getAllCards ( deck ) { const result = [ ] ; for ( const deckPart of DECK_PART_ARR ) { result . push ( ... deck . parts [ deckPart ] ) ; } return result ; } return createEmptyDeck ( ) { return { name : null , parts : { [ DeckPart . MAIN ] : [ ] , [ DeckPart . EXTRA ] : [ ] , [ DeckPart . SIDE ] : [ ] , } , } ; } #cloneDeck ( deck ) { return { name : deck . name , parts : { [ DeckPart . MAIN ] : Array . from ( deck . parts [ DeckPart . MAIN ] ) , [ DeckPart . EXTRA ] : Array . from ( deck . parts [ DeckPart . EXTRA ] ) , [ DeckPart . SIDE ] : Array . from ( deck . parts [ DeckPart . SIDE ] ) , } , } ; } }