UNPKG

card-games-utils

Version:
634 lines (633 loc) 25.4 kB
"use strict"; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rummy = void 0; var ErrorEnum_1 = require("../constants/ErrorEnum"); var StandardDeckEnum_1 = require("../constants/StandardDeckEnum"); var StandardDeck_1 = require("../data/StandardDeck"); var StandardCardHelper_1 = require("../helpers/StandardCardHelper"); /** * Class to handle operations of Rummy game * @class */ var Rummy = /** @class */ (function () { /** * Constructs a new instance of Rummy. * @param {RummyConfig} config the config for the rules of game. */ function Rummy(config) { this.config = config; } /** * @method * @static * make the config for Rummy game * * @param {boolean} [isSetRequired = true] - is SET of card-group required for meld to be ready to declare? * @param {boolean} [isSequenceRequired = true] - is SEQUENCE of card-group required for meld to be ready to declare? * @param {boolean} [isPureSequenceRequired = true] - is PURE-SEQUENCE of card-group required for meld to be ready to declare? * @param {boolean} [isJokerAllowed = true] - is joker card allowed in meld or not * @param {boolean} [isWildAllowed = true] - is wild card allowed in meld or not * @param {boolean} [isDuplicateCardsAllowed = true] - is duplicate cards allowed in 1 meld(wild cards will be ignored)? * @param {number} [numCardsPerMeld = 13] - how many cards should there be in 1 meld * @param {number} [numFlexCardPerMeld = 3] - how many flexible cards(JOKER + WILD) should there be in 1 meld * @param {number} [numFlexCardPerGroup = 1] - how many flexible cards(JOKER + WILD) should there be in 1 group(SET, SEQUENCE, PURE-SEQUENCE) * @returns {RummyConfig} - instance of RummyConfig interface based on given data */ Rummy.makeRummyConfig = function (isSetRequired, isSequenceRequired, isPureSequenceRequired, isJokerAllowed, isWildAllowed, numCardsPerMeld) { if (isSetRequired === void 0) { isSetRequired = true; } if (isSequenceRequired === void 0) { isSequenceRequired = true; } if (isPureSequenceRequired === void 0) { isPureSequenceRequired = true; } if (isJokerAllowed === void 0) { isJokerAllowed = true; } if (isWildAllowed === void 0) { isWildAllowed = true; } if (numCardsPerMeld === void 0) { numCardsPerMeld = 13; } if (!isSetRequired && !isSequenceRequired && !isPureSequenceRequired) { throw new Error(ErrorEnum_1.ErrorEnum.AT_LEAST_ONE_RULE_IS_REQUIRED); } var config = { isSetRequired: isSetRequired, isSequenceRequired: isSequenceRequired, isPureSequenceRequired: isPureSequenceRequired, isJokerAllowed: isJokerAllowed, isWildAllowed: isWildAllowed, numCardsPerMeld: numCardsPerMeld, }; return config; }; /** * @method * verify the given meld with the config of the game. * * * @param {meld} meld meld to verify * @returns {boolean} return true if meld is correct * @throws {Error} throws error if there is something wrong with meld */ Rummy.prototype.verifyMeld = function (meld) { if (this.config.numCardsPerMeld !== meld.cards.length) { throw new Error("numbers of cards in 1 meld must be ".concat(this.config.numCardsPerMeld)); } if (!this.config.isJokerAllowed) { if (StandardCardHelper_1.StandardCardHelper.isInDeck(meld.cards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { throw new Error(ErrorEnum_1.ErrorEnum.JOKER_NOT_ALLOWED); } } return true; }; /** * @method * convert given array cards and groups into Meld interface * * @param {StandardCard[]} cards - array of cards * @param {number[][]} groups - mutlidimenal array of numbers that make different groups in meld * @returns {Meld} - returns the generated meld */ Rummy.prototype.makeMeld = function (cards, groups) { var meld = { cards: cards, groups: groups !== null && groups !== void 0 ? groups : [], }; this.verifyMeld(meld); return meld; }; /** * @method * get the groups of card from the given Meld * * @param {Meld} meld - the meld who's groups should be returned * @throws {Error} - if groups has any index that doesn't have card in it. */ Rummy.prototype.getCardGroup = function (meld) { this.verifyMeld(meld); var cardGroups = new Array(meld.groups.length).fill(null).map(function () { return []; }); meld.groups.forEach(function (group, index) { group.forEach(function (cardIndex) { if (meld.cards[cardIndex] === undefined) { throw new Error("No card exist on ".concat(cardIndex, " index")); } cardGroups[index].push(meld.cards[cardIndex]); }); }); return cardGroups; }; /** * @method * sort the cards in meld and divide card into 4 groups with game suites, and 1 for joker(if any) * * @param {Meld} meld - the meld who's groups should be returned */ Rummy.prototype.sortMeld = function (meld) { this.verifyMeld(meld); meld.groups = new Array(5).fill(null).map(function () { return []; }); meld.cards.forEach(function (card, index) { switch (card.suite) { case StandardDeckEnum_1.StandardCardSuite.CLUBS: meld.groups[0].push(index); break; case StandardDeckEnum_1.StandardCardSuite.DIAMONDS: meld.groups[1].push(index); break; case StandardDeckEnum_1.StandardCardSuite.HEARTS: meld.groups[2].push(index); break; case StandardDeckEnum_1.StandardCardSuite.SPADES: meld.groups[3].push(index); break; case StandardDeckEnum_1.StandardCardSuite.JOKER: meld.groups[4].push(index); break; default: break; } }); meld.groups.forEach(function (group) { group.sort(function (currentCardIndex, nextCardIndex) { return meld.cards[currentCardIndex].number - meld.cards[nextCardIndex].number; }); }); return meld; }; /** * @method * @static * calculate the points for given array cards(joker wont be counted) * * @param {StandardCard[]} cards - the cards which's points are going to be calculated * @param {keyof typeof StandardCardName} [cardNameToIgnore] - the card that should be ignored(wildcard) * @returns {number} - total points for the given cards */ Rummy.calculatePoints = function (cards, cardNameToIgnore) { var points = 0; cards.forEach(function (card) { if (card.number === -1) { return; } if (cardNameToIgnore !== undefined && card.name === cardNameToIgnore) { return; } if (card.number >= 10) { points = points + 10; } else { points = points + card.number; } }); return points; }; /** * @method * decides if the given meld is ready to be declared * * @param {Meld} meld - the meld who is about to be declared * @param {keyof typeof StandardCardName} [wildCardName] - optional wild card. * @returns {RummyDeclareCheck} - returns an instance of RummyDeclareCheck with proper value. */ Rummy.prototype.isReadyToDeclare = function (meld, wildCardName) { var _this = this; try { this.verifyMeld(meld); } catch (error) { if (error instanceof Error) { return { isValid: false, error: error.message, points: Rummy.calculatePoints(meld.cards, wildCardName), }; } else { return { isValid: false, error: ErrorEnum_1.ErrorEnum.INVALID_DATA, points: Rummy.calculatePoints(meld.cards, wildCardName), }; } } if (!this.config.isJokerAllowed) { if (StandardCardHelper_1.StandardCardHelper.isInDeck(meld.cards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.JOKER_NOT_ALLOWED, points: Rummy.calculatePoints(meld.cards, wildCardName), }; } } if (!this.config.isWildAllowed) { if (wildCardName !== undefined) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.WILD_NOT_ALLOWED, points: Rummy.calculatePoints(meld.cards, wildCardName), }; } } var cardGroups = this.getCardGroup(meld); var usedIndexes = []; var points = 0; var isReadyToDeclare = true; var errorMessage; if (this.config.isSetRequired && isReadyToDeclare) { var isInSet_1 = false; cardGroups.forEach(function (cards, index) { if (usedIndexes.includes(index)) { return; } var temp; if (wildCardName === undefined) { temp = _this.isInSet(cards); } else { temp = _this.isInSet(cards, wildCardName); } if (temp.isValid) { usedIndexes.push(index); isInSet_1 = true; } }); if (!isInSet_1 && isReadyToDeclare) { isReadyToDeclare = false; errorMessage = ErrorEnum_1.ErrorEnum.SET_REQUIRED; } } if (this.config.isPureSequenceRequired && isReadyToDeclare) { var isInPureSequence_1 = false; cardGroups.forEach(function (cards, index) { if (usedIndexes.includes(index)) { return; } if (!isInPureSequence_1) { if (_this.isInPureSequence(cards).isValid) { usedIndexes.push(index); isInPureSequence_1 = true; } } }); if (!isInPureSequence_1 && isReadyToDeclare) { isReadyToDeclare = false; errorMessage = ErrorEnum_1.ErrorEnum.PURE_SEQUENCE_REQUIRED; } } if (this.config.isSequenceRequired && isReadyToDeclare) { var isInSequence_1 = false; cardGroups.forEach(function (cards, index) { if (usedIndexes.includes(index)) { return; } var temp; if (wildCardName === undefined) { temp = _this.isInSequence(cards); } else { temp = _this.isInSequence(cards, wildCardName); } if (temp.isValid) { usedIndexes.push(index); isInSequence_1 = true; } }); if (!isInSequence_1 && isReadyToDeclare) { isReadyToDeclare = false; errorMessage = ErrorEnum_1.ErrorEnum.SEQUENCE_REQUIRED; } } if (this.config.isPureSequenceRequired && isReadyToDeclare) { cardGroups.forEach(function (cards, index) { if (usedIndexes.includes(index)) { return; } if (_this.isInPureSequence(cards).isValid) { usedIndexes.push(index); } }); } cardGroups.forEach(function (cards, index) { if (usedIndexes.includes(index)) { return; } points = points + Rummy.calculatePoints(cards, wildCardName); }); if (!isReadyToDeclare) { return { isValid: false, error: errorMessage, points: points, }; } else { return { isValid: true, points: points, }; } }; /** * @method * decides if the given array of cards are in sequence or not * * @param {StandardCard[]} cards - array of cards that needs be checked * @param {keyof typeof StandardCardName} [wildCardName] - optional wild card. * @returns {RummyDeclareCheck} - returns an instance of RummyDeclareCheck with proper value. */ Rummy.prototype.isInSequence = function (cards, wildCardName) { if (cards.length < 3) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.AT_LEAST_THREE_CARDS_NEEDED_FOR_SEQUENCE, }; } if (!this.config.isJokerAllowed) { if (StandardCardHelper_1.StandardCardHelper.isInDeck(cards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.JOKER_NOT_ALLOWED, }; } } if (!this.config.isWildAllowed) { if (wildCardName !== undefined) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.WILD_NOT_ALLOWED, }; } } var tempCards = __spreadArray([], cards, true); var sequenceNumber; var suite; var isInSequence = true; var isAllCardCheck = false; var jokerCardCount = 0; var wildCardCount = 0; while (StandardCardHelper_1.StandardCardHelper.isInDeck(tempCards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { tempCards.splice(StandardCardHelper_1.StandardCardHelper.isInDeck(tempCards, StandardDeckEnum_1.StandardCardName.JOKER), 1); jokerCardCount++; } if (wildCardName !== undefined) { while (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(tempCards, StandardCardHelper_1.StandardCardHelper.makeStandardCard(wildCardName).number) !== -1) { tempCards.splice(StandardCardHelper_1.StandardCardHelper.isNumberInDeck(tempCards, StandardCardHelper_1.StandardCardHelper.makeStandardCard(wildCardName).number), 1); wildCardCount++; } } var shouldSkipOnce = false; if (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(cards, 1) !== -1) { if (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(cards, 13) !== -1) { shouldSkipOnce = true; } } tempCards = StandardCardHelper_1.StandardCardHelper.sortCards(tempCards); if (tempCards.length < 2) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.AT_LEAST_TWO_NORMAL_CARDS_NEEDED_FOR_SEQUENCE, }; } if (tempCards.length === 2) { if (tempCards[0].suite === tempCards[1].suite) { if (tempCards[0].number + 1 === tempCards[1].number) { return { isValid: true, points: 0, }; } else { if (tempCards[1].number - tempCards[0].number - 1 <= jokerCardCount + wildCardCount) { return { isValid: true, points: 0, }; } else { if (tempCards[1].number !== tempCards[0].number) { if ((tempCards[1].number === 1 && tempCards[0].number === 13) || (tempCards[0].number === 1 && tempCards[1].number === 13)) { return { isValid: true, points: 0, }; } } return { isValid: false, error: ErrorEnum_1.ErrorEnum.NOT_VALID_SEQUENCE, }; } } } else { return { isValid: false, error: ErrorEnum_1.ErrorEnum.NOT_VALID_SEQUENCE, }; } } var index = 0; var shouldSkipThisIndex = false; while (isInSequence && !isAllCardCheck) { if (sequenceNumber === undefined || suite === undefined) { sequenceNumber = tempCards[index].number; suite = tempCards[index].suite; } else { shouldSkipThisIndex = false; if (tempCards[index].number === sequenceNumber + 1 && tempCards[index].suite === suite) { sequenceNumber = tempCards[index].number; } else if (Math.abs(sequenceNumber - tempCards[index].number) > 1 && shouldSkipOnce) { shouldSkipOnce = false; sequenceNumber = tempCards[index].number; } else if (wildCardName !== undefined && wildCardCount > 0 && StandardDeck_1.StandardDeck.getNumber(wildCardName) === sequenceNumber + 1 && StandardDeck_1.StandardDeck.getSuite(wildCardName) === suite) { wildCardCount--; shouldSkipThisIndex = true; sequenceNumber++; } else if (jokerCardCount > 0) { jokerCardCount--; shouldSkipThisIndex = true; sequenceNumber++; } else if (wildCardCount > 0) { wildCardCount--; shouldSkipThisIndex = true; sequenceNumber++; } else { isInSequence = false; } } if (tempCards.length - 1 <= index) { isAllCardCheck = true; } if (!shouldSkipThisIndex) { index++; } } if (isInSequence) { return { isValid: true, points: 0, }; } else { return { isValid: false, error: ErrorEnum_1.ErrorEnum.NOT_VALID_SEQUENCE, }; } }; /** * @method * decides if the given array of cards are in sets or not * * @param {StandardCard[]} cards - array of cards that needs be checked * @param {keyof typeof StandardCardName} [wildCardName] - optional wild card. * @returns {RummyDeclareCheck} - returns an instance of RummyDeclareCheck with proper value. */ Rummy.prototype.isInSet = function (cards, wildCardName) { if (cards.length < 3) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.AT_LEAST_THREE_CARDS_NEEDED_FOR_SET, }; } if (cards.length > 4) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.MAX_FOUR_CARDS_ALLOWED_FOR_SET, }; } if (!this.config.isJokerAllowed) { if (StandardCardHelper_1.StandardCardHelper.isInDeck(cards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.JOKER_NOT_ALLOWED, }; } } if (!this.config.isWildAllowed) { if (wildCardName !== undefined) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.WILD_NOT_ALLOWED, }; } } if (StandardCardHelper_1.StandardCardHelper.hasSameNumber(cards) && !StandardCardHelper_1.StandardCardHelper.hasPairSuite(cards)) { return { isValid: true, points: 0, }; } var tempCards = __spreadArray([], cards, true); while (StandardCardHelper_1.StandardCardHelper.isInDeck(tempCards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { tempCards.splice(StandardCardHelper_1.StandardCardHelper.isInDeck(tempCards, StandardDeckEnum_1.StandardCardName.JOKER), 1); } if (wildCardName !== undefined) { while (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(tempCards, StandardCardHelper_1.StandardCardHelper.makeStandardCard(wildCardName).number) !== -1) { tempCards.splice(StandardCardHelper_1.StandardCardHelper.isNumberInDeck(tempCards, StandardCardHelper_1.StandardCardHelper.makeStandardCard(wildCardName).number), 1); } } if (tempCards.length <= 0) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.AT_LEAST_ONE_NORMAL_CARD_NEEDED_FOR_SET, }; } if (StandardCardHelper_1.StandardCardHelper.hasSameNumber(tempCards) && !StandardCardHelper_1.StandardCardHelper.hasPairSuite(tempCards)) { return { isValid: true, points: 0, }; } return { isValid: false, error: ErrorEnum_1.ErrorEnum.NOT_VALID_SET, }; }; /** * @method * decides if the given array of cards are in pure sequence or not * * @param {StandardCard[]} cards - array of cards that needs be checked * @returns {RummyDeclareCheck} - returns an instance of RummyDeclareCheck with proper value. */ Rummy.prototype.isInPureSequence = function (cards) { if (cards.length < 3) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.AT_LEAST_THREE_CARDS_NEEDED_FOR_PURE_SEQUENCE, }; } if (StandardCardHelper_1.StandardCardHelper.isInDeck(cards, StandardDeckEnum_1.StandardCardName.JOKER) !== -1) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.JOKER_NOT_ALLOWED_IN_PURE_SEQUENCE, }; } if (!StandardCardHelper_1.StandardCardHelper.hasSameSuite(cards)) { return { isValid: false, error: ErrorEnum_1.ErrorEnum.SAME_SUITE_NEEDED_FOR_PURE_SEQUENCE, }; } var tempCards = __spreadArray([], cards, true); var shouldSkipOnce = false; if (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(cards, 1) !== -1) { if (StandardCardHelper_1.StandardCardHelper.isNumberInDeck(cards, 13) !== -1) { shouldSkipOnce = true; } } tempCards = StandardCardHelper_1.StandardCardHelper.sortCards(tempCards); var isInSequence = true; var isAllCardCheck = false; var sequenceNumber; var index = 0; while (isInSequence && !isAllCardCheck) { if (sequenceNumber === undefined) { sequenceNumber = tempCards[index].number; } else { if (tempCards[index].number === sequenceNumber + 1) { sequenceNumber = tempCards[index].number; } else if (shouldSkipOnce) { shouldSkipOnce = false; sequenceNumber = tempCards[index].number; } else { isInSequence = false; } } if (tempCards.length - 1 === index) { isAllCardCheck = true; } index++; } if (isInSequence) { return { isValid: true, points: 0, }; } else { return { isValid: false, error: ErrorEnum_1.ErrorEnum.NOT_VALID_PURE_SEQUENCE, }; } }; return Rummy; }()); exports.Rummy = Rummy;