fix(ai): fix database model mapping for AI matches and missing AI names
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { Chessboard } from "react-chessboard";
|
||||
import { Chess } from "chess.js";
|
||||
import { API_URL } from "@/config";
|
||||
import { useSession } from "@/context/session";
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
const AI_LEVELS = [
|
||||
@@ -16,6 +17,7 @@ const AI_LEVELS = [
|
||||
];
|
||||
|
||||
export default function AiGamePage() {
|
||||
const { user } = useSession();
|
||||
const [selectedLevel, setSelectedLevel] = useState(3);
|
||||
const [gameStarted, setGameStarted] = useState(false);
|
||||
const [game, setGame] = useState(new Chess());
|
||||
@@ -23,6 +25,9 @@ export default function AiGamePage() {
|
||||
const [status, setStatus] = useState("");
|
||||
const [thinking, setThinking] = useState(false);
|
||||
const [lastMove, setLastMove] = useState<{ from: string; to: string } | null>(null);
|
||||
const [savedGameId, setSavedGameId] = useState<number | null>(null);
|
||||
const startedAtRef = useRef<number>(Date.now());
|
||||
const gameSavedRef = useRef(false);
|
||||
|
||||
const getStatus = useCallback((g: Chess) => {
|
||||
if (g.isCheckmate()) return g.turn() === "w" ? "Schwarz gewinnt! Schachmatt." : "Weiß gewinnt! Schachmatt.";
|
||||
@@ -31,7 +36,31 @@ export default function AiGamePage() {
|
||||
return g.turn() === "w" ? "Weiß am Zug" : "Schwarz am Zug";
|
||||
}, []);
|
||||
|
||||
const requestAiMove = useCallback(async (fen: string, level: number) => {
|
||||
const saveGame = useCallback(async (g: Chess, color: "white" | "black", level: number) => {
|
||||
if (!user?.id || typeof user.id !== "number") return;
|
||||
if (gameSavedRef.current) return;
|
||||
gameSavedRef.current = true;
|
||||
|
||||
const winner = g.isCheckmate() ? (g.turn() === "w" ? "black" : "white") : "draw";
|
||||
const endReason = g.isCheckmate() ? "checkmate" : g.isStalemate() ? "stalemate" : g.isThreefoldRepetition() ? "repetition" : g.isInsufficientMaterial() ? "insufficient" : "draw";
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/v1/ai/save`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ pgn: g.pgn(), winner, endReason, playerColor: color, level, startedAt: startedAtRef.current })
|
||||
});
|
||||
if (res.ok) {
|
||||
const { id } = await res.json();
|
||||
setSavedGameId(id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Save game error:", e);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const requestAiMove = useCallback(async (fen: string, level: number, color: "white" | "black") => {
|
||||
setThinking(true);
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/v1/ai/move`, {
|
||||
@@ -48,6 +77,7 @@ export default function AiGamePage() {
|
||||
const result = newGame.move({ from: move.slice(0, 2), to: move.slice(2, 4), promotion: move[4] || "q" });
|
||||
if (result) setLastMove({ from: result.from, to: result.to });
|
||||
setStatus(getStatus(newGame));
|
||||
if (newGame.isGameOver()) saveGame(newGame, color, level);
|
||||
return newGame;
|
||||
});
|
||||
}
|
||||
@@ -56,7 +86,7 @@ export default function AiGamePage() {
|
||||
console.error("AI move error:", e);
|
||||
}
|
||||
setThinking(false);
|
||||
}, [getStatus]);
|
||||
}, [getStatus, saveGame]);
|
||||
|
||||
const onDrop = useCallback((sourceSquare: string, targetSquare: string) => {
|
||||
if (thinking) return false;
|
||||
@@ -76,11 +106,13 @@ export default function AiGamePage() {
|
||||
setGame(newGame);
|
||||
setStatus(getStatus(newGame));
|
||||
|
||||
if (!newGame.isGameOver()) {
|
||||
requestAiMove(newGame.fen(), selectedLevel);
|
||||
if (newGame.isGameOver()) {
|
||||
saveGame(newGame, playerColor, selectedLevel);
|
||||
} else {
|
||||
requestAiMove(newGame.fen(), selectedLevel, playerColor);
|
||||
}
|
||||
return true;
|
||||
}, [game, playerColor, thinking, selectedLevel, getStatus, requestAiMove]);
|
||||
}, [game, playerColor, thinking, selectedLevel, getStatus, requestAiMove, saveGame]);
|
||||
|
||||
const startGame = (color: "white" | "black") => {
|
||||
const newGame = new Chess();
|
||||
@@ -88,10 +120,13 @@ export default function AiGamePage() {
|
||||
setPlayerColor(color);
|
||||
setGameStarted(true);
|
||||
setLastMove(null);
|
||||
setSavedGameId(null);
|
||||
gameSavedRef.current = false;
|
||||
startedAtRef.current = Date.now();
|
||||
setStatus(getStatus(newGame));
|
||||
|
||||
if (color === "black") {
|
||||
requestAiMove(newGame.fen(), selectedLevel);
|
||||
requestAiMove(newGame.fen(), selectedLevel, color);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,6 +134,8 @@ export default function AiGamePage() {
|
||||
setGameStarted(false);
|
||||
setGame(new Chess());
|
||||
setLastMove(null);
|
||||
setSavedGameId(null);
|
||||
gameSavedRef.current = false;
|
||||
setStatus("");
|
||||
};
|
||||
|
||||
@@ -166,6 +203,11 @@ export default function AiGamePage() {
|
||||
<div className={`card shadow p-4 ${game.isGameOver() ? "bg-warning text-warning-content" : "bg-base-200"}`}>
|
||||
<p className="font-semibold">{status}</p>
|
||||
{thinking && <p className="text-sm opacity-70 mt-1">KI denkt nach...</p>}
|
||||
{savedGameId && (
|
||||
<a className="btn btn-sm btn-ghost mt-2" href={`/archive/${savedGameId}`} target="_blank" rel="noopener noreferrer">
|
||||
Spiel überprüfen
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button className="btn btn-outline" onClick={resetGame}>Neues Spiel</button>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default async function Profile({ params }: { params: { name: string } })
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="mb-1 text-lg font-bold">Recent games</h2>
|
||||
<h2 className="mb-1 text-lg font-bold">Letzte Spiele</h2>
|
||||
<ul className="bg-base-300 flex h-[60vh] flex-col gap-1 overflow-y-scroll rounded-lg">
|
||||
{data.recentGames.map((game) => {
|
||||
let endReason = game.endReason as string;
|
||||
@@ -77,6 +77,9 @@ export default async function Profile({ params }: { params: { name: string } })
|
||||
key={game.id}
|
||||
className="border-base-100 flex flex-wrap items-center justify-between gap-8 border-b-2 p-3"
|
||||
>
|
||||
{game.vsAi && (
|
||||
<span className="badge badge-info badge-sm">vs KI Lv.{game.aiLevel}</span>
|
||||
)}
|
||||
<div className="flex w-72 justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span className="flex items-center gap-1 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user