package game import ( "errors" "fmt" "math/rand" "time" ) type PlayerState struct { ID string Nickname string IsBot bool Hand []*Card SkillZone []*Card ChallengeZone []*Card IsExited bool InfectedActive bool } type Engine struct { Players []*PlayerState HarmonyZone []*Card CurrentTurn int Phase string TurnPhase string HarmonyTarget int Setting GameSetting SelectedCard *Card SelectedBy string Effect *EffectState rng *rand.Rand } func NewEngine(playerIDs []string, nicknames []string, botFlags []bool, playerCount int) *Engine { e := &Engine{ Phase: "dealing", rng: rand.New(rand.NewSource(time.Now().UnixNano())), } for i, pid := range playerIDs { e.Players = append(e.Players, &PlayerState{ ID: pid, Nickname: nicknames[i], IsBot: botFlags[i], }) } return e } func (e *Engine) Deal() { deck, setting := BuildDeck(len(e.Players)) e.Setting = setting e.HarmonyTarget = setting.HarmonyTarget idx := 0 for idx < len(deck) { for _, p := range e.Players { if idx >= len(deck) { break } p.Hand = append(p.Hand, deck[idx]) idx++ } } e.CurrentTurn = e.findStartingPlayer() e.Phase = "playing" e.TurnPhase = "select_card" e.startTurn() } func (e *Engine) findStartingPlayer() int { for i, p := range e.Players { for _, c := range p.Hand { if c.TypeID == "student_council_president" { return i } } } return e.rng.Intn(len(e.Players)) } func (e *Engine) startTurn() { if e.CheckInfectedEffect() { e.TurnPhase = "effect_resolving" } else { e.TurnPhase = "select_card" } } func (e *Engine) CurrentPlayer() *PlayerState { if e.CurrentTurn < 0 || e.CurrentTurn >= len(e.Players) { return nil } return e.Players[e.CurrentTurn] } func (e *Engine) GetPlayer(id string) *PlayerState { for _, p := range e.Players { if p.ID == id { return p } } return nil } func (e *Engine) SelectCard(playerID, cardUID string) error { if e.Phase != "playing" || e.TurnPhase != "select_card" { return errors.New("cannot select card now") } cp := e.CurrentPlayer() if cp == nil || cp.ID != playerID { return errors.New("not your turn") } for _, c := range cp.Hand { if c.UID == cardUID { if IsUnusable(c.TypeID) { return fmt.Errorf("「%s」无法被使用", c.Name) } e.SelectedCard = c e.SelectedBy = playerID e.TurnPhase = "select_action" return nil } } return errors.New("card not in hand") } func (e *Engine) ConfirmAction(playerID, action string, targetPlayerID string) error { if e.Phase != "playing" { return errors.New("not playing") } cp := e.CurrentPlayer() if cp == nil || cp.ID != playerID { return errors.New("not your turn") } if e.SelectedCard == nil { return errors.New("no card selected") } switch action { case "skill": return e.doSkill(cp) case "harmony": return e.doHarmony(cp) case "challenge": if e.TurnPhase == "select_action" && targetPlayerID == "" { e.TurnPhase = "select_target" return nil } return e.doChallenge(cp, targetPlayerID) } return errors.New("unknown action") } func (e *Engine) doSkill(p *PlayerState) error { card := e.SelectedCard e.removeFromHand(p, card.UID) p.SkillZone = append(p.SkillZone, card) e.SelectedCard = nil e.SelectedBy = "" if e.triggerEffect(p, card) { e.TurnPhase = "effect_resolving" return nil } e.finishTurn(p) return nil } func (e *Engine) doHarmony(p *PlayerState) error { card := e.SelectedCard e.removeFromHand(p, card.UID) e.HarmonyZone = append(e.HarmonyZone, card) e.SelectedCard = nil e.SelectedBy = "" e.finishTurn(p) return nil } func (e *Engine) doChallenge(p *PlayerState, targetID string) error { if targetID == "" || targetID == p.ID { return errors.New("invalid challenge target") } target := e.GetPlayer(targetID) if target == nil { return errors.New("target not found") } card := e.SelectedCard e.removeFromHand(p, card.UID) target.ChallengeZone = append(target.ChallengeZone, card) e.SelectedCard = nil e.SelectedBy = "" e.finishTurn(p) return nil } func (e *Engine) finishTurn(p *PlayerState) { if len(p.Hand) <= 1 { p.IsExited = true } if e.allExited() { e.Phase = "all_exited" e.TurnPhase = "" return } e.advanceTurn() e.startTurn() } func (e *Engine) advanceTurn() { n := len(e.Players) next := (e.CurrentTurn + 1) % n for i := 0; i < n; i++ { if !e.Players[next].IsExited { e.CurrentTurn = next return } next = (next + 1) % n } } func (e *Engine) allExited() bool { for _, p := range e.Players { if !p.IsExited { return false } } return true } func (e *Engine) removeFromHand(p *PlayerState, uid string) { for i, c := range p.Hand { if c.UID == uid { p.Hand = append(p.Hand[:i], p.Hand[i+1:]...) return } } } func (e *Engine) IsEffectResolving() bool { return e.Effect != nil && e.Effect.Active } func (e *Engine) EffectNeedsResponseFrom(playerID string) bool { if !e.IsEffectResolving() { return false } ef := e.Effect if ef.ChoiceType == "all_select_card" { _, done := ef.PendingResponses[playerID] p := e.GetPlayer(playerID) return !done && p != nil && !p.IsExited && len(p.Hand) > 0 } return ef.WaitingFor == playerID } func (e *Engine) BotAutoPlay(playerID string) (string, string) { p := e.GetPlayer(playerID) if p == nil || !p.IsBot || p.IsExited { return "", "" } var playable []*Card for _, c := range p.Hand { if !IsUnusable(c.TypeID) { playable = append(playable, c) } } if len(playable) == 0 { return "", "" } card := playable[e.rng.Intn(len(playable))] e.SelectedCard = card e.SelectedBy = playerID roll := e.rng.Intn(10) if roll < 4 { return "harmony", "" } else if roll < 7 { return "skill", "" } else { var targets []string for _, o := range e.Players { if o.ID != playerID && !o.IsExited { targets = append(targets, o.ID) } } if len(targets) == 0 { return "harmony", "" } return "challenge", targets[e.rng.Intn(len(targets))] } } func (e *Engine) BotAutoEffectResponse(playerID string) map[string]interface{} { ef := e.Effect if ef == nil || !ef.Active { return nil } p := e.GetPlayer(playerID) if p == nil { return nil } resp := make(map[string]interface{}) switch ef.ChoiceType { case "view_cards": resp["ok"] = true case "select_player": if targets, ok := ef.ChoiceData["targets"].([]map[string]interface{}); ok && len(targets) > 0 { resp["player_id"] = targets[e.rng.Intn(len(targets))]["player_id"] } case "select_card_from_hand", "all_select_card": if len(p.Hand) > 0 { playable := p.Hand var filtered []*Card for _, c := range playable { if !IsUnusable(c.TypeID) { filtered = append(filtered, c) } } if len(filtered) > 0 { playable = filtered } resp["card_uid"] = playable[e.rng.Intn(len(playable))].UID } case "select_skill_card": if cards, ok := ef.ChoiceData["cards"].([]map[string]interface{}); ok && len(cards) > 0 { resp["card_uid"] = cards[e.rng.Intn(len(cards))]["uid"] } case "select_challenge_card": if cards, ok := ef.ChoiceData["cards"].([]map[string]interface{}); ok && len(cards) > 0 { c := cards[e.rng.Intn(len(cards))] resp["card_uid"] = c["uid"] resp["owner_id"] = c["owner_id"] } case "select_harmony_card": if cards, ok := ef.ChoiceData["cards"].([]map[string]interface{}); ok && len(cards) > 0 { resp["card_uid"] = cards[e.rng.Intn(len(cards))]["uid"] } case "yes_no": resp["yes"] = e.rng.Intn(2) == 0 case "select_option": if opts, ok := ef.ChoiceData["options"].([]map[string]interface{}); ok && len(opts) > 0 { resp["value"] = opts[e.rng.Intn(len(opts))]["value"] } else if opts2, ok := ef.ChoiceData["options"].([]interface{}); ok && len(opts2) > 0 { if m, ok := opts2[e.rng.Intn(len(opts2))].(map[string]interface{}); ok { resp["value"] = m["value"] } } } return resp }