P2482 [SDOI2010] 猪国杀

方法论

这是一道复杂的模拟题。由于游戏规则的条目很多,我们需要仔细考虑程序的组织。否则,在编写程序的过程中极容易陷入停滞的状态(不知道下一步应该怎么做),或在发现程序出问题时,难以快速定位到错误点,对着一大堆代码“望洋兴叹”,不知如何下手。

在编写这种复杂程序的时候,可以采取以下的方法论:

  1. 首先需要对题目大致的流程和框架有所了解,这样我们才能明白这个程序的目的是什么,宏观上是如何运行的。在这个阶段,我们应当避免思考过于细节的问题,以免见树不见林,切入点太多,导致完全无从下手。
  2. 筛选出题目中最核心的,比较容易实现的部分,其他的高级、复杂的部分都是围绕这些简单的部分进行展开的。
  3. 编写这个最核心的部分,编写的时候可以采取 Wishful Thinking——在编写上层函数或者功能的时候,假设某些下层的函数或者功能已经实现,这样可以降低我们的思考负担,即在编写每个函数的时候,只考虑该层次的细节,屏蔽掉下层一些无关的细节。什么细节需要屏蔽,这就要具体的程序来看了。如果编写了太多函数来封装细节,也可能导致可读性的降低。什么可以认为是原子操作(最小的操作单元)就需要编写者自行斟酌了。通过对复杂问题进行分解,我们解决了一个个小问题,后期如果需要进一步改进程序的性能、扩充新的功能、修复错误,都会变得更加容易,因为处理一个独立的小问题永远比处理一整个大问题容易的多
  4. 在确保基本部分的正确的基础上,再一步步加上高级功能。每加上一个高级功能,都需要测试一下。这里我们采用渐进式的修改,一次添加一个新功能。如果一次性加入大量新功能,在调试的时候,就不知道是哪一个新功能导致的问题。

实践方法论

现在我们将这套方法论应用到本题目中,

这个题目需要我们编写一个卡牌游戏。可以简单将玩家分为两方——正方和反方,在游戏中,通过不断出牌来保护友方和攻击敌方,游戏的最终目的是杀死正方的主公或者所有反方的反贼。
游戏的大致流程如下:

  1. 玩家抽卡;
  2. 玩家出卡 and 其他玩家对此进行反应;
  3. 结束出卡,轮到下一个玩家抽卡和出牌。

好了,我们现在已经掌握核心游戏流程了。

现在我们来考虑游戏中最核心的部分。

角色状态

游戏的通过攻击来分出胜负,所以不同玩家要么各自为营,要么归属于不同的联盟。猪国杀中将游戏分为两个联盟——主公联盟和反方联盟。
游戏不仅划分了联盟,还将联盟的角色进行了划分。主公联盟有:主公角色和忠臣角色;反贼联盟有:反贼角色。

角色个数

主公:只能有一个;
忠臣和反贼:可以有多个。

角色属性

角色属性也就是所有用来描述角色的信息,包含:

  • 体力值 HP:初始为 4 点
  • 人设:包含了:从自己视角,自己的身份;从其他角色的视角,该角色的身份。
    从其他角色的视角,角色身份,分为已知、未知、错误认知。
    • 已知:主公初始状态是已知的(因为规定第一个出牌的人就是主公);其他人可以通过有针对性的攻击别人或者帮助别人来暴露自己的身份。
    • 未知:除了主公之外的所有角色的初始状态都是未知的。
    • 错误认知:如果一个忠臣触发了全体伤害的卡牌,不小心攻击了主公,那么主公就可能将他视作伪反贼。
  • 手牌:目前手中有的牌,初始为 4 张
  • 装备:该游戏中只有诸葛连弩这一种装备,初始为没有装备。
    角色的属性最好尽可能少,因为过多的属性容易加重认知负担,也就是角色的状态尽可能越简单越好。
    如果你尝试将人设替换为角色,你就需要额外再添加一个角色状态的属性(已知、未知、错误认知)。人设属性同时包含了角色(自己知道)和角色状态(其他人知道)。
    如果你尝试使用字符数组来表示手牌,那么你还需要维护一个手牌数量的属性;这里可以通过使用 std::string 来避免这个麻烦,std::string 自身包含了字符数量的属性。
    因为该游戏中只有诸葛连弩这一种装备,所以我们只需要将装备设置 bool 类型即可,也就是,角色只有有装备和没有装备两种状态。

