UNPKG

poker-html-client

Version:
1,287 lines (1,153 loc) 116 kB
/* 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<string>; /** * Current login which sitting on this view. */ public currentLogin: KnockoutObservable<string>; /** * Represents the value indicating that information * about the table is retreiving. */ public connecting: KnockoutObservable<boolean>; /** * Request which performs connecting to the table. */ public connectingRequest: JQueryDeferred<any> = null; public smallBlind: KnockoutObservable<number>; public bigBlind: KnockoutObservable<number>; public ante: KnockoutObservable<number>; public changeBetParametersNextGame = ko.observable(false); public nextGameSmallBlind: KnockoutObservable<number> = ko.observable(0); public nextGameBigBlind: KnockoutObservable<number> = ko.observable(0); public nextGameAnte: KnockoutObservable<number> = ko.observable(0); public nextGameInformation: KnockoutComputed<string>; /** * Minimal amount of money which currently authenticated player * could bring on the table if he stand up from the table lately. */ public minimalPlayerBuyIn: KnockoutObservable<number>; /** * Minimal base amount of buy-in in BB to bring on the table. */ public minimalBuyIn: KnockoutObservable<number>; public gamePlayers: KnockoutObservableArray<number>; public places: KnockoutComputed<TablePlaceModel[]>; public pots: KnockoutObservableArray<number>; public tableCards: TableCardsPlace; public messages: KnockoutObservableArray<PlayerMessage>; public systemMessages: KnockoutObservableArray<SystemMessage>; public lastRaise: KnockoutObservable<number>; public timePass: KnockoutObservable<number>; /** * Count of actual actions which was performed by the players during the current game */ public actionsCount: KnockoutObservable<number>; /** * Indicating whether authenticated player is playing in the game */ public myPlayerInGame: KnockoutComputed<boolean>; /** * Indicating whether authenticated player was playing in the game */ public myPlayerWasInGame: KnockoutComputed<boolean>; /** * Id of the current game on the table. */ public gameId: KnockoutObservable<number>; public currentGameId: KnockoutObservable<number>; /** * Indicates that game finished. */ public gameFinished: KnockoutObservable<boolean>; /** * Indicates that prizes distributed in the game. */ public prizesDistributed: KnockoutObservable<boolean>; /** * Indicates that game started. */ public gameStarted: KnockoutObservable<boolean>; /** * Count of active players in the game */ public activePlayersCount: KnockoutComputed<number>; /** * Value indicating whether all bets are rounded. */ public allBetsRounded: KnockoutObservable<boolean>; /** * Value indicating whether use cards variant up */ public cardsVariantUp: KnockoutObservable<boolean>; /** * Value indicating whether use cards variant down */ public cardsVariantDown: KnockoutObservable<boolean>; /** * Css rules for table-container */ public containerCss: KnockoutObservable<any>; public timeLeft: KnockoutComputed<number>; public timerInterval: number; public chipWidth: number; public chatMessage: KnockoutObservable<string>; public combinations: KnockoutObservableArray<string>; public currentPlayer: KnockoutComputed<TablePlaceModel>; public myPlayer: KnockoutComputed<TablePlaceModel>; public mainButtonsEnabled: KnockoutObservable<boolean>; public playerActions: KnockoutObservable<any>; public turnEnabled: KnockoutComputed<boolean>; public isMyTurn: KnockoutComputed<boolean>; public notMyTurn: KnockoutComputed<boolean>; public isInGame: KnockoutComputed<boolean>; public checkOrCallAmount: KnockoutComputed<number>; /** * Bet amount for currently active player. */ public currentBet: KnockoutComputed<number>; /** * Bet amount for player. */ public myBet: KnockoutComputed<number>; public currentTotalBet: KnockoutComputed<number>; public maximumBet: KnockoutComputed<number>; public currentRaise: KnockoutComputed<number>; public minimumRaiseAmount: KnockoutComputed<number>; public maximumRaiseAmount: KnockoutComputed<number>; public amountSupported: KnockoutObservable<number>; public maxAmountOfMoneyForOtherActivePlayers: KnockoutObservable<number>; public isSitOut: KnockoutComputed<boolean>; public totalPot: KnockoutComputed<number>; public totalPotCaption: KnockoutComputed<string>; public currentCombination = ko.observable(""); public actionBlock: ActionBlock; public onMyTurn: Signal; public tablePlaces: TablePlaces; public lastHandHistory: KnockoutObservable<HandHistory>; public hasPreviousHand: KnockoutComputed<boolean>; public tableBetsCaption: KnockoutComputed<string>; public currentHandCaption: KnockoutComputed<string>; public previousHandCaption: KnockoutComputed<string>; /** * Indicates that animation is suppressed from playing. */ public animationSuppressed = ko.observable(false); /** * Tournament to which belongs given table. */ public tournament = ko.observable<TournamentView>(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<number>(null); public soundEnabled = false; public hasPendingMoney = ko.observable(false); public hasNotification = ko.observable(false); public notification = ko.observable(""); public couldAddChips: KnockoutComputed<boolean>; /* 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) { /// <signature> /// <summary>Updates the information about the table from the server</summary> /// <param name="tableId" type="Number">Id of the table for which view has to be created.</param> /// </signature> const self = this; this.tableId = tableId; this.tableName = ko.observable(model === null ? "0" : model.TableName); this.connecting = ko.observable(true); this.gamePlayers = ko.observableArray<number>([]); this.pots = ko.observableArray<number>([]); this.tableCards = new TableCardsPlace(); this.messages = ko.observableArray<PlayerMessage>([]); this.systemMessages = ko.observableArray<SystemMessage>([]); this.chatMessage = ko.observable(""); this.playerActions = ko.observable(); this.mainButtonsEnabled = ko.observable(false); this.gameId = ko.observable<number>(); this.currentGameId = ko.observable<number>(); this.actionsCount = ko.observable(0); this.bigBlind = ko.observable<number>(model === null ? 0 : model.BigBlind); this.smallBlind = ko.observable<number>(model === null ? 0 : model.SmallBlind); this.ante = ko.observable<number>(null); this.minimalPlayerBuyIn = ko.observable<number>(); this.minimalBuyIn = ko.observable<number>(); this.lastRaise = ko.observable<number>(); this.timePass = ko.observable<number>(); this.gameFinished = ko.observable(true); this.prizesDistributed = ko.observable(true); this.gameStarted = ko.observable(false); this.combinations = ko.observableArray<string>([]); 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<boolean>(false); this.cardsVariantDown = ko.observable<boolean>(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<number>({ 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<boolean>(true); this.cardsVariantDown = ko.observable<boolean>(false); } else if (cardsVariant === "down") { this.cardsVariantUp = ko.observable<boolean>(false); this.cardsVariantDown = ko.observable<boolean>(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<any> = 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<any> = 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() { /// <signature> /// <summary>Updates the information about the table from the server</summary> /// </signature> 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) { /// <signature> /// <summary>Standup player from the table.</summary> /// <param name="playerId" type="Number">Id of the player which join the table</param> /// </signature> 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) { /// <signature> /// <summary>Start the game.</summary> /// <param name="gameId" type="Number">Id of the player which join the table</param> /// <param name="winners" type="Array" /// value="[{PlayerId:1,Pot:1,Amount:100,CardsDescription:'Hands 1'}]"> /// </param> /// <param name="rake" type="Number">Amount of rake paid.</param> /// </signature> const self = this; this.queue.wait(this.animationSettings.finishGamePr