diff --git a/.hmrc b/.hmrc
index 31678dd..53bb6bb 100644
--- a/.hmrc
+++ b/.hmrc
@@ -1,34 +1,134 @@
{
"path": "G:\\Other\\Development\\Projects\\[ideas]\\domino",
- "name": "domino",
- "initialVersion": "1.0.0",
- "version": "1.0.0",
+ "name": "domino-server",
+ "initialVersion": "0.1.1",
+ "version": "0.1.2",
"docker": {
- "repository": "arhuako/domino"
+ "useRegistry": true,
+ "registry": "192.168.1.115:5000",
+ "repository": "arhuako/domino-server"
},
"repository": {
- "type": "github",
- "user": "jmconde",
- "name": "domino",
- "manage": true,
- "createOnInit": true
+ "type": "other",
+ "url": "https://gitea.xintanalabs.net/arhuako/domino-server.git",
+ "manage": true
},
"changelog": {
- "create": true,
"managed": true,
"createHTML": true,
"htmlPath": "public"
},
"_backupInitial": {
- "name": "domino",
- "version": "1.0.0",
+ "name": "domino-server",
+ "version": "0.1.1",
"description": "",
"main": "index.js",
+ "engines": {
+ "node": ">=20.6.0"
+ },
"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": [],
- "author": "",
- "license": "ISC"
+ "author": "arhuako",
+ "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"
+ }
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8cfef1..b8a7074 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,4 +2,7 @@
All notable changes to this project will be documented in this file.
## Unreleased
-Initial commit
+
+## 0.1.2 - 2024-07-17
+### Added
+- This changelog
diff --git a/package.json b/package.json
index 5468d5d..e0764eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "domino",
- "version": "1.0.0",
+ "name": "domino-server",
+ "version": "0.1.2",
"description": "",
"main": "index.js",
"engines": {
@@ -12,16 +12,19 @@
"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",
+ "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.2",
+ "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"
},
"keywords": [],
"author": "arhuako",
"license": "ISC",
"type": "commonjs",
- "reposityory": "github:jmconde/domino",
+ "reposityory": {
+ "type": "git",
+ "url": "https://gitea.xintanalabs.net/arhuako/domino-server.git"
+ },
"dependencies": {
"bcryptjs": "^2.4.3",
"chalk": "^4.1.2",
diff --git a/public/CHANGELOG.html b/public/CHANGELOG.html
new file mode 100644
index 0000000..2fa5aaf
--- /dev/null
+++ b/public/CHANGELOG.html
@@ -0,0 +1,7 @@
+
Changelog
+All notable changes to this project will be documented in this file.
+0.1.2 - 2024-07-17
+Added
+
diff --git a/src/game/DominoesGame.ts b/src/game/DominoesGame.ts
index badefff..76592a8 100644
--- a/src/game/DominoesGame.ts
+++ b/src/game/DominoesGame.ts
@@ -288,12 +288,19 @@ export class DominoesGame extends EventEmitter {
if (this.winner !== null) {
const winner = this.winner;
winner.score = totalPips;
- if (winner.teamedWith !== null) {
- winner.teamedWith.score = totalPips;
+ if (winner.teamedWith !== undefined) {
+ const p = this.getPlayer(winner.teamedWith)
+ if (p !== undefined) {
+ p.score = totalPips;
+ }
}
}
}
+ private getPlayer(userId: string) {
+ return this.players.find(player => player.id === userId) || undefined;
+ }
+
private autoDealTiles() {
for (let i = 0; i < this.handSize; i++) {
for (let player of this.players) {
diff --git a/src/game/MatchSession.ts b/src/game/MatchSession.ts
index a243ea8..2f98894 100644
--- a/src/game/MatchSession.ts
+++ b/src/game/MatchSession.ts
@@ -11,6 +11,7 @@ import { GameSummary } from "./dto/GameSummary";
import { PlayerMove } from "./entities/PlayerMove";
import { SessionService } from "../server/services/SessionService";
import { Score } from "../server/db/interfaces";
+import { MatchSessionOptions } from "./dto/MatchSessionOptions";
export class MatchSession {
@@ -33,26 +34,30 @@ export class MatchSession {
maxPlayers: number = 4;
mode: string = 'classic';
players: PlayerInterface[] = [];
- pointsToWin: number = 50;
+ // pointsToWin: number = 50;
rng!: PRNG
scoreboard: Map = new Map();
seed!: string
sessionInProgress: boolean = false;
status: string = 'created'
+ name: string
- constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
+ constructor(public creator: PlayerInterface, private options: MatchSessionOptions) {
+ const { sessionName, seed, winType, winTarget } = options;
this.seed = seed || getRandomSeed();
this.id = uuid();
- this.name = name || `Game ${this.id}`;
+ this.name = sessionName || `Match ${this.id}`;
this.addPlayerToSession(creator);
this.creator = creator;
this.logger.info(`Match session created by: ${creator.name}`);
this.logger.info(`Match session ID: ${this.id}`);
this.logger.info(`Match session name: ${this.name}`);
- this.logger.info(`Points to win: ${this.pointsToWin}`);
- this.sessionInProgress = true;
+ this.logger.info(`Win type: ${options.winType}`);
+ this.logger.info(`Win target: ${options.winTarget}`);
+ this.sessionInProgress = false;
this.waitingForPlayers = true;
+ this.status = 'created';
this.logger.info('Waiting for players to be ready');
}
@@ -103,7 +108,7 @@ export class MatchSession {
}
getPlayer(userId: string) {
- return this.players.find(player => player.id === userId);
+ return this.players.find(player => player.id === userId) || null;
}
playerMove(move: any) {
@@ -122,19 +127,42 @@ export class MatchSession {
if (!tile) {
throw new Error("Tile not found");
}
- const newMove = new PlayerMove(tile, move.type, move. playerId)
+ const newMove = new PlayerMove(tile, move.type, move.playerId, move.direction)
this.currentGame.finishTurn(newMove);
}
}
// This is the entry point for the game, method called by session host
- async start() {
+ async start(data: any) {
if (this.matchInProgress) {
throw new Error("Game already in progress");
}
this.waitingForPlayers = false;
+ this.sessionInProgress = true;
+ this.status = 'started'
+ this.sessionService.updateSession(this);
await this.startMatch(this.seed);
- }
+ }
+
+ setTeams(data: any) {
+ if (data.teamedWith !== undefined) {
+ const creatorTeam = this.getPlayer(data.teamedWith)
+ if (!creatorTeam) {
+ throw new Error("Teamed player not found");
+ }
+ this.creator.teamedWith = data.teamedWith;
+ this.creator.team = 1
+ creatorTeam.teamedWith = this.creator.id;
+ creatorTeam.team = 1;
+
+ const others = this.players.filter(player => player.team === 0);
+ others[0].teamedWith = others[1].id;
+ others[0].team = 2;
+ others[1].teamedWith = others[0].id;
+ others[1].team = 2;
+ this.players = [this.creator, others[0], creatorTeam, others[1]];
+ }
+ }
addPlayerToSession(player: PlayerInterface) {
if (this.numPlayers >= this.maxPlayers) {
@@ -225,7 +253,16 @@ export class MatchSession {
checkMatchWinner() {
const scores = Array.from(this.scoreboard.values());
const maxScore = Math.max(...scores);
- if (maxScore >= this.pointsToWin) {
+
+ if (this.options.winType === 'rounds') {
+ this.checkRoundsWinner(maxScore);
+ } else if (this.options.winType === 'points') {
+ this.checkPointsWinner(maxScore);
+ }
+ }
+
+ checkRoundsWinner(maxScore: number) {
+ if (maxScore >= this.options.winTarget) {
this.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
if (!this.matchWinner) {
throw new Error('Match winner not found');
@@ -234,6 +271,20 @@ export class MatchSession {
this.matchInProgress = false;
}
}
+
+ checkPointsWinner(maxScore: number) {
+ if (maxScore >= this.options.winTarget) {
+ this.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
+ if (!this.matchWinner) {
+ throw new Error('Match winner not found');
+ }
+ this.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
+ this.matchInProgress = false;
+ }
+ }
+
+
+
resetScoreboard() {
this.scoreboard = new Map();
this.players.forEach(player => {
@@ -245,6 +296,22 @@ export class MatchSession {
if (!gameSummary) {
return;
}
+ if (this.options.winType === 'rounds') {
+ this.setScoresRounds(gameSummary);
+ } else if (this.options.winType === 'points') {
+ this.setScoresPoints(gameSummary);
+ }
+ }
+
+ setScoresRounds(gameSummary: GameSummary) {
+ const { winner } = gameSummary;
+ if (winner !== undefined) {
+ const currentScore = this.scoreboard.get(winner.name) ?? 0;
+ this.scoreboard.set(winner.name, 1 + currentScore);
+ }
+ }
+
+ setScoresPoints(gameSummary: GameSummary) {
const { score } = gameSummary;
score.forEach(playerScore => {
const currentScore = this.scoreboard.get(playerScore.name) ?? 0;
@@ -324,12 +391,12 @@ export class MatchSession {
waitingSeconds: this.waitingSeconds,
seed: this.seed,
mode: this.mode,
- pointsToWin: this.pointsToWin,
- status: this.sessionInProgress ? 'in progress' : 'waiting',
+ status: this.status,
scoreboard: this.getScoreBoardState(),
matchWinner: this.matchWinner?.getState(true) || null,
matchInProgress: this.matchInProgress,
gameSummaries: this.gameSummaries,
+ options: this.options,
};
}
diff --git a/src/game/dto/MatchSessionOptions.ts b/src/game/dto/MatchSessionOptions.ts
new file mode 100644
index 0000000..84e62ee
--- /dev/null
+++ b/src/game/dto/MatchSessionOptions.ts
@@ -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
+}
\ No newline at end of file
diff --git a/src/game/dto/MatchSessionState.ts b/src/game/dto/MatchSessionState.ts
index 8d5540b..437ab8c 100644
--- a/src/game/dto/MatchSessionState.ts
+++ b/src/game/dto/MatchSessionState.ts
@@ -1,5 +1,6 @@
import { Score } from "../../server/db/interfaces";
import { GameSummary } from "./GameSummary";
+import { MatchSessionOptions } from "./MatchSessionOptions";
import { PlayerDto } from "./PlayerDto";
export interface MatchSessionState {
@@ -10,7 +11,6 @@ export interface MatchSessionState {
seed: string;
waitingForPlayers: boolean;
mode: string;
- pointsToWin: number;
sessionInProgress: boolean;
status: string;
maxPlayers: number;
@@ -21,4 +21,5 @@ export interface MatchSessionState {
matchInProgress: boolean;
playersReady: number,
gameSummaries: GameSummary[];
+ options: MatchSessionOptions
}
\ No newline at end of file
diff --git a/src/game/dto/PlayerDto.ts b/src/game/dto/PlayerDto.ts
index 29a1bce..4de00e7 100644
--- a/src/game/dto/PlayerDto.ts
+++ b/src/game/dto/PlayerDto.ts
@@ -11,7 +11,7 @@ export interface PlayerDto {
name: string;
score?: number;
hand?: TileDto[];
- teamedWith?: PlayerDto | null;
+ teamedWith?: string;
ready: boolean;
isHuman: boolean;
}
\ No newline at end of file
diff --git a/src/game/entities/PlayerMove.ts b/src/game/entities/PlayerMove.ts
index 6604350..3f60d02 100644
--- a/src/game/entities/PlayerMove.ts
+++ b/src/game/entities/PlayerMove.ts
@@ -4,7 +4,7 @@ import { Tile } from "./Tile";
export class PlayerMove {
id: string = uuid();
- constructor(public tile: Tile, public type: PlayerMoveSideType | null, public playerId: string, direction?: string) {}
+ constructor(public tile: Tile, public type: PlayerMoveSideType | null, public playerId: string, public direction?: string) {}
toString() {
return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`;
diff --git a/src/game/entities/player/AbstractPlayer.ts b/src/game/entities/player/AbstractPlayer.ts
index 6eee596..920545b 100644
--- a/src/game/entities/player/AbstractPlayer.ts
+++ b/src/game/entities/player/AbstractPlayer.ts
@@ -13,10 +13,11 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
hand: Tile[] = [];
score: number = 0;
logger: LoggingService = new LoggingService();
- teamedWith: PlayerInterface | null = null;
+ teamedWith?: string;
playerInteraction: PlayerInteractionInterface = undefined as any;
id: string = uuid();
ready: boolean = false;
+ team: number = 0;
constructor(public name: string) {
super();
@@ -58,7 +59,7 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
name: this.name,
score: this.score,
hand: this.hand.map(tile => tile.getState(showPips)),
- teamedWith: this.teamedWith?.getState(showPips) ?? null,
+ teamedWith: this.teamedWith,
ready: this.ready,
isHuman: this instanceof PlayerHuman
};
diff --git a/src/game/entities/player/PlayerInterface.ts b/src/game/entities/player/PlayerInterface.ts
index 524e7f0..e49581b 100644
--- a/src/game/entities/player/PlayerInterface.ts
+++ b/src/game/entities/player/PlayerInterface.ts
@@ -9,7 +9,8 @@ export interface PlayerInterface {
name: string;
score: number;
hand: Tile[];
- teamedWith: PlayerInterface | null;
+ teamedWith?: string;
+ team: number;
playerInteraction: PlayerInteractionInterface;
ready: boolean;
diff --git a/src/server/controllers/GameController.ts b/src/server/controllers/GameController.ts
index 1398bc1..a02964b 100644
--- a/src/server/controllers/GameController.ts
+++ b/src/server/controllers/GameController.ts
@@ -8,8 +8,8 @@ export class GameController extends BaseController {
public async createMatch(req: Request, res: Response) {
try {
const { user, body } = req;
- const { sessionName, seed, options } = body;
- const sessionId = await this.sessionService.createSession(user, sessionName, seed, options);
+ const { options } = body;
+ const sessionId = await this.sessionService.createSession(user, options);
res.status(201).json({ sessionId });
} catch (error) {
this.handleError(res, error);
diff --git a/src/server/db/DbAdapter.ts b/src/server/db/DbAdapter.ts
index 3c4f6d9..0adcd24 100644
--- a/src/server/db/DbAdapter.ts
+++ b/src/server/db/DbAdapter.ts
@@ -10,7 +10,7 @@ export function matchSessionAdapter(session: MatchSession, showPips: boolean = f
players: state.players,
seed: state.seed,
mode: state.mode,
- pointsToWin: state.pointsToWin,
+ options: state.options,
maxPlayers: state.maxPlayers,
numPlayers: state.numPlayers,
scoreboard: state.scoreboard,
diff --git a/src/server/db/interfaces.ts b/src/server/db/interfaces.ts
index dda4837..a4ad7c6 100644
--- a/src/server/db/interfaces.ts
+++ b/src/server/db/interfaces.ts
@@ -1,4 +1,5 @@
import { GameSummary } from "../../game/dto/GameSummary";
+import { MatchSessionOptions } from "../../game/dto/MatchSessionOptions";
import { PlayerDto } from "../../game/dto/PlayerDto";
export interface Entity {
@@ -52,7 +53,7 @@ export interface DbMatchSession extends EntityMongo {
players: PlayerDto[];
seed: string;
mode: string;
- pointsToWin: number;
+ options: MatchSessionOptions;
maxPlayers: number;
numPlayers: number;
scoreboard: Score[];
diff --git a/src/server/services/InteractionService.ts b/src/server/services/InteractionService.ts
index 8010e44..9be5bb0 100644
--- a/src/server/services/InteractionService.ts
+++ b/src/server/services/InteractionService.ts
@@ -64,10 +64,11 @@ export class InteractionService extends ServiceBase{
for (let i = 0; i < missingHumans; i++) {
session.addPlayerToSession(session.createPlayerAI(i));
}
+ session.setTeams(data);
this.notifyService.sendEventToPlayers('server:match-starting', session.players, {
sessionState: session.getState()
});
- session.start();
+ session.start(data);
return {
status: 'ok'
};
diff --git a/src/server/services/SessionService.ts b/src/server/services/SessionService.ts
index db43197..895bd91 100644
--- a/src/server/services/SessionService.ts
+++ b/src/server/services/SessionService.ts
@@ -10,6 +10,7 @@ import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
import { SessionManager } from "../managers/SessionManager";
import { ServiceBase } from "./ServiceBase";
import { SocketIoService } from "./SocketIoService";
+import { MatchSessionOptions } from "../../game/dto/MatchSessionOptions";
export class SessionService extends ServiceBase{
private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager();
@@ -19,7 +20,7 @@ export class SessionService extends ServiceBase{
super()
}
- public async createSession(user: any, sessionName: string, seed: string, options: any ): Promise {
+ public async createSession(user: any, options: MatchSessionOptions ): Promise {
let socketClient;
try {
socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
@@ -27,8 +28,7 @@ export class SessionService extends ServiceBase{
throw new SessionCreationError();
}
const player = new NetworkPlayer(user._id, user.username, socketClient.socketId);
- const session = new MatchSession(player, sessionName, seed);
- session.pointsToWin = options.pointsToWin;
+ const session = new MatchSession(player, options);
const dbSessionId = await this.dbManager.create(matchSessionAdapter(session));
if (dbSessionId === undefined) {
throw new SessionCreationError();