turtlesoup.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. // stores/games/turtlesoup.ts
  2. import { defineStore } from 'pinia'
  3. import { turtleSoupService } from '@/services/games/turtlesoup'
  4. import {
  5. type TurtleSoupGameHostView,
  6. type TurtleSoupGamePlayerView,
  7. type TurtleSoupQuestion,
  8. type TurtleSoupHint,
  9. type TurtleSoupTheme,
  10. type TurtleSoupPuzzle,
  11. type TurtleSoupGameSettings,
  12. TurtleSoupAnswerType,
  13. TurtleSoupGameStatus,
  14. TurtleSoupDifficulty
  15. } from '@/types/games/turtlesoup'
  16. import { useUserStore } from '@/stores/user'
  17. import { RoomStatus } from '@/types/room'
  18. import { ref, computed } from 'vue'
  19. export const useTurtleSoupStore = defineStore('turtlesoup', () => {
  20. // 基本状态
  21. const hostView = ref<TurtleSoupGameHostView | null>(null)
  22. const playerView = ref<TurtleSoupGamePlayerView | null>(null)
  23. const loading = ref(false)
  24. const submitting = ref(false)
  25. const error = ref<string | null>(null)
  26. const isHost = ref(false)
  27. const localDraft = ref('') // 问题/解答草稿
  28. // 游戏设置 - 主持人创建或修改游戏时使用
  29. const gameSettings = ref<TurtleSoupGameSettings>({
  30. themeId: '',
  31. puzzleId: '',
  32. difficulty: TurtleSoupDifficulty.MEDIUM,
  33. maxPlayers: 10,
  34. isPrivate: false
  35. })
  36. // 可用主题列表
  37. const availableThemes = ref<TurtleSoupTheme[]>([])
  38. // 可用题目列表
  39. const availablePuzzles = ref<TurtleSoupPuzzle[]>([])
  40. // 计算属性
  41. const currentView = computed(() => {
  42. return isHost.value ? hostView.value : playerView.value
  43. })
  44. // 获取游戏状态
  45. const gameStatus = computed(() => {
  46. return currentView.value?.status || TurtleSoupGameStatus.CREATED
  47. })
  48. // 游戏是否激活
  49. const isGameActive = computed(() => {
  50. return gameStatus.value === TurtleSoupGameStatus.ACTIVE
  51. })
  52. // 获取已公开的提示
  53. const revealedHints = computed(() => {
  54. if (isHost.value && hostView.value) {
  55. return hostView.value.hints.filter(hint => hint.revealed)
  56. } else if (playerView.value) {
  57. return playerView.value.revealedHints
  58. }
  59. return []
  60. })
  61. // 获取未公开的提示 (只有主持人可见)
  62. const unrevealedHints = computed(() => {
  63. if (isHost.value && hostView.value) {
  64. return hostView.value.hints.filter(hint => !hint.revealed)
  65. }
  66. return []
  67. })
  68. // 获取排序后的问题列表(最新的在前)
  69. const sortedQuestions = computed(() => {
  70. if (isHost.value && hostView.value) {
  71. return [...hostView.value.questions].sort((a, b) => b.timestamp - a.timestamp)
  72. } else if (playerView.value) {
  73. return [...playerView.value.allQuestions].sort((a, b) => b.timestamp - a.timestamp)
  74. }
  75. return []
  76. })
  77. // 获取待回答的问题(只有主持人可见)
  78. const pendingQuestions = computed(() => {
  79. if (isHost.value && hostView.value) {
  80. return hostView.value.questions.filter(q => !q.answered)
  81. .sort((a, b) => b.timestamp - a.timestamp)
  82. }
  83. return []
  84. })
  85. // 获取我的问题(玩家视图)
  86. const myQuestions = computed(() => {
  87. if (!isHost.value && playerView.value) {
  88. return playerView.value.myQuestions.sort((a, b) => b.timestamp - a.timestamp)
  89. }
  90. return []
  91. })
  92. // 获取游戏时长(分钟)
  93. const gameDuration = computed(() => {
  94. const view = currentView.value
  95. if (!view || !view.startTime) return 0
  96. return Math.floor(((view.currentTime || Date.now()) - view.startTime) / 60000)
  97. })
  98. // 获取游戏进度
  99. const gameProgress = computed(() => {
  100. return currentView.value?.progress || 0
  101. })
  102. // 获取房间代码
  103. const roomCode = computed(() => {
  104. return isHost.value && hostView.value ? hostView.value.roomCode : ''
  105. })
  106. // 游戏是否已结束
  107. const isGameEnded = computed(() => {
  108. return gameStatus.value === TurtleSoupGameStatus.COMPLETED ||
  109. gameStatus.value === TurtleSoupGameStatus.ABANDONED
  110. })
  111. // Actions
  112. // 重置状态
  113. function resetState() {
  114. hostView.value = null
  115. playerView.value = null
  116. loading.value = false
  117. submitting.value = false
  118. error.value = null
  119. isHost.value = false
  120. localDraft.value = ''
  121. // 重置游戏设置到默认值
  122. gameSettings.value = {
  123. themeId: '',
  124. puzzleId: '',
  125. difficulty: TurtleSoupDifficulty.MEDIUM,
  126. maxPlayers: 10,
  127. isPrivate: false
  128. }
  129. }
  130. // 设置游戏视图类型
  131. function setViewType(role: 'host' | 'player') {
  132. isHost.value = role === 'host'
  133. }
  134. // 更新游戏设置
  135. function updateGameSettings(settings: Partial<TurtleSoupGameSettings>) {
  136. gameSettings.value = {
  137. ...gameSettings.value,
  138. ...settings
  139. }
  140. }
  141. // 加载可用的主题列表
  142. async function loadThemes() {
  143. if (availableThemes.value.length > 0) return availableThemes.value
  144. loading.value = true
  145. error.value = null
  146. try {
  147. const result = await turtleSoupService.getThemes()
  148. if (result.success && result.data) {
  149. availableThemes.value = result.data
  150. return result.data
  151. } else {
  152. error.value = result.message || '加载主题失败'
  153. return []
  154. }
  155. } catch (e) {
  156. console.error('加载主题失败:', e)
  157. error.value = e instanceof Error ? e.message : '未知错误'
  158. return []
  159. } finally {
  160. loading.value = false
  161. }
  162. }
  163. async function unlockTheme(themeId: string) {
  164. const result = await turtleSoupService.unlockTheme(themeId)
  165. if (result.success) {
  166. return result
  167. }
  168. return null
  169. }
  170. // 加载可用的题目列表
  171. async function loadPuzzles(themeId: string, difficulty?: TurtleSoupDifficulty) {
  172. loading.value = true
  173. error.value = null
  174. try {
  175. const result = await turtleSoupService.getPuzzles(themeId, difficulty)
  176. if (result.success && result.data) {
  177. availablePuzzles.value = result.data
  178. return result.data
  179. } else {
  180. error.value = result.message || '加载题目失败'
  181. return []
  182. }
  183. } catch (e) {
  184. console.error('加载题目失败:', e)
  185. error.value = e instanceof Error ? e.message : '未知错误'
  186. return []
  187. } finally {
  188. loading.value = false
  189. }
  190. }
  191. // 加载主持人游戏视图
  192. async function loadHostGame(gameId: string) {
  193. loading.value = true
  194. error.value = null
  195. isHost.value = true
  196. try {
  197. const result = await turtleSoupService.getHostGameData(gameId)
  198. if (result.success && result.data) {
  199. hostView.value = result.data
  200. return true
  201. } else {
  202. error.value = result.message || '加载游戏数据失败'
  203. return false
  204. }
  205. } catch (e) {
  206. console.error('加载主持人游戏视图失败:', e)
  207. error.value = e instanceof Error ? e.message : '未知错误'
  208. return false
  209. } finally {
  210. loading.value = false
  211. }
  212. }
  213. // 加载玩家游戏视图
  214. async function loadPlayerGame(gameId: string) {
  215. loading.value = true
  216. error.value = null
  217. isHost.value = false
  218. try {
  219. const result = await turtleSoupService.getPlayerGameData(gameId)
  220. if (result.success && result.data) {
  221. playerView.value = result.data
  222. return true
  223. } else {
  224. error.value = result.message || '加载游戏数据失败'
  225. return false
  226. }
  227. } catch (e) {
  228. console.error('加载玩家游戏视图失败:', e)
  229. error.value = e instanceof Error ? e.message : '未知错误'
  230. return false
  231. } finally {
  232. loading.value = false
  233. }
  234. }
  235. // 转换游戏状态为房间状态
  236. function gameStatusToRoomStatus(status: TurtleSoupGameStatus): RoomStatus {
  237. switch (status) {
  238. case TurtleSoupGameStatus.CREATED:
  239. case TurtleSoupGameStatus.WAITING:
  240. return RoomStatus.WAITING
  241. case TurtleSoupGameStatus.ACTIVE:
  242. return RoomStatus.PLAYING
  243. case TurtleSoupGameStatus.COMPLETED:
  244. case TurtleSoupGameStatus.ABANDONED:
  245. return RoomStatus.ENDED
  246. default:
  247. return RoomStatus.WAITING
  248. }
  249. }
  250. // 创建新游戏 - 只处理游戏部分的数据
  251. async function createGame(settings: TurtleSoupGameSettings) {
  252. submitting.value = true
  253. error.value = null
  254. try {
  255. // 保存游戏设置
  256. gameSettings.value = settings
  257. // 创建游戏
  258. const result = await turtleSoupService.createGame(settings)
  259. if (result.success && result.data) {
  260. return {
  261. success: true,
  262. gameId: result.data.gameId,
  263. roomId: result.data.roomId
  264. }
  265. } else {
  266. error.value = result.message || '创建游戏失败'
  267. return { success: false, message: error.value }
  268. }
  269. } catch (e) {
  270. console.error('创建游戏失败:', e)
  271. error.value = e instanceof Error ? e.message : '创建游戏时发生错误'
  272. return { success: false, message: error.value }
  273. } finally {
  274. submitting.value = false
  275. }
  276. }
  277. // 开始游戏
  278. async function startGame() {
  279. if (!isHost.value || !hostView.value) {
  280. error.value = '主持人视图未加载'
  281. return false
  282. }
  283. submitting.value = true
  284. error.value = null
  285. try {
  286. const result = await turtleSoupService.startGame(hostView.value.id)
  287. if (result.success) {
  288. // 更新游戏状态
  289. if (hostView.value) {
  290. hostView.value = {
  291. ...hostView.value,
  292. status: TurtleSoupGameStatus.ACTIVE,
  293. startTime: Date.now(),
  294. currentTime: Date.now()
  295. }
  296. }
  297. return true
  298. } else {
  299. error.value = result.message || '开始游戏失败'
  300. return false
  301. }
  302. } catch (e) {
  303. console.error('开始游戏失败:', e)
  304. error.value = e instanceof Error ? e.message : '开始游戏时发生错误'
  305. return false
  306. } finally {
  307. submitting.value = false
  308. }
  309. }
  310. // 提交问题 (玩家)
  311. async function submitQuestion(content: string) {
  312. if (!playerView.value || !content.trim()) {
  313. error.value = '无效的问题或游戏未加载'
  314. return false
  315. }
  316. submitting.value = true
  317. error.value = null
  318. try {
  319. const userStore = useUserStore()
  320. const result = await turtleSoupService.submitQuestion(playerView.value.id, content)
  321. if (result.success && result.data) {
  322. // 创建新问题对象
  323. const newQuestion: TurtleSoupQuestion = {
  324. id: result.data.questionId,
  325. content,
  326. askedBy: userStore.openid,
  327. askedByName: userStore.nickname,
  328. timestamp: Date.now(),
  329. answered: false
  330. }
  331. // 更新本地玩家视图
  332. if (playerView.value) {
  333. playerView.value = {
  334. ...playerView.value,
  335. myQuestions: [...playerView.value.myQuestions, newQuestion]
  336. }
  337. }
  338. // 清空草稿
  339. localDraft.value = ''
  340. return true
  341. } else {
  342. error.value = result.message || '提交问题失败'
  343. return false
  344. }
  345. } catch (e) {
  346. console.error('提交问题失败:', e)
  347. error.value = e instanceof Error ? e.message : '提交问题时发生错误'
  348. return false
  349. } finally {
  350. submitting.value = false
  351. }
  352. }
  353. // 回答问题 (主持人)
  354. async function answerQuestion(questionId: string, answer: TurtleSoupAnswerType) {
  355. if (!isHost.value || !hostView.value) {
  356. error.value = '主持人视图未加载'
  357. return false
  358. }
  359. submitting.value = true
  360. error.value = null
  361. try {
  362. const result = await turtleSoupService.answerQuestion(questionId, answer)
  363. if (result.success) {
  364. // 更新本地状态
  365. if (hostView.value) {
  366. const updatedQuestions = hostView.value.questions.map(q => {
  367. if (q.id === questionId) {
  368. return {
  369. ...q,
  370. answer,
  371. answered: true,
  372. answeredAt: Date.now()
  373. }
  374. }
  375. return q
  376. })
  377. hostView.value = {
  378. ...hostView.value,
  379. questions: updatedQuestions
  380. }
  381. }
  382. return true
  383. } else {
  384. error.value = result.message || '回答问题失败'
  385. return false
  386. }
  387. } catch (e) {
  388. console.error('回答问题失败:', e)
  389. error.value = e instanceof Error ? e.message : '回答问题时发生错误'
  390. return false
  391. } finally {
  392. submitting.value = false
  393. }
  394. }
  395. // 公开提示 (主持人)
  396. async function revealHint(hintId: string) {
  397. if (!isHost.value || !hostView.value) {
  398. error.value = '主持人视图未加载'
  399. return false
  400. }
  401. submitting.value = true
  402. error.value = null
  403. try {
  404. const result = await turtleSoupService.revealHint(hostView.value.id, hintId)
  405. if (result.success) {
  406. // 更新本地状态
  407. if (hostView.value) {
  408. const updatedHints = hostView.value.hints.map(hint => {
  409. if (hint.id === hintId) {
  410. return {
  411. ...hint,
  412. revealed: true,
  413. revealedAt: Date.now()
  414. }
  415. }
  416. return hint
  417. })
  418. hostView.value = {
  419. ...hostView.value,
  420. hints: updatedHints
  421. }
  422. }
  423. return true
  424. } else {
  425. error.value = result.message || '公开提示失败'
  426. return false
  427. }
  428. } catch (e) {
  429. console.error('公开提示失败:', e)
  430. error.value = e instanceof Error ? e.message : '公开提示时发生错误'
  431. return false
  432. } finally {
  433. submitting.value = false
  434. }
  435. }
  436. // 提交解答 (玩家)
  437. async function submitSolution(solution: string) {
  438. if (!playerView.value || !solution.trim()) {
  439. error.value = '无效的解答或游戏未加载'
  440. return { success: false, correct: false }
  441. }
  442. submitting.value = true
  443. error.value = null
  444. try {
  445. const result = await turtleSoupService.submitSolution(playerView.value.id, solution)
  446. if (result.success) {
  447. // 解答正确
  448. if (result.data?.correct) {
  449. return { success: true, correct: true }
  450. }
  451. // 解答错误
  452. return { success: true, correct: false }
  453. } else {
  454. error.value = result.message || '提交解答失败'
  455. return { success: false, correct: false }
  456. }
  457. } catch (e) {
  458. console.error('提交解答失败:', e)
  459. error.value = e instanceof Error ? e.message : '提交解答时发生错误'
  460. return { success: false, correct: false }
  461. } finally {
  462. submitting.value = false
  463. }
  464. }
  465. // 结束游戏 - 只更新状态,不处理房间
  466. async function endGame(params: {
  467. gameId: string,
  468. solved: boolean,
  469. solvedBy?: string,
  470. solvedByName?: string,
  471. solution?: string
  472. }) {
  473. submitting.value = true
  474. error.value = null
  475. try {
  476. const { gameId, ...resultData } = params
  477. const endResult = await turtleSoupService.endGame(gameId, resultData)
  478. if (endResult.success && endResult.data) {
  479. // 更新游戏状态
  480. if (isHost.value && hostView.value && hostView.value.id === gameId) {
  481. hostView.value = {
  482. ...hostView.value,
  483. status: TurtleSoupGameStatus.COMPLETED,
  484. currentTime: Date.now()
  485. }
  486. } else if (playerView.value && playerView.value.id === gameId) {
  487. playerView.value = {
  488. ...playerView.value,
  489. status: TurtleSoupGameStatus.COMPLETED,
  490. currentTime: Date.now()
  491. }
  492. }
  493. return endResult.data
  494. } else {
  495. error.value = endResult.message || '结束游戏失败'
  496. return null
  497. }
  498. } catch (e) {
  499. console.error('结束游戏失败:', e)
  500. error.value = e instanceof Error ? e.message : '结束游戏时发生错误'
  501. return null
  502. } finally {
  503. submitting.value = false
  504. }
  505. }
  506. // 保存问题草稿
  507. function saveDraft(content: string) {
  508. localDraft.value = content
  509. }
  510. // 更新游戏进度 (主持人)
  511. async function updateProgress(progress: number) {
  512. if (!isHost.value || !hostView.value) {
  513. error.value = '主持人视图未加载'
  514. return false
  515. }
  516. submitting.value = true
  517. try {
  518. const result = await turtleSoupService.updateProgress(hostView.value.id, progress)
  519. if (result.success) {
  520. if (hostView.value) {
  521. hostView.value = {
  522. ...hostView.value,
  523. progress
  524. }
  525. }
  526. return true
  527. } else {
  528. error.value = result.message || '更新游戏进度失败'
  529. return false
  530. }
  531. } catch (e) {
  532. console.error('更新游戏进度失败:', e)
  533. error.value = e instanceof Error ? e.message : '更新进度时发生错误'
  534. return false
  535. } finally {
  536. submitting.value = false
  537. }
  538. }
  539. // 刷新游戏数据
  540. async function refreshGameData(gameId: string) {
  541. if (isHost.value) {
  542. return loadHostGame(gameId)
  543. } else {
  544. return loadPlayerGame(gameId)
  545. }
  546. }
  547. return {
  548. // 状态
  549. hostView,
  550. playerView,
  551. loading,
  552. submitting,
  553. error,
  554. isHost,
  555. localDraft,
  556. gameSettings,
  557. availableThemes,
  558. availablePuzzles,
  559. // 计算属性
  560. currentView,
  561. gameStatus,
  562. isGameActive,
  563. isGameEnded,
  564. revealedHints,
  565. unrevealedHints,
  566. sortedQuestions,
  567. pendingQuestions,
  568. myQuestions,
  569. gameDuration,
  570. gameProgress,
  571. roomCode,
  572. // 操作方法
  573. resetState,
  574. setViewType,
  575. updateGameSettings,
  576. loadThemes,
  577. unlockTheme,
  578. loadPuzzles,
  579. createGame,
  580. loadHostGame,
  581. loadPlayerGame,
  582. gameStatusToRoomStatus,
  583. startGame,
  584. submitQuestion,
  585. answerQuestion,
  586. revealHint,
  587. submitSolution,
  588. endGame,
  589. saveDraft,
  590. updateProgress,
  591. refreshGameData
  592. }
  593. })