请使用指定的代码模板完成该题~
- 由于原题在题面和数据上的一些小问题,本题经过了一些小修小改,数据,和题面,与 SDOI2010 略有不同。参考网络资料将让你在完成作业的路径上绕远路(不保证可行)并且收获甚少。相信自己,独立完成会收获很多,你也可以在思考无所获后向同学请教。
- 代码模板精心制作,请大家理解后进行补全代码实验
- 本题代码模板不严格遵守面向对象的规则,而是面向教育用途设计
- 比起费力优化代码的常数复杂度,更乐于精心设计将代码逻辑区分开
- 本实验考点:
- 问题理解及其逻辑化能力
大型(显然并不大第三方代码框架理解和分析能力- 工程改动的创口分析和实践能力
- 程序调试能力
- 期末将近,预祝同学们期末考得好成绩,不负努力,加油。
背景
《猪国杀》是一种多猪牌类回合制游戏,一共有 3 种角色:主猪,忠猪,反猪。每局游戏主猪有且只有 1 只,忠猪和反猪可以有多只,每只猪扮演 1 种角色。
目的
主猪 / MP:自己存活的情况下消灭所有的反猪。
忠猪 / ZP:不惜一切保护主猪,胜利条件与主猪相同。
反猪 / FP:杀死主猪。
游戏过程
游戏开始时,每个玩家手里都会有 4 张牌,且体力上限和初始体力都是 4 。
开始游戏时,从主猪开始,按照逆时针方向(数据中就是按照编号从 的顺序)依次行动。
每个玩家自己的回合可以分为 2 个阶段:
- 摸牌阶段:从牌堆顶部摸 2 张牌,依次放到手牌的最右边;
- 出牌阶段:你可以使用任意张牌,每次使用牌的时候都使用最靠左的能够使用的牌。当然,要满足如下规则:
- 如果没有猪哥连弩,每个出牌阶段只能使用 1 次「杀」来攻击;
- 任何牌被使用后被弃置(武器是装备上);被弃置的牌以后都不能再用,即与游戏无关。
各种牌介绍
每张手牌用 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 行,每行 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; //没找到
}