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

130
.hmrc
View File

@ -1,34 +1,134 @@
{ {
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino", "path": "G:\\Other\\Development\\Projects\\[ideas]\\domino",
"name": "domino", "name": "domino-server",
"initialVersion": "1.0.0", "initialVersion": "0.1.1",
"version": "1.0.0", "version": "0.1.2",
"docker": { "docker": {
"repository": "arhuako/domino" "useRegistry": true,
"registry": "192.168.1.115:5000",
"repository": "arhuako/domino-server"
}, },
"repository": { "repository": {
"type": "github", "type": "other",
"user": "jmconde", "url": "https://gitea.xintanalabs.net/arhuako/domino-server.git",
"name": "domino", "manage": true
"manage": true,
"createOnInit": true
}, },
"changelog": { "changelog": {
"create": true,
"managed": true, "managed": true,
"createHTML": true, "createHTML": true,
"htmlPath": "public" "htmlPath": "public"
}, },
"_backupInitial": { "_backupInitial": {
"name": "domino", "name": "domino-server",
"version": "1.0.0", "version": "0.1.1",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"engines": {
"node": ">=20.6.0"
},
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "build": "tsc",
"dev": "node --env-file=.env --watch -r ts-node/register src/server/index.ts",
"create": "node --env-file=.env -r ts-node/register ./create.ts",
"test": "node --env-file=.env -r ts-node/register src/test.ts",
"test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
"docker-build": "docker build -t arhuako/domino:latest .",
"docker-tag": "docker tag arhuako/domino:latest arhuako/domino:1.0.0",
"docker-push": "docker push arhuako/domino:latest && docker push arhuako/domino:1.0.0",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "arhuako",
"license": "ISC" "license": "ISC",
"type": "commonjs",
"reposityory": "github:jmconde/domino",
"dependencies": {
"bcryptjs": "^2.4.3",
"chalk": "^4.1.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"express-validator": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.8.0",
"nodemailer": "^6.9.14",
"nodemailer-express-handlebars": "^6.1.2",
"pino": "^9.2.0",
"pino-http": "^10.2.0",
"pino-pretty": "^11.2.1",
"pino-rotating-file-stream": "^0.0.2",
"pubsub-js": "^1.9.4",
"seedrandom": "^3.0.5",
"socket.io": "^4.7.5"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.8",
"@types/nodemailer": "^6.4.15",
"@types/nodemailer-express-handlebars": "^4.0.5",
"@types/pubsub-js": "^1.8.6",
"@types/seedrandom": "^3.0.8",
"ts-node": "^10.9.2",
"typescript": "^5.5.2"
}
},
"_backup": {
"name": "domino-server",
"version": "0.1.1",
"description": "",
"main": "index.js",
"engines": {
"node": ">=20.6.0"
},
"scripts": {
"build": "tsc",
"dev": "node --env-file=.env --watch -r ts-node/register src/server/index.ts",
"create": "node --env-file=.env -r ts-node/register ./create.ts",
"test": "node --env-file=.env -r ts-node/register src/test.ts",
"test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
"docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
"docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.1",
"docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.1",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
},
"keywords": [],
"author": "arhuako",
"license": "ISC",
"type": "commonjs",
"reposityory": {
"type": "git",
"url": "https://gitea.xintanalabs.net/arhuako/domino-server.git"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"chalk": "^4.1.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"express-validator": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.8.0",
"nodemailer": "^6.9.14",
"nodemailer-express-handlebars": "^6.1.2",
"pino": "^9.2.0",
"pino-http": "^10.2.0",
"pino-pretty": "^11.2.1",
"pino-rotating-file-stream": "^0.0.2",
"pubsub-js": "^1.9.4",
"seedrandom": "^3.0.5",
"socket.io": "^4.7.5"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.8",
"@types/nodemailer": "^6.4.15",
"@types/nodemailer-express-handlebars": "^4.0.5",
"@types/pubsub-js": "^1.8.6",
"@types/seedrandom": "^3.0.8",
"ts-node": "^10.9.2",
"typescript": "^5.5.2"
}
} }
} }

View File

@ -2,4 +2,7 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## Unreleased ## Unreleased
Initial commit
## 0.1.2 - 2024-07-17
### Added
- This changelog

View File

@ -1,6 +1,6 @@
{ {
"name": "domino", "name": "domino-server",
"version": "1.0.0", "version": "0.1.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
@ -12,16 +12,19 @@
"create": "node --env-file=.env -r ts-node/register ./create.ts", "create": "node --env-file=.env -r ts-node/register ./create.ts",
"test": "node --env-file=.env -r ts-node/register src/test.ts", "test": "node --env-file=.env -r ts-node/register src/test.ts",
"test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts", "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
"docker-build": "docker build -t arhuako/domino:latest .", "docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
"docker-tag": "docker tag arhuako/domino:latest arhuako/domino:1.0.0", "docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.2",
"docker-push": "docker push arhuako/domino:latest && docker push arhuako/domino:1.0.0", "docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.2",
"publish": "npm run docker-build && npm run docker-tag && npm run docker-push" "publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
}, },
"keywords": [], "keywords": [],
"author": "arhuako", "author": "arhuako",
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
"reposityory": "github:jmconde/domino", "reposityory": {
"type": "git",
"url": "https://gitea.xintanalabs.net/arhuako/domino-server.git"
},
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"chalk": "^4.1.2", "chalk": "^4.1.2",

7
public/CHANGELOG.html Normal file
View File

@ -0,0 +1,7 @@
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.</p>
<h2>0.1.2 - 2024-07-17</h2>
<h3>Added</h3>
<ul>
<li>This changelog</li>
</ul>

