猪国杀(模板)

请使用指定的代码模板完成该题~

  • 由于原题在题面和数据上的一些小问题,本题经过了一些小修小改,数据,和题面,与 SDOI2010 略有不同。参考网络资料将让你在完成作业的路径上绕远路(不保证可行)并且收获甚少。相信自己,独立完成会收获很多,你也可以在思考无所获后向同学请教。
  • 代码模板精心制作,请大家理解后进行补全代码实验
    • 本题代码模板不严格遵守面向对象的规则,而是面向教育用途设计
    • 比起费力优化代码的常数复杂度,更乐于精心设计将代码逻辑区分开
  • 本实验考点:
    • 问题理解及其逻辑化能力
    • 大型(显然并不大 第三方代码框架理解和分析能力
    • 工程改动的创口分析和实践能力
    • 程序调试能力
  • 期末将近,预祝同学们期末考得好成绩,不负努力,加油。

背景

《猪国杀》是一种多猪牌类回合制游戏,一共有 3 种角色:主猪,忠猪,反猪。每局游戏主猪有且只有 1 只,忠猪和反猪可以有多只,每只猪扮演 1 种角色。

目的

主猪 / MP:自己存活的情况下消灭所有的反猪。
忠猪 / ZP:不惜一切保护主猪,胜利条件与主猪相同。
反猪 / FP:杀死主猪。

游戏过程

游戏开始时,每个玩家手里都会有 4 张牌,且体力上限和初始体力都是 4 。

开始游戏时,从主猪开始,按照逆时针方向(数据中就是按照编号从 1,2,3n1 , 2, 3 \ldots n 的顺序)依次行动。

每个玩家自己的回合可以分为 2 个阶段:

  • 摸牌阶段:从牌堆顶部摸 2 张牌,依次放到手牌的最右边;
  • 出牌阶段:你可以使用任意张牌,每次使用牌的时候都使用最靠左的能够使用的牌。当然,要满足如下规则:
  1. 如果没有猪哥连弩,每个出牌阶段只能使用 1 次「杀」来攻击;
  2. 任何牌被使用后被弃置(武器是装备上);被弃置的牌以后都不能再用,即与游戏无关。

各种牌介绍

每张手牌用 1 个字母表示,字母代表牌的种类。

基本牌

『桃 / P』在自己的回合内,如果自己的体力值不等于体力上限,那么使用 1 个桃可以为自己补充 1 点体力,否则不能使用桃;桃只能对自己使用;在自己的回合外,如果自己的血变为 0 或者更低,那么也可以使用。

『杀 / K』在自己的回合内,对攻击范围内除自己以外的 1 名角色使用。如果没有被『闪』抵消,则造成 1 点伤害。无论有无武器,杀的攻击范围都是 1。

『闪 / D』当你受到杀的攻击时,可以弃置 1 张闪来抵消杀的效果。

锦囊牌

『决斗 / F』出牌阶段,对除自己以外任意 1 名角色使用,由目标角色先开始,自己和目标角色轮流弃置 1 张杀,首先没有杀可弃的一方受到 1 点伤害,另一方视为此伤害的来源。

『南猪入侵 / N』出牌阶段,对除你以外所有角色使用,按逆时针顺序从使用者下家开始依次结算,除非弃置 1 张杀,否则受到 1 点伤害。

『万箭齐发 / W』和南猪入侵类似,不过要弃置的不是杀而是闪。

『无懈可击 / J』在目标锦囊生效前抵消其效果。每次有 1 张锦囊即将生效时,从使用这张锦囊的猪开始,按照逆时针顺序,依次得到使用无懈可击的机会;效果:用于决斗时,决斗无效并弃置;用于南猪入侵或万箭齐发时,当结算到某个角色时才能使用,当前角色不需弃置牌并且不会受到伤害(仅对 1 个角色产生效果);用于无懈可击时,成为目标的无懈可击被无效。

装备牌

『猪哥连弩 / Z』武器,攻击范围 1 ,出牌阶段你可以使用任意张杀; 同一时刻最多只能装 1 把武器;如果先前已经有了 1 把武器,那么之后再装武器的话,会弃置以前的武器来装现在的武器。

特殊事件及概念解释

伤害来源:杀、南猪入侵、万箭齐发的伤害来源均是使用该牌的猪,决斗的伤害来源如上;

距离:两只猪的距离定义为沿着逆时针方向间隔的猪数 +1 。即初始时 1 和 2 的距离为 1 ,但是 2 和 1 的距离就是 n−1 。注意一个角色的死亡会导致一些猪距离的改变;

玩家死亡:如果该玩家的体力降到 0 或者更低,并且自己手中没有足够的桃使得自己的体力值回到 1 ,那么就死亡了,死亡后所有的牌(装备区,手牌区)被弃置

奖励与惩罚:反猪死亡时,最后一个伤害来源处(即使是反猪)立即摸 3 张牌。忠猪死亡时,如果最后一个伤害来源是主猪,那么主猪所有装备牌、手牌被弃置。

注意:一旦达成胜利条件,游戏立刻结束,因此即使会摸 3 张牌或者还有牌可以用也不用执行了。

现在,我们已经知道每只猪的角色、手牌,还有牌堆初始情况,并且假设每个角色会按照如下的行为准则进行游戏,你需要做的就是告诉最后的结果。

几种行为:

献殷勤:使用无懈可击挡下南猪入侵、万箭齐发、决斗;使用无懈可击抵消表敌意的锦囊效果;
表敌意:对某个角色使用杀、决斗;使用无懈可击抵消献殷勤的锦囊效果;
跳忠:即通过行动表示自己是忠猪。跳忠行动就是对主猪或对某只已经跳忠的猪献殷勤,或者对某只已经跳反的猪表敌意;
跳反:即通过行动表示自己是反猪。跳反行动就是对主猪或对某只已经跳忠的猪表敌意,或者对某只已经跳反的猪献殷勤。

注意:忠猪不会跳反,反猪也不会跳忠;不管是忠猪还是反猪,能够跳必然跳

行动准则

共性

  • 每个角色如果手里有桃且生命值未满,那么必然吃掉;
  • 有南猪入侵、万箭齐发、必然使用;有装备必然装上;
  • 受到杀时,有闪必然弃置;
  • 响应南猪入侵或者万箭齐发时候,有杀 / 闪必然弃置;
  • 不会对未表明身份的猪献殷勤(包括自己)。

特性

主猪:

  • 主猪会认为「没有跳身份,且用南猪入侵 / 万箭齐发对自己造成伤害的猪」是类反猪(没伤害到不算,注意类反猪并没有表明身份),如果之后跳了,那么主猪会重新认识这只猪;
  • 对于每种表敌意的方式,对逆时针方向能够执行到的第一只已跳反猪表;如果没有,对逆时针方向能够执行到的第一只类反猪表,再没有,那么就不表敌意;
  • 决斗时会不遗余力弃置杀;
  • 如果能对已经跳忠的猪或自己献殷勤,那么一定献;如果能够对已经跳反的猪表敌意,那么一定表。

忠猪:

  • 对于每种表敌意的方式,对「逆时针方向能够执行到的第一只已经跳反的猪」表,如果没有,那么就不表敌意;
    决斗时,如果对方是主猪,那么不会弃置杀,否则,会不遗余力弃置杀;
  • 如果有机会对主猪或者已经跳忠的猪献殷勤,那么一定献。

反猪:

  • 对于每种表敌意的方式,如果有机会则对主猪表,否则,对「逆时针方向能够执行到的第一只已经跳忠的猪」表,如果没有,那么就不表敌意;
  • 决斗时会不遗余力弃置杀;
  • 如果有机会对已经跳反的猪献殷勤,那么一定献。

输入格式

输入文件第一行包含两个正整数 n(2n10)n (2 \leqslant n \leqslant 10)m(m2000)m (m \leqslant 2000),分别代表玩家数和牌堆中牌的数量。数据保证牌的数量够用。

接下来 n 行,每行 5 个字符串,依次表示对第 i 只猪的角色和初始 4 张手牌描述。编号为 1 的肯定是主猪。

再接下来一行,一共 m 个字符串,按照从牌堆顶部到牌堆底部的顺序描述每张牌。

注意:所有的相邻的两个字符串都严格用 1 个空格隔开,行尾没有多余空格。

输出格式

输出数据第一行包含一个字符串代表游戏结果。如果是主猪胜利,那么输出 MP ,否则输出 FP 。数据保证游戏总会结束。

接下来 n 行,第 i 行是对第 i 只猪的手牌描述(注意只需要输出手牌),按照手牌从左往右的顺序输出,相邻两张牌用 1 个空格隔开,行末尾没有多余空格。如果这只猪已阵亡,那么只要输出 DEAD 即可。

注意:如果要输出手牌而没有手牌的话,那么只需输出 1 个空行。

样例

Input:

3 10
MP D D F F
ZP N N N D
FP J J J J
F F D D J J F F K D

Output:

FP
DEAD
DEAD
J J J J J J D

解释:

  • 第一回合:

    • 主猪没有目标可以表敌意;
    • 接下来忠猪使用了 3 张南猪入侵,主猪掉了 3 点体力,并认为该角色为类反猪,3 号角色尽管手里有无懈可击,但是因为自己未表明身份,所以同样不能对自己用,乖乖掉 3 点体力;
  • 下一回合:

    • 反猪无牌可出;
    • 接下来主猪对着类反猪爆发,使用 4 张决斗,忠猪死亡,结果主猪弃掉所有牌;

子任务

一共 20 组测试数据,每个点 5 分。

10% 的数据没有锦囊牌,另外 20% 的数据没有无懈可击。

测试用例

1

输入

3 25
MP K K K K
ZP Z Z Z Z
FP J J J J
K K Z Z J J K K W Z W W K Z J J K K J J K K W W W

输出

FP
DEAD
DEAD
J J J J J J J J J J W

#include <bits/stdc++.h>
#define rep(i, s, t) for(int i=s;i<=t;i++)
using namespace std;

/* 全局变量 */
int n;                   // 猪的数量
queue<char> allCards;    // 牌堆

struct Pig {
/* 属性 */
    int index;            // 当前猪下标
    int hp = 4;           // 血量
    char type;            // 猪的类型: M, Z, F
    char jumpType = 0;    // 跳的类型: Z(忠), F(反), f(MP视角的类反), 0(空), 注意对MP来说针对F更优先于f
    bool arming = false;  // 有无装备
    list<char> cards;     // 手中的牌
/* 方法定义, 这里是完成本题必须的方法, 可以不必多定义了 */
    void jump(){jumpType = type=='F'?'F':'Z';} // 跳阵营
    bool isJumpItsFriend(Pig* pig) { return pig->type == 'F' ? jumpType == 'F' : jumpType == 'Z'; } // 我跳它朋友了吗?
    Pig* getNextPig();    // 获取存活的下家
    void addCards(int num);// 从牌堆拿牌放到自己手右
    bool del(char c);     // 删掉手里一张牌, 删成功则返回true
    void hurt(Pig* attacker);// 被攻击掉一点血
    bool cost(Pig* attacker, char c);// 被迫消耗手中一张牌, 消耗成功返回true并不减血, 否则减血, 减血到0尝试用桃, 无则死亡, 根据身份对杀者做后续处理
    bool useP();          // 主动使用桃, 满足使用条件就使用, 使用后返回true
    bool useK();          // 主动使用杀, 满足使用条件就使用, 使用后返回true
    bool useF();          // 主动使用决斗, 满足使用条件就使用, 使用后返回true
    bool useN();          // 主动使用南猪入侵, 满足使用条件就使用, 使用后返回true
    bool useW();          // 主动使用万箭齐发, 满足使用条件就使用, 使用后返回true
    bool findJ(Pig* attacker); // 被迫去求无懈可击, 求到则返回true
    bool useJ(Pig* pig);  // 帮某个猪用无懈可击, 用成功返回true
} ps[15];

/* 全局方法 */
bool isGameEnd() {
    if (ps[0].hp <= 0) return true;
    rep(i, 1, n-1)
        if (ps[i].type == 'F' && ps[i].hp > 0)
            return false;
    return true;
}
int cnt = 0;
void judgeGameEnd() {
    if (isGameEnd()) {
        printf(ps[0].hp > 0 ? "MP\n" : "FP\n");
        rep(i, 0, n-1) {
            if (ps[i].hp <= 0) {
                printf("DEAD\n");
            } else {
                for (list<char>::iterator it = ps[i].cards.begin(); it != ps[i].cards.end(); it++)
                    printf(it==ps[i].cards.end()?"%c":"%c ", *it);
                printf("\n");
            }
        }
        exit(0);
    }
}

void solve() {
    ps[0].jump();
    while (true) {
        rep(i, 0, n-1) {
            if (ps[i].hp <= 0) continue;
            ps[i].addCards(2);
            bool usedK = false;
            for (list<char>::iterator it = ps[i].cards.begin(); it != ps[i].cards.end(); it++) {
                bool used = false;
                char c = *it;
                it = ps[i].cards.erase(it);
                switch (c) {
                    case 'P': used = ps[i].useP(); break;
                    case 'K': if (!usedK || ps[i].arming) used = usedK = ps[i].useK(); break;
                    case 'F': used = ps[i].useF(); break;
                    case 'N': used = ps[i].useN(); break;
                    case 'W': used = ps[i].useW(); break;
                    case 'Z': ps[i].arming = used = true; break;
                }
                if (used) it = --ps[i].cards.begin();
                else it = ps[i].cards.insert(it, c);
                if (ps[i].hp <= 0) break;
            }
        }
    }
}
/* 主控方法 */
int main() {
    // Input
    int m; scanf("%d%d", &n, &m);
    char s[5];
    rep(i, 0, n-1) {
        ps[i].index = i;
        scanf("%s", s), ps[i].type = s[0];
        rep(j, 1, 4) scanf("%s", s), ps[i].cards.push_back(s[0]);
    }
    while(m--) scanf("%s", s), allCards.push(s[0]);
    // To solve
    solve();
}

Pig* Pig::getNextPig() {
    int nxt = (index + 1) % n;
    while (ps[nxt].hp <= 0) {
        nxt = (nxt+1) % n;
    }
    return &ps[nxt];
}

void Pig::addCards(int num) {
    rep(i,1,num) {
        cards.push_back(allCards.front());
        allCards.pop();
    }
}

bool Pig::useP() {
    if (hp < 4) {
        hp ++;
        return true;
    }
    return false;
}

void Pig::hurt(Pig *attacker) {
    if (--hp == 0) {
        if (this->del('P')) {
            this->useP();
        } else {
            judgeGameEnd();
            switch(type) {
                case 'F': attacker->addCards(3); break;
                case 'Z': if (attacker->type == 'M') attacker->cards.clear(), attacker->arming=false; break;
            }
        }
    }
}

bool Pig::cost(Pig* attacker, char c) {
    if (this->del(c)) {
        return true;
    }
    hurt(attacker);
    return false;
}

bool Pig::findJ(Pig *attacker) {
    Pig* nxt = attacker;
    do {
        // "找个好心的猪猪帮我挡刀"
        if (this->isJumpItsFriend(nxt) && nxt->del('J')) {
            nxt->jump();
            return !nxt->useJ(this);
        }
        nxt = nxt->getNextPig();
    } while (nxt != attacker);
    return false;
}

bool Pig::useJ(Pig *pig) {
    for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
        // "套娃指找找有没有猪猪会阻止我帮别人挡刀"
        if (!this->isJumpItsFriend(nxt) && nxt->del('J')) {
            nxt->jump();
            return !pig->findJ(nxt);
        }
    }
    return false;
}

