From 157c14c11ee59693be91e43220c13cbc36d76a29 Mon Sep 17 00:00:00 2001 From: Michess Date: Tue, 14 Apr 2026 13:32:43 +0200 Subject: [PATCH] feat(bots): add named bot profiles with ELO-based difficulty - 5 bot profiles (Holzpferd Heinz to Meister Magnus, ELO 200-1400) in server/bots.ts and client/bots.ts with emoji + descriptions - Bot users auto-created in DB on server startup (role='bot') - AI page replaced difficulty buttons with bot profile cards - Tournament host can add bots via dropdown (POST /:code/add-bot) - Bot auto-moves triggered on joinLobby and after each human move - Fixed clock start order so bot-as-white games initialize correctly Co-Authored-By: Claude Sonnet 4.6 --- client/src/app/ai/page.tsx | 34 ++++---- client/src/app/tournament/[code]/page.tsx | 32 ++++++- client/src/bots.ts | 8 ++ client/src/lib/tournament.ts | 10 +++ server/src/bots.ts | 8 ++ .../src/controllers/tournament.controller.ts | 20 +++++ server/src/routes/tournament.route.ts | 3 +- server/src/server.ts | 9 ++ server/src/socket/game.socket.ts | 87 +++++++++++++++++++ 9 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 client/src/bots.ts create mode 100644 server/src/bots.ts diff --git a/client/src/app/ai/page.tsx b/client/src/app/ai/page.tsx index 9db2845..d9e1ec7 100644 --- a/client/src/app/ai/page.tsx +++ b/client/src/app/ai/page.tsx @@ -5,20 +5,13 @@ import { Chessboard } from "react-chessboard"; import { Chess } from "chess.js"; import { API_URL } from "@/config"; import { useSession } from "@/context/session"; +import { BOTS } from "@/bots"; import type { CSSProperties } from "react"; -const AI_LEVELS = [ - { level: 1, name: "Anfänger" }, - { level: 2, name: "Leicht" }, - { level: 3, name: "Mittel" }, - { level: 4, name: "Fortgeschritten" }, - { level: 5, name: "Experte" }, - { level: 6, name: "Meister" }, -]; - export default function AiGamePage() { const { user } = useSession(); - const [selectedLevel, setSelectedLevel] = useState(3); + const [selectedBot, setSelectedBot] = useState(BOTS[2]); + const selectedLevel = selectedBot.level; const [gameStarted, setGameStarted] = useState(false); const [game, setGame] = useState(new Chess()); const [playerColor, setPlayerColor] = useState<"white" | "black">("white"); @@ -152,15 +145,20 @@ export default function AiGamePage() {
-

Schwierigkeitsgrad

-
- {AI_LEVELS.map((l) => ( +

Gegner wählen

+
+ {BOTS.map((b) => ( ))}
@@ -196,7 +194,7 @@ export default function AiGamePage() {
-

Niveau: {AI_LEVELS.find(l => l.level === selectedLevel)?.name}

+

Gegner: {selectedBot.emoji} {selectedBot.name}

Du spielst: {playerColor === "white" ? "Weiß ♔" : "Schwarz ♚"}

diff --git a/client/src/app/tournament/[code]/page.tsx b/client/src/app/tournament/[code]/page.tsx index 842b1b7..9a99c99 100644 --- a/client/src/app/tournament/[code]/page.tsx +++ b/client/src/app/tournament/[code]/page.tsx @@ -2,7 +2,8 @@ import InviteFriendsModal from "@/components/InviteFriendsModal"; import { useSession } from "@/context/session"; -import { fetchTournament, joinTournament, startTournament } from "@/lib/tournament"; +import { fetchTournament, joinTournament, startTournament, addBotToTournament } from "@/lib/tournament"; +import { BOTS } from "@/bots"; import type { Tournament, TournamentRound } from "@michess/types"; import Link from "next/link"; import { useParams } from "next/navigation"; @@ -16,6 +17,7 @@ export default function TournamentDetailPage() { const [tournament, setTournament] = useState(null); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(false); + const [selectedBot, setSelectedBot] = useState(BOTS[0].name); const intervalRef = useRef | null>(null); async function load() { @@ -47,6 +49,13 @@ export default function TournamentDetailPage() { setActionLoading(false); } + async function handleAddBot() { + setActionLoading(true); + const updated = await addBotToTournament(code, selectedBot); + if (updated) setTournament(updated); + setActionLoading(false); + } + async function handleStart() { if (!user?.id) return; setActionLoading(true); @@ -123,6 +132,27 @@ export default function TournamentDetailPage() { {tournament.status === "waiting" && isJoined && ( )} + {isHost && tournament.status === "waiting" && ( +
+ + +
+ )} {canStart && (