Felix Zieger commited on
Commit
6864389
·
1 Parent(s): 2e860a4
src/components/GameContainer.tsx CHANGED
@@ -8,12 +8,10 @@ import { WelcomeScreen } from "./game/WelcomeScreen";
8
  import { ThemeSelector } from "./game/ThemeSelector";
9
  import { SentenceBuilder } from "./game/SentenceBuilder";
10
  import { GuessDisplay } from "./game/GuessDisplay";
11
- import { GameOver } from "./game/GameOver";
12
  import { useTranslation } from "@/hooks/useTranslation";
13
  import { LanguageContext } from "@/contexts/LanguageContext";
14
- import { supabase } from "@/integrations/supabase/client";
15
 
16
- type GameState = "welcome" | "theme-selection" | "building-sentence" | "showing-guess" | "game-over";
17
 
18
  export const GameContainer = () => {
19
  const [gameState, setGameState] = useState<GameState>("welcome");
@@ -32,7 +30,6 @@ export const GameContainer = () => {
32
  const { language } = useContext(LanguageContext);
33
 
34
  useEffect(() => {
35
- // Generate a new session ID when starting a new game
36
  if (gameState === "theme-selection") {
37
  setSessionId(crypto.randomUUID());
38
  }
@@ -43,12 +40,10 @@ export const GameContainer = () => {
43
  if (e.key === 'Enter') {
44
  if (gameState === 'welcome') {
45
  handleStart();
46
- } else if (gameState === 'game-over' || gameState === 'showing-guess') {
47
  const correct = isGuessCorrect();
48
  if (correct) {
49
  handleNextRound();
50
- } else {
51
- setGameState("game-over");
52
  }
53
  }
54
  }
@@ -176,8 +171,6 @@ export const GameContainer = () => {
176
  }
177
  };
178
  getNewWord();
179
- } else {
180
- setGameState("game-over");
181
  }
182
  };
183
 
@@ -232,7 +225,7 @@ export const GameContainer = () => {
232
  onMakeGuess={handleMakeGuess}
233
  onBack={handleBack}
234
  />
235
- ) : gameState === "showing-guess" ? (
236
  <GuessDisplay
237
  sentence={sentence}
238
  aiGuess={aiGuess}
@@ -243,13 +236,8 @@ export const GameContainer = () => {
243
  avgWordsPerRound={getAverageWordsPerRound()}
244
  sessionId={sessionId}
245
  />
246
- ) : gameState === "game-over" ? (
247
- <GameOver
248
- successfulRounds={successfulRounds}
249
- onPlayAgain={handlePlayAgain}
250
- />
251
- ) : null}
252
  </motion.div>
253
  </div>
254
  );
255
- };
 
8
  import { ThemeSelector } from "./game/ThemeSelector";
9
  import { SentenceBuilder } from "./game/SentenceBuilder";
10
  import { GuessDisplay } from "./game/GuessDisplay";
 
11
  import { useTranslation } from "@/hooks/useTranslation";
12
  import { LanguageContext } from "@/contexts/LanguageContext";
 
13
 
14
+ type GameState = "welcome" | "theme-selection" | "building-sentence" | "showing-guess";
15
 
16
  export const GameContainer = () => {
17
  const [gameState, setGameState] = useState<GameState>("welcome");
 
30
  const { language } = useContext(LanguageContext);
31
 
32
  useEffect(() => {
 
33
  if (gameState === "theme-selection") {
34
  setSessionId(crypto.randomUUID());
35
  }
 
40
  if (e.key === 'Enter') {
41
  if (gameState === 'welcome') {
42
  handleStart();
43
+ } else if (gameState === 'showing-guess') {
44
  const correct = isGuessCorrect();
45
  if (correct) {
46
  handleNextRound();
 
 
47
  }
48
  }
49
  }
 
171
  }
172
  };
173
  getNewWord();
 
 
174
  }
175
  };
176
 
 
225
  onMakeGuess={handleMakeGuess}
226
  onBack={handleBack}
227
  />
