jiang buqing commited on
Commit
51fe0af
·
1 Parent(s): 8181255

多人共享状态

Browse files
Files changed (3) hide show
  1. app.py +44 -31
  2. static/js/app.js +352 -268
  3. templates/index.html +2 -2
app.py CHANGED
@@ -49,28 +49,46 @@ model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniL
49
  with open("words.txt", "r", encoding="utf-8") as f:
50
  word_pool = [line.strip() for line in f if line.strip()]
51
 
52
- # Game state
53
- game_state = {
54
- "secret_word": "",
55
- "secret_vec": None,
56
- "guesses": [],
57
- "latest_guess": None
58
- }
 
 
 
 
 
 
59
 
60
  @app.route('/')
61
  def home():
62
  return render_template('index.html')
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  @app.route('/new-game', methods=['POST'])
65
  def new_game():
66
  global game_state
67
- # Reset game state
68
- game_state["secret_word"] = random.choice(word_pool)
69
- game_state["secret_vec"] = model.encode(game_state["secret_word"], convert_to_tensor=True)
70
- game_state["guesses"] = []
71
- game_state["latest_guess"] = None
72
-
73
- return jsonify({"status": "success", "message": "New game started"})
74
 
75
  @app.route('/guess', methods=['POST'])
76
  def guess():
@@ -78,7 +96,7 @@ def guess():
78
  guess_word = data.get('guess', '').strip()
79
 
80
  if not guess_word:
81
- return jsonify({"status": "error", "message": "No guess provided"})
82
 
83
  # Encode the guess
84
  guess_vec = model.encode(guess_word, convert_to_tensor=True)
@@ -90,21 +108,17 @@ def guess():
90
  # Check if correct
91
  is_correct = (game_state["secret_word"] == guess_word)
92
 
93
- # Update latest guess
94
- game_state["latest_guess"] = guess_word
95
-
96
- # Store guess
97
- game_state["guesses"].append({
98
- "word": guess_word,
99
- "similarity": percent,
100
- "is_correct": is_correct,
101
- "is_latest": True
102
- })
103
 
104
- # Update previous guesses to not be latest
105
- for i in range(len(game_state["guesses"]) - 1):
106
- if "is_latest" in game_state["guesses"][i]:
107
- game_state["guesses"][i]["is_latest"] = False
 
 
 
 
108
 
109
  # Sort guesses by similarity (descending)
110
  sorted_guesses = sorted(game_state["guesses"], key=lambda x: x["similarity"], reverse=True)
@@ -113,8 +127,7 @@ def guess():
113
  "status": "success",
114
  "similarity": percent,
115
  "is_correct": is_correct,
116
- "guesses": game_state["guesses"],
117
- "latest_guess": guess_word
118
  }
119
 
120
  if is_correct:
 
49
  with open("words.txt", "r", encoding="utf-8") as f:
50
  word_pool = [line.strip() for line in f if line.strip()]
51
 
52
+ # 初始化游戏状态函数
53
+ def initialize_game_state():
54
+ secret_word = random.choice(word_pool)
55
+ secret_vec = model.encode(secret_word, convert_to_tensor=True)
56
+ print(f"新游戏已开始,秘密词语: {secret_word}")
57
+ return {
58
+ "secret_word": secret_word,
59
+ "secret_vec": secret_vec,
60
+ "guesses": []
61
+ }
62
+
63
+ # 服务启动时初始化游戏状态
64
+ game_state = initialize_game_state()
65
 
66
  @app.route('/')
67
  def home():
68
  return render_template('index.html')
69
 
70
+ @app.route('/game-status', methods=['GET'])
71
+ def game_status():
72
+ """获取当前游戏状态,包括猜测历史,但不包含秘密词语"""
73
+ # 对猜测列表按相似度降序排序
74
+ sorted_guesses = sorted(game_state["guesses"], key=lambda x: x["similarity"], reverse=True)
75
+
76
+ # 检查是否有人已经猜对了
77
+ has_correct_guess = any(guess["is_correct"] for guess in game_state["guesses"])
78
+
79
+ return jsonify({
80
+ "status": "success",
81
+ "guesses": sorted_guesses,
82
+ "has_correct_guess": has_correct_guess,
83
+ "is_game_active": True
84
+ })
85
+
86
  @app.route('/new-game', methods=['POST'])
87
  def new_game():
88
  global game_state
89
+ # 重置游戏状态
90
+ game_state = initialize_game_state()
91
+ return jsonify({"status": "success", "message": "新游戏已开始"})
 
 
 
 
92
 