最终我们得到角色属性的结构体定义如下:

struct Player
{
    std::string persona;
    std::string hand;
    int  hp = 4;
    bool equipped = false;
};

persona: 表示人设,是长度为 2 的字符串,第一个字符表示角色(M:主公、Z:忠臣、F:反贼),第二个字符表示角色状态(P:未知、1:已知、0:错误认知)。

回合状态

每个回合都至多使用一次杀(如果没有装备的话),所以我们使用一个变量来记录每个回合是否使用了杀。

bool use_strike = false;

游戏状态

我们需要维护一个玩家集合,这里我们使用 std::vector 来存储所有的玩家。

std::vector<Player> players;

我们需要实时知晓游戏是否已经达到结束状态(主公死亡或者全部反贼死亡)。为此我们需要维护一个变量,标记游戏是否已经结束。

bool game_over = false;

为了方便判断反贼是否全部阵亡,我还维护了一个存活的反贼人数的变量

int fp_cnt = 0;

我们还需要一个字符数组来表示牌堆,

char deck[2000];

以及一个指向牌堆顶部的索引,

int deck_idx = 0;

主循环

此时我们可以写出整个游戏的主循环

while(!game_over)
{
    draw_cards(cur_player, 2);
    while(can_play_a_card(cur_player));
    use_strike = false;
    cur_player = next_player(cur_player);
}

简单解释:

  1. 玩家从牌堆中抽两张卡
  2. 玩家从最左侧开始出第一张能用卡牌
  3. 不断重复第 2 步,直到没有卡可以出
  4. 重置回合状态
  5. 跳转到下一个玩家
  6. 判断游戏是否已经结束,如果没结束,则重新从第 1 步开始

抽牌的实现

抽牌的实现很容易,只要不断更新 deck_idx 以及把牌堆顶部的牌插入到手牌的右侧即可。

inline void draw_cards(int player_idx, int n_cards)
{
    for (int i = 0; i < n_cards; i++)
    {
        players[player_idx].hand.push_back(deck[deck_idx]);
        if (deck_idx < m - 1) { deck_idx++; }
    }
}

跳转到下一个玩家

因为游戏最终需要输出所有的玩家信息,所以即使已经死亡的玩家,我也不会将他从集合中删去。故在查找下一个玩家的时候,会跳过体力值为 0 的玩家,实现如下:

inline int next_player(int player_idx)
{
    int next_idx = (player_idx + 1) % players.size();
    while(players[next_idx].hp == 0)
    {
        next_idx = (next_idx + 1) % players.size();
    }
    return next_idx;
}

主动出牌

主动出牌是指在自己的回合内,从手牌中主用使用能使用的牌。
主动出牌能使用的牌包括:

  • 决斗
  • 南蛮入侵
  • 万箭齐发
  • 诸葛连弩

由于每张牌至多使用一次,每次出牌的时候,都必须丢弃这张牌。
所以每次出牌分为两步:

  1. 丢弃这张牌
  2. 使得这张牌开始生效

目前最容易实现的两种牌是:

  • 诸葛连弩

有关桃的实现如下:

if (card == 'P' && attacker.hp < 4)
{
    discard(attacker_idx, i);
    attacker.hp++;
}

有关诸葛连弩的实现如下:

else if (card == 'Z')
{
    discard(attacker_idx, i);
    attacker.equipped = true;
}

有关杀,我们需要考虑这几个方面:

  1. 玩家是否已经使用过杀?
  2. 玩家是否有装备,这允许玩家可以无限次使用杀?
  3. 玩家位置的下一个玩家是否是被玩家视作敌人?

  1. 玩家是否已经使用过杀可以通过 use_strike 变量来进行判断。
  2. 玩家是否已经装备可以通过玩家的 equipped 属性来进行判断。
  3. 是否为敌人,需要编写一个额外的函数来进行判断,以下几种情况,可以被视为敌人:
    1. 攻击方:主公;防守方:伪反贼、反贼
    2. 攻击方:忠臣;防守方:反贼
    3. 攻击方:反贼;防守方:主公、忠臣