//主动使用杀, 满足使用条件就使用, 使用后返回true
//忠猪不杀忠猪、主猪,反猪不杀反猪,主猪不杀忠猪
bool Pig::useK() {
    Pig* nxt = getNextPig();
    if (type == 'M') {  //主猪
        if (nxt->jumpType == 'F'||nxt->jumpType == 'f') {
            nxt->cost(this, 'D');//消耗一张闪,如果闪不够,则扣一滴血
            return true;
        }
    } else if (type == 'F') {  //反猪
        if (nxt->jumpType == 'Z') {
            this->jump();//不管是否杀成功都已经暴露了自己的身份
            nxt->cost(this, 'D');//消耗一张闪,如果闪不够,则扣一滴血
            return true;
        }
    } else if (type == 'Z') {  //忠猪
        if (nxt->jumpType == 'F') {
            this->jump();//不管是否杀成功都已经暴露了自己的身份
            nxt->cost(this, 'D');//消耗一张闪,如果闪不够,则扣一滴血
            return true;
        }
    }
    return false;
}
// 主动使用决斗, 满足使用条件就使用, 使用后返回true
//如果主猪对类反猪决斗,就会导致类反猪直接扣一血。一旦用了决斗就表名身份了
bool Pig::useF() {
    if (type == 'M') {
        for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
            if (nxt->jumpType == 'F') {//优先查找反猪
                if (nxt->findJ(this)) {//看看有没有猪用无懈可击帮它挡下攻击
                    return true;//如果有猪帮它用了无懈可击本次攻击就无效
                }
                //双方轮流消耗一张杀,直到有一方没有杀
                while (nxt->cost(this, 'K')) {//不断消耗杀直到没有杀
                    if (!this->del('K')) {//如果自己没有杀了,就扣自己一滴血
                        this->hurt(nxt);
                        return true;
                    }
                }
                return true;
            }
        }
        for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
            if (nxt->jumpType == 'f') {//如果没有反猪才查找类反猪
                if (nxt->type == 'Z') {//如果类反猪是忠猪,就扣一滴血,因为类反猪被主猪决斗不出杀
                    nxt->hurt(this);
                } else {//如果类反猪是反猪,双方轮流消耗一张杀,直到有一方没有杀
                    while (nxt->cost(this, 'K')) {
                        if (!this->del('K')) {
                            this->hurt(nxt);
                            return true;
                        }
                    }
                }
                return true;
            }
        }
    } else if (type == 'F') {//如果是反猪,就查找主猪
        for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
            if (nxt->type == 'M') {
                this->jump();//不管是否决斗成功都已经暴露了自己的身份
                if (nxt->findJ(this)) {//看看有没有猪用无懈可击帮它挡下攻击
                    return true;
                }
                while (nxt->cost(this, 'K')) {//不断消耗杀直到没有杀
                    if (!this->del('K')) {
                        this->hurt(nxt);
                        return true;
                    }
                }
                return true;
            }
        }  //决斗一定能对主猪表,因为主猪一开始就表名身份了
    } else if (type == 'Z') {//如果是忠猪,就查找反猪
        for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
            if (nxt->jumpType == 'F') {
                this->jump();//不管是否决斗成功都已经暴露了自己的身份
                if (nxt->findJ(this)) {//看看有没有猪用无懈可击帮它挡下攻击
                    return true;
                }
                while (nxt->cost(this, 'K')) {//不断消耗杀直到没有杀
                    if (!this->del('K')) {
                        this->hurt(nxt);
                        return true;
                    }
                }
                return true;
            }
        }
    }
    return false;
}
// 主动使用南猪入侵, 满足使用条件就使用, 使用后返回true
bool Pig::useN() {
    for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
        //如果用了J就不会执行用K的函数了
        //首先看看有没有猪用无懈可击帮它挡下攻击
        //如果没有猪用无懈可击,就尝试消耗一张杀(消耗失败会扣被攻击方的血)
        //如果扣血的是主猪,并且自己没有跳忠,就会被主猪认为是类反猪
        if (!nxt->findJ(this) && !nxt->cost(this, 'K') && jumpType == 0 && nxt->type == 'M') {
            jumpType = 'f';//自己被认为是类反猪
        }
    }
    return true;//一定能用成功
}
// 主动使用万箭齐发, 满足使用条件就使用, 使用后返回true
bool Pig::useW() {
    for (Pig* nxt = getNextPig(); nxt != this; nxt = nxt->getNextPig()) {
        //首先看看有没有猪用无懈可击帮它挡下攻击
        //如果没有猪用无懈可击,就尝试消耗一张闪(消耗失败会扣被攻击方的血)
        //如果扣血的是主猪,并且自己没有跳忠,就会被主猪认为是类反猪
        if (!nxt->findJ(this) && !nxt->cost(this, 'D') && jumpType == 0 && nxt->type == 'M') {
            jumpType = 'f';//自己被认为是类反猪
        }
    }
    return true;//一定能用成功
}
// 删掉手里一张牌, 删成功则返回true
bool Pig::del(char c) {
    for (list<char>::iterator it = cards.begin(); it != cards.end(); it++) {
        if (*it == c) {  //找到了,就删掉
            cards.erase(it);
            return true;
        }
    }
    return false;  //没找到
}