Template
372 lines
9.8 KiB
TypeScript
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 });
|
|
}
|
|
}
|