Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/__tests__/gameData/data/gameData/BattleResultDtoBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { BattleResultDto } from '../../../../gameData/dto/battleResult.dto';
import { RequestType } from '../../../../gameData/enum/requestType.enum';

export class BattleResultDtoBuilder {
private readonly base: BattleResultDto = {
private readonly base: any = {
matchId: 'default-match',
type: RequestType.RESULT,
team1: [],
team2: [],
duration: 0,
result: 0,
winnerTeam: 0,
};

build(): BattleResultDto {
return { ...this.base } as BattleResultDto;
setMatchId(id: string): this {
this.base.matchId = id;
return this;
}

setType(type: RequestType): this {
Expand All @@ -29,13 +32,18 @@ export class BattleResultDtoBuilder {
return this;
}

setDuration(duration: number): this {
this.base.duration = duration;
setDuration(d: number): this {
this.base.duration = d;
return this;
}

setWinnerTeam(winnerTeam: number): this {
this.base.winnerTeam = winnerTeam;
setWinnerTeam(team: number): this {
this.base.result = team;
this.base.winnerTeam = team;
return this;
}

build(): BattleResultDto {
return { ...this.base } as BattleResultDto;
}
}
25 changes: 17 additions & 8 deletions src/__tests__/gameData/data/gameData/GameBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Game } from '../../../../gameData/game.schema';
import { ObjectId } from 'mongodb';
import { BattleStatus } from '../../../../gameData/enum/battleStatus.enum';
import { GameType } from '../../../../gameData/enum/gameType.enum';

export class GameBuilder {
private readonly base: Game = {
Expand All @@ -10,7 +12,11 @@ export class GameBuilder {
winner: 1,
startedAt: new Date(),
endedAt: new Date(),
_id: undefined,
_id: undefined as unknown as string,
gameType: GameType.CASUAL,
status: BattleStatus.OPEN,
receivedResults: [],
finalWinner: 0,
};

build(): Game {
Expand All @@ -21,39 +27,42 @@ export class GameBuilder {
this.base.team1 = team1;
return this;
}

setTeam2(team2: string[]): this {
this.base.team2 = team2;
return this;
}

setTeam1Clan(team1Clan: string): this {
this.base.team1Clan = team1Clan;
return this;
}

setTeam2Clan(team2Clan: string): this {
this.base.team2Clan = team2Clan;
return this;
}

setWinner(winner: 1 | 2): this {
this.base.winner = winner;
return this;
}

setStartedAt(date: Date): this {
this.base.startedAt = date;
return this;
}

setEndedAt(date: Date): this {
this.base.endedAt = date;
return this;
}

setId(id: string): this {
this.base._id = id;
return this;
}

setStatus(status: BattleStatus): this {
this.base.status = status;
return this;
}

setGameType(gameType: GameType): this {
this.base.gameType = gameType;
return this;
}
}
13 changes: 12 additions & 1 deletion src/gameData/dto/battleResult.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import {
IsArray,
IsEnum,
IsInt,
IsString,
IsMongoId,
IsPositive,
IsNotEmpty,
Max,
Min,
} from 'class-validator';
Expand All @@ -18,6 +20,15 @@ export class BattleResultDto {
@IsEnum(RequestType)
type: RequestType.RESULT;

/**
* The unique identifier for the battle match.
* This is used to look up the existing game record in the database.
* * @example "665af23e5e982f0013aa9999"
*/
@IsString()
@IsNotEmpty()
matchId: string;

/**
* IDs of players in team 1
*
Expand Down Expand Up @@ -53,5 +64,5 @@ export class BattleResultDto {
@IsInt()
@Min(1)
@Max(2)
winnerTeam: number;
result: number;
}
41 changes: 41 additions & 0 deletions src/gameData/dto/startBattle.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
IsEnum,
IsArray,
IsMongoId,
IsOptional,
IsString,
} from 'class-validator';
import { GameType } from '../enum/gameType.enum';

export class StartBattleDto {
/**
* Type of the game session
* @example "matchmaking"
*/
@IsEnum(GameType)
gameType: GameType;

/**
* List of player IDs for Team 1
* @example ["60f7c2d9a2d3c7b7e56d01df"]
*/
@IsArray()
@IsMongoId({ each: true })
team1: string[];

/**
* List of player IDs for Team 2
* @example ["60f7c2d9a2d3c7b7e56d01df"]
*/
@IsArray()
@IsMongoId({ each: true })
team2: string[];

/**
* Optional custom match ID. If these are not provided, the server generates one.
* @example "match_12345"
*/
@IsOptional()
@IsString()
matchId?: string;
}
36 changes: 36 additions & 0 deletions src/gameData/dto/submitResult.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
IsString,
IsInt,
IsNotEmpty,
Min,
Max,
IsPositive,
} from 'class-validator';

export class SubmitResultDto {
/**
* The unique identifier for the battle match.
* @example "665af23e5e982f0013aa9999"
*/
@IsString()
@IsNotEmpty()
matchId: string;

/**
* Duration of the battle in seconds.
* @example 120
*/
@IsInt()
@IsPositive()
duration: number;

/**
* The result of the battle.
* 1 represents a win for Team 1, 2 represents a win for Team 2.
* @example 1
*/
@IsInt()
@Min(1)
@Max(2)
result: number;
}
18 changes: 18 additions & 0 deletions src/gameData/enum/battleStatus.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Defines all the possible states of a battle.
*/
export enum BattleStatus {
/** Match is registered and awaiting results from the players. */
OPEN = 'OPEN',

/** * Conflicting results detected or 2+ results received.
* Indicates the conflict resolution timer is active.
*/
PROCESSING = 'PROCESSING',

/** Results have been validated and rewards have been given. */
COMPLETED = 'COMPLETED',

/** No results were received within the 30 minute period. */
TIMED_OUT = 'TIMED_OUT',
}
5 changes: 5 additions & 0 deletions src/gameData/enum/gameType.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum GameType {
MATCHMAKING = 'matchmaking',
CASUAL = 'casual',
CUSTOM = 'custom',
}
68 changes: 58 additions & 10 deletions src/gameData/game.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Schema as MongooseSchema } from 'mongoose';
import { ModelName } from '../common/enum/modelName.enum';
import { ExtractField } from '../common/decorator/response/ExtractField';
import { BattleStatus } from './enum/battleStatus.enum';

export type GameDocument = HydratedDocument<Game>;

Expand All @@ -23,32 +24,79 @@ export class Game {

@Prop({
type: MongooseSchema.Types.ObjectId,
required: true,
required: false, // not required anymore due to a business logic change
ref: ModelName.CLAN,
})
team1Clan: string;
team1Clan?: string;

@Prop({
type: MongooseSchema.Types.ObjectId,
required: true,
required: false, // not required anymore due to a business logic change
ref: ModelName.CLAN,
})
team2Clan: string;
team2Clan?: string;

@Prop({ type: String, required: true, default: 'REGULAR' })
gameType: string;

@Prop({ type: String, enum: BattleStatus, default: BattleStatus.OPEN })
status: BattleStatus;

@Prop({
type: [
{
playerId: {
type: MongooseSchema.Types.ObjectId,
ref: ModelName.PLAYER,
},
winnerTeam: Number,
duration: Number,
receivedAt: { type: Date, default: Date.now },
},
],
default: [],
})
receivedResults: {
playerId: string;
winnerTeam: number;
duration: number;
receivedAt?: Date;
}[];

@Prop({ type: Number })
finalWinner: number;

@Prop({ type: Number, enum: [1, 2], required: true })
winner: number;
@Prop({ type: Number, enum: [1, 2] })
winner?: number;

@Prop({ type: Date, required: true })
startedAt: Date;
@Prop({ type: Date })
startedAt?: Date;

@Prop({ type: Date, required: true })
endedAt: Date;
@Prop({ type: Date })
endedAt?: Date;

@ExtractField()
_id: string;
}

export const GameSchema = SchemaFactory.createForClass(Game);

// Making sure players can't be on both teams at the same time
GameSchema.pre('validate', function (next) {
const t1 = (this.team1 || []).map((id) => id.toString());
const t2 = (this.team2 || []).map((id) => id.toString());

const intersection = t1.filter((id) => t2.includes(id));
if (intersection.length > 0) {
return next(
new Error(
`Player ${intersection[0]} cannot be on both teams at the same time`,
),
);
}
next();
});

GameSchema.set('collection', ModelName.GAME);
GameSchema.virtual(ModelName.PLAYER + '1', {
ref: ModelName.PLAYER,
Expand Down
Loading