1
0

app.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // GuessWhoAmI Game - Frontend Logic
  2. class GuessWhoAmIGame {
  3. constructor() {
  4. this.sessionId = null;
  5. this.remainingQuestions = 20;
  6. this.remainingHints = 3;
  7. this.initializeEventListeners();
  8. }
  9. // Initialize event listeners
  10. initializeEventListeners() {
  11. // Start game button (HTML id="start-game")
  12. document.getElementById('start-game')
  13. .addEventListener('click', () => this.startNewGame());
  14. // Send message button (HTML id="send-btn")
  15. document.getElementById('send-btn')
  16. .addEventListener('click', () => this.sendMessage());
  17. // User input enter key (HTML id="user-input")
  18. document.getElementById('user-input').addEventListener('keypress', (e) => {
  19. if (e.key === 'Enter') this.sendMessage();
  20. });
  21. // Hint button (HTML id="get-hint")
  22. document.getElementById('get-hint')
  23. .addEventListener('click', () => this.requestHint());
  24. // Guess button (HTML id="guess-btn")
  25. document.getElementById('guess-btn')
  26. .addEventListener('click', () => this.submitGuess());
  27. // Play again button (HTML id="play-again")
  28. document.getElementById('play-again')
  29. .addEventListener('click', () => this.restartGame());
  30. }
  31. // Start new game
  32. async startNewGame() {
  33. try {
  34. this.setStartBtnLoading(true);
  35. this.showLoadingOverlay();
  36. const response = await fetch('http://localhost:8000/api/game/start', {
  37. method: 'POST',
  38. headers: {'Content-Type': 'application/json'},
  39. body: JSON.stringify({})
  40. });
  41. if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
  42. const result = await response.json();
  43. if (!result.success) throw new Error(result.error || '启动失败');
  44. const data = result.data;
  45. this.sessionId = data.session_id;
  46. this.remainingQuestions = data.max_questions || 20;
  47. this.remainingHints = data.max_hints || 3;
  48. // Switch to game screen
  49. document.getElementById('intro-section').classList.add('hidden');
  50. document.getElementById('game-section').classList.remove('hidden');
  51. document.getElementById('result-modal').classList.add('hidden');
  52. // Enable controls
  53. document.getElementById('user-input').disabled = false;
  54. document.getElementById('send-btn').disabled = false;
  55. document.getElementById('get-hint').disabled = false;
  56. document.getElementById('guess-btn').disabled = false;
  57. document.getElementById('guess-input').disabled = false;
  58. // Clear chat and add welcome message
  59. this.clearChat();
  60. this.addMessage(data.welcome_message, 'agent');
  61. // Update stats
  62. this.updateStats();
  63. } catch (error) {
  64. alert(`无法开始游戏:${
  65. error.message}\n请确认后端服务已在 http://localhost:8000 启动`);
  66. console.error('Start game error:', error);
  67. } finally {
  68. this.hideLoadingOverlay();
  69. this.setStartBtnLoading(false);
  70. }
  71. }
  72. // Send message to agent
  73. async sendMessage() {
  74. const input = document.getElementById('user-input');
  75. const message = input.value.trim();
  76. if (!message) return;
  77. if (!this.sessionId) {
  78. alert('请先开始游戏');
  79. return;
  80. }
  81. input.value = '';
  82. this.addMessage(message, 'user');
  83. this.setControlsDisabled(true);
  84. try {
  85. const response = await fetch('http://localhost:8000/api/game/chat', {
  86. method: 'POST',
  87. headers: {'Content-Type': 'application/json'},
  88. body: JSON.stringify({session_id: this.sessionId, message: message})
  89. });
  90. if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
  91. const result = await response.json();
  92. if (!result.success) throw new Error(result.error || '消息发送失败');
  93. const data = result.data;
  94. this.addMessage(data.response, 'agent');
  95. // Update remaining questions from server
  96. this.remainingQuestions = data.remaining_questions;
  97. this.updateStats();
  98. if (data.is_game_over) {
  99. this.endGame(false);
  100. }
  101. } catch (error) {
  102. this.addMessage(`⚠️ 发送失败:${error.message}`, 'agent');
  103. console.error('Send message error:', error);
  104. } finally {
  105. this.setControlsDisabled(false);
  106. }
  107. }
  108. // Request hint
  109. async requestHint() {
  110. if (!this.sessionId) return;
  111. if (this.remainingHints <= 0) {
  112. alert('提示次数已用完');
  113. return;
  114. }
  115. this.setControlsDisabled(true);
  116. try {
  117. const response = await fetch('http://localhost:8000/api/game/hint', {
  118. method: 'POST',
  119. headers: {'Content-Type': 'application/json'},
  120. body: JSON.stringify({session_id: this.sessionId})
  121. });
  122. if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
  123. const result = await response.json();
  124. if (!result.success) throw new Error(result.error || '获取提示失败');
  125. const data = result.data;
  126. const hintText = data.hint || data.message || '暂无提示';
  127. this.addMessage(`💡 提示:${hintText}`, 'agent');
  128. this.remainingHints = data.remaining_hints !== undefined ?
  129. data.remaining_hints :
  130. this.remainingHints - 1;
  131. this.updateStats();
  132. } catch (error) {
  133. alert(`获取提示失败:${error.message}`);
  134. console.error('Hint error:', error);
  135. } finally {
  136. this.setControlsDisabled(false);
  137. }
  138. }
  139. // Submit guess
  140. async submitGuess() {
  141. const guessInput = document.getElementById('guess-input');
  142. const guess = guessInput.value.trim();
  143. if (!guess) {
  144. alert('请输入猜测的人物姓名');
  145. return;
  146. }
  147. if (!this.sessionId) return;
  148. this.setControlsDisabled(true);
  149. try {
  150. const response = await fetch('http://localhost:8000/api/game/guess', {
  151. method: 'POST',
  152. headers: {'Content-Type': 'application/json'},
  153. body: JSON.stringify({session_id: this.sessionId, guess: guess})
  154. });
  155. if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
  156. const result = await response.json();
  157. if (!result.success) throw new Error(result.error || '猜测失败');
  158. const data = result.data;
  159. guessInput.value = '';
  160. if (data.is_correct) {
  161. this.addMessage(`🎉 恭喜!你猜对了!答案就是:${guess}`, 'agent');
  162. this.endGame(true, data.figure_info, data.portrait_images || []);
  163. } else {
  164. this.addMessage(`❌ 猜错了!${data.message || '再想想看~'}`, 'agent');
  165. this.remainingQuestions = data.remaining_questions !== undefined ?
  166. data.remaining_questions :
  167. this.remainingQuestions - 1;
  168. this.updateStats();
  169. if (data.is_game_over || this.remainingQuestions <= 0) {
  170. this.endGame(false);
  171. }
  172. }
  173. } catch (error) {
  174. alert(`提交猜测失败:${error.message}`);
  175. console.error('Guess error:', error);
  176. } finally {
  177. this.setControlsDisabled(false);
  178. }
  179. }
  180. // End game and show result
  181. async endGame(isWin, figureInfo = null, portraitImages = []) {
  182. // Disable controls
  183. document.getElementById('user-input').disabled = true;
  184. document.getElementById('send-btn').disabled = true;
  185. document.getElementById('get-hint').disabled = true;
  186. document.getElementById('guess-btn').disabled = true;
  187. document.getElementById('guess-input').disabled = true;
  188. // Fetch figure info if not provided
  189. if (!figureInfo && this.sessionId) {
  190. try {
  191. const response = await fetch('http://localhost:8000/api/game/end', {
  192. method: 'POST',
  193. headers: {'Content-Type': 'application/json'},
  194. body: JSON.stringify({session_id: this.sessionId})
  195. });
  196. if (response.ok) {
  197. const result = await response.json();
  198. if (result.success && result.data) {
  199. figureInfo = result.data;
  200. }
  201. }
  202. } catch (e) {
  203. console.error('End game fetch error:', e);
  204. }
  205. }
  206. // Show result modal
  207. document.getElementById('result-modal').classList.remove('hidden');
  208. const resultTitle = document.getElementById('result-title');
  209. const resultMessage = document.getElementById('result-message');
  210. const figureInfoEl = document.getElementById('figure-info');
  211. if (isWin) {
  212. resultTitle.textContent = '🎉 恭喜你猜对了!';
  213. resultMessage.textContent = '你成功猜出了这个人物的身份!';
  214. } else {
  215. resultTitle.textContent = '⏰ 游戏结束';
  216. resultMessage.textContent = '提问次数已用完,下次加油!';
  217. }
  218. if (figureInfo) {
  219. const name = figureInfo.figure_name || figureInfo.name || '未知';
  220. const dynasty = figureInfo.dynasty || '';
  221. const occupation = figureInfo.occupation || figureInfo.profession || '';
  222. const achievements = figureInfo.achievements || '';
  223. const characteristics =
  224. figureInfo.characteristics || figureInfo.key_features || '';
  225. // Build portrait gallery HTML
  226. let portraitHtml = '';
  227. if (portraitImages && portraitImages.length > 0) {
  228. const imgItems = portraitImages
  229. .map(photo => `
  230. <div class="portrait-item">
  231. <img src="${photo.url}" alt="${photo.description || name}"
  232. title="📷 ${photo.photographer || 'Unsplash'}"
  233. onerror="this.parentElement.style.display='none'">
  234. </div>
  235. `).join('');
  236. portraitHtml = `<div class="portrait-gallery">${imgItems}</div>`;
  237. }
  238. figureInfoEl.innerHTML = `
  239. ${portraitHtml}
  240. <p><strong>答案:</strong>${name}</p>
  241. ${dynasty ? `<p><strong>朝代/时代:</strong>${dynasty}</p>` : ''}
  242. ${occupation ? `<p><strong>职业/身份:</strong>${occupation}</p>` : ''}
  243. ${
  244. achievements ? `<p><strong>主要成就:</strong>${achievements}</p>` :
  245. ''}
  246. ${
  247. characteristics ?
  248. `<p><strong>关键特征:</strong>${characteristics}</p>` :
  249. ''}
  250. `;
  251. } else {
  252. figureInfoEl.innerHTML = '';
  253. }
  254. this.sessionId = null;
  255. }
  256. // Restart game
  257. restartGame() {
  258. document.getElementById('result-modal').classList.add('hidden');
  259. document.getElementById('game-section').classList.add('hidden');
  260. document.getElementById('intro-section').classList.remove('hidden');
  261. this.clearChat();
  262. this.sessionId = null;
  263. this.remainingQuestions = 20;
  264. this.remainingHints = 3;
  265. }
  266. // Add message to chat
  267. addMessage(text, type) {
  268. const chatContainer = document.getElementById('chat-container');
  269. // Remove static welcome message on first real message
  270. const staticWelcome = chatContainer.querySelector('.welcome-message');
  271. if (staticWelcome) staticWelcome.remove();
  272. const messageDiv = document.createElement('div');
  273. messageDiv.className = `message ${type}-message`;
  274. const contentDiv = document.createElement('div');
  275. contentDiv.className = 'message-content';
  276. contentDiv.textContent = text;
  277. messageDiv.appendChild(contentDiv);
  278. chatContainer.appendChild(messageDiv);
  279. chatContainer.scrollTop = chatContainer.scrollHeight;
  280. }
  281. // Clear chat
  282. clearChat() {
  283. const chatContainer = document.getElementById('chat-container');
  284. chatContainer.innerHTML = `
  285. <div class="welcome-message">
  286. <div class="message agent-message">
  287. <div class="message-content">
  288. 你好!我是一个知名人物,你可以通过提问来猜测我的身份。开始吧!
  289. </div>
  290. </div>
  291. </div>`;
  292. }
  293. // Update stats display
  294. updateStats() {
  295. document.getElementById('remaining-questions').textContent =
  296. `剩余提问: ${this.remainingQuestions}`;
  297. document.getElementById('remaining-hints').textContent =
  298. `剩余提示: ${this.remainingHints}`;
  299. }
  300. // Disable/enable game controls
  301. setControlsDisabled(disabled) {
  302. document.getElementById('send-btn').disabled = disabled;
  303. document.getElementById('get-hint').disabled = disabled;
  304. document.getElementById('guess-btn').disabled = disabled;
  305. document.getElementById('user-input').disabled = disabled;
  306. document.getElementById('guess-input').disabled = disabled;
  307. }
  308. // Start button loading state
  309. setStartBtnLoading(loading) {
  310. const btn = document.getElementById('start-game');
  311. btn.disabled = loading;
  312. btn.textContent = loading ? '正在启动...' : '开始游戏';
  313. }
  314. // Show full-screen loading overlay with step text rotation
  315. showLoadingOverlay() {
  316. const overlay = document.getElementById('loading-overlay');
  317. const stepEl = document.getElementById('loading-step');
  318. overlay.classList.remove('hidden');
  319. const steps = [
  320. '🔍 正在随机选择人物...',
  321. '📚 正在搜索人物资料...',
  322. '🤖 AI 正在准备提示...',
  323. '🎤 正在准备角色扮演...',
  324. ];
  325. let idx = 0;
  326. stepEl.textContent = steps[0];
  327. this._loadingTimer = setInterval(() => {
  328. idx = (idx + 1) % steps.length;
  329. stepEl.style.opacity = '0';
  330. setTimeout(() => {
  331. stepEl.textContent = steps[idx];
  332. stepEl.style.opacity = '1';
  333. }, 400);
  334. }, 2000);
  335. }
  336. // Hide loading overlay and clear timer
  337. hideLoadingOverlay() {
  338. const overlay = document.getElementById('loading-overlay');
  339. overlay.classList.add('hidden');
  340. if (this._loadingTimer) {
  341. clearInterval(this._loadingTimer);
  342. this._loadingTimer = null;
  343. }
  344. }
  345. }
  346. // Initialize game on page load
  347. window.onload = function() {
  348. window.game = new GuessWhoAmIGame();
  349. };