poker-html-client
Version:
HTML client for online poker
1,287 lines (1,153 loc) • 116 kB
text/typescript
/* 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