Felix Zieger commited on
Commit
5835ecd
·
1 Parent(s): f114b7f
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Think in Sync
3
- emoji: 🐢
4
  colorFrom: blue
5
  colorTo: pink
6
  sdk: docker
@@ -9,8 +9,8 @@ pinned: false
9
  ---
10
  # Think in Sync
11
 
12
- You will be given a secret word. Your goal is to describe this secret word so that an AI can guess it.
13
- However, you are only allowed to say one word at the time, taking turns with another AI.
14
 
15
  ## Develop locally
16
 
@@ -25,10 +25,11 @@ npm run dev
25
 
26
  ## What technologies are used for this project?
27
 
28
- This project is built with .
29
 
30
  - Vite
31
  - TypeScript
32
  - React
33
  - shadcn-ui
34
  - Tailwind CSS
 
 
1
  ---
2
  title: Think in Sync
3
+ emoji: 🧠
4
  colorFrom: blue
5
  colorTo: pink
6
  sdk: docker
 
9
  ---
10
  # Think in Sync
11
 
12
+ You will be given a secret word. You aim to describe this secret word so an AI can guess it.
13
+ However, you can only say one word at a time, taking turns with another AI.
14
 
15
  ## Develop locally
16
 
 
25
 
26
  ## What technologies are used for this project?
27
 
28
+ This project is built with
29
 
30
  - Vite
31
  - TypeScript
32
  - React
33
  - shadcn-ui
34
  - Tailwind CSS
35
+ - Supabase
src/components/GameContainer.tsx CHANGED
@@ -172,7 +172,7 @@ export const GameContainer = () => {
172
  };
173
 
174
  const handlePlayAgain = () => {
175
- setGameState("welcome");
176
  setSentence([]);
177
  setAiGuess("");
178
  setCurrentWord("");
 
172
  };
173
 
