trucoshi 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ import { IChatRoom, TMap } from "../../types";
2
+ import { TrucoshiServer } from "./SocketServer";
3
+ export interface IChat {
4
+ rooms: TMap<string, IChatRoom>;
5
+ create(id: string): void;
6
+ }
7
+ export declare const Chat: (io: TrucoshiServer) => IChat;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Chat = void 0;
4
+ const types_1 = require("../../types");
5
+ const SYSTEM_ID = "system";
6
+ const ChatMessage = (user, message, system = false) => {
7
+ return {
8
+ date: Date.now() / 1000,
9
+ user,
10
+ system,
11
+ content: message,
12
+ };
13
+ };
14
+ const ChatRoom = (io, id) => {
15
+ const room = {
16
+ id,
17
+ messages: [],
18
+ send(user, message) {
19
+ room.messages.push(ChatMessage(user, message));
20
+ room.emit();
21
+ },
22
+ system(message) {
23
+ room.messages.push(ChatMessage({ id: SYSTEM_ID, key: SYSTEM_ID }, message, true));
24
+ room.emit();
25
+ },
26
+ emit() {
27
+ io.sockets.adapter
28
+ .fetchSockets({
29
+ rooms: new Set([room.id]),
30
+ })
31
+ .then((sockets) => {
32
+ for (const playerSocket of sockets) {
33
+ playerSocket.emit(types_1.EServerEvent.UPDATE_CHAT, { id: room.id, messages: room.messages });
34
+ }
35
+ });
36
+ },
37
+ };
38
+ return room;
39
+ };
40
+ const Chat = (io) => {
41
+ const chat = {
42
+ rooms: new types_1.TMap(),
43
+ create(id) {
44
+ const room = ChatRoom(io, id);
45
+ const exists = chat.rooms.get(id);
46
+ if (exists) {
47
+ return;
48
+ }
49
+ chat.rooms.set(id, room);
50
+ },
51
+ };
52
+ const adapter = io.of("/").adapter;
53
+ adapter.on("join-room", (room, socketId) => {
54
+ var _a;
55
+ const userSocket = io.sockets.sockets.get(socketId);
56
+ if (!userSocket || !userSocket.data.user) {
57
+ return;
58
+ }
59
+ const { id, key } = userSocket.data.user;
60
+ (_a = chat.rooms.get(room)) === null || _a === void 0 ? void 0 : _a.system(`${id} entro a la sala`);
61
+ userSocket.on(types_1.EClientEvent.CHAT, (matchId, message, callback) => {
62
+ if (matchId !== room || !userSocket.data.user) {
63
+ return;
64
+ }
65
+ const chatroom = chat.rooms.get(matchId);
66
+ if (chatroom) {
67
+ chatroom.send({ id, key }, message);
68
+ }
69
+ callback();
70
+ });
71
+ });
72
+ adapter.on("leave-room", (room, socketId) => {
73
+ var _a;
74
+ const userSocket = io.sockets.sockets.get(socketId);
75
+ if (!userSocket || !userSocket.data.user) {
76
+ return;
77
+ }
78
+ const { id } = userSocket.data.user;
79
+ (_a = chat.rooms.get(room)) === null || _a === void 0 ? void 0 : _a.system(`${id} salio de la sala`);
80
+ });
81
+ return chat;
82
+ };
83
+ exports.Chat = Chat;
@@ -0,0 +1,15 @@
1
+ import { IHand, ILobby, IPlayedCard, IPlayer } from "../../lib";
2
+ import { EMatchTableState, IMatchPreviousHand, IPublicMatch, IPublicMatchInfo } from "../../types";
3
+ export interface IMatchTable {
4
+ ownerSession: string;
5
+ matchSessionId: string;
6
+ lobby: ILobby;
7
+ state(): EMatchTableState;
8
+ isSessionPlaying(session: string): IPlayer | null;
9
+ getPreviousHand(hand: IHand): IMatchPreviousHand;
10
+ getHandRounds(hand: IHand): IPlayedCard[][];
11
+ getPublicMatch(session?: string): IPublicMatch;
12
+ getPublicMatchInfo(): IPublicMatchInfo;
13
+ waitPlayerReconnection(player: IPlayer, callback: (onReconnect: () => void, onAbandon: () => void) => void, update: () => void): Promise<void>;
14
+ }
15
+ export declare function MatchTable(matchSessionId: string, ownerSession: string, teamSize?: 1 | 2 | 3): IMatchTable;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MatchTable = void 0;
13
+ const lib_1 = require("../../lib");
14
+ const types_1 = require("../../types");
15
+ function MatchTable(matchSessionId, ownerSession, teamSize) {
16
+ const matchTable = {
17
+ ownerSession,
18
+ matchSessionId,
19
+ lobby: (0, lib_1.Lobby)(teamSize),
20
+ state() {
21
+ var _a;
22
+ matchTable.lobby.calculateReady();
23
+ if ((_a = matchTable.lobby.gameLoop) === null || _a === void 0 ? void 0 : _a.winner) {
24
+ return types_1.EMatchTableState.FINISHED;
25
+ }
26
+ if (matchTable.lobby.started) {
27
+ return types_1.EMatchTableState.STARTED;
28
+ }
29
+ if (matchTable.lobby.ready) {
30
+ return types_1.EMatchTableState.READY;
31
+ }
32
+ return types_1.EMatchTableState.UNREADY;
33
+ },
34
+ isSessionPlaying(session) {
35
+ const { lobby } = matchTable;
36
+ const search = lobby.players.find((player) => player && player.session === session);
37
+ return search || null;
38
+ },
39
+ getPublicMatchInfo() {
40
+ const { matchSessionId, state, lobby: { players, maxPlayers }, } = matchTable;
41
+ return {
42
+ matchSessionId,
43
+ maxPlayers,
44
+ players: players.length,
45
+ state: state(),
46
+ };
47
+ },
48
+ waitPlayerReconnection(player, callback, update) {
49
+ return __awaiter(this, void 0, void 0, function* () {
50
+ if (matchTable.state() !== types_1.EMatchTableState.STARTED) {
51
+ player.setReady(false);
52
+ }
53
+ update();
54
+ try {
55
+ yield new Promise(callback);
56
+ }
57
+ catch (e) {
58
+ if (matchTable.state() !== types_1.EMatchTableState.STARTED) {
59
+ player.setReady(false);
60
+ matchTable.lobby.removePlayer(player.session);
61
+ }
62
+ else {
63
+ player.setReady(true);
64
+ }
65
+ }
66
+ update();
67
+ });
68
+ },
69
+ getHandRounds(hand) {
70
+ if (!hand) {
71
+ return [];
72
+ }
73
+ return hand.rounds.map((round) => round.cards) || [];
74
+ },
75
+ getPreviousHand(hand) {
76
+ return {
77
+ rounds: matchTable.getHandRounds(hand),
78
+ points: hand.points,
79
+ matchSessionId: matchTable.matchSessionId,
80
+ };
81
+ },
82
+ getPublicMatch(userSession) {
83
+ var _a, _b, _c, _d;
84
+ const { lobby } = matchTable;
85
+ const winner = ((_a = lobby.gameLoop) === null || _a === void 0 ? void 0 : _a.winner) || null;
86
+ const rounds = ((_b = lobby.gameLoop) === null || _b === void 0 ? void 0 : _b.currentHand)
87
+ ? matchTable.getHandRounds((_c = lobby.gameLoop) === null || _c === void 0 ? void 0 : _c.currentHand)
88
+ : [];
89
+ const players = lobby.players.filter((player) => Boolean(player));
90
+ const currentPlayerIdx = players.findIndex((player) => player && player.session === userSession);
91
+ const me = players[currentPlayerIdx];
92
+ const cut = players.slice(currentPlayerIdx, players.length);
93
+ const end = players.slice(0, currentPlayerIdx);
94
+ const publicPlayers = cut
95
+ .concat(end)
96
+ .map((player) => ((player === null || player === void 0 ? void 0 : player.session) === userSession ? player : player.getPublicPlayer()));
97
+ const teams = ((_d = lobby.gameLoop) === null || _d === void 0 ? void 0 : _d.teams) || lobby.teams;
98
+ const publicTeams = teams.map((team) => team.getPublicTeam(userSession));
99
+ return {
100
+ me,
101
+ winner,
102
+ matchSessionId: matchTable.matchSessionId,
103
+ state: matchTable.state(),
104
+ teams: publicTeams,
105
+ players: publicPlayers,
106
+ rounds
107
+ };
108
+ },
109
+ };
110
+ return matchTable;
111
+ }
112
+ exports.MatchTable = MatchTable;
@@ -0,0 +1,34 @@
1
+ /// <reference types="node" />
2
+ import { Server as HttpServer } from "http";
3
+ import { Server, Socket } from "socket.io";
4
+ import { IPlayer, IPlayInstance } from "../../lib";
5
+ import { ClientToServerEvents, ECommand, IEventCallback, IPublicMatch, ServerToClientEvents } from "../../types";
6
+ import { IChat } from "./Chat";
7
+ import { IMatchTable } from "./MatchTable";
8
+ import { ITrucoshi } from "./Trucoshi";
9
+ import { IUser } from "./User";
10
+ interface InterServerEvents {
11
+ }
12
+ interface SocketData {
13
+ user?: IUser;
14
+ }
15
+ export type TrucoshiServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
16
+ export type TrucoshiSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
17
+ export interface ISocketServer extends ITrucoshi {
18
+ io: TrucoshiServer;
19
+ httpServer: HttpServer;
20
+ chat: IChat;
21
+ getTableSockets(table: IMatchTable, callback: (playerSocket: TrucoshiSocket, player: IPlayer | null) => Promise<void>): Promise<void>;
22
+ setOrGetSession(socket: TrucoshiSocket, id: string | null, session: string | null, callback: IEventCallback<{
23
+ session?: string;
24
+ }>): Promise<void>;
25
+ emitWaitingPossibleSay(play: IPlayInstance, table: IMatchTable): Promise<ECommand>;
26
+ emitWaitingForPlay(play: IPlayInstance, table: IMatchTable): Promise<void>;
27
+ emitMatchUpdate(table: IMatchTable, skipSocketIds?: Array<string>): Promise<void>;
28
+ emitPreviousHand(play: IPlayInstance, table: IMatchTable): Promise<void>;
29
+ emitSocketMatch(socket: TrucoshiSocket, currentMatchId: string | null): IPublicMatch | null;
30
+ startMatch(matchSessionId: string): Promise<void>;
31
+ listen: (callback: (io: TrucoshiServer) => void) => void;
32
+ }
33
+ export declare const SocketServer: (trucoshi: ITrucoshi, port: number, origin?: string | Array<string>) => ISocketServer;
34
+ export {};
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SocketServer = void 0;
13
+ const crypto_1 = require("crypto");
14
+ const http_1 = require("http");
15
+ const socket_io_1 = require("socket.io");
16
+ const types_1 = require("../../types");
17
+ const constants_1 = require("../constants");
18
+ const Chat_1 = require("./Chat");
19
+ const User_1 = require("./User");
20
+ const SocketServer = (trucoshi, port, origin) => {
21
+ const httpServer = (0, http_1.createServer)();
22
+ const io = new socket_io_1.Server(httpServer, {
23
+ cors: {
24
+ origin,
25
+ methods: ["GET", "POST"],
26
+ },
27
+ });
28
+ const server = {
29
+ io,
30
+ httpServer,
31
+ turns: trucoshi.turns,
32
+ tables: trucoshi.tables,
33
+ users: trucoshi.users,
34
+ chat: (0, Chat_1.Chat)(io),
35
+ listen(callback) {
36
+ httpServer.listen(port, undefined, undefined, () => callback(server.io));
37
+ },
38
+ setOrGetSession(socket, id, session, callback = () => { }) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ if (session) {
41
+ const user = server.users.get(session);
42
+ if (user) {
43
+ const newId = id || user.id || "Satoshi";
44
+ user.connect();
45
+ user.setId(newId);
46
+ socket.data.user = user;
47
+ return callback({ success: true, session });
48
+ }
49
+ }
50
+ const newSession = (0, crypto_1.randomUUID)();
51
+ const userKey = (0, crypto_1.randomUUID)();
52
+ const newId = id || "Satoshi";
53
+ const newUser = (0, User_1.User)(userKey, newId, newSession);
54
+ socket.data.user = newUser;
55
+ server.users.set(newSession, newUser);
56
+ callback({ success: false, session: newSession });
57
+ });
58
+ },
59
+ getTableSockets(table, callback) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
62
+ var _a;
63
+ const sockets = yield server.io.sockets.adapter.fetchSockets({
64
+ rooms: new Set([table.matchSessionId]),
65
+ });
66
+ for (const playerSocket of sockets) {
67
+ if (!((_a = playerSocket.data.user) === null || _a === void 0 ? void 0 : _a.session)) {
68
+ continue;
69
+ }
70
+ const player = table.isSessionPlaying(playerSocket.data.user.session);
71
+ yield callback(playerSocket, player);
72
+ }
73
+ resolve();
74
+ }));
75
+ });
76
+ },
77
+ emitMatchUpdate(table, skipSocketIds = []) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ yield server.getTableSockets(table, (playerSocket) => __awaiter(this, void 0, void 0, function* () {
80
+ var _a;
81
+ if (skipSocketIds.includes(playerSocket.id)) {
82
+ return;
83
+ }
84
+ playerSocket.emit(types_1.EServerEvent.UPDATE_MATCH, table.getPublicMatch((_a = playerSocket.data.user) === null || _a === void 0 ? void 0 : _a.session));
85
+ }));
86
+ });
87
+ },
88
+ emitWaitingPossibleSay(play, table) {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ let someoneSaidSomething = false;
91
+ return new Promise((resolve, reject) => {
92
+ return server.getTableSockets(table, (playerSocket, player) => __awaiter(this, void 0, void 0, function* () {
93
+ if (!player) {
94
+ return;
95
+ }
96
+ playerSocket.emit(types_1.EServerEvent.WAITING_POSSIBLE_SAY, table.getPublicMatch(player.session), (data) => {
97
+ var _a;
98
+ if (!data) {
99
+ return;
100
+ }
101
+ if (someoneSaidSomething) {
102
+ return;
103
+ }
104
+ const { command } = data;
105
+ try {
106
+ if (command) {
107
+ const saidCommand = play.say(command, player);
108
+ if (saidCommand) {
109
+ someoneSaidSomething = true;
110
+ (_a = server.chat.rooms
111
+ .get(table.matchSessionId)) === null || _a === void 0 ? void 0 : _a.send({ id: player.id, key: player.key }, saidCommand);
112
+ return resolve(saidCommand);
113
+ }
114
+ }
115
+ }
116
+ catch (e) {
117
+ reject(e);
118
+ }
119
+ });
120
+ }));
121
+ });
122
+ });
123
+ },
124
+ emitWaitingForPlay(play, table) {
125
+ return __awaiter(this, void 0, void 0, function* () {
126
+ return new Promise((resolve, reject) => {
127
+ server
128
+ .emitWaitingPossibleSay(play, table)
129
+ .then(() => {
130
+ resolve();
131
+ })
132
+ .catch(console.error);
133
+ return server.getTableSockets(table, (playerSocket, player) => __awaiter(this, void 0, void 0, function* () {
134
+ var _a;
135
+ if (!player) {
136
+ return;
137
+ }
138
+ if (player.session === ((_a = play.player) === null || _a === void 0 ? void 0 : _a.session)) {
139
+ playerSocket.emit(types_1.EServerEvent.WAITING_PLAY, table.getPublicMatch(player.session), (data) => {
140
+ if (!data) {
141
+ return reject(new Error(types_1.EServerEvent.WAITING_PLAY + " callback returned empty"));
142
+ }
143
+ const { cardIdx, card } = data;
144
+ if (cardIdx !== undefined && card) {
145
+ const playedCard = play.use(cardIdx, card);
146
+ if (playedCard) {
147
+ return resolve();
148
+ }
149
+ return reject(new Error("Invalid Card"));
150
+ }
151
+ return reject(new Error("Invalid Callback Response"));
152
+ });
153
+ }
154
+ }));
155
+ });
156
+ });
157
+ },
158
+ emitPreviousHand(play, table) {
159
+ return __awaiter(this, void 0, void 0, function* () {
160
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
161
+ try {
162
+ const promises = [];
163
+ yield server.getTableSockets(table, (playerSocket, player) => __awaiter(this, void 0, void 0, function* () {
164
+ promises.push(new Promise((resolvePlayer, rejectPlayer) => {
165
+ if (!player || !play.prevHand) {
166
+ return rejectPlayer();
167
+ }
168
+ playerSocket.emit(types_1.EServerEvent.PREVIOUS_HAND, table.getPreviousHand(play.prevHand), resolvePlayer);
169
+ setTimeout(() => {
170
+ rejectPlayer();
171
+ }, constants_1.PREVIOUS_HAND_ACK_TIMEOUT);
172
+ }));
173
+ }));
174
+ yield Promise.allSettled(promises);
175
+ resolve();
176
+ }
177
+ catch (e) {
178
+ reject(e);
179
+ }
180
+ }));
181
+ });
182
+ },
183
+ startMatch(matchSessionId) {
184
+ return __awaiter(this, void 0, void 0, function* () {
185
+ const table = server.tables.getOrThrow(matchSessionId);
186
+ if (table && !table.lobby.gameLoop) {
187
+ table.lobby
188
+ .startMatch()
189
+ .onTurn((play) => {
190
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
191
+ var _a, _b;
192
+ const currentTurn = server.turns.get(table.matchSessionId);
193
+ const emitPreviousHand = currentTurn
194
+ ? currentTurn.previousHandIdx !== ((_a = play.prevHand) === null || _a === void 0 ? void 0 : _a.idx)
195
+ : false;
196
+ server.turns.set(table.matchSessionId, {
197
+ play,
198
+ resolve,
199
+ previousHandIdx: play.prevHand ? play.prevHand.idx : null,
200
+ });
201
+ try {
202
+ const session = (_b = play.player) === null || _b === void 0 ? void 0 : _b.session;
203
+ if (!session || !play) {
204
+ throw new Error("Unexpected Error");
205
+ }
206
+ server.users.getOrThrow(session);
207
+ if (emitPreviousHand) {
208
+ yield server.emitPreviousHand(play, table);
209
+ }
210
+ yield server.emitWaitingForPlay(play, table);
211
+ return resolve();
212
+ }
213
+ catch (e) {
214
+ reject(e);
215
+ }
216
+ }));
217
+ })
218
+ .onTruco((play) => {
219
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
220
+ server.turns.update(table.matchSessionId, {
221
+ play,
222
+ resolve,
223
+ });
224
+ try {
225
+ yield server.emitWaitingPossibleSay(play, table);
226
+ return resolve();
227
+ }
228
+ catch (e) {
229
+ reject(e);
230
+ }
231
+ }));
232
+ })
233
+ .onWinner(() => __awaiter(this, void 0, void 0, function* () {
234
+ yield server.emitMatchUpdate(table);
235
+ }))
236
+ .begin();
237
+ server.tables.set(matchSessionId, table);
238
+ return;
239
+ }
240
+ });
241
+ },
242
+ emitSocketMatch(socket, matchId) {
243
+ var _a;
244
+ if (!matchId || !((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session)) {
245
+ return null;
246
+ }
247
+ const currentTable = server.tables.get(matchId);
248
+ if (currentTable) {
249
+ socket.join(currentTable.matchSessionId);
250
+ const { play, resolve } = server.turns.get(currentTable.matchSessionId) || {};
251
+ if (play && play.player && currentTable.isSessionPlaying(socket.data.user.session)) {
252
+ if (play.state === types_1.EHandState.WAITING_PLAY &&
253
+ socket.data.user.session === play.player.session) {
254
+ server.emitWaitingForPlay(play, currentTable).then(resolve).catch(console.error);
255
+ }
256
+ else {
257
+ server.emitWaitingPossibleSay(play, currentTable).then(resolve).catch(console.error);
258
+ }
259
+ }
260
+ else {
261
+ socket.emit(types_1.EServerEvent.UPDATE_MATCH, currentTable.getPublicMatch(socket.data.user.session));
262
+ }
263
+ return currentTable.getPublicMatch(socket.data.user.session);
264
+ }
265
+ return null;
266
+ },
267
+ };
268
+ const getPossiblePlayingMatch = (room, socketId) => {
269
+ const socket = server.io.sockets.sockets.get(socketId);
270
+ if (!socket || !socket.data.user) {
271
+ return null;
272
+ }
273
+ const table = server.tables.get(room);
274
+ if (table) {
275
+ const player = table.isSessionPlaying(socket.data.user.session);
276
+ if (player) {
277
+ return { table, player, user: socket.data.user };
278
+ }
279
+ }
280
+ return null;
281
+ };
282
+ io.of("/").adapter.on("leave-room", (room, socketId) => {
283
+ const playingMatch = getPossiblePlayingMatch(room, socketId);
284
+ if (!playingMatch) {
285
+ return;
286
+ }
287
+ const { player, table, user } = playingMatch;
288
+ table.waitPlayerReconnection(player, (reconnect, abandon) => {
289
+ user.waitReconnection(table.matchSessionId, reconnect, abandon);
290
+ }, () => {
291
+ server.emitMatchUpdate(table, []);
292
+ });
293
+ });
294
+ io.of("/").adapter.on("join-room", (room, socketId) => {
295
+ const playingMatch = getPossiblePlayingMatch(room, socketId);
296
+ if (!playingMatch) {
297
+ return;
298
+ }
299
+ const { table, user } = playingMatch;
300
+ user.reconnect(table.matchSessionId);
301
+ });
302
+ return server;
303
+ };
304
+ exports.SocketServer = SocketServer;
@@ -0,0 +1,23 @@
1
+ import { IPlayInstance } from "../../lib";
2
+ import { EMatchTableState, IPublicMatchInfo, TMap } from "../../types";
3
+ import { IMatchTable } from "./MatchTable";
4
+ import { IUser } from "./User";
5
+ export interface ITrucoshi {
6
+ users: TMap<string, IUser>;
7
+ tables: MatchTableMap;
8
+ turns: TMap<string, ITrucoshiTurn>;
9
+ }
10
+ interface ITrucoshiTurn {
11
+ previousHandIdx: number | null;
12
+ play: IPlayInstance;
13
+ resolve(): void;
14
+ }
15
+ interface MatchTableMap extends TMap<string, IMatchTable> {
16
+ getAll(filters: {
17
+ state?: Array<EMatchTableState>;
18
+ }): Array<IPublicMatchInfo>;
19
+ }
20
+ declare class MatchTableMap extends TMap<string, IMatchTable> {
21
+ }
22
+ export declare const Trucoshi: () => ITrucoshi;
23
+ export {};
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Trucoshi = void 0;
4
+ const types_1 = require("../../types");
5
+ class MatchTableMap extends types_1.TMap {
6
+ getAll(filters = {}) {
7
+ let results = [];
8
+ for (let value of this.values()) {
9
+ if (!filters.state || !filters.state.length || filters.state.includes(value.state())) {
10
+ results.push(value.getPublicMatchInfo());
11
+ }
12
+ }
13
+ return results;
14
+ }
15
+ }
16
+ const Trucoshi = () => {
17
+ const users = new types_1.TMap(); // sessionId, user
18
+ const tables = new MatchTableMap(); // sessionId, table
19
+ const turns = new types_1.TMap(); // sessionId, play instance, play promise resolve and type
20
+ const trucoshi = {
21
+ users,
22
+ tables,
23
+ turns,
24
+ };
25
+ return trucoshi;
26
+ };
27
+ exports.Trucoshi = Trucoshi;
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ export interface IUser {
3
+ key: string;
4
+ id: string;
5
+ session: string;
6
+ online: boolean;
7
+ ownedMatchId: string | null;
8
+ waitingTimeouts: Map<string, NodeJS.Timeout | null>;
9
+ waitingPromises: Map<string, () => void>;
10
+ waitReconnection(room: string, reconnect: () => void, abandon: () => void): void;
11
+ setOwnedMatch(id: string): void;
12
+ connect(): void;
13
+ disconnect(): void;
14
+ reconnect(room: string): void;
15
+ setId(id: string): void;
16
+ }
17
+ export declare function User(key: string, id: string, session: string): IUser;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.User = void 0;
4
+ const PLAYER_ABANDON_TIMEOUT = 10000;
5
+ function User(key, id, session) {
6
+ const user = {
7
+ id,
8
+ key,
9
+ session,
10
+ online: true,
11
+ ownedMatchId: null,
12
+ waitingTimeouts: new Map(),
13
+ waitingPromises: new Map(),
14
+ waitReconnection(room, reconnect, abandon) {
15
+ user.waitingTimeouts.set(room, setTimeout(() => {
16
+ abandon();
17
+ user.waitingPromises.delete(room);
18
+ }, PLAYER_ABANDON_TIMEOUT));
19
+ user.waitingPromises.set(room, reconnect);
20
+ },
21
+ reconnect(room) {
22
+ const promise = user.waitingPromises.get(room);
23
+ if (promise) {
24
+ promise();
25
+ user.waitingPromises.delete(room);
26
+ }
27
+ const timeout = user.waitingTimeouts.get(room);
28
+ if (timeout) {
29
+ clearTimeout(timeout);
30
+ user.waitingTimeouts.delete(room);
31
+ }
32
+ },
33
+ setOwnedMatch(id) {
34
+ user.ownedMatchId = id;
35
+ },
36
+ connect() {
37
+ user.online = true;
38
+ },
39
+ disconnect() {
40
+ user.online = false;
41
+ },
42
+ setId(id) {
43
+ user.id = id;
44
+ },
45
+ };
46
+ return user;
47
+ }
48
+ exports.User = User;
@@ -0,0 +1,5 @@
1
+ export * from "./Chat";
2
+ export * from "./MatchTable";
3
+ export * from "./SocketServer";
4
+ export * from "./Trucoshi";
5
+ export * from "./User";
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Chat"), exports);
18
+ __exportStar(require("./MatchTable"), exports);
19
+ __exportStar(require("./SocketServer"), exports);
20
+ __exportStar(require("./Trucoshi"), exports);
21
+ __exportStar(require("./User"), exports);
@@ -0,0 +1 @@
1
+ export declare const PREVIOUS_HAND_ACK_TIMEOUT = 5000;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PREVIOUS_HAND_ACK_TIMEOUT = void 0;
4
+ exports.PREVIOUS_HAND_ACK_TIMEOUT = 5000;
@@ -0,0 +1 @@
1
+ export * from './classes';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./classes"), exports);
@@ -0,0 +1,3 @@
1
+ import { ExtendedError } from "socket.io/dist/namespace";
2
+ import { ISocketServer, TrucoshiSocket } from "../classes";
3
+ export declare const trucoshiEvents: (server: ISocketServer) => (socket: TrucoshiSocket, next: (err?: ExtendedError) => void) => void;
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trucoshiEvents = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const types_1 = require("../../types");
6
+ const classes_1 = require("../classes");
7
+ const trucoshiEvents = (server) => (socket, next) => {
8
+ socket.on("disconnect", (_reason) => {
9
+ var _a;
10
+ try {
11
+ const user = server.users.getOrThrow((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session);
12
+ if (user) {
13
+ user.disconnect();
14
+ }
15
+ }
16
+ catch (e) {
17
+ // noop
18
+ }
19
+ });
20
+ socket.on(types_1.EClientEvent.PING, (msg) => {
21
+ socket.emit(types_1.EServerEvent.PONG, msg);
22
+ });
23
+ /**
24
+ * Create Match
25
+ */
26
+ socket.on(types_1.EClientEvent.CREATE_MATCH, (callback) => {
27
+ try {
28
+ if (!socket.data.user) {
29
+ throw new Error("Session not found");
30
+ }
31
+ const user = server.users.getOrThrow(socket.data.user.session);
32
+ const existingTable = user.ownedMatchId && server.tables.get(user.ownedMatchId);
33
+ if (existingTable) {
34
+ return callback({
35
+ success: false,
36
+ match: existingTable.getPublicMatch(socket.data.user.session),
37
+ });
38
+ }
39
+ const matchId = (0, crypto_1.randomUUID)();
40
+ const table = (0, classes_1.MatchTable)(matchId, socket.data.user.session);
41
+ user.ownedMatchId = matchId;
42
+ table.lobby.addPlayer(user.key, user.id, socket.data.user.session, 0, true);
43
+ server.chat.create(matchId);
44
+ socket.join(matchId);
45
+ server.tables.set(matchId, table);
46
+ return callback({ success: true, match: table.getPublicMatch(user.id) });
47
+ }
48
+ catch (e) {
49
+ return callback({ success: false });
50
+ }
51
+ });
52
+ /**
53
+ * Start Match
54
+ */
55
+ socket.on(types_1.EClientEvent.START_MATCH, (callback) => {
56
+ var _a;
57
+ try {
58
+ const user = server.users.getOrThrow((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session);
59
+ const matchId = user.ownedMatchId;
60
+ if (matchId) {
61
+ server.startMatch(matchId);
62
+ return callback({ success: true, matchSessionId: matchId });
63
+ }
64
+ }
65
+ catch (e) {
66
+ callback({ success: false });
67
+ }
68
+ callback({ success: false });
69
+ });
70
+ /**
71
+ * Join Match
72
+ */
73
+ socket.on(types_1.EClientEvent.JOIN_MATCH, (matchSessionId, teamIdx, callback) => {
74
+ var _a, _b;
75
+ try {
76
+ const user = server.users.getOrThrow((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session);
77
+ const table = server.tables.get(matchSessionId);
78
+ if (table) {
79
+ table.lobby.addPlayer(user.key, user.id, user.session, teamIdx, user.ownedMatchId === matchSessionId);
80
+ socket.join(table.matchSessionId);
81
+ server.emitMatchUpdate(table, []);
82
+ return callback({ success: true, match: table.getPublicMatch((_b = socket.data.user) === null || _b === void 0 ? void 0 : _b.session) });
83
+ }
84
+ callback({ success: false });
85
+ }
86
+ catch (e) {
87
+ callback({ success: false });
88
+ }
89
+ });
90
+ /**
91
+ * Get public matches
92
+ */
93
+ socket.on(types_1.EClientEvent.LIST_MATCHES, (filters = {}, callback) => {
94
+ const publicMatches = server.tables.getAll(filters);
95
+ callback({ success: true, matches: publicMatches });
96
+ });
97
+ /**
98
+ * Set Session
99
+ */
100
+ socket.on(types_1.EClientEvent.SET_SESSION, (id, session, callback = () => { }) => {
101
+ server.setOrGetSession(socket, id, session, ({ success, session }) => {
102
+ if (session && success) {
103
+ const activeMatches = server.tables
104
+ .findAll((table) => Boolean(table.isSessionPlaying(session)))
105
+ .map((match) => match.getPublicMatchInfo());
106
+ return callback({ success, session, activeMatches });
107
+ }
108
+ callback({ success, session, activeMatches: [] });
109
+ });
110
+ });
111
+ /**
112
+ * Fetch match with session
113
+ */
114
+ socket.on(types_1.EClientEvent.FETCH_MATCH, (session, matchId, callback) => {
115
+ return server.setOrGetSession(socket, null, session, ({ success }) => {
116
+ var _a;
117
+ if (!success) {
118
+ return callback({ success: false, match: null });
119
+ }
120
+ (_a = server.chat.rooms.get(matchId)) === null || _a === void 0 ? void 0 : _a.emit();
121
+ const match = server.emitSocketMatch(socket, matchId);
122
+ callback({ success: Boolean(match), match });
123
+ });
124
+ });
125
+ /**
126
+ * Set Player Ready
127
+ */
128
+ socket.on(types_1.EClientEvent.SET_PLAYER_READY, (matchSessionId, ready, callback) => {
129
+ var _a;
130
+ try {
131
+ if (!socket.data.user) {
132
+ throw new Error("Session not found");
133
+ }
134
+ const table = server.tables.getOrThrow(matchSessionId);
135
+ const player = table.lobby.players.find((player) => { var _a; return player && player.session === ((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session); });
136
+ if (player) {
137
+ player.setReady(ready);
138
+ server.emitMatchUpdate(table, [socket.id]);
139
+ callback({ success: true, match: table.getPublicMatch((_a = socket.data.user) === null || _a === void 0 ? void 0 : _a.session) });
140
+ }
141
+ }
142
+ catch (e) {
143
+ callback({ success: false });
144
+ }
145
+ });
146
+ next();
147
+ };
148
+ exports.trucoshiEvents = trucoshiEvents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trucoshi",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "main": "dist/index.js",
5
5
  "license": "GPL-3.0",
6
6
  "scripts": {
@@ -28,6 +28,7 @@
28
28
  "dist/index.d.ts",
29
29
  "dist/types.js",
30
30
  "dist/types.d.ts",
31
+ "dist/server/**/*",
31
32
  "dist/lib/**/*"
32
33
  ],
33
34
  "dependencies": {