xiongzhu 2 лет назад
Родитель
Сommit
e4f8d3bbf2
2 измененных файлов с 113 добавлено и 35 удалено
  1. 3 0
      src/game/entities/game.entity.ts
  2. 110 35
      src/game/game.service.ts

+ 3 - 0
src/game/entities/game.entity.ts

@@ -5,6 +5,9 @@ import { GameStatus } from '../enums/game-status.enum'
 
 @Entity()
 export class Game {
+    constructor(game: Partial<Game>) {
+        Object.assign(this, game)
+    }
     @PrimaryGeneratedColumn()
     id: number
 

+ 110 - 35
src/game/game.service.ts

@@ -75,6 +75,7 @@ export class GameService implements OnModuleInit {
     ) {}
 
     async onModuleInit() {
+        await this.gameRepository.update({ status: GameStatus.Running }, { status: GameStatus.Initialized })
         const self = this
         const cb = CallbackManager.fromHandlers({
             async handleLLMStart(llm: Serialized, prompts: string[]) {
@@ -157,11 +158,9 @@ export class GameService implements OnModuleInit {
     }
 
     async createGame(dto: CreateGameDto): Promise<Game> {
-        let game = new Game()
-        game.name = dto.name
-        game.roomId = dto.roomId
-        game.background = dto.background
-        game.charactors = dto.charactors
+        let game = new Game({
+            ...dto
+        })
         game = await this.gameRepository.save(game)
         await this.roomService.setCurrentGameId(dto.roomId, game.id)
         return game
@@ -249,9 +248,14 @@ export class GameService implements OnModuleInit {
 
     formatCharactors(charactors: Charactor[]) {
         return charactors
-            .filter((i) => i.hp > 0)
             .map((i) => {
-                return `- 姓名:${i.name};性别:${i.gender};年龄:${i.age};职业:${i.occupation};性格:${i.personality};背景:${i.background};HP:${i.hp}`
+                let s = `- ${i.name}: ${i.gender}, ${i.occupation}, ${i.personality}`
+                if (i.dead) {
+                    s += ' (死亡)'
+                } else if (i.hp === 0) {
+                    s += ' (濒死)'
+                }
+                return s
             })
             .join('\n')
     }
@@ -367,6 +371,7 @@ export class GameService implements OnModuleInit {
         if (!game) {
             throw new NotFoundException(`game #${id} not found`)
         }
+        this.logger.log(`继续游戏 ${game.id} status=${game.status}`)
         const history: GameState[] = await this.getHistory(game.currentState, [])
         const lastState = history[history.length - 1]
 
@@ -406,7 +411,7 @@ export class GameService implements OnModuleInit {
             template: await this.promptService.getPromptByName(PromptName.NextPlot),
             inputVariables: ['background', 'charactors', 'datetime', 'summary', 'choice', 'death', 'newCharactor']
         })
-        const formatedCharactors = this.formatCharactors(charactorsAlive)
+        const formatedCharactors = this.formatCharactors(charactors)
         const formatedDatatime = this.formatDatetime(date, time)
 
         if (newCharactor) {
@@ -440,6 +445,12 @@ export class GameService implements OnModuleInit {
 
         const response = await this.callLLM([new HumanMessage(input)])
         plot = response.content
+
+        this.send(`${id}`, {
+            type: 'timeChange',
+            data: { date, time }
+        })
+
         this.send(`${id}`, {
             type: 'plot',
             data: plot
@@ -449,7 +460,7 @@ export class GameService implements OnModuleInit {
             this.calculateHp(charactors, [], charactorsWillDead, date)
             this.send(`${id}`, {
                 type: 'death',
-                data: options
+                data: charactorsWillDead
             })
         }
 
@@ -458,8 +469,10 @@ export class GameService implements OnModuleInit {
             charactorsAlive.push(newCharactor)
         }
 
-        if (createOptions) {
-            options = await this.createOptions(charactorsAlive, `${summary}\n\n${formatedDatatime}\n${plot}`)
+        const willEnd = charactorsAlive.filter((i) => !i.dead).length === 0
+
+        if (!willEnd && createOptions) {
+            options = await this.createOptions(charactors, `${summary}\n\n${formatedDatatime}\n${plot}`)
             this.send(`${id}`, {
                 type: 'options',
                 data: options
@@ -470,14 +483,16 @@ export class GameService implements OnModuleInit {
 
         const [newSummary, nextChoice] = await Promise.all([
             this.refineSummary(history, this.formatDatetime(date, time) + '\n' + plot),
-            this.makeVotes(id, options, new Date(), {
-                onProgress: (p) => {
-                    this.send(`${id}`, { type: 'votes', data: p })
-                },
-                onResult: (v) => {
-                    this.send(`${id}`, { type: 'voteResult', data: v })
-                }
-            })
+            willEnd
+                ? Promise.resolve(null)
+                : this.makeVotes(id, options, new Date(), {
+                      onProgress: (p) => {
+                          this.send(`${id}`, { type: 'votes', data: p })
+                      },
+                      onResult: (v) => {
+                          this.send(`${id}`, { type: 'voteResult', data: v })
+                      }
+                  })
         ])
 
         const gameState = await this.gameStateRepository.save(
@@ -496,6 +511,17 @@ export class GameService implements OnModuleInit {
         await this.gameRepository.update(id, { currentState: gameState.id })
         this.send(`${id}`, { type: 'state', data: gameState })
 
+        if (willEnd) {
+            this.logger.log(`游戏即将结束 status=${game.status}`)
+            const run = game.status === GameStatus.Running
+            return await this.end(id).then((res) => {
+                if (game.autoReset) {
+                    this.resetGame(id, run)
+                }
+                return res
+            })
+        }
+
         return gameState
     }
 
@@ -541,7 +567,7 @@ export class GameService implements OnModuleInit {
         const response = await this.callLLM([new HumanMessage(input)])
         plot = response.content
 
-        this.send(`${id}`, { type: 'ending', data: plot })
+        this.send(`${id}`, { type: 'plot', data: plot })
 
         const gameState = await this.gameStateRepository.save(
             new GameState({
@@ -561,11 +587,56 @@ export class GameService implements OnModuleInit {
         return gameState
     }
 
-    async startRun(id: number) {
-        if (this.controllers.has(id)) {
-            return
+    async resetGame(id: number, run: boolean = false) {
+        this.logger.log(`重置游戏 run=${run}`)
+        const game = await this.findById(id)
+        const history = await this.history(id)
+        const firstState = history.find((i) => i.firstState)
+        if (!firstState) {
+            throw new InternalServerErrorException('游戏未初始化')
+        }
+        const newGame = await this.gameRepository.save(
+            new Game({
+                ...game,
+                id: null,
+                resetNum: game.resetNum + 1,
+                status: GameStatus.Initialized
+            })
+        )
+        const newState = await this.gameStateRepository.save(
+            new GameState({
+                ...firstState,
+                id: null,
+                gameId: newGame.id,
+                firstState: true,
+                lastState: null
+            })
+        )
+        this.send(`${game.id}`, {
+            type: 'reset',
+            data: {
+                newGameId: newGame.id,
+                firstState: newState
+            }
+        })
+        await this.gameRepository.update(newGame.id, { currentState: newState.id })
+        await this.roomService.setCurrentGameId(game.roomId, newGame.id)
+
+        this.logger.log(`重置游戏完成`)
+        if (run) {
+            this.startRun(newGame.id)
         }
+        return newGame
+    }
+
+    async startRun(id: number) {
+        this.logger.log(`开始运行: ${id}`)
         const game = await this.findById(id)
+
+        if (this.controllers.has(game.roomId)) {
+            throw new InternalServerErrorException('该直播间已有游戏在运行')
+        }
+
         switch (game.status) {
             case GameStatus.Created:
                 throw new InternalServerErrorException('游戏未初始化')
@@ -576,10 +647,10 @@ export class GameService implements OnModuleInit {
         }
 
         const controller = new RunningController()
-        this.controllers.set(id, controller)
+        this.controllers.set(game.roomId, controller)
+        await this.gameRepository.update(game.id, { status: GameStatus.Running })
+        await this.roomService.setCurrentGameId(game.roomId, game.id)
         this.run(id, controller)
-        game.status = GameStatus.Running
-        await this.gameRepository.save(game)
     }
 
     async stopRun(id: number) {
@@ -588,7 +659,7 @@ export class GameService implements OnModuleInit {
             throw new InternalServerErrorException('游戏未运行')
         }
 
-        const controller = this.controllers.get(id)
+        const controller = this.controllers.get(game.roomId)
         if (controller) {
             await controller.stop()
         } else {
@@ -600,8 +671,8 @@ export class GameService implements OnModuleInit {
         let stop = false
         let stopReason = ''
         let resolve = null
+        const game = await this.gameRepository.findOneBy({ id })
         try {
-            const game = await this.gameRepository.findOneBy({ id })
             if (!game) {
                 throw new NotFoundException(`game #${id} not found`)
             }
@@ -632,7 +703,7 @@ export class GameService implements OnModuleInit {
         }
         this.logger.log('停止运行')
         if (resolve) resolve()
-        this.controllers.delete(id)
+        this.controllers.delete(game?.roomId)
         if (stopReason !== 'ending') {
             await this.gameRepository.update(id, { status: GameStatus.Initialized })
         }
@@ -663,18 +734,18 @@ export class GameService implements OnModuleInit {
         }
     }
 
-    async createOptions(charactors, text) {
+    async createOptions(charactors: Charactor[], summary: string) {
         for (let i = 0; i < 5; i++) {
             try {
                 if (i > 0) await setTimeout(1000)
                 const parser = StructuredOutputParser.fromZodSchema(
                     z.array(
                         z.object({
-                            content: z.string().describe('剧情发展方向'),
+                            content: z.string().describe('剧情发展选项'),
                             modifyHp: z.array(
                                 z.object({
                                     name: z.string().describe('角色名称'),
-                                    changeValue: z.number().describe('HP调整值')
+                                    changeValue: z.number().describe('HP值变化')
                                 })
                             )
                         })
@@ -683,16 +754,19 @@ export class GameService implements OnModuleInit {
                 const formatInstructions = parser.getFormatInstructions()
                 const prompt = new PromptTemplate({
                     template: await this.promptService.getPromptByName(PromptName.GenChoice),
-                    inputVariables: ['text'],
+                    inputVariables: ['charactors', 'summary'],
                     partialVariables: { format_instructions: formatInstructions }
                 })
                 const input = await prompt.format({
-                    text
+                    charactors: this.formatCharactors(charactors),
+                    summary
                 })
                 const response = await this.callLLM([new HumanMessage(input)])
                 const output = await parser.parse(response.content.replace('+', '').replace("'", '"'))
                 return output.map((i) => {
-                    i.modifyHp = i.modifyHp.filter((j) => charactors.find((k) => k.name === j.name))
+                    i.modifyHp = i.modifyHp.filter(
+                        (j) => j.changeValue !== 0 && charactors.find((k) => k.name === j.name)
+                    )
                     return i
                 })
             } catch (error) {
@@ -734,6 +808,7 @@ export class GameService implements OnModuleInit {
             throw new InternalServerErrorException('无法回退')
         } else {
             game.currentState = currentState.lastState
+            game.status = GameStatus.Initialized
             await this.gameRepository.save(game)
         }
     }