Skip to content

Commit

Permalink
Merge pull request #556 from SprocketBot/personal/gankoji/lfs
Browse files Browse the repository at this point in the history
LFS Scrim Backend Changes
  • Loading branch information
gankoji authored Jan 2, 2025
2 parents 6ecb895 + 6757f7c commit 44e517b
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
} from "@nestjs/common";
import {ClientProxy} from "@nestjs/microservices";
import {lastValueFrom, timeout} from "rxjs";
import {v4 as uuidv4} from "uuid";

import type {MicroserviceRequestOptions} from "../../global.types";
import {CommonClient, ResponseStatus} from "../../global.types";
import {v4 as uuidv4} from "uuid";
import type {
MatchmakingEndpoint, MatchmakingInput, MatchmakingResponse,
} from "./matchmaking.types";
Expand All @@ -17,10 +17,7 @@ import {MatchmakingSchemas} from "./matchmaking.types";
export class MatchmakingService {
private logger = new Logger(MatchmakingService.name);

constructor(
@Inject(CommonClient.Matchmaking) private microserviceClient: ClientProxy,

) {}
constructor(@Inject(CommonClient.Matchmaking) private microserviceClient: ClientProxy) {}

async send<E extends MatchmakingEndpoint>(endpoint: E, data: MatchmakingInput<E>, options?: MicroserviceRequestOptions): Promise<MatchmakingResponse<E>> {
const rid = uuidv4();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum MatchmakingEndpoint {
GetScrimBySubmissionId = "GetScrimBySubmissionId",
GetAllScrims = "GetAllScrims",
GetScrimMetrics = "GetScrimMetrics",
CreateLFSScrim = "CreateLFSScrim",
CreateScrim = "CreateScrim",
JoinScrim = "JoinScrim",
LeaveScrim = "LeaveScrim",
Expand Down Expand Up @@ -39,6 +40,10 @@ export const MatchmakingSchemas = {
input: Schemas.GetScrimMetrics_Request,
output: Schemas.GetScrimMetrics_Response,
},
[MatchmakingEndpoint.CreateLFSScrim]: {
input: Schemas.CreateLFSScrim_Request,
output: Schemas.CreateLFSScrim_Response,
},
[MatchmakingEndpoint.CreateScrim]: {
input: Schemas.CreateScrim_Request,
output: Schemas.CreateScrim_Response,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {z} from "zod";

import {
ScrimJoinOptionsSchema,
ScrimSchema,
ScrimSettingsSchema,
} from "../../types";

export const CreateLFSScrim_Request = z.object({
authorId: z.number(),
organizationId: z.number(),
gameModeId: z.number(),
skillGroupId: z.number(),
settings: ScrimSettingsSchema,
numRounds: z.number(),
join: z.array(ScrimJoinOptionsSchema),
});

export type CreateLFSScrimRequest = z.infer<typeof CreateLFSScrim_Request>;

export const CreateLFSScrim_Response = ScrimSchema;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./CancelScrim.schema";
export * from "./CheckInToScrim.schema";
export * from "./CompleteScrim.schema";
export * from "./CreateLFSScrim.schema";
export * from "./CreateScrim.schema";
export * from "./GetAllScrims.schema";
export * from "./GetScrim.schema";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const ScrimJoinOptionsSchema = z.object({
leaveAfter: z.number().min(0),
createGroup: z.boolean().optional(),
joinGroup: z.string().optional(),
team: z.union([z.literal(0), z.literal(1)]).optional(),
});

export type ScrimJoinOptions = z.infer<typeof ScrimJoinOptionsSchema>;
2 changes: 1 addition & 1 deletion core/src/scrim/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const ScrimPubSub = "SCRIM_PUB_SUB" as const;
export const ScrimPubSub: string = "SCRIM_PUB_SUB";
43 changes: 42 additions & 1 deletion core/src/scrim/scrim.mod.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {CreateScrimPlayerGuard, JoinScrimPlayerGuard} from "./scrim.guard";
import {ScrimService} from "./scrim.service";
import {ScrimToggleService} from "./scrim-toggle";
import {
CreateScrimInput, Scrim, ScrimEvent,
CreateLFSScrimInput, CreateScrimInput, Scrim, ScrimEvent,
} from "./types";
import {ScrimMetrics} from "./types/ScrimMetrics";

Expand Down Expand Up @@ -170,6 +170,47 @@ export class ScrimModuleResolver {
}) as Promise<Scrim>;
}

@Mutation(() => Boolean)
@UseGuards(QueueBanGuard, JoinScrimPlayerGuard, FormerPlayerScrimGuard)
async createLFSScrim(
@CurrentUser() user: UserPayload,
@Args("data") data: CreateLFSScrimInput,
): Promise<Scrim> {
if (!user.currentOrganizationId) throw new GraphQLError("User is not connected to an organization");
if (await this.scrimToggleService.scrimsAreDisabled()) throw new GraphQLError("Scrims are disabled");

const gameMode = await this.gameModeService.getGameModeById(data.gameModeId);
const player = await this.playerService.getPlayerByOrganizationAndGame(user.userId, user.currentOrganizationId, gameMode.gameId);

const mlePlayer = await this.mlePlayerService.getMlePlayerBySprocketUser(player.member.userId);
if (mlePlayer.teamName === "FP") throw new GraphQLError("User is a former player");

const checkinTimeout = await this.organizationConfigurationService.getOrganizationConfigurationValue<number>(user.currentOrganizationId, OrganizationConfigurationKeyCode.SCRIM_QUEUE_BAN_CHECKIN_TIMEOUT_MINUTES);

const settings: IScrimSettings = {
teamSize: gameMode.teamSize,
teamCount: gameMode.teamCount,
mode: data.settings.mode,
competitive: data.settings.competitive,
observable: data.settings.observable,
checkinTimeout: minutesToMilliseconds(checkinTimeout),
};

return this.scrimService.createLFSScrim({
authorId: user.userId,
organizationId: user.currentOrganizationId,
gameModeId: gameMode.id,
skillGroupId: player.skillGroupId,
settings: settings,
join: {
playerId: user.userId,
playerName: user.username,
leaveAfter: data.leaveAfter,
createGroup: data.createGroup,
},
}) as Promise<Scrim>;
}

@Mutation(() => Boolean)
@UseGuards(QueueBanGuard, JoinScrimPlayerGuard, FormerPlayerScrimGuard)
async joinScrim(
Expand Down
7 changes: 7 additions & 0 deletions core/src/scrim/scrim.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ export class ScrimService {
if (result.status === ResponseStatus.SUCCESS) return result.data;
throw result.error;
}

async createLFSScrim(data: CreateLFSScrimOptions): Promise<IScrim> {
const result = await this.matchmakingService.send(MatchmakingEndpoint.CreateLFSScrim, data);

if (result.status === ResponseStatus.SUCCESS) return result.data;
throw result.error;
}

async joinScrim(data: JoinScrimOptions): Promise<boolean> {
const result = await this.matchmakingService.send(MatchmakingEndpoint.JoinScrim, data);
Expand Down
30 changes: 30 additions & 0 deletions core/src/scrim/types/CreateLFSScrimInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
Field, InputType, Int,
} from "@nestjs/graphql";

import type {ScrimPlayer} from "./ScrimPlayer";
import {ScrimSettingsInput} from "./ScrimSettings";

@InputType()
export class CreateLFSScrimInput {
@Field(() => Int)
gameModeId: number;

@Field()
settings: ScrimSettingsInput;

@Field({nullable: true})
createGroup: boolean;

@Field(() => Int)
leaveAfter: number;

@Field()
players: ScrimPlayer[];

@Field()
teams: ScrimPlayer[][];

@Field(() => Int)
numRounds: number;
}
1 change: 1 addition & 0 deletions core/src/scrim/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {registerEnumType} from "@nestjs/graphql";
import {ScrimMode, ScrimStatus} from "@sprocketbot/common";

export * from "./CreateLFSScrimInput";
export * from "./CreateScrimInput";
export * from "./Scrim";
export * from "./ScrimGame";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import type {
Scrim, ScrimGame, ScrimPlayer,
ScrimTeam,
} from "@sprocketbot/common";
import {ScrimMode, ScrimStatus} from "@sprocketbot/common";
import { add, sub } from "date-fns";
import { now } from "lodash";
import {ScrimMode} from "@sprocketbot/common";

import {ScrimGroupService} from "../scrim-group/scrim-group.service";

@Injectable()
export class GameOrderService {
private readonly logger = new Logger(GameOrderService.name);

private threesPartitions: number[][][] = [];

private twosPartitions: number[][][] = [];

constructor(private readonly scrimGroupService: ScrimGroupService) {
// Generate all possible team permutations for 3v3
this.allPartitions([1,2,3,4,5,6], this.threesPartitions, 3);
this.allPartitions([1, 2, 3, 4, 5, 6], this.threesPartitions, 3);
// Do the same for 2v2 (there are 3, this is overkill)
this.allPartitions([1, 2, 3, 4], this.twosPartitions, 2);
}
Expand All @@ -26,14 +26,14 @@ export class GameOrderService {
// space generating all subsets and then throwing away the ones that don't
// meet our criteria. I recognize this, and accept responsibility, because
// I couldn't be arsed to get the proper algorithm (Ruskey's, if you're
// curious) working properly. We use this for such small sets that even
// powerset complexity is acceptable.
// curious) working properly. We use this for such small sets that even
// powerset complexity is acceptable.
collectPartitions(partition: number[][], teamSize: number, final: number[][][]): void {
let part: number[][] = [];
const part: number[][] = [];
for (const team of partition) {
if (team.length != teamSize) {
return;
}
if (team.length !== teamSize) {
return;
}
part.push([...team]);
}
final.push(part);
Expand All @@ -50,11 +50,11 @@ export class GameOrderService {
// For each subset in the partition
// add the current element to it
// and recall
for (let i = 0; i < ans.length; i++) {
for (const answer of ans) {
const el = set[index];
ans[i].push(el);
answer.push(el);
this.Partition(set, index + 1, ans, final, teamSize);
ans[i].pop();
answer.pop();
}

// Add the current element as a
Expand All @@ -63,12 +63,13 @@ export class GameOrderService {
ans.push([el]);
this.Partition(set, index + 1, ans, final, teamSize);
ans.pop();
};
}

allPartitions(set: number[], output: number[][][], teamSize: number): void {
let ans = [];
const ans = [];
this.Partition(set, 0, ans, output, teamSize);
};
}

generateGameOrder(scrim: Scrim): ScrimGame[] {
switch (scrim.settings.mode) {
case ScrimMode.TEAMS:
Expand Down Expand Up @@ -121,34 +122,30 @@ export class GameOrderService {

private generateRoundRobinGameOrder(scrim: Scrim): ScrimGame[] {
const output: ScrimGame[] = [];
const {teamSize, teamCount} = scrim.settings;
const teamSize = scrim.settings.teamSize;

const numGames = 3; // for now
const usedPartitions = new Set<number>();
const numPartitions = teamSize === 3 ? 10 : 3;
let partition = Math.floor(Math.random() * numPartitions);
for (let i = 0; i < numGames; i++) {
for (let i = 0;i < numGames;i++) {
while (usedPartitions.has(partition)) {
partition = Math.floor(Math.random() * numPartitions);
}
usedPartitions.add(partition);

if (teamSize === 3) {
output.push({
teams: this.threesPartitions[partition].map(i => {
return {
players: i.map(j => scrim.players[j-1])
}
}),
teams: this.threesPartitions[partition].map(k => ({
players: k.map(j => scrim.players[j - 1]),
})),
});
} else {
output.push({
teams: this.twosPartitions[partition].map(i => {
return {
players: i.map(j => scrim.players[j-1])
}
}),
});
teams: this.twosPartitions[partition].map(k => ({
players: k.map(j => scrim.players[j - 1]),
})),
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Injectable, Logger} from "@nestjs/common";
import type {
CreateScrimOptions,
Scrim, ScrimGame, ScrimPlayer,
ScrimSettings,
} from "@sprocketbot/common";
import {
config, RedisService, ScrimSchema, ScrimStatus,
Expand Down Expand Up @@ -55,6 +56,49 @@ export class ScrimCrudService {
return scrim;
}

async createLFSScrim(
authorId: number,
organizationId: number,
gameModeId: number,
skillGroupId: number,
settings: ScrimSettings,
players: ScrimPlayer[],
teams: ScrimPlayer[][],
numRounds: number,
): Promise<Scrim> {
const at = new Date();
const scrim: Scrim = {
id: v4(),
createdAt: at,
updatedAt: at,
status: ScrimStatus.PENDING,
authorId: authorId,
organizationId: organizationId,
gameModeId: gameModeId,
skillGroupId: skillGroupId,
players: [],
games: [],
settings: settings,
};

players.map(p => scrim.players.push(p));
for (let i = 0;i < numRounds;i++) {
const game = {
teams: teams.map(t => ({
players: t.map(p => p),
})),
};
scrim.games?.push(game);
}

await this.redisService.setJson(
`${this.prefix}${scrim.id}`,
scrim,
);

return scrim;
}

async removeScrim(id: string): Promise<void> {
await this.redisService.deleteJsonField(`${this.prefix}${id}`, "$");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export class ScrimController {
return this.scrimService.createScrim(data);
}

@MessagePattern(MatchmakingEndpoint.CreateLFSScrim)
async createLFSScrim(@Payload() payload: unknown): Promise<Scrim> {
const data = MatchmakingSchemas.CreateLFSScrim.input.parse(payload);
return this.scrimService.createLFSScrim(data);
}

@MessagePattern(MatchmakingEndpoint.GetAllScrims)
async getAllScrims(@Payload() payload: unknown): Promise<Scrim[]> {
const data = MatchmakingSchemas.GetAllScrims.input.parse(payload);
Expand Down
Loading

0 comments on commit 44e517b

Please sign in to comment.