Merge pull request 'feature/initial_gameplay_websockets' (#77) from feature/initial_gameplay_websockets into develop
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DADProject/pulls/77 Reviewed-by: Edd <[email protected]> Reviewed-by: FernandoJVideira <[email protected]>
This commit was merged in pull request #77.
This commit is contained in:
+4
-1
@@ -101,5 +101,8 @@ Desktop.ini
|
|||||||
# If you have any local secrets or files not to track, add them here:
|
# If you have any local secrets or files not to track, add them here:
|
||||||
# /path/to/some/file
|
# /path/to/some/file
|
||||||
|
|
||||||
package-lock.json
|
/frontend/package-lock.json
|
||||||
|
/websockets/package-lock.json
|
||||||
|
/api/package-lock.json
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
@@ -10,6 +10,8 @@ use App\Models\MatchGame;
|
|||||||
|
|
||||||
class GameController extends Controller
|
class GameController extends Controller
|
||||||
{
|
{
|
||||||
|
const GHOST_ID = 1;
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$query = Game::query()->with(["winner", "player1", "player2"]);
|
$query = Game::query()->with(["winner", "player1", "player2"]);
|
||||||
@@ -47,51 +49,34 @@ class GameController extends Controller
|
|||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
|
||||||
$q->where("player1_user_id", $user->id)->orWhere(
|
|
||||||
"player2_user_id",
|
|
||||||
$user->id,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
->whereIn("status", ["Pending", "Playing"])
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
$activeGame = Game::where(function ($q) use ($user) {
|
$activeGame = Game::where(function ($q) use ($user) {
|
||||||
$q->where("player1_user_id", $user->id)->orWhere(
|
$q->where("player1_user_id", $user->id)
|
||||||
"player2_user_id",
|
->orWhere("player2_user_id", $user->id);
|
||||||
$user->id,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
->whereIn("status", ["Pending", "Playing"])
|
->whereIn("status", ["Pending", "Playing"])
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if ($activeMatch || $activeGame) {
|
if ($activeGame) {
|
||||||
return response()->json(
|
return response()->json(["message" => "You already have an active game."], 400);
|
||||||
["message" => "You already have an active game or match."],
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$game = Game::create([
|
$game = Game::create([
|
||||||
"type" => $validated["type"],
|
"type" => $validated["type"],
|
||||||
"status" => "Pending",
|
"status" => "Pending",
|
||||||
"player1_user_id" => $user->id,
|
"player1_user_id" => $user->id,
|
||||||
"player2_user_id" => null,
|
"player2_user_id" => self::GHOST_ID,
|
||||||
"winner_user_id" => null,
|
"winner_user_id" => self::GHOST_ID,
|
||||||
"loser_user_id" => null,
|
"loser_user_id" => self::GHOST_ID,
|
||||||
"began_at" => now(),
|
"began_at" => now(),
|
||||||
"player1_points" => 0,
|
"player1_points" => 0,
|
||||||
"player2_points" => 0,
|
"player2_points" => 0,
|
||||||
"custom" => null,
|
"custom" => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json(
|
return response()->json([
|
||||||
[
|
"message" => "Multiplayer game room created",
|
||||||
"message" => "Multiplayer game room created",
|
"game" => $game,
|
||||||
"game" => $game,
|
], 201);
|
||||||
],
|
|
||||||
201,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function join(Game $game)
|
public function join(Game $game)
|
||||||
@@ -99,35 +84,14 @@ class GameController extends Controller
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
if ($game->player1_user_id == $user->id) {
|
if ($game->player1_user_id == $user->id) {
|
||||||
return response()->json(
|
return response()->json([
|
||||||
[
|
"message" => "You are the host. Waiting for opponent...",
|
||||||
"message" => "Waiting for opponent...",
|
"game" => $game
|
||||||
"game" => $game,
|
], 200);
|
||||||
],
|
|
||||||
200,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($game->status !== "Pending") {
|
if ($game->player2_user_id !== self::GHOST_ID) {
|
||||||
return response()->json(
|
return response()->json(["message" => "Game is full"], 400);
|
||||||
["message" => "Game is full or already started"],
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$activeGame = Game::where(function ($query) use ($user) {
|
|
||||||
$query
|
|
||||||
->where("player1_user_id", $user->id)
|
|
||||||
->orWhere("player2_user_id", $user->id);
|
|
||||||
})
|
|
||||||
->whereIn("status", ["Pending", "Playing"])
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
if ($activeGame) {
|
|
||||||
return response()->json(
|
|
||||||
["message" => "You are already in another active game."],
|
|
||||||
400,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$game->update([
|
$game->update([
|
||||||
@@ -135,13 +99,10 @@ class GameController extends Controller
|
|||||||
"player2_user_id" => $user->id,
|
"player2_user_id" => $user->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json(
|
return response()->json([
|
||||||
[
|
"message" => "Joined successfully!",
|
||||||
"message" => "Joined successfully! Game starts now.",
|
"game" => $game,
|
||||||
"game" => $game,
|
], 200);
|
||||||
],
|
|
||||||
200,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Game $game)
|
public function show(Game $game)
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
class BiscaGame {
|
||||||
|
constructor(id, type, player1, player2) {
|
||||||
|
this.id = id;
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
|
this.deck = [];
|
||||||
|
this.trumpCard = null;
|
||||||
|
this.trumpSuit = null;
|
||||||
|
|
||||||
|
this.table = {
|
||||||
|
playerCard: null,
|
||||||
|
opponentCard: null,
|
||||||
|
firstPlayerId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currentTurn = null;
|
||||||
|
this.status = "pending";
|
||||||
|
this.startTime = null;
|
||||||
|
|
||||||
|
this.players = {
|
||||||
|
[player1.id]: {
|
||||||
|
id: player1.id,
|
||||||
|
name: player1.name,
|
||||||
|
hand: [],
|
||||||
|
points: 0,
|
||||||
|
tricks: 0,
|
||||||
|
},
|
||||||
|
[player2.id]: {
|
||||||
|
id: player2.id,
|
||||||
|
name: player2.name,
|
||||||
|
hand: [],
|
||||||
|
points: 0,
|
||||||
|
tricks: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.deck = this.createDeck();
|
||||||
|
const lastCard = this.deck.pop();
|
||||||
|
this.trumpCard = lastCard;
|
||||||
|
this.trumpSuit = lastCard.suit;
|
||||||
|
|
||||||
|
this.dealInitialCards();
|
||||||
|
|
||||||
|
const playerIds = Object.keys(this.players);
|
||||||
|
this.currentTurn = playerIds.find((id) => id != 1) || playerIds[0];
|
||||||
|
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.status = "playing";
|
||||||
|
}
|
||||||
|
|
||||||
|
createDeck() {
|
||||||
|
const suits = ["c", "e", "o", "p"];
|
||||||
|
const ranks = [1, 2, 3, 4, 5, 6, 7, 11, 12, 13];
|
||||||
|
let deck = [];
|
||||||
|
|
||||||
|
for (const suit of suits) {
|
||||||
|
for (const rank of ranks) {
|
||||||
|
deck.push({
|
||||||
|
id: `${suit}-${rank}`,
|
||||||
|
suit,
|
||||||
|
rank,
|
||||||
|
strength: this.getStrength(rank),
|
||||||
|
points: this.getPoints(rank),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deck.sort(() => Math.random() - 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
dealInitialCards() {
|
||||||
|
const cardsToDeal = this.type == 9 ? 9 : 3;
|
||||||
|
const pIds = Object.keys(this.players);
|
||||||
|
for (let i = 0; i < cardsToDeal; i++) {
|
||||||
|
pIds.forEach((id) => this.drawCard(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCard(userId) {
|
||||||
|
if (this.deck.length > 0) {
|
||||||
|
this.players[userId].hand.push(this.deck.pop());
|
||||||
|
} else if (this.trumpCard) {
|
||||||
|
this.players[userId].hand.push(this.trumpCard);
|
||||||
|
this.trumpCard = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playCard(userId, cardId) {
|
||||||
|
if (this.currentTurn != userId) return { error: "Not your turn!" };
|
||||||
|
|
||||||
|
const player = this.players[userId];
|
||||||
|
const cardIndex = player.hand.findIndex((c) => c.id === cardId);
|
||||||
|
|
||||||
|
if (cardIndex === -1) return { error: "You don't own that card!" };
|
||||||
|
|
||||||
|
const card = player.hand[cardIndex];
|
||||||
|
|
||||||
|
const deckEmpty = this.deck.length === 0 && this.trumpCard === null;
|
||||||
|
if (this.table.playerCard && deckEmpty) {
|
||||||
|
const suitToFollow = this.table.playerCard.suit;
|
||||||
|
const hasSuit = player.hand.some((c) => c.suit === suitToFollow);
|
||||||
|
if (hasSuit && card.suit !== suitToFollow) {
|
||||||
|
return { error: "You must follow the suit!" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player.hand.splice(cardIndex, 1);
|
||||||
|
|
||||||
|
if (!this.table.playerCard) {
|
||||||
|
this.table.playerCard = card;
|
||||||
|
this.table.firstPlayerId = userId;
|
||||||
|
this.currentTurn = this.getOpponentId(userId);
|
||||||
|
return { action: "next_turn" };
|
||||||
|
} else {
|
||||||
|
this.table.opponentCard = card;
|
||||||
|
return this.resolveTrick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveTrick() {
|
||||||
|
const p1Id = this.table.firstPlayerId;
|
||||||
|
const p2Id = this.getOpponentId(p1Id);
|
||||||
|
const card1 = this.table.playerCard;
|
||||||
|
const card2 = this.table.opponentCard;
|
||||||
|
|
||||||
|
let winnerId = p2Id;
|
||||||
|
let p1Wins = false;
|
||||||
|
|
||||||
|
if (card2.suit === this.trumpSuit && card1.suit !== this.trumpSuit)
|
||||||
|
p1Wins = false;
|
||||||
|
else if (card1.suit === this.trumpSuit && card2.suit !== this.trumpSuit)
|
||||||
|
p1Wins = true;
|
||||||
|
else if (card1.suit === card2.suit)
|
||||||
|
p1Wins = card1.strength > card2.strength;
|
||||||
|
else p1Wins = true;
|
||||||
|
|
||||||
|
winnerId = p1Wins ? p1Id : p2Id;
|
||||||
|
|
||||||
|
const points = card1.points + card2.points;
|
||||||
|
this.players[winnerId].points += points;
|
||||||
|
this.players[winnerId].tricks += 1;
|
||||||
|
this.currentTurn = winnerId;
|
||||||
|
|
||||||
|
if (this.type == 3) {
|
||||||
|
this.drawCard(winnerId);
|
||||||
|
this.drawCard(this.getOpponentId(winnerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const trickResult = { winnerId, points, cards: [card1, card2] };
|
||||||
|
this.table = { playerCard: null, opponentCard: null, firstPlayerId: null };
|
||||||
|
|
||||||
|
if (this.players[winnerId].hand.length === 0) {
|
||||||
|
this.status = "finished";
|
||||||
|
|
||||||
|
const pIds = Object.keys(this.players);
|
||||||
|
const p1Obj = this.players[pIds[0]];
|
||||||
|
const p2Obj = this.players[pIds[1]];
|
||||||
|
|
||||||
|
const totalTime = (Date.now() - this.startTime) / 1000;
|
||||||
|
|
||||||
|
let gameWinnerId = null;
|
||||||
|
let isDraw = false;
|
||||||
|
|
||||||
|
if (p1Obj.points > p2Obj.points) gameWinnerId = p1Obj.id;
|
||||||
|
else if (p2Obj.points > p1Obj.points) gameWinnerId = p2Obj.id;
|
||||||
|
else isDraw = true;
|
||||||
|
|
||||||
|
const finalWinner = isDraw ? null : gameWinnerId;
|
||||||
|
const finalLoser = isDraw
|
||||||
|
? null
|
||||||
|
: gameWinnerId == p1Obj.id
|
||||||
|
? p2Obj.id
|
||||||
|
: p1Obj.id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
action: "game_ended",
|
||||||
|
trickResult,
|
||||||
|
winnerId: finalWinner,
|
||||||
|
loserId: finalLoser,
|
||||||
|
isDraw: isDraw,
|
||||||
|
player1_id: pIds[0],
|
||||||
|
player1_points: p1Obj.points,
|
||||||
|
player2_id: pIds[1],
|
||||||
|
player2_points: p2Obj.points,
|
||||||
|
totalTime: totalTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { action: "trick_resolved", trickResult };
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateForPlayer(userId) {
|
||||||
|
const oppId = this.getOpponentId(userId);
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
trumpCard: this.trumpCard,
|
||||||
|
trumpSuit: this.trumpSuit,
|
||||||
|
deckCount: this.deck.length + (this.trumpCard ? 1 : 0),
|
||||||
|
currentTurn: this.currentTurn,
|
||||||
|
table: this.table,
|
||||||
|
hand: this.players[userId].hand,
|
||||||
|
points: this.players[userId].points,
|
||||||
|
opponent: {
|
||||||
|
points: this.players[oppId].points,
|
||||||
|
cardCount: this.players[oppId].hand.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getOpponentId(id) {
|
||||||
|
return Object.keys(this.players).find((pid) => pid != id);
|
||||||
|
}
|
||||||
|
getPoints(r) {
|
||||||
|
const p = { 1: 11, 7: 10, 13: 4, 11: 3, 12: 2 };
|
||||||
|
return p[r] || 0;
|
||||||
|
}
|
||||||
|
getStrength(r) {
|
||||||
|
const s = {
|
||||||
|
1: 10,
|
||||||
|
7: 9,
|
||||||
|
13: 8,
|
||||||
|
11: 7,
|
||||||
|
12: 6,
|
||||||
|
6: 5,
|
||||||
|
5: 4,
|
||||||
|
4: 3,
|
||||||
|
3: 2,
|
||||||
|
2: 1,
|
||||||
|
};
|
||||||
|
return s[r] || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BiscaGame;
|
||||||
+138
-11
@@ -1,19 +1,146 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { getGame, createGame } from "../state/game.js";
|
||||||
import { getUser } from "../state/connection.js";
|
import { getUser } from "../state/connection.js";
|
||||||
import { createGame, joinGame, playCard, getGame } from "../state/game.js";
|
|
||||||
|
|
||||||
const API_URL = process.env.API_URL || "http://localhost:8000/api";
|
const API_URL = "http://localhost:8000/api/v1";
|
||||||
|
|
||||||
export const handleGameEvents = (io, socket) => {
|
async function saveGameResult(gameId, result, token) {
|
||||||
socket.on("create-game", async (data) => {
|
try {
|
||||||
try {
|
const payload = {
|
||||||
const user = getUser(socket.id);
|
status: "Ended",
|
||||||
const game = createGame(data.type, user);
|
is_draw: result.isDraw,
|
||||||
socket.join(`game_${game.match_id}`);
|
total_time: result.totalTime,
|
||||||
|
player1_points: result.player1_points,
|
||||||
|
player2_points: result.player2_points,
|
||||||
|
};
|
||||||
|
|
||||||
} catch (error) {
|
if (!result.isDraw && result.winnerId) {
|
||||||
console.error("Error creating game:", error);
|
payload.winner_user_id = result.winnerId;
|
||||||
socket.emit("error", { message: "Failed to create game." });
|
payload.loser_user_id = result.loserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios.put(`${API_URL}/games/${gameId}`, payload, {
|
||||||
|
headers: { Authorization: token },
|
||||||
|
});
|
||||||
|
console.log(`[DB] Game ${gameId} saved.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[DB Error] Game ${gameId}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (io, socket) => {
|
||||||
|
socket.on("join-game", async ({ gameId }) => {
|
||||||
|
const user = getUser(socket.id);
|
||||||
|
if (!user) {
|
||||||
|
socket.emit("error", { message: "Not authenticated." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let game = getGame(gameId);
|
||||||
|
|
||||||
|
if (!game) {
|
||||||
|
try {
|
||||||
|
const token = socket.handshake.auth.token;
|
||||||
|
const response = await axios.get(`${API_URL}/games/${gameId}`, {
|
||||||
|
headers: { Authorization: token },
|
||||||
|
});
|
||||||
|
|
||||||
|
const gameData = response.data.data || response.data;
|
||||||
|
|
||||||
|
if (gameData.status === "Ended") {
|
||||||
|
socket.emit("error", { message: "Game already finished." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
gameData.player1_user_id != user.id &&
|
||||||
|
gameData.player2_user_id != user.id &&
|
||||||
|
gameData.player2_user_id !== 1
|
||||||
|
) {
|
||||||
|
socket.emit("error", { message: "You are not in this game." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const p1Name = gameData.player1?.name || "Player 1";
|
||||||
|
const p2Name = gameData.player2?.name || "Player 2";
|
||||||
|
const type = gameData.type == "9" ? 9 : 3;
|
||||||
|
|
||||||
|
game = createGame(
|
||||||
|
gameId,
|
||||||
|
type,
|
||||||
|
{ id: gameData.player1_user_id, name: p1Name },
|
||||||
|
{ id: gameData.player2_user_id, name: p2Name }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
socket.emit("error", { message: "Failed to join game." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GHOST_ID = 1;
|
||||||
|
|
||||||
|
if (game && game.players) {
|
||||||
|
if (!game.players[user.id]) {
|
||||||
|
if (game.players[GHOST_ID]) {
|
||||||
|
delete game.players[GHOST_ID];
|
||||||
|
game.players[user.id] = {
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
hand: [],
|
||||||
|
points: 0,
|
||||||
|
tricks: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.join(`game_${gameId}`);
|
||||||
|
|
||||||
|
if (game && game.players[user.id]) {
|
||||||
|
socket.emit("game-state", game.getStateForPlayer(user.id));
|
||||||
|
console.log(`--> Estado enviado para User ${user.id}`);
|
||||||
|
} else {
|
||||||
|
socket.emit("error", { message: "Error joining game state." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("play-card", ({ gameId, cardId }) => {
|
||||||
|
const user = getUser(socket.id);
|
||||||
|
const game = getGame(gameId);
|
||||||
|
|
||||||
|
if (!game || !user) return;
|
||||||
|
|
||||||
|
const result = game.playCard(user.id, cardId);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
socket.emit("game-error", { message: result.error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomName = `game_${gameId}`;
|
||||||
|
const socketsInRoom = io.sockets.adapter.rooms.get(roomName);
|
||||||
|
if (socketsInRoom) {
|
||||||
|
for (const socketId of socketsInRoom) {
|
||||||
|
const s = io.sockets.sockets.get(socketId);
|
||||||
|
const u = getUser(socketId);
|
||||||
|
if (u && game.players[u.id]) {
|
||||||
|
s.emit("game-state", game.getStateForPlayer(u.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.action === "trick_resolved") {
|
||||||
|
io.to(roomName).emit("trick-end", result.trickResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.action === "game_ended") {
|
||||||
|
io.to(roomName).emit("game-over", {
|
||||||
|
winnerId: result.winnerId,
|
||||||
|
isDraw: result.isDraw,
|
||||||
|
p1Points: result.player1_points,
|
||||||
|
p2Points: result.player2_points,
|
||||||
|
});
|
||||||
|
saveGameResult(gameId, result, socket.handshake.auth.token);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
-573
@@ -1,573 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "websockets",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "websockets",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"nodemon": "^3.1.11",
|
|
||||||
"socket.io": "^4.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@socket.io/component-emitter": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/cors": {
|
|
||||||
"version": "2.8.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
|
||||||
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "24.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
|
||||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~7.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/accepts": {
|
|
||||||
"version": "1.3.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
|
||||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-types": "~2.1.34",
|
|
||||||
"negotiator": "0.6.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/anymatch": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"normalize-path": "^3.0.0",
|
|
||||||
"picomatch": "^2.0.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/base64id": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^4.5.0 || >= 5.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/binary-extensions": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/brace-expansion": {
|
|
||||||
"version": "1.1.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/braces": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fill-range": "^7.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"anymatch": "~3.1.2",
|
|
||||||
"braces": "~3.0.2",
|
|
||||||
"glob-parent": "~5.1.2",
|
|
||||||
"is-binary-path": "~2.1.0",
|
|
||||||
"is-glob": "~4.0.1",
|
|
||||||
"normalize-path": "~3.0.0",
|
|
||||||
"readdirp": "~3.6.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.10.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "~2.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/cookie": {
|
|
||||||
"version": "0.7.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
|
||||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cors": {
|
|
||||||
"version": "2.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
|
||||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"object-assign": "^4",
|
|
||||||
"vary": "^1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/debug": {
|
|
||||||
"version": "4.3.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "^2.1.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"supports-color": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io": {
|
|
||||||
"version": "6.6.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
|
|
||||||
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/cors": "^2.8.12",
|
|
||||||
"@types/node": ">=10.0.0",
|
|
||||||
"accepts": "~1.3.4",
|
|
||||||
"base64id": "2.0.0",
|
|
||||||
"cookie": "~0.7.2",
|
|
||||||
"cors": "~2.8.5",
|
|
||||||
"debug": "~4.3.1",
|
|
||||||
"engine.io-parser": "~5.2.1",
|
|
||||||
"ws": "~8.17.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io-parser": {
|
|
||||||
"version": "5.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
|
||||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fill-range": {
|
|
||||||
"version": "7.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"to-regex-range": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/glob-parent": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"is-glob": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-flag": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ignore-by-default": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/is-binary-path": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"binary-extensions": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-extglob": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-glob": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-extglob": "^2.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-number": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-db": {
|
|
||||||
"version": "1.52.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "2.1.35",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "1.52.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
|
||||||
"version": "3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^1.1.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
|
||||||
"version": "2.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/negotiator": {
|
|
||||||
"version": "0.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/nodemon": {
|
|
||||||
"version": "3.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
|
|
||||||
"integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"chokidar": "^3.5.2",
|
|
||||||
"debug": "^4",
|
|
||||||
"ignore-by-default": "^1.0.1",
|
|
||||||
"minimatch": "^3.1.2",
|
|
||||||
"pstree.remy": "^1.1.8",
|
|
||||||
"semver": "^7.5.3",
|
|
||||||
"simple-update-notifier": "^2.0.0",
|
|
||||||
"supports-color": "^5.5.0",
|
|
||||||
"touch": "^3.1.0",
|
|
||||||
"undefsafe": "^2.0.5"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"nodemon": "bin/nodemon.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/nodemon"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/normalize-path": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/object-assign": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/picomatch": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pstree.remy": {
|
|
||||||
"version": "1.1.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
|
||||||
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/readdirp": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"picomatch": "^2.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
|
||||||
"version": "7.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
|
||||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/simple-update-notifier": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^7.5.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io": {
|
|
||||||
"version": "4.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
|
||||||
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"accepts": "~1.3.4",
|
|
||||||
"base64id": "~2.0.0",
|
|
||||||
"cors": "~2.8.5",
|
|
||||||
"debug": "~4.3.2",
|
|
||||||
"engine.io": "~6.6.0",
|
|
||||||
"socket.io-adapter": "~2.5.2",
|
|
||||||
"socket.io-parser": "~4.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-adapter": {
|
|
||||||
"version": "2.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
|
||||||
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "~4.3.4",
|
|
||||||
"ws": "~8.17.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socket.io-parser": {
|
|
||||||
"version": "4.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
|
||||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
|
||||||
"debug": "~4.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/supports-color": {
|
|
||||||
"version": "5.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"has-flag": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-number": "^7.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/touch": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"nodetouch": "bin/nodetouch.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undefsafe": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "7.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
|
||||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/vary": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.13.2",
|
||||||
"nodemon": "^3.1.11",
|
"nodemon": "^3.1.11",
|
||||||
"socket.io": "^4.8.1"
|
"socket.io": "^4.8.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,67 @@
|
|||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
|
import axios from "axios";
|
||||||
import { handleConnectionEvents } from "./events/connection.js";
|
import { handleConnectionEvents } from "./events/connection.js";
|
||||||
|
import { addUser, removeUser } from "./state/connection.js";
|
||||||
|
import gameEvents from "./events/game.js";
|
||||||
|
|
||||||
export const server = {
|
export const server = {
|
||||||
io: null,
|
io: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_URL = "http://localhost:8000/api/v1";
|
||||||
|
|
||||||
export const serverStart = (port) => {
|
export const serverStart = (port) => {
|
||||||
server.io = new Server(port, {
|
server.io = new Server(port, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*",
|
origin: "*",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.io.use(async (socket, next) => {
|
||||||
|
const token = socket.handshake.auth.token;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return next(new Error("Authentication error: No token provided"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_URL}/users/me`, {
|
||||||
|
headers: { Authorization: token },
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = response.data.data || response.data;
|
||||||
|
|
||||||
|
if (!user || !user.id) {
|
||||||
|
return next(new Error("Authentication error: User data not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
addUser(socket.id, user);
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
console.error(
|
||||||
|
"❌ Erro Laravel:",
|
||||||
|
error.response.status,
|
||||||
|
error.response.data
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error("❌ Erro Rede:", error.message);
|
||||||
|
}
|
||||||
|
next(new Error("Authentication error: Invalid token"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.io.on("connection", (socket) => {
|
server.io.on("connection", (socket) => {
|
||||||
console.log("New connection:", socket.id);
|
console.log("New connection:", socket.id);
|
||||||
|
|
||||||
|
gameEvents(server.io, socket);
|
||||||
|
|
||||||
handleConnectionEvents(server.io, socket);
|
handleConnectionEvents(server.io, socket);
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
removeUser(socket.id);
|
||||||
|
console.log("Disconnected:", socket.id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
const users = new Map();
|
const users = new Map();
|
||||||
|
|
||||||
export const addUser = (socket, user) => {
|
export const addUser = (socketId, userData) => {
|
||||||
users.set(socket.id, user);
|
users.set(socketId, userData);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeUser = (socketID) => {
|
export const getUser = (socketId) => {
|
||||||
const userToDelete = { ...users.get(socketID) };
|
return users.get(socketId);
|
||||||
users.delete(socketID);
|
|
||||||
return userToDelete;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUser = (socketID) => {
|
export const removeUser = (socketId) => {
|
||||||
return users.get(socketID);
|
users.delete(socketId);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserCount = () => {
|
export const getUserCount = () => {
|
||||||
|
|||||||
+16
-173
@@ -1,186 +1,29 @@
|
|||||||
const games = new Map();
|
const BiscaGame = require("../classes/BiscaGame");
|
||||||
let currentGameID = 0;
|
|
||||||
|
|
||||||
// Bisca deck configuration
|
const activeGames = new Map();
|
||||||
const suits = ["hearts", "diamonds", "clubs", "spades"];
|
|
||||||
const cards = [
|
|
||||||
{ face: "A", value: 11, points: 11 },
|
|
||||||
{ face: "7", value: 10, points: 10 },
|
|
||||||
{ face: "K", value: 4, points: 4 },
|
|
||||||
{ face: "J", value: 3, points: 3 },
|
|
||||||
{ face: "Q", value: 2, points: 2 },
|
|
||||||
{ face: "6", value: 0, points: 0 },
|
|
||||||
{ face: "5", value: 0, points: 0 },
|
|
||||||
{ face: "4", value: 0, points: 0 },
|
|
||||||
{ face: "3", value: 0, points: 0 },
|
|
||||||
{ face: "2", value: 0, points: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const createDeck = () => {
|
const createGame = (id, type, player1, player2) => {
|
||||||
const deck = [];
|
const game = new BiscaGame(id, type, player1, player2);
|
||||||
suits.forEach((suit) => {
|
|
||||||
cards.forEach((card) => {
|
|
||||||
deck.push({ ...card, suit });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return deck;
|
|
||||||
};
|
|
||||||
|
|
||||||
const shuffleDeck = (deck) => {
|
game.init();
|
||||||
const shuffled = [...deck];
|
|
||||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
||||||
}
|
|
||||||
return shuffled;
|
|
||||||
};
|
|
||||||
|
|
||||||
const dealCards = (deck, cardsPerPlayer = 3) => {
|
activeGames.set(id, game);
|
||||||
const player1Hand = deck.slice(0, cardsPerPlayer);
|
|
||||||
const player2Hand = deck.slice(cardsPerPlayer, cardsPerPlayer * 2);
|
|
||||||
const trumpCard = deck.pop();
|
|
||||||
return {
|
|
||||||
player1Hand,
|
|
||||||
player2Hand,
|
|
||||||
trumpCard,
|
|
||||||
remainingDeck: deck,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createGame = (type, host) => {
|
console.log(`[Game State] Game created: ${id} (Type: ${type})`);
|
||||||
const gameID = currentGameID++;
|
|
||||||
const game = {
|
|
||||||
match_id: gameID,
|
|
||||||
type: type,
|
|
||||||
player1_user_id: host.id,
|
|
||||||
player2_user_id: null,
|
|
||||||
is_draw: false,
|
|
||||||
winner_user_id: null,
|
|
||||||
loser_user_id: null,
|
|
||||||
status: "pending",
|
|
||||||
began_at: null,
|
|
||||||
ended_at: null,
|
|
||||||
player1_points: 0,
|
|
||||||
player2_points: 0,
|
|
||||||
// Game-specific state
|
|
||||||
player1_hand: [],
|
|
||||||
player2_hand: [],
|
|
||||||
trump_card: null,
|
|
||||||
trump_suit: null,
|
|
||||||
current_trick: [],
|
|
||||||
current_player: null,
|
|
||||||
round_number: 0,
|
|
||||||
};
|
|
||||||
games.set(gameID, game);
|
|
||||||
return game;
|
return game;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const joinGame = (gameID, player2) => {
|
const getGame = (id) => {
|
||||||
const game = games.get(gameID);
|
return activeGames.get(id);
|
||||||
game.player2_user_id = player2.id;
|
|
||||||
game.status = "playing";
|
|
||||||
game.began_at = new Date();
|
|
||||||
|
|
||||||
const cardCount = parseInt(game.type, 10);
|
|
||||||
if (![3, 9].includes(cardCount)) {
|
|
||||||
throw new Error("Unsupported game type");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize game state
|
|
||||||
const deck = shuffleDeck(createDeck());
|
|
||||||
const { player1Hand, player2Hand, trumpCard, remainingDeck } = dealCards(
|
|
||||||
deck,
|
|
||||||
cardCount
|
|
||||||
);
|
|
||||||
|
|
||||||
game.player1_hand = player1Hand;
|
|
||||||
game.player2_hand = player2Hand;
|
|
||||||
game.trump_card = trumpCard;
|
|
||||||
game.trump_suit = trumpCard.suit;
|
|
||||||
game.remaining_deck = remainingDeck;
|
|
||||||
game.current_player = game.player1_user_id; // Player 1 starts
|
|
||||||
|
|
||||||
return game;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const playCard = (gameID, playerID, cardIndex) => {
|
const removeGame = (id) => {
|
||||||
const game = games.get(gameID);
|
activeGames.delete(id);
|
||||||
if (game.current_player !== playerID) {
|
console.log(`[Game State] Game removed: ${id}`);
|
||||||
throw new Error("Not this player's turn");
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPlayer1 = playerID === game.player1_user_id;
|
|
||||||
const playerHand = isPlayer1 ? game.player1_hand : game.player2_hand;
|
|
||||||
const playedCard = playerHand.splice(cardIndex, 1)[0];
|
|
||||||
|
|
||||||
game.current_trick.push({ playerID, card: playedCard });
|
|
||||||
|
|
||||||
if (game.current_trick.length === 2) {
|
|
||||||
resolveTrick(game);
|
|
||||||
} else {
|
|
||||||
game.current_player = isPlayer1
|
|
||||||
? game.player2_user_id
|
|
||||||
: game.player1_user_id;
|
|
||||||
}
|
|
||||||
return game;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveTrick = (game) => {
|
module.exports = {
|
||||||
const [first, second] = game.current_trick;
|
createGame,
|
||||||
let winner;
|
getGame,
|
||||||
|
removeGame,
|
||||||
if (first.card.suit === second.card.suit) {
|
|
||||||
winner = first.card.value > second.card.value ? first : second;
|
|
||||||
} else if (second.card.suit === game.trump_suit) {
|
|
||||||
winner = second;
|
|
||||||
} else {
|
|
||||||
winner = first; // First card wins if no trump and different suits
|
|
||||||
}
|
|
||||||
|
|
||||||
const points = first.card.points + second.card.points;
|
|
||||||
winner.playerID === game.player1_user_id
|
|
||||||
? (game.player1_points += points)
|
|
||||||
: (game.player2_points += points);
|
|
||||||
|
|
||||||
if (deck.length > 0) {
|
|
||||||
const winnerHand =
|
|
||||||
winner.playerID === game.player1_user_id
|
|
||||||
? game.player1_hand
|
|
||||||
: game.player2_hand;
|
|
||||||
loserHand =
|
|
||||||
winner.playerID === game.player1_user_id
|
|
||||||
? game.player2_hand
|
|
||||||
: game.player1_hand;
|
|
||||||
|
|
||||||
winnerHand.push(game.deck.shift());
|
|
||||||
if (game.deck.length > 0) {
|
|
||||||
loserHand.push(game.deck.shift());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
game.current_trick = [];
|
|
||||||
game.current_player = winner.playerID;
|
|
||||||
game.round_number += 1;
|
|
||||||
|
|
||||||
if (game.player1_hand.length === 0 && game.player2_hand.length === 0) {
|
|
||||||
endGame(game);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const endGame = (game) => {
|
|
||||||
game.status = "ended";
|
|
||||||
game.ended_at = new Date();
|
|
||||||
|
|
||||||
if (game.player1_points > game.player2_points) {
|
|
||||||
game.winner_user_id = game.player1_user_id;
|
|
||||||
game.loser_user_id = game.player2_user_id;
|
|
||||||
} else if (game.player2_points > game.player1_points) {
|
|
||||||
game.winner_user_id = game.player2_user_id;
|
|
||||||
game.loser_user_id = game.player1_user_id;
|
|
||||||
} else {
|
|
||||||
game.is_draw = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getGame = (gameID) => games.get(gameID);
|
|
||||||
export const getAllGames = () => Array.from(games.values());
|
|
||||||
|
|||||||
Reference in New Issue
Block a user