Skip to content

Commit

Permalink
scaffolding for substitutions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrosack committed Dec 6, 2023
1 parent 9dc0af0 commit af65f7d
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 12 deletions.
4 changes: 4 additions & 0 deletions api/controllers/gameController/_models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface LeaveRequestBody {
steamId?: string;
}

export interface RequestSubstitutionBody {
steamId?: string;
}

export interface SurrenderBody {
kickUserId?: string;
}
Expand Down
3 changes: 3 additions & 0 deletions api/controllers/gameController/finishSubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ In save but not enabled: ${notInGame
);
}

// Keep track of dead civs so we don't have people join a dead civ
game.players[i].isDead = civ.type === ActorType.DEAD;

if (GameUtil.playerIsHuman(game.players[i])) {
if (civ.type === ActorType.DEAD) {
// Player has been defeated!
Expand Down
1 change: 1 addition & 0 deletions api/controllers/gameController/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './leave';
export * from './listOpen';
export * from './refreshState';
export * from './replacePlayer';
export * from './requestSubstitution';
export * from './resetGameStateOnNextUpload';
export * from './restart';
export * from './revert';
Expand Down
30 changes: 26 additions & 4 deletions api/controllers/gameController/listOpen.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { orderBy } from 'lodash';
import { Get, Response, Route, Tags } from 'tsoa';
import { Get, Route, Tags } from 'tsoa';
import { GAME_REPOSITORY_SYMBOL, IGameRepository } from '../../../lib/dynamoose/gameRepository';
import { inject, provideSingleton } from '../../../lib/ioc';
import { Game } from '../../../lib/models';
import { ErrorResponse } from '../../framework';
import { OpenGamesResponse } from './_models';

@Route('game')
Expand All @@ -12,10 +11,9 @@ import { OpenGamesResponse } from './_models';
export class GameController_ListOpen {
constructor(@inject(GAME_REPOSITORY_SYMBOL) private gameRepository: IGameRepository) {}

@Response<ErrorResponse>(401, 'Unauthorized')
@Get('listOpen')
public async listOpen(): Promise<OpenGamesResponse> {
const games: Game[] = await this.gameRepository.incompleteGames();
const games = await this.gameRepository.incompleteGames();
const orderedGames = orderBy(games, ['createdAt'], ['desc']);

return {
Expand All @@ -38,4 +36,28 @@ export class GameController_ListOpen {
})
};
}

@Get('notStarted')
public async notStarted(): Promise<Game[]> {
const games = await this.gameRepository.unstartedGames(0);
return orderBy(games, ['createdAt'], ['desc']);
}

@Get('openSlots')
public async openSlots(): Promise<Game[]> {
const games = await this.gameRepository.incompleteGames();
return orderBy(games, ['createdAt'], ['desc']).filter(game => {
const numHumans = game.players.filter(player => {
return !!player.steamId;
}).length;

return (
game.inProgress &&
game.allowJoinAfterStart &&
!game.completed &&
((numHumans < game.players.length && numHumans < game.humans) ||
game.players.some(x => x.substitutionRequested))
);
});
}
}
45 changes: 45 additions & 0 deletions api/controllers/gameController/requestSubstitution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Body, Post, Request, Response, Route, Security, Tags } from 'tsoa';
import { GAME_REPOSITORY_SYMBOL, IGameRepository } from '../../../lib/dynamoose/gameRepository';
import { inject, provideSingleton } from '../../../lib/ioc';
import { Game } from '../../../lib/models';
import { ErrorResponse, HttpRequest, HttpResponseError } from '../../framework';
import { RequestSubstitutionBody } from './_models';

@Route('game')
@Tags('game')
@provideSingleton(GameController_RequestSubstitution)
export class GameController_RequestSubstitution {
constructor(@inject(GAME_REPOSITORY_SYMBOL) private gameRepository: IGameRepository) {}

@Security('api_key')
@Response<ErrorResponse>(401, 'Unauthorized')
@Post('{gameId}/requestSubstitution')
public async requestSubstitution(
@Request() request: HttpRequest,
gameId: string,
@Body() body: RequestSubstitutionBody
): Promise<Game> {
const game = await this.gameRepository.getOrThrow404(gameId);
const steamId = body.steamId || request.user;

if (game.createdBySteamId !== request.user && body.steamId) {
throw new HttpResponseError(400, `Only admin can request substitution for another player!`);
}

if (!game.inProgress) {
throw new HttpResponseError(400, 'You can only request substitution for a started game.');
}

const player = game.players.find(x => x.steamId === steamId);

if (!player) {
throw new HttpResponseError(400, 'Player not in Game');
}

player.substitutionRequested = !player.substitutionRequested;

await this.gameRepository.saveVersioned(game);

return game;
}
}
25 changes: 17 additions & 8 deletions api/controllers/statsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,39 @@ import { GlobalStatsData } from '../../lib/models/miscData';
@provideSingleton(StatsController)
export class StatsController {
private cachedUsers?: User[];
private cachedUsersDate?: Date;

private cachedGlobalStats?: GlobalStatsData;

private userCacheDate?: Date;
private cachedGlobalStatsDate?: Date;

constructor(
@inject(MISC_DATA_REPOSITORY_SYMBOL) private miscDataRepository: IMiscDataRepository,
@inject(USER_REPOSITORY_SYMBOL) private userRepository: IUserRepository
) {}

private async refreshCachedData() {
private async refreshUsers() {
if (
!this.userCacheDate ||
moment(this.userCacheDate).isBefore(moment().subtract(5, 'minutes'))
!this.cachedUsersDate ||
moment(this.cachedUsersDate).isBefore(moment().subtract(5, 'minutes'))
) {
this.cachedUsers = await this.userRepository.usersWithTurnsPlayed();
this.cachedUsersDate = new Date();
}
}

private async refreshGlobalStats() {
if (
!this.cachedGlobalStatsDate ||
moment(this.cachedGlobalStatsDate).isBefore(moment().subtract(5, 'minutes'))
) {
this.cachedGlobalStats = (await this.miscDataRepository.getGlobalStats(false)).data;
this.userCacheDate = new Date();
this.cachedGlobalStatsDate = new Date();
}
}

@Get('users/{gameType}')
public async users(gameType: string): Promise<UsersByGameTypeResponse> {
await this.refreshCachedData();
await Promise.all([this.refreshUsers(), this.refreshGlobalStats()]);

const pickTurnData = (data: TurnData) =>
pick(
Expand Down Expand Up @@ -88,7 +97,7 @@ export class StatsController {

@Get('global')
public async global(): Promise<GlobalStatsData> {
await this.refreshCachedData();
await this.refreshGlobalStats();

return this.cachedGlobalStats;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/models/gamePlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export interface GamePlayer extends Partial<TurnData> {
civType: string;
hasSurrendered?: boolean;
surrenderDate?: Date;
substitutionRequested?: boolean;
isDead?: boolean;
}

0 comments on commit af65f7d

Please sign in to comment.