任务:实现 BOSS 战 AI 决策函数

任务:实现 BOSS 战 AI 决策函数

一、背景概述

在《极妙幻境》中,第 4、7、10 层结束后会触发 BOSS 战。BOSS 由心魔扮演,其出牌策略需要由 AI 控制。你需要实现一个纯函数,根据当前牌局状态(BOSS 视角)决定 BOSS 应执行的动作(出牌、质疑、跟牌或不跟)。该函数不维护内部状态,所有依赖通过参数传入,便于测试和集成。

二、函数签名与数据结构

from enum import Enum
from typing import List, Dict, Optional, Tuple, Union
import random

class CardType(Enum):
    GOOD = "good"
    EVIL = "evil"
    NEUTRAL = "neutral"
    WILD = "wild"

# 手牌表示为字典,如 {'good':5, 'evil':3, 'neutral':2, 'wild':1}
Hand = Dict[CardType, int]

class GameState:
    """BOSS 视角的牌局状态"""
    boss_hand: Hand                 # BOSS 自己的手牌分布
    player_hand_count: int          # 玩家手牌总数(BOSS 可观察)
    table_pile_count: int           # 桌面牌堆累积的张数(具体牌型未知)
    last_claim: Optional[CardType]  # 上一轮对手宣称的类型(若 BOSS 刚出完牌则为 None)
    is_boss_turn: bool              # True 表示当前是 BOSS 的出牌轮次,False 表示 BOSS 需回应玩家的出牌
    rounds_since_last_challenge: int = 0  # 距离上次质疑经过的轮数(可选,用于策略)

动作返回值格式(字典):

# 当 is_boss_turn == True(BOSS 出牌)
{
    "action": "play",
    "num_cards": int,          # 出牌张数,1~3,不得超过手牌总数
    "claim": CardType          # 宣称的类型(可能虚张声势)
}

# 当 is_boss_turn == False(BOSS 回应)
{
    "action": "challenge" | "follow" | "fold",
    # 如果 follow,还需以下字段:
    "num_cards": int,          # 跟牌张数
    "claim": CardType          # 跟牌时宣称的类型
}

三、决策逻辑

3.1 辅助函数(你需要实现)

  • count_total_cards(hand: Hand) -> int:返回手牌总张数。
  • get_most_common_type(hand: Hand) -> Optional[CardType]:返回手牌中数量最多的普通牌类型(忽略万能牌),若普通牌全为0则返回 None。
  • can_claim(hand: Hand, claim_type: CardType, num_cards: int) -> bool:判断手牌中指定类型的普通牌 + 万能牌数量是否 ≥ num_cards。
  • random_choice(options: List, probabilities: List[float]):根据概率列表随机选择一项。

3.2 BOSS 出牌逻辑(is_boss_turn == True)

# 1. 决定出牌张数
total_cards = count_total_cards(state.boss_hand)
if total_cards <= 3:
    num = total_cards
else:
    # 随机出1~3张,概率可调(例如 1张30%,2张50%,3张20%)
    num = random_choice([1,2,3], [0.3, 0.5, 0.2])

# 2. 决定宣称类型
# 先判断是否虚张声势:虚张概率 20%
if random.random() < 0.2:
    # 虚张:宣称一个与手牌最多的类型不同的类型(若存在其他类型,随机选一个;否则诚实)
    most = get_most_common_type(state.boss_hand)
    other_types = [t for t in CardType if t != most and t != CardType.WILD]
    if other_types:
        claim = random.choice(other_types)
    else:
        # 没有其他类型,只能诚实
        claim = most if most is not None else random.choice([CardType.GOOD, CardType.EVIL, CardType.NEUTRAL])
else:
    # 诚实:宣称自己手牌最多的类型
    most = get_most_common_type(state.boss_hand)
    if most is None:
        # 只有万能牌,随机宣称
        claim = random.choice([CardType.GOOD, CardType.EVIL, CardType.NEUTRAL])
    else:
        claim = most

# 3. 验证宣称可行性(若不可行,回退到诚实出牌,用最多的类型,且张数不超过可用牌数)
if not can_claim(state.boss_hand, claim, num):
    # 回退:用最多的普通牌类型,若没有则用万能牌冒充任意类型
    most = get_most_common_type(state.boss_hand)
    if most is None:
        # 只有万能牌,随便选一个类型,但确保万能牌数量足够
        claim = CardType.GOOD
        # 如果万能牌不足 num,则 num 调整为万能牌数量
        num = min(num, state.boss_hand.get(CardType.WILD, 0))
    else:
        # 用最多的类型,但张数不能超过该类型普通牌+万能牌总数
        available = state.boss_hand.get(most, 0) + state.boss_hand.get(CardType.WILD, 0)
        if available < num:
            num = available
        claim = most

