card-games-utils
Version:
Utility package for card games
634 lines (633 loc) • 25.4 kB
JavaScript
"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;