代码如下:

inline bool is_enemy(int attacker_idx, int defender_idx)
{
   Player &attacker = players[attacker_idx];
   Player &defender = players[defender_idx];
   if (attacker.persona[0] == 'M' && (defender.persona == "Z0" || defender.persona == "F1")
   ||  attacker.persona[0] == 'Z' &&  defender.persona == "F1"
   ||  attacker.persona[0] == 'F' && (defender.persona == "M1" || defender.persona == "Z1"))
   {
       return true;
   }
   return false;
}

综合以上几个部分,我们得到杀的代码:

else if (card == 'K' && (!use_strike || attacker.equipped) && is_enemy(attacker_idx, next_player(attacker_idx)))
{
    discard(attacker_idx, i);
    do_strike(attacker_idx, next_player(attacker_idx));
}

inline void do_strike(int attacker_idx, int defender_idx)
{
    use_strike = true;
    expose_identity(attacker_idx);

    if (!can_play_the_card(defender_idx, 'D'))
    {
        lose_1hp(attacker_idx, defender_idx);
    }
}

决斗不受距离影响,所以

  • 反贼一定会决斗主公
  • 主公会决斗逆时针第一个遇到的反贼或者伪反贼,如果主公向伪反贼(即忠臣)进行攻击,则伪反贼不会做任何抵抗
  • 忠臣会决斗逆时针第一个遇到的反贼
  • 找不到就不决斗

注意:决斗会暴露身份。

决斗的代码如下:

else if (card == 'F' && next_enemy(attacker_idx) != -1)
{
    discard(attacker_idx, i);
    do_duel(attacker_idx, next_enemy(attacker_idx));
}

inline void do_duel(int attacker_idx, int defender_idx)
{
    Player &attacker = players[attacker_idx];
    Player &defender = players[defender_idx];

    expose_identity(attacker_idx);

    if (can_negate(attacker_idx, defender_idx))
    {
        return;
    }

    char loser;

    if (attacker.persona[0] == 'M' && defender.persona[0] == 'Z')
    {
        loser = 'D';
    }
    else while(true)
    {
        if (!can_play_the_card(defender_idx, 'K'))
        {
            loser = 'D';
            break;
        }

       if (!can_play_the_card(attacker_idx, 'K'))
        {
            loser = 'A';
            break;
        }
    }

    if (loser == 'D')
    {
        lose_1hp(attacker_idx, defender_idx);
    }
    else
    {
        lose_1hp(defender_idx, attacker_idx);
    }
}

上面的代码包含了一些未说明的函数,

  • expose_identity:暴露身份
  • can_negate:无懈可击
  • can_play_the_card:是否能出指定的牌,如果能出就出
  • lose_1hp:扣除一点体力值

我们先不考虑这些代码的实现,继续往下走


南蛮入侵和万箭齐发都是全体性伤害,唯一的区别是需要丢弃的牌不同。
这种全体性伤害的触发是有顺序的(逆时针方向),如果某个玩家被杀死,而且此时游戏判断为结束,那么需要中止南蛮入侵或者万箭齐发对后续玩家的触发,直接结束当前游戏。

代码如下:

else if (card == 'N')
{
    discard(attacker_idx, i);
    do_invasion(attacker_idx);
}
else if (card == 'W')
{
    discard(attacker_idx, i);
    do_arrows(attacker_idx);
}

inline void do_invasion(int attacker_idx)
{
    do_aoe(attacker_idx, 'K');
}

inline void do_arrows(int attacker_idx)
{
    do_aoe(attacker_idx, 'D');
}

