working flow
This commit is contained in:
parent
5f117667a4
commit
7741b07d60
20
create.ts
Normal file
20
create.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { UsersService } from './src/server/services/UsersService';
|
||||||
|
|
||||||
|
const usersService = new UsersService();
|
||||||
|
usersService.createUser({
|
||||||
|
firstname: 'Test',
|
||||||
|
lastname: 'User 2',
|
||||||
|
username: 'test2',
|
||||||
|
password: 'qwerty123',
|
||||||
|
roles: ['user'],
|
||||||
|
email: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
usersService.createUser({
|
||||||
|
firstname: 'Test',
|
||||||
|
lastname: 'User 3',
|
||||||
|
username: 'test4',
|
||||||
|
password: 'qwerty123',
|
||||||
|
roles: ['user'],
|
||||||
|
email: ''
|
||||||
|
});
|
22
secret.js
Normal file
22
secret.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random base32-encoded secret for authentication.
|
||||||
|
* @param {number} length - The length of the generated secret.
|
||||||
|
* @returns {string} - The base32-encoded secret.
|
||||||
|
*/
|
||||||
|
function generateRandomSecret(length = 32) {
|
||||||
|
const bytes = crypto.randomBytes(length);
|
||||||
|
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // RFC 4648 Base32 alphabet
|
||||||
|
let secret = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
secret += base32Chars[bytes[i] % 32];
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = generateRandomSecret();
|
||||||
|
console.log('Generated secret:', secret);
|
||||||
|
console.log(crypto.randomBytes(32).toString('hex'))
|
@ -5,9 +5,12 @@ import { PlayerMove } from "./entities/PlayerMove";
|
|||||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
||||||
import { Tile } from "./entities/Tile";
|
import { Tile } from "./entities/Tile";
|
||||||
import { LoggingService } from "../common/LoggingService";
|
import { LoggingService } from "../common/LoggingService";
|
||||||
import { printBoard, printLine, uuid, wait, whileNotUndefined } from '../common/utilities';
|
import { printBoard, printLine, uuid, wait, whileNot, whileNotUndefined } from '../common/utilities';
|
||||||
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||||
import { GameState } from './dto/GameState';
|
import { GameState } from './dto/GameState';
|
||||||
|
import { PlayerHuman } from './entities/player/PlayerHuman';
|
||||||
|
import { PlayerAI } from './entities/player/PlayerAI';
|
||||||
|
import { GameSummary } from './dto/GameSummary';
|
||||||
|
|
||||||
export class DominoesGame extends EventEmitter {
|
export class DominoesGame extends EventEmitter {
|
||||||
private id: string;
|
private id: string;
|
||||||
@ -29,6 +32,7 @@ export class DominoesGame extends EventEmitter {
|
|||||||
lastMove: PlayerMove | null = null;
|
lastMove: PlayerMove | null = null;
|
||||||
forcedInitialPlayerIndex: number | null = null;
|
forcedInitialPlayerIndex: number | null = null;
|
||||||
canAskNextPlayerMove: boolean = true;
|
canAskNextPlayerMove: boolean = true;
|
||||||
|
clientsReady: string[] = [];
|
||||||
|
|
||||||
constructor(public players: PlayerInterface[], seed: PRNG) {
|
constructor(public players: PlayerInterface[], seed: PRNG) {
|
||||||
super();
|
super();
|
||||||
@ -39,6 +43,10 @@ export class DominoesGame extends EventEmitter {
|
|||||||
this.initializeGame();
|
this.initializeGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get numHumanPlayers() {
|
||||||
|
return this.players.filter(player => player instanceof PlayerHuman).length;
|
||||||
|
}
|
||||||
|
|
||||||
async initializeGame() {
|
async initializeGame() {
|
||||||
this.gameOver = false;
|
this.gameOver = false;
|
||||||
this.gameBlocked = false;
|
this.gameBlocked = false;
|
||||||
@ -83,7 +91,13 @@ export class DominoesGame extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isBlocked(): boolean {
|
isBlocked(): boolean {
|
||||||
return this.blockedCount === this.players.length;
|
const freeEnds = this.board.getFreeEnds();
|
||||||
|
const tiles = []
|
||||||
|
for (let player of this.players) {
|
||||||
|
tiles.push(...player.hand);
|
||||||
|
}
|
||||||
|
const canPlay = tiles.some(tile => tile.pips[0] === freeEnds[0] || tile.pips[1] === freeEnds[0] || tile.pips[0] === freeEnds[1] || tile.pips[1] === freeEnds[1]);
|
||||||
|
return !canPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
isGameOver(): boolean {
|
isGameOver(): boolean {
|
||||||
@ -132,6 +146,7 @@ export class DominoesGame extends EventEmitter {
|
|||||||
const player = this.players[this.currentPlayerIndex];
|
const player = this.players[this.currentPlayerIndex];
|
||||||
this.notificationService.sendEventToPlayers('server:next-turn', this.players, this.getGameState());
|
this.notificationService.sendEventToPlayers('server:next-turn', this.players, this.getGameState());
|
||||||
this.logger.debug(`${player.name}'s turn (${player.hand.length} tiles)`);
|
this.logger.debug(`${player.name}'s turn (${player.hand.length} tiles)`);
|
||||||
|
this.printPlayerHand(player);
|
||||||
printBoard(this.board)
|
printBoard(this.board)
|
||||||
player.askForMove(this.board);
|
player.askForMove(this.board);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -139,23 +154,40 @@ export class DominoesGame extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finishTurn(playerMove: PlayerMove | null) {
|
async checkAllClientsReadyToContinue() {
|
||||||
|
try {
|
||||||
|
const conditionFn = () => {
|
||||||
|
this.logger.trace(`Clients ready: ${this.clientsReady.length}/${this.numHumanPlayers}`);
|
||||||
|
return this.clientsReady.length === this.numHumanPlayers
|
||||||
|
}
|
||||||
|
await whileNot(conditionFn, 100);
|
||||||
|
this.clientsReady = [];
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error, 'Error starting game');
|
||||||
|
throw new Error('Error starting game (checkAllClientsReadyToContinue)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async finishTurn(playerMove: PlayerMove | null) {
|
||||||
try {
|
try {
|
||||||
this.lastMove = playerMove;
|
this.lastMove = playerMove;
|
||||||
if (playerMove === null) {
|
if (playerMove === null) {
|
||||||
console.log('Player cannot move');
|
this.logger.info('Player cannot move');
|
||||||
this.blockedCount += 1;
|
this.blockedCount += 1;
|
||||||
|
this.logger.trace(`Blocked count: ${this.blockedCount}`);
|
||||||
this.gameBlocked = this.isBlocked();
|
this.gameBlocked = this.isBlocked();
|
||||||
|
this.notificationService.sendEventToPlayers('server:server-player-move', this.players, { move: playerMove });
|
||||||
if (this.gameBlocked) {
|
if (this.gameBlocked) {
|
||||||
this.gameEnded();
|
this.gameEnded();
|
||||||
return;
|
} else {
|
||||||
|
this.nextPlayer();
|
||||||
|
await this.checkAllClientsReadyToContinue()
|
||||||
|
this.playTurn();
|
||||||
}
|
}
|
||||||
this.nextPlayer();
|
|
||||||
this.playTurn();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const player = this.players[this.currentPlayerIndex];
|
const player = this.players[this.currentPlayerIndex];
|
||||||
|
const skipWaitForConfirmation = player instanceof PlayerAI && playerMove === null;
|
||||||
this.blockedCount = 0;
|
this.blockedCount = 0;
|
||||||
this.board.play(playerMove);
|
this.board.play(playerMove);
|
||||||
player.hand = player.hand.filter(tile => tile !== playerMove.tile);
|
player.hand = player.hand.filter(tile => tile !== playerMove.tile);
|
||||||
@ -164,8 +196,10 @@ export class DominoesGame extends EventEmitter {
|
|||||||
// whileNotUndefined(() => this.canAskNextPlayerMove === true ? {} : undefined);
|
// whileNotUndefined(() => this.canAskNextPlayerMove === true ? {} : undefined);
|
||||||
this.gameOver = this.isGameOver();
|
this.gameOver = this.isGameOver();
|
||||||
if (!this.gameOver) {
|
if (!this.gameOver) {
|
||||||
this.printPlayersHand();
|
this.nextPlayer();
|
||||||
this.nextPlayer();
|
if (!skipWaitForConfirmation) {
|
||||||
|
await this.checkAllClientsReadyToContinue()
|
||||||
|
}
|
||||||
this.playTurn();
|
this.playTurn();
|
||||||
} else {
|
} else {
|
||||||
this.gameEnded();
|
this.gameEnded();
|
||||||
@ -179,12 +213,13 @@ export class DominoesGame extends EventEmitter {
|
|||||||
this.gameInProgress = false;
|
this.gameInProgress = false;
|
||||||
this.winner = this.getWinner();
|
this.winner = this.getWinner();
|
||||||
this.setScores();
|
this.setScores();
|
||||||
const summary = {
|
const summary: GameSummary = {
|
||||||
gameId: this.id,
|
gameId: this.id,
|
||||||
isBlocked: this.gameBlocked,
|
isBlocked: this.gameBlocked,
|
||||||
isTied: this.gameTied,
|
isTied: this.gameTied,
|
||||||
winner: this.winner?.getState(),
|
winner: this.winner?.getState(),
|
||||||
score: this.players.map(player => ({name: player.name, score: player.score}))
|
score: this.players.map(player => ({id: player.id, name: player.name, score: player.score})),
|
||||||
|
players: this.players.map(player => player.getState())
|
||||||
}
|
}
|
||||||
this.emit('game-over', summary);
|
this.emit('game-over', summary);
|
||||||
}
|
}
|
||||||
@ -210,22 +245,25 @@ export class DominoesGame extends EventEmitter {
|
|||||||
|
|
||||||
printPlayersHand() {
|
printPlayersHand() {
|
||||||
for (let player of this.players) {
|
for (let player of this.players) {
|
||||||
this.logger.debug(`${player.name}'s hand (${player.hand.length}): ${player.hand.map(tile => tile.toString())}`);
|
this.printPlayerHand(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printPlayerHand(player: PlayerInterface) {
|
||||||
|
this.logger.debug(`${player.name}'s hand (${player.hand.length}): ${player.hand.map(tile => tile.toString())}`);
|
||||||
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Initalize game
|
// Initalize game
|
||||||
this.gameInProgress = false;
|
this.gameInProgress = false;
|
||||||
this.resetPlayersScore();
|
this.resetPlayersScore();
|
||||||
this.tileSelectionPhase = true;
|
this.tileSelectionPhase = true;
|
||||||
// await this.notificationManager.notifyGameState(this);
|
|
||||||
// await this.notificationManager.notifyPlayersState(this.players);
|
|
||||||
this.deal();
|
this.deal();
|
||||||
const extractStates = (p: PlayerInterface) => {
|
const extractStates = (p: PlayerInterface) => ({
|
||||||
return p.getState()
|
player: p.getState(true),
|
||||||
};
|
gameState: this.getGameState()
|
||||||
|
});
|
||||||
await this.notificationService.sendEventToPlayers('server:hand-dealt', this.players, extractStates);
|
await this.notificationService.sendEventToPlayers('server:hand-dealt', this.players, extractStates);
|
||||||
|
|
||||||
this.tileSelectionPhase = false;
|
this.tileSelectionPhase = false;
|
||||||
@ -237,10 +275,9 @@ export class DominoesGame extends EventEmitter {
|
|||||||
printLine(`${this.players[this.currentPlayerIndex].name} is the starting player:`);
|
printLine(`${this.players[this.currentPlayerIndex].name} is the starting player:`);
|
||||||
this.logger.debug("Before play turn")
|
this.logger.debug("Before play turn")
|
||||||
this.playTurn();
|
this.playTurn();
|
||||||
// await this.notificationManager.notifyGameState(this);
|
|
||||||
// await this.notificationManager.notifyPlayersState(this.players);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, 'Error starting game');
|
this.logger.error(error, 'Error starting game');
|
||||||
|
throw new Error('Error starting game');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,8 +309,6 @@ export class DominoesGame extends EventEmitter {
|
|||||||
while (this.board.boneyard.length > 0) {
|
while (this.board.boneyard.length > 0) {
|
||||||
for (let player of this.players) {
|
for (let player of this.players) {
|
||||||
const choosen = await player.chooseTile(this.board);
|
const choosen = await player.chooseTile(this.board);
|
||||||
// await this.notificationService.notifyGameState(this);
|
|
||||||
// await this.notificationService.notifyPlayersState(this.players);
|
|
||||||
if (this.board.boneyard.length === 0) {
|
if (this.board.boneyard.length === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -292,16 +327,22 @@ export class DominoesGame extends EventEmitter {
|
|||||||
gameBlocked: this.gameBlocked,
|
gameBlocked: this.gameBlocked,
|
||||||
gameTied: this.gameTied,
|
gameTied: this.gameTied,
|
||||||
gameId: this.id,
|
gameId: this.id,
|
||||||
boneyard: this.board.boneyard.map(tile => ({ id: tile.id})),
|
boneyard: this.board.boneyard.map(tile => tile.getState(false)),
|
||||||
players: this.players.map(player => player.getState()),
|
players: this.players.map(player => player.getState()),
|
||||||
currentPlayer: currentPlayer.getState(),
|
currentPlayer: currentPlayer.getState(),
|
||||||
board: this.board.tiles.map(tile => ({
|
board: this.board.tiles.map(tile => tile.getState(true)),
|
||||||
id: tile.id,
|
|
||||||
playerId: tile.playerId,
|
|
||||||
pips: tile.pips
|
|
||||||
})),
|
|
||||||
boardFreeEnds: this.board.getFreeEnds(),
|
boardFreeEnds: this.board.getFreeEnds(),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setClientReady(userId: string) {
|
||||||
|
this.logger.trace(`${userId} - ${this.clientsReady}`);
|
||||||
|
if (!this.clientsReady.includes(userId)) {
|
||||||
|
this.logger.trace(`Client ${userId} is ready`)
|
||||||
|
this.clientsReady.push(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ export class MatchSession {
|
|||||||
private notificationService = new PlayerNotificationService();
|
private notificationService = new PlayerNotificationService();
|
||||||
private winnerIndex: number | null = null;
|
private winnerIndex: number | null = null;
|
||||||
private clientsReady: string[] = [];
|
private clientsReady: string[] = [];
|
||||||
|
private gameSummaries: GameSummary[] = [];
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
matchInProgress: boolean = false;
|
matchInProgress: boolean = false;
|
||||||
@ -35,7 +36,7 @@ export class MatchSession {
|
|||||||
scoreboard: Map<string, number> = new Map();
|
scoreboard: Map<string, number> = new Map();
|
||||||
seed!: string
|
seed!: string
|
||||||
sessionInProgress: boolean = false;
|
sessionInProgress: boolean = false;
|
||||||
state: string = 'created'
|
status: string = 'created'
|
||||||
|
|
||||||
constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
|
constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
|
||||||
this.seed = seed || getRandomSeed();
|
this.seed = seed || getRandomSeed();
|
||||||
@ -71,6 +72,13 @@ export class MatchSession {
|
|||||||
this.logger.trace(`Client ${userId} is ready`)
|
this.logger.trace(`Client ${userId} is ready`)
|
||||||
this.clientsReady.push(userId);
|
this.clientsReady.push(userId);
|
||||||
}
|
}
|
||||||
|
this.logger.trace(`${this.clientsReady.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentGameClientReady(userId: string) {
|
||||||
|
if (this.currentGame) {
|
||||||
|
this.currentGame.setClientReady(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkAllClientsReadyBeforeStart() {
|
async checkAllClientsReadyBeforeStart() {
|
||||||
@ -80,13 +88,15 @@ export class MatchSession {
|
|||||||
this.logger.trace(`Clients ready: ${this.clientsReady.length}/${this.numHumanPlayers}`);
|
this.logger.trace(`Clients ready: ${this.clientsReady.length}/${this.numHumanPlayers}`);
|
||||||
return this.clientsReady.length === this.numHumanPlayers
|
return this.clientsReady.length === this.numHumanPlayers
|
||||||
}
|
}
|
||||||
await whileNot(conditionFn, 10);
|
await whileNot(conditionFn, 50);
|
||||||
this.logger.info(`Game #${this.gameNumber} started`);
|
this.logger.info(`Game #${this.gameNumber} started`);
|
||||||
this.currentGame.start();
|
this.currentGame.start();
|
||||||
this.gameInProgress = true;
|
this.gameInProgress = true;
|
||||||
|
this.clientsReady = [];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, 'Error starting game');
|
this.logger.error(error, 'Error starting game');
|
||||||
|
throw new Error('Error starting game (checkAllClientsReadyBeforeStart)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +105,8 @@ export class MatchSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playerMove(move: any) {
|
playerMove(move: any) {
|
||||||
|
this.logger.trace('Handling player move (playerMove)');
|
||||||
|
this.logger.trace(`${this.clientsReady.length}`);
|
||||||
if (this.currentGame) {
|
if (this.currentGame) {
|
||||||
if ((move === null) || (move === undefined) || move.type === 'pass') {
|
if ((move === null) || (move === undefined) || move.type === 'pass') {
|
||||||
this.currentGame.finishTurn(null);
|
this.currentGame.finishTurn(null);
|
||||||
@ -131,6 +143,7 @@ export class MatchSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private continueMatch(gameSummary: GameSummary) {
|
private continueMatch(gameSummary: GameSummary) {
|
||||||
|
this.gameSummaries.push(gameSummary);
|
||||||
this.winnerIndex = this.players.findIndex(player => player.id === gameSummary?.winner?.id);
|
this.winnerIndex = this.players.findIndex(player => player.id === gameSummary?.winner?.id);
|
||||||
if (this.winnerIndex !== null) {
|
if (this.winnerIndex !== null) {
|
||||||
this.currentGame?.setForcedInitialPlayerIndex(this.winnerIndex);
|
this.currentGame?.setForcedInitialPlayerIndex(this.winnerIndex);
|
||||||
@ -139,19 +152,20 @@ export class MatchSession {
|
|||||||
this.checkMatchWinner();
|
this.checkMatchWinner();
|
||||||
this.resetPlayers();
|
this.resetPlayers();
|
||||||
if (!this.matchInProgress) {
|
if (!this.matchInProgress) {
|
||||||
this.state = 'end'
|
this.status = 'end'
|
||||||
this.notificationService.sendEventToPlayers('server:match-finished', this.players, {
|
this.notificationService.sendEventToPlayers('server:match-finished', this.players, {
|
||||||
lastGame: gameSummary,
|
lastGame: gameSummary,
|
||||||
sessionState: this.getState(),
|
sessionState: this.getState(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.state = 'waiting'
|
this.status = 'waiting'
|
||||||
// await this.playerNotificationManager.notifyMatchState(this);
|
// await this.playerNotificationManager.notifyMatchState(this);
|
||||||
this.notificationService.sendEventToPlayers('server:game-finished', this.players, {
|
this.notificationService.sendEventToPlayers('server:game-finished', this.players, {
|
||||||
lastGame: gameSummary,
|
lastGame: gameSummary,
|
||||||
sessionState: this.getState()
|
sessionState: this.getState()
|
||||||
});
|
});
|
||||||
this.waitingForPlayers = true;
|
this.waitingForPlayers = true;
|
||||||
|
this.startGame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +183,7 @@ export class MatchSession {
|
|||||||
|
|
||||||
private async startMatch(seed: string) {
|
private async startMatch(seed: string) {
|
||||||
try {
|
try {
|
||||||
this.state = 'in-game'
|
this.status = 'in-game'
|
||||||
this.rng = seedrandom(seed);
|
this.rng = seedrandom(seed);
|
||||||
this.resetScoreboard()
|
this.resetScoreboard()
|
||||||
this.gameNumber = 0;
|
this.gameNumber = 0;
|
||||||
@ -180,7 +194,7 @@ export class MatchSession {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
this.matchInProgress = false;
|
this.matchInProgress = false;
|
||||||
this.state = 'error'
|
this.status = 'error'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,11 +310,7 @@ export class MatchSession {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name!,
|
name: this.name!,
|
||||||
creator: this.creator.id,
|
creator: this.creator.id,
|
||||||
players: this.players.map(player =>( {
|
players: this.players.map(player => player.getState()),
|
||||||
id: player.id,
|
|
||||||
name: player.name,
|
|
||||||
ready: player.ready,
|
|
||||||
})),
|
|
||||||
playersReady: this.numPlayersReady,
|
playersReady: this.numPlayersReady,
|
||||||
sessionInProgress: this.sessionInProgress,
|
sessionInProgress: this.sessionInProgress,
|
||||||
maxPlayers: this.maxPlayers,
|
maxPlayers: this.maxPlayers,
|
||||||
@ -313,7 +323,8 @@ export class MatchSession {
|
|||||||
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
||||||
scoreboard: [...this.scoreboard.entries()],
|
scoreboard: [...this.scoreboard.entries()],
|
||||||
matchWinner: this.matchWinner?.getState() || null,
|
matchWinner: this.matchWinner?.getState() || null,
|
||||||
matchInProgress: this.matchInProgress
|
matchInProgress: this.matchInProgress,
|
||||||
|
gameSummaries: this.gameSummaries,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,13 +34,14 @@ export class PlayerInteractionAI implements PlayerInteractionInterface {
|
|||||||
} else {
|
} else {
|
||||||
move = this.chooseTileGreed(board);
|
move = this.chooseTileGreed(board);
|
||||||
}
|
}
|
||||||
const rndWait = Math.floor(Math.random() * 1500) + 2000;
|
this.logger.debug(`AI move: ${move?.tile.pips}`);
|
||||||
|
const rndWait = Math.floor(Math.random() * 1000) + 1000;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.interactionService.playerMove({
|
this.interactionService.playerMoveAI({
|
||||||
sessionId: (<PlayerAI>this.player).sessionId,
|
sessionId: (<PlayerAI>this.player).sessionId,
|
||||||
move
|
move
|
||||||
});
|
});
|
||||||
this.logger.trace('Move sent to server (AI');
|
this.logger.trace('Move sent to server (AI)');
|
||||||
}, rndWait);
|
}, rndWait);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,17 +8,20 @@ import { NetworkPlayer } from './entities/player/NetworkPlayer';
|
|||||||
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
|
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
|
||||||
import { SocketDisconnectedError } from '../common/errors/SocketDisconnectedError';
|
import { SocketDisconnectedError } from '../common/errors/SocketDisconnectedError';
|
||||||
import { InteractionService } from '../server/services/InteractionService';
|
import { InteractionService } from '../server/services/InteractionService';
|
||||||
|
import { LoggingService } from '../common/LoggingService';
|
||||||
|
|
||||||
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
||||||
player: PlayerInterface;
|
player: PlayerInterface;
|
||||||
interactionService: InteractionService = new InteractionService();
|
interactionService: InteractionService = new InteractionService();
|
||||||
clientNotifier = new NetworkClientNotifier();
|
clientNotifier = new NetworkClientNotifier();
|
||||||
|
logger: LoggingService = new LoggingService();
|
||||||
|
|
||||||
constructor(player: PlayerInterface) {
|
constructor(player: PlayerInterface) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
askForMove(board: Board): void {
|
askForMove(board: Board): void {
|
||||||
|
this.logger.trace('Asking for move (Player)');
|
||||||
this.clientNotifier.sendEvent(this.player as NetworkPlayer, 'server:player-turn', {
|
this.clientNotifier.sendEvent(this.player as NetworkPlayer, 'server:player-turn', {
|
||||||
freeHands: board.getFreeEnds(),
|
freeHands: board.getFreeEnds(),
|
||||||
isFirstMove: board.tiles.length === 0
|
isFirstMove: board.tiles.length === 0
|
||||||
|
@ -5,5 +5,6 @@ export interface GameSummary {
|
|||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
isTied: boolean;
|
isTied: boolean;
|
||||||
winner: PlayerDto;
|
winner: PlayerDto;
|
||||||
score: { name: string; score: number; }[]
|
score: { id: string, name: string; score: number; }[],
|
||||||
|
players?: PlayerDto[];
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { GameSummary } from "./GameSummary";
|
||||||
import { PlayerDto } from "./PlayerDto";
|
import { PlayerDto } from "./PlayerDto";
|
||||||
|
|
||||||
export interface MatchSessionState {
|
export interface MatchSessionState {
|
||||||
@ -17,5 +18,6 @@ export interface MatchSessionState {
|
|||||||
scoreboard: [string, number][];
|
scoreboard: [string, number][];
|
||||||
matchWinner: PlayerDto | null;
|
matchWinner: PlayerDto | null;
|
||||||
matchInProgress: boolean;
|
matchInProgress: boolean;
|
||||||
playersReady: number
|
playersReady: number,
|
||||||
|
gameSummaries: GameSummary[];
|
||||||
}
|
}
|
@ -1,8 +1,16 @@
|
|||||||
|
export interface TileDto {
|
||||||
|
id: string;
|
||||||
|
pips?: number[];
|
||||||
|
flipped: boolean;
|
||||||
|
revealed: boolean;
|
||||||
|
playerId: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PlayerDto {
|
export interface PlayerDto {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
score?: number;
|
score?: number;
|
||||||
hand?: any[];
|
hand?: TileDto[];
|
||||||
teamedWith?: PlayerDto | null;
|
teamedWith?: PlayerDto | null;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
}
|
}
|
@ -28,6 +28,16 @@ export class Tile {
|
|||||||
this.flipped = !this.flipped;
|
this.flipped = !this.flipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getState(showPips: boolean = false) {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
pips: showPips ? this.pips : undefined,
|
||||||
|
flipped: this.flipped,
|
||||||
|
revealed: this.revealed,
|
||||||
|
playerId: this.playerId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
if (!this.revealed) {
|
if (!this.revealed) {
|
||||||
return '[ | ]';
|
return '[ | ]';
|
||||||
|
@ -6,7 +6,7 @@ import { LoggingService } from "../../../common/LoggingService";
|
|||||||
import { EventEmitter } from "stream";
|
import { EventEmitter } from "stream";
|
||||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||||
import { uuid } from "../../../common/utilities";
|
import { uuid } from "../../../common/utilities";
|
||||||
import { PlayerDto } from "../../dto/PlayerDto";
|
import { PlayerDto, TileDto } from "../../dto/PlayerDto";
|
||||||
|
|
||||||
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
|
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
|
||||||
hand: Tile[] = [];
|
hand: Tile[] = [];
|
||||||
@ -56,19 +56,8 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
score: this.score,
|
score: this.score,
|
||||||
hand: this.hand.map(tile => {
|
hand: this.hand.map(tile => tile.getState(showPips)),
|
||||||
const d = {
|
teamedWith: this.teamedWith?.getState(showPips) ?? null,
|
||||||
id: tile.id,
|
|
||||||
pips: tile.pips,
|
|
||||||
flipped: tile.revealed,
|
|
||||||
playerId: tile.playerId,
|
|
||||||
};
|
|
||||||
if (showPips) {
|
|
||||||
d.pips = tile.pips;
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}),
|
|
||||||
teamedWith: this.teamedWith?.getState() ?? null,
|
|
||||||
ready: this.ready,
|
ready: this.ready,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||||
import { Board } from "../Board";
|
import { Board } from "../Board";
|
||||||
import { GameState } from "../../dto/GameState";
|
|
||||||
import { PlayerMove } from "../PlayerMove";
|
import { PlayerMove } from "../PlayerMove";
|
||||||
import { Tile } from "../Tile";
|
import { Tile } from "../Tile";
|
||||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
|
||||||
import { PlayerDto } from "../../dto/PlayerDto";
|
import { PlayerDto } from "../../dto/PlayerDto";
|
||||||
|
|
||||||
export interface PlayerInterface {
|
export interface PlayerInterface {
|
||||||
@ -24,5 +22,5 @@ export interface PlayerInterface {
|
|||||||
sendEvent(event: string, data: any): Promise<void>;
|
sendEvent(event: string, data: any): Promise<void>;
|
||||||
sendEventWithAck(event: string, data: any): Promise<any>;
|
sendEventWithAck(event: string, data: any): Promise<any>;
|
||||||
|
|
||||||
getState(): PlayerDto;
|
getState(showPips?: boolean): PlayerDto;
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ export class ApiKeyController extends BaseController{
|
|||||||
async createApiKey(req: Request, res: Response) {
|
async createApiKey(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const token: Token = this._createTokenObject(req);
|
const token: Token = this._createTokenObject(req);
|
||||||
await this.apiTokenManager.addToken(token);
|
await this.apiTokenManager.create(token);
|
||||||
res.status(201).end();
|
res.status(201).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(res, error);
|
this.handleError(res, error);
|
||||||
@ -62,7 +62,7 @@ export class ApiKeyController extends BaseController{
|
|||||||
async createNamespaceApiKey(req: Request, res: Response) {
|
async createNamespaceApiKey(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const token = this._createTokenObject(req);
|
const token = this._createTokenObject(req);
|
||||||
await this.apiTokenManager.addToken(token);
|
await this.apiTokenManager.create(token);
|
||||||
res.status(201).end();
|
res.status(201).end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(res, error);
|
this.handleError(res, error);
|
||||||
@ -81,9 +81,9 @@ export class ApiKeyController extends BaseController{
|
|||||||
type
|
type
|
||||||
};
|
};
|
||||||
if (type === 'namespace') {
|
if (type === 'namespace') {
|
||||||
newToken.namespaceId = toObjectId(namespaceId);
|
newToken.namespaceId = namespaceId;
|
||||||
} else if (type === 'user') {
|
} else if (type === 'user') {
|
||||||
newToken.userId = toObjectId(userId);
|
newToken.userId = userId;
|
||||||
}
|
}
|
||||||
return newToken;
|
return newToken;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ export class UserController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.temporalTokenManager.deleteAllByUserAndType(userId.toString(), TemporalTokenMongoManager.Types.PASSWORD_RECOVERY);
|
this.temporalTokenManager.deleteAllByUserAndType(userId.toString(), TemporalTokenMongoManager.Types.PASSWORD_RECOVERY);
|
||||||
this.temporalTokenManager.addToken(temporalToken);
|
this.temporalTokenManager.create(temporalToken);
|
||||||
await this.mailService.sendRecoveryPasswordEmail(firstname, lastname, email, pin);
|
await this.mailService.sendRecoveryPasswordEmail(firstname, lastname, email, pin);
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -14,7 +14,7 @@ export function matchSessionAdapter(session: MatchSession) : DbMatchSession {
|
|||||||
numPlayers: session.numPlayers,
|
numPlayers: session.numPlayers,
|
||||||
scoreboard: Array.from(session.scoreboard.entries()).map(([player, score]) => ({ player, score })),
|
scoreboard: Array.from(session.scoreboard.entries()).map(([player, score]) => ({ player, score })),
|
||||||
matchWinner: session.matchWinner ? session.matchWinner.id : null,
|
matchWinner: session.matchWinner ? session.matchWinner.id : null,
|
||||||
state: session.state
|
status: session.status
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
import { ObjectId } from "mongodb";
|
|
||||||
|
|
||||||
export interface Entity {
|
export interface Entity {
|
||||||
createdAt?: number | null;
|
createdAt?: number;
|
||||||
modifiedAt?: number | null;
|
modifiedAt?: number;
|
||||||
createdBy?: ObjectId | null;
|
createdBy?: string;
|
||||||
modifiedBy?: ObjectId | null;
|
modifiedBy?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityMongo extends Entity {
|
export interface EntityMongo extends Entity {
|
||||||
_id?: ObjectId;
|
_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Score {
|
export interface Score {
|
||||||
@ -21,14 +19,14 @@ export interface Namespace extends EntityMongo {
|
|||||||
description?: string;
|
description?: string;
|
||||||
default: boolean;
|
default: boolean;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
ownerId?: ObjectId;
|
ownerId?: string;
|
||||||
users?: any[];
|
users?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User extends EntityMongo {
|
export interface User extends EntityMongo {
|
||||||
id: string,
|
id: string,
|
||||||
username: string;
|
username: string;
|
||||||
namespaceId: ObjectId;
|
namespaceId: string;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
firstname?: string;
|
firstname?: string;
|
||||||
@ -55,13 +53,13 @@ export interface DbMatchSession extends EntityMongo {
|
|||||||
numPlayers: number;
|
numPlayers: number;
|
||||||
scoreboard: Score[];
|
scoreboard: Score[];
|
||||||
matchWinner: string | null;
|
matchWinner: string | null;
|
||||||
state: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DbUser extends EntityMongo {
|
export interface DbUser extends EntityMongo {
|
||||||
id: string,
|
id: string,
|
||||||
username: string;
|
username: string;
|
||||||
namespaceId: ObjectId;
|
namespaceId: string;
|
||||||
hash?: string;
|
hash?: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
firstname?: string;
|
firstname?: string;
|
||||||
@ -77,16 +75,16 @@ export interface DbNamespace extends EntityMongo {
|
|||||||
description?: string;
|
description?: string;
|
||||||
default: boolean;
|
default: boolean;
|
||||||
type: string | null;
|
type: string | null;
|
||||||
ownerId?: ObjectId;
|
ownerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Token extends EntityMongo {
|
export interface Token extends EntityMongo {
|
||||||
token: string;
|
token: string;
|
||||||
userId?: ObjectId;
|
userId?: string;
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
expiresAt?: number | null;
|
expiresAt?: number | null;
|
||||||
type: string;
|
type: string;
|
||||||
namespaceId?: ObjectId
|
namespaceId?: string
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,3 +98,14 @@ export interface Role {
|
|||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DbListResponse{
|
||||||
|
pagination?: {
|
||||||
|
page: number;
|
||||||
|
next?: number;
|
||||||
|
size: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
|
sort?: any;
|
||||||
|
data: EntityMongo[];
|
||||||
|
}
|
@ -5,13 +5,6 @@ import { Token } from '../interfaces';
|
|||||||
export class ApiTokenMongoManager extends BaseMongoManager{
|
export class ApiTokenMongoManager extends BaseMongoManager{
|
||||||
collection = 'tokens';
|
collection = 'tokens';
|
||||||
|
|
||||||
async addToken(token: Token) {
|
|
||||||
return await mongoExecute(async ({ collection }) => {
|
|
||||||
await collection?.insertOne(token);
|
|
||||||
return token;
|
|
||||||
}, { colName: this.collection });
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTokens(userId: string): Promise<Token[]> {
|
async getTokens(userId: string): Promise<Token[]> {
|
||||||
return await mongoExecute(async ({ collection }) => {
|
return await mongoExecute(async ({ collection }) => {
|
||||||
return await collection?.find({ userId: this.toObjectId(userId) }).toArray();
|
return await collection?.find({ userId: this.toObjectId(userId) }).toArray();
|
||||||
|
@ -11,19 +11,6 @@ export class NamespacesMongoManager extends BaseMongoManager{
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createNamespace(namespace: Namespace): Promise<string> {
|
|
||||||
return await mongoExecute(async ({collection}) => {
|
|
||||||
const now = new Date().getTime();
|
|
||||||
delete namespace.users;
|
|
||||||
const result = await collection?.insertOne({
|
|
||||||
...namespace,
|
|
||||||
createdAt: now,
|
|
||||||
modifiedAt: now
|
|
||||||
});
|
|
||||||
return result?.insertedId.toString() || '';
|
|
||||||
}, {colName: this.collection})
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateNamespace(id: string, namespace: Namespace): Promise<number> {
|
async updateNamespace(id: string, namespace: Namespace): Promise<number> {
|
||||||
return await mongoExecute(async ({collection}) => {
|
return await mongoExecute(async ({collection}) => {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { mongoExecute } from "./mongoDBPool";
|
import { mongoExecute } from "./mongoDBPool";
|
||||||
import { Entity, EntityMongo } from "../../interfaces";
|
import { DbListResponse, Entity, EntityMongo } from "../../interfaces";
|
||||||
import { LoggingService } from "../../../../common/LoggingService";
|
import { LoggingService } from "../../../../common/LoggingService";
|
||||||
import toObjectId from "./mongoUtils";
|
import toObjectId from "./mongoUtils";
|
||||||
|
|
||||||
@ -9,12 +8,12 @@ export abstract class BaseMongoManager {
|
|||||||
protected abstract collection?: string;
|
protected abstract collection?: string;
|
||||||
logger = new LoggingService().logger;
|
logger = new LoggingService().logger;
|
||||||
|
|
||||||
async create(data: Entity): Promise<ObjectId | undefined> {
|
async create(data: EntityMongo): Promise<string | undefined> {
|
||||||
this.stampEntity(data);
|
this.stampEntity(data);
|
||||||
return mongoExecute(
|
return mongoExecute(
|
||||||
async ({ collection }) => {
|
async ({ collection }) => {
|
||||||
const result = await collection?.insertOne(data as any);
|
const result = await collection?.insertOne(data as any);
|
||||||
return result?.insertedId;
|
return result?.insertedId.toString() || undefined;
|
||||||
},
|
},
|
||||||
{ colName: this.collection }
|
{ colName: this.collection }
|
||||||
);
|
);
|
||||||
@ -58,7 +57,7 @@ export abstract class BaseMongoManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(sortCriteria?: any, pagination?: {pageSize: number, page: number}): Promise<EntityMongo[]> {
|
async list(sortCriteria?: any, pagination?: {pageSize: number, page: number}): Promise<DbListResponse> {
|
||||||
return mongoExecute(
|
return mongoExecute(
|
||||||
async ({ collection }) => {
|
async ({ collection }) => {
|
||||||
const cursor = collection?.find();
|
const cursor = collection?.find();
|
||||||
@ -68,13 +67,39 @@ export abstract class BaseMongoManager {
|
|||||||
if (pagination) {
|
if (pagination) {
|
||||||
cursor?.skip(pagination.pageSize * (pagination.page - 1)).limit(pagination.pageSize);
|
cursor?.skip(pagination.pageSize * (pagination.page - 1)).limit(pagination.pageSize);
|
||||||
}
|
}
|
||||||
return await cursor?.toArray();
|
const cursorArray = (await cursor?.toArray()) || []
|
||||||
|
const data = cursorArray.map((item: any) => {
|
||||||
|
item._id = item._id.toString();
|
||||||
|
return item;
|
||||||
|
}) as EntityMongo[]
|
||||||
|
|
||||||
|
const listResponse: DbListResponse = {
|
||||||
|
data
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination) {
|
||||||
|
const total = await collection?.countDocuments() || 0;
|
||||||
|
const totalPages = Math.ceil(total / pagination.pageSize);
|
||||||
|
listResponse.pagination = {
|
||||||
|
page: pagination.page,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
next: totalPages > pagination.page ? pagination.page + 1 : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortCriteria) {
|
||||||
|
listResponse.sort = sortCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
return listResponse;
|
||||||
},
|
},
|
||||||
{ colName: this.collection }
|
{ colName: this.collection }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listByFilter(filter: any, sortCriteria?: any, pagination?: {pageSize: number, page: number}): Promise<EntityMongo[]> {
|
async listByFilter(filter: any, sortCriteria?: any, pagination?: {pageSize: number, page: number}): Promise<DbListResponse> {
|
||||||
return mongoExecute(
|
return mongoExecute(
|
||||||
async ({ collection }) => {
|
async ({ collection }) => {
|
||||||
const cursor = collection?.find(filter);
|
const cursor = collection?.find(filter);
|
||||||
@ -84,7 +109,35 @@ export abstract class BaseMongoManager {
|
|||||||
if (pagination) {
|
if (pagination) {
|
||||||
cursor?.skip(pagination.pageSize * (pagination.page - 1)).limit(pagination.pageSize);
|
cursor?.skip(pagination.pageSize * (pagination.page - 1)).limit(pagination.pageSize);
|
||||||
}
|
}
|
||||||
return await cursor?.toArray();
|
|
||||||
|
const cursorArray = (await cursor?.toArray()) || []
|
||||||
|
const data = cursorArray.map((item: any) => {
|
||||||
|
item._id = item._id.toString();
|
||||||
|
return item;
|
||||||
|
}) as EntityMongo[]
|
||||||
|
|
||||||
|
const listResponse: DbListResponse = {
|
||||||
|
data
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pagination) {
|
||||||
|
const total = await collection?.countDocuments(filter) || 0;
|
||||||
|
const totalPages = Math.ceil(total / pagination.pageSize);
|
||||||
|
listResponse.pagination = {
|
||||||
|
page: pagination.page,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
total,
|
||||||
|
totalPages,
|
||||||
|
next: totalPages > pagination.page ? pagination.page + 1 : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortCriteria) {
|
||||||
|
listResponse.sort = sortCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
return listResponse;
|
||||||
},
|
},
|
||||||
{ colName: this.collection }
|
{ colName: this.collection }
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
|
|
||||||
export default function toObjectId(id: string) {
|
export default function toObjectId(oid: string | ObjectId): ObjectId {
|
||||||
return ObjectId.createFromHexString(id);
|
if (oid instanceof ObjectId) {
|
||||||
|
return oid;
|
||||||
|
}
|
||||||
|
return ObjectId.createFromHexString(oid);
|
||||||
}
|
}
|
@ -27,14 +27,18 @@ export class InteractionService extends ServiceBase{
|
|||||||
this.logger.trace(`Handling event: ${event}`);
|
this.logger.trace(`Handling event: ${event}`);
|
||||||
switch(event) {
|
switch(event) {
|
||||||
case 'client:player-move':
|
case 'client:player-move':
|
||||||
this.onClientMoveResponse(eventData);
|
this.playerMoveHuman(eventData);
|
||||||
break;
|
break;
|
||||||
|
case 'client:set-client-ready-for-next-game':
|
||||||
case 'client:set-client-ready':
|
case 'client:set-client-ready':
|
||||||
this.onClientReady(eventData);
|
this.onClientReady(eventData);
|
||||||
break;
|
break;
|
||||||
case EventActions.PLAYER_READY:
|
case EventActions.PLAYER_READY:
|
||||||
this.onPlayerReady(eventData);
|
this.onPlayerReady(eventData);
|
||||||
break;
|
break;
|
||||||
|
case 'client:animation-ended':
|
||||||
|
this.onClientsAnimationEnded(eventData);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
PubSub.publish(event, eventData);
|
PubSub.publish(event, eventData);
|
||||||
break;
|
break;
|
||||||
@ -60,7 +64,9 @@ export class InteractionService extends ServiceBase{
|
|||||||
for (let i = 0; i < missingHumans; i++) {
|
for (let i = 0; i < missingHumans; i++) {
|
||||||
session.addPlayerToSession(session.createPlayerAI(i));
|
session.addPlayerToSession(session.createPlayerAI(i));
|
||||||
}
|
}
|
||||||
this.notifyService.sendEventToPlayers('server:match-starting', session.players);
|
this.notifyService.sendEventToPlayers('server:match-starting', session.players, {
|
||||||
|
sessionState: session.getState()
|
||||||
|
});
|
||||||
session.start();
|
session.start();
|
||||||
return {
|
return {
|
||||||
status: 'ok'
|
status: 'ok'
|
||||||
@ -68,15 +74,23 @@ export class InteractionService extends ServiceBase{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public playerMove(data: any) {
|
public playerMoveAI(data: any) {
|
||||||
this.onClientMoveResponse(data);
|
this.onClientMoveResponse(data, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClientMoveResponse(data: any): any {
|
public playerMoveHuman(data: any) {
|
||||||
|
this.onClientMoveResponse(data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClientMoveResponse(data: any, notifyClientReady: boolean = false): any {
|
||||||
|
this.logger.trace('Handling player move (onClientMoveResponse)');
|
||||||
const { sessionId, move }: { sessionId: string, move: PlayerMove } = data;
|
const { sessionId, move }: { sessionId: string, move: PlayerMove } = data;
|
||||||
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
||||||
if (session !== undefined) {
|
if (session !== undefined) {
|
||||||
session.playerMove(move);
|
if (notifyClientReady) {
|
||||||
|
session.setCurrentGameClientReady(move.playerId);
|
||||||
|
}
|
||||||
|
session.playerMove(move);
|
||||||
return {
|
return {
|
||||||
status: 'ok'
|
status: 'ok'
|
||||||
};
|
};
|
||||||
@ -100,8 +114,19 @@ export class InteractionService extends ServiceBase{
|
|||||||
return {
|
return {
|
||||||
status: 'ok'
|
status: 'ok'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClientsAnimationEnded(data: any): any {
|
||||||
|
const { sessionId, userId } = data;
|
||||||
|
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
||||||
|
session?.setCurrentGameClientReady(userId);
|
||||||
|
return {
|
||||||
|
status: 'ok'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public updateSocketId(sessionId: string, userId: string, socketId: string): any {
|
public updateSocketId(sessionId: string, userId: string, socketId: string): any {
|
||||||
return this.sessionManager.updateSocketId(sessionId, userId, socketId);
|
return this.sessionManager.updateSocketId(sessionId, userId, socketId);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Namespace, User } from "../db/interfaces";
|
import { Namespace, User } from "../db/interfaces";
|
||||||
|
import toObjectId from "../db/mongo/common/mongoUtils";
|
||||||
import { NamespacesMongoManager } from "../db/mongo/NamespacesMongoManager";
|
import { NamespacesMongoManager } from "../db/mongo/NamespacesMongoManager";
|
||||||
import { UsersService } from "./UsersService";
|
import { UsersService } from "./UsersService";
|
||||||
|
|
||||||
@ -7,8 +8,12 @@ export class NamespacesService {
|
|||||||
usersService = new UsersService();
|
usersService = new UsersService();
|
||||||
|
|
||||||
async createNamespace(namespace: Namespace, user: User) {
|
async createNamespace(namespace: Namespace, user: User) {
|
||||||
const insertedId = await this.namespacesManager.createNamespace({ ownerId: user._id, ...namespace, createdBy: user._id });
|
const userId = user._id
|
||||||
await this._updateNamespaceUsers(namespace.users ?? [], insertedId);
|
if (userId === undefined) return undefined;
|
||||||
|
|
||||||
|
const insertedId = await this.namespacesManager.create({ ownerId: userId, createdBy: userId, ...namespace });
|
||||||
|
if (insertedId === undefined) return undefined;
|
||||||
|
await this._updateNamespaceUsers(namespace.users ?? [], insertedId.toString());
|
||||||
return insertedId;
|
return insertedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer";
|
|||||||
import { MatchSession } from "../../game/MatchSession";
|
import { MatchSession } from "../../game/MatchSession";
|
||||||
import { PlayerNotificationService } from "./PlayerNotificationService";
|
import { PlayerNotificationService } from "./PlayerNotificationService";
|
||||||
import { matchSessionAdapter } from "../db/DbAdapter";
|
import { matchSessionAdapter } from "../db/DbAdapter";
|
||||||
import { DbMatchSession, DbMatchSessionUpdate } from "../db/interfaces";
|
import { DbListResponse, DbMatchSession, DbMatchSessionUpdate } from "../db/interfaces";
|
||||||
import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
|
import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
|
||||||
import { SessionManager } from "../managers/SessionManager";
|
import { SessionManager } from "../managers/SessionManager";
|
||||||
import { ServiceBase } from "./ServiceBase";
|
import { ServiceBase } from "./ServiceBase";
|
||||||
@ -62,11 +62,11 @@ export class SessionService extends ServiceBase{
|
|||||||
return sessionId
|
return sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listJoinableSessions(): Promise<DbMatchSession[]> {
|
public async listJoinableSessions(): Promise<DbListResponse> {
|
||||||
return await this.dbManager.listByFilter(
|
return await this.dbManager.listByFilter(
|
||||||
{ state: 'created' },
|
{ status: 'created' },
|
||||||
{ createdAt: -1 },
|
{ createdAt: -1 },
|
||||||
{ page: 1, pageSize: 5 }) as DbMatchSession[];
|
{ page: 1, pageSize: 12 }) as DbListResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSession(sessionId: string): Promise<DbMatchSession | undefined> {
|
public async getSession(sessionId: string): Promise<DbMatchSession | undefined> {
|
||||||
@ -76,8 +76,8 @@ export class SessionService extends ServiceBase{
|
|||||||
public async deleteSession(sessionId: string): Promise<any> {
|
public async deleteSession(sessionId: string): Promise<any> {
|
||||||
this.sessionManager.deleteSession(sessionId);
|
this.sessionManager.deleteSession(sessionId);
|
||||||
const session = {
|
const session = {
|
||||||
_id: toObjectId(sessionId),
|
_id: sessionId,
|
||||||
state: 'deleted'
|
status: 'deleted'
|
||||||
} as DbMatchSessionUpdate;
|
} as DbMatchSessionUpdate;
|
||||||
return this.dbManager.update(session);
|
return this.dbManager.update(session);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ export class SocketIoService extends ServiceBase{
|
|||||||
if (event.startsWith('client:') && args.length > 0) {
|
if (event.startsWith('client:') && args.length > 0) {
|
||||||
logStr = `${logStr} (${args[0].event})`;
|
logStr = `${logStr} (${args[0].event})`;
|
||||||
}
|
}
|
||||||
this.logger.debug(logStr);
|
this.logger.trace(logStr);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pingClients()
|
this.pingClients()
|
||||||
|
@ -69,7 +69,7 @@ export class UsersService extends ServiceBase {
|
|||||||
|
|
||||||
this.logger.info(`${password === undefined}`);
|
this.logger.info(`${password === undefined}`);
|
||||||
if (_id !== undefined) {
|
if (_id !== undefined) {
|
||||||
user._id = toObjectId(_id);
|
user._id = _id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password !== undefined && typeof password === 'string' && password.length > 0) {
|
if (password !== undefined && typeof password === 'string' && password.length > 0) {
|
||||||
|
34
src/test.ts
34
src/test.ts
@ -1,7 +1,7 @@
|
|||||||
import { PlayerAI } from "./game/entities/player/PlayerAI";
|
import { PlayerAI } from "./game/entities/player/PlayerAI";
|
||||||
import { PlayerHuman } from "./game/entities/player/PlayerHuman";
|
import { PlayerHuman } from "./game/entities/player/PlayerHuman";
|
||||||
import {LoggingService} from "./common/LoggingService";
|
import {LoggingService} from "./common/LoggingService";
|
||||||
import { GameSession } from "./game/GameSession";
|
import { MatchSession } from "./game/MatchSession";
|
||||||
|
|
||||||
console.log('process.arg :>> ', process.argv);
|
console.log('process.arg :>> ', process.argv);
|
||||||
|
|
||||||
@ -18,35 +18,35 @@ console.log('process.arg :>> ', process.argv);
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
async function playSolo(seed?: string) {
|
async function playSolo(seed?: string) {
|
||||||
const session = new GameSession(new PlayerHuman( "Jose"), "Test Game");
|
const session = new MatchSession(new PlayerHuman( "Jose"), "Test Game");
|
||||||
console.log(`Session (${session.id}) created by: ${session.creator.name}`);
|
console.log(`Session (${session.id}) created by: ${session.creator.name}`);
|
||||||
setTimeout(() => session.addPlayer(new PlayerAI("AI 2")), 1000);
|
setTimeout(() => session.addPlayerToSession(new PlayerAI("AI 2")), 1000);
|
||||||
setTimeout(() => session.addPlayer(new PlayerAI("AI 3")), 2000);
|
setTimeout(() => session.addPlayerToSession(new PlayerAI("AI 3")), 2000);
|
||||||
setTimeout(() => session.addPlayer(new PlayerAI("AI 4")), 3000);
|
setTimeout(() => session.addPlayerToSession(new PlayerAI("AI 4")), 3000);
|
||||||
session.start(seed);
|
session.start(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playHumans(seed?: string) {
|
async function playHumans(seed?: string) {
|
||||||
const session = new GameSession(new PlayerHuman("Jose"), "Test Game");
|
const session = new MatchSession(new PlayerHuman("Jose"), "Test Game");
|
||||||
session.addPlayer(new PlayerHuman("Pepe"));
|
session.addPlayerToSession(new PlayerHuman("Pepe"));
|
||||||
session.addPlayer(new PlayerHuman("Juan"));
|
session.addPlayerToSession(new PlayerHuman("Juan"));
|
||||||
session.addPlayer(new PlayerHuman("Luis"));
|
session.addPlayerToSession(new PlayerHuman("Luis"));
|
||||||
session.start(seed);
|
session.start(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playAIs(seed?: string) {
|
async function playAIs(seed?: string) {
|
||||||
const session = new GameSession(new PlayerAI("AI 1"), "Test Game");
|
const session = new MatchSession(new PlayerAI("AI 1"), "Test Game");
|
||||||
session.addPlayer(new PlayerAI("AI 2"));
|
session.addPlayerToSession(new PlayerAI("AI 2"));
|
||||||
session.addPlayer(new PlayerAI("AI 3"));
|
session.addPlayerToSession(new PlayerAI("AI 3"));
|
||||||
session.addPlayer(new PlayerAI("AI 4"));
|
session.addPlayerToSession(new PlayerAI("AI 4"));
|
||||||
session.start(seed);
|
session.start(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function playTeams(seed?: string) {
|
async function playTeams(seed?: string) {
|
||||||
const session = new GameSession(new PlayerHuman("Jose"), "Test Game");
|
const session = new MatchSession(new PlayerHuman("Jose"), "Test Game");
|
||||||
session.addPlayer(new PlayerAI("AI 1"));
|
session.addPlayerToSession(new PlayerAI("AI 1"));
|
||||||
session.addPlayer(new PlayerHuman("Juan"));
|
session.addPlayerToSession(new PlayerHuman("Juan"));
|
||||||
session.addPlayer(new PlayerAI("AI 2"));
|
session.addPlayerToSession(new PlayerAI("AI 2"));
|
||||||
session.start(seed);
|
session.start(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
206
text.txt
Normal file
206
text.txt
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
To add a player that uses a client connected by a socket in your dominoes game, you'll need to integrate WebSockets or Socket.io to enable real-time communication between the server and the client. This allows players to interact with the game from their web browsers or other client applications.
|
||||||
|
|
||||||
|
Here’s a step-by-step guide to adding a socket-connected player to your game:
|
||||||
|
|
||||||
|
### 1. **Setup Socket.io on the Server**
|
||||||
|
|
||||||
|
Socket.io provides a straightforward way to handle real-time communication.
|
||||||
|
|
||||||
|
#### **Install Dependencies**
|
||||||
|
|
||||||
|
First, install the necessary dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install express socket.io
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Server Setup**
|
||||||
|
|
||||||
|
Create an Express server and integrate Socket.io.
|
||||||
|
|
||||||
|
**server.ts**:
|
||||||
|
```typescript
|
||||||
|
import express from 'express';
|
||||||
|
import http from 'http';
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
import { DominoesGame, Player, AIPlayer } from './dominoes-game'; // Adjust the path as needed
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const server = http.createServer(app);
|
||||||
|
const io = new Server(server);
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
let game: DominoesGame;
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.send('Dominoes Game Server');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Server running on http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Integrate Socket.io into the Game**
|
||||||
|
|
||||||
|
Modify the `DominoesGame` class to support socket-connected players.
|
||||||
|
|
||||||
|
**dominoes-game.ts**:
|
||||||
|
```typescript
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
|
||||||
|
interface Player {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
hand: number[][];
|
||||||
|
isAI: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DominoesGame {
|
||||||
|
players: Player[];
|
||||||
|
board: number[][];
|
||||||
|
remainingTiles: number[][];
|
||||||
|
currentPlayerIndex: number;
|
||||||
|
gameOver: boolean;
|
||||||
|
io: Server;
|
||||||
|
|
||||||
|
constructor(io: Server) {
|
||||||
|
this.players = [];
|
||||||
|
this.board = [];
|
||||||
|
this.remainingTiles = this.generateTiles();
|
||||||
|
this.currentPlayerIndex = 0;
|
||||||
|
this.gameOver = false;
|
||||||
|
this.io = io;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlayer(id: string, name: string, isAI: boolean = false) {
|
||||||
|
const player: Player = { id, name, hand: [], isAI };
|
||||||
|
this.players.push(player);
|
||||||
|
this.dealTiles(player);
|
||||||
|
this.io.emit('playerJoined', player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other methods remain the same...
|
||||||
|
|
||||||
|
generateTiles(): number[][] {
|
||||||
|
const tiles: number[][] = [];
|
||||||
|
for (let i = 0; i <= 6; i++) {
|
||||||
|
for (let j = i; j <= 6; j++) {
|
||||||
|
tiles.push([i, j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.shuffle(tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(array: any[]): any[] {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
dealTiles(player: Player): void {
|
||||||
|
const tilesPerPlayer = 7;
|
||||||
|
for (let i = 0; i < tilesPerPlayer; i++) {
|
||||||
|
player.hand.push(this.remainingTiles.pop() as number[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DominoesGame, Player };
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Handle Socket Connections**
|
||||||
|
|
||||||
|
Update the server to handle connections and manage player interactions.
|
||||||
|
|
||||||
|
**server.ts**:
|
||||||
|
```typescript
|
||||||
|
let game = new DominoesGame(io);
|
||||||
|
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
console.log(`Player connected: ${socket.id}`);
|
||||||
|
|
||||||
|
socket.on('joinGame', (name) => {
|
||||||
|
game.addPlayer(socket.id, name);
|
||||||
|
io.emit('updateGame', game);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('playTile', (tile) => {
|
||||||
|
const player = game.players.find(p => p.id === socket.id);
|
||||||
|
if (player) {
|
||||||
|
if (game.playTile(game.players.indexOf(player), tile)) {
|
||||||
|
io.emit('updateGame', game);
|
||||||
|
} else {
|
||||||
|
socket.emit('invalidMove', tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
console.log(`Player disconnected: ${socket.id}`);
|
||||||
|
game.players = game.players.filter(p => p.id !== socket.id);
|
||||||
|
io.emit('updateGame', game);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **Client Implementation**
|
||||||
|
|
||||||
|
Create a basic client to connect to the server and interact with the game.
|
||||||
|
|
||||||
|
**client.html**:
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Dominoes Game</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dominoes Game</h1>
|
||||||
|
<div id="game"></div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script>
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
const name = prompt("Enter your name:");
|
||||||
|
socket.emit('joinGame', name);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('updateGame', (game) => {
|
||||||
|
console.log('Game updated:', game);
|
||||||
|
// Update UI based on game state
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('invalidMove', (tile) => {
|
||||||
|
alert(`Invalid move: ${tile}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Example of playing a tile
|
||||||
|
function playTile(tile) {
|
||||||
|
socket.emit('playTile', tile);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. **Testing and Debugging**
|
||||||
|
|
||||||
|
1. **Run the Server**: Start your server with `node server.js`.
|
||||||
|
2. **Access the Game**: Open multiple browser windows and connect to `http://localhost:3000`.
|
||||||
|
3. **Join Game**: Each client can join the game and start interacting.
|
||||||
|
4. **Play Tiles**: Test playing tiles and ensure the game state updates correctly for all clients.
|
||||||
|
|
||||||
|
### 6. **Enhancements**
|
||||||
|
|
||||||
|
1. **Game State Persistence**: Implement game state persistence using a database.
|
||||||
|
2. **Authentication**: Add user authentication to track player progress.
|
||||||
|
3. **AI Integration**: Allow the game to mix human and AI players.
|
||||||
|
|
||||||
|
By following these steps, you can effectively add a socket-connected player to your dominoes game, enabling real-time multiplayer interaction.
|
Loading…
x
Reference in New Issue
Block a user