return {"action": "play", "num_cards": num, "claim": claim}

3.3 BOSS 回应逻辑(is_boss_turn == False)

此时 state.last_claim 是玩家上一轮宣称的类型。

# 1. 计算质疑倾向(怀疑度)
suspicion = 0.3  # 基础怀疑度
# 玩家手牌少时更可能虚张,提高怀疑
if state.player_hand_count <= 3:
    suspicion += 0.2
# 桌面牌堆多时,玩家可能想一次性清空,提高怀疑
if state.table_pile_count >= 5:
    suspicion += 0.2
# 可根据其他因素调整,但不超过1
suspicion = min(1.0, suspicion)

# 2. 质疑决策(阈值0.6)
if suspicion > 0.6:
    return {"action": "challenge"}

# 3. 不跟决策
total_boss_cards = count_total_cards(state.boss_hand)
if state.table_pile_count >= 6 and total_boss_cards <= 5:
    return {"action": "fold"}

# 4. 否则跟牌
# 跟牌时决定张数和宣称类型(策略与出牌类似)
total_cards = total_boss_cards
if total_cards <= 3:
    num = total_cards
else:
    num = random_choice([1,2,3], [0.3, 0.5, 0.2])

# 决定宣称类型(同样有20%虚张概率)
if random.random() < 0.2:
    most = get_most_common_type(state.boss_hand)
    other_types = [t for t in CardType if t != most and t != CardType.WILD]
    if other_types:
        claim = random.choice(other_types)
    else:
        claim = most if most is not None else random.choice([CardType.GOOD, CardType.EVIL, CardType.NEUTRAL])
else:
    most = get_most_common_type(state.boss_hand)
    if most is None:
        claim = random.choice([CardType.GOOD, CardType.EVIL, CardType.NEUTRAL])
    else:
        claim = most

# 验证宣称可行性(同出牌逻辑)
if not can_claim(state.boss_hand, claim, num):
    most = get_most_common_type(state.boss_hand)
    if most is None:
        claim = CardType.GOOD
        num = min(num, state.boss_hand.get(CardType.WILD, 0))
    else:
        available = state.boss_hand.get(most, 0) + state.boss_hand.get(CardType.WILD, 0)
        if available < num:
            num = available
        claim = most

return {"action": "follow", "num_cards": num, "claim": claim}

四、辅助函数说明

请实现以下辅助函数(可放在同一文件中):

def count_total_cards(hand: Hand) -> int:
    return sum(hand.values())

def get_most_common_type(hand: Hand) -> Optional[CardType]:
    """返回普通牌中数量最多的类型,忽略万能牌;若所有普通牌都为0,返回None"""
    ordinary = {t: hand.get(t, 0) for t in [CardType.GOOD, CardType.EVIL, CardType.NEUTRAL]}
    max_count = max(ordinary.values())
    if max_count == 0:
        return None
    # 若有多个相同最大值,可随机选一个(但为了简单,取第一个)
    for t, cnt in ordinary.items():
        if cnt == max_count:
            return t
    return None

def can_claim(hand: Hand, claim_type: CardType, num_cards: int) -> bool:
    """检查能否宣称 num_cards 张 claim_type(可用万能牌补足)"""
    ordinary = hand.get(claim_type, 0)
    wild = hand.get(CardType.WILD, 0)
    return ordinary + wild >= num_cards

def random_choice(options: List, probs: List[float]):
    """根据概率列表随机选择一项"""
    return random.choices(options, weights=probs)[0]

五、测试要求

编写单元测试覆盖以下场景:

  1. BOSS 出牌(手牌充足):验证出牌张数和宣称类型符合预期(随机性可通过固定种子统计)。
  2. BOSS 出牌(手牌很少):手牌≤3时,应出所有牌。
  3. BOSS 出牌(虚张概率):固定种子,多次调用,验证虚张概率接近20%。
  4. BOSS 回应(质疑):构造高怀疑度场景(玩家手牌少、桌面牌堆多),期望返回 challenge
  5. BOSS 回应(不跟):构造桌面牌堆≥6且BOSS手牌≤5,期望返回 fold
  6. BOSS 回应(跟牌):常规情况,返回 follow 且张数、宣称合理。
  7. 宣称可行性回退:故意让决策出的 numclaim 不可行,验证函数自动回退到可行值。
  8. 边界情况:手牌全为万能牌时,决策应正确处理。

六、提交要求

  • 提供完整 Python 代码文件,包含所有函数及测试。
  • 使用 unittestpytest,测试通过。
  • 注释清晰,关键逻辑说明。

开始实现吧!

posted @ 2026-03-15 20:48  神秘园欢迎您  阅读(0)  评论(0)    收藏  举报