228
+ ) : (
229
  <GuessDisplay
230
  sentence={sentence}
231
  aiGuess={aiGuess}
 
236
  avgWordsPerRound={getAverageWordsPerRound()}
237
  sessionId={sessionId}
238
  />
239
+ )}
 
 
 
 
 
240
  </motion.div>
241
  </div>
242
  );
243
+ };
src/components/game/GameOver.tsx DELETED
@@ -1,45 +0,0 @@
1
- import { Button } from "@/components/ui/button";
2
- import { motion } from "framer-motion";
3
- import { useEffect } from "react";
4
-
5
- interface GameOverProps {
6
- successfulRounds: number;
7
- onPlayAgain: () => void;
8
- }
9
-
10
- export const GameOver = ({
11
- successfulRounds,
12
- onPlayAgain,
13
- }: GameOverProps) => {
14
- useEffect(() => {
15
- const handleKeyPress = (e: KeyboardEvent) => {
16
- if (e.key.toLowerCase() === 'enter') {
17
- onPlayAgain();
18
- }
19
- };
20
-
21
- window.addEventListener('keydown', handleKeyPress);
22
- return () => window.removeEventListener('keydown', handleKeyPress);
23
- }, [onPlayAgain]);
24
-
25
- return (
26
- <motion.div
27
- initial={{ opacity: 0 }}
28
- animate={{ opacity: 1 }}
29
- className="text-center"
30
- >
31
- <h2 className="mb-4 text-2xl font-semibold text-gray-900">Game Over!</h2>
32
- <p className="mb-6 text-lg text-gray-800">
33
- You completed {successfulRounds} rounds successfully!
34
- </p>
35
- <div className="flex gap-4">
36
- <Button
37
- onClick={onPlayAgain}
38
- className="flex-1 bg-primary text-lg hover:bg-primary/90"
39
- >
40
- Play Again ⏎
41
- </Button>
42
- </div>
43
- </motion.div>
44
- );
45
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/game/GuessDisplay.tsx CHANGED
@@ -1,25 +1,18 @@
1
- import { Button } from "@/components/ui/button";
2
  import { motion } from "framer-motion";
 
 
 
3
  import {
4
  Dialog,
5
  DialogContent,
6
  DialogTrigger,
7
  } from "@/components/ui/dialog";
 
 
 
 
 
8
  import { HighScoreBoard } from "@/components/HighScoreBoard";
9
- import { useState, useEffect } from "react";
10
- import { useTranslation } from "@/hooks/useTranslation";
11
- import { supabase } from "@/integrations/supabase/client";
12
- import { House } from "lucide-react";
13
- import {
14
- AlertDialog,
15
- AlertDialogAction,
16
- AlertDialogCancel,
17
- AlertDialogContent,
18
- AlertDialogDescription,
19
- AlertDialogFooter,
20
- AlertDialogHeader,
21
- AlertDialogTitle,
22
- } from "@/components/ui/alert-dialog";
23
 
24
  interface GuessDisplayProps {
25
  sentence: string[];
@@ -44,174 +37,44 @@ export const GuessDisplay = ({
44
  avgWordsPerRound,
45
  sessionId,
46
  }: GuessDisplayProps) => {
47
- const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
48
- const isCheating = () => aiGuess === 'CHEATING';
49
- const [isDialogOpen, setIsDialogOpen] = useState(false);
50
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
51
  const [hasSubmittedScore, setHasSubmittedScore] = useState(false);
52
  const t = useTranslation();
53
-
54
- useEffect(() => {
55
- const saveGameResult = async () => {
56
- try {
57
- const { error } = await supabase
58
- .from('game_results')
59
- .insert({
60
- target_word: currentWord,
61
- description: sentence.join(' '),
62
- ai_guess: aiGuess,
63
- is_correct: isGuessCorrect(),
64
- session_id: sessionId
65
- });
66
-
67
- if (error) {
68
- console.error('Error saving game result:', error);
69
- } else {
70
- console.log('Game result saved successfully');
71
- }
72
- } catch (error) {
73
- console.error('Error in saveGameResult:', error);
74
- }
75
- };
76
-
77
- saveGameResult();
78
- }, []);
79
-
80
- const handleHomeClick = () => {
81
- console.log('Home button clicked', { currentScore, hasSubmittedScore });
82
- if (currentScore > 0 && !hasSubmittedScore) {
83
- setShowConfirmDialog(true);
84
- } else {
85
- if (onBack) {
86
- console.log('Navigating back to welcome screen');
87
- onBack();
88
- }
89
- }
90
- };
91
 
92
  const handleScoreSubmitted = () => {
93
  console.log('Score submitted, updating state');
94
  setHasSubmittedScore(true);
95
  };
96
 
97
- const handleConfirmHome = () => {
98
- console.log('Confirmed navigation to home');
99
- setShowConfirmDialog(false);
100
- if (onBack) {
101
- onBack();
102
- }
103
- };
104
-
105
  return (
106
  <motion.div
107
  initial={{ opacity: 0 }}
108
  animate={{ opacity: 1 }}
109
  className="text-center relative space-y-6"
110
  >
111
- <div className="flex items-center justify-between">
112
- <Button
113
- variant="ghost"
114
- size="icon"
115
- onClick={handleHomeClick}
116
- className="text-gray-600 hover:text-primary"
117
- >
118
- <House className="h-5 w-5" />
119
- </Button>
120
- <h2 className="text-2xl font-semibold text-gray-900">Think in Sync</h2>
121
- <div className="bg-primary/10 px-3 py-1 rounded-lg">
122
- <span className="text-sm font-medium text-primary">
123
- {t.game.round} {currentScore + 1}
124
- </span>
125
- </div>
126
- </div>
127
-
128
- <div className="space-y-2">
129
- <p className="text-sm text-gray-600">{t.guess.goalDescription}</p>
130
- <div className="overflow-hidden rounded-lg bg-secondary/10">
131
- <p className="p-4 text-2xl font-bold tracking-wider text-secondary">
132
- {currentWord}
133
- </p>
134
- </div>
135
- </div>
136
-
137
- <div className="space-y-2">
138
- <p className="text-sm text-gray-600">{t.guess.providedDescription}</p>
139
- <div className="rounded-lg bg-gray-50">
140
- <p className="p-4 text-2xl tracking-wider text-gray-800">
141
- {sentence.join(" ")}
142
- </p>
143
- </div>
144
- </div>
145
-
146
- <div className="space-y-2">
147
- <p className="text-sm text-gray-600">
148
- {isCheating() ? t.guess.cheatingDetected : t.guess.aiGuessedDescription}
149
- </p>
150
- <div className={`rounded-lg ${isGuessCorrect() ? 'bg-green-50' : 'bg-red-50'}`}>
151
- <p className={`p-4 text-2xl font-bold tracking-wider ${isGuessCorrect() ? 'text-green-600' : 'text-red-600'}`}>
152
- {aiGuess}
153
- </p>
154
- </div>
155
- </div>
156
 
157
- <div className="flex flex-col gap-4">
158
- {isGuessCorrect() ? (
159
- <Button
160
- onClick={onNextRound}
161
- className="w-full bg-primary text-lg hover:bg-primary/90"
162
- >
163
- {t.guess.nextRound} ⏎
164
- </Button>
165
- ) : (
166
- <>
167
- <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
168
- <DialogTrigger asChild>
169
- <Button
170
- className="w-full bg-secondary text-lg hover:bg-secondary/90"
171
- >
172
- {t.guess.viewLeaderboard} 🏆
173
- </Button>
174
- </DialogTrigger>
175
- <DialogContent className="max-w-md bg-white">
176
- <HighScoreBoard
177
- currentScore={currentScore}
178
- avgWordsPerRound={avgWordsPerRound}
179
- onClose={() => setIsDialogOpen(false)}
180
- onPlayAgain={() => {
181
- setIsDialogOpen(false);
182
- onPlayAgain();
183
- }}
184
- sessionId={sessionId}
185
- onScoreSubmitted={handleScoreSubmitted}
186
- />
187
- </DialogContent>
188
- </Dialog>
189
- <Button
190
- onClick={onPlayAgain}
191
- className="w-full bg-primary text-lg hover:bg-primary/90"
192
- >
193
- {t.guess.playAgain} ⏎
194
- </Button>
195
- </>
196
- )}
197
- </div>
198
 
199
- <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
200
- <AlertDialogContent>
201
- <AlertDialogHeader>
202
- <AlertDialogTitle>{t.game.leaveGameTitle}</AlertDialogTitle>
203
- <AlertDialogDescription>
204
- {t.game.leaveGameDescription}
205
- </AlertDialogDescription>
206
- </AlertDialogHeader>
207
- <AlertDialogFooter>
208
- <AlertDialogCancel>{t.game.cancel}</AlertDialogCancel>
209
- <AlertDialogAction onClick={handleConfirmHome}>
210
- {t.game.confirm}
211
- </AlertDialogAction>
212
- </AlertDialogFooter>
213
- </AlertDialogContent>
214
- </AlertDialog>
215
  </motion.div>
216
  );
217
  };
 
 
1
  import { motion } from "framer-motion";
2
+ import { useState } from "react";
3
+ import { useTranslation } from "@/hooks/useTranslation";
4
+ import { Button } from "@/components/ui/button";
5
  import {
6
  Dialog,
7
  DialogContent,
8
  DialogTrigger,
9
  } from "@/components/ui/dialog";
10
+ import { RoundHeader } from "./sentence-builder/RoundHeader";
11
+ import { WordDisplay } from "./sentence-builder/WordDisplay";
12
+ import { GuessDescription } from "./guess-display/GuessDescription";
13
+ import { GuessResult } from "./guess-display/GuessResult";
14
+ import { ActionButtons } from "./guess-display/ActionButtons";
15
  import { HighScoreBoard } from "@/components/HighScoreBoard";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  interface GuessDisplayProps {
18
  sentence: string[];
 
37
  avgWordsPerRound,
38
  sessionId,
39
  }: GuessDisplayProps) => {
 
 
 
40
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
41
  const [hasSubmittedScore, setHasSubmittedScore] = useState(false);
42
  const t = useTranslation();
43
+ const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  const handleScoreSubmitted = () => {
46
  console.log('Score submitted, updating state');
47
  setHasSubmittedScore(true);
48
  };
49
 
 
 
 
 
 
 
 
 
50
  return (
51
  <motion.div
52
  initial={{ opacity: 0 }}
53
  animate={{ opacity: 1 }}
54
  className="text-center relative space-y-6"
55
  >
56
+ <RoundHeader
57
+ successfulRounds={currentScore}
58
+ goToWelcomeScreen={onBack}
59
+ showConfirmDialog={showConfirmDialog}
60
+ setShowConfirmDialog={setShowConfirmDialog}
61
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ <WordDisplay currentWord={currentWord} />
64
+
65
+ <GuessDescription sentence={sentence} aiGuess={aiGuess} />
66
+
67
+ <GuessResult aiGuess={aiGuess} isCorrect={isGuessCorrect()} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ <ActionButtons
70
+ isCorrect={isGuessCorrect()}
71
+ onNextRound={onNextRound}
72
+ onPlayAgain={onPlayAgain}
73
+ currentScore={currentScore}
74
+ avgWordsPerRound={avgWordsPerRound}
75
+ sessionId={sessionId}
76
+ onScoreSubmitted={handleScoreSubmitted}
77
+ />
 
 
 
 
 
 
 
78
  </motion.div>
79
  );
80
  };
src/components/game/SentenceBuilder.tsx CHANGED
@@ -67,7 +67,7 @@ export const SentenceBuilder = ({
67
  >
68
  <RoundHeader
69
  successfulRounds={successfulRounds}
70
- onBack={onBack}
71
  showConfirmDialog={showConfirmDialog}
72
  setShowConfirmDialog={setShowConfirmDialog}
73
  />
@@ -85,6 +85,7 @@ export const SentenceBuilder = ({
85
  hasMultipleWords={hasMultipleWords}
86
  containsTargetWord={containsTargetWord}
87
  isValidInput={isValidInput}
 
88
  />
89
 
90
  <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
 
67
  >
68
  <RoundHeader
69
  successfulRounds={successfulRounds}
70
+ goToWelcomeScreen={onBack}
71
  showConfirmDialog={showConfirmDialog}
72
  setShowConfirmDialog={setShowConfirmDialog}
73
  />
 
85
  hasMultipleWords={hasMultipleWords}
86
  containsTargetWord={containsTargetWord}
87
  isValidInput={isValidInput}
88
+ sentence={sentence}
89
  />
90
 
91
  <AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
src/components/game/guess-display/ActionButtons.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
3
+ import { HighScoreBoard } from "@/components/HighScoreBoard";
4
+ import { useState } from "react";
5
+ import { useTranslation } from "@/hooks/useTranslation";
6
+
7
+ interface ActionButtonsProps {
8
+ isCorrect: boolean;
9
+ onNextRound: () => void;
10
+ onPlayAgain: () => void;
11
+ currentScore: number;
12
+ avgWordsPerRound: number;
13
+ sessionId: string;
14
+ onScoreSubmitted: () => void;
15
+ }
16
+
17
+ export const ActionButtons = ({
18
+ isCorrect,
19
+ onNextRound,
20
+ onPlayAgain,
21
+ currentScore,
22
+ avgWordsPerRound,
23
+ sessionId,
24
+ onScoreSubmitted,
25
+ }: ActionButtonsProps) => {
26
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
27
+ const t = useTranslation();
28
+
29
+ return (
30
+ <div className="flex flex-col gap-4">
31
+ {isCorrect ? (
32
+ <Button
33
+ onClick={onNextRound}
34
+ className="w-full bg-primary text-lg hover:bg-primary/90"
35
+ >
36
+ {t.guess.nextRound} ⏎
37
+ </Button>
38
+ ) : (
39
+ <>
40
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
41
+ <DialogTrigger asChild>
42
+ <Button
43
+ className="w-full bg-secondary text-lg hover:bg-secondary/90"
44
+ >
45
+ {t.guess.viewLeaderboard} 🏆
46
+ </Button>
47
+ </DialogTrigger>
48
+ <DialogContent className="max-w-md bg-white">
49
+ <HighScoreBoard
50
+ currentScore={currentScore}
51
+ avgWordsPerRound={avgWordsPerRound}
52
+ onClose={() => setIsDialogOpen(false)}
53
+ onPlayAgain={() => {
54
+ setIsDialogOpen(false);
55
+ onPlayAgain();
56
+ }}
57
+ sessionId={sessionId}
58
+ onScoreSubmitted={onScoreSubmitted}
59
+ />
60
+ </DialogContent>
61
+ </Dialog>
62
+ <Button
63
+ onClick={onPlayAgain}
64
+ className="w-full bg-primary text-lg hover:bg-primary/90"
65
+ >
66
+ {t.guess.playAgain} ⏎
67
+ </Button>
68
+ </>
69
+ )}
70
+ </div>
71
+ );
72
+ };
src/components/game/guess-display/GuessDescription.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslation } from "@/hooks/useTranslation";
2
+
3
+ interface GuessDescriptionProps {
4
+ sentence: string[];
5
+ aiGuess: string;
6
+ }
7
+
8
+ export const GuessDescription = ({ sentence, aiGuess }: GuessDescriptionProps) => {
9
+ const t = useTranslation();
10
+ const isCheating = () => aiGuess === 'CHEATING';
11
+
12
+ return (
13
+ <div className="space-y-2">
14
+ <p className="text-sm text-gray-600">{t.guess.providedDescription}</p>
15
+ <div className="rounded-lg bg-gray-50">
16
+ <p className="p-4 text-2xl tracking-wider text-gray-800">
17
+ {sentence.join(" ")}
18
+ </p>
19
+ </div>
20
+
21
+ <p className="text-sm text-gray-600">
22
+ {isCheating() ? t.guess.cheatingDetected : t.guess.aiGuessedDescription}
23
+ </p>
24
+ </div>
25
+ );
26
+ };
src/components/game/guess-display/GuessResult.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface GuessResultProps {
2
+ aiGuess: string;
3
+ isCorrect: boolean;
4
+ }
5
+
6
+ export const GuessResult = ({ aiGuess, isCorrect }: GuessResultProps) => {
7
+ return (
8
+ <div className={`rounded-lg ${isCorrect ? 'bg-green-50' : 'bg-red-50'}`}>
9
+ <p className={`p-4 text-2xl font-bold tracking-wider ${isCorrect ? 'text-green-600' : 'text-red-600'}`}>
10
+ {aiGuess}
11
+ </p>
12
+ </div>
13
+ );
14
+ };
src/components/game/sentence-builder/InputForm.tsx CHANGED
@@ -12,6 +12,7 @@ interface InputFormProps {
12
  hasMultipleWords: boolean;
13
  containsTargetWord: boolean;
14
  isValidInput: boolean;
 
15
  }
16
 
17
  export const InputForm = ({
@@ -22,16 +23,20 @@ export const InputForm = ({
22
  isAiThinking,
23
  hasMultipleWords,
24
  containsTargetWord,
25
- isValidInput
 
26
  }: InputFormProps) => {
27
  const inputRef = useRef<HTMLInputElement>(null);
28
  const t = useTranslation();
29
 
 
30
  useEffect(() => {
31
- setTimeout(() => {
32
- inputRef.current?.focus();
33
- }, 100);
34
- }, []);
 
 
35
 
36
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
37
  if (e.shiftKey && e.key === 'Enter') {
@@ -51,6 +56,10 @@ export const InputForm = ({
51
 
52
  const error = getInputError();
53
 
 
 
 
 
54
  return (
55
  <form onSubmit={onSubmitWord} className="mb-4">
56
  <div className="relative mb-4">
@@ -82,7 +91,7 @@ export const InputForm = ({
82
  type="button"
83
  onClick={onMakeGuess}
84
  className="flex-1 bg-secondary text-lg hover:bg-secondary/90"
85
- disabled={(!playerInput.trim() && !playerInput.trim()) || isAiThinking || hasMultipleWords || containsTargetWord || !isValidInput}
86
  >
87
  {isAiThinking ? t.game.aiThinking : `${t.game.makeGuess} ⇧⏎`}
88
  </Button>
 
12
  hasMultipleWords: boolean;
13
  containsTargetWord: boolean;
14
  isValidInput: boolean;
15
+ sentence: string[];
16
  }
17
 
18
  export const InputForm = ({
 
23
  isAiThinking,
24
  hasMultipleWords,
25
  containsTargetWord,
26
+ isValidInput,
27
+ sentence
28
  }: InputFormProps) => {
29
  const inputRef = useRef<HTMLInputElement>(null);
30
  const t = useTranslation();
31
 
32
+ // Focus input on mount and after AI response
33
  useEffect(() => {
34
+ if (!isAiThinking) {
35
+ setTimeout(() => {
36
+ inputRef.current?.focus();
37
+ }, 100);
38
+ }
39
+ }, [isAiThinking]);
40
 
41
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
42
  if (e.shiftKey && e.key === 'Enter') {
 
56
 
57
  const error = getInputError();
58
 
59
+ // Check if there's either something in the sentence or in the input box
60
+ const canMakeGuess = (sentence.length > 0 || playerInput.trim().length > 0) &&
61
+ !hasMultipleWords && !containsTargetWord && isValidInput && !isAiThinking;
62
+
63
  return (
64
  <form onSubmit={onSubmitWord} className="mb-4">
65
  <div className="relative mb-4">
 
91
  type="button"
92
  onClick={onMakeGuess}
93
  className="flex-1 bg-secondary text-lg hover:bg-secondary/90"
94
+ disabled={!canMakeGuess}
95
  >
96
  {isAiThinking ? t.game.aiThinking : `${t.game.makeGuess} ⇧⏎`}
97
  </Button>
src/components/game/sentence-builder/RoundHeader.tsx CHANGED
@@ -4,14 +4,14 @@ import { useTranslation } from "@/hooks/useTranslation";
4
 
5
  interface RoundHeaderProps {
6
  successfulRounds: number;
7
- onBack?: () => void;
8
  showConfirmDialog: boolean;
9
  setShowConfirmDialog: (show: boolean) => void;
10
  }
11
 
12
  export const RoundHeader = ({
13
  successfulRounds,
14
- onBack,
15
  showConfirmDialog,
16
  setShowConfirmDialog
17
  }: RoundHeaderProps) => {
@@ -21,7 +21,7 @@ export const RoundHeader = ({
21
  if (successfulRounds > 0) {
22
  setShowConfirmDialog(true);
23
  } else {
24
- onBack?.();
25
  }
26
  };
27
 
 
4
 
5
  interface RoundHeaderProps {
6
  successfulRounds: number;
7
+ goToWelcomeScreen?: () => void;
8
  showConfirmDialog: boolean;
9
  setShowConfirmDialog: (show: boolean) => void;
10
  }
11
 
12
  export const RoundHeader = ({
13
  successfulRounds,
14
+ goToWelcomeScreen,
15
  showConfirmDialog,
16
  setShowConfirmDialog
17
  }: RoundHeaderProps) => {
 
21
  if (successfulRounds > 0) {
22
  setShowConfirmDialog(true);
23
  } else {
24
+ goToWelcomeScreen?.();
25
  }
26
  };
27
 
src/services/mistralService.ts CHANGED
@@ -31,41 +31,15 @@ export const generateAIResponse = async (currentWord: string, currentSentence: s
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
32
  console.log('Processing guess for sentence:', sentence);
33
 
34
- // Extract the target word from the sentence (assuming it's the first word)
35
  const words = sentence.trim().split(/\s+/);
36
  const targetWord = words[0].toLowerCase();
37
 
38
- // Check for potential fraud if the sentence has less than 3 words
39
- if (words.length < 3) {
40
- console.log('Short description detected, checking for fraud...');
41
-
42
- try {
43
- const { data: fraudData, error: fraudError } = await supabase.functions.invoke('detect-fraud', {
44
- body: {
45
- sentence,
46
- targetWord,
47
- language
48
- }
49
- });
50
-
51
- if (fraudError) throw fraudError;
52
-
53
- if (fraudData?.verdict === 'cheating') {
54
- console.log('Fraud detected!');
55
- return 'CHEATING';
56
- }
57
- } catch (error) {
58
- console.error('Error in fraud detection:', error);
59
- // Continue with normal guessing if fraud detection fails
60
- }
61
- }
62
-
63
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
64
 
65
  const { data, error } = await supabase.functions.invoke('guess-word', {
66
  body: {
67
  sentence,
68
- targetWord, // Pass the target word to prevent guessing it
69
  language
70
  }
71
  });
 
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
32
  console.log('Processing guess for sentence:', sentence);
33
 
 
34
  const words = sentence.trim().split(/\s+/);
35
  const targetWord = words[0].toLowerCase();
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
38
 
39
  const { data, error } = await supabase.functions.invoke('guess-word', {
40
  body: {
41
  sentence,
42
+ targetWord,
43
  language
44
  }
45
  });
supabase/functions/detect-fraud/index.ts DELETED
@@ -1,145 +0,0 @@
1
- import { serve } from "https://deno.land/[email protected]/http/server.ts";
2
- import { Mistral } from "npm:@mistralai/mistralai";
3
-
4
- const corsHeaders = {
5
- 'Access-Control-Allow-Origin': '*',
6
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
7
- };
8
-
9
- const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
10
-
11
- serve(async (req) => {
12
- // Handle CORS preflight requests
13
- if (req.method === 'OPTIONS') {
14
- return new Response(null, { headers: corsHeaders });
15
- }
16
-
17
- try {
18
- const { sentence, targetWord, language } = await req.json();
19
- console.log('Checking for fraud:', { sentence, targetWord, language });
20
-
21
- const client = new Mistral({
22
- apiKey: Deno.env.get('MISTRAL_API_KEY'),
23
- });
24
-
25
- const maxRetries = 3;
26
- let retryCount = 0;
27
- let lastError = null;
28
-
29
- while (retryCount < maxRetries) {
30
- try {
31
- console.log(`Attempt ${retryCount + 1} to check for fraud`);
32
-
33
- const response = await client.chat.complete({
34
- model: "mistral-large-latest",
35
- messages: [
36
- {
37
- role: "system",
38
- content: `You are a fraud detection system for a word guessing game.
39
- The game is being played in ${language}.
40
- Your task is to detect if a player is trying to cheat by one of two methods:
41
- 1. The Player's description is a misspelling of the target word
42
- 2. The Player's description is a sentence without spaces
43
-
44
- Examples for cheating:
45
-
46
- Target word: hand
47
- Player's description: hnd
48
- Language: en
49
- CORRECT ANSWER: cheating
50
-
51
- Target word: barfuß
52
- Player's description: germanwordforbarefoot
53
- Language: de
54
- CORRECT ANSWER: cheating
55
-
56
- Synonyms and names of instances of a class are legitimate descriptions.
57
-
58
- Target word: laptop
59
- Player's description: notebook
60
- Language: en
61
- CORRECT ANSWER: legitimate
62
-
63
- Target word: play
64
- Player's description: children often
65
- Language: en
66
- CORRECT ANSWER: legitimate
67
-
68
- Target word: Pfankuchen
69
- Player's description: Berliner
70
- Language: de
71
- CORRECT ANSWER: legitimate
72
-
73
- Target word: Burrito
74
- Player's description: Wrap
75
- Language: es
76
- CORRECT ANSWER: legitimate
77
-
78
- Respond with ONLY "cheating" or "legitimate" (no punctuation or explanation).`
79
- },
80
- {
81
- role: "user",
82
- content: `Target word: "${targetWord}"
83
- Player's description: "${sentence}"
84
- Language: ${language}
85
-
86
- Is this a legitimate description or an attempt to cheat?`
87
- }
88
- ],
89
- maxTokens: 20,
90
- temperature: 0.1
91
- });
92
-
93
- if (!response?.choices?.[0]?.message?.content) {
94
- throw new Error('Invalid response format from Mistral API');
95
- }
96
-
97
- const verdict = response.choices[0].message.content.trim().toLowerCase();
98
- console.log('Fraud detection verdict:', verdict);
99
-
100
- return new Response(
101
- JSON.stringify({ verdict }),
102
- { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
103
- );
104
- } catch (error) {
105
- console.error(`Attempt ${retryCount + 1} failed:`, error);
106
- lastError = error;
107
-
108
- // Check if it's a rate limit or service unavailable error
109
- if (error.message?.includes('rate limit') ||
110
- error.message?.includes('503') ||
111
- error.message?.includes('Service unavailable')) {
112
- const waitTime = Math.pow(2, retryCount) * 1000; // Exponential backoff
113
- console.log(`Service unavailable or rate limited, waiting ${waitTime}ms before retry`);
114
- await sleep(waitTime);
115
- retryCount++;
116
- continue;
117
- }
118
-
119
- // If it's not a retryable error, throw immediately
120
- throw error;
121
- }
122
- }
123
-
124
- // If we've exhausted all retries
125
- throw new Error(`Failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
126
-
127
- } catch (error) {
128
- console.error('Error in fraud detection:', error);
129
-
130
- const errorMessage = error.message?.includes('rate limit') || error.message?.includes('503')
131
- ? "The AI service is currently busy. Please try again in a few moments."
132
- : "Sorry, there was an error checking for fraud. Please try again.";
133
-
134
- return new Response(
135
- JSON.stringify({
136
- error: errorMessage,
137
- details: error.message
138
- }),
139
- {
140
- status: error.message?.includes('rate limit') || error.message?.includes('503') ? 429 : 500,
141
- headers: { ...corsHeaders, 'Content-Type': 'application/json' }
142
- }
143
- );
144
- }
145
- });