fix: resolve all 3 open Gitea issues
Issue #1 - Bot vs Bot Turnierspiele: - triggerBotMove ruft sich rekursiv selbst auf wenn der nächste Spieler auch ein Bot ist (bot vs bot läuft jetzt vollautomatisch durch) - startNextRoundGames erstellt aktive Spiele für die nächste Runde wenn eine Runde endet; bot vs bot Spiele starten sofort ohne Spectator - updateRoundResult gibt jetzt die nächste Runden-Daten zurück statt void - Alle Handler (sendMove, claimAbandoned, claimTimeout, triggerBotMove) starten die nächste Runde automatisch Issue #2 - Bot-Schwierigkeit zu hoch: - randomChance zu StockfishLevel hinzugefügt: Level 1=80%, 2=50%, 3=15%, 4=3%, 5-6=0% zufällige Züge statt Stockfish-Best-Move - Gilt sowohl für Socket-Bot-Züge als auch /v1/ai/move REST-Endpoint Issue #3 - Turnier beenden + Zurück-Button: - POST /:code/cancel Endpoint (nur Host), setzt status='finished' - Turnier-Seite: 'Turnier beenden' Button für Host (rot, outline) - tournamentCode Feld im Game-Typ; wird beim Erstellen von Turnierspielen gesetzt (inkl. Folgerunden) - GamePage zeigt '← Zur Turnierübersicht' Link wenn tournamentCode gesetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import InviteFriendsModal from "@/components/InviteFriendsModal";
|
import InviteFriendsModal from "@/components/InviteFriendsModal";
|
||||||
import { useSession } from "@/context/session";
|
import { useSession } from "@/context/session";
|
||||||
import { fetchTournament, joinTournament, startTournament, addBotToTournament } from "@/lib/tournament";
|
import { fetchTournament, joinTournament, startTournament, cancelTournament, addBotToTournament } from "@/lib/tournament";
|
||||||
import { BOTS } from "@/bots";
|
import { BOTS } from "@/bots";
|
||||||
import type { Tournament, TournamentRound } from "@michess/types";
|
import type { Tournament, TournamentRound } from "@michess/types";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -56,6 +56,14 @@ export default function TournamentDetailPage() {
|
|||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCancel() {
|
||||||
|
if (!user?.id) return;
|
||||||
|
setActionLoading(true);
|
||||||
|
const updated = await cancelTournament(code);
|
||||||
|
if (updated) setTournament(updated);
|
||||||
|
setActionLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleStart() {
|
async function handleStart() {
|
||||||
if (!user?.id) return;
|
if (!user?.id) return;
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
@@ -162,6 +170,15 @@ export default function TournamentDetailPage() {
|
|||||||
Turnier starten
|
Turnier starten
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{isHost && tournament.status !== "finished" && (
|
||||||
|
<button
|
||||||
|
className={"btn btn-error btn-sm btn-outline" + (actionLoading ? " loading" : "")}
|
||||||
|
onClick={handleCancel}
|
||||||
|
disabled={actionLoading}
|
||||||
|
>
|
||||||
|
Turnier beenden
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -656,10 +656,17 @@ export default function GamePage({ initialLobby }: { initialLobby: Game }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!lobby.endReason && (
|
{!lobby.endReason && (
|
||||||
<div className="mt-1 flex justify-end">
|
<div className="mt-1 flex justify-end gap-2">
|
||||||
<InviteFriendsModal gameCode={initialLobby.code!} label="Freunde einladen" />
|
<InviteFriendsModal gameCode={initialLobby.code!} label="Freunde einladen" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{initialLobby.tournamentCode && (
|
||||||
|
<div className="mt-1 flex justify-end">
|
||||||
|
<a href={`/tournament/${initialLobby.tournamentCode}`} className="btn btn-ghost btn-xs">
|
||||||
|
← Zur Turnierübersicht
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="h-32 w-full overflow-y-scroll" ref={moveListRef}>
|
<div className="h-32 w-full overflow-y-scroll" ref={moveListRef}>
|
||||||
<table className="table-compact table w-full">
|
<table className="table-compact table w-full">
|
||||||
<tbody>{getMoveListHtml()}</tbody>
|
<tbody>{getMoveListHtml()}</tbody>
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ export const startTournament = async (code: string): Promise<Tournament | null>
|
|||||||
return res.json();
|
return res.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cancelTournament = async (code: string): Promise<Tournament | null> => {
|
||||||
|
const res = await fetch(`${API_URL}/v1/tournaments/${code}/cancel`, { method: "POST", credentials: "include" });
|
||||||
|
if (!res.ok) return null;
|
||||||
|
return res.json();
|
||||||
|
};
|
||||||
|
|
||||||
export const addBotToTournament = async (code: string, botName: string): Promise<Tournament | null> => {
|
export const addBotToTournament = async (code: string, botName: string): Promise<Tournament | null> => {
|
||||||
const res = await fetch(`${API_URL}/v1/tournaments/${code}/add-bot`, {
|
const res = await fetch(`${API_URL}/v1/tournaments/${code}/add-bot`, {
|
||||||
method: "POST", credentials: "include",
|
method: "POST", credentials: "include",
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ export interface StockfishLevel {
|
|||||||
skillLevel: number;
|
skillLevel: number;
|
||||||
depth: number;
|
depth: number;
|
||||||
moveTime: number;
|
moveTime: number;
|
||||||
|
randomChance: number; // 0–1 probability of playing a random legal move instead of Stockfish's best
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AI_LEVELS: StockfishLevel[] = [
|
export const AI_LEVELS: StockfishLevel[] = [
|
||||||
{ level: 1, name: "Anfänger", skillLevel: 0, depth: 1, moveTime: 100 },
|
{ level: 1, name: "Anfänger", skillLevel: 0, depth: 1, moveTime: 50, randomChance: 0.80 },
|
||||||
{ level: 2, name: "Leicht", skillLevel: 3, depth: 3, moveTime: 200 },
|
{ level: 2, name: "Leicht", skillLevel: 2, depth: 2, moveTime: 100, randomChance: 0.50 },
|
||||||
{ level: 3, name: "Mittel", skillLevel: 8, depth: 5, moveTime: 500 },
|
{ level: 3, name: "Mittel", skillLevel: 7, depth: 4, moveTime: 300, randomChance: 0.15 },
|
||||||
{ level: 4, name: "Fortgeschritten", skillLevel: 14, depth: 10, moveTime: 1000 },
|
{ level: 4, name: "Fortgeschritten", skillLevel: 13, depth: 8, moveTime: 800, randomChance: 0.03 },
|
||||||
{ level: 5, name: "Experte", skillLevel: 18, depth: 15, moveTime: 2000 },
|
{ level: 5, name: "Experte", skillLevel: 18, depth: 14, moveTime: 1500, randomChance: 0 },
|
||||||
{ level: 6, name: "Meister", skillLevel: 20, depth: 20, moveTime: 3000 },
|
{ level: 6, name: "Meister", skillLevel: 20, depth: 20, moveTime: 3000, randomChance: 0 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export class StockfishEngine {
|
export class StockfishEngine {
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export const startTournament = async (req: Request, res: Response) => {
|
|||||||
startedAt: Date.now(),
|
startedAt: Date.now(),
|
||||||
timeControl: tc || undefined,
|
timeControl: tc || undefined,
|
||||||
whiteTimeMs: tc ? tc * 60 * 1000 : undefined,
|
whiteTimeMs: tc ? tc * 60 * 1000 : undefined,
|
||||||
blackTimeMs: tc ? tc * 60 * 1000 : undefined
|
blackTimeMs: tc ? tc * 60 * 1000 : undefined,
|
||||||
|
tournamentCode: req.params.code
|
||||||
};
|
};
|
||||||
activeGames.push(game);
|
activeGames.push(game);
|
||||||
await TournamentModel.setRoundGameCode(row.id, code);
|
await TournamentModel.setRoundGameCode(row.id, code);
|
||||||
@@ -82,6 +83,19 @@ export const startTournament = async (req: Request, res: Response) => {
|
|||||||
} catch (e) { console.error(e); res.status(500).end(); }
|
} catch (e) { console.error(e); res.status(500).end(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cancelTournament = async (req: Request, res: Response) => {
|
||||||
|
if (!req.session.user?.id || typeof req.session.user.id === "string") { res.status(401).end(); return; }
|
||||||
|
try {
|
||||||
|
const tournament = await TournamentModel.findByCode(req.params.code);
|
||||||
|
if (!tournament) { res.status(404).end(); return; }
|
||||||
|
if (tournament.hostId !== req.session.user.id) { res.status(403).end(); return; }
|
||||||
|
if (tournament.status === "finished") { res.status(400).json({ message: "Turnier bereits beendet." }); return; }
|
||||||
|
await db.query(`UPDATE "tournament" SET status='finished' WHERE code=$1`, [req.params.code]);
|
||||||
|
const updated = await TournamentModel.findByCode(req.params.code);
|
||||||
|
res.status(200).json(updated);
|
||||||
|
} catch (e) { console.error(e); res.status(500).end(); }
|
||||||
|
};
|
||||||
|
|
||||||
export const addBot = async (req: Request, res: Response) => {
|
export const addBot = async (req: Request, res: Response) => {
|
||||||
if (!req.session.user?.id || typeof req.session.user.id === "string") { res.status(401).end(); return; }
|
if (!req.session.user?.id || typeof req.session.user.id === "string") { res.status(401).end(); return; }
|
||||||
const { botName } = req.body;
|
const { botName } = req.body;
|
||||||
|
|||||||
@@ -138,12 +138,14 @@ export const setRoundGameCode = async (roundId: number, gameCode: string): Promi
|
|||||||
await db.query(`UPDATE "tournament_round" SET game_code=$1 WHERE id=$2`, [gameCode, roundId]);
|
await db.query(`UPDATE "tournament_round" SET game_code=$1 WHERE id=$2`, [gameCode, roundId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateRoundResult = async (gameCode: string, result: "white" | "black" | "draw"): Promise<void> => {
|
export type NextRoundData = { rows: any[]; timeControl: number | null } | null;
|
||||||
|
|
||||||
|
export const updateRoundResult = async (gameCode: string, result: "white" | "black" | "draw"): Promise<NextRoundData> => {
|
||||||
const roundRes = await db.query(
|
const roundRes = await db.query(
|
||||||
`UPDATE "tournament_round" SET result=$1 WHERE game_code=$2 RETURNING *`,
|
`UPDATE "tournament_round" SET result=$1 WHERE game_code=$2 RETURNING *`,
|
||||||
[result, gameCode]
|
[result, gameCode]
|
||||||
);
|
);
|
||||||
if (!roundRes.rows[0]) return;
|
if (!roundRes.rows[0]) return null;
|
||||||
const round = roundRes.rows[0];
|
const round = roundRes.rows[0];
|
||||||
|
|
||||||
// Update scores
|
// Update scores
|
||||||
@@ -164,15 +166,17 @@ export const updateRoundResult = async (gameCode: string, result: "white" | "bla
|
|||||||
);
|
);
|
||||||
if (parseInt(pendingRes.rows[0].count) === 0) {
|
if (parseInt(pendingRes.rows[0].count) === 0) {
|
||||||
const nextRes = await db.query(
|
const nextRes = await db.query(
|
||||||
`SELECT COUNT(*) FROM "tournament_round" WHERE tournament_id=$1 AND round=$2`,
|
`SELECT tr.*, t.time_control, t.code AS tournament_code FROM "tournament_round" tr JOIN "tournament" t ON t.id=tr.tournament_id WHERE tr.tournament_id=$1 AND tr.round=$2`,
|
||||||
[round.tournament_id, round.round + 1]
|
[round.tournament_id, round.round + 1]
|
||||||
);
|
);
|
||||||
if (parseInt(nextRes.rows[0].count) > 0) {
|
if (nextRes.rows.length > 0) {
|
||||||
await db.query(`UPDATE "tournament" SET current_round=current_round+1 WHERE id=$1`, [round.tournament_id]);
|
await db.query(`UPDATE "tournament" SET current_round=current_round+1 WHERE id=$1`, [round.tournament_id]);
|
||||||
|
return { rows: nextRes.rows, timeControl: nextRes.rows[0].time_control ?? null };
|
||||||
} else {
|
} else {
|
||||||
await db.query(`UPDATE "tournament" SET status='finished' WHERE id=$1`, [round.tournament_id]);
|
await db.query(`UPDATE "tournament" SET status='finished' WHERE id=$1`, [round.tournament_id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TournamentModel = { createTournament, joinTournament, startTournament, findByCode, findAll, setRoundGameCode, updateRoundResult };
|
const TournamentModel = { createTournament, joinTournament, startTournament, findByCode, findAll, setRoundGameCode, updateRoundResult };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
|
import { Chess } from "chess.js";
|
||||||
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
||||||
import { requireAuth } from "../middleware/auth.js";
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
import GameModel from "../db/models/game.model.js";
|
import GameModel from "../db/models/game.model.js";
|
||||||
@@ -17,10 +18,20 @@ router.post("/move", async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const aiLevel = AI_LEVELS.find((l) => l.level === (level || 3)) || AI_LEVELS[2];
|
const aiLevel = AI_LEVELS.find((l) => l.level === (level || 3)) || AI_LEVELS[2];
|
||||||
const engine = getEngine();
|
|
||||||
const move = await engine.getBestMove(fen, aiLevel);
|
|
||||||
|
|
||||||
res.status(200).json({ move: move || null });
|
let move: string | null = null;
|
||||||
|
if (aiLevel.randomChance > 0 && Math.random() < aiLevel.randomChance) {
|
||||||
|
const chess = new Chess(fen);
|
||||||
|
const moves = chess.moves({ verbose: true });
|
||||||
|
if (moves.length) {
|
||||||
|
const rand = moves[Math.floor(Math.random() * moves.length)];
|
||||||
|
move = rand.from + rand.to + (rand.promotion ?? "");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
move = await getEngine().getBestMove(fen, aiLevel) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ move });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).end();
|
res.status(500).end();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { createTournament, getTournaments, getTournament, joinTournament, startTournament, addBot } from "../controllers/tournament.controller.js";
|
import { createTournament, getTournaments, getTournament, joinTournament, startTournament, cancelTournament, addBot } from "../controllers/tournament.controller.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.post("/", createTournament);
|
router.post("/", createTournament);
|
||||||
@@ -7,5 +7,6 @@ router.get("/", getTournaments);
|
|||||||
router.get("/:code", getTournament);
|
router.get("/:code", getTournament);
|
||||||
router.post("/:code/join", joinTournament);
|
router.post("/:code/join", joinTournament);
|
||||||
router.post("/:code/start", startTournament);
|
router.post("/:code/start", startTournament);
|
||||||
|
router.post("/:code/cancel", cancelTournament);
|
||||||
router.post("/:code/add-bot", addBot);
|
router.post("/:code/add-bot", addBot);
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,13 +1,43 @@
|
|||||||
import type { Game } from "@michess/types";
|
import type { Game } from "@michess/types";
|
||||||
import { Chess } from "chess.js";
|
import { Chess } from "chess.js";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import type { DisconnectReason, Socket } from "socket.io";
|
import type { DisconnectReason, Socket } from "socket.io";
|
||||||
|
|
||||||
import GameModel, { activeGames } from "../db/models/game.model.js";
|
import GameModel, { activeGames } from "../db/models/game.model.js";
|
||||||
import TournamentModel from "../db/models/tournament.model.js";
|
import TournamentModel, { type NextRoundData } from "../db/models/tournament.model.js";
|
||||||
import { io } from "../server.js";
|
import { io } from "../server.js";
|
||||||
import { BOTS, BOT_NAMES } from "../bots.js";
|
import { BOTS, BOT_NAMES } from "../bots.js";
|
||||||
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
import { AI_LEVELS, getEngine } from "../controllers/stockfish.controller.js";
|
||||||
|
|
||||||
|
async function startNextRoundGames(nextRound: NonNullable<NextRoundData>) {
|
||||||
|
for (const row of nextRound.rows) {
|
||||||
|
const code = nanoid(6);
|
||||||
|
const tc = nextRound.timeControl;
|
||||||
|
const whiteUser = { id: row.white_id, name: row.white_name, connected: false };
|
||||||
|
const blackUser = { id: row.black_id, name: row.black_name, connected: false };
|
||||||
|
const game: Game = {
|
||||||
|
code, unlisted: true, host: whiteUser,
|
||||||
|
white: whiteUser, black: blackUser, pgn: "",
|
||||||
|
startedAt: Date.now(),
|
||||||
|
timeControl: tc || undefined,
|
||||||
|
whiteTimeMs: tc ? tc * 60 * 1000 : undefined,
|
||||||
|
blackTimeMs: tc ? tc * 60 * 1000 : undefined,
|
||||||
|
tournamentCode: row.tournament_code,
|
||||||
|
};
|
||||||
|
activeGames.push(game);
|
||||||
|
await TournamentModel.setRoundGameCode(row.id, code);
|
||||||
|
|
||||||
|
// Auto-start bot vs bot games immediately
|
||||||
|
if (BOT_NAMES.has(whiteUser.name) && BOT_NAMES.has(blackUser.name)) {
|
||||||
|
game.white!.connected = true;
|
||||||
|
game.black!.connected = true;
|
||||||
|
if (tc) game.lastMoveAt = Date.now();
|
||||||
|
const chess = new Chess();
|
||||||
|
setTimeout(() => triggerBotMove(game, chess), 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: clean up
|
// TODO: clean up
|
||||||
|
|
||||||
async function triggerBotMove(game: Game, chess: Chess) {
|
async function triggerBotMove(game: Game, chess: Chess) {
|
||||||
@@ -17,8 +47,17 @@ async function triggerBotMove(game: Game, chess: Chess) {
|
|||||||
if (!bot || !game.code) return;
|
if (!bot || !game.code) return;
|
||||||
|
|
||||||
const sfLevel = AI_LEVELS.find(l => l.level === bot.level) ?? AI_LEVELS[2];
|
const sfLevel = AI_LEVELS.find(l => l.level === bot.level) ?? AI_LEVELS[2];
|
||||||
const engine = getEngine();
|
|
||||||
const bestMove = await engine.getBestMove(chess.fen(), sfLevel);
|
// Possibly play a random legal move instead of Stockfish's best (weakens lower levels)
|
||||||
|
let bestMove: string;
|
||||||
|
if (sfLevel.randomChance > 0 && Math.random() < sfLevel.randomChance) {
|
||||||
|
const moves = chess.moves({ verbose: true });
|
||||||
|
if (!moves.length) return;
|
||||||
|
const rand = moves[Math.floor(Math.random() * moves.length)];
|
||||||
|
bestMove = rand.from + rand.to + (rand.promotion ?? "");
|
||||||
|
} else {
|
||||||
|
bestMove = await getEngine().getBestMove(chess.fen(), sfLevel);
|
||||||
|
}
|
||||||
if (!bestMove) return;
|
if (!bestMove) return;
|
||||||
|
|
||||||
// Check game still active
|
// Check game still active
|
||||||
@@ -50,7 +89,15 @@ async function triggerBotMove(game: Game, chess: Chess) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chess.isGameOver()) {
|
if (!chess.isGameOver()) {
|
||||||
|
// Bot vs Bot: recursively trigger next bot move
|
||||||
|
const nextPlayer = chess.turn() === "w" ? game.white : game.black;
|
||||||
|
if (BOT_NAMES.has(nextPlayer?.name ?? "")) {
|
||||||
|
setTimeout(() => triggerBotMove(game, chess), 300);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const prevTurn = turn; // the bot was this turn
|
const prevTurn = turn; // the bot was this turn
|
||||||
let reason: Game["endReason"];
|
let reason: Game["endReason"];
|
||||||
if (chess.isCheckmate()) reason = "checkmate";
|
if (chess.isCheckmate()) reason = "checkmate";
|
||||||
@@ -66,11 +113,13 @@ async function triggerBotMove(game: Game, chess: Chess) {
|
|||||||
|
|
||||||
const saved = (await GameModel.save(game)) as Game;
|
const saved = (await GameModel.save(game)) as Game;
|
||||||
game.id = saved?.id;
|
game.id = saved?.id;
|
||||||
await TournamentModel.updateRoundResult(game.code, game.winner as "white" | "black" | "draw");
|
const nextRound = await TournamentModel.updateRoundResult(game.code, game.winner as "white" | "black" | "draw");
|
||||||
io.to(game.code).emit("gameOver", { reason, winnerName, winnerSide, id: game.id });
|
io.to(game.code).emit("gameOver", { reason, winnerName, winnerSide, id: game.id });
|
||||||
if (game.timeout) clearTimeout(game.timeout);
|
if (game.timeout) clearTimeout(game.timeout);
|
||||||
activeGames.splice(activeGames.indexOf(game), 1);
|
activeGames.splice(activeGames.indexOf(game), 1);
|
||||||
}
|
|
||||||
|
// Create active games for the next tournament round if one was returned
|
||||||
|
if (nextRound) await startNextRoundGames(nextRound);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function joinLobby(this: Socket, gameCode: string) {
|
export async function joinLobby(this: Socket, gameCode: string) {
|
||||||
@@ -232,7 +281,7 @@ export async function claimAbandoned(this: Socket, type: "win" | "draw") {
|
|||||||
const { id } = (await GameModel.save(game)) as Game;
|
const { id } = (await GameModel.save(game)) as Game;
|
||||||
game.id = id;
|
game.id = id;
|
||||||
|
|
||||||
await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
const nextRound1 = await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
||||||
|
|
||||||
const gameOver = {
|
const gameOver = {
|
||||||
reason: game.endReason,
|
reason: game.endReason,
|
||||||
@@ -245,6 +294,7 @@ export async function claimAbandoned(this: Socket, type: "win" | "draw") {
|
|||||||
|
|
||||||
if (game.timeout) clearTimeout(game.timeout);
|
if (game.timeout) clearTimeout(game.timeout);
|
||||||
activeGames.splice(activeGames.indexOf(game), 1);
|
activeGames.splice(activeGames.indexOf(game), 1);
|
||||||
|
if (nextRound1) await startNextRoundGames(nextRound1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
@@ -291,10 +341,11 @@ export async function sendMove(this: Socket, m: { from: string; to: string; prom
|
|||||||
const saved = (await GameModel.save(game)) as Game;
|
const saved = (await GameModel.save(game)) as Game;
|
||||||
const id = saved?.id;
|
const id = saved?.id;
|
||||||
game.id = id;
|
game.id = id;
|
||||||
await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
const nextRoundT = await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
||||||
io.to(game.code as string).emit("gameOver", { reason: "timeout", winnerSide: game.winner, id });
|
io.to(game.code as string).emit("gameOver", { reason: "timeout", winnerSide: game.winner, id });
|
||||||
if (game.timeout) clearTimeout(game.timeout);
|
if (game.timeout) clearTimeout(game.timeout);
|
||||||
activeGames.splice(activeGames.indexOf(game), 1);
|
activeGames.splice(activeGames.indexOf(game), 1);
|
||||||
|
if (nextRoundT) await startNextRoundGames(nextRoundT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,11 +396,12 @@ export async function sendMove(this: Socket, m: { from: string; to: string; prom
|
|||||||
|
|
||||||
const { id } = (await GameModel.save(game)) as Game; // save game to db
|
const { id } = (await GameModel.save(game)) as Game; // save game to db
|
||||||
game.id = id;
|
game.id = id;
|
||||||
await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
const nextRoundM = await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
||||||
io.to(game.code as string).emit("gameOver", { reason, winnerName, winnerSide, id });
|
io.to(game.code as string).emit("gameOver", { reason, winnerName, winnerSide, id });
|
||||||
|
|
||||||
if (game.timeout) clearTimeout(game.timeout);
|
if (game.timeout) clearTimeout(game.timeout);
|
||||||
activeGames.splice(activeGames.indexOf(game), 1);
|
activeGames.splice(activeGames.indexOf(game), 1);
|
||||||
|
if (nextRoundM) await startNextRoundGames(nextRoundM);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("invalid move");
|
throw new Error("invalid move");
|
||||||
@@ -440,8 +492,9 @@ export async function claimTimeout(this: Socket) {
|
|||||||
const saved = (await GameModel.save(game)) as Game;
|
const saved = (await GameModel.save(game)) as Game;
|
||||||
const id = saved?.id;
|
const id = saved?.id;
|
||||||
game.id = id;
|
game.id = id;
|
||||||
await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
const nextRoundCT = await TournamentModel.updateRoundResult(game.code!, game.winner as "white" | "black" | "draw");
|
||||||
io.to(game.code as string).emit("gameOver", { reason: "timeout", winnerSide: game.winner, id });
|
io.to(game.code as string).emit("gameOver", { reason: "timeout", winnerSide: game.winner, id });
|
||||||
if (game.timeout) clearTimeout(game.timeout);
|
if (game.timeout) clearTimeout(game.timeout);
|
||||||
activeGames.splice(activeGames.indexOf(game), 1);
|
activeGames.splice(activeGames.indexOf(game), 1);
|
||||||
|
if (nextRoundCT) await startNextRoundGames(nextRoundCT);
|
||||||
}
|
}
|
||||||
|
|||||||
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
@@ -18,6 +18,7 @@ export interface Game {
|
|||||||
whiteTimeMs?: number;
|
whiteTimeMs?: number;
|
||||||
blackTimeMs?: number;
|
blackTimeMs?: number;
|
||||||
lastMoveAt?: number;
|
lastMoveAt?: number;
|
||||||
|
tournamentCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
|||||||
Reference in New Issue
Block a user