feat: Überarbeite Crash-Spiel-Logik zur Initialisierung mit WebSocket und verbessere Verbindungsverwaltung

This commit is contained in:
2025-11-23 13:31:55 +01:00
parent 1fc0542df2
commit b8d142f241
2 changed files with 62 additions and 86 deletions

View File

@@ -1,7 +1,7 @@
import express from "express";
import http from "http";
import { WebSocketServer } from "ws";
import { handleConnection } from "./services/crashGame";
import { WebSocketServer } from "ws";
import { initializeCrashGame } from "./services/crashGame";
import session from "express-session";
import cors from "cors";
import cookieParser from "cookie-parser";
@@ -20,12 +20,14 @@ import { adminRouter } from "./routes/admin";
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
wss.on('connection', handleConnection);
const wss = new WebSocketServer({ server });
// Übergebe den WebSocket-Server an die Spiellogik zur Initialisierung.
initializeCrashGame(wss);
// Reverse Proxy
app.set("trust proxy", true);
// Session Store auf PostgreSQL umstellen
const PgStore = connectPgSimple(session);
const sessionStore = new PgStore({
@@ -117,6 +119,6 @@ app.use("/wallet", walletRouter);
app.use("/slot", slotRouter);
app.use("/admin", adminRouter);
app.listen(config.port, () => {
server.listen(config.port, () => {
console.log(`Bierbaron backend läuft auf Port ${config.port}`);
});

View File

@@ -1,20 +1,18 @@
// backend/src/services/crashGame.ts
import { WebSocket } from "ws";
import { WebSocket, WebSocketServer } from "ws";
import { pool } from "../db";
import { query as dbQuery } from "../db"; // Umbenannt, um Konflikte zu vermeiden
import { query as dbQuery } from "../db";
// --- Typen und Interfaces ---
type GamePhase = "waiting" | "betting" | "running" | "crashed";
interface Player {
ws: WebSocket;
ws: AuthenticatedWebSocket;
userId: number;
discordName: string;
bet: number;
cashedOutAt?: number;
}
// Erweitern des WebSocket-Typs, um Benutzerinformationen zu speichern
interface AuthenticatedWebSocket extends WebSocket {
userId?: number;
discordName?: string;
@@ -30,14 +28,24 @@ let roundStartTime = 0;
const clients = new Set<AuthenticatedWebSocket>();
// --- WebSocket-Verwaltung ---
// --- Initialisierungsfunktion ---
export function initializeCrashGame(wss: WebSocketServer) {
wss.on('connection', (ws: AuthenticatedWebSocket) => {
handleConnection(ws);
});
export function handleConnection(ws: AuthenticatedWebSocket) {
runGameLoop();
startHealthCheck();
console.log("[Crash] Crash game service initialized and attached to WebSocket server.");
}
// --- WebSocket-Verwaltung ---
function handleConnection(ws: AuthenticatedWebSocket) {
ws.isAlive = true;
clients.add(ws);
console.log("[Crash] New client connected.");
// Sende den aktuellen Zustand an den neuen Client
ws.send(JSON.stringify({
type: "gameState",
phase,
@@ -50,14 +58,8 @@ export function handleConnection(ws: AuthenticatedWebSocket) {
}))
}));
ws.on("pong", () => {
ws.isAlive = true;
});
ws.on("message", (message) => {
handleMessage(ws, message.toString());
});
ws.on("pong", () => { ws.isAlive = true; });
ws.on("message", (message) => { handleMessage(ws, message.toString()); });
ws.on("close", () => {
clients.delete(ws);
players.delete(ws);
@@ -86,13 +88,11 @@ function broadcastPlayerList() {
}
// --- Nachrichtenverarbeitung ---
async function handleMessage(ws: AuthenticatedWebSocket, message: string) {
try {
const data = JSON.parse(message);
switch (data.type) {
case "auth":
// Spieler authentifiziert sich mit seiner Session
const [user] = await dbQuery<{ id: number, discord_name: string }>(
"SELECT id, discord_name FROM users WHERE id = $1",
[data.payload.userId]
@@ -103,11 +103,9 @@ async function handleMessage(ws: AuthenticatedWebSocket, message: string) {
console.log(`[Crash] Client authenticated as ${ws.discordName} (ID: ${ws.userId})`);
}
break;
case "bet":
await handleBet(ws, data.payload.amount);
break;
case "cashout":
await handleCashout(ws);
break;
@@ -118,13 +116,9 @@ async function handleMessage(ws: AuthenticatedWebSocket, message: string) {
}
async function handleBet(ws: AuthenticatedWebSocket, amount: number) {
if (phase !== "betting" || !ws.userId || !ws.discordName || players.has(ws)) {
return; // Falsche Phase, nicht authentifiziert oder schon gewettet
}
if (phase !== "betting" || !ws.userId || !ws.discordName || players.has(ws)) return;
const betAmount = Math.floor(amount);
if (!Number.isFinite(betAmount) || betAmount <= 0) {
return;
}
if (!Number.isFinite(betAmount) || betAmount <= 0) return;
const client = await pool.connect();
try {
@@ -146,7 +140,6 @@ async function handleBet(ws: AuthenticatedWebSocket, amount: number) {
players.set(ws, { ws, userId: ws.userId, discordName: ws.discordName, bet: betAmount });
console.log(`[Crash] ${ws.discordName} placed a bet of ${betAmount}`);
broadcastPlayerList();
} catch (error) {
await client.query("ROLLBACK");
console.error("[Crash] Bet failed:", error);
@@ -156,13 +149,9 @@ async function handleBet(ws: AuthenticatedWebSocket, amount: number) {
}
async function handleCashout(ws: AuthenticatedWebSocket) {
if (phase !== "running" || !players.has(ws)) {
return;
}
if (phase !== "running" || !players.has(ws)) return;
const player = players.get(ws)!;
if (player.cashedOutAt) {
return; // Schon ausbezahlt
}
if (player.cashedOutAt) return;
player.cashedOutAt = multiplier;
console.log(`[Crash] ${player.discordName} cashed out at ${multiplier}x`);
@@ -181,7 +170,6 @@ async function handleCashout(ws: AuthenticatedWebSocket) {
}
// --- Spiellogik ---
function calculateCrashPoint(): number {
const r = Math.random();
const crash = 1 / (1 - r);
@@ -190,66 +178,52 @@ function calculateCrashPoint(): number {
async function runGameLoop() {
while (true) {
// 1. Betting Phase (10 Sekunden)
phase = "betting";
crashPoint = calculateCrashPoint();
console.log(`[Crash] New round. Crash point: ${crashPoint}x`);
broadcast({ type: "newRound", phase: "betting", duration: 10000 });
await new Promise(resolve => setTimeout(resolve, 10000));
// 2. Running Phase
phase = "running";
roundStartTime = Date.now();
multiplier = 1.00;
broadcast({ type: "roundStart", phase: "running" });
if (players.size > 0) {
phase = "running";
roundStartTime = Date.now();
multiplier = 1.00;
broadcast({ type: "roundStart", phase: "running" });
const runInterval = setInterval(() => {
const elapsed = (Date.now() - roundStartTime) / 1000;
multiplier = parseFloat(Math.max(1.00, Math.pow(1.05, elapsed)).toFixed(2));
if (multiplier >= crashPoint) {
clearInterval(runInterval);
phase = "crashed";
multiplier = crashPoint;
console.log(`[Crash] Round crashed at ${crashPoint}x`);
broadcast({ type: "crash", multiplier: crashPoint });
// Verluste wurden bereits beim Einsatz verbucht. Gewinne beim Cashout.
} else {
broadcast({ type: "multiplierUpdate", multiplier });
let gameRunning = true;
while(gameRunning) {
const elapsed = (Date.now() - roundStartTime) / 1000;
multiplier = parseFloat(Math.max(1.00, Math.pow(1.05, elapsed)).toFixed(2));
if (multiplier >= crashPoint) {
gameRunning = false;
phase = "crashed";
multiplier = crashPoint;
console.log(`[Crash] Round crashed at ${crashPoint}x`);
broadcast({ type: "crash", multiplier: crashPoint });
} else {
broadcast({ type: "multiplierUpdate", multiplier });
}
await new Promise(resolve => setTimeout(resolve, 100));
}
}, 100);
} else {
console.log("[Crash] No players, skipping round.");
}
// Warte, bis die Runde gecrasht ist
await new Promise<void>(resolve => {
const checkCrash = () => {
if (phase === 'crashed') resolve();
else setTimeout(checkCrash, 50);
};
checkCrash();
});
clearInterval(runInterval);
// 5s Pause nach dem Crash
await new Promise(resolve => setTimeout(resolve, 5000));
// Reset für die nächste Runde
players.clear();
multiplier = 1.0;
phase = "waiting";
}
}
// --- Health Check für tote Verbindungen ---
setInterval(() => {
clients.forEach((ws) => {
if (!ws.isAlive) {
ws.terminate();
return;
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
// Starte den Spiel-Loop
runGameLoop();
function startHealthCheck() {
setInterval(() => {
clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
}