|
|
@@ -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)
|
|
|
}
|
|
|
}
|