174
  const handlePlayAgain = () => {
175
+ setGameState("theme-selection");
176
  setSentence([]);
177
  setAiGuess("");
178
  setCurrentWord("");
src/components/game/GuessDisplay.tsx CHANGED
@@ -6,8 +6,10 @@ import {
6
  DialogTrigger,
7
  } from "@/components/ui/dialog";
8
  import { HighScoreBoard } from "@/components/HighScoreBoard";
9
- import { useState } from "react";
10
  import { useTranslation } from "@/hooks/useTranslation";
 
 
11
 
12
  interface GuessDisplayProps {
13
  sentence: string[];
@@ -15,6 +17,7 @@ interface GuessDisplayProps {
15
  currentWord: string;
16
  onNextRound: () => void;
17
  onPlayAgain: () => void;
 
18
  currentScore: number;
19
  avgWordsPerRound: number;
20
  }
@@ -25,57 +28,97 @@ export const GuessDisplay = ({
25
  currentWord,
26
  onNextRound,
27
  onPlayAgain,
 
28
  currentScore,
29
  avgWordsPerRound,
30
  }: GuessDisplayProps) => {
31
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
 
32
  const [isDialogOpen, setIsDialogOpen] = useState(false);
33
  const t = useTranslation();
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  return (
36
  <motion.div
37
  initial={{ opacity: 0 }}
38
  animate={{ opacity: 1 }}
39
- className="text-center relative"
40
  >
41
- <div className="absolute right-0 top-0 bg-primary/10 px-3 py-1 rounded-lg">
42
- <span className="text-sm font-medium text-primary">
43
- {t.game.round} {currentScore + 1}
44
- </span>
 
 
 
 
 
 
 
 
 
 
 
45
  </div>
46
 
47
- <h2 className="mb-4 text-2xl font-semibold text-gray-900">Think in Sync</h2>
48
-
49
  <div>
50
- <p className="text-sm text-gray-600 mb-1">{t.guess.goalDescription}</p>
51
- <div className="mb-6 overflow-hidden rounded-lg bg-secondary/10">
52
- <p className="p-4 text-2xl font-bold tracking-wider text-secondary">
53
- {currentWord}
54
- </p>
55
- </div>
56
- </div>
57
 
58
- <div className="space-y-4">
59
- <div>
60
- <p className="text-sm text-gray-600 mb-1">{t.guess.providedDescription}</p>
61
- <div className="rounded-lg bg-gray-50">
62
- <p className="p-4 text-2xl tracking-wider text-gray-800">
63
- {sentence.join(" ")}
64
  </p>
65
  </div>
66
  </div>
 
67
 
68
- <div>
69
- <p className="text-sm text-gray-600 mb-1">{t.guess.aiGuessedDescription}</p>
70
- <div className={`rounded-lg p-4 ${isGuessCorrect() ? 'bg-green-50' : 'bg-red-50'}`}>
71
- <p className={`text-2xl font-bold tracking-wider ${isGuessCorrect() ? 'text-green-600' : 'text-red-600'}`}>
72
- {aiGuess}
73
- </p>
74
- </div>
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
  </div>
77
 
78
- <div className="mt-6 flex flex-col gap-4">
79
  {isGuessCorrect() ? (
80
  <Button
81
  onClick={onNextRound}
 
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
 
14
  interface GuessDisplayProps {
15
  sentence: string[];
 
17
  currentWord: string;
18
  onNextRound: () => void;
19
  onPlayAgain: () => void;
20
+ onBack?: () => void;
21
  currentScore: number;
22
  avgWordsPerRound: number;
23
  }
 
28
  currentWord,
29
  onNextRound,
30
  onPlayAgain,
31
+ onBack,
32
  currentScore,
33
  avgWordsPerRound,
34
  }: GuessDisplayProps) => {
35
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
36
+ const isCheating = () => aiGuess === 'CHEATING';
37
  const [isDialogOpen, setIsDialogOpen] = useState(false);
38
  const t = useTranslation();
39
 
40
+ useEffect(() => {
41
+ const saveGameResult = async () => {
42
+ try {
43
+ const { error } = await supabase
44
+ .from('game_results')
45
+ .insert({
46
+ target_word: currentWord,
47
+ description: sentence.join(' '),
48
+ ai_guess: aiGuess,
49
+ is_correct: isGuessCorrect()
50
+ });
51
+
52
+ if (error) {
53
+ console.error('Error saving game result:', error);
54
+ } else {
55
+ console.log('Game result saved successfully');
56
+ }
57
+ } catch (error) {
58
+ console.error('Error in saveGameResult:', error);
59
+ }
60
+ };
61
+
62
+ saveGameResult();
63
+ }, []);
64
+
65
  return (
66
  <motion.div
67
  initial={{ opacity: 0 }}
68
  animate={{ opacity: 1 }}
69
+ className="text-center relative space-y-6"
70
  >
71
+ <div className="flex items-center justify-between mb-4">
72
+ <Button
73
+ variant="ghost"
74
+ size="icon"
75
+ onClick={onBack}
76
+ className="text-gray-600 hover:text-primary"
77
+ >
78
+ <House className="h-5 w-5" />
79
+ </Button>
80
+ <div className="bg-primary/10 px-3 py-1 rounded-lg">
81
+ <span className="text-sm font-medium text-primary">
82
+ {t.game.round} {currentScore + 1}
83
+ </span>
84
+ </div>
85
+ <div className="w-8" /> {/* Spacer for centering */}
86
  </div>
87
 
 
 
88
  <div>
89
+ <h2 className="mb-4 text-2xl font-semibold text-gray-900">Think in Sync</h2>
 
 
 
 
 
 
90
 
91
+ <div className="space-y-2">
92
+ <p className="text-sm text-gray-600">{t.guess.goalDescription}</p>
93
+ <div className="overflow-hidden rounded-lg bg-secondary/10">
94
+ <p className="p-4 text-2xl font-bold tracking-wider text-secondary">
95
+ {currentWord}
 
96
  </p>
97
  </div>
98
  </div>
99
+ </div>
100
 
101
+ <div className="space-y-2">
102
+ <p className="text-sm text-gray-600">{t.guess.providedDescription}</p>
103
+ <div className="rounded-lg bg-gray-50">
104
+ <p className="p-4 text-2xl tracking-wider text-gray-800">
105
+ {sentence.join(" ")}
106
+ </p>
107
+ </div>
108
+ </div>
109
+
110
+ <div className="space-y-2">
111
+ <p className="text-sm text-gray-600">
112
+ {isCheating() ? t.guess.cheatingDetected : t.guess.aiGuessedDescription}
113
+ </p>
114
+ <div className={`rounded-lg ${isGuessCorrect() ? 'bg-green-50' : 'bg-red-50'}`}>
115
+ <p className={`p-4 text-2xl font-bold tracking-wider ${isGuessCorrect() ? 'text-green-600' : 'text-red-600'}`}>
116
+ {aiGuess}
117
+ </p>
118
  </div>
119
  </div>
120
 
121
+ <div className="flex flex-col gap-4">
122
  {isGuessCorrect() ? (
123
  <Button
124
  onClick={onNextRound}
src/components/game/SentenceBuilder.tsx CHANGED
@@ -42,6 +42,7 @@ export const SentenceBuilder = ({
42
  const inputRef = useRef<HTMLInputElement>(null);
43
  const [imageLoaded, setImageLoaded] = useState(false);
44
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
 
45
  const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
46
  const { toast } = useToast();
47
  const t = useTranslation();
@@ -67,13 +68,18 @@ export const SentenceBuilder = ({
67
  }
68
  }, [isAiThinking, sentence.length]);
69
 
 
 
 
 
 
70
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
71
  if (e.shiftKey && e.key === 'Enter') {
72
  e.preventDefault();
73
- if (playerInput.trim()) {
74
- handleSubmit(e as any);
 
75
  }
76
- onMakeGuess();
77
  }
78
  };
79
 
@@ -82,6 +88,15 @@ export const SentenceBuilder = ({
82
  const input = playerInput.trim().toLowerCase();
83
  const target = currentWord.toLowerCase();
84
 
 
 
 
 
 
 
 
 
 
85
  if (!/^[\p{L}]+$/u.test(input)) {
86
  toast({
87
  title: t.game.invalidWord,
@@ -169,21 +184,23 @@ export const SentenceBuilder = ({
169
  ref={inputRef}
170
  type="text"
171
  value={playerInput}
172
- onChange={(e) => {
173
- const value = e.target.value.replace(/[^a-zA-ZÀ-ÿ]/g, '');
174
- onInputChange(value);
175
- }}
176
  onKeyDown={handleKeyDown}
177
  placeholder={t.game.inputPlaceholder}
178
- className="w-full"
179
  disabled={isAiThinking}
180
  />
 
 
 
 
 
181
  </div>
182
  <div className="flex gap-4">
183
  <Button
184
  type="submit"
185
  className="flex-1 bg-primary text-lg hover:bg-primary/90"
186
- disabled={!playerInput.trim() || isAiThinking}
187
  >
188
  {isAiThinking ? t.game.aiThinking : `${t.game.addWord} ⏎`}
189
  </Button>
@@ -191,7 +208,7 @@ export const SentenceBuilder = ({
191
  type="button"
192
  onClick={onMakeGuess}
193
  className="flex-1 bg-secondary text-lg hover:bg-secondary/90"
194
- disabled={(!sentence.length && !playerInput.trim()) || isAiThinking}
195
  >
196
  {isAiThinking ? t.game.aiThinking : `${t.game.makeGuess} ⇧⏎`}
197
  </Button>
@@ -216,4 +233,4 @@ export const SentenceBuilder = ({
216
  </AlertDialog>
217
  </motion.div>
218
  );
219
- };
 
42
  const inputRef = useRef<HTMLInputElement>(null);
43
  const [imageLoaded, setImageLoaded] = useState(false);
44
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
45
+ const [hasMultipleWords, setHasMultipleWords] = useState(false);
46
  const imagePath = `/think_in_sync_assets/${currentWord.toLowerCase()}.jpg`;
47
  const { toast } = useToast();
48
  const t = useTranslation();
 
68
  }
69
  }, [isAiThinking, sentence.length]);
70
 
71
+ useEffect(() => {
72
+ // Check if input contains multiple words
73
+ setHasMultipleWords(playerInput.trim().split(/\s+/).length > 1);
74
+ }, [playerInput]);
75
+
76
  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
77
  if (e.shiftKey && e.key === 'Enter') {
78
  e.preventDefault();
79
+ // Only trigger if buttons are not disabled and either we have a sentence or valid input
80
+ if (!hasMultipleWords && !isAiThinking && (sentence.length > 0 || playerInput.trim())) {
81
+ onMakeGuess();
82
  }
 
83
  }
84
  };
85
 
 
88
  const input = playerInput.trim().toLowerCase();
89
  const target = currentWord.toLowerCase();
90
 
91
+ if (hasMultipleWords) {
92
+ toast({
93
+ title: t.game.invalidWord,
94
+ description: t.game.singleWordOnly,
95
+ variant: "destructive",
96
+ });
97
+ return;
98
+ }
99
+
100
  if (!/^[\p{L}]+$/u.test(input)) {
101
  toast({
102
  title: t.game.invalidWord,
 
184
  ref={inputRef}
185
  type="text"
186
  value={playerInput}
187
+ onChange={(e) => onInputChange(e.target.value)}
 
 
 
188
  onKeyDown={handleKeyDown}
189
  placeholder={t.game.inputPlaceholder}
190
+ className={`w-full ${hasMultipleWords ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
191
  disabled={isAiThinking}
192
  />
193
+ {hasMultipleWords && (
194
+ <p className="text-sm text-red-500 mt-1">
195
+ {t.game.singleWordOnly}
196
+ </p>
197
+ )}
198
  </div>
199
  <div className="flex gap-4">
200
  <Button
201
  type="submit"
202
  className="flex-1 bg-primary text-lg hover:bg-primary/90"
203
+ disabled={!playerInput.trim() || isAiThinking || hasMultipleWords}
204
  >
205
  {isAiThinking ? t.game.aiThinking : `${t.game.addWord} ⏎`}
206
  </Button>
 
208
  type="button"
209
  onClick={onMakeGuess}
210
  className="flex-1 bg-secondary text-lg hover:bg-secondary/90"
211
+ disabled={(!sentence.length && !playerInput.trim()) || isAiThinking || hasMultipleWords}
212
  >
213
  {isAiThinking ? t.game.aiThinking : `${t.game.makeGuess} ⇧⏎`}
214
  </Button>
 
233
  </AlertDialog>
234
  </motion.div>
235
  );
236
+ };
src/i18n/translations/de.ts CHANGED
@@ -12,6 +12,7 @@ export const de = {
12
  invalidWord: "Ungültiges Wort",
13
  cantUseTargetWord: "Du kannst das Zielwort nicht verwenden",
14
  lettersOnly: "Bitte nur Buchstaben verwenden",
 
15
  leaveGameTitle: "Spiel verlassen?",
16
  leaveGameDescription: "Dein aktueller Fortschritt geht verloren. Bist du sicher, dass du das Spiel verlassen möchtest?",
17
  cancel: "Abbrechen",
@@ -52,7 +53,8 @@ export const de = {
52
  incorrect: "Das ist falsch.",
53
  nextRound: "Nächste Runde",
54
  playAgain: "Erneut spielen",
55
- viewLeaderboard: "In Bestenliste eintragen"
 
56
  },
57
  themes: {
58
  title: "Wähle ein Thema",
@@ -69,12 +71,12 @@ export const de = {
69
  },
70
  welcome: {
71
  title: "Think in Sync",
72
- subtitle: "Baue Sätze zusammen und lass die KI dein Wort erraten!",
73
  startButton: "Spiel starten",
74
  howToPlay: "Spielanleitung",
75
  leaderboard: "Bestenliste",
76
  credits: "Erstellt während des",
77
- helpWin: "Hilf uns gewinnen",
78
  onHuggingface: "mit einem Like auf Huggingface"
79
  },
80
  howToPlay: {
@@ -96,5 +98,4 @@ export const de = {
96
  ]
97
  }
98
  }
99
- };
100
-
 
12
  invalidWord: "Ungültiges Wort",
13
  cantUseTargetWord: "Du kannst das Zielwort nicht verwenden",
14
  lettersOnly: "Bitte nur Buchstaben verwenden",
15
+ singleWordOnly: "Bitte nur ein Wort eingeben",
16
  leaveGameTitle: "Spiel verlassen?",
17
  leaveGameDescription: "Dein aktueller Fortschritt geht verloren. Bist du sicher, dass du das Spiel verlassen möchtest?",
18
  cancel: "Abbrechen",
 
53
  incorrect: "Das ist falsch.",
54
  nextRound: "Nächste Runde",
55
  playAgain: "Erneut spielen",
56
+ viewLeaderboard: "In Bestenliste eintragen",
57
+ cheatingDetected: "Betrugsversuch erkannt!"
58
  },
59
  themes: {
60
  title: "Wähle ein Thema",
 
71
  },
72
  welcome: {
73
  title: "Think in Sync",
74
+ subtitle: "Arbeite mit einer KI zusammen, um einen Hinweis zu erstellen, und lass eine andere KI dein geheimes Wort erraten!",
75
  startButton: "Spiel starten",
76
  howToPlay: "Spielanleitung",
77
  leaderboard: "Bestenliste",
78
  credits: "Erstellt während des",
79
+ helpWin: "Hilf uns zu gewinnen",
80
  onHuggingface: "mit einem Like auf Huggingface"
81
  },
82
  howToPlay: {
 
98
  ]
99
  }
100
  }
101
+ };
 
src/i18n/translations/en.ts CHANGED
@@ -12,89 +12,90 @@ export const en = {
12
  invalidWord: "Invalid Word",
13
  cantUseTargetWord: "You can't use the target word",
14
  lettersOnly: "Please use letters only",
 
15
  leaveGameTitle: "Leave Game?",
16
  leaveGameDescription: "Your current progress will be lost. Are you sure you want to leave?",
17
  cancel: "Cancel",
18
  confirm: "Confirm",
19
  describeWord: "Your goal is to describe the word"
20
  },
21
- leaderboard: {
22
- title: "High Scores",
23
- yourScore: "Your Score",
24
- roundCount: "rounds",
25
- wordsPerRound: "words per round",
26
- enterName: "Enter your name",
27
- submitting: "Submitting...",
28
- submit: "Submit Score",
29
- rank: "Rank",
30
- player: "Player",
31
- roundsColumn: "Rounds",
32
- avgWords: "Avg. Words",
33
- noScores: "No scores yet",
34
- previous: "Previous",
35
- next: "Next",
36
- error: {
37
- invalidName: "Please enter a valid name",
38
- noRounds: "You need to complete at least one round",
39
- alreadySubmitted: "Score already submitted",
40
- newHighScore: "New High Score!",
41
- beatRecord: "You beat your previous record of {score}!",
42
- notHigher: "Score of {current} not higher than your best of {best}",
43
- submitError: "Error submitting score"
44
- }
45
- },
46
- guess: {
47
- title: "AI's Guess",
48
- goalDescription: "Your goal was to describe the word",
49
- providedDescription: "You provided the description",
50
- aiGuessedDescription: "Based on your description, the AI guessed",
51
- correct: "This is right!",
52
- incorrect: "This is wrong.",
53
- nextRound: "Next Round",
54
- playAgain: "Play Again",
55
- viewLeaderboard: "Safe your score"
56
- },
57
- themes: {
58
- title: "Choose a Theme",
59
- subtitle: "Select a theme for the word the AI will try to guess",
60
- standard: "Standard",
61
- technology: "Technology",
62
- sports: "Sports",
63
- food: "Food",
64
- custom: "Custom Theme",
65
- customPlaceholder: "Enter your custom theme...",
66
- continue: "Continue",
67
- generating: "Generating...",
68
- pressKey: "Press"
69
- },
70
- welcome: {
71
- title: "Think in Sync",
72
- subtitle: "Build sentences together and let AI guess your word!",
73
- startButton: "Start Game",
74
- howToPlay: "How to Play",
75
- leaderboard: "Leaderboard",
76
- credits: "Created during the",
77
- helpWin: "Help us win by",
78
- onHuggingface: "Liking on Huggingface"
79
- },
80
- howToPlay: {
81
- setup: {
82
- title: "Setup",
83
- description: "Choose a theme and get a secret word that the AI will try to guess."
84
  },
85
- goal: {
86
- title: "Goal",
87
- description: "Build sentences together with the AI that describe your word without using it directly."
 
 
 
 
 
 
88
  },
89
- rules: {
90
- title: "Rules",
91
- items: [
92
- "Take turns adding words to build descriptive sentences",
93
- "Don't use the secret word or its variations",
94
- "Try to be creative and descriptive",
95
- "The AI will try to guess your word after each sentence"
96
- ]
 
 
 
 
 
 
 
 
 
 
97
  }
98
- }
99
  };
100
-
 
12
  invalidWord: "Invalid Word",
13
  cantUseTargetWord: "You can't use the target word",
14
  lettersOnly: "Please use letters only",
15
+ singleWordOnly: "Please enter only one word",
16
  leaveGameTitle: "Leave Game?",
17
  leaveGameDescription: "Your current progress will be lost. Are you sure you want to leave?",
18
  cancel: "Cancel",
19
  confirm: "Confirm",
20
  describeWord: "Your goal is to describe the word"
21
  },
22
+ leaderboard: {
23
+ title: "High Scores",
24
+ yourScore: "Your Score",
25
+ roundCount: "rounds",
26
+ wordsPerRound: "words per round",
27
+ enterName: "Enter your name",
28
+ submitting: "Submitting...",
29
+ submit: "Submit Score",
30
+ rank: "Rank",
31
+ player: "Player",
32
+ roundsColumn: "Rounds",
33
+ avgWords: "Avg. Words",
34
+ noScores: "No scores yet",
35
+ previous: "Previous",
36
+ next: "Next",
37
+ error: {
38
+ invalidName: "Please enter a valid name",
39
+ noRounds: "You need to complete at least one round",
40
+ alreadySubmitted: "Score already submitted",
41
+ newHighScore: "New High Score!",
42
+ beatRecord: "You beat your previous record of {score}!",
43
+ notHigher: "Score of {current} not higher than your best of {best}",
44
+ submitError: "Error submitting score"
45
+ }
46
+ },
47
+ guess: {
48
+ title: "AI's Guess",
49
+ goalDescription: "Your goal was to describe the word",
50
+ providedDescription: "You provided the description",
51
+ aiGuessedDescription: "Based on your description, the AI guessed",
52
+ correct: "This is right!",
53
+ incorrect: "This is wrong.",
54
+ nextRound: "Next Round",
55
+ playAgain: "Play Again",
56
+ viewLeaderboard: "Save your score",
57
+ cheatingDetected: "Cheating detected!"
58
+ },
59
+ themes: {
60
+ title: "Choose a Theme",
61
+ subtitle: "Select a theme for the word the AI will try to guess",
62
+ standard: "Standard",
63
+ technology: "Technology",
64
+ sports: "Sports",
65
+ food: "Food",
66
+ custom: "Custom Theme",
67
+ customPlaceholder: "Enter your custom theme...",
68
+ continue: "Continue",
69
+ generating: "Generating...",
70
+ pressKey: "Press"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  },
72
+ welcome: {
73
+ title: "Think in Sync",
74
+ subtitle: "Team up with AI to craft a clue and have a different AI guess your secret word!",
75
+ startButton: "Start Game",
76
+ howToPlay: "How to Play",
77
+ leaderboard: "Leaderboard",
78
+ credits: "Created during the",
79
+ helpWin: "Help us win by",
80
+ onHuggingface: "Liking on Huggingface"
81
  },
82
+ howToPlay: {
83
+ setup: {
84
+ title: "Setup",
85
+ description: "Choose a theme and get a secret word that the AI will try to guess."
86
+ },
87
+ goal: {
88
+ title: "Goal",
89
+ description: "Build sentences together with the AI that describe your word without using it directly."
90
+ },
91
+ rules: {
92
+ title: "Rules",
93
+ items: [
94
+ "Take turns adding words to build descriptive sentences",
95
+ "Don't use the secret word or its variations",
96
+ "Try to be creative and descriptive",
97
+ "The AI will try to guess your word after each sentence"
98
+ ]
99
+ }
100
  }
 
101
  };
 
src/i18n/translations/es.ts CHANGED
@@ -12,88 +12,90 @@ export const es = {
12
  invalidWord: "Palabra inválida",
13
  cantUseTargetWord: "No puedes usar la palabra objetivo",
14
  lettersOnly: "Por favor, usa solo letras",
 
15
  leaveGameTitle: "¿Salir del juego?",
16
  leaveGameDescription: "Tu progreso actual se perderá. ¿Estás seguro de que quieres salir?",
17
  cancel: "Cancelar",
18
  confirm: "Confirmar",
19
  describeWord: "Tu objetivo es describir la palabra"
20
  },
21
- leaderboard: {
22
- title: "Puntuaciones Más Altas",
23
- yourScore: "Tu Puntuación",
24
- roundCount: "rondas",
25
- wordsPerRound: "palabras por ronda",
26
- enterName: "Ingresa tu nombre",
27
- submitting: "Enviando...",
28
- submit: "Enviar Puntuación",
29
- rank: "Posición",
30
- player: "Jugador",
31
- roundsColumn: "Rondas",
32
- avgWords: "Prom. Palabras",
33
- noScores: "Aún no hay puntuaciones",
34
- previous: "Anterior",
35
- next: "Siguiente",
36
- error: {
37
- invalidName: "Por favor, ingresa un nombre válido",
38
- noRounds: "Debes completar al menos una ronda",
39
- alreadySubmitted: "Puntuación ya enviada",
40
- newHighScore: "¡Nueva Puntuación Más Alta!",
41
- beatRecord: "¡Has superado tu récord anterior de {score}!",
42
- notHigher: "Puntuación de {current} no superior a tu mejor de {best}",
43
- submitError: "Error al enviar la puntuación"
44
- }
45
- },
46
- guess: {
47
- title: "Suposición de la IA",
48
- goalDescription: "Tu objetivo era describir la palabra",
49
- providedDescription: "Proporcionaste la descripción",
50
- aiGuessedDescription: "Basado en tu descripción, la IA adivinó",
51
- correct: "¡Esto es correcto!",
52
- incorrect: "Esto es incorrecto.",
53
- nextRound: "Siguiente Ronda",
54
- playAgain: "Jugar de Nuevo",
55
- viewLeaderboard: "Ver Clasificación"
56
- },
57
- themes: {
58
- title: "Elige un Tema",
59
- subtitle: "Selecciona un tema para la palabra que la IA intentará adivinar",
60
- standard: "Estándar",
61
- technology: "Tecnología",
62
- sports: "Deportes",
63
- food: "Comida",
64
- custom: "Tema Personalizado",
65
- customPlaceholder: "Ingresa tu tema personalizado...",
66
- continue: "Continuar",
67
- generating: "Generando...",
68
- pressKey: "Presiona"
69
- },
70
- welcome: {
71
- title: "Think in Sync",
72
- subtitle: "¡Construye frases juntos y deja que la IA adivine tu palabra!",
73
- startButton: "Comenzar Juego",
74
- howToPlay: "Cómo Jugar",
75
- leaderboard: "Clasificación",
76
- credits: "Creado durante el",
77
- helpWin: "Ayúdanos a ganar",
78
- onHuggingface: "Dando me gusta en Huggingface"
79
- },
80
- howToPlay: {
81
- setup: {
82
- title: "Preparación",
83
- description: "Elige un tema y obtén una palabra secreta que la IA intentará adivinar."
84
  },
85
- goal: {
86
- title: "Objetivo",
87
- description: "Construye frases junto con la IA que describan tu palabra sin usarla directamente."
 
 
 
 
 
 
88
  },
89
- rules: {
90
- title: "Reglas",
91
- items: [
92
- "Añade palabras por turnos para construir frases descriptivas",
93
- "No uses la palabra secreta o sus variaciones",
94
- "Sé creativo y descriptivo",
95
- "La IA intentará adivinar tu palabra después de cada frase"
96
- ]
 
 
 
 
 
 
 
 
 
 
97
  }
98
- }
99
  };
 
12
  invalidWord: "Palabra inválida",
13
  cantUseTargetWord: "No puedes usar la palabra objetivo",
14
  lettersOnly: "Por favor, usa solo letras",
15
+ singleWordOnly: "Por favor, ingresa solo una palabra",
16
  leaveGameTitle: "¿Salir del juego?",
17
  leaveGameDescription: "Tu progreso actual se perderá. ¿Estás seguro de que quieres salir?",
18
  cancel: "Cancelar",
19
  confirm: "Confirmar",
20
  describeWord: "Tu objetivo es describir la palabra"
21
  },
22
+ leaderboard: {
23
+ title: "Puntuaciones Más Altas",
24
+ yourScore: "Tu Puntuación",
25
+ roundCount: "rondas",
26
+ wordsPerRound: "palabras por ronda",
27
+ enterName: "Ingresa tu nombre",
28
+ submitting: "Enviando...",
29
+ submit: "Enviar Puntuación",
30
+ rank: "Posición",
31
+ player: "Jugador",
32
+ roundsColumn: "Rondas",
33
+ avgWords: "Prom. Palabras",
34
+ noScores: "Aún no hay puntuaciones",
35
+ previous: "Anterior",
36
+ next: "Siguiente",
37
+ error: {
38
+ invalidName: "Por favor, ingresa un nombre válido",
39
+ noRounds: "Debes completar al menos una ronda",
40
+ alreadySubmitted: "Puntuación ya enviada",
41
+ newHighScore: "¡Nueva Puntuación Más Alta!",
42
+ beatRecord: "¡Has superado tu récord anterior de {score}!",
43
+ notHigher: "Puntuación de {current} no superior a tu mejor de {best}",
44
+ submitError: "Error al enviar la puntuación"
45
+ }
46
+ },
47
+ guess: {
48
+ title: "Suposición de la IA",
49
+ goalDescription: "Tu objetivo era describir la palabra",
50
+ providedDescription: "Proporcionaste la descripción",
51
+ aiGuessedDescription: "Basado en tu descripción, la IA adivinó",
52
+ correct: "¡Esto es correcto!",
53
+ incorrect: "Esto es incorrecto.",
54
+ nextRound: "Siguiente Ronda",
55
+ playAgain: "Jugar de Nuevo",
56
+ viewLeaderboard: "Ver Clasificación",
57
+ cheatingDetected: "¡Trampa detectada!"
58
+ },
59
+ themes: {
60
+ title: "Elige un Tema",
61
+ subtitle: "Selecciona un tema para la palabra que la IA intentará adivinar",
62
+ standard: "Estándar",
63
+ technology: "Tecnología",
64
+ sports: "Deportes",
65
+ food: "Comida",
66
+ custom: "Tema Personalizado",
67
+ customPlaceholder: "Ingresa tu tema personalizado...",
68
+ continue: "Continuar",
69
+ generating: "Generando...",
70
+ pressKey: "Presiona"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  },
72
+ welcome: {
73
+ title: "Think in Sync",
74
+ subtitle: "¡Haz equipo con una IA para crear una pista y deja que otra IA adivine tu palabra secreta!",
75
+ startButton: "Comenzar Juego",
76
+ howToPlay: "Cómo Jugar",
77
+ leaderboard: "Clasificación",
78
+ credits: "Creado durante el",
79
+ helpWin: "Ayúdanos a ganar",
80
+ onHuggingface: "Dando me gusta en Huggingface"
81
  },
82
+ howToPlay: {
83
+ setup: {
84
+ title: "Preparación",
85
+ description: "Elige un tema y obtén una palabra secreta que la IA intentará adivinar."
86
+ },
87
+ goal: {
88
+ title: "Objetivo",
89
+ description: "Construye frases junto con la IA que describan tu palabra sin usarla directamente."
90
+ },
91
+ rules: {
92
+ title: "Reglas",
93
+ items: [
94
+ "Añade palabras por turnos para construir frases descriptivas",
95
+ "No uses la palabra secreta o sus variaciones",
96
+ "Sé creativo y descriptivo",
97
+ "La IA intentará adivinar tu palabra después de cada frase"
98
+ ]
99
+ }
100
  }
 
101
  };
src/i18n/translations/fr.ts CHANGED
@@ -11,6 +11,7 @@ export const fr = {
11
  invalidWord: "Mot invalide",
12
  cantUseTargetWord: "Vous ne pouvez pas utiliser le mot cible",
13
  lettersOnly: "Veuillez utiliser uniquement des lettres",
 
14
  leaveGameTitle: "Quitter le jeu ?",
15
  leaveGameDescription: "Votre progression actuelle sera perdue. Êtes-vous sûr de vouloir quitter ?",
16
  cancel: "Annuler",
@@ -51,7 +52,8 @@ export const fr = {
51
  incorrect: "C'est incorrect.",
52
  nextRound: "Tour Suivant",
53
  playAgain: "Rejouer",
54
- viewLeaderboard: "Voir les Scores"
 
55
  },
56
  themes: {
57
  title: "Choisissez un Thème",
@@ -68,7 +70,7 @@ export const fr = {
68
  },
69
  welcome: {
70
  title: "Think in Sync",
71
- subtitle: "Construisez des phrases ensemble et laissez l'IA deviner votre mot !",
72
  startButton: "Commencer",
73
  howToPlay: "Comment Jouer",
74
  leaderboard: "Classement",
@@ -95,4 +97,4 @@ export const fr = {
95
  ]
96
  }
97
  }
98
- };
 
11
  invalidWord: "Mot invalide",
12
  cantUseTargetWord: "Vous ne pouvez pas utiliser le mot cible",
13
  lettersOnly: "Veuillez utiliser uniquement des lettres",
14
+ singleWordOnly: "Veuillez entrer un seul mot",
15
  leaveGameTitle: "Quitter le jeu ?",
16
  leaveGameDescription: "Votre progression actuelle sera perdue. Êtes-vous sûr de vouloir quitter ?",
17
  cancel: "Annuler",
 
52
  incorrect: "C'est incorrect.",
53
  nextRound: "Tour Suivant",
54
  playAgain: "Rejouer",
55
+ viewLeaderboard: "Voir les Scores",
56
+ cheatingDetected: "Tentative de triche détectée !"
57
  },
58
  themes: {
59
  title: "Choisissez un Thème",
 
70
  },
71
  welcome: {
72
  title: "Think in Sync",
73
+ subtitle: "Faites équipe avec une IA pour créer un indice et laissez une autre IA deviner votre mot secret",
74
  startButton: "Commencer",
75
  howToPlay: "Comment Jouer",
76
  leaderboard: "Classement",
 
97
  ]
98
  }
99
  }
100
+ };
src/i18n/translations/it.ts CHANGED
@@ -12,6 +12,7 @@ export const it = {
12
  invalidWord: "Parola non valida",
13
  cantUseTargetWord: "Non puoi usare la parola obiettivo",
14
  lettersOnly: "Usa solo lettere",
 
15
  leaveGameTitle: "Lasciare il gioco?",
16
  leaveGameDescription: "I tuoi progressi attuali andranno persi. Sei sicuro di voler uscire?",
17
  cancel: "Annulla",
@@ -54,7 +55,8 @@ export const it = {
54
  incorrect: "Sbagliato. Riprova!",
55
  nextRound: "Prossimo Turno",
56
  playAgain: "Gioca Ancora",
57
- viewLeaderboard: "Vedi Classifica"
 
58
  },
59
  themes: {
60
  title: "Scegli un Tema",
@@ -71,7 +73,7 @@ export const it = {
71
  },
72
  welcome: {
73
  title: "Think in Sync",
74
- subtitle: "Costruisci frasi insieme e lascia che l'IA indovini la tua parola!",
75
  startButton: "Inizia Gioco",
76
  howToPlay: "Come Giocare",
77
  leaderboard: "Classifica",
@@ -98,4 +100,4 @@ export const it = {
98
  ]
99
  }
100
  }
101
- };
 
12
  invalidWord: "Parola non valida",
13
  cantUseTargetWord: "Non puoi usare la parola obiettivo",
14
  lettersOnly: "Usa solo lettere",
15
+ singleWordOnly: "Inserisci una sola parola",
16
  leaveGameTitle: "Lasciare il gioco?",
17
  leaveGameDescription: "I tuoi progressi attuali andranno persi. Sei sicuro di voler uscire?",
18
  cancel: "Annulla",
 
55
  incorrect: "Sbagliato. Riprova!",
56
  nextRound: "Prossimo Turno",
57
  playAgain: "Gioca Ancora",
58
+ viewLeaderboard: "Vedi Classifica",
59
+ cheatingDetected: "Tentativo di imbroglio rilevato!"
60
  },
61
  themes: {
62
  title: "Scegli un Tema",
 
73
  },
74
  welcome: {
75
  title: "Think in Sync",
76
+ subtitle: "Collabora con un'IA per creare un indizio e lascia che un'altra IA indovini la tua parola segreta!",
77
  startButton: "Inizia Gioco",
78
  howToPlay: "Come Giocare",
79
  leaderboard: "Classifica",
 
100
  ]
101
  }
102
  }
103
+ };
src/integrations/supabase/types.ts CHANGED
@@ -9,6 +9,33 @@ export type Json =
9
  export type Database = {
10
  public: {
11
  Tables: {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  high_scores: {
13
  Row: {
14
  avg_words_per_round: number
 
9
  export type Database = {
10
  public: {
11
  Tables: {
12
+ game_results: {
13
+ Row: {
14
+ ai_guess: string
15
+ created_at: string
16
+ description: string
17
+ id: string
18
+ is_correct: boolean
19
+ target_word: string
20
+ }
21
+ Insert: {
22
+ ai_guess: string
23
+ created_at?: string
24
+ description: string
25
+ id?: string
26
+ is_correct: boolean
27
+ target_word: string
28
+ }
29
+ Update: {
30
+ ai_guess?: string
31
+ created_at?: string
32
+ description?: string
33
+ id?: string
34
+ is_correct?: boolean
35
+ target_word?: string
36
+ }
37
+ Relationships: []
38
+ }
39
  high_scores: {
40
  Row: {
41
  avg_words_per_round: number
src/lib/words.ts CHANGED
@@ -1,104 +1,106 @@
1
  export const words = [
2
- "ELEPHANT",
3
- "SUNSHINE",
4
- "RAINBOW",
 
 
 
 
 
 
 
 
 
 
 
5
  "BUTTERFLY",
6
- "MOUNTAIN",
7
  "OCEAN",
8
- "GALAXY",
9
- "WHISPER",
10
- "BREEZE",
11
- "DIAMOND",
12
- "STARDUST",
13
- "MEADOW",
14
- "FLOWER",
15
- "HORIZON",
16
- "JOURNEY",
17
- "FEATHER",
18
- "TWILIGHT",
19
- "CANYON",
20
- "WONDER",
21
  "FOREST",
 
 
 
 
 
 
 
22
  "THUNDER",
23
- "LIGHTNING",
24
- "LANTERN",
25
- "CLOUD",
26
- "CRYSTAL",
27
- "HARMONY",
28
- "MIRACLE",
29
- "SHADOW",
30
- "TREASURE",
31
- "ECHO",
32
- "SNOWFLAKE",
33
- "COMET",
34
- "WILDERNESS",
35
- "FREEDOM",
36
- "NIGHT",
37
- "FANTASY",
38
- "ADVENTURE",
39
- "SERENITY",
 
 
 
 
 
40
  "DREAM",
41
- "GLOW",
42
- "RIVER",
43
- "VISTA",
 
44
  "WISH",
45
- "PEBBLE",
46
- "MOONLIGHT",
47
- "HEARTBEAT",
48
- "WANDERLUST",
49
- "STARLIGHT",
50
- "CRESCENT",
51
- "WATERFALL",
52
- "CITADEL",
53
- "AURORA",
54
- "BLISS",
55
- "CASCADE",
56
- "DAWN",
57
- "ECLIPSE",
58
- "FIRELIGHT",
59
- "GARDEN",
60
- "HAVEN",
61
- "INFINITY",
62
- "JUBILEE",
63
- "KALEIDOSCOPE",
64
- "LULLABY",
65
- "MARVEL",
66
- "NEBULA",
67
- "OASIS",
68
- "PARADISE",
69
- "QUIETUDE",
70
- "RADIANCE",
71
- "SANCTUARY",
72
- "TRANQUILITY",
73
- "UNIVERSE",
74
- "VELVET",
75
- "WONDERLAND",
76
- "YIELD",
77
- "ABYSS",
78
- "BALANCE",
79
- "CALM",
80
- "DAZZLE",
81
- "EMBRACE",
82
- "FLICKER",
83
- "GLIMMER",
84
- "HALO",
85
- "ILLUMINATE",
86
- "JEWEL",
87
- "KINDLE",
88
- "LUMINOUS",
89
- "MYSTIC",
90
- "NIRVANA",
91
- "OPULENCE",
92
  "PEACE",
93
- "QUIET",
94
- "RAPTURE",
95
- "SERENE",
96
- "TRANQUIL",
97
- "UNITY",
98
- "VISION",
99
- "WONDERMENT",
100
- "YEARNING",
101
- "ZEAL"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  ];
103
 
104
  export const getRandomWord = () => {
 
1
  export const words = [
2
+ "DOG",
3
+ "CAT",
4
+ "SUN",
5
+ "RAIN",
6
+ "TREE",
7
+ "STAR",
8
+ "MOON",
9
+ "FISH",
10
+ "BIRD",
11
+ "CLOUD",
12
+ "SKY",
13
+ "WIND",
14
+ "SNOW",
15
+ "FLOWER",
16
  "BUTTERFLY",
17
+ "WATER",
18
  "OCEAN",
19
+ "RIVER",
20
+ "MOUNTAIN",
 
 
 
 
 
 
 
 
 
 
 
21
  "FOREST",
22
+ "HOUSE",
23
+ "CANDLE",
24
+ "GARDEN",
25
+ "BRIDGE",
26
+ "ISLAND",
27
+ "BREEZE",
28
+ "LIGHT",
29
  "THUNDER",
30
+ "RAINBOW",
31
+ "SMILE",
32
+ "FRIEND",
33
+ "FAMILY",
34
+ "APPLE",
35
+ "BANANA",
36
+ "CAR",
37
+ "BOAT",
38
+ "BALL",
39
+ "CAKE",
40
+ "FROG",
41
+ "HORSE",
42
+ "LION",
43
+ "MONKEY",
44
+ "PANDA",
45
+ "PLANE",
46
+ "TRAIN",
47
+ "CANDY",
48
+ "JUMP",
49
+ "PLAY",
50
+ "SLEEP",
51
+ "LAUGH",
52
  "DREAM",
53
+ "HAPPY",
54
+ "FUN",
55
+ "COLOR",
56
+ "BRIGHT",
57
  "WISH",
58
+ "LOVE",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  "PEACE",
60
+ "HUG",
61
+ "KISS",
62
+ "ZOO",
63
+ "PARK",
64
+ "BEACH",
65
+ "TOY",
66
+ "BOOK",
67
+ "BUBBLE",
68
+ "SHELL",
69
+ "PEN",
70
+ "ICE",
71
+ "CAKE",
72
+ "HAT",
73
+ "SHOE",
74
+ "CLOCK",
75
+ "BED",
76
+ "CUP",
77
+ "KEY",
78
+ "DOOR",
79
+ "CHICKEN",
80
+ "DUCK",
81
+ "SHEEP",
82
+ "COW",
83
+ "PIG",
84
+ "GOAT",
85
+ "FOX",
86
+ "BEAR",
87
+ "DEER",
88
+ "OWL",
89
+ "EGG",
90
+ "NEST",
91
+ "ROCK",
92
+ "LEAF",
93
+ "BRUSH",
94
+ "TOOTH",
95
+ "HAND",
96
+ "FEET",
97
+ "EYE",
98
+ "NOSE",
99
+ "EAR",
100
+ "MOUTH",
101
+ "CHILD",
102
+ "KITE",
103
+ "BALLON"
104
  ];
105
 
106
  export const getRandomWord = () => {
src/services/mistralService.ts CHANGED
@@ -29,6 +29,34 @@ export const generateAIResponse = async (currentWord: string, currentSentence: s
29
  };
30
 
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
33
 
34
  const { data, error } = await supabase.functions.invoke('guess-word', {
 
29
  };
30
 
31
  export const guessWord = async (sentence: string, language: string): Promise<string> => {
32
+ console.log('Processing guess for sentence:', sentence);
33
+
34
+ // Check for potential fraud if the sentence has less than 3 words
35
+ const words = sentence.trim().split(/\s+/);
36
+ if (words.length < 3) {
37
+ console.log('Short description detected, checking for fraud...');
38
+
39
+ try {
40
+ const { data: fraudData, error: fraudError } = await supabase.functions.invoke('detect-fraud', {
41
+ body: {
42
+ sentence,
43
+ targetWord: words[0], // First word is usually the target in cheating attempts
44
+ language
45
+ }
46
+ });
47
+
48
+ if (fraudError) throw fraudError;
49
+
50
+ if (fraudData?.verdict === 'cheating') {
51
+ console.log('Fraud detected!');
52
+ return 'CHEATING';
53
+ }
54
+ } catch (error) {
55
+ console.error('Error in fraud detection:', error);
56
+ // Continue with normal guessing if fraud detection fails
57
+ }
58
+ }
59
+
60
  console.log('Calling guess-word function with sentence:', sentence, 'language:', language);
61
 
62
  const { data, error } = await supabase.functions.invoke('guess-word', {
supabase/functions/detect-fraud/index.ts ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ serve(async (req) => {
10
+ // Handle CORS preflight requests
11
+ if (req.method === 'OPTIONS') {
12
+ return new Response(null, { headers: corsHeaders });
13
+ }
14
+
15
+ try {
16
+ const { sentence, targetWord, language } = await req.json();
17
+ console.log('Checking for fraud:', { sentence, targetWord, language });
18
+
19
+ const client = new Mistral({
20
+ apiKey: Deno.env.get('MISTRAL_API_KEY'),
21
+ });
22
+
23
+ const maxRetries = 3;
24
+ let retryCount = 0;
25
+ let lastError = null;
26
+
27
+ while (retryCount < maxRetries) {
28
+ try {
29
+ const response = await client.chat.complete({
30
+ model: "mistral-large-latest",
31
+ messages: [
32
+ {
33
+ role: "system",
34
+ content: `You are a fraud detection system for a word guessing game.
35
+ The game is being played in ${language}.
36
+ Your task is to detect if a player is trying to cheat by:
37
+ 1. Using a misspelling of the target word
38
+ 2. Writing a sentence without spaces to bypass word count checks
39
+
40
+ Examples for cheating:
41
+
42
+ Target word: hand
43
+ Player's description: hnd
44
+ Language: en
45
+ CORRECT ANSWER: cheating
46
+
47
+ Target word: barfuß
48
+ Player's description: germanwordforbarefoot
49
+ Language: de
50
+ CORRECT ANSWER: cheating
51
+
52
+ Synonyms and names of instances of a class are legitimate descriptions.
53
+
54
+ Target word: laptop
55
+ Player's description: notebook
56
+ Language: en
57
+ CORRECT ANSWER: legitimate
58
+
59
+ Target word: play
60
+ Player's description: children often
61
+ Language: en
62
+ CORRECT ANSWER: legitimate
63
+
64
+ Target word: Pfankuchen
65
+ Player's description: Berliner
66
+ Language: de
67
+ CORRECT ANSWER: legitimate
68
+
69
+ Respond with ONLY "cheating" or "legitimate" (no punctuation or explanation).`
70
+ },
71
+ {
72
+ role: "user",
73
+ content: `Target word: "${targetWord}"
74
+ Player's description: "${sentence}"
75
+ Language: ${language}
76
+
77
+ Is this a legitimate description or an attempt to cheat?`
78
+ }
79
+ ],
80
+ maxTokens: 20,
81
+ temperature: 0.1
82
+ });
83
+
84
+ const verdict = response.choices[0].message.content.trim().toLowerCase();
85
+ console.log('Fraud detection verdict:', verdict);
86
+
87
+ return new Response(
88
+ JSON.stringify({ verdict }),
89
+ { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
90
+ );
91
+ } catch (error) {
92
+ console.error(`Attempt ${retryCount + 1} failed:`, error);
93
+ lastError = error;
94
+
95
+ if (error.message?.includes('rate limit') || error.status === 429) {
96
+ const waitTime = Math.pow(2, retryCount) * 1000;
97
+ console.log(`Rate limit hit, waiting ${waitTime}ms before retry`);
98
+ await new Promise(resolve => setTimeout(resolve, waitTime));
99
+ retryCount++;
100
+ continue;
101
+ }
102
+
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ throw new Error(`Failed after ${maxRetries} attempts. Last error: ${lastError?.message}`);
108
+
109
+ } catch (error) {
110
+ console.error('Error in fraud detection:', error);
111
+
112
+ const errorMessage = error.message?.includes('rate limit')
113
+ ? "The AI service is currently busy. Please try again in a few moments."
114
+ : "Sorry, there was an error checking for fraud. Please try again.";
115
+
116
+ return new Response(
117
+ JSON.stringify({
118
+ error: errorMessage,
119
+ details: error.message
120
+ }),
121
+ {
122
+ status: error.message?.includes('rate limit') ? 429 : 500,
123
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' }
124
+ }
125
+ );
126
+ }
127
+ });
supabase/functions/guess-word/index.ts CHANGED
@@ -8,28 +8,29 @@ const corsHeaders = {
8
 
9
  const languagePrompts = {
10
  en: {
11
- systemPrompt: "You are playing a word guessing game in English. Given a descriptive sentence, your task is to guess the single word being described.",
12
- instruction: "Based on this description, what single word is being described:"
13
  },
14
  fr: {
15
- systemPrompt: "Vous jouez à un jeu de devinettes de mots en français. À partir d'une phrase descriptive, votre tâche est de deviner le mot unique décrit.",
16
- instruction: "D'après cette description, quel mot unique est décrit :"
17
  },
18
  de: {
19
- systemPrompt: "Sie spielen ein Worträtselspiel auf Deutsch. Anhand eines beschreibenden Satzes ist es Ihre Aufgabe, das beschriebene einzelne Wort zu erraten.",
20
- instruction: "Welches einzelne Wort wird basierend auf dieser Beschreibung beschrieben:"
21
  },
22
  it: {
23
- systemPrompt: "Stai giocando a un gioco di indovinelli in italiano. Data una frase descrittiva, il tuo compito è indovinare la singola parola descritta.",
24
- instruction: "In base a questa descrizione, quale singola parola viene descritta:"
25
  },
26
  es: {
27
- systemPrompt: "Estás jugando a un juego de adivinanzas de palabras en español. Dada una frase descriptiva, tu tarea es adivinar la única palabra que se describe.",
28
- instruction: "Según esta descripción, ¿qué palabra única se está describiendo:"
29
  }
30
  };
31
 
32
  serve(async (req) => {
 
33
  if (req.method === 'OPTIONS') {
34
  return new Response(null, { headers: corsHeaders });
35
  }
 
8
 
9
  const languagePrompts = {
10
  en: {
11
+ systemPrompt: "You are helping in a word guessing game. Given a description, guess what single word is being described.",
12
+ instruction: "Based on this description"
13
  },
14
  fr: {
15
+ systemPrompt: "Vous aidez dans un jeu de devinettes. À partir d'une description, devinez le mot unique qui est décrit.",
16
+ instruction: "D'après cette description"
17
  },
18
  de: {
19
+ systemPrompt: "Sie helfen bei einem Worträtsel. Erraten Sie anhand einer Beschreibung, welches einzelne Wort beschrieben wird.",
20
+ instruction: "Basierend auf dieser Beschreibung"
21
  },
22
  it: {
23
+ systemPrompt: "Stai aiutando in un gioco di indovinelli. Data una descrizione, indovina quale singola parola viene descritta.",
24
+ instruction: "Basandoti su questa descrizione"
25
  },
26
  es: {
27
+ systemPrompt: "Estás ayudando en un juego de adivinanzas. Dada una descripción, adivina qué palabra única se está describiendo.",
28
+ instruction: "Basándote en esta descripción"
29
  }
30
  };
31
 
32
  serve(async (req) => {
33
+ // Handle CORS preflight requests
34
  if (req.method === 'OPTIONS') {
35
  return new Response(null, { headers: corsHeaders });
36
  }