93
  @app.route('/guess', methods=['POST'])
94
  def guess():
 
96
  guess_word = data.get('guess', '').strip()
97
 
98
  if not guess_word:
99
+ return jsonify({"status": "error", "message": "请输入猜测词语"})
100
 
101
  # Encode the guess
102
  guess_vec = model.encode(guess_word, convert_to_tensor=True)
 
108
  # Check if correct
109
  is_correct = (game_state["secret_word"] == guess_word)
110
 
111
+ # 检查猜测的词是否已存在于历史记录中
112
+ word_exists = any(g["word"] == guess_word for g in game_state["guesses"])
 
 
 
 
 
 
 
 
113
 
114
+ # 仅当词语不在历史记录中时,才添加到猜测历史
115
+ if not word_exists:
116
+ # Store guess
117
+ game_state["guesses"].append({
118
+ "word": guess_word,
119
+ "similarity": percent,
120
+ "is_correct": is_correct
121
+ })
122
 
123
  # Sort guesses by similarity (descending)
124
  sorted_guesses = sorted(game_state["guesses"], key=lambda x: x["similarity"], reverse=True)
 
127
  "status": "success",
128
  "similarity": percent,
129
  "is_correct": is_correct,
130
+ "guesses": sorted_guesses
 
131
  }
132
 
133
  if is_correct:
static/js/app.js CHANGED
@@ -1,227 +1,149 @@
1
  // Semantic Hunter Game Frontend
2
 
