127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useNavigate } from 'react-router';
|
|
import { useGameStore } from '@/stores/game-store';
|
|
import { useVersusStore } from '@/stores/versus-store';
|
|
import { useChainValidation } from '@/hooks/use-chain-validation';
|
|
import PageLayout from '@/components/layout/PageLayout';
|
|
import GameHeader from '@/components/game/GameHeader';
|
|
import ChainDisplay from '@/components/game/ChainDisplay';
|
|
import SearchAutocomplete from '@/components/game/SearchAutocomplete';
|
|
import ValidationFeedback from '@/components/game/ValidationFeedback';
|
|
import HintButton from '@/components/game/HintButton';
|
|
import OpponentProgress from '@/components/versus/OpponentProgress';
|
|
import VersusCompletionModal from '@/components/versus/VersusCompletionModal';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { Undo2 } from 'lucide-react';
|
|
|
|
export default function VersusGame() {
|
|
const status = useGameStore((s) => s.status);
|
|
const chain = useGameStore((s) => s.chain);
|
|
const currentSearchMode = useGameStore((s) => s.currentSearchMode);
|
|
const passedMovieB = useGameStore((s) => s.passedMovieB);
|
|
const movieA = useGameStore((s) => s.movieA);
|
|
const movieB = useGameStore((s) => s.movieB);
|
|
const isValidating = useGameStore((s) => s.isValidating);
|
|
const score = useGameStore((s) => s.score);
|
|
const undoLastLink = useGameStore((s) => s.undoLastLink);
|
|
const navigate = useNavigate();
|
|
const { validateAndAddActor, validateAndAddMovie } = useChainValidation();
|
|
const sendChainUpdate = useVersusStore((s) => s.sendChainUpdate);
|
|
const sendMatchComplete = useVersusStore((s) => s.sendMatchComplete);
|
|
|
|
// Broadcast chain updates to opponent
|
|
useEffect(() => {
|
|
if (chain.length > 0) {
|
|
sendChainUpdate(chain.length);
|
|
}
|
|
}, [chain.length, sendChainUpdate]);
|
|
|
|
// Send match completion when game completes
|
|
useEffect(() => {
|
|
if (status === 'completed' && score) {
|
|
sendMatchComplete(score);
|
|
}
|
|
}, [status, score, sendMatchComplete]);
|
|
|
|
useEffect(() => {
|
|
if (status === 'idle') {
|
|
navigate('/versus');
|
|
}
|
|
}, [status, navigate]);
|
|
|
|
if (status === 'idle' || !movieA || !movieB) {
|
|
return null;
|
|
}
|
|
|
|
const lastLink = chain[chain.length - 1];
|
|
let searchLabel = '';
|
|
let searchPlaceholder = '';
|
|
|
|
if (currentSearchMode === 'actor' && lastLink?.type === 'movie') {
|
|
searchLabel = `Who was in "${lastLink.title}"?`;
|
|
searchPlaceholder = 'Search for an actor...';
|
|
} else if (currentSearchMode === 'movie' && lastLink?.type === 'actor') {
|
|
searchLabel = `What movie was ${lastLink.name} also in?`;
|
|
searchPlaceholder = 'Search for a movie...';
|
|
}
|
|
|
|
const showLoopHint = passedMovieB && currentSearchMode === 'movie' && movieA;
|
|
|
|
return (
|
|
<PageLayout>
|
|
<div className="space-y-4">
|
|
<GameHeader />
|
|
|
|
<OpponentProgress />
|
|
|
|
<Separator />
|
|
|
|
<ChainDisplay />
|
|
|
|
{status === 'playing' && (
|
|
<div className="space-y-3">
|
|
<Separator />
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-sm font-medium">{searchLabel}</label>
|
|
<div className="flex items-center gap-2">
|
|
<HintButton />
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={undoLastLink}
|
|
disabled={chain.length <= 1 || isValidating}
|
|
>
|
|
<Undo2 className="mr-1 h-3.5 w-3.5" />
|
|
Undo
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<SearchAutocomplete
|
|
mode={currentSearchMode}
|
|
onSelectActor={validateAndAddActor}
|
|
onSelectMovie={validateAndAddMovie}
|
|
disabled={isValidating}
|
|
placeholder={searchPlaceholder}
|
|
/>
|
|
|
|
<ValidationFeedback />
|
|
|
|
{showLoopHint && (
|
|
<p className="text-sm text-green-600">
|
|
Select "{movieA.title}" to close the loop!
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<VersusCompletionModal />
|
|
</PageLayout>
|
|
);
|
|
}
|