UNPKG

tarot-game-engine

Version:
928 lines (908 loc) 38.9 kB
'use strict'; var tarotCardDeck = require('tarot-card-deck'); var rxjs = require('rxjs'); var playWithDeck = require('play-with-deck'); exports.Announce = void 0; (function (Announce) { Announce["PRISE"] = "PRISE"; Announce["GARDE"] = "GARDE"; Announce["GARDE_SANS"] = "GARDE_SANS"; Announce["GARDE_CONTRE"] = "GARDE_CONTRE"; })(exports.Announce || (exports.Announce = {})); function pruneAnnouncesLowerThanGivenAnnounceFrom(arrayToPrune, limitAnnounce) { if (!limitAnnounce) { return arrayToPrune; } const indexOfAnnounce = arrayToPrune.findIndex((current) => current === limitAnnounce); return arrayToPrune.slice(indexOfAnnounce + 1); } function isClassicCard(playingCard) { return playingCard.type === tarotCardDeck.PlayingCardType.CLASSIC || playingCard.type === tarotCardDeck.PlayingCardType.FACE; } function isTrumpCard(playingCard) { return playingCard.type === tarotCardDeck.PlayingCardType.TRUMP; } const isOudler = (playingCard) => playingCard.type === tarotCardDeck.PlayingCardType.EXCUSE || playingCard.type === tarotCardDeck.PlayingCardType.TRUMP && (playingCard.value === 1 || playingCard.value === 21); const isExcuse = (playingCard) => playingCard.type === tarotCardDeck.PlayingCardType.EXCUSE; const isKing = (playingCard) => (playingCard.type === tarotCardDeck.PlayingCardType.FACE && playingCard.face === tarotCardDeck.Face.K); const isQueen = (playingCard) => (playingCard.type === tarotCardDeck.PlayingCardType.FACE && playingCard.face === tarotCardDeck.Face.Q); const isCavalier = (playingCard) => (playingCard.type === tarotCardDeck.PlayingCardType.FACE && playingCard.face === tarotCardDeck.Face.C); const isJack = (playingCard) => (playingCard.type === tarotCardDeck.PlayingCardType.FACE && playingCard.face === tarotCardDeck.Face.J); const isTrump = (playingCard) => (playingCard.type === tarotCardDeck.PlayingCardType.TRUMP); const isTrumpsButNotOudler = (playingCard) => isTrump(playingCard) && !isOudler(playingCard); class TarotGame { constructor(players, table, dealer, announceManager, cardGameManager, poigneePlugin, verifyCardsSetAside, getPossibleCardsToSetAside, countEndGameTakerPoints, countEndGameScore, endOfGameCallback) { this.players = players; this.table = table; this.dealer = dealer; this.announceManager = announceManager; this.cardGameManager = cardGameManager; this.poigneePlugin = poigneePlugin; this.verifyCardsSetAside = verifyCardsSetAside; this.getPossibleCardsToSetAside = getPossibleCardsToSetAside; this.countEndGameTakerPoints = countEndGameTakerPoints; this.countEndGameScore = countEndGameScore; this.endOfGameCallback = endOfGameCallback; this.numberOfCardsInDog = 6; this.taker = undefined; this.takerAnnounce = undefined; this.gameHasBegan = false; this.takerHasExcuseAtStartOfGame = undefined; this.cardGameManager.registerPlayerTurnPlugin(poigneePlugin); this.table.shuffle(); this.table.cut(); this.dealer.deal(this.numberOfCardsInDog); this.resolveTakerAndContinueOrEndGame(); } announce(playerThatAnnounce, announce) { this.announceManager.announce(playerThatAnnounce, announce); } setAside(playerThatSetAside, cardsSetAside) { if (this.gameHasBegan || !this.taker || this.taker.id !== playerThatSetAside.id || cardsSetAside.length !== this.numberOfCardsInDog) { return TarotGame.notifyErrorWhileSettingAside(playerThatSetAside); } const forbiddenCardsSetAside = this.verifyCardsSetAside(this.table.listCardsOf(this.taker.id), cardsSetAside); if (forbiddenCardsSetAside.length > 0) { return TarotGame.notifyErrorWhileSettingAside(playerThatSetAside); } this.table.moveFromHandToPointsOf(cardsSetAside, playerThatSetAside.id); TarotGame.notifyCardsAvailable(this.taker, this.table.listCardsOf(this.taker.id)); this.beginGame(); } declinePoignee(player) { this.poigneePlugin.decline(player); } announcePoignee(player, shownCards) { this.poigneePlugin.announce(player, shownCards); } play(playerThatPlay, card) { this.cardGameManager.play(playerThatPlay, card); } beginGame() { this.takerHasExcuseAtStartOfGame = this.table.listCardsOf(this.taker.id).some((playingCard) => isExcuse(playingCard)); this.cardGameManager.gameIsOver().subscribe(endGameTricks => this.endGame(endGameTricks)); this.cardGameManager.begin(); this.gameHasBegan = true; } resolveTakerAndContinueOrEndGame() { this.announceManager.announcesAreComplete().subscribe((takerAnnounce) => { if (!takerAnnounce) { this.players.forEach((playerToNotify) => TarotGame.notifyGameAborted(playerToNotify)); this.endOfGameCallback(this.noTakerGameResult()); return; } this.taker = takerAnnounce.taker; this.takerAnnounce = takerAnnounce.announce; this.players.forEach((playerToNotify) => TarotGame.notifyTakerIsKnown(playerToNotify, takerAnnounce.taker, takerAnnounce.announce)); switch (takerAnnounce.announce) { case exports.Announce.PRISE: case exports.Announce.GARDE: this.managePriseOrGarde(); break; case exports.Announce.GARDE_SANS: this.manageGardeSans(); break; case exports.Announce.GARDE_CONTRE: this.manageGardeContre(); break; } }); this.announceManager.beginAnnounces(); } managePriseOrGarde() { this.table.giveDogToPlayerHand(this.taker.id); TarotGame.notifyCardsAvailable(this.taker, this.table.listCardsOf(this.taker.id)); TarotGame.notifyPlayerHasToSetAside(this.taker, this.getPossibleCardsToSetAside(this.table.listCardsOf(this.taker.id), this.numberOfCardsInDog)); } manageGardeSans() { this.table.giveDogToPlayerPoints(this.taker.id); this.beginGame(); } manageGardeContre() { const playerThatIsNotTaker = this.players.find(player => player.id !== this.taker.id); this.table.giveDogToPlayerPoints(playerThatIsNotTaker.id); this.beginGame(); } noTakerGameResult() { return { numberOfPointsForAttack: undefined, numberOfPointsForDefense: undefined, finalScores: this.players.map(currentPlayer => ({ player: currentPlayer.id, score: 0 })), endOfGameDeck: this.table.gatherDeck() }; } endGame(allTricks) { const endedGameResult = this.endedGameResult(allTricks); this.players.forEach((playerToNotify) => TarotGame.notifyGameIsOver(playerToNotify, endedGameResult)); this.endOfGameCallback(endedGameResult); } endedGameResult(allTricks) { const wonCardsByTaker = this.table.listPointsFor(this.taker.id); const endGamePointsForTaker = this.countEndGameTakerPoints(wonCardsByTaker, this.takerHasExcuseAtStartOfGame); const endGameScores = this.countEndGameScore({ announce: this.takerAnnounce, poignee: this.poigneePlugin.getPotentialPoignee(), petitInLastTrick: this.resolveTeamThatPlayedPetitInLastTrick(allTricks[allTricks.length - 1]), attackNumberOfOudlers: wonCardsByTaker.filter(card => isOudler(card) && isTrumpCard(card)).length + (this.takerHasExcuseAtStartOfGame ? 1 : 0), attackNumberOfPoints: endGamePointsForTaker }); const finalScores = this.players.map(currentPlayer => ({ player: currentPlayer.id, score: currentPlayer === this.taker ? endGameScores.attackScoreByPlayer : endGameScores.defenseScoreByPlayer })); const totalNumberOfPointsInGame = 91; return { numberOfPointsForAttack: endGamePointsForTaker, numberOfPointsForDefense: totalNumberOfPointsInGame - endGamePointsForTaker, finalScores: finalScores, endOfGameDeck: this.table.gatherDeck() }; } resolveTeamThatPlayedPetitInLastTrick(lastTrick) { const petitInLastTrick = lastTrick.cards.some(playedCard => playedCard.playingCard === tarotCardDeck.TRUMP_1); if (!petitInLastTrick) { return null; } return lastTrick.winner === this.taker.id ? "ATTACK" : "DEFENSE"; } static notifyTakerIsKnown(playerToNotify, playerThatHaveAnnounced, announce) { playerToNotify.notify({ type: "TAKER_IS_KNOWN", player: playerThatHaveAnnounced.id, announce: announce }); } static notifyPlayerHasToSetAside(playerToNotify, availableCardsToSetAside) { playerToNotify.notify({ type: "ASKED_FOR_SET_ASIDE", possibleCardsToSetAside: availableCardsToSetAside }); } static notifyErrorWhileSettingAside(playerToNotify) { playerToNotify.notify({ type: "ERROR_WHILE_SETTING_ASIDE" }); } static notifyGameIsOver(playerToNotify, gameResultWithDeck) { playerToNotify.notify({ type: "GAME_IS_OVER", numberOfPointsForAttack: gameResultWithDeck.numberOfPointsForAttack, numberOfPointsForDefense: gameResultWithDeck.numberOfPointsForDefense, finalScores: gameResultWithDeck.finalScores }); } static notifyGameAborted(playerToNotify) { playerToNotify.notify({ type: "GAME_IS_ABORTED", }); } static notifyCardsAvailable(playerToNotify, cards) { playerToNotify.notify({ type: "GOT_AVAILABLE_CARDS", cards: cards }); } } class DefaultTarotDealer { constructor(tarotTable, players, dealFunction) { this.tarotTable = tarotTable; this.players = players; this.dealFunction = dealFunction; } deal() { const dealtCards = this.dealFunction(this.tarotTable.gatherDeck(), this.players.length, 6); dealtCards.playersDecks .forEach((playerDeck, index) => playerDeck .forEach((currentCard) => this.tarotTable.giveCardTo(currentCard.identifier, this.players[index].id))); dealtCards.dog.forEach((currentCard) => this.tarotTable.putCardInDog(currentCard.identifier)); this.players.forEach((player) => player.notify({ type: "GOT_AVAILABLE_CARDS", cards: this.tarotTable.listCardsOf(player.id) })); } } class DefaultAnnounceManager { constructor(players) { this.players = players; this.potentialTakerAnnounce = null; this.taker = new rxjs.ReplaySubject(1); this.availableAnnounces = Object.values(exports.Announce); } static askForAnnounce(player, availableAnnounces) { player.notify({ type: "ASKED_FOR_ANNOUNCE", availableAnnounces: availableAnnounces }); } static notifyErrorWhileAnnouncing(player) { player.notify({ type: "ERROR_WHILE_ANNOUNCING" }); } static notifyPlayerHasAnnounced(playerToNotify, playerThatHaveAnnounced, announce) { playerToNotify.notify({ type: "PLAYER_HAS_ANNOUNCED", player: playerThatHaveAnnounced.id, announce: announce }); } beginAnnounces() { this.currentPlayer = this.players[0]; DefaultAnnounceManager.askForAnnounce(this.currentPlayer, this.availableAnnounces); } announce(playerThatAnnounce, announce) { if (!this.currentPlayer || playerThatAnnounce.id !== this.currentPlayer.id) { return DefaultAnnounceManager.notifyErrorWhileAnnouncing(playerThatAnnounce); } if (announce && this.availableAnnounces.findIndex((availableAnnounce) => availableAnnounce === announce) < 0) { return DefaultAnnounceManager.notifyErrorWhileAnnouncing(playerThatAnnounce); } this.availableAnnounces = [...pruneAnnouncesLowerThanGivenAnnounceFrom(this.availableAnnounces, announce)]; this.players.forEach((playerToNotify) => DefaultAnnounceManager.notifyPlayerHasAnnounced(playerToNotify, playerThatAnnounce, announce)); if (announce) { this.potentialTakerAnnounce = { taker: playerThatAnnounce, announce: announce }; } const nextPlayerIndex = this.players.findIndex(playerToTry => playerThatAnnounce.id === playerToTry.id) + 1; if (nextPlayerIndex === this.players.length) { this.currentPlayer = undefined; this.taker.next(this.potentialTakerAnnounce); this.taker.complete(); } else { const nextPlayer = this.players[nextPlayerIndex]; this.currentPlayer = nextPlayer; DefaultAnnounceManager.askForAnnounce(nextPlayer, this.availableAnnounces); } } announcesAreComplete() { return this.taker; } } class DefaultCardGameManager { constructor(resolveTurn, getPlayableCards, table, players) { this.resolveTurn = resolveTurn; this.getPlayableCards = getPlayableCards; this.table = table; this.players = players; this.gameIsOverSubject = new rxjs.ReplaySubject(1); this.gameTricks = []; this.playerTurnPlugin = []; } static notifyEndOfTurn(playerToNotify, turnWinner) { playerToNotify.notify({ type: "TURN_RESULT_IS_KNOWN", turnWinner: turnWinner.id }); } registerPlayerTurnPlugin(plugin) { this.playerTurnPlugin.push(plugin); } begin() { this.beginTurn(this.players[0]); } gameIsOver() { return this.gameIsOverSubject; } play(playerThatPlay, card) { if (!this.currentTurnManager) { playerThatPlay.notify({ type: "ERROR_WHILE_PLAYING" }); return; } this.currentTurnManager.play(playerThatPlay, card); } beginTurn(playerThatBegin) { var _a; this.currentTurnManager = new OneTurnManager(this.resolveTurn, this.getPlayableCards, (((_a = this.currentTurnManager) === null || _a === void 0 ? void 0 : _a.turnNumber) | 0) + 1, this.playerTurnPlugin, this.table, this.players); this.currentTurnManager.turnIsComplete().subscribe((turnResult) => this.manageEndOfTurn(turnResult)); this.currentTurnManager.beginTurn(playerThatBegin); } manageEndOfTurn(turnResult) { const turnWinner = this.players.find((currentPlayer) => currentPlayer.id === turnResult.winner); this.players.forEach((playerToNotify) => DefaultCardGameManager.notifyEndOfTurn(playerToNotify, turnWinner)); turnResult.wonCardsByPlayer.forEach((wonCardsForPlayer) => this.table.moveFromTableToPointsOf(wonCardsForPlayer.wonCards, wonCardsForPlayer.playerIdentifier)); this.gameTricks.push({ cards: turnResult.playedCards, winner: turnResult.winner }); if (this.table.getNumberOfRemainingCardsToPlayFor(this.players[0].id) !== 0) { this.beginTurn(turnWinner); } else { this.gameIsOverSubject.next(this.gameTricks); } } } class OneTurnManager { constructor(resolveTurn, getPlayableCards, turnNumber, playerTurnPlugins, table, players) { this.resolveTurn = resolveTurn; this.getPlayableCards = getPlayableCards; this.turnNumber = turnNumber; this.playerTurnPlugins = playerTurnPlugins; this.table = table; this.players = players; this.turnResult = new rxjs.ReplaySubject(1); this.playedCards = []; } static notifyPlayerHasPlayed(playerToNotify, playerThatPlayed, playedCard) { playerToNotify.notify({ type: "PLAYER_HAS_PLAYED", player: playerThatPlayed.id, card: playedCard }); } static notifyCardsAvailable(playerToNotify, cards) { playerToNotify.notify({ type: "GOT_AVAILABLE_CARDS", cards: cards }); } beginTurn(playerThatBegin) { this.currentOnePlayerTurnManager = this.instantiateOnePlayerTurnManager(playerThatBegin); } play(playerThatPlay, card) { const potentiallyPlayedCard = this.currentOnePlayerTurnManager.play(playerThatPlay, card); if (!potentiallyPlayedCard) { return; } this.playedCards.push(potentiallyPlayedCard); this.players.forEach((playerToNotify) => OneTurnManager.notifyPlayerHasPlayed(playerToNotify, playerThatPlay, card)); this.table.moveCardOfPlayerToTable(card, playerThatPlay.id); OneTurnManager.notifyCardsAvailable(playerThatPlay, this.table.listCardsOf(playerThatPlay.id)); const currentPlayerIndex = this.players.findIndex(playerToTry => playerThatPlay.id === playerToTry.id); const nextPlayerIndex = currentPlayerIndex != this.players.length - 1 ? currentPlayerIndex + 1 : 0; const allPlayersHasPlayedTurn = this.playedCards.length === this.players.length; if (allPlayersHasPlayedTurn) { const turnResult = this.resolveTurn(this.playedCards); this.turnResult.next(turnResult); } else { const nextPlayer = this.players[nextPlayerIndex]; this.currentOnePlayerTurnManager = this.instantiateOnePlayerTurnManager(nextPlayer); } } turnIsComplete() { return this.turnResult; } instantiateOnePlayerTurnManager(player) { return new OnePlayerTurnManager(this.turnNumber, player, this.table.listCardsOf(player.id), this.playedCards, this.getPlayableCards, this.playerTurnPlugins); } } class OnePlayerTurnManager { constructor(turnNumber, currentPlayer, currentPlayerCards, playedCards, getPlayableCards, plugins) { this.turnNumber = turnNumber; this.currentPlayer = currentPlayer; this.currentPlayerCards = currentPlayerCards; this.playedCards = playedCards; this.getPlayableCards = getPlayableCards; this.plugins = plugins; this.pluginsAreEnded = false; rxjs.forkJoin(plugins.map(currentPlugin => { return currentPlugin.apply(turnNumber, currentPlayer, currentPlayerCards); })).pipe(rxjs.defaultIfEmpty(null)) .subscribe(() => { this.pluginsAreEnded = true; const playableCards = this.getPlayableCardsForPlayer(); OnePlayerTurnManager.askToPlay(this.currentPlayer, playableCards); }); } static notifyErrorWhilePlaying(player) { player.notify({ type: "ERROR_WHILE_PLAYING" }); } static askToPlay(player, playableCards) { player.notify({ type: "ASKED_TO_PLAY", playableCards: playableCards }); } play(playerThatPlay, card) { if (!this.pluginsAreEnded) { OnePlayerTurnManager.notifyErrorWhilePlaying(playerThatPlay); return null; } if (!this.currentPlayer || playerThatPlay.id !== this.currentPlayer.id) { OnePlayerTurnManager.notifyErrorWhilePlaying(playerThatPlay); return null; } const playableCards = this.getPlayableCardsForPlayer(); if (!playableCards.some((playableCard) => card.identifier === playableCard.identifier)) { OnePlayerTurnManager.notifyErrorWhilePlaying(playerThatPlay); return null; } return { playingCard: card, playerIdentifier: playerThatPlay.id }; } getPlayableCardsForPlayer() { return this.getPlayableCards(this.playedCards.map((currentPlayedCard) => currentPlayedCard.playingCard), this.currentPlayerCards); } } const faceValues = { "J": 11, "C": 12, "Q": 13, "K": 14 }; function resolveTarotTurn(playedCards) { const trumpPlayed = playedCards.some(playedCard => playedCard.playingCard.type === tarotCardDeck.PlayingCardType.TRUMP); let winnerPlayedCard; if (trumpPlayed) { winnerPlayedCard = [...playedCards] .filter(playedCard => isTrumpCard(playedCard.playingCard)) .sort((a, b) => b.playingCard.value - a.playingCard.value)[0]; } else { const masterPlayedCard = [...playedCards] .filter(playedCard => isClassicCard(playedCard.playingCard))[0].playingCard; const masterSuit = masterPlayedCard.suit; winnerPlayedCard = [...playedCards] .filter(playedCard => isClassicCard(playedCard.playingCard)) .filter(playedCard => playedCard.playingCard.suit === masterSuit) .sort((a, b) => sortClassicCards(a.playingCard, b.playingCard))[0]; } const players = playedCards.map(playedCard => playedCard.playerIdentifier); return { playedCards: playedCards, winner: winnerPlayedCard.playerIdentifier, wonCardsByPlayer: players.map(currentPlayer => { if (currentPlayer === winnerPlayedCard.playerIdentifier) { return { playerIdentifier: currentPlayer, wonCards: playedCards.map(playedCard => playedCard.playingCard), }; } else { return { playerIdentifier: currentPlayer, wonCards: [] }; } }) }; } function sortClassicCards(a, b) { return getValueFromCard(b) - getValueFromCard(a); } function getValueFromCard(card) { return card.type === tarotCardDeck.PlayingCardType.CLASSIC ? card.value : faceValues[card.face]; } function getPlayableTarotCards(alreadyPlayedCards, playerCards) { const masterCard = getMasterCard(alreadyPlayedCards); const masterTrump = getMasterTrump((alreadyPlayedCards)); if (!masterCard) { return playerCards; } return resolvePlayableCards(playerCards, masterCard, masterTrump); } function getMasterCard(alreadyPlayedCards) { let masterCard; for (let potentialMasterCard of alreadyPlayedCards) { if (!masterCard && potentialMasterCard !== tarotCardDeck.EXCUSE) { masterCard = potentialMasterCard; } } return masterCard; } function getMasterTrump(alreadyPlayedCards) { let masterTrump; for (let potentialMasterCard of alreadyPlayedCards) { if (isTrumpCard(potentialMasterCard) && (!masterTrump || masterTrump.value < potentialMasterCard.value)) { masterTrump = potentialMasterCard; } } return masterTrump; } function resolvePlayableCards(playerCards, masterCard, masterTrump) { const askedSuitInPlayerCards = getAskedSuitInPlayerCards(playerCards, masterCard); const trumpsOverAskedTrumpInPlayerCards = getTrumpsOverAskedTrumpInPlayerCards(playerCards, masterTrump); const allTrumpsInPlayerCards = getAllTrumpsInPlayerCards(playerCards); let playableCards = [...askedSuitInPlayerCards]; if (playableCards.length === 0) { playableCards = [...playableCards, ...trumpsOverAskedTrumpInPlayerCards]; } if (playableCards.length === 0) { playableCards = [...playableCards, ...allTrumpsInPlayerCards]; } if (playableCards.length === 0) { playableCards = [...playableCards, ...playerCards]; } else { const excuseInPlayerCards = playerCards .filter(card => card.type === tarotCardDeck.PlayingCardType.EXCUSE); playableCards = [...playableCards, ...excuseInPlayerCards]; } return playableCards; } function getAskedSuitInPlayerCards(playerCards, masterCard) { return playerCards .filter(card => isClassicCard(masterCard) && isClassicCard(card)) .filter(card => card.suit === masterCard.suit); } function getTrumpsOverAskedTrumpInPlayerCards(playerCards, masterTrump) { return playerCards .filter(card => masterTrump && isTrumpCard(card)) .filter(card => card.value > masterTrump.value); } function getAllTrumpsInPlayerCards(playerCards) { return playerCards .filter(card => isTrumpCard(card)); } function dealTarotCards(deck, numberOfPlayers, numberOfCardsInDog) { const result = { playersDecks: [[], [], [], []], dog: [] }; for (let i = 0; i < 72; i++) { result.playersDecks[i % 4].push(deck[i]); } for (let i = 72; i < 78; i++) { result.dog.push(deck[i]); } return result; } const MAIN_DECK_IDENTIFIER = "MAIN"; const DOG_DECK_IDENTIFIER = "DOG"; const TABLE_IDENTIFIER = "TABLE"; const PLAYER_PREFIX = "PLAYER"; const POINTS_PREFIX = "POINTS"; class PlayableTarotTable { constructor(deck) { this.table = playWithDeck.Table.fromDeck(MAIN_DECK_IDENTIFIER, deck); } cut() { this.table.getPile(MAIN_DECK_IDENTIFIER).cut(); } gatherDeck() { return this.table.gather(MAIN_DECK_IDENTIFIER); } getNumberOfRemainingCardsToPlayFor(player) { return this.listCardsOf(player).length; } shuffle() { this.table.getPile(MAIN_DECK_IDENTIFIER).shuffle(); } giveCardTo(cardIdentifier, player) { this.table.pick(cardIdentifier, MAIN_DECK_IDENTIFIER, PLAYER_PREFIX + player); } listCardsOf(player) { return this.table.getPile(PLAYER_PREFIX + player).list(); } putCardInDog(cardIdentifier) { this.table.pick(cardIdentifier, MAIN_DECK_IDENTIFIER, DOG_DECK_IDENTIFIER); } moveCardOfPlayerToTable(cardToMove, playerThatPlay) { this.table.pick(cardToMove.identifier, PLAYER_PREFIX + playerThatPlay, TABLE_IDENTIFIER); } moveFromTableToPointsOf(wonCards, playerThatGetCards) { wonCards.forEach(wonCard => this.table.pick(wonCard.identifier, TABLE_IDENTIFIER, POINTS_PREFIX + playerThatGetCards)); } moveFromHandToPointsOf(wonCards, playerThatGetCards) { wonCards.forEach(wonCard => this.table.pick(wonCard.identifier, PLAYER_PREFIX + playerThatGetCards, POINTS_PREFIX + playerThatGetCards)); } giveDogToPlayerHand(player) { const dogCards = [...this.table.getPile(DOG_DECK_IDENTIFIER).list()]; dogCards.forEach(dogCard => { this.table.pick(dogCard.identifier, DOG_DECK_IDENTIFIER, PLAYER_PREFIX + player); }); } giveDogToPlayerPoints(player) { const dogCards = [...this.table.getPile(DOG_DECK_IDENTIFIER).list()]; dogCards.forEach(dogCard => { this.table.pick(dogCard.identifier, DOG_DECK_IDENTIFIER, POINTS_PREFIX + player); }); } listPointsFor(player) { return this.table.getPile(POINTS_PREFIX + player).list(); } } function getIncorrectCardsSetAside(allAvailableCards, cardsSetAside) { const numberOfCardsSetAside = cardsSetAside.length; const numberOfCardsThatAreNotTrumpsNorKingNorOudlers = allAvailableCards.filter(cardIsNotTrumpNorKingNorOudler).length; const numberOfTrumpsToSetAside = numberOfCardsSetAside - numberOfCardsThatAreNotTrumpsNorKingNorOudlers; const oudlersAndKingSetAside = cardsSetAside.filter(currentCard => isOudler(currentCard) || isKing(currentCard)); const incorrectTrumpsSetAside = cardsSetAside .filter((currentCard) => isTrumpsButNotOudler(currentCard)) .slice(numberOfTrumpsToSetAside); return [...oudlersAndKingSetAside, ...incorrectTrumpsSetAside]; } function getPossibleCardsToSetAside(allAvailableCards, numberOfCardsToSetAside) { const cardsThatAreNotTrumpsNorKingNorOudlers = allAvailableCards.filter(cardIsNotTrumpNorKingNorOudler); const trumpsThatAreNotOudlers = allAvailableCards.filter(currentCard => currentCard.type === tarotCardDeck.PlayingCardType.TRUMP && currentCard.value !== 21 && currentCard.value !== 1); const trumpsMightBeSetAside = numberOfCardsToSetAside > cardsThatAreNotTrumpsNorKingNorOudlers.length; if (trumpsMightBeSetAside) { return [...cardsThatAreNotTrumpsNorKingNorOudlers, ...trumpsThatAreNotOudlers]; } else { return cardsThatAreNotTrumpsNorKingNorOudlers; } } function cardIsNotTrumpNorKingNorOudler(card) { return card.type !== tarotCardDeck.PlayingCardType.EXCUSE && card.type !== tarotCardDeck.PlayingCardType.TRUMP && (card.type !== tarotCardDeck.PlayingCardType.FACE || card.type === tarotCardDeck.PlayingCardType.FACE && card.face !== tarotCardDeck.Face.K); } const valueForGivenCard = (playingCard) => { if (isKing(playingCard) || isOudler(playingCard)) { return 4.5; } else if (isQueen(playingCard)) { return 3.5; } else if (isCavalier(playingCard)) { return 2.5; } else if (isJack(playingCard)) { return 1.5; } else { return 0.5; } }; function countTarotEndGameTakerPoints(takerWonCards, takerHasExcuseAtStartOfGame) { const scoreWithoutExcuseTakenIntoAccount = takerWonCards .map((playingCard) => valueForGivenCard(playingCard)) .reduce((sum, current) => sum + current, 0); const excuseIsInTakerGame = takerWonCards.some(playingCard => isExcuse(playingCard)); if (excuseIsInTakerGame && !takerHasExcuseAtStartOfGame) { return scoreWithoutExcuseTakenIntoAccount - 4; } else if (!excuseIsInTakerGame && takerHasExcuseAtStartOfGame) { return scoreWithoutExcuseTakenIntoAccount + 4; } else { return scoreWithoutExcuseTakenIntoAccount; } } var Poignee; (function (Poignee) { Poignee["SIMPLE"] = "SIMPLE"; Poignee["DOUBLE"] = "DOUBLE"; Poignee["TRIPLE"] = "TRIPLE"; })(Poignee || (Poignee = {})); const contractFromNumberOfOudlers = (numberOfOudlers) => { switch (numberOfOudlers) { case 1: return 51; case 2: return 41; case 3: return 36; case 0: return 56; } }; const multiplierFromAnnounce = (announce) => { switch (announce) { case exports.Announce.PRISE: return 1; case exports.Announce.GARDE: return 2; case exports.Announce.GARDE_SANS: return 4; case exports.Announce.GARDE_CONTRE: return 6; } }; const pointsFromPoignee = (poignee) => { switch (poignee) { case Poignee.SIMPLE: return 20; case Poignee.DOUBLE: return 30; case Poignee.TRIPLE: return 40; default: return 0; } }; const pointsFromPetitInLastTrick = (teamThatPutPetitInLastTrick) => { switch (teamThatPutPetitInLastTrick) { case "ATTACK": return 10; case "DEFENSE": return -10; default: return 0; } }; function countFourPlayersTarotScore(endGameStatus) { let points = 0; const contract = contractFromNumberOfOudlers(endGameStatus.attackNumberOfOudlers); const differenceWithContract = endGameStatus.attackNumberOfPoints - contract; const attackWon = differenceWithContract >= 0; points += attackWon ? 25 : -25; points += differenceWithContract; const petitInLastTrickPoints = pointsFromPetitInLastTrick(endGameStatus.petitInLastTrick); points += petitInLastTrickPoints; const multiplier = multiplierFromAnnounce(endGameStatus.announce); points *= multiplier; const poigneePoints = pointsFromPoignee(endGameStatus.poignee); points += points > 0 ? poigneePoints : -poigneePoints; return { attackScoreByPlayer: points * 3, defenseScoreByPlayer: -points }; } const THRESHOLD_BY_POIGNEE_TYPE = { "SIMPLE": 10, "DOUBLE": 13, "TRIPLE": 15 }; class DefaultPlayerPoigneeManager { constructor(player, playerCards, allPlayers) { this.player = player; this.playerCards = playerCards; this.allPlayers = allPlayers; this.announceIsComplete = false; this.poigneeAnnounceObservable = new rxjs.ReplaySubject(1); this.filterOnTrumpsAndExcuse = (card) => { return card.type === tarotCardDeck.PlayingCardType.TRUMP || card.type === tarotCardDeck.PlayingCardType.EXCUSE; }; this.filterOnTrumpsOnly = (card) => { return card.type === tarotCardDeck.PlayingCardType.TRUMP; }; this.numberOfEligibleCards = playerCards.filter(this.filterOnTrumpsAndExcuse).length; if (this.numberOfEligibleCards >= THRESHOLD_BY_POIGNEE_TYPE[Poignee.SIMPLE] && this.numberOfEligibleCards < THRESHOLD_BY_POIGNEE_TYPE[Poignee.DOUBLE]) { this.potentialPoigneeType = Poignee.SIMPLE; } else if (this.numberOfEligibleCards >= THRESHOLD_BY_POIGNEE_TYPE[Poignee.DOUBLE] && this.numberOfEligibleCards < THRESHOLD_BY_POIGNEE_TYPE[Poignee.TRIPLE]) { this.potentialPoigneeType = Poignee.DOUBLE; } else if (this.numberOfEligibleCards >= THRESHOLD_BY_POIGNEE_TYPE[Poignee.TRIPLE]) { this.potentialPoigneeType = Poignee.TRIPLE; } else { this.potentialPoigneeType = null; } if (!this.potentialPoigneeType) { this.terminatePoigneeAnnounce(undefined); return; } const cardsContainsExcuse = playerCards.some(card => card.type === tarotCardDeck.PlayingCardType.EXCUSE); const justEnoughCardsForPoignee = Object.values(THRESHOLD_BY_POIGNEE_TYPE).includes(this.numberOfEligibleCards); this.excuseIsEligibleForPoignee = cardsContainsExcuse && justEnoughCardsForPoignee; this.possibleCardsToShow = playerCards.filter(this.excuseIsEligibleForPoignee ? this.filterOnTrumpsAndExcuse : this.filterOnTrumpsOnly); DefaultPlayerPoigneeManager.askForPoigneeAnnounce(this.player, THRESHOLD_BY_POIGNEE_TYPE[this.potentialPoigneeType], this.possibleCardsToShow); } announcePoignee(shownCards) { if (this.announceIsComplete) { DefaultPlayerPoigneeManager.notifyErrorWhileAnnouncing(this.player); return; } const distinctShownCards = new Set(shownCards); if (distinctShownCards.size !== THRESHOLD_BY_POIGNEE_TYPE[this.potentialPoigneeType]) { DefaultPlayerPoigneeManager.notifyErrorWhileAnnouncing(this.player); return; } const possibleCardsToShowIds = this.possibleCardsToShow.map(card => card.identifier); const shownCardsAreAllAuthorized = shownCards.map(card => card.identifier).every(shownCardId => possibleCardsToShowIds.includes(shownCardId)); if (!shownCardsAreAllAuthorized) { DefaultPlayerPoigneeManager.notifyErrorWhileAnnouncing(this.player); return; } this.allPlayers.forEach(currentPlayerToNotify => { DefaultPlayerPoigneeManager.notifyPlayerHasAnnouncedPoignee(currentPlayerToNotify, this.player, shownCards); }); this.terminatePoigneeAnnounce(this.potentialPoigneeType); } declinePoignee() { if (this.announceIsComplete) { DefaultPlayerPoigneeManager.notifyErrorWhileAnnouncing(this.player); return; } this.terminatePoigneeAnnounce(undefined); } observePoigneeAnnounce() { return this.poigneeAnnounceObservable; } terminatePoigneeAnnounce(potentialPoignee) { this.poigneeAnnounceObservable.next(potentialPoignee); this.poigneeAnnounceObservable.complete(); this.announceIsComplete = true; } static askForPoigneeAnnounce(player, numberOfCardsToShow, possibleCardsToShow) { player.notify({ type: "ASKED_FOR_POIGNEE_ANNOUNCE", numberOfCardsToShow, possibleCardsToShow }); } static notifyErrorWhileAnnouncing(player) { player.notify({ type: "ERROR_WHILE_ANNOUNCING_POIGNEE" }); } static notifyPlayerHasAnnouncedPoignee(playerToNotify, playerThatHaveAnnounced, shownCards) { playerToNotify.notify({ type: "POIGNEE_HAS_BEEN_ANNOUNCED", player: playerThatHaveAnnounced.id, shownCards: shownCards }); } } class PoigneeCardGamePlugin { constructor(players) { this.players = players; this.poigneesManagerByPlayers = new Map(); this.potentialPoignee = null; } apply(turnNumber, player, playerCards) { if (turnNumber !== 1) { return rxjs.EMPTY; } this.poigneesManagerByPlayers.set(player.id, new DefaultPlayerPoigneeManager(player, playerCards, this.players)); const poigneeObservation = this.poigneesManagerByPlayers.get(player.id).observePoigneeAnnounce(); poigneeObservation.subscribe(poignee => { this.potentialPoignee = this.potentialPoignee || poignee; }); return poigneeObservation; } decline(player) { if (!this.poigneesManagerByPlayers.has(player.id)) { PoigneeCardGamePlugin.notifyErrorWhileAnnouncing(player); return; } this.poigneesManagerByPlayers.get(player.id).declinePoignee(); } announce(player, shownCards) { if (!this.poigneesManagerByPlayers.has(player.id)) { PoigneeCardGamePlugin.notifyErrorWhileAnnouncing(player); return; } this.poigneesManagerByPlayers.get(player.id).announcePoignee(shownCards); } getPotentialPoignee() { return this.potentialPoignee; } static notifyErrorWhileAnnouncing(player) { player.notify({ type: "ERROR_WHILE_ANNOUNCING_POIGNEE" }); } } function getTarotGame(playingCards, players, endOfGameCallback) { return getTarotGameWithCustomDealFunction(playingCards, players, endOfGameCallback, dealTarotCards); } function getTarotGameWithCustomDealFunction(playingCards, players, endOfGameCallback, dealFunction) { const table = new PlayableTarotTable(tarotCardDeck.DECK_78); const announceManager = new DefaultAnnounceManager(players); const cardGameManager = new DefaultCardGameManager(resolveTarotTurn, getPlayableTarotCards, table, players); const dealer = new DefaultTarotDealer(table, players, dealFunction); const poigneeCardGamePlugin = new PoigneeCardGamePlugin(players); return new TarotGame(players, table, dealer, announceManager, cardGameManager, poigneeCardGamePlugin, getIncorrectCardsSetAside, getPossibleCardsToSetAside, countTarotEndGameTakerPoints, countFourPlayersTarotScore, endOfGameCallback); } exports.TarotGame = TarotGame; exports.getTarotGame = getTarotGame; exports.getTarotGameWithCustomDealFunction = getTarotGameWithCustomDealFunction;