153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
import { usePrisma } from "../../../../utils/prisma";
|
||
|
||
interface MemoryCard {
|
||
id: number;
|
||
emoji: string;
|
||
matchedBy: "X" | "O" | null;
|
||
}
|
||
|
||
interface MemoryState {
|
||
cards: MemoryCard[];
|
||
flipped: number[];
|
||
scores: { X: number; O: number };
|
||
mismatchRevealed: boolean;
|
||
}
|
||
|
||
export default defineEventHandler(async (event) => {
|
||
const user = await requireUser(event);
|
||
const id = getRouterParam(event, "id");
|
||
if (!id) throw createError({ statusCode: 400, message: "id fehlt" });
|
||
|
||
const body = await readBody(event) as { cardIndex: number };
|
||
const { cardIndex } = body;
|
||
|
||
if (typeof cardIndex !== "number" || cardIndex < 0 || cardIndex > 15) {
|
||
throw createError({ statusCode: 400, message: "Ungültiger Zug" });
|
||
}
|
||
|
||
const db = usePrisma();
|
||
const challenge = await db.gameChallenge.findUnique({ where: { id } });
|
||
|
||
if (!challenge) throw createError({ statusCode: 404, message: "Challenge nicht gefunden" });
|
||
if (challenge.status !== "ACTIVE") throw createError({ statusCode: 409, message: "Spiel nicht aktiv" });
|
||
if (challenge.gameType !== "memory") throw createError({ statusCode: 400, message: "Kein Memory-Spiel" });
|
||
|
||
const isChallenger = user.id === challenge.challengerId;
|
||
const isOpponent = user.id === challenge.opponentId;
|
||
if (!isChallenger && !isOpponent) throw createError({ statusCode: 403, message: "Nicht autorisiert" });
|
||
|
||
const myMark: "X" | "O" = isChallenger ? "X" : "O";
|
||
if (challenge.currentTurn !== myMark) throw createError({ statusCode: 409, message: "Nicht dein Zug" });
|
||
|
||
const state = JSON.parse(JSON.stringify(challenge.memoryState)) as MemoryState;
|
||
const card = state.cards[cardIndex];
|
||
if (!card) throw createError({ statusCode: 400, message: "Ungültige Karte" });
|
||
if (card.matchedBy !== null) throw createError({ statusCode: 409, message: "Karte bereits gefunden" });
|
||
|
||
let newStatus = challenge.status as string;
|
||
let newWinner: string | null = challenge.winner;
|
||
let nextTurn: string = myMark;
|
||
|
||
// Mismatch pending: player must click one of their 2 revealed cards to hide them
|
||
if (state.mismatchRevealed) {
|
||
if (!state.flipped.includes(cardIndex)) {
|
||
throw createError({ statusCode: 409, message: "Decke zuerst deine aufgedeckten Karten zu" });
|
||
}
|
||
// Hide both cards and switch turn
|
||
state.flipped = [];
|
||
state.mismatchRevealed = false;
|
||
nextTurn = myMark === "X" ? "O" : "X";
|
||
} else {
|
||
if (state.flipped.includes(cardIndex)) {
|
||
throw createError({ statusCode: 409, message: "Karte bereits aufgedeckt" });
|
||
}
|
||
|
||
if (state.flipped.length === 0) {
|
||
state.flipped = [cardIndex];
|
||
} else {
|
||
// Second flip
|
||
const firstId = state.flipped[0]!;
|
||
const firstCard = state.cards[firstId]!;
|
||
state.flipped = [firstId, cardIndex];
|
||
|
||
if (firstCard.emoji === card.emoji) {
|
||
// Match – mark both, clear flipped, stay on same turn
|
||
state.cards[firstId]!.matchedBy = myMark;
|
||
state.cards[cardIndex]!.matchedBy = myMark;
|
||
state.scores[myMark]++;
|
||
state.flipped = [];
|
||
|
||
const totalPairs = state.cards.length / 2;
|
||
const matchedPairs = state.cards.filter(c => c.matchedBy !== null).length / 2;
|
||
|
||
if (matchedPairs === totalPairs) {
|
||
newStatus = "FINISHED";
|
||
const { X, O } = state.scores;
|
||
newWinner = X > O ? "X" : O > X ? "O" : "draw";
|
||
}
|
||
// nextTurn stays as myMark (scorer goes again)
|
||
} else {
|
||
// Mismatch – show cards, stay on same turn until player hides them
|
||
state.mismatchRevealed = true;
|
||
// nextTurn stays as myMark
|
||
}
|
||
}
|
||
}
|
||
|
||
const updated = await db.gameChallenge.update({
|
||
where: { id },
|
||
data: {
|
||
memoryState: state as any,
|
||
currentTurn: nextTurn,
|
||
...(newStatus !== challenge.status && { status: newStatus as any }),
|
||
...(newWinner !== challenge.winner && { winner: newWinner }),
|
||
},
|
||
});
|
||
|
||
if (newStatus === "FINISHED" && challenge.opponentId && challenge.opponentName) {
|
||
const challengerId = challenge.challengerId;
|
||
const opponentId = challenge.opponentId;
|
||
const challengerName = challenge.challengerName;
|
||
const opponentName = challenge.opponentName;
|
||
|
||
if (newWinner === "draw") {
|
||
await Promise.all([
|
||
db.gameScore.upsert({
|
||
where: { userId: challengerId },
|
||
update: { draws: { increment: 1 }, points: { increment: 1 }, playerName: challengerName },
|
||
create: { userId: challengerId, playerName: challengerName, draws: 1, points: 1 },
|
||
}),
|
||
db.gameScore.upsert({
|
||
where: { userId: opponentId },
|
||
update: { draws: { increment: 1 }, points: { increment: 1 }, playerName: opponentName },
|
||
create: { userId: opponentId, playerName: opponentName, draws: 1, points: 1 },
|
||
}),
|
||
]);
|
||
} else {
|
||
const winnerId = newWinner === "X" ? challengerId : opponentId;
|
||
const loserId = newWinner === "X" ? opponentId : challengerId;
|
||
const winnerName = newWinner === "X" ? challengerName : opponentName;
|
||
const loserName = newWinner === "X" ? opponentName : challengerName;
|
||
|
||
await Promise.all([
|
||
db.gameScore.upsert({
|
||
where: { userId: winnerId },
|
||
update: { wins: { increment: 1 }, points: { increment: 3 }, playerName: winnerName },
|
||
create: { userId: winnerId, playerName: winnerName, wins: 1, points: 3 },
|
||
}),
|
||
db.gameScore.upsert({
|
||
where: { userId: loserId },
|
||
update: { losses: { increment: 1 }, playerName: loserName },
|
||
create: { userId: loserId, playerName: loserName, losses: 1 },
|
||
}),
|
||
]);
|
||
}
|
||
|
||
if (challenge.postId) {
|
||
await db.communityPost.delete({ where: { id: challenge.postId } }).catch(() => {});
|
||
}
|
||
}
|
||
|
||
return updated;
|
||
});
|