tarot-game-engine
Version:
A tarot card game Engine
928 lines (908 loc) • 38.9 kB
JavaScript
'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;