///
import * as ko from "knockout";
import * as moment from "moment";
import { Game } from "../api/game";
import { TournamentBetStructure, TournamentPrizeStructure } from "../api/information";
import { Tournament, TournamentDefinition, TournamentPlayerDefinition, TournamentPlayerStatus, TournamentTableDefinition } from "../api/tournament";
import { App } from "../app";
import * as authManager from "../authmanager";
import { debugSettings } from "../debugsettings";
import { _ } from "../languagemanager";
import * as metadataManager from "../metadatamanager";
import { SimplePopup } from "../popups/simplepopup";
import { reloadManager } from "../services";
import { AccountManager } from "../services/accountManager";
import { tableManager } from "../table/tablemanager";
import * as timeService from "../timeservice";
import { PageBase } from "../ui/pagebase";
declare var apiHost: string;
declare var host: string;
declare var app: App;
interface TournamentTablePlayerView {
id: number;
login: string;
}
interface TournamentTableListView {
id: number;
name: string;
isClosed: boolean;
selected: KnockoutObservable;
players: TournamentTablePlayerView[];
}
export class TournamentLobbyPage extends PageBase {
public loading: KnockoutObservable;
public tournamentId = 0;
public tournamentData: KnockoutObservable;
public tournamentCaption: KnockoutComputed;
public authenticated: KnockoutComputed;
public tablesAvailable: KnockoutComputed;
public lateRegistrationAllowed: KnockoutComputed;
public stackInformation: KnockoutComputed;
public participantsInformation: KnockoutComputed;
public betLevelInformation: KnockoutComputed;
public parentView: string;
public currentView: KnockoutObservable;
public getBetStructure: KnockoutComputed;
public playersSortOrder = ko.observable("asc");
public playersColumnOrder = ko.observable("Login");
public getTournamentPlayers: KnockoutComputed;
public tablesData: KnockoutObservable;
public getSelectedTablePlayers: KnockoutComputed;
public couldRegister: KnockoutComputed;
public couldUnregister: KnockoutComputed;
public couldContinueGame: KnockoutComputed;
public couldView: KnockoutComputed;
public currentTime: KnockoutComputed;
public duration: KnockoutComputed;
public lateRegistrationLeft: KnockoutComputed;
public lateRegistrationRunning: KnockoutComputed;
public totalPrize: KnockoutComputed;
public prizesCount: KnockoutComputed;
public scrollTrigger: KnockoutComputed;
constructor() {
super();
const self = this;
this.tournamentData = ko.observable(null);
this.tablesData = ko.observableArray([]);
this.tournamentData.subscribe(function(data) {
if (data == null) {
self.tablesData([]);
return;
}
const result = [];
for (let i = 0; i < data.TournamentTables.length; i++) {
const table = data.TournamentTables[i];
const tableModel: TournamentTableListView = {
id: table.TableId,
name: table.TableName,
isClosed: table.IsClosed,
selected: ko.observable(false),
players: [],
};
if (!table.IsClosed) {
table.Players.forEach(function(item) {
tableModel.players.push({ id: item.PlayerId, login: item.PlayerName });
});
}
result.push(tableModel);
}
if (result.length > 0) {
result[0].selected(true);
}
self.tablesData(result);
});
this.loading = ko.observable(true);
this.currentView = ko.observable(1);
authManager.authenticated.subscribe(function(newValue) {
self.refreshTournament();
});
this.lateRegistrationAllowed = ko.computed(function() {
const data = self.tournamentData();
if (data === null) {
return false;
}
return data.RegistrationEndDate > data.StartDate;
}, this);
this.totalPrize = ko.computed(function() {
const tdata = self.tournamentData();
if (tdata === null) {
return null;
}
return tdata.PrizeAmount + (tdata.CollectedPrizeAmount || 0);
}, this);
this.stackInformation = ko.computed(function() {
const data = self.tournamentData();
if (data === null) {
return null;
}
const activePlayers = data.TournamentPlayers.filter(function(item) {
return item.Status === 2;
});
let maxStack = 0;
let minStack = 10000000000;
let stackSum = 0;
activePlayers.forEach(function(item) {
const stack = item.Stack === null ? 0 : item.Stack;
if (stack > maxStack) {
maxStack = stack;
}
if (stack < minStack) {
minStack = stack;
}
stackSum += stackSum;
});
const avgStack = stackSum / activePlayers.length;
const result = _("tournamentLobby.stackInformation")
.replace("#max", maxStack.toString())
.replace("#min", minStack.toString())
.replace("#avg", avgStack.toFixed(0));
return result;
}, this);
this.participantsInformation = ko.computed(function() {
const data = self.tournamentData();
if (data == null) {
return null;
}
const activePlayers = data.TournamentPlayers.filter(function(item) {
return item.Status === 2;
});
return _("tournamentLobby.participantsInformation")
.replace("#joined", data.JoinedPlayers.toString())
.replace("#tablesCount", data.TournamentTables.length.toString())
.replace("#playersCount", activePlayers.length.toString());
}, this);
this.betLevelInformation = ko.computed(function() {
const data = self.tournamentData();
if (data == null) {
return null;
}
if (data.BetLevel == null) {
return null;
}
const betStructure = metadataManager.bets[data.WellKnownBetStructure]
.sort((a, b) => a.Level - b.Level);
// Values in the betStructure is starting from 1, be caution.
// Different methods could produce different indexation.
const betLevel = betStructure.length < data.BetLevel ? betStructure.length : data.BetLevel;
const currentBets = betStructure.filter((bs) => bs.Level === betLevel)[0];
if (currentBets.Ante !== null && currentBets.Ante !== 0) {
return _("tournamentLobby.betStructureWithAnte")
.replace("#sb", currentBets.SmallBlind.toString())
.replace("#bb", currentBets.BigBlind.toString())
.replace("#ante", currentBets.Ante.toString());
}
return _("tournamentLobby.betStructureWithoutAnte")
.replace("#sb", currentBets.SmallBlind.toString())
.replace("#bb", currentBets.BigBlind.toString());
}, this);
this.getBetStructure = ko.computed(function() {
const data = self.tournamentData();
if (data == null) {
return [];
}
const prizeStructure = metadataManager.bets[data.WellKnownBetStructure];
const sortedPrizes = prizeStructure.sort(function(a, b) {
return a.Level > b.Level
? 1
: (a.Level < b.Level ? -1 : 0);
});
return sortedPrizes;
}, this);
this.getTournamentPlayers = ko.computed(() => {
const tdata = self.tournamentData();
if (tdata === null || tdata === undefined) {
return null;
}
let players = tdata.TournamentPlayers;
const modifier = self.playersSortOrder() === "asc" ? 1 : -1;
const sortByName = (a: TournamentPlayerDefinition, b: TournamentPlayerDefinition) => {
return a.PlayerName > b.PlayerName ? modifier : a.PlayerName < b.PlayerName ? -modifier : 0;
};
const sortByPlace = (a: TournamentPlayerDefinition, b: TournamentPlayerDefinition) => {
const aPrize = a.Prize === null ? 0 : a.Prize;
const bPrize = b.Prize === null ? 0 : b.Prize;
return aPrize > bPrize ? modifier : aPrize < bPrize ? -modifier : sortByName(a, b);
};
const sortByStack = (a: TournamentPlayerDefinition, b: TournamentPlayerDefinition) => {
if (a.Prize === null && b.Prize === null) {
const aStack = a.Stack === null ? 0 : a.Stack;
const bStack = b.Stack === null ? 0 : b.Stack;
return aStack > bStack ? modifier : aStack < bStack ? -modifier : -sortByPlace(a, b);
} else {
if (a.Prize === null) {
return modifier;
}
if (b.Prize === null) {
return -modifier;
}
return -sortByPlace(a, b);
}
};
if (self.playersColumnOrder() === "Login") {
players = players.sort(sortByName);
}
if (self.playersColumnOrder() === "Stack") {
players = players.sort(sortByStack);
}
if (self.playersColumnOrder() === "Prize") {
players = players.sort(sortByPlace);
}
return players;
}, this);
this.authenticated = ko.computed(function() {
const value = authManager.authenticated();
return value;
}, this);
this.tablesAvailable = ko.computed(function() {
const data = self.tournamentData();
return data != null
&& data.TournamentTables != null
&& data.TournamentTables.length > 0;
}, this);
this.tournamentCaption = ko.computed(function() {
const data = self.tournamentData();
if (data === null) {
return;
}
if (debugSettings.lobby.useTournamentNameForTournamentCaption) {
return data.TournamentName;
}
return _("tournamentLobby.caption")
.replace("#number", data.TournamentId.toString());
}, this);
this.getSelectedTablePlayers = ko.computed(function() {
const selectedTables = self.tablesData().filter(function(item) {
return item.selected();
});
if (selectedTables.length < 1) {
return [];
}
return selectedTables[0].players;
}, this);
this.couldRegister = ko.computed(function() {
if (!self.authenticated()) {
return false;
}
const tdata = self.tournamentData();
if (tdata === null) {
return false;
}
return !tdata.IsRegistered
&& (tdata.Status === TournamentStatus.RegistrationStarted
|| tdata.Status === TournamentStatus.LateRegistration);
}, this);
this.couldUnregister = ko.computed(function() {
if (!self.authenticated()) {
return false;
}
const tdata = self.tournamentData();
if (tdata === null) {
return false;
}
return tdata.IsRegistered
&& tdata.Status === TournamentStatus.RegistrationStarted;
}, this);
this.couldContinueGame = ko.computed(function() {
if (!self.authenticated()) {
return false;
}
const tdata = self.tournamentData();
if (tdata === null) {
return false;
}
const tplayer = tdata.TournamentPlayers.filter(function(item) {
return item.PlayerId === authManager.loginId();
});
if (tplayer.length === 0 || tplayer[0].Status !== TournamentPlayerStatus.Playing) {
return false;
}
return tdata.Status === TournamentStatus.LateRegistration
|| tdata.Status === TournamentStatus.Started;
}, this);
this.couldView = ko.computed(function() {
if (!self.authenticated()) {
return false;
}
const tdata = self.tournamentData();
if (tdata === null) {
return false;
}
const tplayer = tdata.TournamentPlayers.filter(function(item) {
return item.PlayerId === authManager.loginId();
});
if (tplayer.length === 0) {
return tdata.Status === TournamentStatus.Started;
}
if (tplayer[0].Status !== TournamentPlayerStatus.Playing) {
return tdata.Status === TournamentStatus.LateRegistration
|| tdata.Status === TournamentStatus.Started;
}
return false;
}, this);
this.currentTime = ko.pureComputed(function() {
return timeService.currentTime();
}, this);
this.duration = ko.pureComputed(function() {
const tdata = self.tournamentData();
if (tdata == null) {
return "";
}
const startDate = moment(tdata.StartDate);
const currentMoment = moment().add(timeService.timeDiff, "ms");
const duration = moment.duration(currentMoment.diff(startDate));
const m = duration.minutes();
return duration.hours() + _("common.hours") + _("common.timeseparator")
+ (m < 10 ? "0" + m : "" + m) + _("common.minutes");
}, this);
this.lateRegistrationLeft = ko.pureComputed(function() {
const tdata = self.tournamentData();
if (tdata == null) {
return "";
}
const registrationEndDate = moment(tdata.RegistrationEndDate);
const currentMoment = moment().add(timeService.timeDiff, "ms");
const duration = moment.duration(currentMoment.diff(registrationEndDate));
const m = duration.minutes();
return duration.hours() + _("common.hours") + _("common.timeseparator")
+ (m < 10 ? "0" + m : "" + m) + _("common.minutes");
}, this);
this.lateRegistrationRunning = ko.pureComputed(function() {
const tdata = self.tournamentData();
if (tdata == null) {
return false;
}
const registrationEndDate = moment(tdata.RegistrationEndDate);
const currentMoment = moment().add(timeService.timeDiff, "ms");
if (currentMoment.diff(registrationEndDate)) {
return false;
}
return true;
}, this);
this.prizesCount = ko.computed(function() {
const data = self.tournamentData();
if (data === null) {
return null;
}
const currentPlayers = data.JoinedPlayers;
const prizeStructure = metadataManager.prizes[data.WellKnownPrizeStructure];
const sortedPrizes = prizeStructure.sort(function(a, b) {
return a.MaxPlayer > b.MaxPlayer
? 1
: (a.MaxPlayer < b.MaxPlayer ? -1 : 0);
});
const filteredPrizes = sortedPrizes.filter(function(a) {
return a.MaxPlayer >= currentPlayers;
});
let currentPrize: TournamentPrizeStructure;
if (filteredPrizes.length === 0) {
currentPrize = sortedPrizes[0];
} else {
currentPrize = filteredPrizes[0];
}
return currentPrize.PrizeLevel.length;
}, this);
let scrollTriggerCounter = 0;
this.scrollTrigger = ko.computed(function() {
self.loading();
self.currentView();
return scrollTriggerCounter++;
}, this);
}
public deactivate() {
super.deactivate();
if (PageBlock.useDoubleView) {
app.lobbyPageBlock.lobbyPage.filterLocked(false);
}
}
public activate() {
super.activate();
this.refreshTournament();
if (PageBlock.useDoubleView) {
app.lobbyPageBlock.lobbyPage.filterLocked(true);
}
reloadManager.setReloadCallback(() => this.refreshTournament());
}
public selectTable(table: TournamentTableListView) {
const tables = this.tablesData();
tables.forEach(function(item) {
item.selected(false);
});
table.selected(true);
this.tablesData(tables);
}
public setTournament(tournamentId: number, parentView: string): void {
this.tournamentId = tournamentId || 0;
this.parentView = parentView;
}
public async refreshTournament(): Promise> {
if (this.tournamentId === 0) {
return Promise.resolve({ Status: "Ok", Data: null });
}
const self = this;
const tournamentApi = new Tournament(host);
this.loading(true);
try {
const data = await tournamentApi.getTournament(this.tournamentId);
if (data.Status === "Ok") {
const tournamentData = data.Data;
self.log("Informaton about tournament ", self.tournamentId, " received: ", data.Data);
self.log(tournamentData.TournamentName);
self.tournamentData(tournamentData);
if (tournamentData.Status === TournamentStatus.Started
|| tournamentData.Status === TournamentStatus.LateRegistration) {
// Connect to the tournament so player could monitor game on it
tableManager.selectTournament(tournamentData, true);
}
if (tournamentData.Status === TournamentStatus.Started
|| tournamentData.Status === TournamentStatus.LateRegistration) {
self.setDefaultSortOrder("Stack");
} else if (tournamentData.Status === TournamentStatus.Completed) {
self.setDefaultSortOrder("Prize");
} else {
self.setDefaultSortOrder("Login");
}
}
self.loading(false);
} catch (e) {
self.log("Failed to get information about tournament " + self.tournamentId);
self.loading(false);
}
}
public back() {
app.lobbyPageBlock.showSecondary(this.parentView);
}
public async register() {
const self = this;
const manager = new AccountManager();
self.loading(true);
try {
const data = await manager.getAccount();
self.loading(false);
if (data.Status === "Ok") {
const personalAccountData = data.Data;
let balance = 0;
const tournament = self.tournamentData();
if (tournament.CurrencyId === 1) {
balance = personalAccountData.RealMoney;
} else {
balance = personalAccountData.GameMoney;
}
await self.promptRegister(balance);
} else {
SimplePopup.display(_("tournamentLobby.registrationSuccess"), _("errors." + data.Status));
}
} catch (e) {
self.loading(false);
SimplePopup.display(_("tournamentLobby.registrationSuccess"), _("tournamentLobby.registrationError"));
}
}
/**
* Initiates cancelling registration in the tournament.
*/
public async unregister() {
const self = this;
const tournament = self.tournamentData();
const name = tournament.TournamentName;
const operationTitle = _("tournamentLobby.registrationCancelled");
await app.promptAsync(
_("tournamentLobby.tournamentRegistrationCancelPromptCaption"),
[_("tournamentLobby.tournamentRegistrationCancelPrompt").replace("#name", name)]);
self.loading(true);
const tournamentApi = new Tournament(host);
try {
const data = await tournamentApi.cancelRegistration(self.tournamentId);
self.loading(false);
if (data.Status === "Ok") {
self.log("Registration cancelled");
tableManager.removeTournamentById(self.tournamentId);
SimplePopup.display(operationTitle, _("tournamentLobby.registrationCancelledCompleteSuccess"));
try {
await self.refreshTournament();
} catch (e) {
SimplePopup.display(operationTitle, _("tournamentLobby.registrationCancelError"));
}
} else {
SimplePopup.display(operationTitle, _("errors." + data.Status));
}
} catch (e) {
SimplePopup.display(operationTitle, _("tournamentLobby.registrationCancelError"));
}
}
public selectView(view: number) {
this.currentView(view);
}
public viewGame() {
const tablesView = 2;
if (this.currentView() !== tablesView) {
this.currentView(tablesView);
} else {
const selectedTable = this.tablesData().filter((tablePage) => {
return tablePage.selected();
});
if (selectedTable.length !== 0) {
const table = selectedTable[0];
if (!table.isClosed) {
this.showTournamentTable(selectedTable[0].id);
}
}
}
}
public continueGame() {
const tournamentView = tableManager.getTournamentById(this.tournamentId);
this.showTournamentTable(tournamentView.currentTableId);
}
public showTable(table: TournamentTableDefinition) {
if (!this.couldView() && this.couldContinueGame()) {
return;
}
this.showTournamentTable(table.TableId);
}
public applyPlayersSort(sortColumn: string) {
if (this.playersColumnOrder() === sortColumn) {
this.playersSortOrder(this.playersSortOrder() === "asc" ? "desc" : "asc");
return;
}
this.playersColumnOrder(sortColumn);
switch (sortColumn) {
case "Login":
this.playersSortOrder("asc");
break;
case "Stack":
this.playersSortOrder("desc");
break;
case "Prize":
this.playersSortOrder("asc");
break;
default:
this.playersSortOrder("asc");
break;
}
}
public setDefaultSortOrder(sortColumn: string) {
this.playersColumnOrder(sortColumn);
switch (sortColumn) {
case "Login":
this.playersSortOrder("asc");
break;
case "Stack":
this.playersSortOrder("desc");
break;
case "Prize":
this.playersSortOrder("asc");
break;
default:
this.playersSortOrder("asc");
break;
}
}
/**
* Shows already opened tournament table, or load new one if needed.
* @param tableId Number Id of the tournament table to show.
*/
private async showTournamentTable(tableId: number) {
const self = this;
const tableView = tableManager.getTableById(tableId);
if (tableView === null) {
this.loading(true);
const tdata = this.tournamentData();
const api = new Game(host);
const currentTournamentTitle = _("tournamentLobby.caption", { number: tdata.TournamentId });
try {
const data = await api.getTableById(tableId);
if (data.Status === "Ok") {
const tapi = new Tournament(host);
try {
const tournamentData = await tapi.getTournament(tdata.TournamentId);
if (data.Status === "Ok") {
self.loading(false);
tableManager.selectTournament(tournamentData.Data, false);
tableManager.selectTable(data.Data, true);
const currentTable = tableManager.getTableById(tableId);
if (currentTable != null) {
currentTable.tournament(tableManager.getTournamentById(tdata.TournamentId));
app.showSubPage("tables");
}
} else {
self.loading(false);
SimplePopup.display(currentTournamentTitle, _("errors." + data.Status));
}
} catch (e) {
self.loading(false);
self.log("Could not get tournament information for tournament #" + tdata.TournamentId);
SimplePopup.display(currentTournamentTitle, _("tournamentLobby.showTableError"));
}
} else {
self.loading(false);
SimplePopup.display(currentTournamentTitle, _("errors." + data.Status));
}
} catch (e) {
self.loading(false);
self.log("Could not get table information for table #" + tableId);
SimplePopup.display(currentTournamentTitle, _("tournamentLobby.showTableError"));
}
} else {
tableManager.selectById(tableId);
app.showSubPage("tables");
}
}
private async promptRegister(currentBalance: number) {
const self = this;
const tournament = self.tournamentData();
const name = tournament.TournamentName;
const joinAmount = tournament.JoinFee + tournament.BuyIn;
/* tslint:disable:no-string-literal */
const numericTextBinding = ko.bindingHandlers["numericText"] as any;
/* tslint:enable:no-string-literal */
const joinAmountString = numericTextBinding.withCommas(joinAmount.toFixed(0));
if (currentBalance < joinAmount) {
await SimplePopup.display(
_("tournamentLobby.tournamentRegistrationPromptCaption"),
_("tournamentLobby.insufficientFunds"));
return;
}
const balanceString = numericTextBinding.withCommas(currentBalance.toFixed(0));
app.okcancelPopup.customStyle("popup-container-left");
await app.promptAsync(
_("tournamentLobby.tournamentRegistrationPromptCaption"),
[
_("tournamentLobby.tournamentRegistrationPrompt", { name }),
_("tournamentLobby.tournamentRegistrationAmount").replace("#amount", joinAmountString),
_("tournamentLobby.tournamentRegistrationPromptBalance").replace("#amount", balanceString),
]);
self.loading(true);
const tournamentApi = new Tournament(host);
try {
const data = await tournamentApi.register(self.tournamentId);
self.loading(false);
if (data.Status === "Ok") {
tableManager.openTournamentById(self.tournamentId);
self.log("Registration success");
SimplePopup.display(
_("tournamentLobby.registrationSuccess"),
_("tournamentLobby.registrationCompleteSuccess"));
try {
await self.refreshTournament();
} catch (e) {
SimplePopup.display(
_("tournamentLobby.registrationSuccess"),
_("tournamentLobby.registrationError"));
}
} else {
SimplePopup.display(_("tournamentLobby.registrationSuccess"), _("errors." + data.Status));
}
} catch (e) {
self.loading(false);
SimplePopup.display(_("tournamentLobby.registrationSuccess"), _("tournamentLobby.registrationError"));
}
}
private log(message: string, ...params: any[]) {
if (debugSettings.lobby.trace) {
console.log(message, params);
}
}
}