View File

@ -288,12 +288,19 @@ export class DominoesGame extends EventEmitter {
if (this.winner !== null) { if (this.winner !== null) {
const winner = this.winner; const winner = this.winner;
winner.score = totalPips; winner.score = totalPips;
if (winner.teamedWith !== null) { if (winner.teamedWith !== undefined) {
winner.teamedWith.score = totalPips; 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() { private autoDealTiles() {
for (let i = 0; i < this.handSize; i++) { for (let i = 0; i < this.handSize; i++) {
for (let player of this.players) { for (let player of this.players) {

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { Tile } from "./Tile";
export class PlayerMove { export class PlayerMove {
id: string = uuid(); 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() { toString() {
return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`; 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[] = []; hand: Tile[] = [];
score: number = 0; score: number = 0;
logger: LoggingService = new LoggingService(); logger: LoggingService = new LoggingService();
teamedWith: PlayerInterface | null = null; teamedWith?: string;
playerInteraction: PlayerInteractionInterface = undefined as any; playerInteraction: PlayerInteractionInterface = undefined as any;
id: string = uuid(); id: string = uuid();
ready: boolean = false; ready: boolean = false;
team: number = 0;
constructor(public name: string) { constructor(public name: string) {
super(); super();
@ -58,7 +59,7 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
name: this.name, name: this.name,
score: this.score, score: this.score,
hand: this.hand.map(tile => tile.getState(showPips)), hand: this.hand.map(tile => tile.getState(showPips)),
teamedWith: this.teamedWith?.getState(showPips) ?? null, teamedWith: this.teamedWith,
ready: this.ready, ready: this.ready,
isHuman: this instanceof PlayerHuman isHuman: this instanceof PlayerHuman
}; };

View File

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

View File

@ -8,8 +8,8 @@ export class GameController extends BaseController {
public async createMatch(req: Request, res: Response) { public async createMatch(req: Request, res: Response) {
try { try {
const { user, body } = req; const { user, body } = req;
const { sessionName, seed, options } = body; const { options } = body;
const sessionId = await this.sessionService.createSession(user, sessionName, seed, options); const sessionId = await this.sessionService.createSession(user, options);
res.status(201).json({ sessionId }); res.status(201).json({ sessionId });
} catch (error) { } catch (error) {
this.handleError(res, error); this.handleError(res, error);

View File

@ -10,7 +10,7 @@ export function matchSessionAdapter(session: MatchSession, showPips: boolean = f
players: state.players, players: state.players,
seed: state.seed, seed: state.seed,
mode: state.mode, mode: state.mode,
pointsToWin: state.pointsToWin, options: state.options,
maxPlayers: state.maxPlayers, maxPlayers: state.maxPlayers,
numPlayers: state.numPlayers, numPlayers: state.numPlayers,
scoreboard: state.scoreboard, scoreboard: state.scoreboard,

View File

@ -1,4 +1,5 @@
import { GameSummary } from "../../game/dto/GameSummary"; import { GameSummary } from "../../game/dto/GameSummary";
import { MatchSessionOptions } from "../../game/dto/MatchSessionOptions";
import { PlayerDto } from "../../game/dto/PlayerDto"; import { PlayerDto } from "../../game/dto/PlayerDto";
export interface Entity { export interface Entity {
@ -52,7 +53,7 @@ export interface DbMatchSession extends EntityMongo {
players: PlayerDto[]; players: PlayerDto[];
seed: string; seed: string;
mode: string; mode: string;
pointsToWin: number; options: MatchSessionOptions;
maxPlayers: number; maxPlayers: number;
numPlayers: number; numPlayers: number;
scoreboard: Score[]; scoreboard: Score[];

View File

@ -64,10 +64,11 @@ 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));
} }
session.setTeams(data);
this.notifyService.sendEventToPlayers('server:match-starting', session.players, { this.notifyService.sendEventToPlayers('server:match-starting', session.players, {
sessionState: session.getState() sessionState: session.getState()
}); });
session.start(); session.start(data);
return { return {
status: 'ok' status: 'ok'
}; };

View File

@ -10,6 +10,7 @@ import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
import { SessionManager } from "../managers/SessionManager"; import { SessionManager } from "../managers/SessionManager";
import { ServiceBase } from "./ServiceBase"; import { ServiceBase } from "./ServiceBase";
import { SocketIoService } from "./SocketIoService"; import { SocketIoService } from "./SocketIoService";
import { MatchSessionOptions } from "../../game/dto/MatchSessionOptions";
export class SessionService extends ServiceBase{ export class SessionService extends ServiceBase{
private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager(); private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager();
@ -19,7 +20,7 @@ export class SessionService extends ServiceBase{
super() super()
} }
public async createSession(user: any, sessionName: string, seed: string, options: any ): Promise<string> { public async createSession(user: any, options: MatchSessionOptions ): Promise<string> {
let socketClient; let socketClient;
try { try {
socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id)); socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
@ -27,8 +28,7 @@ export class SessionService extends ServiceBase{
throw new SessionCreationError(); throw new SessionCreationError();
} }
const player = new NetworkPlayer(user._id, user.username, socketClient.socketId); const player = new NetworkPlayer(user._id, user.username, socketClient.socketId);
const session = new MatchSession(player, sessionName, seed); const session = new MatchSession(player, options);
session.pointsToWin = options.pointsToWin;
const dbSessionId = await this.dbManager.create(matchSessionAdapter(session)); const dbSessionId = await this.dbManager.create(matchSessionAdapter(session));
if (dbSessionId === undefined) { if (dbSessionId === undefined) {
throw new SessionCreationError(); throw new SessionCreationError();