inline void do_aoe(int attacker_idx, char card)
{
    Player &attacker = players[attacker_idx];

    int defender_idx = attacker_idx;
    while((defender_idx = next_player(defender_idx)) != attacker_idx)
    {
        if (can_negate(attacker_idx, defender_idx))
        {
            continue;
        }
        if (attacker.persona == "ZP" && players[defender_idx].persona[0] == 'M')
        {
            attacker.persona[1] = '0';
        }
        if (!can_play_the_card(defender_idx, card))
        {
            lose_1hp(attacker_idx, defender_idx);
        }
        if (game_over)
        {
            return;
        }
    }
}

综合上面的部分,我们现在可以写出完整的主动出牌函数了。代码如下:


inline bool can_play_a_card(int attacker_idx)
{
    Player &attacker = players[attacker_idx];

    for (int i = 0; i < attacker.hand.length(); i++)
    {
        char card = attacker.hand[i];
        if (card == 'P' && attacker.hp < 4)
        {
            discard(attacker_idx, i);
            attacker.hp++;
        }
        else if (card == 'Z')
        {
            discard(attacker_idx, i);
            attacker.equipped = true;
        }
        else if (card == 'K' && (!use_strike || attacker.equipped) && is_enemy(attacker_idx, next_player(attacker_idx)))
        {
            discard(attacker_idx, i);
            do_strike(attacker_idx, next_player(attacker_idx));
        }
        else if (card == 'F' && next_enemy(attacker_idx) != -1)
        {
            discard(attacker_idx, i);
            do_duel(attacker_idx, next_enemy(attacker_idx));
        }
        else if (card == 'N')
        {
            discard(attacker_idx, i);
            do_invasion(attacker_idx);
        }
        else if (card == 'W')
        {
            discard(attacker_idx, i);
            do_arrows(attacker_idx);
        }
        else
        {
            continue;
        }

        // NOTE: attacker can die in a duel
        if (!game_over && attacker.hp > 0) // play a card
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    return false; // play no card
}

我们需要注意如果玩家出牌之后,造成的游戏结束或者玩家自己的死亡(即使是主动出牌的人也会死,会死在的决斗中),我们需要让当前玩家停止后续的出牌,立刻中止当前的游戏。


其他的一些函数代码。

expose_identity:暴露身份

inline void expose_identity(int player_idx)
{
    players[player_idx].persona[1] = '1';
}

can_play_the_card:是否能出指定的牌,如果能出就出

inline bool can_play_the_card(int player_idx, char card)
{
    int pos = players[player_idx].hand.find(card);
    if (pos != std::string::npos)
    {
        discard(player_idx, pos);
        return true;
    }
    return false;
}

lose_1hp:扣除一点体力值,比较复杂,在这个函数我们需要做四件事情,

  1. 扣除防守方的体力值
  2. 防守方体力值为 0 时,尝试使用桃进行自救
  3. 防守方自救失败时,判断是否符合游戏结束条件
  4. 游戏未结束,判断是否需要是否奖惩机制
    1. 主公杀死忠臣,失去所有手牌,装备
    2. 任何角色杀死反贼,从牌堆中抽 3 张牌
inline void lose_1hp(int attacker_idx, int defender_idx)
{
    Player &attacker = players[attacker_idx];
    Player &defender = players[defender_idx];

    defender.hp--;

    // attempt to self-rescue
    if (defender.hp == 0)
    {
        if (can_play_the_card(defender_idx, 'P'))
        {
            defender.hp++;
        }
    }

    // game over?
    if (defender.hp == 0)
    {
        if (defender.persona[0] == 'M')
        {
            game_over = true;
            return;
        }
        else if (defender.persona[0] == 'F')
        {
            fp_cnt--;
            if (fp_cnt == 0)
            {
                game_over = true;
                return;
            }
        }
    }

    // reward and punishment
    if (defender.hp == 0)
    {
        if (defender.persona[0] == 'F')
        {
            draw_cards(attacker_idx, 3);
        }
        else if (attacker.persona[0] == 'M' && defender.persona[0] == 'Z')
        {
            attacker.hand = "";
            attacker.equipped = false;
        }
    }
}

目前我们已经实现了一个没有无懈可击的游戏。下面我们来考虑无懈可击。

无懈可击的作用就是使用锦囊牌无效化,可以无效化的锦囊牌包含无懈可击。
也就是针对某一锦囊牌(决斗、南蛮入侵、万箭齐发),正反双方不断出无懈可击,来确保这张锦囊卡生效或者失效。

注意:

  • 无懈可击会暴露角色的身份。
  • 攻击方可能对防守方使用无懈可击,比如发动全体性伤害的忠臣对主公。
  • 可以对自己使用无懈可击。
  • 防守方必须已经暴露身份了,才能使用无懈可击。

我们得到如下的代码:

inline bool can_negate(int attacker_idx, int defender_idx)
{
    if (players[defender_idx].persona[1] != '1') // hidden identity
    {
        return false;
    }

    bool succeeded = false;

    // NOTE: attacker and defender can be friends
    if (is_friend(attacker_idx, defender_idx))
    {
        if (can_play_the_card(attacker_idx, 'J'))
        {
            expose_identity(attacker_idx);
            succeeded = true;
        }
    }

    int latest_idx  = attacker_idx;
    int negator_idx = next_player(latest_idx);

    while(negator_idx != latest_idx)
    {
        Player &negator = players[negator_idx];

        if (is_friend(negator_idx, defender_idx) && !succeeded)
        {
            if (can_play_the_card(negator_idx, 'J'))
            {
                expose_identity(negator_idx);
                succeeded = true;
                latest_idx = negator_idx;
            }
        }
        else if (is_enemy(negator_idx, defender_idx) && succeeded)
        {
            if (can_play_the_card(negator_idx, 'J'))
            {
                expose_identity(negator_idx);
                succeeded = false;
                latest_idx = negator_idx;
            }
        }
        negator_idx = next_player(negator_idx);
    }

    return succeeded;
}

is_friend 判断两个玩家之间是否可以视作朋友,被观察者必须已经暴露身份,才能被观察者视作朋友。
注意:自己是自己的朋友!


inline bool is_friend(int observer_idx, int observable_idx)
{
    Player &observer   = players[observer_idx];
    Player &observable = players[observable_idx];
    if (observer.persona[0] == 'M' && (observable.persona == "Z1")
    ||  observer.persona[0] == 'F' &&  observable.persona == "F1"
    ||  observer.persona[0] == 'Z' && (observable.persona == "M1" || observable.persona == "Z1")
    ||  observer_idx == observable_idx) // NOTE: everyone is their own friend
    {
        return true;
    }
    return false;
}

至此,我们已经实现了所有的功能。

完整代码,完结撒花!

// https://www.luogu.com.cn/problem/P2482

#include <iostream>
#include <vector>
#include <string>

int n, m;

int deck_idx = 0;
char deck[2000];

bool game_over = false;
bool use_strike = false;

int fp_cnt = 0;

struct Player
{
    std::string persona;
    std::string hand;
    int  hp = 4;
    bool equipped = false;
};

std::vector<Player> players;

inline void discard(int player_idx, int i)
{
    players[player_idx].hand.erase(i, 1);
}

inline int next_player(int player_idx)
{
    int next_idx = (player_idx + 1) % players.size();
    while(players[next_idx].hp == 0)
    {
        next_idx = (next_idx + 1) % players.size();
    }
    return next_idx;
}

inline void draw_cards(int player_idx, int n_cards)
{
    for (int i = 0; i < n_cards; i++)
    {
        players[player_idx].hand.push_back(deck[deck_idx]);
        if (deck_idx < m - 1) { deck_idx++; }
    }
}

inline bool is_enemy(int attacker_idx, int defender_idx)
{
    Player &attacker = players[attacker_idx];
    Player &defender = players[defender_idx];
    if (attacker.persona[0] == 'M' && (defender.persona == "Z0" || defender.persona == "F1")
    ||  attacker.persona[0] == 'Z' &&  defender.persona == "F1"
    ||  attacker.persona[0] == 'F' && (defender.persona == "M1" || defender.persona == "Z1"))
    {
        return true;
    }
    return false;
}

inline bool is_friend(int observer_idx, int observable_idx)
{
    Player &observer   = players[observer_idx];
    Player &observable = players[observable_idx];
    if (observer.persona[0] == 'M' && (observable.persona == "Z1")
    ||  observer.persona[0] == 'F' &&  observable.persona == "F1"
    ||  observer.persona[0] == 'Z' && (observable.persona == "M1" || observable.persona == "Z1")
    ||  observer_idx == observable_idx) // NOTE: everyone is their own friend
    {
        return true;
    }
    return false;
}

inline void expose_identity(int player_idx)
{
    players[player_idx].persona[1] = '1';
}

inline bool can_play_the_card(int player_idx, char card)
{
    int pos = players[player_idx].hand.find(card);
    if (pos != std::string::npos)
    {
        discard(player_idx, pos);
        return true;
    }
    return false;
}

inline void lose_1hp(int attacker_idx, int defender_idx)
{
    Player &attacker = players[attacker_idx];
    Player &defender = players[defender_idx];

    defender.hp--;

    // attempt to self-rescue
    if (defender.hp == 0)
    {
        if (can_play_the_card(defender_idx, 'P'))
        {
            defender.hp++;
        }
    }

    // game over?
    if (defender.hp == 0)
    {
        if (defender.persona[0] == 'M')
        {
            game_over = true;
            return;
        }
        else if (defender.persona[0] == 'F')
        {
            fp_cnt--;
            if (fp_cnt == 0)
            {
                game_over = true;
                return;
            }
        }
    }

    // reward and punishment
    if (defender.hp == 0)
    {
        if (defender.persona[0] == 'F')
        {
            draw_cards(attacker_idx, 3);
        }
        else if (attacker.persona[0] == 'M' && defender.persona[0] == 'Z')
        {
            attacker.hand = "";
            attacker.equipped = false;
        }
    }
}

inline void do_strike(int attacker_idx, int defender_idx)
{
    use_strike = true;
    expose_identity(attacker_idx);

    if (!can_play_the_card(defender_idx, 'D'))
    {
        lose_1hp(attacker_idx, defender_idx);
    }
}

inline int next_enemy(int attacker_idx)
{
    Player &attacker = players[attacker_idx];

    int next_idx = next_player(attacker_idx);
    while(next_idx != attacker_idx)
    {
        Player &next = players[next_idx];
        if (attacker.persona[0] == 'F' &&  next.persona == "M1"
        ||  attacker.persona[0] == 'Z' &&  next.persona == "F1"
        ||  attacker.persona[0] == 'M' && (next.persona == "F1" || next.persona == "Z0"))
        {
            return next_idx;
        }
        next_idx = next_player(next_idx);
    }
    return -1;
}

inline bool can_negate(int attacker_idx, int defender_idx)
{
    if (players[defender_idx].persona[1] != '1') // hidden identity
    {
        return false;
    }

    bool succeeded = false;

    // NOTE: attacker and defender can be friends
    if (is_friend(attacker_idx, defender_idx))
    {
        if (can_play_the_card(attacker_idx, 'J'))
        {
            expose_identity(attacker_idx);
            succeeded = true;
        }
    }

    int latest_idx  = attacker_idx;
    int negator_idx = next_player(latest_idx);

    while(negator_idx != latest_idx)
    {
        Player &negator = players[negator_idx];

        if (is_friend(negator_idx, defender_idx) && !succeeded)
        {
            if (can_play_the_card(negator_idx, 'J'))
            {
                expose_identity(negator_idx);
                succeeded = true;
                latest_idx = negator_idx;
            }
        }
        else if (is_enemy(negator_idx, defender_idx) && succeeded)
        {
            if (can_play_the_card(negator_idx, 'J'))
            {
                expose_identity(negator_idx);
                succeeded = false;
                latest_idx = negator_idx;
            }
        }
        negator_idx = next_player(negator_idx);
    }

    return succeeded;
}

inline void do_duel(int attacker_idx, int defender_idx)
{
    Player &attacker = players[attacker_idx];
    Player &defender = players[defender_idx];

    expose_identity(attacker_idx);

    if (can_negate(attacker_idx, defender_idx))
    {
        return;
    }

    char loser;

    if (attacker.persona[0] == 'M' && defender.persona[0] == 'Z')
    {
        loser = 'D';
    }
    else while(true)
    {
        if (!can_play_the_card(defender_idx, 'K'))
        {
            loser = 'D';
            break;
        }

       if (!can_play_the_card(attacker_idx, 'K'))
        {
            loser = 'A';
            break;
        }
    }

    if (loser == 'D')
    {
        lose_1hp(attacker_idx, defender_idx);
    }
    else
    {
        lose_1hp(defender_idx, attacker_idx);
    }
}

inline void do_aoe(int attacker_idx, char card)
{
    Player &attacker = players[attacker_idx];

    int defender_idx = attacker_idx;
    while((defender_idx = next_player(defender_idx)) != attacker_idx)
    {
        if (can_negate(attacker_idx, defender_idx))
        {
            continue;
        }
        if (attacker.persona == "ZP" && players[defender_idx].persona[0] == 'M')
        {
            attacker.persona[1] = '0';
        }
        if (!can_play_the_card(defender_idx, card))
        {
            lose_1hp(attacker_idx, defender_idx);
        }
        if (game_over)
        {
            return;
        }
    }
}

inline void do_invasion(int attacker_idx)
{
    do_aoe(attacker_idx, 'K');
}

inline void do_arrows(int attacker_idx)
{
    do_aoe(attacker_idx, 'D');
}

inline bool can_play_a_card(int attacker_idx)
{
    Player &attacker = players[attacker_idx];

    for (int i = 0; i < attacker.hand.length(); i++)
    {
        char card = attacker.hand[i];
        if (card == 'P' && attacker.hp < 4)
        {
            discard(attacker_idx, i);
            attacker.hp++;
        }
        else if (card == 'Z')
        {
            discard(attacker_idx, i);
            attacker.equipped = true;
        }
        else if (card == 'K' && (!use_strike || attacker.equipped) && is_enemy(attacker_idx, next_player(attacker_idx)))
        {
            discard(attacker_idx, i);
            do_strike(attacker_idx, next_player(attacker_idx));
        }
        else if (card == 'F' && next_enemy(attacker_idx) != -1)
        {
            discard(attacker_idx, i);
            do_duel(attacker_idx, next_enemy(attacker_idx));
        }
        else if (card == 'N')
        {
            discard(attacker_idx, i);
            do_invasion(attacker_idx);
        }
        else if (card == 'W')
        {
            discard(attacker_idx, i);
            do_arrows(attacker_idx);
        }
        else
        {
            continue;
        }

        // NOTE: attacker can die in a duel
        if (!game_over && attacker.hp > 0) // play a card
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    return false; // play no card
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::cin >> n >> m;

    int cur_player;
    players.resize(n);
    for (int i = 0; i < n; i++)
    {
        std::cin >> players[i].persona;

        if (players[i].persona == "MP")
        {
            players[i].persona = "M1";
            cur_player = i;
        }

        if (players[i].persona == "FP")
        {
            fp_cnt++;
        }

        for(int j = 0; j < 4; j++)
        {
            players[i].hand.resize(4);
            std::cin >> players[i].hand[j];
        }
    }

    for (int i = 0; i < m; i++)
    {
        std::cin >> deck[i];
    }

    while(!game_over)
    {
        draw_cards(cur_player, 2);
        while(can_play_a_card(cur_player));
        use_strike = false;
        cur_player = next_player(cur_player);
    }

    if (fp_cnt > 0)
    {
        std::cout << "FP\n";
    }
    else
    {
        std::cout << "MP\n";
    }

    for (int i = 0; i < players.size(); i++)
    {
        if (players[i].hp == 0)
        {
            std::cout << "DEAD";
        }
        else for (int j = 0; j < players[i].hand.length(); j++)
        {
            std::cout << players[i].hand[j] << ' ';
        }
        std::cout << '\n';
    }
    return 0;
}
posted @ 2023-03-18 20:36  Revc  阅读(283)  评论(0编辑  收藏  举报