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:
Michess
2026-04-15 07:46:40 +02:00
parent 536160bba6
commit 86fbdd39ed
10 changed files with 159 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
import InviteFriendsModal from "@/components/InviteFriendsModal";
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 type { Tournament, TournamentRound } from "@michess/types";
import Link from "next/link";
@@ -56,6 +56,14 @@ export default function TournamentDetailPage() {
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() {
if (!user?.id) return;
setActionLoading(true);
@@ -162,6 +170,15 @@ export default function TournamentDetailPage() {
Turnier starten
</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>

View File

@@ -656,10 +656,17 @@ export default function GamePage({ initialLobby }: { initialLobby: Game }) {
</div>
</div>
{!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" />
</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}>
<table className="table-compact table w-full">
<tbody>{getMoveListHtml()}</tbody>

View File

@@ -35,6 +35,12 @@ export const startTournament = async (code: string): Promise<Tournament | null>
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> => {
const res = await fetch(`${API_URL}/v1/tournaments/${code}/add-bot`, {
method: "POST", credentials: "include",