poker-html-client
Version:
HTML client for online poker
617 lines (555 loc) • 23.9 kB
text/typescript
import * as ko from "knockout";
import { Game } from "../api/game";
import { TournamentPrizeStructure } from "../api/information";
import { Tournament, TournamentDefinition, TournamentPlayerStatus } from "../api/tournament";
import { App } from "../app";
import { appConfig } from "../appconfig";
import * as authManager from "../authmanager";
import { debugSettings } from "../debugsettings";
import { _ } from "../languagemanager";
import * as metadataManager from "../metadatamanager";
import { SimplePopup } from "../popups/simplepopup";
import { connectionService, slowInternetService } from "../services";
import { ConnectionWrapper } from "../services/connectionwrapper";
import * as timeService from "../timeservice";
import { tableManager } from "./tablemanager";
declare var apiHost: string;
declare var host: string;
declare var app: App;
export class TournamentView {
public tournamentData = ko.observable<TournamentDefinition>(null);
public loading = ko.observable(false);
/**
* Indicates that rebuy is allowed in the tournament.
*/
public rebuyAllowed = ko.observable(false);
/**
* Indicates that add-on is allowed in the tournament.
*/
public addonAllowed = ko.observable(false);
/**
* Count of rebuys which player made in the tournament.
*/
public rebuyCount = ko.observable(0);
/**
* Count of add-ons which player made in the tournament.
*/
public addonCount = ko.observable(0);
/**
* Indicates that tournament is frozen
*/
public frozen = ko.observable(false);
/**
* Id of the current table where player sitting.
*/
public currentTableId: number = null;
/**
* Indicates that player is finished playing in the tournament.
*/
public finishedPlaying = ko.observable(false);
/**
* Indicates when player finish tournament.
*/
public finishTime = ko.observable(0);
/**
* Place which player take in the tournament.
*/
public finishedPlace: number = null;
/**
* Request which performs connecting to the table.
*/
public connectingRequest: JQueryDeferred<any> = null;
/**
* Status of the tournament.
*/
public status = ko.observable(TournamentStatus.Pending);
/**
* Total prize.
*/
public totalPrize: KnockoutComputed<number>;
constructor(public tournamentId: number, data: TournamentDefinition) {
const self = this;
this.tournamentData(data);
this.status(data.Status);
this.rebuyAllowed(data.IsRebuyAllowed);
this.addonAllowed(data.IsAddonAllowed);
const currentPlayerCandidates = data.TournamentPlayers.filter((tournamentPlayer) => tournamentPlayer.PlayerId === authManager.loginId());
if (currentPlayerCandidates.length > 0) {
const currentPlayer = currentPlayerCandidates[0];
if (currentPlayer.Status === TournamentPlayerStatus.Playing) {
this.currentTableId = currentPlayer.TableId;
}
}
this.totalPrize = ko.computed(function() {
const tdata = self.tournamentData();
if (tdata === null) {
return null;
}
return tdata.PrizeAmount + (tdata.CollectedPrizeAmount || 0);
}, this);
}
public async refreshTournament(): Promise<ApiResult<TournamentDefinition>> {
if (this.tournamentId === 0) {
return Promise.resolve({ Status: "Ok", Data: null });
}
const self = this;
const tournamentApi = new Tournament(host);
this.loading(true);
const data = await tournamentApi.getTournament(this.tournamentId);
if (data.Status === "Ok") {
const tournamentData: TournamentDefinition = data.Data;
self.log("Informaton about tournament " + self.tournamentId + " received: ", data.Data);
self.log(tournamentData.TournamentName);
self.tournamentData(tournamentData);
}
self.loading(false);
return data;
}
public clearInformation() {
// Do nothing.
}
/**
* Updates information about tournament.
*/
public async updateTournamentInformation() {
const self = this;
if (this.connectingRequest !== null && this.connectingRequest.state() === "pending") {
// Re-schedule updating information.
this.connectingRequest.then(null, function() {
self.log("Rescheduling the updating information.");
self.updateTournamentInformation();
});
self.log("Cancelling the connection request process");
self.cancelUpdateTableInformation();
return;
}
// this.connecting(true);
const currentLoadingRequest = $.Deferred();
const wrapper = connectionService.currentConnection;
let hubId = wrapper.connection.id;
const connectionInfo = "HID:" + hubId;
this.log("Connecting to tournament " + this.tournamentId + " on connection " + connectionInfo);
const startConnection = app.buildStartConnection();
startConnection.then(function() {
if (wrapper.terminated) {
return;
}
hubId = wrapper.connection.id;
self.log("Attempting to connect to table and chat over connection " + hubId);
const joinTournamentRequest = self.joinTournament(wrapper);
const joinRequest = $.when(joinTournamentRequest);
currentLoadingRequest.progress(function(command: string) {
self.log("Receiving request to cancel all joining operations");
joinTournamentRequest.notify(command);
});
joinRequest.then(function() {
if (wrapper.terminated) {
currentLoadingRequest.reject("Cancelled");
return;
}
self.log("Joining to tournament finished");
currentLoadingRequest.resolve();
}, function(result1) {
if (wrapper.terminated) {
return;
}
const message = "Rejecting request due to join tournament failure in the connection."
+ "Failed request: " + result1[0];
self.log(message);
currentLoadingRequest.reject(message);
});
}, function(message) {
self.log("Tournament connection failed. Error: " + message);
currentLoadingRequest.reject("Table connection failed. Error: " + message);
});
this.connectingRequest = currentLoadingRequest;
}
public cancelUpdateTableInformation() {
if (this.connectingRequest != null) {
this.connectingRequest.notify("cancel");
this.connectingRequest = null;
}
}
public joinTournament(wrapper: ConnectionWrapper, maxAttempts = 3) {
const self = this;
const result = $.Deferred();
if (maxAttempts === 0 || wrapper.terminated) {
this.log("Stop connecting to tournament");
result.reject("Stop connecting to tournament", false);
return result;
}
const hubId = connectionService.currentConnection.connection.id;
const connectionInfo = "HID:" + hubId;
this.log("Joining tournament on connection " + connectionInfo);
const cancelled = false;
let subsequentDeferred: JQueryDeferred<any> = null;
const cancelOperation = function() {
self.log("Cancelling join tournament request");
result.reject("Cancelled", true);
};
wrapper.buildStartConnection()().then(function() {
if (wrapper.terminated) {
cancelOperation();
return;
}
const connectionId = wrapper.connection.id;
const connectionState = wrapper.connection.state;
self.log(`Executing Game.subscribeTournament on connection ${connectionId} in state ${connectionState}`);
const operation = wrapper.connection.Game.server.subscribeTournament(self.tournamentId)
.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 tournament ${self.tournamentId}, ${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.joinTournament(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 async onTournamentStatusChanged(status: TournamentStatus) {
const self = this;
const data = this.tournamentData();
const oldStatus = this.status();
this.status(status);
let rebuyTime: number;
let addonTime: number;
if (status === TournamentStatus.LateRegistration) {
rebuyTime = data.RebuyPeriodTime || 0;
addonTime = data.AddonPeriodTime || 0;
this.rebuyAllowed(rebuyTime > 0);
this.addonAllowed(addonTime > 0 && rebuyTime === 0);
if (this.finishedPlaying()) {
return;
}
await self.openTournamentTableUI();
}
if (status === TournamentStatus.Started) {
if (oldStatus === TournamentStatus.LateRegistration) {
return;
}
rebuyTime = data.RebuyPeriodTime || 0;
addonTime = data.AddonPeriodTime || 0;
this.rebuyAllowed(rebuyTime > 0);
this.addonAllowed(addonTime > 0 && rebuyTime === 0);
if (this.finishedPlaying()) {
return;
}
await self.openTournamentTableUI();
}
if (status === TournamentStatus.Cancelled) {
if (this.finishedPlaying()) {
return;
}
try {
await SimplePopup.display(
_("tournament.caption", { tournament: data.TournamentName }),
_("tournament.tournamentCancelled", { tournament: data.TournamentName }));
} finally {
self.log("Tournament " + self.tournamentId + " cancelled");
app.lobbyPageBlock.showLobby();
app.tablesPage.deactivate();
}
}
if (status === TournamentStatus.Completed) {
this.executeOnCurrentTable(() => {
self.displayTournamentFinished();
});
}
}
public onTournamentTableChanged(tableId: number) {
if (this.finishedPlaying()) {
return;
}
if (this.currentTableId === tableId) {
return;
}
if (this.status() !== TournamentStatus.Started
&& this.status() !== TournamentStatus.LateRegistration) {
this.currentTableId = tableId;
return;
}
const data = this.tournamentData();
if (this.currentTableId !== null) {
const previousTableId = this.currentTableId;
this.log(`Removing player from the tournament table ${previousTableId} in tournament ${this.tournamentId}`);
this.removeCurrentTable();
SimplePopup.display(_("tournament.caption", { tournament: data.TournamentName }),
_("tournament.tableChanged", { tournament: data.TournamentName }));
}
if (tableId != null) {
this.log("Put player on the tournament table " + tableId + " in tournament " + this.tournamentId);
this.currentTableId = tableId;
this.openTournamentTable(tableId);
}
}
public onTournamentPlayerGameCompleted(placeTaken: number) {
const self = this;
this.finishedPlaying(true);
this.finishTime(new Date().valueOf());
this.finishedPlace = placeTaken;
const data = this.tournamentData();
const structure = this.getPrizeStructure(data.WellKnownPrizeStructure);
const ascendingSort = (a, b) => {
return a.MaxPlayer - b.MaxPlayer;
};
const prizes = structure.filter((prizeStructure) => {
return prizeStructure.MaxPlayer > data.JoinedPlayers;
}).sort(ascendingSort);
let prize: TournamentPrizeStructure;
if (prizes.length > 0) {
prize = prizes[0];
} else {
prize = structure.sort(ascendingSort)[0];
}
this.executeOnCurrentTable(() => {
self.displayGameFinishedNotification(prize, placeTaken);
});
}
public onTournamentBetLevelChanged(level: number) {
const data = this.tournamentData();
const structure = this.getBetLevelStructure(data.WellKnownBetStructure).sort((a, b) => a.Level - b.Level);
const betLevelCandidate = structure.filter((betStructure) => betStructure.Level === level);
const betLevel = betLevelCandidate.length === 0
? structure[structure.length - 1]
: betLevelCandidate[0];
const currentTable = this.getTableForNotification();
if (currentTable === null) {
this.log("Table " + this.currentTableId + " is no longer valid for the tournament " + this.tournamentId);
return;
}
currentTable.bigBlind(betLevel.BigBlind);
currentTable.smallBlind(betLevel.SmallBlind);
currentTable.ante(betLevel.Ante);
if (this.finishedPlaying()) {
// return;
}
if (level > 1) {
if (betLevel.Ante == null) {
const notificationParameters = {
tournament: data.TournamentName,
sb: betLevel.SmallBlind,
bb: betLevel.BigBlind,
};
currentTable.showNotificationWithDelay(
_("tournament.betLevelChanged1", notificationParameters),
debugSettings.tableView.betLevelChangeDelay);
} else {
const notificationParameters = {
tournament: data.TournamentName,
sb: betLevel.SmallBlind,
bb: betLevel.BigBlind,
ante: betLevel.Ante,
};
currentTable.showNotificationWithDelay(
_("tournament.betLevelChanged2", notificationParameters),
debugSettings.tableView.betLevelChangeDelay);
}
}
}
public onTournamentRoundChanged(round: number) {
// Do nothing.
}
public onTournamentRebuyStatusChanged(rebuyAllowed: boolean, addonAllowed: boolean) {
if (this.finishedPlaying()) {
// return;
}
this.rebuyAllowed(rebuyAllowed);
this.addonAllowed(addonAllowed);
if (addonAllowed) {
const currentTable = this.getTableForNotification();
if (currentTable != null) {
if (this.isInTournament()) {
currentTable.showNotificationWithDelay(
_("table.addonAllowed"),
debugSettings.tableView.addonPeriodStartedDelay);
} else {
currentTable.showNotificationWithDelay(
_("table.addonAllowedNotPlaying"),
debugSettings.tableView.addonPeriodStartedDelay);
}
}
}
}
/**
* Notifies that in tournament count of made rebuy or addons changed.
* @param rebuyCount Count of rebuys which player done.
* @param addonCount Count of add-ons which player done.
*/
public onTournamentRebuyCountChanged(rebuyCount: number, addonCount: number) {
this.rebuyCount(rebuyCount);
this.addonCount(addonCount);
}
public onTournamentFrozen() {
this.frozen(true);
}
public onTournamentUnfrozen() {
this.frozen(false);
}
public onTournamentRegistrationCancelled() {
// Do nothing.
}
public getBetLevelStructure(level: number) {
return metadataManager.bets[level];
}
public getPrizeStructure(structure: number) {
return metadataManager.prizes[structure];
}
public removeCurrentTable() {
tableManager.removeTableById(this.currentTableId);
tableManager.adjustTablePosition();
this.currentTableId = null;
}
private async openTournamentTableUI() {
const self = this;
const data = this.tournamentData();
const messageKey = appConfig.tournament.openTableAutomatically
? "tournament.tournamentStarted"
: "tournament.tournamentStartedNoOpen";
try {
await SimplePopup.display(
_("tournament.caption", { tournament: data.TournamentName }),
_(messageKey, { tournament: data.TournamentName }));
} finally {
self.log("Tournament " + self.tournamentId + " started");
if (appConfig.tournament.openTableAutomatically) {
await this.openTournamentTable(this.currentTableId);
self.log("Opening table for tournament " + self.tournamentId + "");
app.showSubPage("tables");
}
}
}
private displayTournamentFinished() {
const self = this;
const data = this.tournamentData();
const currentDate = new Date().valueOf();
if (this.finishedPlaying() && (currentDate - this.finishTime()) > 2000) {
return;
}
if (this.finishedPlace !== 1 && this.finishedPlace !== 2) {
self.log("Tournament " + self.tournamentId + " completed");
timeService.setTimeout(async () => {
if (app.tablesPage.tablesShown()/* && app.tablesPage.currentTable().tournament() == self*/) {
try {
await SimplePopup.display(_("tournament.caption", { tournament: data.TournamentName }),
_("tournament.tournamentCompleted", { tournament: data.TournamentName }));
} finally {
self.finalizeTournament();
}
} else {
self.finalizeTournament();
}
}, 2000);
} else {
app.closePopup();
this.finalizeTournament();
}
}
private isInTournament() {
if (this.currentTableId == null) {
return false;
}
return !this.finishedPlaying();
}
private getTableForNotification() {
const self = this;
const table = tableManager.getTableById(this.currentTableId);
if (table !== null) {
return table;
}
const tournamentTables = tableManager.tables().filter((tournamentTable) => tournamentTable.tournament() !== null
&& tournamentTable.tournament().tournamentId === self.tournamentId);
if (tournamentTables.length !== 0) {
return tournamentTables[0];
}
return null;
}
private displayGameFinishedNotification(prize: TournamentPrizeStructure, placeTaken: number) {
const data = this.tournamentData();
if (prize.PrizeLevel.length < placeTaken) {
return SimplePopup.displayWithTimeout(_("tournament.caption", { tournament: data.TournamentName }),
_("tournament.playerGameCompleted", { tournament: data.TournamentName, place: placeTaken }),
10 * 1000);
} else {
const winAmount = (this.totalPrize() * prize.PrizeLevel[placeTaken - 1] / 100).toFixed();
const onTournamentCompleted = () => {
if (placeTaken === 1 || placeTaken === 2) {
this.finalizeTournament();
} else {
const currentTable = tableManager.getTableById(this.currentTableId);
if (!currentTable.opened()) {
this.finalizeTournament();
}
}
};
const message = _("tournament.playerGameCompletedAndWin", {
tournament: data.TournamentName,
place: placeTaken,
win: winAmount,
});
return SimplePopup.displayWithTimeout(
_("tournament.caption", { tournament: data.TournamentName }),
message,
10 * 1000)
.then(onTournamentCompleted, onTournamentCompleted);
}
}
private executeOnCurrentTable(callback: () => void) {
const currentTable = tableManager.getTableById(this.currentTableId);
if (app.tablesPage.visible() && currentTable != null) {
currentTable.pushCallback(() => {
callback();
});
} else {
callback();
}
}
private async openTournamentTable(tableId: number) {
const self = this;
const api = new Game(host);
const data = await api.getTableById(tableId);
tableManager.selectTable(data.Data, true);
const currentTable = tableManager.getTableById(tableId);
currentTable.tournament(self);
}
private finalizeTournament() {
if (tableManager.tables().length <= 1) {
app.lobbyPageBlock.showLobby();
app.tablesPage.deactivate();
}
this.removeCurrentTable();
}
private log(message: string, ...params: any[]) {
// tslint:disable-next-line:no-console
console.log(message);
}
}