3
- document.addEventListener('DOMContentLoaded', () => {
4
- // DOM Elements
5
- const guessInput = document.getElementById('guess-input');
6
- const guessBtn = document.getElementById('guess-btn');
7
- const messageArea = document.getElementById('message-area');
8
- const guessesList = document.getElementById('guesses-list');
9
- const newGameBtn = document.getElementById('new-game-btn');
10
- const giveUpBtn = document.getElementById('give-up-btn');
11
-
12
- // Game state
13
- let gameActive = false;
14
- let latestGuessId = null;
15
- let allGuesses = [];
16
- let currentPage = 1;
17
- const itemsPerPage = 7;
18
 
19
- // Initialize game
20
- initGame();
 
 
 
21
 
22
- // Event listeners
23
- guessBtn.addEventListener('click', submitGuess);
24
- guessInput.addEventListener('keypress', (e) => {
25
- if (e.key === 'Enter') submitGuess();
26
- });
27
- newGameBtn.addEventListener('click', initGame);
28
- giveUpBtn.addEventListener('click', giveUp);
29
 
30
- // Functions
31
- function initGame() {
32
- fetch('/new-game', {
33
- method: 'POST',
34
- headers: {
35
- 'Content-Type': 'application/json'
36
- }
37
- })
38
- .then(response => response.json())
39
- .then(data => {
40
- if (data.status === 'success') {
41
- gameActive = true;
42
- guessesList.innerHTML = '';
43
- messageArea.innerHTML = '';
44
- guessInput.value = '';
45
- guessInput.focus();
46
- allGuesses = [];
47
- currentPage = 1;
48
- latestGuessId = null;
49
- messageArea.innerHTML = `<p class="message">游戏开始! 尝试猜测秘密词语!</p>`;
50
- } else {
51
- showError(data.message || '初始化游戏时出错');
52
- }
53
- })
54
- .catch(err => {
55
- showError('无法连接到服务器');
56
- console.error(err);
57
- });
58
  }
59
 
60
- function submitGuess() {
61
- const guess = guessInput.value.trim();
62
- if (!guess) return;
63
- if (!gameActive) {
64
- showError('游戏未开始,请点击新游戏');
65
- return;
66
- }
67
 
68
- // Check if this word has already been guessed, but just proceed silently
69
- const alreadyGuessed = allGuesses.some(g => g.word === guess) ||
70
- (latestGuessId && latestGuessId === guess);
 
71
 
72
- // Always send the request to get similarity, but we'll handle duplicates when updating the UI
73
- fetch('/guess', {
74
- method: 'POST',
75
- headers: {
76
- 'Content-Type': 'application/json'
77
- },
78
- body: JSON.stringify({ guess })
79
- })
80
- .then(response => response.json())
81
- .then(data => {
82
- if (data.status === 'success') {
83
- // If the word was already guessed, just update the latest guess display
84
- // without adding a duplicate to the history
85
- if (alreadyGuessed) {
86
- // Find this guess in our existing data to get its similarity
87
- const existingGuess = allGuesses.find(g => g.word === guess);
88
- if (existingGuess) {
89
- // Show just the latest guess with this word
90
- updateLatestGuess({
91
- word: guess,
92
- similarity: existingGuess.similarity,
93
- is_correct: existingGuess.is_correct
94
- });
95
-
96
- // Show similarity message
97
- showMessage(`相似度: ${existingGuess.similarity}%`);
98
- }
99
- } else {
100
- // Normal flow for new guesses
101
- updateGuessList(data.guesses, guess);
102
-
103
- if (data.is_correct) {
104
- gameActive = false;
105
- showSuccess(`恭喜你猜对了! 秘密词语是: ${guess}`);
106
- } else {
107
- showMessage(`相似度: ${data.similarity}%`);
108
- }
109
- }
110
-
111
- // Always update latest guess ID and clear input
112
- latestGuessId = guess;
113
- guessInput.value = '';
114
- guessInput.focus();
115
- } else {
116
- showError(data.message || '提交猜测时出错');
117
- }
118
- })
119
- .catch(err => {
120
- showError('无法连接到服务器');
121
- console.error(err);
122
- });
123
  }
124
 
125
- function giveUp() {
126
- if (!gameActive) {
127
- showError('游戏未开始,请点击新游戏');
128
- return;
 
 
 
 
 
 
 
 
 
 
129
  }
130
-
131
- fetch('/give-up', {
132
- method: 'POST',
133
- headers: {
134
- 'Content-Type': 'application/json'
135
- }
136
- })
137
- .then(response => response.json())
138
- .then(data => {
139
- if (data.status === 'success') {
140
- gameActive = false;
141
- showError(`你放弃了! 秘密词语是: ${data.secret_word}`);
142
- } else {
143
- showError(data.message || '放弃游戏时出错');
144
- }
145
- })
146
- .catch(err => {
147
- showError('无法连接到服务器');
148
- console.error(err);
149
- });
150
  }
151
 
152
- function updateGuessList(guesses, latestGuess) {
153
- guessesList.innerHTML = '';
154
-
155
- // Create a copy of the guesses array to avoid modifying the original
156
- const sortedGuesses = [...guesses];
157
 
158
- // Find the latest guess and remove it from the sorted array
159
- const latestGuessIndex = sortedGuesses.findIndex(g => g.word === latestGuess);
160
- let latestGuessItem = null;
161
- if (latestGuessIndex !== -1) {
162
- latestGuessItem = sortedGuesses.splice(latestGuessIndex, 1)[0];
163
  }
164
 
165
- // Sort the remaining guesses by similarity (descending)
166
- sortedGuesses.sort((a, b) => b.similarity - a.similarity);
167
 
168
- // Update our allGuesses array for pagination
169
- allGuesses = sortedGuesses;
 
 
 
 
 
 
170
 
171
- // If we have a latest guess, add it to the top of the list
172
- if (latestGuessItem) {
173
- // Create a container for the latest guess
174
- const latestGuessContainer = document.createElement('div');
175
- latestGuessContainer.className = 'latest-guess-container';
176
-
177
- // Add the latest guess with a different style
178
- const latestGuessElement = document.createElement('div');
179
- latestGuessElement.className = 'latest-guess';
180
-
181
- // Add perfect-match class if 100% similarity
182
- if (latestGuessItem.similarity === 100) {
183
- latestGuessElement.classList.add('perfect-match');
184
- }
185
-
186
- // Determine similarity color class
187
- let similarityClass = getSimilarityClass(latestGuessItem.similarity);
188
-
189
- latestGuessElement.innerHTML = `
190
- <span class="guess-word">${latestGuessItem.word}</span>
191
- <span class="guess-similarity ${similarityClass}">${latestGuessItem.similarity}%</span>
192
- `;
193
-
194
- latestGuessContainer.appendChild(latestGuessElement);
195
-
196
- // Add separator line
197
- const separator = document.createElement('div');
198
- separator.className = 'guess-separator';
199
- latestGuessContainer.appendChild(separator);
200
-
201
- guessesList.appendChild(latestGuessContainer);
202
  }
203
 
204
- // Create a container for history guesses
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  const historyContainer = document.createElement('div');
206
  historyContainer.className = 'history-guesses-container';
207
 
208
- // Get current page of items
209
- const startIndex = (currentPage - 1) * itemsPerPage;
210
- const endIndex = Math.min(startIndex + itemsPerPage, allGuesses.length);
211
- const currentPageItems = allGuesses.slice(startIndex, endIndex);
212
 
213
- // Add the current page of guesses
214
  currentPageItems.forEach(guess => {
 
 
 
 
 
215
  const guessItem = document.createElement('div');
216
  guessItem.className = 'guess-item';
217
 
218
- // Add perfect-match class if 100% similarity
219
  if (guess.similarity === 100) {
220
  guessItem.classList.add('perfect-match');
221
  }
222
 
223
- // Determine similarity color class
224
- let similarityClass = getSimilarityClass(guess.similarity);
225
 
226
  guessItem.innerHTML = `
227
  <span class="guess-word">${guess.word}</span>
@@ -231,130 +153,292 @@ document.addEventListener('DOMContentLoaded', () => {
231
  historyContainer.appendChild(guessItem);
232
  });
233
 
234
- guessesList.appendChild(historyContainer);
235
-
236
- // Add pagination if we have more than one page
237
- if (allGuesses.length > itemsPerPage) {
238
- addPagination();
239
- }
240
  }
241
-
242
- function addPagination() {
243
- const totalPages = Math.ceil(allGuesses.length / itemsPerPage);
 
 
244
 
245
- // Create pagination container
246
  const paginationContainer = document.createElement('div');
247
  paginationContainer.className = 'pagination-container';
248
 
249
- // Previous button
250
  const prevBtn = document.createElement('button');
251
  prevBtn.className = 'pagination-btn prev-btn';
252
  prevBtn.innerHTML = `<i class="fas fa-chevron-left"></i>`;
253
  prevBtn.disabled = currentPage === 1;
254
  prevBtn.addEventListener('click', () => {
255
  if (currentPage > 1) {
256
- currentPage--;
257
- updateGuessList(allGuesses.concat([]), ''); // Pass empty string for latestGuess to avoid highlighting
258
  }
259
  });
260
 
261
- // Page indicator
262
  const pageIndicator = document.createElement('div');
263
  pageIndicator.className = 'page-indicator';
264
  pageIndicator.textContent = `${currentPage} / ${totalPages}`;
265
 
266
- // Next button
267
  const nextBtn = document.createElement('button');
268
  nextBtn.className = 'pagination-btn next-btn';
269
  nextBtn.innerHTML = `<i class="fas fa-chevron-right"></i>`;
270
  nextBtn.disabled = currentPage === totalPages;
271
  nextBtn.addEventListener('click', () => {
272
  if (currentPage < totalPages) {
273
- currentPage++;
274
- updateGuessList(allGuesses.concat([]), ''); // Pass empty string for latestGuess to avoid highlighting
275
  }
276
  });
277
 
278
- // Append buttons to container
279
  paginationContainer.appendChild(prevBtn);
280
  paginationContainer.appendChild(pageIndicator);
281
  paginationContainer.appendChild(nextBtn);
282
 
283
- // Add to the DOM
284
- guessesList.appendChild(paginationContainer);
285
  }
 
286
 
287
- function showMessage(msg) {
288
- messageArea.innerHTML = `<p class="message">${msg}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  }
290
 
291
- function showError(msg) {
292
- messageArea.innerHTML = `<p class="message error">${msg}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
 
295
- function showSuccess(msg) {
296
- messageArea.innerHTML = `<p class="message success">${msg}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  }
298
 
299
- // Function to determine similarity class based on percentage
300
- function getSimilarityClass(similarity) {
301
- if (similarity === 100) {
302
- return 'similarity-perfect';
303
- } else if (similarity >= 90) {
304
- return 'similarity-super-high';
305
- } else if (similarity >= 80) {
306
- return 'similarity-very-high';
307
- } else if (similarity >= 70) {
308
- return 'similarity-high';
309
- } else if (similarity >= 50) {
310
- return 'similarity-medium';
311
- } else {
312
- return 'similarity-low';
313
  }
314
  }
 
315
 
316
- // Function to just update the latest guess display without changing history
317
- function updateLatestGuess(guessItem) {
318
- // Remove existing latest guess container if it exists
319
- const existingContainer = document.querySelector('.latest-guess-container');
320
- if (existingContainer) {
321
- existingContainer.remove();
322
- }
323
 
324
- // Create a new container for the latest guess
325
- const latestGuessContainer = document.createElement('div');
326
- latestGuessContainer.className = 'latest-guess-container';
 
 
327
 
328
- // Add the latest guess with its style
329
- const latestGuessElement = document.createElement('div');
330
- latestGuessElement.className = 'latest-guess';
331
 
332
- // Add perfect-match class if 100% similarity
333
- if (guessItem.similarity === 100) {
334
- latestGuessElement.classList.add('perfect-match');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
- // Determine similarity color class
338
- let similarityClass = getSimilarityClass(guessItem.similarity);
339
-
340
- latestGuessElement.innerHTML = `
341
- <span class="guess-word">${guessItem.word}</span>
342
- <span class="guess-similarity ${similarityClass}">${guessItem.similarity}%</span>
343
- `;
344
-
345
- latestGuessContainer.appendChild(latestGuessElement);
346
-
347
- // Add separator line
348
- const separator = document.createElement('div');
349
- separator.className = 'guess-separator';
350
- latestGuessContainer.appendChild(separator);
351
-
352
- // Add to DOM before the history container
353
- const historyContainer = document.querySelector('.history-guesses-container');
354
- if (historyContainer) {
355
- guessesList.insertBefore(latestGuessContainer, historyContainer);
356
- } else {
357
- guessesList.appendChild(latestGuessContainer);
358
  }
359
  }
 
 
 
 
 
360
  });
 
1
  // Semantic Hunter Game Frontend
2
 
3
+ /**
4
+ * UI类 - 负责DOM操作和UI渲染
5
+ */
6
+ class SemanticHunterUI {
7
+ constructor() {
8
+ // DOM元素
9
+ this.guessInput = document.getElementById('guess-input');
10
+ this.guessBtn = document.getElementById('guess-btn');
11
+ this.messageArea = document.getElementById('message-area');
12
+ this.guessesList = document.getElementById('guesses-list');
13
+ this.newGameBtn = document.getElementById('new-game-btn');
14
+ this.giveUpBtn = document.getElementById('give-up-btn');
15
+ this.itemsPerPage = 7;
16
+ }
 
17
 
18
+ // 清空输入框并聚焦
19
+ clearAndFocusInput() {
20
+ this.guessInput.value = '';
21
+ this.guessInput.focus();
22
+ }
23
 
24
+ // 获取输入值
25
+ getInputValue() {
26
+ return this.guessInput.value.trim();
27
+ }
 
 
 
28
 
29
+ // 显示消息
30
+ showMessage(msg) {
31
+ this.messageArea.innerHTML = `<p class="message">${msg}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
+ // 显示错误消息
35
+ showError(msg) {
36
+ this.messageArea.innerHTML = `<p class="message error">${msg}</p>`;
37
+ }
 
 
 
38
 
39
+ // 显示成功消息
40
+ showSuccess(msg) {
41
+ this.messageArea.innerHTML = `<p class="message success">${msg}</p>`;
42
+ }
43
 
44
+ // 重置猜测列表
45
+ resetGuessList() {
46
+ this.guessesList.innerHTML = '';
47
+ this.messageArea.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
+ // 获取相似度样式类
51
+ getSimilarityClass(similarity) {
52
+ if (similarity === 100) {
53
+ return 'similarity-perfect';
54
+ } else if (similarity >= 90) {
55
+ return 'similarity-super-high';
56
+ } else if (similarity >= 80) {
57
+ return 'similarity-very-high';
58
+ } else if (similarity >= 70) {
59
+ return 'similarity-high';
60
+ } else if (similarity >= 50) {
61
+ return 'similarity-medium';
62
+ } else {
63
+ return 'similarity-low';
64
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
 
67
+ // 渲染猜测列表,包括最新猜测和历史猜测
68
+ renderGuessList(guesses, latestGuess, currentPage) {
69
+ this.guessesList.innerHTML = '';
 
 
70
 
71
+ // 渲染最新猜测(如果有)
72
+ if (latestGuess) {
73
+ this.renderLatestGuess(latestGuess, guesses);
 
 
74
  }
75
 
76
+ // 渲染历史猜测列表
77
+ this.renderHistoryGuesses(guesses, latestGuess, currentPage);
78
 
79
+ return guesses;
80
+ }
81
+
82
+ // 渲染最新猜测(置顶显示)
83
+ renderLatestGuess(latestGuess, guesses) {
84
+ // 寻找最新猜测的详细信息
85
+ const latestGuessData = guesses.find(g => g.word === latestGuess.word);
86
+ if (!latestGuessData) return;
87
 
88
+ // 创建最新猜测的容器
89
+ const latestGuessContainer = document.createElement('div');
90
+ latestGuessContainer.className = 'latest-guess-container';
91
+
92
+ // 添加最新猜测并使用不同样式
93
+ const latestGuessElement = document.createElement('div');
94
+ latestGuessElement.className = 'latest-guess';
95
+
96
+ // 如果100%匹配,添加perfect-match类
97
+ if (latestGuessData.similarity === 100) {
98
+ latestGuessElement.classList.add('perfect-match');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
+ // 确定相似度颜色类
102
+ const similarityClass = this.getSimilarityClass(latestGuessData.similarity);
103
+
104
+ latestGuessElement.innerHTML = `
105
+ <span class="guess-word">${latestGuessData.word}</span>
106
+ <span class="guess-similarity ${similarityClass}">${latestGuessData.similarity}%</span>
107
+ `;
108
+
109
+ latestGuessContainer.appendChild(latestGuessElement);
110
+
111
+ // 添加分隔线
112
+ const separator = document.createElement('div');
113
+ separator.className = 'guess-separator';
114
+ latestGuessContainer.appendChild(separator);
115
+
116
+ this.guessesList.appendChild(latestGuessContainer);
117
+ }
118
+
119
+ // 渲染历史猜测列表
120
+ renderHistoryGuesses(guesses, latestGuess, currentPage) {
121
+ // 创建历史猜测的容器
122
  const historyContainer = document.createElement('div');
123
  historyContainer.className = 'history-guesses-container';
124
 
125
+ // 获取当前页的项目
126
+ const startIndex = (currentPage - 1) * this.itemsPerPage;
127
+ const endIndex = Math.min(startIndex + this.itemsPerPage, guesses.length);
128
+ const currentPageItems = guesses.slice(startIndex, endIndex);
129
 
130
+ // 添加当前页的猜测
131
  currentPageItems.forEach(guess => {
132
+ // 如果有最新猜测且与当前历史猜测相同,则跳过(避免重复显示)
133
+ if (latestGuess && guess.word === latestGuess.word) {
134
+ return;
135
+ }
136
+
137
  const guessItem = document.createElement('div');
138
  guessItem.className = 'guess-item';
139
 
140
+ // 如果100%匹配,添加perfect-match
141
  if (guess.similarity === 100) {
142
  guessItem.classList.add('perfect-match');
143
  }
144
 
145
+ // 确定相似度颜色类
146
+ const similarityClass = this.getSimilarityClass(guess.similarity);
147
 
148
  guessItem.innerHTML = `
149
  <span class="guess-word">${guess.word}</span>
 
153
  historyContainer.appendChild(guessItem);
154
  });
155
 
156
+ this.guessesList.appendChild(historyContainer);
 
 
 
 
 
157
  }
158
+
159
+ // 添加分页控件
160
+ addPagination(allGuesses, currentPage, onPageChange) {
161
+ const totalPages = Math.ceil(allGuesses.length / this.itemsPerPage);
162
+ if (totalPages <= 1) return; // 如果只有一页,不显示分页控件
163
 
164
+ // 创建分页容器
165
  const paginationContainer = document.createElement('div');
166
  paginationContainer.className = 'pagination-container';
167
 
168
+ // 上一页按钮
169
  const prevBtn = document.createElement('button');
170
  prevBtn.className = 'pagination-btn prev-btn';
171
  prevBtn.innerHTML = `<i class="fas fa-chevron-left"></i>`;
172
  prevBtn.disabled = currentPage === 1;
173
  prevBtn.addEventListener('click', () => {
174
  if (currentPage > 1) {
175
+ onPageChange(currentPage - 1);
 
176
  }
177
  });
178
 
179
+ // 页面指示器
180
  const pageIndicator = document.createElement('div');
181
  pageIndicator.className = 'page-indicator';
182
  pageIndicator.textContent = `${currentPage} / ${totalPages}`;
183
 
184
+ // 下一页按钮
185
  const nextBtn = document.createElement('button');
186
  nextBtn.className = 'pagination-btn next-btn';
187
  nextBtn.innerHTML = `<i class="fas fa-chevron-right"></i>`;
188
  nextBtn.disabled = currentPage === totalPages;
189
  nextBtn.addEventListener('click', () => {
190
  if (currentPage < totalPages) {
191
+ onPageChange(currentPage + 1);
 
192
  }
193
  });
194
 
195
+ // 将按钮添加到容器
196
  paginationContainer.appendChild(prevBtn);
197
  paginationContainer.appendChild(pageIndicator);
198
  paginationContainer.appendChild(nextBtn);
199
 
200
+ // 添加到DOM
201
+ this.guessesList.appendChild(paginationContainer);
202
  }
203
+ }
204
 
205
+ /**
206
+ * API类 - 负责与后端通信
207
+ */
208
+ class SemanticHunterAPI {
209
+ // 获取当前游戏状态
210
+ async getGameStatus() {
211
+ try {
212
+ const response = await fetch('/game-status', {
213
+ method: 'GET',
214
+ headers: {
215
+ 'Content-Type': 'application/json'
216
+ }
217
+ });
218
+ return await response.json();
219
+ } catch (error) {
220
+ console.error('获取游戏状态时出错:', error);
221
+ throw new Error('无法连接到服务器');
222
+ }
223
  }
224
 
225
+ // 初始化新游戏
226
+ async initGame() {
227
+ try {
228
+ const response = await fetch('/new-game', {
229
+ method: 'POST',
230
+ headers: {
231
+ 'Content-Type': 'application/json'
232
+ }
233
+ });
234
+ return await response.json();
235
+ } catch (error) {
236
+ console.error('初始化游戏时出错:', error);
237
+ throw new Error('无法连接到服务器');
238
+ }
239
  }
240
 
241
+ // 提交猜测
242
+ async submitGuess(guess) {
243
+ try {
244
+ const response = await fetch('/guess', {
245
+ method: 'POST',
246
+ headers: {
247
+ 'Content-Type': 'application/json'
248
+ },
249
+ body: JSON.stringify({ guess })
250
+ });
251
+ return await response.json();
252
+ } catch (error) {
253
+ console.error('提交猜测时出错:', error);
254
+ throw new Error('无法连接到服务器');
255
+ }
256
  }
257
 
258
+ // 放弃游戏
259
+ async giveUp() {
260
+ try {
261
+ const response = await fetch('/give-up', {
262
+ method: 'POST',
263
+ headers: {
264
+ 'Content-Type': 'application/json'
265
+ }
266
+ });
267
+ return await response.json();
268
+ } catch (error) {
269
+ console.error('放弃游戏时出错:', error);
270
+ throw new Error('无法连接到服务器');
 
271
  }
272
  }
273
+ }
274
 
275
+ /**
276
+ * 游戏控制器 - 处理游戏逻辑
277
+ */
278
+ class SemanticHunterGame {
279
+ constructor() {
280
+ this.ui = new SemanticHunterUI();
281
+ this.api = new SemanticHunterAPI();
282
 
283
+ // 游戏状态
284
+ this.gameActive = true; // 默认假设游戏是活跃的
285
+ this.latestGuess = null; // 只在前端存储最新猜测
286
+ this.allGuesses = []; // 所有猜测历史
287
+ this.currentPage = 1; // 当前分页
288
 
289
+ // 初始化事件监听器
290
+ this.initEventListeners();
 
291
 
292
+ // 加载游戏状态
293
+ this.loadGameStatus();
294
+ }
295
+
296
+ // 初始化事件监听器
297
+ initEventListeners() {
298
+ this.ui.guessBtn.addEventListener('click', () => this.submitGuess());
299
+ this.ui.guessInput.addEventListener('keypress', (e) => {
300
+ if (e.key === 'Enter') this.submitGuess();
301
+ });
302
+ this.ui.newGameBtn.addEventListener('click', () => this.initGame());
303
+ this.ui.giveUpBtn.addEventListener('click', () => this.giveUp());
304
+ }
305
+
306
+ // 加载游戏状态 - 从后端获取当前状态
307
+ async loadGameStatus() {
308
+ try {
309
+ const data = await this.api.getGameStatus();
310
+ if (data.status === 'success') {
311
+ // 更新游戏状态
312
+ this.allGuesses = data.guesses;
313
+ this.gameActive = !data.has_correct_guess;
314
+
315
+ // 如果有猜测历史,渲染它们
316
+ if (this.allGuesses.length > 0) {
317
+ // 不指定最新猜测,所有猜测都在历史列表中显示
318
+ this.updateGuessList(this.allGuesses, null);
319
+
320
+ // 如果已经猜对了
321
+ if (!this.gameActive) {
322
+ const correctGuess = this.allGuesses.find(g => g.is_correct);
323
+ if (correctGuess) {
324
+ this.ui.showSuccess(`有人已经猜对了! 秘密词语是: ${correctGuess.word}`);
325
+ }
326
+ } else {
327
+ this.ui.showMessage('游戏进行中! 尝试猜测秘密词语!');
328
+ }
329
+ } else {
330
+ this.ui.showMessage('游戏开始! 尝试猜测秘密词语!');
331
+ }
332
+
333
+ // 清空输入框并聚焦
334
+ this.ui.clearAndFocusInput();
335
+ } else {
336
+ this.ui.showError('获取游戏状态失败');
337
+ }
338
+ } catch (error) {
339
+ this.ui.showError(error.message);
340
  }
341
+ }
342
+
343
+ // 初始化游戏 - 创建新游戏
344
+ async initGame() {
345
+ try {
346
+ const data = await this.api.initGame();
347
+ if (data.status === 'success') {
348
+ this.gameActive = true;
349
+ this.ui.resetGuessList();
350
+ this.ui.clearAndFocusInput();
351
+ this.allGuesses = [];
352
+ this.latestGuess = null;
353
+ this.currentPage = 1;
354
+ this.ui.showMessage('新游戏开始! 尝试猜测秘密词语!');
355
+ } else {
356
+ this.ui.showError(data.message || '初始化游戏时出错');
357
+ }
358
+ } catch (error) {
359
+ this.ui.showError(error.message);
360
+ }
361
+ }
362
+
363
+ // 提交猜测
364
+ async submitGuess() {
365
+ const guessWord = this.ui.getInputValue();
366
+ if (!guessWord) return;
367
+ if (!this.gameActive) {
368
+ this.ui.showError('游戏已结束,请点击新游戏');
369
+ return;
370
+ }
371
+
372
+ try {
373
+ const data = await this.api.submitGuess(guessWord);
374
+ if (data.status === 'success') {
375
+ // 更新猜测历史
376
+ this.allGuesses = data.guesses;
377
+
378
+ // 在前端存储最新的猜测
379
+ this.latestGuess = {
380
+ word: guessWord,
381
+ similarity: data.similarity,
382
+ is_correct: data.is_correct
383
+ };
384
+
385
+ // 渲染猜测列表,将最新猜测置顶
386
+ this.updateGuessList(this.allGuesses, this.latestGuess);
387
+
388
+ // 根据猜测结果更新游戏状态和显示消息
389
+ if (data.is_correct) {
390
+ this.gameActive = false;
391
+ this.ui.showSuccess(`恭喜你猜对了! 秘密词语是: ${guessWord}`);
392
+ } else {
393
+ this.ui.showMessage(`相似度: ${data.similarity}%`);
394
+ }
395
+
396
+ // 清空输入框并聚焦
397
+ this.ui.clearAndFocusInput();
398
+ } else {
399
+ this.ui.showError(data.message || '提交猜测时出错');
400
+ }
401
+ } catch (error) {
402
+ this.ui.showError(error.message);
403
+ }
404
+ }
405
+
406
+ // 放弃游戏
407
+ async giveUp() {
408
+ if (!this.gameActive) {
409
+ this.ui.showError('游戏已结束,请点击新游戏');
410
+ return;
411
+ }
412
+
413
+ try {
414
+ const data = await this.api.giveUp();
415
+ if (data.status === 'success') {
416
+ this.gameActive = false;
417
+ this.ui.showError(`你放弃了! 秘密词语是: ${data.secret_word}`);
418
+ } else {
419
+ this.ui.showError(data.message || '放弃游戏时出错');
420
+ }
421
+ } catch (error) {
422
+ this.ui.showError(error.message);
423
+ }
424
+ }
425
+
426
+ // 更新猜测列表 - 同时处理最新猜测和历史猜测
427
+ updateGuessList(guesses, latestGuess) {
428
+ // 渲染猜测列表
429
+ this.ui.renderGuessList(guesses, latestGuess, this.currentPage);
430
 
431
+ // 如果有多页,添加分页控件
432
+ if (guesses.length > this.ui.itemsPerPage) {
433
+ this.ui.addPagination(guesses, this.currentPage, (newPage) => {
434
+ this.currentPage = newPage;
435
+ this.updateGuessList(guesses, latestGuess);
436
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
  }
439
+ }
440
+
441
+ // 当DOM内容加载完成时初始化游戏
442
+ document.addEventListener('DOMContentLoaded', () => {
443
+ new SemanticHunterGame();
444
  });
templates/index.html CHANGED
@@ -22,9 +22,9 @@
22
 
23
  <main>
24
  <div class="game-area">
25
- <h2>Can you guess the secret word?</h2>
26
  <div class="input-area">
27
- <input type="text" id="guess-input" placeholder="Can you guess the word?" autofocus>
28
  <button id="guess-btn" class="btn btn-primary">
29
  <i class="fas fa-arrow-right"></i>
30
  </button>
 
22
 
23
  <main>
24
  <div class="game-area">
25
+ <h2>你能猜出秘密词语吗?</h2>
26
  <div class="input-area">
27
+ <input type="text" id="guess-input" placeholder="试试 洋葱" autofocus>
28
  <button id="guess-btn" class="btn btn-primary">
29
  <i class="fas fa-arrow-right"></i>
30
  </button>