Files
nl-game-api/internal/ai/chess_llm.go
2026-08-14 13:17:03 +08:00

89 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ai
import (
"context"
"fmt"
"strings"
"time"
"nl-game-api-gin/internal/gamecore/xiangqi"
)
// llmChessAI 大模型象棋 AI服务端先用搜索引擎给候选着法排序
// 再按难度截取候选交给 LLM 挑选(困难=只给高分着法,简单=全量随机)
type llmChessAI struct {
client *LLMClient // LLM 客户端
difficulty string // 难度
fallback ChessAI // 规则 AI 兜底
name string // 显示名
}
// Name AI 显示名
func (a *llmChessAI) Name() string { return a.name }
// boardText 把棋盘渲染成文本LLM 提示词用)
func boardText(b *xiangqi.Board) string {
var sb strings.Builder
sb.WriteString(" 0 1 2 3 4 5 6 7 8\n")
for r := 0; r < 10; r++ {
sb.WriteString(fmt.Sprintf("%d ", r))
for c := 0; c < 9; c++ {
p := b[r][c]
if p == 0 {
sb.WriteString(" ")
} else {
sb.WriteString(xiangqi.PieceName(p) + " ")
}
}
sb.WriteString("\n")
}
return sb.String()
}
// DecideMove 走子:候选着法按引擎评分排序后交 LLM 选择
func (a *llmChessAI) DecideMove(b *xiangqi.Board, side int) (xiangqi.Move, string) {
// 用 2 层搜索为候选排序困难截取前6中等前12简单全部
ranked := b.RankedMoves(side, 2)
if len(ranked) == 0 {
return xiangqi.Move{}, ""
}
limit := len(ranked)
switch a.difficulty {
case DiffHard:
limit = 6
case DiffMedium:
limit = 12
}
if limit > len(ranked) {
limit = len(ranked)
}
shown := ranked[:limit]
// 组装候选清单(附带吃子信息帮助 LLM 判断)
options := make([]string, 0, len(shown))
for i, m := range shown {
target := b[m.ToR][m.ToC]
capture := ""
if target != 0 {
capture = "(吃" + xiangqi.PieceName(target) + ""
}
options = append(options, fmt.Sprintf("%d: %s从(%d,%d)走到(%d,%d)%s",
i, b.MoveDesc(m), m.FromR, m.FromC, m.ToR, m.ToC, capture))
}
sideName := "红方"
if side == xiangqi.Black {
sideName = "黑方"
}
system := styleFor(a.difficulty) + "你在下中国象棋,执" + sideName + "。" +
"只输出 JSON{\"choice\":候选编号,\"say\":\"一句台词\"},不要输出其他内容。"
user := fmt.Sprintf("当前棋盘行0在上为黑方行9在下为红方\n%s\n候选着法已按棋力排序越靠前越好\n%s\n请选择一个候选编号。",
boardText(b), strings.Join(options, "\n"))
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
defer cancel()
// 带返回校验与重试的决策调用(传输层自动重试 + 输出非法自动纠错重问)
if d, err := a.client.ChatDecision(ctx, system, user, temperatureFor(a.difficulty), len(shown)-1); err == nil {
return shown[d.Choice], d.Say
}
// LLM 多次重试仍失败:走规则兜底
return a.fallback.DecideMove(b, side)
}