Files
webapp-back/src/games/services/games.service.ts
T
2024-12-27 22:54:05 +01:00

372 lines
9.8 KiB
TypeScript

import {
forwardRef,
HttpException,
HttpStatus,
Inject,
Injectable,
} from '@nestjs/common';
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
import { Container, Game, Player, Team } from '../schemas/game.schema';
import mongoose, { Document, Model } from 'mongoose';
import { Card } from 'src/cards/schemas/cards.schema';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { WithTransaction, getCurrentTeam, shuffle } from '../utils';
import { TurnService } from './turn.service';
import { CardRole } from 'src/cards/schemas/cards.types';
import { User } from 'src/users/schemas/user.entity';
import { ReadTeamDto } from '../dto/read-games.dto';
@Injectable()
export class GameService {
constructor(
@Inject(forwardRef(() => TurnService))
private readonly turnService: TurnService,
@InjectModel(Game.name) private gameModel: Model<Game>,
@InjectModel(Card.name) private cardModel: Model<Card>,
@InjectConnection() private readonly connection: mongoose.Connection,
private eventEmitter: EventEmitter2,
) {}
@WithTransaction
async endTurn(
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
let currentTeam = getCurrentTeam(game);
for (const player of currentTeam.players) {
if (player.discardMarkers > 0) {
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'player.removeDiscardMarker',
operation: -player.discardMarkers,
discardMarkers: 0,
playerId: player._id.toHexString(),
});
player.discardMarkers = 0;
}
if (player.fuckUMarkers > 0) {
player.fuckUMarkers--;
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'player.removeFuckUMarker',
operation: -player.fuckUMarkers,
fuckUMarkers: 0,
playerId: player._id.toHexString(),
});
player.fuckUMarkers = 0;
}
await this.distributeCards(
player.board.cards.filter((c) => !c.defense),
player.board,
player.discard,
game,
session,
);
await this.drawToHand(5, player, game, session);
}
const index = game.teams.indexOf(currentTeam);
game.currentTurn.currentTeam =
game.teams[(index + 1) % game.teams.length]._id.toHexString();
game.currentTurn.cardsLocked = [];
game.currentTurn.lockedEffects = [];
game.currentTurn.tokens = [];
game.currentTurn.damage = 0;
game.currentTurn.gold = 0;
await game.save({ session });
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'endTurn',
currentTurn: game.currentTurn,
});
currentTeam = getCurrentTeam(game);
for (const player of currentTeam.players) {
for (const card of player.board.cards) {
if (card.defense) {
await this.turnService.lockCard(game, player, card, session);
}
}
}
for (const player of currentTeam.players) {
await this.distributeCards(
player.hand.cards,
player.hand,
player.board,
game,
session,
);
}
await game.save({ session });
}
@WithTransaction
async drawToBoard(
amount,
player: Player,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
await this.drawCards(
amount,
player.stack,
player.board,
player.discard,
game,
session,
);
}
@WithTransaction
async drawToHand(
amount,
player: Player,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
await this.drawCards(
amount,
player.stack,
player.hand,
player.discard,
game,
session,
);
}
@WithTransaction
private async drawCards(
amount: number,
from: Container,
to: Container,
discard: Container,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
const shallow = [...from.cards];
shallow.reverse();
if (amount <= from.cards.length) {
await this.distributeCards(
shallow.slice(0, amount),
from,
to,
game,
session,
);
await game.save({ session });
return;
} else {
const toDraw = from.cards.length;
await this.distributeCards(
shallow.slice(0, toDraw),
from,
to,
game,
session,
);
if (discard.cards.length == 0) {
await game.save({ session });
return;
}
// restack all discarded
await this.distributeCards(discard.cards, discard, from, game, session);
await this.shuffleContainer(from, game, session);
await this.drawCards(amount - toDraw, from, to, discard, game, session);
}
}
@WithTransaction
async fillMarket(
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
if (game.market.cards.length < 5) {
const amount = Math.min(
game.marketStack.cards.length,
5 - game.market.cards.length,
);
await this.distributeCards(
game.marketStack.cards.slice(0, amount),
game.marketStack,
game.market,
game,
session,
);
}
const missingIndexes = game.market.cards.reduce(function (a, e, i) {
if (e === null) a.push(i);
return a;
}, []);
if (missingIndexes.length > 0) {
console.error('not implemented');
}
}
@WithTransaction
async shuffleContainer(
container: Container,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
container.cards = shuffle(container.cards);
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'shuffleContainer',
container: container._id.toHexString(),
});
await game.save({ session });
}
@WithTransaction
async distributeCards(
cards: Card[],
from: Container,
to: Container,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
if (cards.some((c) => !from.cards.some((cc) => cc._id.equals(c._id)))) {
throw new HttpException(
'Card not found in container',
HttpStatus.NOT_FOUND,
);
}
const shallow = [...cards];
for (const c of shallow) {
const index = from.cards.findIndex((cc) => cc?._id.equals(c._id));
from.cards.splice(index, 1);
to.cards.push(c);
}
await game.save({ session });
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'distributeCards',
from: from._id.toHexString(),
to: to._id.toHexString(),
cards: shallow,
});
}
@WithTransaction
async destroyCard(
card: Card,
from: Container,
game: Document<Game> & Game,
session?: mongoose.mongo.ClientSession,
) {
if (card.role == CardRole.FIRE_GEM) {
await this.distributeCards([card], from, game.fireGems, game, session);
return;
}
const index = from.cards.findIndex((cc) => cc?._id.equals(card._id));
from.cards.splice(index, 1);
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'destroyCard',
from: from._id.toHexString(),
card: card._id,
});
await game.save({ session });
}
@WithTransaction
async killTeam(
game: Document<Game> & Game,
team: Team,
session?: mongoose.mongo.ClientSession,
) {
const index = game.teams.findIndex((t) => t._id.equals(team._id));
game.teams.splice(index, 1);
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'killTeam',
teamId: team._id,
});
await game.save({ session });
if (game.teams.length <= 1) {
await this.endGame(game, game.teams[0], session);
return false;
}
return true;
}
@WithTransaction
async leaveGame(
game: Document<Game> & Game,
user: User,
session?: mongoose.mongo.ClientSession,
) {
const team = game.teams.find((t) =>
t.players.find((p) => p.user._id.equals(user._id)),
);
if (team == undefined) {
throw new HttpException('User not in game', HttpStatus.FORBIDDEN);
}
if (game.owner._id.equals(user._id)) {
throw new HttpException(
'You cannot leave a game you own',
HttpStatus.FORBIDDEN,
);
}
const player = team.players.find((p) => p.user._id.equals(user._id));
await this.kickPlayer(game, game.owner, player._id.toHexString(), session);
}
@WithTransaction
async kickPlayer(
game: Document<Game> & Game,
user: User,
kickedPlayerId: string,
session?: mongoose.mongo.ClientSession,
) {
const team = game.teams.find((t) =>
t.players.find((p) => p._id.equals(kickedPlayerId)),
);
if (team == undefined) {
throw new HttpException('User not in game', HttpStatus.FORBIDDEN);
}
const index = team.players.findIndex((p) => p.user._id.equals(user._id));
team.players.splice(index, 1);
this.eventEmitter.emit('sse.lobby', {
type: 'leaveTeam',
playerId: kickedPlayerId,
team: new ReadTeamDto(team),
game: game._id.toHexString(),
});
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'leaveTeam',
team: new ReadTeamDto(team),
playerId: kickedPlayerId,
});
let gameExists = true;
if (team.players.length == 0) {
if (!game.started) {
const teamIndex = game.teams.indexOf(team);
game.teams.splice(teamIndex, 1);
} else {
gameExists = await this.killTeam(game, team, session);
}
}
if (gameExists) {
await game.save({ session });
}
}
@WithTransaction
async endGame(
game: Document<Game> & Game,
winner: Team,
session?: mongoose.mongo.ClientSession,
) {
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'endGame',
winner: winner._id.toHexString(),
});
await game.deleteOne({ session });
}
}