ChingCL commited on
Commit
61e3aa1
·
verified ·
1 Parent(s): 23604b3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -0
app.py CHANGED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ // 完整的成語資料庫(50組,之後擴充到200組)
4
+ const idiomDatabase = [
5
+ { mainIdiom: '急如星火', synonyms: ['迫在眉睫', '刻不容緩'], meaning: '形容事情非常緊急' },
6
+ { mainIdiom: '一葉知秋', synonyms: ['見微知著', '月暈而風'], meaning: '從小徵兆可以預見未來的發展' },
7
+ { mainIdiom: '一丘之貉', synonyms: ['沆瀣一氣', '狐群狗黨'], meaning: '形容品性相同的壞人' },
8
+ { mainIdiom: '一蹴可幾', synonyms: ['一步登天', '唾手可得'], meaning: '一下子就能達到目的' },
9
+ { mainIdiom: '一暴十寒', synonyms: ['兩天打魚,三天曬網', '虎頭蛇尾'], meaning: '形容學習或做事不能持之以恆' },
10
+ { mainIdiom: '環堵蕭然', synonyms: ['一貧如洗', '家徒四壁'], meaning: '形容生活很貧困' },
11
+ { mainIdiom: '一字千金', synonyms: ['字字珠璣', '文不加點'], meaning: '形容文章寫得好,文字珍貴' },
12
+ { mainIdiom: '一擲千金', synonyms: ['揮金如土', '揮霍無度'], meaning: '形容揮霍錢財' },
13
+ { mainIdiom: '生靈塗炭', synonyms: ['民不聊生', '民生凋敝'], meaning: '形容人民生活困苦' },
14
+ { mainIdiom: '瓜熟蒂落', synonyms: ['水到渠成', '順理成章'], meaning: '形容事情自然成功' }
15
+ ].map((item, index) => ({ ...item, id: index + 1 }));
16
+
17
+ export default function ChineseIdiomGame() {
18
+ const [currentQuestion, setCurrentQuestion] = useState(null);
19
+ const [options, setOptions] = useState([]);
20
+ const [selectedAnswers, setSelectedAnswers] = useState([]);
21
+ const [score, setScore] = useState(0);
22
+ const [message, setMessage] = useState('');
23
+ const [errorMessage, setErrorMessage] = useState('');
24
+ const [gameComplete, setGameComplete] = useState(false);
25
+ const [incorrectOption, setIncorrectOption] = useState(null);
26
+ const [availableQuestions, setAvailableQuestions] = useState([...idiomDatabase]);
27
+
28
+ const shuffleArray = (array) => {
29
+ return [...array].sort(() => Math.random() - 0.5);
30
+ };
31
+
32
+ const getRandomOptions = (currentQ, count) => {
33
+ const otherOptions = idiomDatabase
34
+ .filter(item => item.id !== currentQ.id)
35
+ .flatMap(item => item.synonyms)
36
+ .filter(opt => !currentQ.synonyms.includes(opt));
37
+
38
+ return shuffleArray(otherOptions).slice(0, count);
39
+ };
40
+
41
+ const startNewQuestion = () => {
42
+ if (availableQuestions.length === 0) {
43
+ setAvailableQuestions([...idiomDatabase]);
44
+ }
45
+
46
+ const randomIndex = Math.floor(Math.random() * availableQuestions.length);
47
+ const newQuestion = availableQuestions[randomIndex];
48
+
49
+ setAvailableQuestions(prev => prev.filter(q => q.id !== newQuestion.id));
50
+
51
+ const allOptions = [
52
+ ...newQuestion.synonyms,
53
+ ...getRandomOptions(newQuestion, 8)
54
+ ].sort(() => Math.random() - 0.5);
55
+
56
+ setCurrentQuestion(newQuestion);
57
+ setOptions(allOptions);
58
+ setSelectedAnswers([]);
59
+ setMessage('');
60
+ setErrorMessage('');
61
+ };
62
+
63
+ const initializeGame = () => {
64
+ setScore(0);
65
+ setGameComplete(false);
66
+ setAvailableQuestions([...idiomDatabase]);
67
+ startNewQuestion();
68
+ };
69
+
70
+ useEffect(() => {
71
+ initializeGame();
72
+ }, []);
73
+
74
+ const handleOptionSelect = (option) => {
75
+ if (selectedAnswers.includes(option)) return;
76
+
77
+ if (currentQuestion.synonyms.includes(option)) {
78
+ const newSelected = [...selectedAnswers, option];
79
+ setSelectedAnswers(newSelected);
80
+ setErrorMessage('');
81
+
82
+ if (newSelected.length === 1) {
83
+ setScore(prev => prev + 5);
84
+ setMessage('答對了!還有一個答案');
85
+ } else if (newSelected.length === 2) {
86
+ const newScore = score + 5;
87
+ setScore(newScore);
88
+
89
+ if (newScore >= 100) {
90
+ setGameComplete(true);
91
+ } else {
92
+ setMessage('太棒了!準備下一題...');
93
+ setTimeout(startNewQuestion, 1500);
94
+ }
95
+ }
96
+ } else {
97
+ setIncorrectOption(option);
98
+ setErrorMessage('答錯了,再試試看!');
99
+ setTimeout(() => {
100
+ setIncorrectOption(null);
101
+ setErrorMessage('');
102
+ }, 1000);
103
+ }
104
+ };
105
+
106
+ if (gameComplete) {
107
+ return (
108
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
109
+ <div className="bg-white rounded-lg p-8 max-w-xl w-full mx-4 flex flex-col items-center">
110
+ <h2 className="text-3xl font-bold text-green-600 mb-12">
111
+ 太棒了!你是成語小天才!
112
+ </h2>
113
+ <div className="flex flex-col sm:flex-row gap-8 w-full max-w-md">
114
+ <button
115
+ onClick={initializeGame}
116
+ className="bg-[#72BFA3] text-white text-xl font-bold px-8 py-4 rounded-lg hover:bg-green-600 transition-colors w-full"
117
+ >
118
+ 繼續挑戰
119
+ </button>
120
+ <button
121
+ onClick={() => window.close()}
122
+ className="bg-[#FFC54F] text-white text-xl font-bold px-8 py-4 rounded-lg hover:bg-yellow-600 transition-colors w-full"
123
+ >
124
+ 休息一下
125
+ </button>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ return (
133
+ <div className="min-h-screen bg-[#F2F1EF] p-4 flex justify-center">
134
+ <div className="w-full max-w-3xl">
135
+ <div className="bg-white rounded-lg shadow-lg p-4 sm:p-8">
136
+ <h1 className="text-2xl sm:text-3xl font-bold text-center mb-6">成語對對碰</h1>
137
+
138
+ <div className="text-center mb-8">
139
+ <div className="flex flex-wrap justify-center items-center gap-4 mb-3">
140
+ <h2 className="text-xl sm:text-2xl font-bold text-blue-600">
141
+ 題目:{currentQuestion?.mainIdiom}
142
+ </h2>
143
+ {selectedAnswers.map((idiom, index) => (
144
+ <span key={index} className="text-xl sm:text-2xl font-bold text-green-600">
145
+ = {idiom}
146
+ </span>
147
+ ))}
148
+ </div>
149
+ <p className="text-gray-600 mb-4">{currentQuestion?.meaning}</p>
150
+ <p className="text-gray-700 bg-gray-100 p-4 rounded-lg">
151
+ 玩法:這裡有兩個成語是和題目一樣的意思,請你把它找出來!
152
+ </p>
153
+ </div>
154
+
155
+ {message && (
156
+ <div className="text-center mb-6 text-green-600 font-bold">
157
+ {message}
158
+ </div>
159
+ )}
160
+
161
+ {errorMessage && (
162
+ <div className="text-center mb-4 text-red-500 font-bold">
163
+ {errorMessage}
164
+ </div>
165
+ )}
166
+
167
+ <div className="grid grid-cols-2 md:grid-cols-5 gap-2 sm:gap-4 mb-6">
168
+ {options.map((option, index) => (
169
+ <button
170
+ key={`${option}-${index}`}
171
+ onClick={() => handleOptionSelect(option)}
172
+ className={`
173
+ p-2 sm:p-4 rounded-lg text-center transition-all
174
+ ${selectedAnswers.includes(option) ? 'bg-[#72BFA3] text-white' : ''}
175
+ ${incorrectOption === option ? 'bg-[#FFC54F]' : 'bg-white'}
176
+ border-2 border-gray-200
177
+ hover:bg-gray-50
178
+ text-sm sm:text-base
179
+ `}
180
+ disabled={selectedAnswers.includes(option)}
181
+ >
182
+ {option}
183
+ </button>
184
+ ))}
185
+ </div>
186
+
187
+ <div className="mt-4 sm:mt-8">
188
+ <div className="w-full bg-gray-200 rounded-full h-6">
189
+ <div
190
+ className="bg-green-500 h-full rounded-full transition-all duration-500 flex items-center justify-end pr-2"
191
+ style={{ width: `${score}%` }}
192
+ >
193
+ <span className="text-white text-sm font-bold">{score}/100</span>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ );
201
+ }