Skip to content

Commit

Permalink
feat: add basic tournament history/activity info on admin dash
Browse files Browse the repository at this point in the history
- fixed an issue where the exit survey was being given on the gameover
  screen of a freeplay game due to some helper functions not
  distinguishing between tournament/game types when searching for
  invites
  • Loading branch information
sgfost committed Nov 3, 2023
1 parent c323388 commit b6bb200
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 44 deletions.
3 changes: 2 additions & 1 deletion client/src/api/admin/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ModerationActionData,
InspectData,
DynamicSettingsData,
LobbyActivityData,
} from "@port-of-mars/shared/types";
import { TStore } from "@port-of-mars/client/plugins/tstore";
import { AjaxRequest } from "@port-of-mars/client/plugins/ajax";
Expand Down Expand Up @@ -83,7 +84,7 @@ export class AdminAPI {
}
}

async getLobbyData(): Promise<any> {
async getLobbyData(): Promise<LobbyActivityData> {
try {
return await this.ajax.get(url("/admin/lobby"), ({ data }) => {
return data;
Expand Down
10 changes: 7 additions & 3 deletions client/src/api/tournament/request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { url } from "@port-of-mars/client/util";
import { TournamentRoundInviteStatus, TournamentStatus } from "@port-of-mars/shared/types";
import {
GameType,
TournamentRoundInviteStatus,
TournamentStatus,
} from "@port-of-mars/shared/types";
import { TStore } from "@port-of-mars/client/plugins/tstore";
import { AjaxRequest } from "@port-of-mars/client/plugins/ajax";

Expand All @@ -16,9 +20,9 @@ export class TournamentAPI {
}
}

async getInviteStatus(): Promise<TournamentRoundInviteStatus> {
async getInviteStatus(gameType: GameType): Promise<TournamentRoundInviteStatus> {
try {
return this.ajax.get(url("/tournament/invite-status"), ({ data }) => data);
return this.ajax.get(url(`/tournament/invite-status?${gameType}`), ({ data }) => data);
} catch (e) {
console.log("Unable to get tournament round invite status");
console.log(e);
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/root/GameOver.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default class Victory extends Vue {
async created() {
this.tournamentAPI = new TournamentAPI(this.$tstore, this.$ajax);
const invite = await this.tournamentAPI.getInviteStatus();
const invite = await this.tournamentAPI.getInviteStatus(this.$tstore.state.gameType);
if (invite) {
this.exitSurveyUrl = invite.exitSurveyUrl;
}
Expand Down
3 changes: 3 additions & 0 deletions client/src/stylesheets/bootstrap-customize.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ $enable-rounded: true;

// Custom Sizes

// generates utility classes like w-10 (width: 10%), mh-40 (max-height: 40%), etc.
$sizes: (
"10" : 10%,
"30" : 30%,
"40" : 40%,
"70" : 70%,
);

Expand Down
2 changes: 1 addition & 1 deletion client/src/views/TournamentDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default class TournamentDashboard extends Vue {
async created() {
this.api = new TournamentAPI(this.$tstore, this.$ajax);
this.invite = await this.api.getInviteStatus();
this.invite = await this.api.getInviteStatus("tournament");
this.loaded = true;
}
}
Expand Down
15 changes: 13 additions & 2 deletions client/src/views/admin/Games.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@
sort-by="dateFinalized"
:sort-desc="true"
>
<template #cell(tournamentRound)="data">
<b-badge
variant="secondary"
v-if="data.item.tournamentRound.tournament.name === 'freeplay'"
>Freeplay</b-badge
>
<b-badge variant="primary" v-else>
{{ data.item.tournamentRound.tournament.name }}
(Round {{ data.item.tournamentRound.roundNumber }})
</b-badge>
</template>
<template #cell(dateFinalized)="data">
{{ new Date(data.item.dateFinalized).toDateString() }}
</template>
Expand Down Expand Up @@ -108,7 +119,7 @@
</b-col>
<!-- players -->
<b-col v-if="showPlayers" cols="5" class="mh-100 p-2">
<h4 class="header-nowrap">Game #{{ this.players[0].gameId }} Scoreboard</h4>
<h4 class="header-nowrap">Game #{{ players[0].gameId }} Scoreboard</h4>
<div class="h-100-header w-100 content-container">
<b-table
dark
Expand Down Expand Up @@ -174,7 +185,7 @@ export default class Games extends Vue {
games: any = [];
gameFields = [
{ key: "id", label: "Game ID" },
{ key: "tournamentRoundId", label: "Round" },
{ key: "tournamentRound", label: "Tournament" },
{ key: "dateFinalized", label: "Date" },
{ key: "status", label: "Status" },
{ key: "players", label: "Players" },
Expand Down
38 changes: 28 additions & 10 deletions client/src/views/admin/Rooms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@
-------------------- ---------------------
-->
<div class="p-3 h-100">
<b-row class="h-30 m-0">
<div class="m-2 d-flex">
<h4 class="mr-3">
<b-badge v-if="$tstore.state.isFreePlayEnabled" variant="secondary">
Free Play Lobby: {{ totalFreePlayLobbyClients }} Player(s) waiting
</b-badge>
</h4>
<h4>
<b-badge v-if="$tstore.state.isTournamentEnabled" variant="primary">
Tournament Lobby: {{ totalTournamentLobbyClients }} Player(s) waiting
</b-badge>
</h4>
</div>
<b-row class="h-40 m-0">
<!-- game list -->
<b-col cols="6" class="mh-100 p-2">
<h4 class="header-nowrap">
Games List
<b-badge v-if="lobby.clients" class="float-right" variant="warning">
{{ lobby.clients }} Player(s) in lobby
</b-badge>
</h4>
<h4 class="header-nowrap">Games List</h4>
<div class="h-100-header w-100 content-container">
<b-table
dark
Expand Down Expand Up @@ -79,7 +86,7 @@
</div>
</b-col>
</b-row>
<b-row class="h-70 w-100 m-0">
<b-row class="h-50 w-100 m-0">
<!-- mars log -->
<b-col cols="6" class="mh-100 w-100 p-2">
<h4 class="header-nowrap">Mars Log</h4>
Expand All @@ -102,7 +109,7 @@
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { AdminAPI } from "@port-of-mars/client/api/admin/request";
import { InspectData } from "@port-of-mars/shared/types";
import { InspectData, LobbyActivityData } from "@port-of-mars/shared/types";
import Chat from "@port-of-mars/client/components/game/static/chat/Chat.vue";
import MarsLog from "@port-of-mars/client/components/game/MarsLog.vue";
import StatusBar from "@port-of-mars/client/components/game/static/systemhealth/StatusBar.vue";
Expand All @@ -123,7 +130,10 @@ export default class Rooms extends Vue {
{ key: "clients", label: "Players" },
{ key: "inspect", label: "" },
];
lobby: any = {};
lobby: LobbyActivityData = {
freeplay: [],
tournament: [],
};
inspectData: InspectData = {
players: [],
systemHealth: 0,
Expand All @@ -138,6 +148,14 @@ export default class Rooms extends Vue {
inspectedRoomId: string = "";
pollingIntervalId = 0;
get totalTournamentLobbyClients() {
return this.lobby.tournament.reduce((acc, room) => acc + room.clients, 0);
}
get totalFreePlayLobbyClients() {
return this.lobby.freeplay.reduce((acc, room) => acc + room.clients, 0);
}
formatTime(time: number) {
const mins = Math.floor(time / 1000 / 60);
const hours = Math.floor(mins / 60);
Expand Down
2 changes: 1 addition & 1 deletion server/src/rooms/game/state/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class GameState
this.lastTimePolled = new Date();
this.maxRound = data.numberOfGameRounds;
this.players = new PlayerSet(data.playerOpts);
this.type = "tournament";
this.type = data.type;
}

static DEFAULTS = {
Expand Down
7 changes: 6 additions & 1 deletion server/src/routes/tournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getServices } from "@port-of-mars/server/services";
import { getPagePath, TOURNAMENT_DASHBOARD_PAGE } from "@port-of-mars/shared/routes";
import { settings, getLogger } from "@port-of-mars/server/settings";
import { ServerError } from "@port-of-mars/server/util";
import { GameType } from "@port-of-mars/shared/types";

export const tournamentRouter = Router();

Expand Down Expand Up @@ -41,7 +42,11 @@ tournamentRouter.get("/invite-status", async (req: Request, res: Response, next)
if (!user) {
res.status(401);
}
const tournamentData = await getServices().tournament.getTournamentRoundInviteStatus(user);
const gameType = String(req.query.gameType) as GameType;
const tournamentData = await getServices().tournament.getTournamentRoundInviteStatus(
user,
gameType
);
if (!tournamentData) {
res.status(404).json({
kind: "danger",
Expand Down
12 changes: 8 additions & 4 deletions server/src/services/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
ModerationActionClientData,
ChatReportRequestData,
ModerationActionType,
LobbyActivityData,
} from "@port-of-mars/shared/types";
import { getLogger } from "@port-of-mars/server/settings";
import { TournamentLobbyRoom } from "../rooms/lobby/tournament";

const logger = getLogger(__filename);

Expand All @@ -42,10 +44,11 @@ export class AdminService extends BaseService {
};
}

async getLobbyData(): Promise<any> {
// FIXME: need to look at all lobby rooms (tournament)
const lobby = await matchMaker.findOneRoomAvailable(FreePlayLobbyRoom.NAME, {});
return lobby ?? {};
async getLobbyData(): Promise<LobbyActivityData> {
return {
freeplay: await matchMaker.query({ name: FreePlayLobbyRoom.NAME }),
tournament: await matchMaker.query({ name: TournamentLobbyRoom.NAME }),
};
}

async getActiveRooms(): Promise<any> {
Expand All @@ -55,6 +58,7 @@ export class AdminService extends BaseService {
roomId: g.roomId,
clients: g.clients,
elapsed: Date.now() - new Date(g.createdAt).getTime(),
type: g.type,
};
});
}
Expand Down
2 changes: 2 additions & 0 deletions server/src/services/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export class GameService extends BaseService {
.createQueryBuilder("game")
.leftJoinAndSelect("game.players", "player")
.leftJoinAndSelect("player.user", "user")
.leftJoinAndSelect("game.tournamentRound", "tournamentRound")
.leftJoinAndSelect("tournamentRound.tournament", "tournament")
.where("game.dateFinalized BETWEEN :start AND :end", interval);
if (defeats) {
query = query.andWhere("game.status IN (:...status)", { status: ["victory", "defeat"] });
Expand Down
36 changes: 27 additions & 9 deletions server/src/services/tournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,38 @@ import {
TournamentRound,
TournamentRoundInvite,
} from "@port-of-mars/server/entity";
import { MoreThan, SelectQueryBuilder } from "typeorm";
import { MoreThan, Not, SelectQueryBuilder } from "typeorm";
import { getServices } from "@port-of-mars/server/services";
import { getLogger } from "@port-of-mars/server/settings";
import { BaseService } from "@port-of-mars/server/services/db";
import { TournamentRoundDate } from "@port-of-mars/server/entity/TournamentRoundDate";
import { TournamentRoundInviteStatus, TournamentStatus } from "@port-of-mars/shared/types";
import {
GameType,
TournamentRoundInviteStatus,
TournamentStatus,
} from "@port-of-mars/shared/types";

const logger = getLogger(__filename);

export class TournamentService extends BaseService {
async getActiveTournament(): Promise<Tournament> {
return await this.em.getRepository(Tournament).findOneOrFail({
where: { active: true },
const x = await this.em.getRepository(Tournament).findOneOrFail({
where: { active: true, name: Not("freeplay") },
order: {
id: "DESC",
},
});
return x;
}

async getTournament(id?: number): Promise<Tournament> {
if (id) {
return await this.em.getRepository(Tournament).findOneOrFail(id);
return await this.em.getRepository(Tournament).findOneOrFail({
where: {
id: id,
name: Not("freeplay"),
},
});
} else {
return await this.getActiveTournament();
}
Expand All @@ -42,6 +52,16 @@ export class TournamentService extends BaseService {
return await this.em.getRepository(Tournament).findOneOrFail({ name });
}

async getCurrentTournamentRoundByType(type: GameType) {
// get the current tournament round, if freeplay game then get the special open/freeplay tournament round
const tournamentService = getServices().tournament;
if (type === "freeplay") {
return await this.getFreePlayTournamentRound();
} else {
return await tournamentService.getCurrentTournamentRound();
}
}

async getCurrentTournamentRound(tournamentId?: number): Promise<TournamentRound> {
tournamentId = (await this.getTournament(tournamentId)).id;
return await this.em.getRepository(TournamentRound).findOneOrFail({
Expand Down Expand Up @@ -168,11 +188,9 @@ export class TournamentService extends BaseService {
*/
async getTournamentRoundInviteStatus(
user: User,
tournamentRound?: TournamentRound
gameType: GameType = "freeplay"
): Promise<TournamentRoundInviteStatus | null> {
if (!tournamentRound) {
tournamentRound = await this.getCurrentTournamentRound();
}
const tournamentRound = await this.getCurrentTournamentRoundByType(gameType);
let invite = await this.getTournamentRoundInvite(user, tournamentRound);
if (!invite) {
// NOTE: we'll need to manually invite users for a non-open tournament
Expand Down
12 changes: 1 addition & 11 deletions server/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,9 @@ export async function mockGameInitOpts(): Promise<GameOpts> {
};
}

async function getCurrentTournamentRound(type: GameType) {
// get the current tournament round, if freeplay game then get the special open/freeplay tournament round
const tournamentService = getServices().tournament;
if (type === "freeplay") {
return await tournamentService.getFreePlayTournamentRound();
} else {
return await tournamentService.getCurrentTournamentRound();
}
}

export async function buildGameOpts(usernames: Array<string>, type: GameType): Promise<GameOpts> {
const services = getServices();
const currentTournamentRound = await getCurrentTournamentRound(type);
const currentTournamentRound = await services.tournament.getCurrentTournamentRoundByType(type);
assert.strictEqual(usernames.length, ROLES.length);
logger.info("building game opts with current tournament round [%d]", currentTournamentRound.id);
for (const u of usernames) {
Expand Down
5 changes: 5 additions & 0 deletions shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,11 @@ export interface AdminStats {
bannedUsers: number;
}

export interface LobbyActivityData {
freeplay: any[];
tournament: any[];
}

export interface TournamentStatus {
name: string;
description: string;
Expand Down

0 comments on commit b6bb200

Please sign in to comment.