/* tslint:disable:no-bitwise */ import * as ko from "knockout"; import * as moment from "moment"; import { Chat } from "../api/chat"; import { Game } from "../api/game"; import { Tournament } from "../api/tournament"; import { App } from "../app"; import { appConfig } from "../appconfig"; import * as authManager from "../authmanager"; import { debugSettings } from "../debugsettings"; import { withCommas } from "../helpers"; import { _ } from "../languagemanager"; import { SimplePopup } from "../popups/simplepopup"; import { connectionService, slowInternetService, soundManager } from "../services"; import { ConnectionWrapper } from "../services/connectionwrapper"; import { SlowInternetService } from "../services/slowinternetservice"; import { settings } from "../settings"; import * as timeService from "../timeservice"; import { ActionBlock } from "./actionBlock"; import { GameActionsQueue } from "./gameactionsqueue"; import { HandHistory } from "./handhistory"; import { PlayerMessage } from "./playerMessage"; import * as runtimeSettings from "./runtimesettings"; import { SystemMessage } from "./SystemMessage"; import { TableCardsPlace } from "./tableCardsPlace"; import { TablePlaces } from "./tableplaces"; import { TablePlaceModel } from "./tabpleplacemodel"; import { TournamentView } from "./tournamentview"; declare let apiHost: string; declare let host: string; declare let app: App; export class TableView { public tableName: KnockoutObservable; /** * Current login which sitting on this view. */ public currentLogin: KnockoutObservable; /** * Represents the value indicating that information * about the table is retreiving. */ public connecting: KnockoutObservable; /** * Request which performs connecting to the table. */ public connectingRequest: JQueryDeferred = null; public smallBlind: KnockoutObservable; public bigBlind: KnockoutObservable; public ante: KnockoutObservable; public changeBetParametersNextGame = ko.observable(false); public nextGameSmallBlind: KnockoutObservable = ko.observable(0); public nextGameBigBlind: KnockoutObservable = ko.observable(0); public nextGameAnte: KnockoutObservable = ko.observable(0); public nextGameInformation: KnockoutComputed; /** * Minimal amount of money which currently authenticated player * could bring on the table if he stand up from the table lately. */ public minimalPlayerBuyIn: KnockoutObservable; /** * Minimal base amount of buy-in in BB to bring on the table. */ public minimalBuyIn: KnockoutObservable; public gamePlayers: KnockoutObservableArray; public places: KnockoutComputed; public pots: KnockoutObservableArray; public tableCards: TableCardsPlace; public messages: KnockoutObservableArray; public systemMessages: KnockoutObservableArray; public lastRaise: KnockoutObservable; public timePass: KnockoutObservable; /** * Count of actual actions which was performed by the players during the current game */ public actionsCount: KnockoutObservable; /** * Indicating whether authenticated player is playing in the game */ public myPlayerInGame: KnockoutComputed; /** * Indicating whether authenticated player was playing in the game */ public myPlayerWasInGame: KnockoutComputed; /** * Id of the current game on the table. */ public gameId: KnockoutObservable; public currentGameId: KnockoutObservable; /** * Indicates that game finished. */ public gameFinished: KnockoutObservable; /** * Indicates that prizes distributed in the game. */ public prizesDistributed: KnockoutObservable; /** * Indicates that game started. */ public gameStarted: KnockoutObservable; /** * Count of active players in the game */ public activePlayersCount: KnockoutComputed; /** * Value indicating whether all bets are rounded. */ public allBetsRounded: KnockoutObservable; /** * Value indicating whether use cards variant up */ public cardsVariantUp: KnockoutObservable; /** * Value indicating whether use cards variant down */ public cardsVariantDown: KnockoutObservable; /** * Css rules for table-container */ public containerCss: KnockoutObservable; public timeLeft: KnockoutComputed; public timerInterval: number; public chipWidth: number; public chatMessage: KnockoutObservable; public combinations: KnockoutObservableArray; public currentPlayer: KnockoutComputed; public myPlayer: KnockoutComputed; public mainButtonsEnabled: KnockoutObservable; public playerActions: KnockoutObservable; public turnEnabled: KnockoutComputed; public isMyTurn: KnockoutComputed; public notMyTurn: KnockoutComputed; public isInGame: KnockoutComputed; public checkOrCallAmount: KnockoutComputed; /** * Bet amount for currently active player. */ public currentBet: KnockoutComputed; /** * Bet amount for player. */ public myBet: KnockoutComputed; public currentTotalBet: KnockoutComputed; public maximumBet: KnockoutComputed; public currentRaise: KnockoutComputed; public minimumRaiseAmount: KnockoutComputed; public maximumRaiseAmount: KnockoutComputed; public amountSupported: KnockoutObservable; public maxAmountOfMoneyForOtherActivePlayers: KnockoutObservable; public isSitOut: KnockoutComputed; public totalPot: KnockoutComputed; public totalPotCaption: KnockoutComputed; public currentCombination = ko.observable(""); public actionBlock: ActionBlock; public onMyTurn: Signal; public tablePlaces: TablePlaces; public lastHandHistory: KnockoutObservable; public hasPreviousHand: KnockoutComputed; public tableBetsCaption: KnockoutComputed; public currentHandCaption: KnockoutComputed; public previousHandCaption: KnockoutComputed; /** * Indicates that animation is suppressed from playing. */ public animationSuppressed = ko.observable(false); /** * Tournament to which belongs given table. */ public tournament = ko.observable(null); /** * Indicates that table is frozen. */ public frozen = ko.observable(false); /** * Indicates that table is opened */ public opened = ko.observable(true); /** * Indicates that table is paused */ public paused = ko.observable(false); public pauseDate = ko.observable(null); public soundEnabled = false; public hasPendingMoney = ko.observable(false); public hasNotification = ko.observable(false); public notification = ko.observable(""); public couldAddChips: KnockoutComputed; /* If of the last message Id starting from which messages could be displayed */ public lastMessageId = 0; private animationSettings = AnimationSettings.getSettings(); private sitting = false; private cardsReceived: boolean; private queue: GameActionsQueue; private handHistory: HandHistory; private pauseDescription = ko.observable(""); private pauseDescriptionHandle: number = null; private notificationHandleTimeout: number = null; private notificationHandleInterval: number = null; private displayingRebuyAddonNotification = false; private enableInjectPlayerCards = false; constructor(public tableId: number, public model: GameTableModel) { /// /// Updates the information about the table from the server /// Id of the table for which view has to be created. /// const self = this; this.tableId = tableId; this.tableName = ko.observable(model === null ? "0" : model.TableName); this.connecting = ko.observable(true); this.gamePlayers = ko.observableArray([]); this.pots = ko.observableArray([]); this.tableCards = new TableCardsPlace(); this.messages = ko.observableArray([]); this.systemMessages = ko.observableArray([]); this.chatMessage = ko.observable(""); this.playerActions = ko.observable(); this.mainButtonsEnabled = ko.observable(false); this.gameId = ko.observable(); this.currentGameId = ko.observable(); this.actionsCount = ko.observable(0); this.bigBlind = ko.observable(model === null ? 0 : model.BigBlind); this.smallBlind = ko.observable(model === null ? 0 : model.SmallBlind); this.ante = ko.observable(null); this.minimalPlayerBuyIn = ko.observable(); this.minimalBuyIn = ko.observable(); this.lastRaise = ko.observable(); this.timePass = ko.observable(); this.gameFinished = ko.observable(true); this.prizesDistributed = ko.observable(true); this.gameStarted = ko.observable(false); this.combinations = ko.observableArray([]); this.actionBlock = new ActionBlock(); this.tablePlaces = new TablePlaces(model == null ? 0 : model.MaxPlayers); this.chipWidth = 30; this.cardsReceived = false; this.onMyTurn = new signals.Signal(); this.queue = new GameActionsQueue(); this.cardsVariantUp = ko.observable(false); this.cardsVariantDown = ko.observable(true); this.places = ko.computed(function () { return self.tablePlaces.places(); }, this); if (typeof window !== "undefined") { if (window.innerWidth >= 1024 || window.innerHeight >= 1024) { this.chipWidth = 60; if (window.innerWidth >= 1920 || window.innerHeight >= 1920) { this.chipWidth = 2 * 83; } if (window.innerWidth >= 3840 || window.innerHeight >= 3840) { this.chipWidth = 4 * 83; } } } this.myPlayer = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const p = self.places().filter((item) => { return item.PlayerName() === self.currentLogin(); }); if (p.length > 0) { return p[0]; } return null; }, this).extend({ notify: "always" }); this.myPlayerInGame = ko.computed(function () { const gid = self.gameId(); if (gid === null || gid === 0) { return false; } const myself = self.myPlayer(); if (myself === null) { return false; } return (myself.Cards() !== null) && (myself.Cards().length !== 0); }, this); this.currentLogin = ko.observable(authManager.login()); authManager.login.subscribe(function (value) { self.currentLogin(value); }); this.timeLeft = ko.computed(function () { if (self.frozen()) { return -1; } let pass = self.timePass(); pass = pass === null ? 0 : pass; return runtimeSettings.game.moveTime - pass; }); this.currentRaise = ko.computed({ owner: this, read() { return self.actionBlock.tableSlider.current(); }, write(value) { self.actionBlock.tableSlider.current(value); }, }); this.currentPlayer = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const p = self.places().filter((item) => { return item.IsCurrent(); }); if (p.length > 0) { return p[0]; } return null; }, this); this.activePlayersCount = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const activePlayersCount = self.places().reduce((prev: number, value: TablePlaceModel) => { const isActive = value.WasInGame() && (value.Cards() !== null); return prev + (isActive ? 1 : 0); }, 0); return activePlayersCount; }, this); this.myPlayerWasInGame = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const myself = self.myPlayer(); if (myself === null) { return false; } return (myself.WasInGame() === true); }, this); this.turnEnabled = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const cp = self.currentPlayer(); if (cp === null) { return false; } const activePlayersCount = this.places().reduce((prev: number, value: TablePlaceModel) => { const isActive = value.WasInGame() && (value.Cards() !== null); return prev + (isActive ? 1 : 0); }, 0); return (cp.PlayerName() === self.currentLogin()) && activePlayersCount > 1; }, this); this.isMyTurn = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const cp = self.myPlayer(); if (cp === null || !cp.IsInGameStatus()) { return false; } if (!self.gameStarted()) { return false; } if (cp.IsCurrent() === null) { return false; } return cp.IsCurrent(); }, this); this.notMyTurn = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const cp = self.myPlayer(); if (cp === null || !cp.IsInGameStatus()) { return false; } if (cp.IsCurrent() === null) { return true; } return !cp.IsCurrent(); }, this); this.isInGame = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const cp = self.myPlayer(); if (cp === null || !cp.IsInGameStatus()) { return false; } return true; }); this.maximumBet = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); let result = 0; result = self.places().reduce(function (previousValue: number, currentValue: TablePlaceModel) { return Math.max(previousValue, currentValue.Bet()); }, 0); return result; }, this); this.allBetsRounded = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const playersInGame = self.places().filter((value) => value.WasInGame()).length; const activePlayers = self.places().filter((value) => value.WasInGame() && (value.Cards() !== null)); const maxBet = self.maximumBet(); const allRounded = activePlayers.filter((player) => (player.Bet() === maxBet) || (player.Money() === 0)).length === activePlayers.length; if (allRounded && (self.actionsCount() >= playersInGame)) { return true; } return false; }, this); this.currentBet = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const result = self.places().reduce(function (previousValue: number, currentValue: TablePlaceModel) { if (currentValue.IsCurrent()) { return currentValue.Bet(); } return previousValue; }, 0); return result; }, this); this.myBet = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const myPlayer = self.myPlayer(); if (myPlayer === null) { return 0; } return myPlayer.Bet(); }, this); this.currentTotalBet = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const result = self.places().reduce(function (previousValue: number, currentValue: TablePlaceModel) { if (currentValue.IsCurrent()) { return currentValue.TotalBet(); } return previousValue; }, 0); return result; }, this); this.checkOrCallAmount = ko.computed(function () { return self.maximumBet() - self.currentBet(); }, this); this.minimumRaiseAmount = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const currentPlayer = self.myPlayer(); if (currentPlayer === null) { return null; } const oldVersion = false; if (oldVersion) { const currentBet = self.currentBet(); let mb = 2 * (self.lastRaise() - currentBet) - self.checkOrCallAmount(); // No less then big blind. mb = Math.max(mb, self.bigBlind()); // No more then current money mb = Math.min(mb, currentPlayer.Money()); const addon = currentBet + self.checkOrCallAmount(); let raiseAmount = mb + addon; const maxAmountOfMoneyForOtherActivePlayers = self.maxAmountOfMoneyForOtherActivePlayers(); raiseAmount = Math.min(raiseAmount, maxAmountOfMoneyForOtherActivePlayers); return raiseAmount; } else { let basicRaise = self.maximumBet() + self.lastRaise(); // No less then big blind. basicRaise = Math.max(basicRaise, self.bigBlind()); // No more then current money basicRaise = Math.min(basicRaise, currentPlayer.Money() + currentPlayer.Bet()); // No more then money which other players has. const maxAmountOfMoneyForOtherActivePlayers = self.maxAmountOfMoneyForOtherActivePlayers(); const raiseAmount = Math.min(basicRaise, maxAmountOfMoneyForOtherActivePlayers); return raiseAmount; } }, this); this.isSitOut = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const currentPlayer = this.myPlayer(); if (currentPlayer === null) { return false; } return (currentPlayer.Status() & 1) !== 0; }, this); this.maxAmountOfMoneyForOtherActivePlayers = ko.computed(function () { let result = 0; result = self.places().reduce(function (previousValue: number, currentValue: TablePlaceModel) { if (currentValue.PlayerName() === self.currentLogin()) { return previousValue; } if (!currentValue.IsInGameStatus()) { return previousValue; } if (currentValue.Cards() === null) { return previousValue; } return Math.max(previousValue, currentValue.Bet() + currentValue.Money()); }, 0); return result; }).extend({ notify: "always" }); this.containerCss = ko.computed(() => { const cardsVariant = settings.cardsVariant(); if (cardsVariant === "up") { this.cardsVariantUp = ko.observable(true); this.cardsVariantDown = ko.observable(false); } else if (cardsVariant === "down") { this.cardsVariantUp = ko.observable(false); this.cardsVariantDown = ko.observable(true); } return { "expanded": this.actionBlock.expanded(), "cardsvariant-up": this.cardsVariantUp(), "cardsvariant-down": this.cardsVariantDown(), }; }); this.nextGameInformation = ko.computed(() => { if (!this.changeBetParametersNextGame()) { return ""; } if (!this.nextGameAnte()) { return _("table.betLevelChangeNextGame", { smallBlind: this.nextGameSmallBlind(), bigBlind: this.nextGameBigBlind(), }); } else { return _("table.betLevelChangeNextGameWithAnte", { smallBlind: this.nextGameSmallBlind(), bigBlind: this.nextGameBigBlind(), ante: this.nextGameAnte(), }); } }); this.maximumRaiseAmount = ko.computed(function () { const currentPlayer = self.myPlayer(); if (currentPlayer === null) { return null; } const max = self.maxAmountOfMoneyForOtherActivePlayers(); const bet = currentPlayer.Bet(); const money = currentPlayer.Money(); return Math.min(money + bet, max) - bet; }, this).extend({ notify: "always" }); this.totalPot = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const totalBetsOnTable = self.places().reduce((prev: number, item: TablePlaceModel) => { return prev + item.Bet(); }, 0); const potsArray = self.pots(); if (potsArray === null) { return totalBetsOnTable; } const pots = potsArray.reduce((prev: number, item: number) => { return prev + item; }, 0); return totalBetsOnTable + pots; }, this); this.totalPotCaption = ko.computed(function () { self.tablePlaces.placesRefreshTrigger(); const totalPot = self.totalPot(); if (totalPot === null || totalPot === 0) { return null; } return _("table.totalpot") .replace("#amount", withCommas(totalPot.toFixed(), ",")); }, this); this.tableBetsCaption = ko.computed(function () { const hasAnte = self.ante() != null; if (hasAnte) { return _("table.betsWithAnte", { ante: self.ante(), bb: self.bigBlind(), sb: self.smallBlind() }); } return _("table.bets", { bb: self.bigBlind(), sb: self.smallBlind() }); }, this); this.couldAddChips = ko.pureComputed(function () { const me = self.myPlayer(); if (me == null) { return false; } if (self.hasPendingMoney()) { return false; } if (appConfig.game.noTableMoneyLimit) { return true; } const totalBet = (me.TotalBet() == null ? 0 : me.TotalBet()); const baseMinimalBuyIn = self.minimalBuyIn() * self.model.BigBlind; const tableTotal = totalBet + me.Money() + me.Bet(); return (20 * baseMinimalBuyIn) > tableTotal; }, this); this.actionBlock.attach(this); this.initHandHistory(); } /** * Gets current cards combination */ public getCurrentCombination() { const my = this.myPlayer(); if (my == null) { return null; } const tableCards = this.tableCards.tableCardsData(); return my.getCombination(tableCards); } public startTimer(startTime: number = 1) { const self: TableView = this; if (this.frozen()) { return; } self.timePass(startTime); this.timerInterval = timeService.setInterval(function () { if (runtimeSettings.updateTimer) { const time = self.timePass(); self.timePass(time + 1); if (self.timeLeft() === 7) { if (self.currentPlayer() === self.myPlayer()) { if (self.soundEnabled) { soundManager.playTurnReminder(); } } else { if (self.soundEnabled) { soundManager.playTurnReminderForAll(); } } } } }, 1000); } public clearTimer() { timeService.clearInterval(this.timerInterval); this.timePass(null); } public clearInformation() { this.messages([]); } /** * Updates information about the table from the server. */ public async updateTableInformation() { const self = this; if (this.connectingRequest !== null && this.connectingRequest.state() === "pending") { this.log("Cancelling the connection request process"); this.cancelUpdateTableInformation(); // Re-schedule updating information. try { await this.connectingRequest; } catch (e) { this.log("Rescheduling the updating information."); await this.updateTableInformation(); } return; } this.connecting(true); const currentLoadingRequest = $.Deferred(); this.clearTimer(); const wrapper = connectionService.currentConnection; let hubId = wrapper.connection.id; const connectionInfo = "HID:" + hubId; this.log("Connecting to table " + this.tableId + " on connection " + connectionInfo); const startConnection = app.buildStartConnection(); const api = new Game(host); // Set opening card parameters in parallel to other operations. await api.setTableParameters(this.tableId, !settings.autoHideCards()); try { this.connectingRequest = currentLoadingRequest; await startConnection; if (wrapper.terminated) { self.log(`Connection ${hubId} appears to be terminated`); return; } hubId = wrapper.connection.id; self.log("Attempting to connect to table and chat over connection " + hubId); const joinTableRequest = this.joinTable(wrapper); const joinChatRequest = this.joinChat(wrapper); const joinRequest = $.when(joinTableRequest, joinChatRequest); currentLoadingRequest.progress(function (command: string) { self.log("Receiving request to cancel all joining operations"); joinTableRequest.notify(command); joinChatRequest.notify(command); }); await joinRequest.then(function () { if (wrapper.terminated) { console.log("Cancel terminated connection."); currentLoadingRequest.reject("Cancelled"); return; } self.log("Jointing to table finished"); currentLoadingRequest.resolve(); }, function (result1, result2) { if (wrapper.terminated) { self.log("Don't use terminated connection."); return; } let message: string; if (result2 == null) { let taskFailed; if (result1[2] === joinTableRequest) { taskFailed = "join table"; } else { taskFailed = "join chat"; } message = "Rejecting request due to " + taskFailed + " failure in the connection." + "Failed request: " + result1[0]; } else { message = "Rejecting request due to one of the error in the connection." + "First request: " + result1[0] + "Second request: " + result2[0]; } self.log(message); currentLoadingRequest.reject(message); }); } catch (message) { self.log("Table connection failed. Error: " + message); currentLoadingRequest.reject("Table connection failed. Error: " + message); } } public cancelUpdateTableInformation() { if (this.connectingRequest !== null) { this.connectingRequest.notify("cancel"); this.connectingRequest = null; } } public joinTable(wrapper: ConnectionWrapper, maxAttempts = 3) { const self = this; const result = $.Deferred(); if (maxAttempts === 0 || wrapper.terminated) { this.log("Stop connecting to table"); result.reject("Stop connecting to table", false); return result; } const hubId = connectionService.currentConnection.connection.id; const connectionInfo = "HID:" + hubId; this.log("Joining table on connection " + connectionInfo); const cancelled = false; let subsequentDeferred: JQueryDeferred = null; const cancelOperation = function () { self.log("Cancelling join table request"); result.reject("Cancelled", true); }; wrapper.buildStartConnectionAsync().then(function () { if (wrapper.terminated) { cancelOperation(); return; } self.log(`Executing Game.join on connection ${wrapper.connection.id} in state ${wrapper.connection.state}`); const operation = wrapper.connection.Game.server.join(self.tableId) .then(function () { if (wrapper.terminated) { cancelOperation(); return; } result.resolve(); }, function (error: any) { if (wrapper.terminated || cancelled || error === "Cancelled") { cancelOperation(); return; } const message = "" + error as string; self.log("Failed to join table " + self.tableId + ", " + connectionInfo + ". Reason: " + message); if (message.indexOf("Connection was disconnected before invocation result was received.") >= 0) { self.log("Stopped connecting to table since underlying connection is broken"); slowInternetService.showReconnectFailedPopup(); result.reject("Stopped connecting to table since underlying connection is broken", false); return; } else { subsequentDeferred = self.joinTable(wrapper, maxAttempts - 1); return subsequentDeferred.then(function () { result.resolve(); }, function (subsequentError, subsequentCancelled: boolean) { result.reject(subsequentError, subsequentCancelled); }); } }); result.progress(function (command: string) { this.cancelled = true; result.reject("Cancelled"); if (subsequentDeferred !== null) { subsequentDeferred.notify("cancel"); subsequentDeferred = null; } }); }, function () { cancelOperation(); }); return result; } public joinChat(wrapper: ConnectionWrapper, maxAttempts = 3) { const self = this; const result = $.Deferred(); if (maxAttempts === 0 || wrapper.terminated) { this.log("Stop connecting to table chat"); result.reject("Stop connecting to table chat", false); return result; } const cancelled = false; let subsequentDeferred: JQueryDeferred = null; const cancelOperation = function () { self.log("Cancelling join table request"); result.reject("Cancelled", true); }; wrapper.buildStartConnection()().then(function () { if (wrapper.terminated) { cancelOperation(); return; } self.log(`Executing Game.join on connection ${wrapper.connection.id} in state ${wrapper.connection.state}`); const operation = wrapper.connection.Chat.server.join(self.tableId) .then(function () { if (wrapper.terminated) { cancelOperation(); return; } result.resolve(); }, function (message: string) { if (wrapper.terminated || cancelled) { self.log("Cancelling join table chat request"); result.reject("Cancelled", true); return; } self.log("Failed to join table " + self.tableId + " chat. Reason: " + message); subsequentDeferred = self.joinChat(wrapper, maxAttempts - 1); return subsequentDeferred.then(function () { result.resolve(); }, function (subsequentError, subsequentCancelled: boolean) { result.reject(subsequentError, subsequentCancelled); }); }); result.progress(function (command: string) { this.cancelled = true; result.reject("Cancelled"); if (subsequentDeferred !== null) { subsequentDeferred.notify("cancel"); subsequentDeferred = null; } }); }, function () { cancelOperation(); }); return result; } public disconnect() { /// /// Updates the information about the table from the server /// const self = this; // HACK: Game server should correctly leave from table. // Looks like when calling leave status stop receiving player specific // notifications. Which is looks like SignalR connection is not correctly // removed from the corresponding group. // connectionService.currentConnection.connection.Game.server.leave(this.tableId); // connectionService.currentConnection.connection.Chat.server.leave(this.tableId); } public setButtons(dealerSeat: number) { if (dealerSeat === 0) { this.setDealer(0); this.setSmallBlind(0); this.setBigBlind(0); } else { const smallBlindSeat = this.getNextPlayerSeat(dealerSeat); const bigBlindSeat = this.getNextPlayerSeat(smallBlindSeat); this.setDealer(dealerSeat); this.setSmallBlind(smallBlindSeat); this.setBigBlind(bigBlindSeat); } const playersInGame = this.places().filter((value) => value.WasInGame() && value.IsInGameStatus()).length; if (playersInGame === 2) { const bigBlindSeat = this.getNextPlayerSeat(dealerSeat); this.setDealer(dealerSeat); this.setSmallBlind(dealerSeat); this.setBigBlind(bigBlindSeat); } } public setDealer(dealerSeat: number) { const players = this.places(); players.forEach((p) => { if (p.Seat() === dealerSeat) { p.IsDealer(true); } else { p.IsDealer(null); } }); // this.places(players); } public setSmallBlind(smallBlindSeat: number) { const players = this.places(); players.forEach((p) => { if (p.Seat() === smallBlindSeat) { p.IsSmallBlind(true); return; } else { p.IsSmallBlind(false); } }); } public setBigBlind(bigBlindSeat: number) { const players = this.places(); players.forEach((p) => { if (p.Seat() === bigBlindSeat) { p.IsBigBlind(true); return; } else { p.IsBigBlind(false); } }); } public getNextPlayerSeat(currentSeat: number) { const players = this.places(); let comparePlayer = currentSeat; let nextPlayer = null; const maxPlayers = this.tablePlaces.getMaxPlayers(); for (let i = 0; i < maxPlayers; i++) { if (nextPlayer != null) { break; } comparePlayer = (comparePlayer + 1) % maxPlayers; if (comparePlayer === 0) { comparePlayer = maxPlayers; } players.forEach((p) => { if (nextPlayer != null) { return; } if (p.Seat() === comparePlayer) { if (p.WasInGame() && p.IsInGameStatus()) { nextPlayer = p.Seat(); } } }); } return nextPlayer; } public setCurrent(currentPlayerId: number) { const players = this.places(); players.forEach((p) => { if (p.PlayerId() === currentPlayerId) { p.IsCurrent(true); } else { p.IsCurrent(null); } }); } /** * Sits players on the table * @param playerId Id of the player which join the table * @param seat Number of seat where player sit * @param playerName Name of the player which join the table * @param amount Amount of money which player bring to the table * @param playerUrl URL for the player avatar. * @param points Point which player account has * @param stars Stars which player account has */ public async onSit( playerId: number, seat: number, playerName: string, amount: number, playerUrl: string, points: number, stars: number) { this.logGameEvent("Player " + playerId + " sit on the seat " + seat); const places = this.places(); const initialStatus = !this.paused() ? 0 : (8 /* IsParticipatingStatus */ + 16 /* IsInGame */); const playerModel = new TablePlaceModel({ Bet: null, Cards: null, Money: amount, PlayerId: playerId, PlayerName: playerName, PlayerUrl: playerUrl, Points: points, Seat: seat, Stars: stars, Status: initialStatus, }); this.tablePlaces.sit(seat, playerModel); this.refreshPlaces(); this.actionBlock.updateNeedBB(); this.actionBlock.updateBlocks(); if (playerName === this.currentLogin()) { const api = new Game(host); await api.setTableParameters(this.tableId, !settings.autoHideCards()); } } public onStandup(playerId) { /// /// Standup player from the table. /// Id of the player which join the table /// const places = this.places(); let lastAmount = 0; for (const p in places) { if (!places.hasOwnProperty(p)) { continue; } const currentPlayer = places[p]; if (currentPlayer.PlayerId() === playerId) { if (currentPlayer.WasInGame()) { this.actionsCount(Math.max(0, this.actionsCount() - 1)); } lastAmount = currentPlayer.Money(); this.tablePlaces.standup(currentPlayer.Seat()); break; } } // If we no longer on the table then leave it. if (this.myPlayer() === null) { this.minimalPlayerBuyIn(lastAmount); this.updateCurrentCombination(); } this.refreshPlaces(); this.actionBlock.updateNeedBB(); this.actionBlock.updateBlocks(); } public onFrozen() { this.frozen(true); this.clearTimer(); } public onUnfrozen() { this.frozen(false); this.startTimer(); } public onOpened() { this.pushCallback(() => { this.opened(true); }); } public onClosed() { this.pushCallback(() => { this.opened(false); this.tablePlaces.clear(); this.pots([]); this.tableCards.clear(); }); } public onPaused() { this.paused(true); this.pauseDate(new Date().valueOf()); this.updatePauseDescription(); } public onResumed() { this.clearPauseMessage(); } public onFinalTableCardsOpened(cards: number[]) { this.handHistory.onFinalTableCardsOpened(cards); } public onTableBetParametersChanged(smallBind: number, bigBlind: number, ante: number) { this.setTableBetingParametersNextGame(smallBind, bigBlind, ante); } /** * Notifies that table is tournament table. * @param tournamentId Id of the tournament to which table becomes belonging. */ public onTableTournamentChanged(tournamentId: number | null) { throw new Error("TableView.onTableTournamentChanged not implemented"); } /** * Informs about current state on the table. * @param players Array of PlayerStatusInfo objects which describe current status of the players * @param pots Array of pots on the table. * @param cards Cards open on the table * @param dealerSeat Current dealer seat. 0 if no game on the table. * @param buyIn Minimum amount of money which player should bring on the table. * @param baseBuyIn Base amount in BB for joining table under normal conditions. * 20*baseBuyIn is maximum amount which player could bring-in. * @param leaveTime Time when player leave table last time. * @param timePass Time in second which is pass from last player turn. * @param currentPlayerId Id of the current player in the game. * @param lastRaise Amount of last raise in the game. * @param gameId Id of the game * @param authenticated Value indicating whether current user is authenticated or not. * @param actionsCount Count of actions which was performed by the player. * @param frozen A value indicating whether game on the table is frozen or not. * @param opened A value indicating whether the table is opened or not. * @param pauseData Unix time when game was paused. If game not paused, then null. * @param lastMessageId Last id of the message. */ public onTableStatusInfo( players: PlayerStatusInfo[], pots: number[], cards: string, dealerSeat: number, buyIn: number, baseBuyIn: number, leaveTime, timePass: number, currentPlayerId: number, lastRaise: number, gameId: number, authenticated: boolean, actionsCount: number, frozen: boolean, opened: boolean, pauseDate: number, lastMessageId: number) { const tablePlayers = [] as TablePlayer[]; for (let i = 0; i < players.length; i++) { const p = (players[i] as any) as TablePlayer; tablePlayers.push(p); } this.lastMessageId = lastMessageId; this.frozen(frozen); this.opened(opened); this.paused(pauseDate != null); this.pauseDate(pauseDate); this.updatePauseDescription(); this.queue.clear(); if (opened) { this.gameId(gameId); this.actionsCount(actionsCount); const gameFinished = gameId === 0 || gameId === null; this.gameFinished(gameFinished); this.gameStarted(!gameFinished); this.prizesDistributed(this.gameFinished()); this.tablePlaces.setPlayers(players); if (this.activePlayersCount() <= 1) { this.gameFinished(true); } const cardsArr = decodeCardsArray(cards); this.setCards(cardsArr); this.setButtons(dealerSeat); this.actionBlock.buttonsEnabled(true); this.actionBlock.showCardsEnabled(true); this.actionBlock.showHoleCard1Enabled(true); this.actionBlock.showHoleCard2Enabled(true); this.actionBlock.dealsAllowed(true); timeService.setTimeout(() => { this.actionBlock.updateBounds(); }, 500); this.setCurrent(currentPlayerId); this.minimalPlayerBuyIn(buyIn); this.minimalBuyIn(baseBuyIn); this.pots(pots); this.refreshPlaces(); this.clearTimer(); this.startTimer(timePass); this.cardsReceived = true; if (cards != null) { if (cards.length === 0) { lastRaise = Math.max(lastRaise, this.bigBlind()); } } this.lastRaise(lastRaise); this.currentRaise(this.minimumRaiseAmount()); this.actionBlock.isRaise(lastRaise > 0); this.actionBlock.isCheck(this.checkOrCallAmount() === 0); this.actionBlock.checkOrCallAmount(this.checkOrCallAmount()); this.actionBlock.resetWaitBB(); this.actionBlock.processing(false); this.actionBlock.updateBlocks(); const myself = this.myPlayer(); if (myself != null) { this.actionBlock.updateAutomaticActionsText(myself.Money(), this.maximumBet() - this.myBet()); } this.updateCurrentCombination(); } else { this.tablePlaces.clear(); this.pots([]); this.tableCards.clear(); } this.cleanTableAfterGameFinish(); this.connecting(false); this.log("Finishing connecting to table " + this.tableId); } /** * Start the game * @param gameId Id of the player which join the table * @param players Players in the game * @param actions Actions in the game * @param dealerSeat Seat where dealer sitting */ public onGameStarted( gameId: number, players: GamePlayerStartInformation[], actions: GameActionStartInformation[], dealerSeat: number) { this.queue.pushCallback(() => { this.onGameStartedCore(gameId, players, actions, dealerSeat); this.handHistory.onGameStarted(gameId, players, actions, dealerSeat); }); const isInGame = players.some((player) => player.PlayerId === authManager.loginId()); if (!isInGame) { this.startDealCards(); } } public onGameFinished(gameId: number, winners: GameWinnerModel[], rake: number) { /// /// Start the game. /// Id of the player which join the table /// /// /// Amount of rake paid. /// const self = this; this.queue.wait(this.animationSettings.finishGamePrePause); if (this.pots().length === 0 && winners.length > 0) { this.onMoveMoneyToPot(winners.map((winner) => winner.Amount)); } if (debugSettings.game.singleSidePots) { this.queue.pushCallback(() => { this.logGameEvent("Game finished"); self.gameFinished(true); this.actionBlock.expanded(false); self.gameStarted(false); self.gamePlayers([]); const places = self.places(); self.finishAnimation(places); let needHightlightCards = true; const activePlayersCount = this.activePlayersCount(); this.logGameEvent("Active players count", activePlayersCount); needHightlightCards = activePlayersCount > 1; if (needHightlightCards) { self.tableCards.CardsHightlighted(true); } const c = this.calculateWinnerAmount(places, winners, needHightlightCards); if (self.soundEnabled) { soundManager.playWinChips(); } self.combinations(c); self.setButtons(0); self.cardsReceived = false; self.actionBlock.buttonsEnabled(false); self.actionBlock.dealsAllowed(false); self.setCurrent(0); self.pots([]); self.refreshPlaces(); self.clearTimer(); self.gameId(null); self.handHistory.onGameFinished(gameId, winners, rake); self.saveHandHistory(); this.enableInjectPlayerCards = true; }); this.queue.waitWithInterruption(this.animationSettings.cleanupTableTimeout); } else { this.queue.pushCallback(() => { this.logGameEvent("Game finished"); self.gameFinished(true); this.actionBlock.expanded(false); self.gameStarted(false); self.gamePlayers([]); const places = self.places(); self.finishAnimation(places); const activePlayersCount = this.activePlayersCount(); this.logGameEvent("Active players count", activePlayersCount); const needHightlightCards = activePlayersCount > 1; if (needHightlightCards) { self.tableCards.CardsHightlighted(true); } self.setButtons(0); self.actionBlock.buttonsEnabled(false); self.actionBlock.dealsAllowed(false); self.setCurrent(0); self.clearTimer(); self.gameId(null); }); this.queue.pushCallback(() => { const places = self.places(); const activePlayersCount = this.activePlayersCount(); const needHightlightCards = activePlayersCount > 1; this.logGameEvent("Distribute pots: ", this.pots().slice()); const potsCount = winners.reduce((prev, current) => Math.max(prev, current.Pot), 0); for (let potNumber = 1; potNumber <= potsCount; potNumber++) { // Inject wait first, since next callback will override that. this.queue.injectWaitWithInterruption(2000); this.queue.injectCallback(() => { this.logGameEvent("Distribute pot " + potNumber); // Clear previous win amount for all players so all animation will play correctly. for (const place of this.places()) { if (place) { place.WinAmount(null); place.CardsHightlighted(false); } } const potWinners = winners.filter((winner) => winner.Pot === potNumber); const c = this.calculateWinnerAmount(places, potWinners, needHightlightCards); if (this.soundEnabled) { soundManager.playWinChips(); } this.combinations(c); this.handHistory.onPotDistributed(this.gameId(), potNumber, winners); this.refreshPlaces(); // Remove processed pot. const pots = this.pots(); pots.pop(); this.pots(pots); this.pots.notifySubscribers(); this.logGameEvent("Removing pot", this.pots().slice()); this.refreshPlaces(); }); } }); this.queue.pushCallback(() => { // tslint:disable-next-line:no-console console.log("Finishing the game"); self.cardsReceived = false; self.pots([]); self.handHistory.onGameFinished(gameId, winners, rake); self.saveHandHistory(); this.enableInjectPlayerCards = true; }); // this.queue.wait(this.animationSettings.cleanupTableTimeout - 8000); } this.queue.pushCallback(() => { this.enableInjectPlayerCards = false; self.cleanTableAfterGameFinish(); self.proposeRebuyOrAddon(); self.displayRebuyOrAddonTime(); }); } public onPlayerStatus(playerId: number, status: number) { this.queue.pushCallback(() => { this.onPlayerStatusCore(playerId, status); }); } public onPlayerCards(playerId: number, cards: number[]) { const self = this; if (this.enableInjectPlayerCards) { this.queue.injectCallback(() => { this.onPlayerCardsCore(playerId, cards); this.ensureCardsOpened(playerId); }); } else { this.queue.pushCallback(() => { if (!self.cardsReceived) { if (playerId === authManager.loginId()) { self.startDealCards(); this.queue.pushCallback(() => { self.onPlayerCardsCore(playerId, cards); }); } else { self.onPlayerCardsCore(playerId, cards); } } else { self.onPlayerCardsCore(playerId, cards); } }); } } public onPlayerCardOpened(playerId: number, cardPosition: number, cardValue: number) { const self = this; if (this.enableInjectPlayerCards) { this.queue.injectCallback(() => { this.onPlayerCardOpenedCore(playerId, cardPosition, cardValue); }); } else { this.queue.pushCallback(() => { self.onPlayerCardOpenedCore(playerId, cardPosition, cardValue); }); } } public onPlayerCardsMucked(playerId: number) { this.queue.pushCallback(() => { const currentPlayer = this.tablePlaces.getPlaceByPlayerId(playerId); this.foldCardsForPlayer(currentPlayer, true, this.animationSettings.foldAnimationTimeout / 2); }); } public onBet(playerId: number, type: number, amount: number, nextPlayerId: number) { /// /// Do bet. /// Id of the player which join the table /// /// Type of the bet.
/// 0 - Blind /// 1 - Ante /// 2 - Check/Call /// 3 - Bet/Raise /// 4 - Fold /// 5 - ForcedBet /// 6 - ReturnMoney /// /// Amount of money which player put in the bet /// Id of the player which join the table ///
this.enableInjectPlayerCards = false; this.queue.pushCallback(() => { this.onBetCore(playerId, type, amount, nextPlayerId); this.handHistory.onBet(playerId, type, amount, nextPlayerId); }); } public onMoveMoneyToPot(amount: number[]) { // Trigger animation. const self = this; this.actionBlock.buttonsEnabled(false); this.queue.wait(this.animationSettings.movingMoneyToPotPrePause); this.queue.pushCallback(() => { self.startMovingChipsToPotAnimation(); }); this.queue.wait(this.animationSettings.movingMoneyToPotAnimationTimeout); this.queue.pushCallback(() => { self.logGameEvent("Updating pots"); self.setPots(amount); self.logGameEvent("Clearing bets"); const places = self.places(); self.places().forEach(function (value) { value.collectBet(); }); self.refreshPlaces(); self.lastRaise(0); self.currentRaise(self.minimumRaiseAmount()); self.actionBlock.isRaise(false); self.actionBlock.buttonsEnabled(true); }); } public onMoneyAdded(playerId: number, amount: number) { const self = this; const places = this.places(); places.forEach(function (value) { if (value.PlayerId() === playerId) { // Add money only if player not currently eligible to be in game. // And when no game on the table. if (!value.IsInGameStatus() || self.gameId() === null) { value.Money(value.Money() + amount); } } }); this.refreshPlaces(); } public onMoneyRemoved(playerId: number, amount: number) { const self = this; const places = this.places(); places.forEach(function (value) { if (value.PlayerId() === playerId) { // Add money only if player not currently eligible to be in game. // And when no game on the table. if (!value.IsInGameStatus() || self.gameId() === null) { value.Money(value.Money() - amount); } } }); this.refreshPlaces(); } public setPots(amount: number[]) { // Add first number to the last pot. const currentPots = this.pots(); const skipCollectingPot = (currentPots.length === amount.length && currentPots[currentPots.length - 1] === amount[currentPots.length - 1]); if (!skipCollectingPot) { this.handHistory.onMoveMoneyToPot(amount); } let main = amount[0]; if (this.pots().length === 0) { // First pot created. this.handHistory.onPotCreated(1, main); } else { // Update last pot main = amount[this.pots().length - 1]; if (currentPots[this.pots().length - 1] !== main) { this.handHistory.onPotUpdated(currentPots.length, main); } } // Append other amounts to the end of stack. // and generate split pots for (let i = Math.max(1, currentPots.length); i < amount.length; i++) { if (currentPots[i] !== amount[i]) { this.handHistory.onPotCreated(i + 1, amount[i]); } } this.pots(amount); } public setCards(cards: number[]) { /// /// Set cards on the table. /// Array of table cards to display /// this.tableCards.setCards(cards); } /** * Gets current seat number for offline table */ public getCurrentOfflineTableSeat(): number { const self = this; let seatNumber = 0; app.requireAuthentication().then(function (authenticated) { if (authenticated) { const currentPlayer = self.currentLogin(); seatNumber = parseInt(currentPlayer.replace("Игрок", ""), 10); // tslint:disable-next-line:no-console console.log("seat number " + seatNumber); } }); return seatNumber; } public showSitPrompt(seat: number) { if (this.sitting) { return; } this.sitting = true; const self = this; app.requireAuthentication().then(function (authenticated) { if (authenticated) { const currentPlayer = self.myPlayer(); if (currentPlayer === null) { app.joinTablePopup.tableView(self); app.joinTablePopup.seatNumber(seat); app.executeCommand("popup.joinTable"); } } }).always(() => { self.sitting = false; }); } public async rebuy() { const self = this; const tournamentView = this.tournament(); const tapi = new Tournament(host); try { const data = await tapi.rebuy(tournamentView.tournamentId, false); if (data.Status === "Ok") { self.hasPendingMoney(true); if (!self.hasPlayersWithoutMoney()) { // self.clearNotification(); } SimplePopup.display(_("tableMenu.rebuy"), _("tableMenu.rebuySuccess")); } else { SimplePopup.display(_("tableMenu.rebuy"), _("errors." + data.Status)); } } catch (e) { SimplePopup.display(_("tableMenu.rebuy"), _("tableMenu.rebuyError")); } } public async doubleRebuy() { const self = this; const tournamentView = this.tournament(); const tapi = new Tournament(host); try { const data = await tapi.rebuy(tournamentView.tournamentId, true); if (data.Status === "Ok") { self.hasPendingMoney(true); if (!self.hasPlayersWithoutMoney()) { // self.clearNotification(); } SimplePopup.display(_("tableMenu.doublerebuy"), _("tableMenu.doublerebuySuccess")); } else { SimplePopup.display(_("tableMenu.doublerebuy"), _("errors." + data.Status)); } } catch (e) { SimplePopup.display(_("tableMenu.doublerebuy"), _("tableMenu.doublerebuyError")); } } public async addon() { const self = this; const tournamentView = this.tournament(); const tapi = new Tournament(host); try { const data = await tapi.addon(tournamentView.tournamentId); if (data.Status === "Ok") { self.hasPendingMoney(true); if (!self.hasPlayersWithoutMoney()) { // self.clearNotification(); } // self.tournament().addonCount(self.tournament().addonCount() + 1); SimplePopup.display(_("tableMenu.addon"), _("tableMenu.addonSuccess")); } else { SimplePopup.display(_("tableMenu.addon"), _("errors." + data.Status)); } } catch (e) { SimplePopup.display(_("tableMenu.addon"), _("tableMenu.addonError")); } } public showRebuyPrompt() { const self = this; const tdata = this.tournament().tournamentData(); const prompt = [_("table.rebuyPrompt", { price: tdata.RebuyFee + tdata.RebuyPrice })]; app.prompt(_("table.rebuyPromptCaption"), prompt).then(function () { self.rebuy(); }); } public showDoubleRebuyPrompt() { const self = this; const tdata = this.tournament().tournamentData(); const prompt = [_("table.doubleRebuyPrompt", { price: 2 * (tdata.RebuyFee + tdata.RebuyPrice) })]; app.prompt(_("table.doubleRebuyPromptCaption"), prompt).then(function () { self.doubleRebuy(); }); } public showAddonPrompt() { const self = this; const tdata = this.tournament().tournamentData(); const prompt = [_("table.addonPrompt", { price: tdata.AddonFee + tdata.AddonPrice })]; app.prompt(_("table.addonPromptCaption"), prompt).then(function () { self.addon(); }); } public async sit(seat: number, amount: number, ticketCode: string) { const self = this; const gameApi = new Game(host); try { const data = await gameApi.sit(self.tableId, seat, amount, ticketCode); // report on successfull seating. if (data.Status === "OperationNotValidAtThisTime") { return { minimalAmount: undefined, status: data.Status, success: false, }; } if (data.Status !== "Ok") { return { minimalAmount: data.MinimalAmount, status: data.Status, success: false, }; } else { return { minimalAmount: data.MinimalAmount, status: data.Status, success: true, }; } } catch (e) { return { minimalAmount: undefined, status: "NotSufficiendFunds", success: false, }; } } /** * Shows stand up prompt */ public async showStandupPrompt() { if (this.myPlayer() == null) { return; } const tournament = this.tournament(); let messages: string[]; let caption: string; if (tournament == null) { const hasWin = this.myPlayer().Money() > 0 || this.myPlayerInGame(); const promptMesssage = hasWin ? "table.standupPrompt" : "table.standupPromptWithoutWin"; caption = hasWin ? "table.standupPromptCaption" : "table.leave"; messages = [_(promptMesssage)]; } else { if (tournament.finishedPlaying()) { return; } caption = "table.leave"; messages = [_("table.standupTournamentPrompt")]; } const approved = await app.promptAsync(_(caption), messages); if (approved) { if (this.tournament() == null) { this.standup(); } } } public showStandupConfirm() { let messages: string[]; messages = [_("table.standupSuccessMessage")]; const title = _("table.standupSuccessTitle"); SimplePopup.display(title, messages); } public async standup() { const self = this; const gameApi = new Game(host); const data = await gameApi.standup(this.tableId); // report on successfull seating. if (data.Status === "AuthorizationError") { self.reportApiError("Ошибка авторизации"); } else if (data.Status === "Ok" && appConfig.game.seatMode) { self.showStandupConfirm(); } } public showAddBalancePrompt(seat: number, ticketCode: string) { /// /// Shows add balance prompt. /// Seat where player willing to join /// Table view where to join /// const targetPlayer = this.tablePlaces.getPlaceBySeat(seat); const playerId = targetPlayer.PlayerId(); const amount = prompt("Укажите сумму для пополнения счета?", "1000"); if (amount) { this.addBalance(Number(amount), ticketCode); } } public async addBalance(amount: number, ticketCode: string) { const places = this.places(); const targetPlayer = this.myPlayer(); const gameApi = new Game(host); try { const data = await gameApi.addBalance(this.tableId, amount, ticketCode); // report on successfull seating. if (data.Status !== "Ok") { throw new Error(data.Status); } if (data.Status === "Ok") { if (this.myPlayerInGame()) { this.hasPendingMoney(true); } } return amount; } catch (e) { throw new Error("OperationNotAllowed"); } } public addMessage(messageId: number, sender: string, message: string) { const messageExists = this.messages().filter((m) => m.messageId === messageId).length > 0; if (!messageExists) { const m = new PlayerMessage(messageId, sender, message); this.messages.unshift(m); while (this.messages().length > 100) { this.messages.pop(); } const messages = this.messages(); this.messages([]); this.messages(messages); } if (this.lastMessageId >= messageId) { return; } const player = this.getPlayerByLogin(sender); if (player != null) { player.displayChatMessage(message); } } public addSystemMessage(messageId: number, message: string) { if (this.lastMessageId >= messageId && messageId !== 0) { return; } const m = new SystemMessage(messageId, message); this.systemMessages.unshift(m); while (this.systemMessages().length > 100) { this.systemMessages.pop(); } } public updateMessage(messageId: number, sender: string, message: string) { // TODO: Implement updating messages. } public updateSystemMessage(messageId: number, message: string) { // TODO: Implement updating messages. } /** * Sends mesage to the table chat. */ public async sendMessage() { const chatApi = new Chat(host); const data = await chatApi.send(this.tableId, this.chatMessage()); this.chatMessage(""); } public async fold() { const self = this; this.actionBlock.buttonsEnabled(false); const gameApi = new Game(host); if (appConfig.game.useSignalR) { // TODO: We should provide notification, which will return any error from the server. connectionService.currentConnection.connection.Game.server.fold(this.tableId); } else { try { const data = await gameApi.fold(this.tableId); if (data.Status === "OperationNotValidAtThisTime") { self.turnRecovery(); return; } if (data.Status === "OperationNotValidWhenTableFrozen") { self.turnRecovery("table.tableFreezed"); return; } if (data.Status !== "Ok") { self.reportApiError(data.Status); } } catch (e) { self.turnRecovery(); } } } public async checkOrCall() { const self = this; this.actionBlock.buttonsEnabled(false); const gameApi = new Game(host); if (appConfig.game.useSignalR) { // TODO: We should provide notification, which will return any error from the server. connectionService.currentConnection.connection.Game.server.checkOrCall(this.tableId); } else { try { const data = await gameApi.checkOrCall(this.tableId); if (data.Status === "OperationNotValidAtThisTime") { self.turnRecovery(); return; } if (data.Status === "OperationNotValidWhenTableFrozen") { self.turnRecovery("table.tableFreezed"); return; } if (data.Status !== "Ok") { self.reportApiError(data.Status); } } catch (e) { self.turnRecovery(); } } } public async betOrRaise() { const self = this; this.actionBlock.buttonsEnabled(false); const gameApi = new Game(host); const amount: number = this.currentRaise() - this.currentBet(); if (appConfig.game.useSignalR) { // TODO: We should provide notification, which will return any error from the server. connectionService.currentConnection.connection.Game.server.betOrRaise(this.tableId, amount); } else { try { const data = await gameApi.betOrRaise(this.tableId, amount); if (data.Status === "OperationNotValidAtThisTime") { self.turnRecovery(); return; } if (data.Status === "OperationNotValidWhenTableFrozen") { self.turnRecovery("table.tableFreezed"); return; } if (data.Status !== "Ok") { self.reportApiError(data.Status); } } catch (e) { self.turnRecovery(); } } } /** * Show cards for the current player. */ public async showCards() { this.actionBlock.showCardsEnabled(false); const gameApi = new Game(host); try { const data = await gameApi.showCards(this.tableId); if (data.Status !== "Ok") { this.reportApiError(data.Status); } } catch (e) { if (app.currentPopup !== SlowInternetService.popupName) { SimplePopup.display(_("table.turn"), _("table.connectionError", { tableName: this.tableName() })); } this.actionBlock.showCardsEnabled(true); } } /** * Muck cards for the current player. */ public async muckCards() { this.actionBlock.showCardsEnabled(false); const gameApi = new Game(host); try { const data = await gameApi.muck(this.tableId); if (data.Status !== "Ok") { this.reportApiError(data.Status); } } catch (e) { this.actionBlock.showCardsEnabled(true); } } public toggleCards() { const my = this.myPlayer(); if (my.IsCardsOpened()) { this.muckCards(); } else { this.showCards(); } } public showFirstCard() { return this.showCard(0); } public showSecondCard() { return this.showCard(1); } /** * Show cards for the current player. */ public async showCard(cardPosition: 0 | 1) { const showCardVariable = cardPosition === 0 ? this.actionBlock.showHoleCard1Enabled : this.actionBlock.showHoleCard2Enabled; showCardVariable(false); const gameApi = new Game(host); try { const data = await gameApi.showHoleCard(this.tableId, cardPosition); if (data.Status !== "Ok") { this.reportApiError(data.Status); } } catch (e) { if (app.currentPopup !== SlowInternetService.popupName) { SimplePopup.display(_("table.turn"), _("table.connectionError", { tableName: this.tableName() })); } showCardVariable(true); } } /** * Cards opening on the table. * @param cards Cards which was opened on the table. */ public onOpenCards(cards: number[]) { const self = this; this.queue.pushCallback(() => { self.actionsCount(0); const currentCardsOpened = self.tableCards.tableCards().length; const myPlayer = this.myPlayer(); if (myPlayer) { myPlayer.cardsOverlayVisible(true); } self.tableCards.openCards(cards); self.handHistory.onOpenCards(cards); if (currentCardsOpened === 0 && cards.length === 3) { self.handHistory.onFlop(cards[0], cards[1], cards[2]); self.actionBlock.dealsAllowed(true); soundManager.playFlopCards(); } if (currentCardsOpened === 3 && cards.length === 4) { self.handHistory.onTurn(cards[3]); self.actionBlock.dealsAllowed(true); soundManager.playTurn(); } if (currentCardsOpened === 4 && cards.length === 5) { self.handHistory.onRiver(cards[4]); self.actionBlock.dealsAllowed(true); soundManager.playRiver(); } if (currentCardsOpened === 3 && cards.length === 5) { self.handHistory.onTurn(cards[3]); self.handHistory.onRiver(cards[4]); // soundManager.playAllIn(); } if (currentCardsOpened === 0 && cards.length === 5) { self.handHistory.onFlop(cards[0], cards[1], cards[2]); self.handHistory.onTurn(cards[3]); self.handHistory.onRiver(cards[4]); // soundManager.playAllIn(); } self.actionBlock.resetAutomaticAction(); self.actionBlock.updateAdditionalButtons(); }); if (self.soundEnabled) { soundManager.playFlop(); } this.queue.wait(this.animationSettings.showCardsTimeout); this.queue.pushCallback(() => { self.updateCurrentCombination(); self.tableCards.clearAnimation(); }); } public async comeBack() { const self = this; const gameApi = new Game(host); const data = await gameApi.comeBack(this.tableId); if (data.Status === "OperationNotValidAtThisTime") { return; } if (data.Status !== "Ok") { self.reportApiError(data.Status); } } public async sitOut() { const self = this; const gameApi = new Game(host); const data = await gameApi.sitOut(this.tableId); if (data.Status === "OperationNotValidAtThisTime") { return; } if (data.Status !== "Ok") { self.reportApiError(data.Status); } } /** * Set new table betting parameters. * @param smallBlind Small blind * @param bigBlind Big blind * @param ante Ante */ public setTableBetingParameters(smallBlind: number, bigBlind: number, ante: number) { this.bigBlind(bigBlind); this.smallBlind(smallBlind); this.ante(ante); } /** * Set new table betting parameters. * @param smallBlind Small blind * @param bigBlind Big blind * @param ante Ante */ public setTableBetingParametersNextGame(smallBlind: number, bigBlind: number, ante: number) { this.nextGameBigBlind(bigBlind); this.nextGameSmallBlind(smallBlind); this.nextGameAnte(ante); this.changeBetParametersNextGame(true); } public showPlayerParameters() { // tablePlayerParameterSelector.showPlayerParameters(this); } /** * Displays last hand in the history */ public showPreviousHand() { app.handHistoryPopup.tableView(this); app.showPopup("handHistory"); } public rotate(offset: number) { const myself = this.myPlayer(); if (myself == null) { return; } const currentOffset = this.tablePlaces.getRealOffset(myself.Seat()); if (currentOffset === offset) { return; } const self = this; app.prompt(_("table.changeplace"), [_("table.doyouwantchangeplace")]).then(function () { self.tablePlaces.rotate(offset - currentOffset); }); } public hasPlayersWithoutMoney() { return this.places().some((place) => place.Money() === 0); } public proposeRebuyOrAddon() { const tournamentView = this.tournament(); if (tournamentView !== null && this.myPlayer() !== null && this.myPlayer().Money() === 0 && !this.hasPendingMoney()) { if (tournamentView.rebuyAllowed()) { this.proposeBuyRebuy(); } if (tournamentView.addonAllowed() && tournamentView.addonCount() === 0) { this.proposeBuyAddon(); } } } /** * Show table notification */ public showNotification(message: string) { this.notification(message); this.hasNotification(true); } /** * Show table notification for specific period of time */ public showNotificationWithDelay(message: string, duration: number) { this.clearNotification(); this.showNotification(message); this.notificationHandleTimeout = timeService.setTimeout(() => { this.clearNotification(); }, duration); } public pushCallback(callback: () => void) { this.queue.pushCallback(callback); } /* * Display counter which indicates how much time left to buy addon or rebuy * if player lose game. */ private displayRebuyOrAddonTime() { const self = this; const tournamentView = this.tournament(); // Display notification about rebuy or addon prompt // only for tournament games. if (tournamentView === null || tournamentView === undefined) { return; } // Display notification about rebuy or addon prompt // only if rebuy or addon allowed. if (!tournamentView.rebuyAllowed() && !tournamentView.addonAllowed()) { return; } if (this.hasPlayersWithoutMoney()) { // This time should be 500ms bigger then the actual time specified // on the server, to accomodate for the 500ms update period. this.displayingRebuyAddonNotification = true; const totalDuration = 10.5; let i = 0; let messageTemplate: string; let popupCaption: string; if (tournamentView.rebuyAllowed()) { messageTemplate = "table.waitingForOtherPlayersRebuy"; popupCaption = "table.tournamentGameFinishedRebuyCaption"; } else if (tournamentView.addonAllowed()) { messageTemplate = "table.waitingForOtherPlayersAddon"; popupCaption = "table.tournamentGameFinishedAddonCaption"; } else { // tslint:disable-next-line:no-console console.error("Unknown state during display rebuy of addon time"); } this.notificationHandleInterval = timeService.setInterval(() => { const secondsLeft = Math.floor(totalDuration - (i / 2)); self.showNotification(_(messageTemplate, { time: secondsLeft })); app.customPopup.title(_(popupCaption, { time: secondsLeft })); i++; if (i >= totalDuration * 2) { self.clearNotification(); if (app.currentPopup === "custom") { app.closePopup(); } } }, 500); } } /** * Propose buying rebuy or double rebuy */ private proposeBuyRebuy() { const self = this; const player = this.myPlayer(); if (player === null) { return; } if (player.Money() === 0 && !this.hasPendingMoney()) { // Display popup with three buttons. const result = $.Deferred(); app.promptEx( _("table.tournamentGameFinishedCaption"), [_("table.tournamentGameFinishedRebuy")], [_("table.rebuy"), _("table.doubleRebuy"), _("common.cancel")], [() => 1, () => 2, () => { /* nothing */ }]) .then(function (value: number) { if (value === 1) { self.showRebuyPrompt(); } if (value === 2) { self.showDoubleRebuyPrompt(); } result.resolve(); }, function () { result.reject(); }); } } /** * Propose buying addon */ private proposeBuyAddon() { const self = this; const player = this.myPlayer(); if (player == null) { // tslint:disable-next-line:no-console console.warn("Proposing but addon when no active player"); return; } if (player.Money() === 0 && !this.hasPendingMoney() && this.tournament().addonCount() === 0) { // Display popup with two buttons. const result = $.Deferred(); app.promptEx( _("table.tournamentGameFinishedCaption"), [_("table.tournamentGameFinishedAddon")], [_("table.addon"), _("common.cancel")], [() => 1, () => { /* nothing */ }]) .then(function (value: number) { self.showAddonPrompt(); result.resolve(); }, function () { result.reject(); }); } } private onBetCore(playerId: number, type: number, amount: number, nextPlayerId: number) { this.logGameEvent("Doing bet of type " + type); const places = this.places(); const myself = this.myPlayer(); const currentPlayer = this.tablePlaces.getPlaceByPlayerId(playerId); if (currentPlayer === null) { this.logGameEvent("No player sitting on the table."); return; } this.updateBetsAndMoney(currentPlayer, type, amount); const isMyTurn = myself !== null && nextPlayerId === myself.PlayerId(); // Don's enable buttons until cards would be recived for players. if (this.cardsReceived) { this.actionBlock.buttonsEnabled(true); if (isMyTurn) { this.onMyTurn.dispatch(this.tableId); } } const forcedBets = type === 1 || type === 5; if (!(forcedBets && nextPlayerId === 0)) { this.setCurrent(nextPlayerId); if (isMyTurn) { // This will trigger reevaluation of the minimumRaiseAmount this.currentRaise(this.minimumRaiseAmount()); } else { this.currentRaise(null); } } // this.refreshPlaces(); const userActions = type === 3 || type === 4 || type === 2; if (userActions) { this.actionsCount(this.actionsCount() + 1); } const allBetsRounded = this.allBetsRounded(); this.logGameEvent("All bets rounded", allBetsRounded); if (allBetsRounded) { this.actionBlock.buttonsEnabled(false); this.actionBlock.dealsAllowed(false); } if (myself !== null) { const requiredBet = this.maximumBet() - this.myBet(); this.actionBlock.updateSupportDirectAmountStatus(requiredBet); if (nextPlayerId === myself.PlayerId()) { if (!allBetsRounded && userActions) { this.actionBlock.performAutomaticActions(); this.actionBlock.resetAutomaticAction(); } } this.actionBlock.updateAutomaticActionsText(myself.Money(), requiredBet); this.actionBlock.updateAdditionalButtons(); } if (nextPlayerId === authManager.loginId()) { this.logGameEvent("Player turn approached"); this.tablePlaces.placesRefreshTrigger(); } this.logGameEvent("Action block status: MMB:.", this.actionBlock.mainButtonsBlockVisible(), " BEn:", this.actionBlock.buttonsEnabled(), " TurnEn:", this.actionBlock.turnEnabled(), " TTurnEn:", this.turnEnabled(), " CP:", this.currentPlayer(), " DA:", this.actionBlock.dealsAllowed(), " InGame:", this.actionBlock.myPlayerInGame()); this.clearTimer(); this.startTimer(); } private startMovingChipsToPotAnimation() { this.logGameEvent("Moving chips to center"); const places = this.places(); this.places().forEach(function (value) { value.IsBetAnimationLocked(false); value.IsMovingBetToPot(true); }); // this.refreshPlaces(); } /** * Start the game * @param gameId Id of the player which join the table * @param players Players in the game * @param actions Actions in the game * @param dealerSeat Seat where dealer sitting */ private onGameStartedCore( gameId: number, players: GamePlayerStartInformation[], actions: GameActionStartInformation[], dealerSeat: number) { this.gameFinished(false); this.prizesDistributed(false); this.currentGameId(gameId); this.gameId(gameId); this.hasPendingMoney(false); this.actionBlock.isRaise(true); this.actionBlock.expanded(false); this.actionBlock.showCardsEnabled(true); this.actionBlock.showHoleCard1Enabled(true); this.actionBlock.showHoleCard2Enabled(true); if (this.changeBetParametersNextGame()) { this.changeBetParametersNextGame(false); this.setTableBetingParameters(this.nextGameSmallBlind(), this.nextGameBigBlind(), this.nextGameAnte()); } this.actionBlock.updateBounds(); this.actionsCount(0); this.tableCards.clear(); this.pots([]); this.logGameEvent("Game started"); if (this.displayingRebuyAddonNotification) { this.clearNotification(); } const combinations: string[] = []; if (runtimeSettings.game.clearAutoFoldOrCheckOnNewGame) { this.actionBlock.autoFoldOrCheck(false); } this.cardsReceived = false; this.combinations(combinations); this.gamePlayers(players.map((item) => item.PlayerId)); this.places().forEach(function (value) { value.prepareForNewGame(); players.forEach(function (pvalue) { if (pvalue.PlayerId === value.PlayerId()) { value.initializeForNewGame(pvalue.Money); } }); if (players.every((pvalue) => pvalue.PlayerId !== value.PlayerId())) { value.WasInGame(null); } }); this.setButtons(dealerSeat); if (this.soundEnabled) { soundManager.playDealCards(); } this.refreshPlaces(); this.lastRaise(this.bigBlind()); this.actionBlock.resetAutomaticAction(); if (players.some((player) => player.PlayerId === authManager.loginId())) { // Reset Wait BB status since player currently join the game. this.actionBlock.resetWaitBB(); } } private startDealCards() { const self = this; this.queue.pushCallback(() => { self.logGameEvent("Deal cards to players"); self.places().forEach(function (value) { if (value.WasInGame()) { value.startDealCards(); } }); }); this.queue.wait(this.animationSettings.dealCardsTime); this.queue.pushCallback(() => { self.places().forEach(function (value) { value.IsDealCards(false); }); self.clearTimer(); self.startTimer(); }); } private calculateWinnerAmount(places: TablePlaceModel[], winners: GameWinnerModel[], needHightlightCards: boolean) { const c: string[] = []; for (const w in winners) { if (!winners.hasOwnProperty(w)) { continue; } const currentWinner = winners[w]; if (c.indexOf(currentWinner.CardsDescription) === -1) { c.push(currentWinner.CardsDescription); } for (const p in places) { if (!places.hasOwnProperty(p)) { continue; } const currentPlayer = places[p]; if (needHightlightCards) { currentPlayer.CardsHightlighted(true); } if (currentPlayer.PlayerId() === currentWinner.PlayerId) { let winnerCards = [] as number[]; const tableCards = this.tableCards.tableCardsData(); const winnerCombination = currentPlayer.getCombination(tableCards); currentPlayer.WinnerCombination(winnerCombination); if (tableCards != null) { winnerCards = winnerCards.concat(tableCards); } winnerCards = winnerCards.concat(currentPlayer.RawCards()); const handRepresentation = { Cards: [], Suits: [], }; winnerCards.forEach(function (card) { handRepresentation.Cards.push((card % 13) + 2); handRepresentation.Suits.push(1 << (card / 13)); }); if (winnerCards.length === 7 && needHightlightCards) { const rank = HoldemHand.getCardRank(handRepresentation); rank.WinnerCardsSet.forEach((cardIndex) => { if (cardIndex === 5) { currentPlayer.Card1Hightlighted(true); } if (cardIndex === 6) { currentPlayer.Card2Hightlighted(true); } if (cardIndex === 0) { this.tableCards.Card1Hightlighted(true); } if (cardIndex === 1) { this.tableCards.Card2Hightlighted(true); } if (cardIndex === 2) { this.tableCards.Card3Hightlighted(true); } if (cardIndex === 3) { this.tableCards.Card4Hightlighted(true); } if (cardIndex === 4) { this.tableCards.Card5Hightlighted(true); } }); } currentPlayer.Money(currentPlayer.Money() + currentWinner.Amount); if (currentPlayer.WinAmount() === null) { currentPlayer.WinAmount(currentWinner.Amount); } else { currentPlayer.WinAmount(currentPlayer.WinAmount() + currentWinner.Amount); } } } } return c; } private cleanTableAfterGameFinish() { if (this.gameFinished()) { this.prizesDistributed(true); this.tableCards.CardsHightlighted(false); this.setCards([]); this.pots([]); const places = this.places(); places.forEach(function (item) { item.prepareForNewGame(); }); } } private onPlayerStatusCore(playerId: number, status: number) { this.logGameEvent("onPlayerStatus", playerId, status); const self = this; const places = this.places(); places.forEach((p) => { if (p.PlayerId() === playerId) { const saveMask = 16; p.Status(status | (p.Status() & saveMask)); if (playerId === authManager.loginId()) { if (p.IsSitoutStatus()) { self.actionBlock.processing(false); } } } }); this.refreshPlaces(); this.actionBlock.updateNeedBB(); this.actionBlock.updateBlocks(); } private onPlayerCardsCore(playerId: number, cards: number[]) { const self = this; const oldCardsReceived = this.cardsReceived; this.cardsReceived = true; const myPlayer = this.myPlayer(); let isHoleCards = false; if (myPlayer != null) { // If this is first cards received then this is our cards // Allow deals only when your cards is recived. if (!oldCardsReceived) { this.actionBlock.dealsAllowed(true); this.gameStarted(true); isHoleCards = true; this.handHistory.onPlayerHoleCards(playerId, cards); this.logGameEvent("Received hole cards"); this.updateCurrentCombination(); } if (myPlayer.IsCurrent()) { this.currentRaise(this.actionBlock.tableSlider.minimum()); this.actionBlock.buttonsEnabled(true); this.actionBlock.updateAdditionalButtons(); this.actionBlock.performAutomaticActions(); this.actionBlock.resetAutomaticAction(); this.logGameEvent("Player turn on the beginning of the game."); this.onMyTurn.dispatch(this.tableId); } } this.logGameEvent("onPlayerCards", playerId, convertToCards(cards)); const places = this.places(); places.forEach((p) => { if (p.PlayerId() === playerId) { const myself = self.currentLogin() === p.PlayerName(); let couldDisplayOtherCards = myself || self.combinations().length > 0; couldDisplayOtherCards = true; if (couldDisplayOtherCards) { p.setCards(cards); if (!isHoleCards) { p.markCardsOpened(); } } } }); this.refreshPlaces(); if (!isHoleCards) { this.handHistory.onPlayerCards(playerId, cards); } } private onPlayerCardOpenedCore(playerId: number, cardPosition: number, cardValue: number) { this.logGameEvent("onPlayerCardOpenedCore", playerId, cardPosition, cardValue); const places = this.places(); places.forEach((p) => { if (p.PlayerId() === playerId) { p.openCard(cardPosition, cardValue); } }); this.refreshPlaces(); } private ensureCardsOpened(playerId: number) { const places = this.places(); places.forEach((p) => { if (p.PlayerId() === playerId) { p.markCardsOpened(); p.cardsOverlayVisible(false); } }); this.refreshPlaces(); } private updateBetsAndMoney(currentPlayer: TablePlaceModel, type: number, amount: number) { const playerId = currentPlayer.PlayerId(); const myself = this.myPlayer(); const totalBet = (currentPlayer.TotalBet() === null ? 0 : currentPlayer.TotalBet()); const currentBet = currentPlayer.Bet(); const maximumBet = this.maximumBet(); const amountPlaced = amount - currentBet; if (type === 6) { currentPlayer.Money(currentPlayer.Money() + amount); this.handHistory.onReturnMoney(playerId, amount); return; } const newMoney = currentPlayer.Money() - amountPlaced; currentPlayer.Money(newMoney); // If player raise then update last raise amount. // We have to do that before current bet for the player is changed, // Since calculation depends on the amount of last bet. if (type === 3) { const raiseAmount = amount; // Since we have raise in this round, then we no longer could // do bets. this.actionBlock.isRaise(true); // The raise amount could be actually smaller then the last raise amount // because player goes all-in, other players should be forced to pay more // to do the raise. if (raiseAmount > this.lastRaise()) { this.lastRaise(raiseAmount); } } if (type === 4) { this.foldCardsForPlayer(currentPlayer, false, this.animationSettings.foldAnimationTimeout); // Mark player as not in game. // During the new game this flag should be set automatically. currentPlayer.IsInGameStatus(false); } if (type !== 4) { // If player folds then current bet amount should not be changed. currentPlayer.Bet(amount); if (type === 0 || type === 1 || type === 5) { currentPlayer.IsBetAnimationLocked(true); } else { // Lock animation place after timeout. this.queue.wait(this.animationSettings.betAnimationTimeout); this.queue.pushCallback(() => { currentPlayer.IsBetAnimationLocked(true); }); } } // Display action which current player executes. if (type === 4) { // Display fold action currentPlayer.startAction("table.actiontext.fold"); this.handHistory.onFold(playerId); if (this.soundEnabled) { soundManager.playFold(); } } else if (type === 2) { // Display check/call action if (currentPlayer.Money() === 0) { currentPlayer.startAction("table.actiontext.allin"); this.handHistory.onAllIn(playerId, amount); if (this.soundEnabled) { soundManager.playAllIn(); } } else { if (amountPlaced === 0) { currentPlayer.startAction("table.actiontext.check"); this.handHistory.onCheck(playerId, amount); if (this.soundEnabled) { soundManager.playCheck(); } } else { currentPlayer.startAction("table.actiontext.call"); this.handHistory.onCall(playerId, amount); if (this.soundEnabled) { soundManager.playCall(); } } } } else if (type === 3) { // Display bet/raise action if (currentPlayer.Money() === 0) { currentPlayer.startAction("table.actiontext.allin"); this.handHistory.onAllIn(playerId, amount); if (this.soundEnabled) { soundManager.playAllIn(); } } else { if (maximumBet === 0) { currentPlayer.startAction("table.actiontext.bet"); this.handHistory.onBet2(playerId, amount); if (this.soundEnabled) { soundManager.playBet(); } } else { currentPlayer.startAction("table.actiontext.raise"); this.handHistory.onRaise(playerId, amount); if (this.soundEnabled) { soundManager.playRaise(); } } } } } private foldCardsForPlayer(currentPlayer: TablePlaceModel, forceAnimation: boolean, duration: number) { if (currentPlayer === null) { // tslint:disable-next-line:no-console console.warn("Attempt to fold cards for the player"); return; } const myself = this.myPlayer(); // If player folds then cards should be hidden. const currentCards = currentPlayer.Cards(); currentPlayer.startFoldAnimation(); // Current player is not active, but it not yet folded cards. const activePlayersCount = this.activePlayersCount() - 1; const displayFoldedCards = myself !== null && currentPlayer.PlayerId() === myself.PlayerId() // && activePlayersCount > 1 && !forceAnimation; const modeSupportShowingFoldedCards = appConfig.game.seatMode; if (displayFoldedCards || modeSupportShowingFoldedCards) { currentPlayer.FoldedCards(currentCards); currentPlayer.IsCardsFolded(true); } if (appConfig.game.tablePreviewMode) { currentPlayer.IsCardsFolded(true); } // Inject tasks in the reverse order. this.queue.injectCallback(() => { currentPlayer.finishFoldAnimation(); }); this.queue.injectWait(duration); } private finishFoldAnimation() { // Do nothing. } private clearNotification() { this.hasNotification(false); this.displayingRebuyAddonNotification = false; if (this.notificationHandleTimeout != null) { timeService.clearTimeout(this.notificationHandleTimeout); this.notificationHandleTimeout = null; } if (this.notificationHandleInterval != null) { timeService.clearInterval(this.notificationHandleInterval); this.notificationHandleInterval = null; } } private reportApiError(status: string) { if (debugSettings.tableView.reportApiError) { alert(status); } } private updatePauseDescription() { if (this.pauseDescriptionHandle != null) { timeService.clearInterval(this.pauseDescriptionHandle); this.pauseDescriptionHandle = null; } if (this.pauseDate() != null) { const updateDescription = () => { const tournament = this.tournament(); if (tournament) { const pauseEnds = moment(this.pauseDate()).add(tournament.tournamentData().PauseTimeout, "minutes"); const currentMoment = moment().add(timeService.timeDiff, "ms"); const diff = pauseEnds.diff(currentMoment); if (diff < 0) { this.clearPauseMessage(); return; } const duration = moment.duration(diff); this.pauseDescription(_("table.gameContinue", { startTime: duration.humanize(true) })); } else { this.pauseDescription(_("table.waitingForSeat")); } }; this.pauseDescriptionHandle = timeService.setInterval(updateDescription, 500); } } private clearPauseMessage() { this.paused(false); this.pauseDate(null); this.updatePauseDescription(); } /** * Finish animation on the table */ private finishAnimation(places: TablePlaceModel[]) { for (const p in places) { if (!places.hasOwnProperty(p)) { continue; } const currentPlayer = places[p]; currentPlayer.Bet(null); if (currentPlayer.IsFoldCards()) { currentPlayer.finishFoldAnimation(); } } } /** * Updates current combination based on table status. */ private updateCurrentCombination() { this.currentCombination(this.getCurrentCombination()); } /** * Saves last hand history */ private saveHandHistory() { this.lastHandHistory(null); if (this.handHistory != null && this.handHistory.valid) { this.lastHandHistory(this.handHistory); this.handHistory = new HandHistory(this); } } /** * Initializes the hand history */ private initHandHistory() { const self = this; this.handHistory = new HandHistory(this); this.lastHandHistory = ko.observable(); this.hasPreviousHand = ko.computed(function () { const lastHand = self.lastHandHistory(); return lastHand != null && lastHand.id != null; }, this); this.currentHandCaption = ko.computed(function () { let currentGame = self.gameId(); if (currentGame == null) { currentGame = self.currentGameId(); if (currentGame == null) { return _("table.currentHandEmpty"); } return _("table.currentHand", { id: currentGame }); } return _("table.currentHand", { id: currentGame }); }, this); this.previousHandCaption = ko.computed(function () { const lastHand = self.lastHandHistory(); if (lastHand == null) { return ""; } return _("table.lastHand", { id: lastHand.id }); }, this); } /* * Prompt to refresh places information. */ private refreshPlaces() { this.logGameEvent("Refreshing places"); this.tablePlaces.refreshPlaces(); } /** * Get player place by login */ private getPlayerByLogin(login: string) { const filteredPlaces = this.places().filter((place) => place.PlayerName() === login); if (filteredPlaces.length === 0) { return null; } return filteredPlaces[0]; } /** * Performs recovery from the network error during making turns * @param messageCode Message to display during recovery. */ private turnRecovery(messageCode: string = "table.connectionError") { if (app.currentPopup !== SlowInternetService.popupName) { SimplePopup.display(_("table.turn"), _(messageCode, { tableName: this.tableName() })); } this.actionBlock.buttonsEnabled(true); } /** * Record log message */ private log(message: string, ...params: any[]) { if (!this.isLogEnabled()) { return; } // tslint:disable-next-line:no-console console.log("Table " + this.tableId + ": " + message, params); } /** * Record log message */ private logGameEvent(message: string, ...params: any[]) { const tableViewSettings = debugSettings.tableView; if (!tableViewSettings.traceGameEvents) { return; } this.log(message, params); } /** * Checks whether logging on this table is enabled. */ private isLogEnabled() { const tableViewSettings = debugSettings.tableView; if (!tableViewSettings.trace) { if (tableViewSettings.traceTables == null) { return false; } if (tableViewSettings.traceTables.indexOf(this.tableId) < 0) { return false; } } return true; } }