This commit is contained in:
Jose Conde
2024-07-18 19:42:32 +02:00
parent d322442022
commit 39060a4064
17 changed files with 255 additions and 50 deletions

View File

@ -288,12 +288,19 @@ export class DominoesGame extends EventEmitter {
if (this.winner !== null) {
const winner = this.winner;
winner.score = totalPips;
if (winner.teamedWith !== null) {
winner.teamedWith.score = totalPips;
if (winner.teamedWith !== undefined) {
const p = this.getPlayer(winner.teamedWith)
if (p !== undefined) {
p.score = totalPips;
}
}
}
}
private getPlayer(userId: string) {
return this.players.find(player => player.id === userId) || undefined;
}
private autoDealTiles() {
for (let i = 0; i < this.handSize; i++) {
for (let player of this.players) {

View File

@ -11,6 +11,7 @@ import { GameSummary } from "./dto/GameSummary";
import { PlayerMove } from "./entities/PlayerMove";
import { SessionService } from "../server/services/SessionService";
import { Score } from "../server/db/interfaces";
import { MatchSessionOptions } from "./dto/MatchSessionOptions";
export class MatchSession {
@ -33,26 +34,30 @@ export class MatchSession {
maxPlayers: number = 4;
mode: string = 'classic';
players: PlayerInterface[] = [];
pointsToWin: number = 50;
// pointsToWin: number = 50;
rng!: PRNG
scoreboard: Map<string, number> = new Map();
seed!: string
sessionInProgress: boolean = false;
status: string = 'created'
name: string
constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
constructor(public creator: PlayerInterface, private options: MatchSessionOptions) {
const { sessionName, seed, winType, winTarget } = options;
this.seed = seed || getRandomSeed();
this.id = uuid();
this.name = name || `Game ${this.id}`;
this.name = sessionName || `Match ${this.id}`;
this.addPlayerToSession(creator);
this.creator = creator;
this.logger.info(`Match session created by: ${creator.name}`);
this.logger.info(`Match session ID: ${this.id}`);
this.logger.info(`Match session name: ${this.name}`);
this.logger.info(`Points to win: ${this.pointsToWin}`);
this.sessionInProgress = true;
this.logger.info(`Win type: ${options.winType}`);
this.logger.info(`Win target: ${options.winTarget}`);
this.sessionInProgress = false;
this.waitingForPlayers = true;
this.status = 'created';
this.logger.info('Waiting for players to be ready');
}
@ -103,7 +108,7 @@ export class MatchSession {
}
getPlayer(userId: string) {
return this.players.find(player => player.id === userId);
return this.players.find(player => player.id === userId) || null;
}
playerMove(move: any) {
@ -122,19 +127,42 @@ export class MatchSession {
if (!tile) {
throw new Error("Tile not found");
}
const newMove = new PlayerMove(tile, move.type, move. playerId)
const newMove = new PlayerMove(tile, move.type, move.playerId, move.direction)
this.currentGame.finishTurn(newMove);
}
}
// This is the entry point for the game, method called by session host
async start() {
async start(data: any) {
if (this.matchInProgress) {
throw new Error("Game already in progress");
}
this.waitingForPlayers = false;
this.sessionInProgress = true;
this.status = 'started'
this.sessionService.updateSession(this);
await this.startMatch(this.seed);
}
}
setTeams(data: any) {
if (data.teamedWith !== undefined) {
const creatorTeam = this.getPlayer(data.teamedWith)
if (!creatorTeam) {
throw new Error("Teamed player not found");
}
this.creator.teamedWith = data.teamedWith;
this.creator.team = 1
creatorTeam.teamedWith = this.creator.id;
creatorTeam.team = 1;
const others = this.players.filter(player => player.team === 0);
others[0].teamedWith = others[1].id;
others[0].team = 2;
others[1].teamedWith = others[0].id;
others[1].team = 2;
this.players = [this.creator, others[0], creatorTeam, others[1]];
}
}
addPlayerToSession(player: PlayerInterface) {
if (this.numPlayers >= this.maxPlayers) {
@ -225,7 +253,16 @@ export class MatchSession {
checkMatchWinner() {
const scores = Array.from(this.scoreboard.values());
const maxScore = Math.max(...scores);
if (maxScore >= this.pointsToWin) {
if (this.options.winType === 'rounds') {
this.checkRoundsWinner(maxScore);
} else if (this.options.winType === 'points') {
this.checkPointsWinner(maxScore);
}
}
checkRoundsWinner(maxScore: number) {
if (maxScore >= this.options.winTarget) {
this.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
if (!this.matchWinner) {
throw new Error('Match winner not found');
@ -234,6 +271,20 @@ export class MatchSession {
this.matchInProgress = false;
}
}
checkPointsWinner(maxScore: number) {
if (maxScore >= this.options.winTarget) {
this.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
if (!this.matchWinner) {
throw new Error('Match winner not found');
}
this.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
this.matchInProgress = false;
}
}
resetScoreboard() {
this.scoreboard = new Map();
this.players.forEach(player => {
@ -245,6 +296,22 @@ export class MatchSession {
if (!gameSummary) {
return;
}
if (this.options.winType === 'rounds') {
this.setScoresRounds(gameSummary);
} else if (this.options.winType === 'points') {
this.setScoresPoints(gameSummary);
}
}
setScoresRounds(gameSummary: GameSummary) {
const { winner } = gameSummary;
if (winner !== undefined) {
const currentScore = this.scoreboard.get(winner.name) ?? 0;
this.scoreboard.set(winner.name, 1 + currentScore);
}
}
setScoresPoints(gameSummary: GameSummary) {
const { score } = gameSummary;
score.forEach(playerScore => {
const currentScore = this.scoreboard.get(playerScore.name) ?? 0;
@ -324,12 +391,12 @@ export class MatchSession {
waitingSeconds: this.waitingSeconds,
seed: this.seed,
mode: this.mode,
pointsToWin: this.pointsToWin,
status: this.sessionInProgress ? 'in progress' : 'waiting',
status: this.status,
scoreboard: this.getScoreBoardState(),
matchWinner: this.matchWinner?.getState(true) || null,
matchInProgress: this.matchInProgress,
gameSummaries: this.gameSummaries,
options: this.options,
};
}

View File

@ -0,0 +1,13 @@
export interface MatchSessionOptions {
boardScale?: number
handScale?: number
width?: number
height?: number
background: string
teamed: boolean
winTarget: number
winType: 'points' | 'rounds'
seed: string
sessionName: string
numPlayers: 1 | 2 | 3 | 4
}

View File

@ -1,5 +1,6 @@
import { Score } from "../../server/db/interfaces";
import { GameSummary } from "./GameSummary";
import { MatchSessionOptions } from "./MatchSessionOptions";
import { PlayerDto } from "./PlayerDto";
export interface MatchSessionState {
@ -10,7 +11,6 @@ export interface MatchSessionState {
seed: string;
waitingForPlayers: boolean;
mode: string;
pointsToWin: number;
sessionInProgress: boolean;
status: string;
maxPlayers: number;
@ -21,4 +21,5 @@ export interface MatchSessionState {
matchInProgress: boolean;
playersReady: number,
gameSummaries: GameSummary[];
options: MatchSessionOptions
}

View File

@ -11,7 +11,7 @@ export interface PlayerDto {
name: string;
score?: number;
hand?: TileDto[];
teamedWith?: PlayerDto | null;
teamedWith?: string;
ready: boolean;
isHuman: boolean;
}

View File

@ -4,7 +4,7 @@ import { Tile } from "./Tile";
export class PlayerMove {
id: string = uuid();
constructor(public tile: Tile, public type: PlayerMoveSideType | null, public playerId: string, direction?: string) {}
constructor(public tile: Tile, public type: PlayerMoveSideType | null, public playerId: string, public direction?: string) {}
toString() {
return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`;

View File

@ -13,10 +13,11 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
hand: Tile[] = [];
score: number = 0;
logger: LoggingService = new LoggingService();
teamedWith: PlayerInterface | null = null;
teamedWith?: string;
playerInteraction: PlayerInteractionInterface = undefined as any;
id: string = uuid();
ready: boolean = false;
team: number = 0;
constructor(public name: string) {
super();
@ -58,7 +59,7 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
name: this.name,
score: this.score,
hand: this.hand.map(tile => tile.getState(showPips)),
teamedWith: this.teamedWith?.getState(showPips) ?? null,
teamedWith: this.teamedWith,
ready: this.ready,
isHuman: this instanceof PlayerHuman
};

View File

@ -9,7 +9,8 @@ export interface PlayerInterface {
name: string;
score: number;
hand: Tile[];
teamedWith: PlayerInterface | null;
teamedWith?: string;
team: number;
playerInteraction: PlayerInteractionInterface;
ready: boolean;