题目描述
[SDOI2010] 猪国杀
题目描述
游戏背景
《猪国杀》是一种多猪牌类回合制游戏,一共有 $3$ 种角色:主猪,忠猪,反猪。每局游戏主猪有且只有 $1$ 只,忠猪和反猪可以有多只,每只猪扮演 $1 $ 种角色。
游戏目的
主猪 / $\texttt{MP}$:自己存活的情况下消灭所有的反猪。
忠猪 / $\texttt{ZP}$:不惜一切保护主猪,胜利条件与主猪相同。
反猪 / $\texttt{FP}$:杀死主猪。
游戏过程
游戏开始时,每个玩家手里都会有 $4$ 张牌,且体力上限和初始体力都是 $4$ 。
开始游戏时,从主猪开始,按照逆时针方向(数据中就是按照编号从 $ 1 , 2, 3 \ldots n , 1 \ldots $ 的顺序)依次行动。
每个玩家自己的回合可以分为 2 个阶段:
- 摸牌阶段:从牌堆顶部摸 $2$ 张牌,依次放到手牌的最右边;
- 出牌阶段:你可以使用任意张牌,每次使用牌的时候都使用最靠左的能够使用的牌。当然,要满足如下规则:
- 如果没有猪哥连弩,每个出牌阶段只能使用 $1$ 次「杀」来攻击;
- 任何牌被使用后被弃置(武器是装备上);被弃置的牌以后都不能再用,即与游戏无关。
各种牌介绍
每张手牌用 $1$ 个字母表示,字母代表牌的种类。
基本牌
-
『桃 / $\texttt{P}$』在自己的回合内,如果自己的体力值不等于体力上限,那么使用 $1$ 个桃可以为自己补充 $1$ 点体力,否则不能使用桃;桃只能对自己使用;在自己的回合外,如果自己的血变为 $0$ 或者更低,那么也可以使用。
-
『杀 / $\texttt{K}$』在自己的回合内,对攻击范围内除自己以外的 $1$ 名角色使用。如果没有被『闪』抵消,则造成 $1$ 点伤害。无论有无武器,杀的攻击范围都是 $1$。
-
『闪 / $\texttt{D}$』当你受到杀的攻击时,可以弃置 $1$ 张闪来抵消杀的效果。
锦囊牌
-
『决斗 / $\texttt{F}$』出牌阶段,对除自己以外任意 $1$ 名角色使用,由目标角色先开始,自己和目标角色轮流弃置 $1$ 张杀,首先没有杀可弃的一方受到 $1$ 点伤害,另一方视为此伤害的来源。
-
『南猪入侵 / $\texttt{N}$』出牌阶段,对除你以外所有角色使用,按逆时针顺序从使用者下家开始依次结算,除非弃置 $1$ 张杀,否则受到 $1$ 点伤害。
-
『万箭齐发 / $\texttt{W}$』和南猪入侵类似,不过要弃置的不是杀而是闪。
-
『无懈可击 / $\texttt{J}$』在目标锦囊生效前抵消其效果。每次有 $1$ 张锦囊即将生效时,从使用这张锦囊的猪开始,按照逆时针顺序,依次得到使用无懈可击的机会;效果:用于决斗时,决斗无效并弃置;用于南猪入侵或万箭齐发时,当结算到某个角色时才能使用,当前角色不需弃置牌并且不会受到伤害(仅对 $1$ 个角色产生效果);用于无懈可击时,成为目标的无懈可击被无效。
装备牌
- 『猪哥连弩 / $\texttt{Z}$』武器,攻击范围 $1$ ,出牌阶段你可以使用任意张杀; 同一时刻最多只能装 $1$ 把武器;如果先前已经有了 $1$ 把武器,那么之后再装武器的话,会弃置以前的武器来装现在的武器。
特殊事件及概念解释
-
伤害来源:杀、南猪入侵、万箭齐发的伤害来源均是使用该牌的猪,决斗的伤害来源如上;
-
距离:两只猪的距离定义为沿着逆时针方向间隔的猪数 $+1$ 。即初始时 $1$ 和 $2$ 的距离为 $1$ ,但是 $2$ 和 $1$ 的距离就是 $n-1$ 。注意一个角色的死亡会导致一些猪距离的改变;
-
玩家死亡:如果该玩家的体力降到 $0$ 或者更低,并且自己手中没有足够的桃使得自己的体力值回到 $1$ ,那么就死亡了,死亡后所有的牌(装备区,手牌区)被弃置;
-
奖励与惩罚:反猪死亡时,最后一个伤害来源处(即使是反猪)立即摸 $3$ 张牌。忠猪死亡时,如果最后一个伤害来源是主猪,那么主猪所有装备牌、手牌被弃置。
注意:一旦达成胜利条件,游戏立刻结束,因此即使会摸 $3$ 张牌或者还有牌可以用也不用执行了。
现在,我们已经知道每只猪的角色、手牌,还有牌堆初始情况,并且假设每个角色会按照如下的行为准则进行游戏,你需要做的就是告诉小猪 iPig 最后的结果。
几种行为
- 献殷勤:使用无懈可击挡下南猪入侵、万箭齐发、决斗;使用无懈可击抵消表敌意;
- 表敌意:对某个角色使用杀、决斗;使用无懈可击抵消献殷勤;
- 跳忠:即通过行动表示自己是忠猪。跳忠行动就是对主猪或对某只已经跳忠的猪献殷勤,或者对某只已经跳反的猪表敌意;
- 跳反:即通过行动表示自己是反猪。跳反行动就是对主猪或对某只已经跳忠的猪表敌意,或者对某只已经跳反的猪献殷勤。
注意:忠猪不会跳反,反猪也不会跳忠;不管是忠猪还是反猪,能够跳必然跳。
行动准则
共性
- 每个角色如果手里有桃且生命值未满,那么必然吃掉;
- 有南猪入侵、万箭齐发、必然使用;有装备必然装上;
- 受到杀时,有闪必然弃置;
- 响应南猪入侵或者万箭齐发时候,有杀 / 闪必然弃置;
- 不会对未表明身份的猪献殷勤(包括自己)。
特性
- 主猪:
- 主猪会认为「没有跳身份,且用南猪入侵 / 万箭齐发对自己造成伤害的猪」是类反猪(没伤害到不算,注意类反猪并没有表明身份),如果之后跳了,那么主猪会重新认识这只猪;
- 对于每种表敌意的方式,对逆时针方向能够执行到的第一只类反猪或者已跳反猪表;如果没有,那么就不表敌意;
- 决斗时会不遗余力弃置杀;
- 如果能对已经跳忠的猪或自己献殷勤,那么一定献;如果能够对已经跳反的猪表敌意,那么一定表。
- 忠猪:
- 对于每种表敌意的方式,对「逆时针方向能够执行到的第一只已经跳反的猪」表,如果没有,那么就不表敌意;
- 决斗时,如果对方是主猪,那么不会弃置杀,否则,会不遗余力弃置杀;
- 如果有机会对主猪或者已经跳忠的猪献殷勤,那么一定献。
- 反猪:
- 对于每种表敌意的方式,如果有机会则对主猪表,否则,对「逆时针方向能够执行到的第一只已经跳忠的猪」表,如果没有,那么就不表敌意;
- 决斗时会不遗余力弃置杀;
- 如果有机会对已经跳反的猪献殷勤,那么一定献。
限于 iPig 只会用 P 语言写 A + B,他请你用 Pigcal (Pascal)、P (C) 或 P (C++) 语言来帮他预测最后的结果。
输入格式
输入文件第一行包含两个正整数 $ n $ $ (2 \leqslant n \leqslant 10) $ 和 $m$ $ (m \leqslant 2000) $,分别代表玩家数和牌堆中牌的数量。数据保证牌的数量够用。
接下来 $n$ 行,每行 $5$ 个字符串,依次表示对第 $i$ 只猪的角色和初始 $4 $ 张手牌描述。编号为 $1$ 的肯定是主猪。
再接下来一行,一共 $m$ 个字符串,按照从牌堆顶部到牌堆底部的顺序描述每张牌。
注意:所有的相邻的两个字符串都严格用 $1$ 个空格隔开,行尾没有多余空格。
输出格式
输出数据第一行包含一个字符串代表游戏结果。如果是主猪胜利,那么输出 $\texttt{MP}$ ,否则输出 $\texttt{FP}$ 。数据保证游戏总会结束。
接下来 $n$ 行,第 $i$ 行是对第 $i$ 只猪的手牌描述(注意只需要输出手牌),按照手牌从左往右的顺序输出,相邻两张牌用 $1$ 个空格隔开,行末尾没有多余空格。如果这只猪已阵亡,那么只要输出 $\texttt{DEAD}$ 即可。
注意:如果要输出手牌而没有手牌的话,那么只需输出 $1$ 个空行。
由于数据问题,若牌堆已空,按照每次抽牌抽到的都是最后一张。
样例 #1
样例输入 #1
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
样例输出 #1
FP
DEAD
DEAD
J J J J J J D
提示
样例解释
第一回合:
主猪没有目标可以表敌意;
接下来忠猪使用了 $3$ 张南猪入侵,主猪掉了 $3$ 点体力,并认为该角色为类反猪,$3$ 号角色尽管手里有无懈可击,但是因为自己未表明身份,所以同样不能对自己用,乖乖掉 $3$ 点体力;
下一回合:
反猪无牌可出;
接下来主猪对着类反猪爆发,使用 $4$ 张决斗,忠猪死亡,结果主猪弃掉所有牌;
* 下来反猪摸到 $1$ 张杀直接杀死主猪获胜。
子任务
一共 $20$ 组测试数据,每个点 $5$ 分。
$10\%$ 的数据没有锦囊牌,另外 $20\%$ 的数据没有无懈可击。
题目分析
猪国杀是一道桌游三国杀的简化版,这道题在代码涉及的知识点上难度很低,主要难度就在于如何实现,以及非常多的细节。作为一个4年TTA、3年TS、170小时STS玩家,虽然我不会玩三国杀,由于我接触过不少规则非常复杂的桌游(TTA规则A4纸30多页,字体大小也就五号不到),在学习规则的过程中经常在一些细节上出错 (专业说法叫村规) ,所以在读这道题的过程中也本能地非常注意,从而避开了那些细节上的初见杀(尽管后来调题的时间还是非常非常长,没有太体现出这个优势)。接下来具体分析一下这道题的题面。
这个规则当中可以分为以下三部分:基本流程,牌和行为逻辑。
基本流程的信息当中,这些是比较简明的:每回合抽2,且轮到谁谁再抽2;有且仅有第一只猪是主猪;一回合没有装备只能打一张杀;反猪被杀抽三张牌;主猪杀忠猪清空手牌和装备;主猪被杀或反猪全部被杀游戏立刻结束,也就是不需要再抽牌。
此外还有一条比较有争议的:每次使用牌的时候都使用最靠左的能够使用的牌。这个“能够使用”如何定义是个问题,具体要等到所有的规则全都看完才能理解(这也是读题的时候卡了我最久的一个点)。
手牌除了无懈可击都比较好理解。无懈可击这牌的描述比较抽象,分析一下要点大约是这样的:无懈可击只能防锦囊牌,锦囊牌每一次判定都要先判定是否会被无懈可击防;无懈可击可以对无懈可击使用,可以对任何人使用。所以基本是一个递归的逻辑。
这道题最复杂的部分就是行为逻辑。首先看完一遍行为逻辑可以发现,对于每一张牌打出的选择是唯一的,所以这道题是一个纯粹的模拟,不涉及决策,这是一个好消息。
概括的来说,每一名玩家都有真实身份和表现身份,攻击有明确表现的对象会使自己表现明确身份,除此之外,用AOE打到主公会被判断为类反这一不明确身份,类反会被主公攻击。类反身份可能会被明确。而且重要的是,玩家一定会跳与自己相同的身份,也就是明确身份一定是自己的真实身份;玩家也只会把定向的牌作用于明确阵营的对象(主公除外),这是这道题没有决策的关键之处。
除此之外,无懈可击用了之后会把作用对象和自己绑成一队。
现在大致看明白了整个游戏规则,回头讨论一下一开始提出的问题:什么叫做“能够使用”?有一些是题目当中明确说的,比如4点血的时候不能用桃;有一些是“不会”进行的行为,比如忠猪不会用手里的杀攻击队友。那这是否算是一种无法出牌的情况?事实上是不算的,从游戏逻辑上似乎也应该是不算的。我确实没有在读题的时候看明白这一点,我也觉得这一点题面描述说的确实不太清楚,这一点是有影响的,因为如果算的话,这是一种“可以出但不出”的行为,它的回合就应该强制结束(很多规则在描述的过程中倒是常有这种问题,比如什么“你可以…”到底是不是强制行为,我只能说是还原的很真实)。
题目看完了,该开工了。
代码
先列一个大纲。对于每一名玩家,记录以下的信息:
struct player{
bool equipped;
//是否有弩
int hp,siz,nxt,lst,id,behave;
//血量,手牌队列大小,右边、左边玩家(逆时针),身份,表现
char deck[N];
//实际手牌队列
}a[11];
然后考虑一下需要哪些函数:先实现一些基本的功能,比如杀和抽牌。弃牌只需要把这张牌改成随便一个字符就行了,不需要单独出来。当一只猪血量为空时有一个死亡阶段,这个单独实现。
剩下的就是四种锦囊牌,对决、南猪入侵、万箭齐发、无懈可击每种一个函数。最后开一个函数模拟游戏过程,便于结束的时候直接return.
然后写就完事了。列出一些我写的时候遇到的坑:
1.定向攻击类:对决和杀。注意目标不能是未表明身份的,所以讨论的时候注意判断条件是否排除了无身份情况。
2.AOE:注意及时更新手牌队列。
3.无懈可击:如果递归实现反而没有太多可说的东西,注意一下发起方、接收方、打无懈可击方三方别混了。题目当中说的自己身份不明也不能给自己无懈可击这一点,只要每一次提到目标都是判断表现身份就行了,不是什么容易出错的东西。
4.对决:目标先弃牌。
5.在表明身份后,一名玩家可以打出的牌位置可能回到左侧,所以要注意哪些情况需要重置。
6.注意i=a[i].nxt如果不作为for的一个条件,不要在continue里面给忘了(这个花了我一个半小时调)。
7.主公永远是1号位,但是还是建议用a[i].id=1判断是否为主公,尤其是我这样从0开始算的情况。
其他细节见如下完整代码:(<300行)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2001;
struct player{
bool equipped;
int hp,siz,nxt,lst,id,behave;
char deck[N];
}a[11];
int n,m,en,top;
char pile[N];//抽牌堆
bool ed;
void draw(int p,int num){//抽牌
while(num--){
a[p].deck[++a[p].siz] = pile[top];
if(top > 1) --top;
}
}
void dying(int fr,int to){//濒死阶段
int i;
for(i = 1;i <= a[to].siz;i++){
if(a[to].deck[i] == 'P'){
a[to].deck[i] = '0';
++a[to].hp;
return;//这个阶段只能把血量回到1
}
}
a[a[to].nxt].lst = a[to].lst;
a[a[to].lst].nxt = a[to].nxt;
if(a[to].id == 3) --en;
if(!en || a[to].id == 1){
ed = 1;
return;
}//摸牌晚于判断结束
if(a[to].id == 3) draw(fr,3);
if(a[to].id == 2 && a[fr].id == 1){
a[fr].siz = 0,a[fr].equipped = 0;
}
}
void atk(int fr,int to){//杀
int i;
for(i = 1;i <= a[to].siz;i++){
if(a[to].deck[i] == 'D'){
a[to].deck[i] = '0';
return;
}
}
--a[to].hp;
if(!a[to].hp) dying(fr,to);
}
bool parry(int fr,int to,int flag){
int i = fr,j;
do{
if(flag){
if(a[to].behave == a[i].id || (a[to].behave == 1 && a[i].id == 2) || (a[to].behave == 2 && a[i].id == 1)){
//第一次无懈可击:助攻
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] == 'J'){
a[i].deck[j] = '0';
a[i].behave = a[i].id;
return !parry(i,fr,0);
}
}
}
}
else{
if(((a[i].id == 1 || a[i].id == 2) && a[fr].behave == 3) || (a[i].id == 3 && (a[fr].behave == 1 || a[fr].behave == 2))){
//无懈可击套娃:打断上一次
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] == 'J'){
a[i].deck[j] = '0';
a[i].behave = a[i].id;
return !parry(i,fr,0);
}
}
}
}
i = a[i].nxt;
}while(i != fr);
return 0;
}
void duel(int fr,int to){
int i,l,r;
if(parry(fr,to,1)) return;
if(a[fr].id == 1 && a[to].id == 2){//这个特判要看目标的实际身份
--a[to].hp;
if(a[to].hp <= 0) dying(fr,to);
return;
}
l = 1,r = 1;
while(1){
while(r <= a[to].siz && a[to].deck[r] != 'K') ++r;
if(r > a[to].siz){
--a[to].hp;
if(a[to].hp <= 0) dying(fr,to);
return;
}
else a[to].deck[r] = '0';
//目标先弃牌
while(l <= a[fr].siz && a[fr].deck[l] != 'K') ++l;
if(l > a[fr].siz){
--a[fr].hp;
if(a[fr].hp <= 0) dying(to,fr);
return;
}
else a[fr].deck[l] = '0';
}
}
void invade(int fr){
int i = a[fr].nxt,j;
for(;i != fr;i = a[i].nxt){//一个值1h30min的for循环
if(parry(fr,i,1)) continue;
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] == 'K'){
a[i].deck[j] = '0';
break;
}
}
if(j > a[i].siz){//省一个变量的判断方式
--a[i].hp;
if(a[i].id == 1 && !a[fr].behave) a[fr].behave = 4;
if(!a[i].hp) dying(fr,i);
if(ed) return;
}
}
}
void shoot(int fr){
int i = a[fr].nxt,j;
for(;i != fr;i = a[i].nxt){
if(parry(fr,i,1)) continue;
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] == 'D'){
a[i].deck[j] = '0';
break;
}
}
if(j > a[i].siz){
--a[i].hp;
if(a[i].id == 1 && !a[fr].behave) a[fr].behave = 4;
if(a[i].hp <= 0) dying(fr,i);
if(ed) return;
}
}
}
void check(){
int i,j;
for(i = 0;i < n;i++){
printf("%d %d hp=%d\n",a[i].id,a[i].behave,a[i].hp);
for(j = 1;j <= a[i].siz;j++){
printf("%c ",a[i].deck[j]);
}
puts("");
}
puts("");
}
void solve(){
if(!en) return;
int i = 0,j,k;
char now;
bool used;
while(!ed){
used = 0;
draw(i,2);
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] == '0' || a[i].deck[j] == 'D') continue;
if(!a[i].hp) break;
if(a[i].deck[j] == 'P'){
if(a[i].hp < 4){
++a[i].hp;
a[i].deck[j] = '0';
}
continue;
}
if(a[i].deck[j] == 'K'){
if(used && !a[i].equipped) continue;
k = a[i].nxt;
if((a[i].id == 1 && a[k].behave != 3 && a[k].behave != 4) || (a[i].id == 2 && a[k].behave != 3) || (a[i].id == 3 && a[k].behave != 1 && a[k].behave != 2)) continue;
//这一段注意不要攻击身份不明的对象
a[i].deck[j] = '0';
atk(i,k);
a[i].behave = a[i].id;
used = 1;
if(ed) return;
continue;
}
if(a[i].deck[j] == 'F'){
if(a[i].id == 3){
a[i].deck[j] = '0';
duel(i,0);
a[i].behave = a[i].id;
if(ed) return;
j = 0;
//重新从最左的手牌开始考虑出牌,下同
}
else{
k = a[i].nxt;
while(k != i){
if((a[i].id == 1 && a[k].behave >= 3) || (a[i].id == 2 && a[k].behave == 3)){
a[i].deck[j] = '0';
duel(i,k);
a[i].behave = a[i].id;
if(ed) return;
j = 0;
break;
}
k = a[k].nxt;
}
}
continue;
}
if(a[i].deck[j] == 'N'){
a[i].deck[j] = '0';
invade(i);
if(ed) return;
j = 0;
continue;
}
if(a[i].deck[j] == 'W'){
a[i].deck[j] = '0';
shoot(i);
if(ed) return;
j = 0;
continue;
}
if(a[i].deck[j] == 'Z'){
a[i].deck[j] = '0';
a[i].equipped = 1;
j = 0;
continue;
}
}
i = a[i].nxt;
}
}
int main(){
int i,j;
char s[3];
scanf("%d %d",&n,&m);
for(i = 0;i < n;i++){
a[i].siz = a[i].hp = 4;
a[i].behave = 0;
scanf("%s",s);
if(s[0] == 'M') a[i].id = a[i].behave = 1;
if(s[0] == 'Z') a[i].id = 2;
if(s[0] == 'F') a[i].id = 3,++en;
a[i].equipped = 0;
a[i].lst = (i - 1 + n) % n,a[i].nxt = (i + 1) % n;
for(j = 1;j <= 4;j++){
scanf("%s",s);
a[i].deck[j] = s[0];
}
}
//下标从0开始便于初始化指针(其实也没啥必要)
for(i = 1;i <= m;i++){
scanf("%s",s);
pile[i] = s[0];
}
reverse(pile + 1,pile + m + 1);
top = m;
solve();
if(a[0].hp <= 0) puts("FP");
else puts("MP");
for(i = 0;i < n;i++){
if(a[i].hp <= 0) puts("DEAD");
else{
for(j = 1;j <= a[i].siz;j++){
if(a[i].deck[j] != '0') putchar(a[i].deck[j]),putchar(' ');
}
puts("");
}
}
return 0;
}
由于最近比较颓,逼自己刷一道大模拟。(其实个人认为这个方法海上挺有用的)
看了这道大模拟后,脑子比较混乱。
先列了一下有用信息(建议认真读,至少读个5遍):
section 1:
主猪(MP):自己存活的情况下消灭所有的反猪。
忠猪(ZP):不惜一切保护主猪,胜利条件与主猪相同。
反猪(AP):杀死主猪。
section 2:
游戏开始时候,每个玩家手里都会有4张牌,且体力上限和初始体力都是4。
开始游戏时,从主猪开始,按照逆时针方向(数据中就是按照编号从1,2,3..n,1..的顺序)依次行动。
摸牌阶段:从牌堆顶部摸两张牌,依次放到手牌的最右边;
出牌阶段:你可以使用0张到任意张牌,每次使用牌的时候都使用最靠左的能够使用的牌。
当然,要满足如下规则:
1.如果没有猪哥连弩,每个出牌阶段只能使用一次“杀”来攻击;
2.任何牌被使用后被弃置(武器是装备上);
被弃置的牌以后都不能再用,即与游戏无关;
section 3:
『桃(P)』:在自己的回合内,如果自己的体力值不等于体力上限,那么使用一个桃可以为自己补充一点体力,否则不能使用桃;桃只能对自己使用;在自己的回合外,如果自己的血变为0或者更低,那么也可以使用;
『杀(K)』:在自己的回合内,对攻击范围内除自己以外的一名角色使用。如果没有被『闪』抵消,则造成1点伤害。无论有无武器,杀的攻击范围都是1;
『闪(D)』:当你受到杀的攻击时,可以弃置一张闪来抵消杀的效果;
『决斗(F)』:出牌阶段,对除自己以外任意一名角色使用,由目标角色先开始,自己和目标角色轮流弃置一张杀,首先没有杀可弃的一方受到1点伤害,另一方视为此伤害的来源;
『南猪入侵(N)』:出牌阶段,对除你以外所有角色使用,按逆时针顺序从使用者下家开始依次结算,除非弃置一张杀,否则受到1点伤害;
『万箭齐发(W)』:和南猪入侵类似,不过要弃置的不是杀而是闪;
『无懈可击(J)』:在目标锦囊生效前抵消其效果。每次有一张锦囊即将生效时,从使用这张锦囊的猪开始,按照逆时针顺序,依次得到使用无懈可击的机会;
效果:用于决斗时,决斗无效并弃置;用于南猪入侵或万箭齐发时,当结算到某个角色时才能使用,当前角色不需弃置牌并且不会受到伤害(仅对一个角色产生效果);用于无懈可击时,成为目标的无懈可击被无效。
『猪哥连弩(Z)』:武器,攻击范围1,出牌阶段你可以使用任意张杀;
同一时刻最多只能装一个武器;如果先前已经有了一把武器,那么之后再装武器的话,会弃置以前的武器来装现在的武器;
section 4:
伤害来源:杀、南猪入侵、万箭齐发的伤害来源均是使用该牌的猪,决斗的伤害来源如上;
距离:两只猪的距离定义为沿着逆时针方向间隔的猪数+1。即初始时1和2的距离为1,但是2和1的距离就是n-1。注意一个角色的死亡会导致一些猪距离的改变;
玩家死亡:如果该玩家的体力降到0或者更低,并且自己手中没有足够的桃使得自己的体力值回到1,那么就死亡了,死亡后所有的牌(装备区,手牌区)被弃置;
奖励与惩罚:反猪死亡时,最后一个伤害来源处(即使是反猪)立即摸三张牌。忠猪死亡时,如果最后一个伤害来源是主猪,那么主猪所有装备牌、手牌被弃置;
注意,一旦达成胜利条件,游戏立刻结束,因此即使会摸3张牌或者还有牌可以用也不用执行了。
section 5:
已经知道每只猪的角色、手牌,还有牌堆初始情况,并且假设每个角色会按照如下的行为准则进行游戏。
section 6:
献殷勤:使用无懈可击挡下南猪入侵、万箭齐发、决斗;使用无懈可击抵消表敌意;
表敌意:对某个角色使用杀、决斗;使用无懈可击抵消献殷勤;
跳忠:即通过行动表示自己是忠猪。跳忠行动就是对主猪或对某只已经跳忠的猪献殷勤,或者对某只已经跳反的猪表敌意;
跳反:即通过行动表示自己是反猪。跳反行动就是对主猪或对某只已经跳忠的猪表敌意,或者对某只已经跳反的猪献殷勤;
忠猪不会跳反,反猪也不会跳忠;不管是忠猪还是反猪,能够跳必然跳;
section 7:
行动准则(共性):每个角色如果手里有桃且生命值未满,那么必然吃掉;有南猪入侵、万箭齐发、必然使用;有装备必然装上;受到杀时,有闪必然弃置;响应南猪入侵或者万箭齐发时候,有杀/闪必然弃置;不会对未表明身份的猪献殷勤(包括自己);
section 8:
主猪:主猪会认为,没有跳身份且用南猪入侵/万箭齐发对自己造成伤害的猪是“类反猪”(没伤害到不算,注意“类反猪”并没有表明身份),如果之后跳了,那么主猪会重新认识这只猪;对于每种表敌意的方式,对逆时针方向能够执行到的第一只“类反猪”或者已跳反猪表;如果没有,那么就不表敌意;决斗时会不遗余力弃置杀;如果能对已经跳忠的猪或自己献殷勤,那么一定献;如果能够对已经跳反的猪表敌意,那么一定表;
忠猪:对于每种表敌意的方式,对逆时针方向能够执行到的第一只已经跳反的猪表,如果没有,那么就不表敌意;决斗时,如果对方是主猪,那么不会弃置杀,否则,会不遗余力弃置杀;如果有机会对主猪或者已经跳忠的猪献殷勤,那么一定献;
反猪:对于每种表敌意的方式,如果有机会则对主猪表,否则,对逆时针方向能够执行到的第一只已经跳忠的猪表,如果没有,那么就不表敌意;决斗时会不遗余力弃置杀;如果有机会对已经跳反的猪献殷勤,那么一定献;
好吧,其实都是挺有用的,即使已经玩过(甚至熟知)三国杀,也需要非常仔细的看懂,看清题目。
我还是一步一步来考虑的。
先看没有锦囊牌的情况。
在自己回合内,有武器当然是装上。闪一定是在非自己回合出的。桃的话血没有满就吃,杀的话能杀就杀,但是要找清目标(必须距离为1)。
所谓找清目标,就是——反贼找主公或表明身份的忠臣,忠臣找已经表明身份的反贼,主公找已经表明身份的反贼或类反贼。
这应该是最简单的部分了。但是我WA了一两次才拿到10分。
错因有:
1.没有完全找清目标;
2.装上诸葛连弩后,没有发现前面的杀也能用;
那么继续,向30分发起冲击。
30分的要求是——有锦囊牌,但是没有无懈可击。好吧,我得承认,没有无懈可击也要写很久。
首先,决斗。还是要找清目标——反贼决斗目标一定是主公,忠臣决斗目标一定是表明身份的反贼,主公决斗目标是表明身份的反贼或类反贼。
然后是万箭齐发和南蛮入侵。这是个多重目标攻击,所以要对每一个存活的非攻击发起者发起一次单向进攻。
这个部分需要注意很多:
3.决斗时,进攻发起者可以要使用多张杀,所以要处理好决斗后,手牌剩余部分;
4.决斗时,可能进攻发起者会GG(不知道他怎么想的,不愧是pig),此时要停止他的回合;
5.万箭齐发(南蛮入侵)时,可能会有多人GG,可能会摸多次牌;
6.万箭齐发(南蛮入侵)时,可能会有多人GG,同时发动者是主公,不仅杀死了忠臣,也杀死了反贼,此时也要考虑弃掉所有手牌和摸三张牌的顺序;
7.决斗时,主公认为对面是类反贼,实际上是忠臣,忠臣要牺牲一滴血;
8.在决斗时,发起者会明确身份;在万箭齐发(南蛮入侵)时,可能并不会,但有可能成为类反贼;
30分的段也WA个不停,但总算还是达到了。
剩下就是无懈可击了。
这是一个难点,但我认为,要突破它,首先要搞清无懈在不同情况下了作用,还有——它一定在锦囊牌生效之前使用。
题目中定义,如果某人使用了一张锦囊牌,那么从他后面的猪开始,依次有出无懈的机会(当然对于一张锦囊牌,最多只有1张无懈针对他本身)。
那么,假如A对B出了一张锦囊牌,首先,需要保证B的身份已经亮明(否则没有人会帮他,包括他自己)
然后,和B一派的,一定想方设法使初始锦囊牌无效;和B不一派的,一定想方设法使初始锦囊牌生效。
那么,这可以写个简易的dfs(顺便在过程中,如果能且有必要出无懈,就直接出掉了)。
然后,无懈不可能当做第一张锦囊牌打出(相当于永远是后手)。
对于无懈而言,需要注意的地方:
9.无懈不可能先手打出,并不代表在自己回合内就不用出无懈;
10.无懈的优先级很高(比一般锦囊牌都高);
11.当一个人使用了无懈,他的身份就完全表明了;
12.无懈看似是最复杂,但是实际上相当于一个补丁(尽管有挺多的细节);
以上是大致的做法,从10pts->30pts->100pts,而实际上,我爆出了形形色色的部分分(等下会展示)
下面还有一些全局的注意点:
13.主公杀死忠臣,一定要把装备给弃了;
14.关于一轮能否出多次杀,要注意判断;
15.一张牌,你出了,就相当于弃置了,不管最后是否生效;
16.最形象的方法存手牌显然是链表,但考虑到实际需要,一般直接开数组;
17.要分清在自己的回合出牌和在别人的回合出牌,最好用不同方法处理,但处理也有很多细节(因人而异);
18.注意主公也会对类反贼表敌意;
19.杀,桃,无懈都是有可能在自己回合打出的“响应牌”(就是回击别人的牌),要注意处理方法(要看清是自己回合还是他人回合出的牌);
20.严格意义上,除了“桃”,在自己回合出了每一张牌后,可能会激活这张牌前面的牌;
21.注意这里的距离是单向的,而不是双向的,而且恒为1;
22.存在用无懈无懈掉自己的锦囊牌的情况;
23.阵亡的人已经没有任何价值,直接跳过他;
24.一旦出现胜利局面,就要停止一切操作,但是需要把没有整理好的手牌整理好;
25.对于一个人,要是把手牌扫了一遍,没有出任何牌,就结束出牌;
26.一个人死了后,即使更新上一个玩家的下一个玩家nxt(不必o1求);
27.注意一个人处于濒死状态的处理,没当有一个人死了,都要判断一下游戏是否结束了;
28.题目有一个小bug,当牌堆没有牌时,要一直摸最后一张牌。
这是我认为这题存在的细节(可能还有我数不过来了),这些细节有些是比较重要的,有些是要看写法的。
对于这一题我也有一些自己的看法——
这题其实很好,大量的代码,条件和情况能锻炼一个人的代码水平和思维能力。尽管有些方面不是特别好(比如出在了省选里面,估计没有什么人能打完并AC)。
然后我发现在luogu里面,这题的分类有一个AOE。为什么标AOE?有些人说因为南蛮和万箭是群体攻击。
我并不是这样认为的(当然也有可能我理解错了)——
因为这个题目依赖于主公亮明了身份。这个条件不是必要的,而是极其必要的。
因为,除主公外任意一个人要表明身份,必须要攻击一个以表明身份的人,这就构成了一个DAG(其实也是一颗树)。
这个DAG就是AOE网。
还有,我只是写了“三国杀”里的很小的一部分就出了这么多bug,如果是真正的三国杀,那真是无法可想。。
code:
#include<bits/stdc++.h>
using namespace std;
const bool diff[3][3]={{0,0,1},{0,0,1},{1,1,0}};
const int M=2005;
int n,m,fanzhu,deadfan,rounds,tmp[M],used[M]; char ch,cu;
struct PIGS {int iden,bloods,perfo,dead,nxt,equip,cnt; char cards[M];}a[15];
deque <char> cards_pile;
void _file() {
//freopen("pigs.in","r",stdin);
//freopen("pigs.out","w",stdout);
}
inline char read() {
ch=getchar();
while (ch<'A'||ch>'Z') ch=getchar();
return ch;
}
void _init() {
scanf("%d%d",&n,&m),fanzhu=deadfan=0;
for (int i=1,las=0; i<=n; i++) {
a[i].bloods=a[i].cnt=4,a[i].dead=a[i].perfo=a[i].equip=0,a[i].nxt=i%n+1;
cu=read(),fanzhu+=(cu=='F'),a[i].iden=(cu!='F')?((cu!='Z')?0:1):2,cu=read();
for (int j=1; j<=4; j++) a[i].cards[j]=read();
}
a[1].perfo=1;
for (int i=1; i<=m; i++) cards_pile.push_back(read());
}
void get_cards(int cur) {
a[cur].cards[++a[cur].cnt]=cards_pile.front();
if (cards_pile.size()>1) cards_pile.pop_front();
}
bool ought(int cur) {
int nxt=a[cur].nxt;
if (a[nxt].perfo==0) return 0; else
if (a[nxt].perfo==1) return diff[a[cur].iden][a[nxt].iden];
else return a[cur].iden==0;
}
int atk(int cur) {
if (a[cur].iden==2) return 1;
for (int nxt=a[cur].nxt; nxt!=cur; nxt=a[nxt].nxt) if (!a[nxt].dead)
if ((a[nxt].iden==2&&a[nxt].perfo==1)||(a[cur].iden==0&&a[nxt].perfo==-1)) return nxt;
return -1;
}
void pend(int x,int y) {
if (a[x].iden==0&&a[y].iden==1) {
for (int i=1; i<=a[x].cnt; i++) used[i]=rounds; a[x].equip=0;
}
else if (a[y].iden==2) get_cards(x),get_cards(x),get_cards(x);
}
int find(int cur,char aim) {
for (int i=1; i<=a[cur].cnt; i++) if (a[cur].cards[i]==aim) return i;
return 0;
}
void adjust(int cur,int s,int t) {
for (int i=s; i<t; i++) a[cur].cards[i]=a[cur].cards[i+1];
}
void respond_peach(int cur,int user) {
int re=find(cur,'P');
if (cur==user) {
re=0;
for (int i=1; i<=a[cur].cnt; i++) if (used[i]!=rounds&&a[cur].cards[i]=='P') {re=i; break;}
if (re) used[re]=rounds,a[cur].bloods++;
return;
}
if (re) a[cur].bloods++,adjust(cur,re,a[cur].cnt),a[cur].cnt--;
}
bool respond_dodge(int cur) {
int re=find(cur,'D');
if (re) adjust(cur,re,a[cur].cnt),a[cur].cnt--;
return re;
}
bool respond_kill(int cur,int user) {
int re=find(cur,'K');
if (cur==user) {
re=0;
for (int i=1; i<=a[cur].cnt; i++) if (used[i]!=rounds&&a[cur].cards[i]=='K') {re=i; break;}
if (re) used[re]=rounds;
return re;
}
if (re) adjust(cur,re,a[cur].cnt),a[cur].cnt--;
return re;
}
bool respond_wuxie(int cur,int user) {
int re=find(cur,'J');
if (cur==user) {
re=0;
for (int i=1; i<=a[cur].cnt; i++) if (used[i]!=rounds&&a[cur].cards[i]=='J') {re=i; break;}
if (re) used[re]=rounds;
return re;
}
if (re) adjust(cur,re,a[cur].cnt),a[cur].cnt--;
return re;
}
void lose_blood(int cur,int user) {
a[cur].bloods--; if (a[cur].bloods<1) respond_peach(cur,user);
}
void change_link(int cur) {
for (int pre=1; pre<=n; pre++)
if (!a[pre].dead&&a[pre].nxt==cur) {a[pre].nxt=a[cur].nxt; break;}
}
void do_peach(int cur) {
a[cur].bloods++;
}
void do_kill(int cur) {
int nxt=a[cur].nxt;
a[cur].perfo=1;
if (!respond_dodge(nxt)) {
lose_blood(nxt,cur);
if (a[nxt].bloods<1) deadfan+=(a[nxt].iden==2),a[nxt].dead=1,a[cur].nxt=a[nxt].nxt;
if (fanzhu==deadfan||a[1].dead) return;
if (a[nxt].bloods<1) pend(cur,nxt);
}
}
bool do_wuxie(int user,int cur,int aim,int now) {
bool ret=now;
for (int nxt=cur; ; ) if (!a[nxt].dead) {
if (!now) {
if (!diff[a[nxt].iden][a[aim].iden])
if (respond_wuxie(nxt,user)) {a[nxt].perfo=1; return do_wuxie(user,nxt,aim,1-now);}
}else {
if (diff[a[nxt].iden][a[aim].iden])
if (respond_wuxie(nxt,user)) {a[nxt].perfo=1; return do_wuxie(user,nxt,aim,1-now);}
}
nxt=a[nxt].nxt; if (nxt==cur) break;
}
return ret;
}
void do_fight(int cur,int aim,int user) {
a[cur].perfo=1;
if (a[aim].perfo==1) {
if (do_wuxie(cur,cur,aim,0)) return;
}
for (; ;) {
if (a[cur].iden==0&&a[aim].iden==1) {
lose_blood(aim,user);
if (a[aim].bloods<1) deadfan+=(a[aim].iden==2),a[aim].dead=1,change_link(aim);
if (fanzhu==deadfan||a[1].dead) return;
if (a[aim].bloods<1) pend(cur,aim);
return;
}else
if (!respond_kill(aim,user)) {
lose_blood(aim,user);
if (a[aim].bloods<1) deadfan+=(a[aim].iden==2),a[aim].dead=1,change_link(aim);
if (fanzhu==deadfan||a[1].dead) return;
if (a[aim].bloods<1) pend(cur,aim);
return;
}
if (!respond_kill(cur,user)) {
lose_blood(cur,user);
if (a[cur].bloods<1) deadfan+=(a[cur].iden==2),a[cur].dead=1,change_link(cur);
if (fanzhu==deadfan||a[1].dead) return;
if (a[cur].bloods<1) pend(aim,cur);
return;
}
}
}
void do_nanzhu(int cur) {
for (int nxt=a[cur].nxt; nxt!=cur; nxt=a[nxt].nxt) if (!a[nxt].dead) {
if (a[nxt].perfo==1) {
if (do_wuxie(cur,cur,nxt,0)) continue;
}
if (!respond_kill(nxt,cur)) {
lose_blood(nxt,cur); if (nxt==1&&a[cur].perfo==0) a[cur].perfo=-1;
if (a[nxt].bloods<1) deadfan+=(a[nxt].iden==2),a[nxt].dead=1,change_link(nxt);
if (fanzhu==deadfan||a[1].dead) return;
if (a[nxt].bloods<1) pend(cur,nxt);
}
}
}
void do_wanjian(int cur) {
for (int nxt=a[cur].nxt; nxt!=cur; nxt=a[nxt].nxt) if (!a[nxt].dead) {
if (a[nxt].perfo==1) {
if (do_wuxie(cur,cur,nxt,0)) continue;
}
if (!respond_dodge(nxt)) {
lose_blood(nxt,cur); if (nxt==1&&a[cur].perfo==0) a[cur].perfo=-1;
if (a[nxt].bloods<1) deadfan+=(a[nxt].iden==2),a[nxt].dead=1,change_link(nxt);
if (fanzhu==deadfan||a[1].dead) return;
if (a[nxt].bloods<1) pend(cur,nxt);
}
}
}
void do_zhuge(int cur) {
a[cur].equip=1;
}
bool dis_cards(int cur) {
memset(used,0,sizeof used);
int i,cntused,totkill=0,counts,ret=-1,aim; char now;
for (rounds=1; ; rounds++) {
cntused=counts=0;
for (i=1; i<=a[cur].cnt; i++) if (used[i]!=rounds) {
now=a[cur].cards[i];
switch (now) {
case 'P':
if (a[cur].bloods<4) do_peach(cur),used[i]=rounds,cntused++,i=a[cur].cnt;
break;
case 'K':
if ((!totkill||a[cur].equip)&&ought(cur)) do_kill(cur),used[i]=rounds,cntused++,totkill++,i=a[cur].cnt;
break;
case 'F':
aim=atk(cur); if (aim!=-1) do_fight(cur,aim,cur),used[i]=rounds,cntused++,i=a[cur].cnt;
break;
case 'N':
do_nanzhu(cur),used[i]=rounds,cntused++,i=a[cur].cnt;
break;
case 'W':
do_wanjian(cur),used[i]=rounds,cntused++,i=a[cur].cnt;
break;
case 'Z':
do_zhuge(cur),used[i]=rounds,cntused++,i=a[cur].cnt;
break;
default:
break;
}
if (fanzhu==deadfan||a[1].dead) {ret=1; break;}
if (a[cur].dead) {ret=0; break;}
}
for (int i=1; i<=a[cur].cnt; i++) if (used[i]!=rounds) tmp[++counts]=a[cur].cards[i];
for (int i=1; i<=counts; i++) a[cur].cards[i]=tmp[i]; a[cur].cnt=counts;
if (!cntused&&ret!=1) ret=0;
if (ret>-1) return ret;
}
}
bool playing(int cur) {
get_cards(cur),get_cards(cur);
return dis_cards(cur);
}
void _duel() {
for (int i=1,event=0; !event&&fanzhu>0; i=a[i].nxt) if (!a[i].dead) event=playing(i);
}
void _print() {
printf("%s\n",a[1].dead?"FP":"MP");
for (int i=1; i<=n; i++) {
if (a[i].dead) printf("%s","DEAD"); else {
if (a[i].cnt>0) printf("%c",a[i].cards[1]);
for (int j=2; j<=a[i].cnt; j++) printf(" %c",a[i].cards[j]);
}
puts("");
}
}
int main() {
_init();
_duel();
_print();
return 0;
}
之前这个代码有些注释有bug,现已修复重新提交【给我过吧
Q
A
Q
QAQ我辣么可怜】
首先我的这段代码参考过了hzwerdalao的做法但是大概架构是我打的只是有些小bug和他的代码对拍而已QAQ
四位dalao都把一些注意事项和小坑都给列出来了,我就贴个有注释的代码吧……【打了我四个中午还鸽了一会QAQ】
/*
牌库为空之后再抽牌,会重复抽最后一张被抽走牌
主公死或反贼全死都中断游戏,直接进入输出阶段
所有人一开始都知道主公的身份
每个人都会无条件帮队友无懈掉决斗【万箭】【南蛮】
每个人都会无条件【无懈】掉对手的【无懈】
每个人都不会【无懈】掉队友的【无懈】
反贼只会【决斗】主公
忠臣被主公【决斗】并不能算跳忠
主公杀了忠臣要清空所有牌以及【诸葛连弩】
杀死反贼摸三张牌
*/
/*
桃 P
杀 K
闪 D
决斗 F
南蛮入侵 N
万箭齐发 W
无懈可击 J
诸葛连弩 Z
*/
#include<bits/stdc++.h>
using namespace std;
struct node{
int cs,hp,next,last;
//cs=cardsize=手牌数
//hp 生命值
char id,card[2010];
//id=identity=身份
bool zgln;
//是否装备了【诸葛连弩】
}a[20];
char kn[11],kpd[2010],sss[10];
//kn表示在主公眼里这只猪的身份
//kpd 卡牌堆
int n,m,fz;
//fz 反贼数量
bool ed;
void mp(int x)
//【摸牌】
{
if(!m) m++;
a[x].card[++a[x].cs]=kpd[m];
m--;
}
void js(int x1,int x2)
//【击杀】
{
for(int i=1;i<=a[x2].cs;i++)
if(a[x2].card[i]=='P')
//判断是否有【桃】
{
a[x2].card[i]='U';
a[x2].hp++;
return ;
}
a[a[x2].next].last=a[x2].last;
a[a[x2].last].next=a[x2].next;
//死亡后改变攻击距离
if(x2==1){ed=true;return ;}
if(a[x2].id=='F') fz--;
if(!fz){ed=true;return ;}
//没有反贼游戏结束
if(a[x2].id=='F') mp(x1),mp(x1),mp(x1);
//击杀反贼摸三张牌
if(a[x2].id=='Z' && a[x1].id=='M') a[x1].cs=0,a[x1].zgln=false;
//如果主公把忠臣杀了那就要弃牌
}
void Kil(int x1,int x2)
//【杀】
{
for(int i=1;i<=a[x2].cs;i++)
if(a[x2].card[i]=='D')
{
a[x2].card[i]='U';
return ;
}
a[x2].hp--;
if(!a[x2].hp) js(x1,x2);
//每次造成伤害都要判断被攻击者是否空血
//然后进入【击杀】环节
}
bool wxkj(int x1,int x2,int x3)
//【无懈可击】
{
int i=x1,pd=x3?x2:x1;
while(1)
{
if(x3==1)
{
if(kn[x2]==a[i].id || (kn[x2]=='M' && a[i].id=='Z') || (kn[x2]=='Z' && a[i].id=='M'))
//假如是敌对的那就【无懈可击】掉
for(int j=1;j<=a[i].cs;j++)
if(a[i].card[j]=='J')
{
a[i].card[j]='U';
kn[i]=a[i].id;
return !wxkj(i,x1,0);
}
}
else
{
if(((a[i].id=='M' || a[i].id=='Z') && kn[x1]=='F') || (a[i].id=='F' && (kn[x1]=='M' || kn[x1]=='Z')))
for(int j=1;j<=a[i].cs;j++)
if(a[i].card[j]=='J')
{
a[i].card[j]='U';
kn[i]=a[i].id;
return !wxkj(i,x1,0);
}
}
i=a[i].next;
if(i==x1) break;
//没有人用【无懈可击】抵挡此【无懈可击】那么这张牌就生效
}
return false;
}
void nmrq(int x1)
//【南蛮入侵】
{
for(int x2=a[x1].next;x2!=x1;x2=a[x2].next)
//进行一个循环来出牌
if(!wxkj(x1,x2,1))
//判断是否被【无懈可击】掉
{
int i;
for(i=1;i<=a[x2].cs;i++)
if(a[x2].card[i]=='K')
{
a[x2].card[i]='U';
break;
}
if(i>a[x2].cs)
{
a[x2].hp--;
if(x2==1 && kn[x1]=='U') kn[x1]='L';
if(!a[x2].hp) js(x1,x2);
if(ed) return ;
}
}
}
void wjqf(int x1)
//【万箭齐发】
{
for(int x2=a[x1].next;x2!=x1;x2=a[x2].next)
if(!wxkj(x1,x2,1))
//判断是否被【无懈可击】掉
{
int i;
for(i=1;i<=a[x2].cs;i++)
if(a[x2].card[i]=='D')
{
a[x2].card[i]='U';
break;
}
if(i>a[x2].cs)
{
a[x2].hp--;
if(x2==1 && kn[x1]=='U') kn[x1]='L';
if(!a[x2].hp) js(x1,x2);
if(ed) return ;
}
}
}
void jd(int x1,int x2)
//【决斗】
{
int i,j,k;
if(wxkj(x1,x2,1)) return ;
//判断是否被【无懈可击】掉
if(x1==1 && a[x2].id=='Z')
{
a[x2].hp--;
if(!a[x2].hp) js(x1,x2);
return ;
}
j=k=1;
while(1)
{
while(a[x2].card[j]!='K' && j<=a[x2].cs) j++;
if(j>a[x2].cs)
{
a[x2].hp--;
if(!a[x2].hp) js(x1,x2);
return ;
}
else a[x2].card[j]='U';
while(a[x1].card[k]!='K' && k<=a[x1].cs) k++;
if(k>a[x1].cs)
{
a[x1].hp--;
if(!a[x1].hp) js(x2,x1);
return ;
}
else a[x1].card[k]='U';
}
}
void hh()
//进入回合
{
char nc;
ed=true;
if(fz) ed=false;
if(ed) return ;
for(int i=1;i;i=a[i].next)
{
mp(i),mp(i);
bool kill=true;
for(int j=1;j<=a[i].cs;j++)
if(a[i].card[j]!='U')
//=='U'就是被使用过,弃置了
{
if(!a[i].hp) break;
nc=a[i].card[j];
//nc=now-card=现在的手牌
if(nc=='P')
//使用【桃】
{
if(a[i].hp!=4) a[i].hp++,a[i].card[j]='U';
continue;
}
if(nc=='K')
//使用【杀】
{
if(!kill && !a[i].zgln) continue;
if(a[i].id=='M' && kn[a[i].next]!='L' && kn[a[i].next]!='F') continue;
if(a[i].id=='Z' && kn[a[i].next]!='F') continue;
if(a[i].id=='F' && kn[a[i].next]!='Z' && kn[a[i].next]!='M') continue;
a[i].card[j]='U';
Kil(i,a[i].next);
kn[i]=a[i].id,kill=false;
if(ed) return ;
continue;
}
if(nc=='F')
//使用【决斗】
{
if(a[i].id=='F')
{
a[i].card[j]='U',jd(i,1);
kn[i]=a[i].id;
if(ed) return ;
j=0;
continue;
}
for(int k=a[i].next;k!=i;k=a[k].next)
if((a[i].id=='M' && (kn[k]=='L' || kn[k]=='F')) || (a[i].id=='Z' && kn[k]=='F'))
{
a[i].card[j]='U',jd(i,k);
kn[i]=a[i].id;
if(ed) return ;
j=0;
break;
}
continue;
}
if(nc=='N')
//使用【南蛮入侵】
{
a[i].card[j]='U';
nmrq(i);
if(ed) return ;
j=0;
continue;
}
if(nc=='W')
//使用【万箭齐发】
{
a[i].card[j]='U';
wjqf(i);
if(ed) return ;
j=0;
continue;
}
if(nc=='Z')
//装备【诸葛连弩】
{
a[i].zgln=true;
a[i].card[j]='U';
j=0;
continue;
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) a[i].next=i+1,a[i].last=i-1;
//攻击距离
a[n].next=1,a[1].last=n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<2010;j++) a[i].card[j]='U';
scanf("%s",sss);a[i].id=sss[0];
for(int j=1;j<=4;j++) scanf("%s",sss),a[i].card[j]=sss[0];
a[i].cs=a[i].hp=4;
//手牌上限和血量上限都是4
if(a[i].id=='F') fz++;
//统计反贼的数量
a[i].zgln=false;
}
for(int i=2;i<=n;i++) kn[i]='U';
kn[1]='M';
for(int i=1;i<=m;i++) scanf("%s",sss),kpd[m-i+1]=sss[0];
hh();
//进入回合
if(a[1].hp<=0) printf("FP\n");
else printf("MP\n");
//看看主公有没有挂
for(int i=1;i<=n;i++)
{
if(a[i].hp<=0) printf("DEAD\n");
//空血凉凉
else
//亮牌
{
for(int j=1;j<=a[i].cs;j++)
if(a[i].card[j]!='U') printf("%c ",a[i].card[j]);
//若该手牌没有被弃置则输出
printf("\n");
}
}
return 0;
}
蒟蒻我和机房oier们宣布要在11月底写完猪国杀,于是在11月31日写完了(众所周知11月至少有31天)。我码的时候也遇到了各种各样的问题,比较重要的有:
使用某些牌后,可能使它之前的手牌变得可用,所以使用手牌后,要从头扫描手牌。
决斗可能会使正在进行回合的猪死亡(所以果然是猪吗……),此时要赶紧返回,不能让已经死亡的猪继续使用手牌。
忠猪面对来自主猪的决斗,会直接自残。
寻找杀和决斗等的对象,要跳过已经死亡的猪。
决斗、南猪、万箭的无懈询问是从出锦囊牌的猪开始的。
此外,还有一点很重要的事情,牌堆可能被抽空。抽空后,要不断抽牌堆最后一张牌。这应该属于出题人的失误。
作为蒟蒻,这么大的模拟我写不了面向过程,只能用三脚猫的面向对象惹。
以下为AC代码,供各位dalao鄙视QwQ
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define for_i_in_hand for(itr i = hand.begin();i < hand.end();i++)
#define itr std::vector<card>::iterator
using std::cin;
using std::cout;
using std::endl;
struct card
{
bool avai;
char card;
};
class Pig
{
public:
//方法
void identity_set (char id);//身份初始化
void turn_start ();//回合开始
void draw ();//抽一张牌
void act ();//出牌阶段
void use_card (itr i,char ch);//使用卡牌
bool find_and_use (char ch);//在手牌中寻找并打出一张牌
int dec_health ();//生命值减少
int inc_health ();//生命值增加
void jump ();//跳身份
void go_rebel_like ();//进入类反状态
void ungo_rebel_like ();//取消类反状态
void unequip ();//卸下诸葛连弩
void hurt (Pig &that);//对that造成伤害
bool close_to_death ();//进入濒死状态
void slay (Pig &that);//杀死that
void slain ();//被杀死
bool use_admission (char ch);//是否可能在出牌阶段使用该卡牌
int try_kill ();//寻找杀对象
void kill (Pig &that);//杀
bool kill_respond ();//响应杀
int try_fight ();//寻找决斗对象
void fight (Pig &that);//决斗
void invasion ();//南蛮入侵
bool invasion_respond();//响应南蛮入侵
void arrows ();//万箭齐发
bool arrows_respond ();//响应万箭齐发
void crossbow ();//装备诸葛连弩
bool first_watertight_ask (Pig &that);//使用无懈可击的第一阶段
bool against_watertight_ask ();//无懈可击的对抗阶段
//变量
int num;//在数组中的编号
std::vector<card> hand;//手牌
char identity;//身份
int health;//剩余生命值
bool equipped;//是否装备着诸葛连弩
bool dead;//是否死亡
bool kill_used;//本回合是否已经使用过杀
bool jumped;//是否已经跳身份
bool rebel_liked;//是否处于类反状态
};
class Input
{
public:
void main_input();//主输入
void in_n_and_m();//输入n和m
void in_id_and_cards();//输入身份和起始手牌
void in_card_heap();//输入牌堆
};
int n,m;//n表示总猪数,m表示牌堆的牌数量
int main_pig;//主猪编号
int anti_num = 0;//当前剩余反猪数量
int heap_top = 1;//牌堆顶
bool gameover;//是否可以结束游戏
char win;//胜利方
char card_heap[20000];//牌堆
Input In;//Input对象
Pig pig[20];//记录各猪
const bool debug = false;//调试入口,改为真即输出调试信息
void num_set();//从数组编号向各猪num属性映射
void heap_copy();//复制牌堆最后一张牌
void print();//输出结果
void initialize();//猪的初始化
int main()//entrance
{
In.main_input();//输入
initialize();
num_set();
heap_copy();
pig[main_pig].jump();//主猪一开始是跳的
if(anti_num == 0)//特判:场上没有反猪,主猪方直接胜利
{
win = 'M';
print();
return 0;
}
register int i = 0;//轮到的猪
while(1)//主循环
{
i = i % n + 1;
if(pig[i].dead) continue;//跳过死亡的猪
if(debug) printf("%d's turn started\n\n",i);
pig[i].turn_start();//回合开始
if(gameover)//检测游戏是否结束
{
print();
break;//输出结果,程序结束
}
}
return 0;
}
void num_set()
{
for(register int i = 1;i <= n;++i) pig[i].num = i;
}
void heap_copy()
{
register char ch = card_heap[m];
for(register int i = m + 1;i <= 5000;i++)
{
card_heap[i] = ch;//复制最后一张牌
}
}
void print()
{
if(win == 'M')
{
printf("MP\n");
}
else
{
printf("FP\n");
}
for(int i = 1;i <= n;i++)
{
if(pig[i].dead)
{
printf("DEAD\n");
}
else
{
Pig s = pig[i];
for(itr i = s.hand.begin();i < s.hand.end();i++)
{
if(i->avai) printf("%c ",i->card);
}
printf("\n");
}
}
}
void initialize()
{
for(int i = 1;i <= n;i++)//信息初始化
{
pig[i].health = 4;
pig[i].equipped = false;
pig[i].dead = false;
pig[i].kill_used = false;
pig[i].jumped = false;
pig[i].rebel_liked = false;
}
}
void Input::main_input()
{
in_n_and_m();
in_id_and_cards();
in_card_heap();
}
void Input::in_n_and_m()//
{
scanf("%d %d",&n,&m);
}
void Input::in_id_and_cards()//
{
char s[10];
char ch;
for(int i = 1;i <= n;i++)
{
scanf("%s",s);
if(s[0] == 'M')
{
main_pig = i;//记录主猪编号
}
else if(s[0] == 'F')
{
anti_num++;//记录反贼数量
}
pig[i].identity_set(s[0]);//身份初始化
while((ch = getchar()) != '\n')//寻找下一张手牌
{
if(ch >= 'A' && ch <= 'Z')
{
pig[i].hand.push_back(card{true,ch});//加入手牌
}
}
}
}
void Input::in_card_heap()
{
char ch;
for(register int i = 1;i <= m;++i)
{
while( ( ch = getchar() ) < 'A' || ch > 'Z' ) continue;
card_heap[i] = ch;
}
}
void Pig::identity_set(char id)
{
identity = id;
}
void Pig::turn_start()
{
draw();
draw();//抽两张牌
if(debug)
{
for(int i = 1;i<=n;i++)
{
if(pig[i].dead)continue;
printf("%d %c %d:",pig[i].num,pig[i].identity,pig[i].health);
Pig s = pig[i];
for(itr i = s.hand.begin();i < s.hand.end();i++) if(i->avai) printf("%c ",i->card);
printf("\n");
}
}
act();//出牌阶段开始
kill_used = false;//重置出杀检测
if(debug) cout<<endl;
}
void Pig::draw()
{
hand.push_back(card{true,card_heap[heap_top++]});//加入一张手牌,牌堆顶后移
}
void Pig::act()
{
bool flag=true;
while(flag)//扫了一遍手牌,如果没有出牌,则结束出牌阶段
{
flag = false;
for(itr i = hand.begin();i != hand.end();i++)
{
if(i->avai&&use_admission(i->card))
{
use_card(i,i->card);
if(dead || gameover) return;
//注意:这只猪有可能在决斗中自杀身亡,这时不能让它继续出牌
//如果游戏结束,那么立即返回
i = hand.begin() - 1;
//使用一张牌后,前面的牌可能变得可用,此时需要回到第一张手牌重新扫描手牌
//之所以要-1,是因为在for循环末尾i++
flag = true;//使用了牌
}
}
}
}
bool Pig::find_and_use(char ch)
{
for_i_in_hand
{
if(i->card == ch && i->avai)//如果是想要的牌而且没出过
{
i->avai = false;//懒惰删除,标记牌已使用过
if(debug) cout<<num<<" used a "<<ch<<endl;
return true;//使用了牌,返回真
}
}
return false;//找不到牌,返回假
}
void Pig::use_card(itr i,char ch)
{
int tmp;
switch(ch)
{
case 'K'://杀
if(tmp = try_kill())//用tmp记下杀的对象,决斗同
{
i->avai = false;
if(debug) printf("%d used a kill to %d\n",num,tmp);
kill(pig[tmp]);
}
break;
case 'P'://桃
i->avai = false;
if(debug) printf("%d used a peach\n",num);
inc_health();
break;
case 'F':
if(tmp = try_fight())
{
i->avai = false;
if(debug) printf("%d used a fight to %d\n",num,tmp);
fight(pig[tmp]);
}
break;
case 'N':
i->avai = false;
if(debug) printf("%d used a invasion\n",num);
invasion();
break;
case 'W':
i->avai = false;
if(debug) printf("%d used a arrows\n",num);
arrows();
break;
case 'Z':
i->avai = false;
if(debug) printf("%d used a crossbow\n",num);
crossbow();
}
}
inline int Pig::dec_health()
{
return --health;
}
inline int Pig::inc_health()
{
return ++health;
}
inline void Pig::jump()
{
jumped = true;
}
inline void Pig::go_rebel_like()
{
rebel_liked = true;
}
inline void Pig::ungo_rebel_like()
{
rebel_liked = false;
}
inline void Pig::unequip()
{
equipped = false;
}
inline void Pig::hurt(Pig &that)
{
that.dec_health();//生命值减少
if(debug) printf("%d hurts %d,left health %d\n",num,that.num,that.health);
if(that.health == 0 && that.close_to_death())//濒死检查
{
if(debug) cout<<that.num<<" died"<<endl;
this->slay(that);//击杀处理
}
}
bool Pig::close_to_death()
{
//找桃
for(itr i = hand.begin();i < hand.end();i++)
{
if(i->card == 'P' && i->avai)
{
i->avai = false;
inc_health();
return false;
}
}
return true;
}
void Pig::slay(Pig &that)
{
that.slain();
if(that.identity == 'M')//主公死亡,反贼胜利
{
win = 'F';
gameover = true;
}
else if(that.identity == 'F')//反贼死亡
{
anti_num--;
if(anti_num == 0)//反贼全灭,主公胜利
{
win = 'M';
gameover = true;
return;//胜利后不进行奖励抽牌
}
draw(),draw(),draw();
}
else if(identity == 'M' && that.identity == 'Z')//主公杀忠臣
{
for(itr i = hand.begin();i < hand.end();i++)
{
i->avai = false;
}
unequip();
}
}
inline void Pig::slain()//
{
dead = true;
}
bool Pig::use_admission(char ch)//
{
if(ch == 'P' && health < 4) return true;//生命值不满时才允许使用桃
else if((ch == 'K' && try_kill()) || (ch == 'F' && try_fight()) || ch == 'N' || ch == 'W' || ch == 'Z') return true;//使用杀和决斗必须有对象
return false;
}
int Pig::try_kill()
{
//返回值为0,表示无猪可杀。返回值不为0,即为被指定为杀的对象的猪的编号
if(kill_used && !equipped) return 0;//本回合不得再出杀
if(identity == 'M')//主猪杀猪
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;//跳过死亡猪,寻找逆时针第一只猪,下同
if(pig[i].rebel_liked || (pig[i].identity == 'F' && pig[i].jumped))//检测下一只猪是否为类反猪或反猪
{
return i;
}
else//不是,不杀
{
return 0;
}
}
}
else if(identity == 'Z')//忠猪杀猪
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(pig[i].identity == 'F' && pig[i].jumped)//需求反猪
{
return i;
}
else
{
return 0;
}
}
}
else//反猪杀猪
{
int i;
for(i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(pig[i].identity != 'F' && pig[i].jumped)//需求反猪
{
return i;
}
else
{
return 0;
}
}
}
}
inline void Pig::kill(Pig &that)
{
jump();//杀必然跳身份
ungo_rebel_like();//杀必然脱离类反状态
kill_used = true;//本回合使用了杀
if(!that.kill_respond())//响应为假,说明无闪可出
{
hurt(that);
}
}
inline bool Pig::kill_respond()
{
return find_and_use('D');//在手牌中寻找闪来抵消杀
}
int Pig::try_fight()
{
if(identity == 'M')
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(pig[i].rebel_liked || (pig[i].identity == 'F' && pig[i].jumped))
{
return i;
}
}
}
else if(identity == 'Z')
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(pig[i].identity == 'F' && pig[i].jumped)
{
return i;
}
}
}
else//反猪必然对主猪决斗
{
return main_pig;
}
return 0;
}
void Pig::fight(Pig &that)
{
jump();
ungo_rebel_like();
if(first_watertight_ask(that)) return;//为对方询问无懈可击
if(identity == 'M' && that.identity == 'Z')//主猪决斗忠猪,忠猪必然放弃
{
hurt(that);
return;
}
int hurted;//记录被杀者
while(1)//直到一方无杀才跳出循环
{
if(!that.find_and_use('K'))
{
hurted = 2;
break;
}
if(!find_and_use('K'))
{
hurted = 1;
break;
}
}
if(hurted == 1)
{
that.hurt(*this);
}
else
{
hurt(that);
}
}
void Pig::invasion()
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(debug) cout<<"invasion->"<<i<<endl;
if(first_watertight_ask(pig[i])) continue;
if(!pig[i].invasion_respond())
{
hurt(pig[i]);
if(gameover) return;
if(pig[i].identity == 'M' && !jumped)//类反猪判定
{
go_rebel_like();
}
}
}
}
inline bool Pig::invasion_respond()
{
return find_and_use('K');
}
void Pig::arrows()
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(debug) cout<<"arrows->"<<i<<endl;
if(first_watertight_ask(pig[i])) continue;
if(!pig[i].arrows_respond())
{
hurt(pig[i]);
if(gameover) return;
if(pig[i].identity == 'M' && !jumped)
{
go_rebel_like();
}
}
}
}
inline bool Pig::arrows_respond()
{
return find_and_use('D');
}
inline void Pig::crossbow()
{
equipped = true;
}
bool Pig::first_watertight_ask(Pig &that)
{
if(!that.jumped) return false;//无猪(注意:包括它自己)为未跳者进行无懈可击
bool flag = true;
for(int i = num;i != num || flag;i = i % n + 1)
{
flag = false;
if(pig[i].dead) continue;
if(that.identity == 'M' && pig[i].identity == 'F') continue;
if(that.identity == 'Z' && pig[i].identity == 'F') continue;
if(that.identity == 'F' && pig[i].identity == 'Z') continue;
if(that.identity == 'F' && pig[i].identity == 'M') continue;//寻找同势力
if(pig[i].find_and_use('J'))
{
pig[i].jump();
return !pig[i].against_watertight_ask();//对抗开始
}
}
return false;
}
bool Pig::against_watertight_ask()
{
for(int i = num % n + 1;i != num;i = i % n + 1)
{
if(pig[i].dead) continue;
if(identity == 'M' && pig[i].identity == 'Z') continue;
if(identity == 'Z' && pig[i].identity == 'Z') continue;
if(identity == 'F' && pig[i].identity == 'F') continue;
if(identity == 'Z' && pig[i].identity == 'M') continue;//寻找异势力
if(pig[i].find_and_use('J'))
{
pig[i].jump();
return !pig[i].against_watertight_ask();
}
}
return false;
}
很古怪的翻译,不过它确实叫猪(Pig)国(Country)杀(Kill)。
我们来好好整理一下这道题目。题面虽较长,但内容基本清晰,只是有部分很Pig的操作部分,很容易让第一次看见这道题目的人百思不得其解。
先整理一下这道长长的题面。
First:人物
四位玩家,初始四张手牌,血量上限
4
4,初始血量
4
4,会告诉你整个牌堆的牌,每位玩家每个回合从牌堆顶部抽走两张牌,放在自己手牌的右侧。人物分主猪,忠猪,和反猪,主猪只有一只,反猪和忠猪可以有多只,反猪全死主猪获胜,主猪死亡反猪获胜。
Second:关于出牌
从主猪开始逆时针旋转,就是沿序号为
1
1
2
2
3
3
.
.
.
…
n
n的顺序依次出牌。每头猪在出牌的时候都会从左往右挨个判断每张牌是否可用,将可用的牌直接用掉。牌分为
P
P(桃),
K
K(杀),
D
D(闪)和锦囊牌
F
F(决斗),
N
N(南猪入侵),
W
W(万箭齐发),
J
J(无懈可击)和装备牌
Z
Z(猪哥连弩)。具体操作会在下面贴代码的地方说明。
Third:猪的特性
有南猪入侵和万箭齐发一定用,有桃且生命值没满一定吃,有猪哥连弩一定装,受到杀,南猪入侵,万箭齐发可以用闪,杀,闪抵消一定抵消。
Forth:高级操作
跳忠和跳反,即表明自己是忠猪或是反猪,主猪开局直接跳,忠猪只会跳忠,反猪只会跳反。已经跳(后面我们将跳忠和跳反统称为跳,因为忠猪不会跳反,反猪不会跳忠)了的猪只会对同样跳了的同类献殷勤,同时对跳了的敌猪表敌意。跳反猪同时会对主猪表敌意,因为主猪和忠猪是统一阵营。
Fifth:献殷勤和表敌意
猪A对猪B使用了杀或是决斗,叫作猪
A
A对猪
B
B表敌意。猪
A
A用无懈可击帮猪
B
B抵挡住攻击性锦囊牌,叫作猪
A
A对猪
B
B献殷勤。猪
C
C对猪
B
B表敌意,被猪
A
A用无懈可击抵挡住,叫作猪
A
A对猪
B
B先殷勤。猪
C
C对猪
B
B献殷勤,被猪
A
A用无懈可击抵消,叫作猪
A
A对猪
B
B表敌意。
Sixth:某些猪的特性
主猪会竭力保护自己,会对跳忠猪献殷勤,对跳反猪表敌意。有“类反猪”一概念,所有对他产生伤害的猪都是“类反猪”(才有了样例中的美好结局),主猪同时会对类反猪表敌意;对忠猪来说,它会对跳反猪(没有类反猪)表敌意,角斗时如果对方是主猪,那么不会出杀,会自愿扣血,即使自己会死,会对主猪和已经跳忠的猪献殷勤;对反猪来说,如果能杀到主猪,一定对主猪表敌意(决斗一定打主猪),如果杀不到,对跳忠猪表敌意,对跳反猪一定献殷勤。
上面的情况如果可以同时有多个对象可以表敌意或献殷勤,选取逆时针找到的第一位。且如果能献殷勤或表敌意,一定做。
Seventh:其他
如果主猪死亡,游戏结束。如果主猪杀死了忠猪,主猪所有的手牌(包括装备)全部弃置。如果反猪全部死亡,游戏结束。反猪死亡后,杀他的猪从牌堆处摸三张牌(即使他是反猪)。如果游戏结束,那么游戏直接在此时停止,接下来需要做的所有事情都被截止。杀只能打到自己正后面的那一头猪,决斗可以随便决斗。万箭齐发和南猪入侵对除自己以外的所有人顺时针使用。无懈可击可以对任何人使用,无懈可击可以使无懈可击无效,可以使决斗报废(不使任何一方受到伤害)。大部分手牌无法在非自己的回合使用,在自己濒死时(血量为
0
0)可以使用桃(自己的),在自己的献殷勤对象被表敌意时可以出无懈可击,在自己的表敌意对象被献殷勤时可以出无懈可击。注意有的猪死去后会使一些猪之间的距离产生变化。
游戏内容差不多就是这样了,如果看不懂,多看几遍也能理解,如果还是难以理解的话,可以往下看本人的AC代码以及解释,可以帮助你加深对本题的理解。
代码
定义
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>
//头文件
using std::ios;
//用using namespace就不用这个了
//以下定义虽复杂,但是方便在程序中梳理思路,让我们不易混淆true和false的关系
#define CANNOT_USE_THIS_CARD true //不能使用这张牌
#define CAN_USE_THIS_CARD false //可以使用这张牌
#define THIS_PIG_IS_DEAD true //这头猪死了
#define THIS_PIG_IS_NOT_DEAD false //这头猪还没死
#define THIS_PIG_IS_JUMP true //这头猪已经跳了
#define THIS_PIG_IS_NOT_JUMP false //这头猪还没有跳
#define THIS_PIG_LIKE_BAD true //这头猪类反
#define THIS_PIG_DO_NOT_LIKE_BAD false //这头猪不类反
#define CROSSBOW_ON true //装备猪哥连弩
#define CROSSBOW_DOWN false //没有装备猪哥连弩
#define THIS_TURN_USED_KILL true //这一回合已经使用杀
#define THIS_TURN_DO_NOT_USE_KILL false //这一回合还没有使用过杀
struct card //对每一张卡牌制造的结构体
{
char which ;//牌的种类
bool use ;//牌是否已用
};
typedef std::vector<card>::iterator myit; //这是牌堆的指针类型
class pig //对每头猪使用的类
{
private: //私有
int hp ;//生命值
bool use_kill ;//这个回合
bool crossbow ;//是否有装猪哥连弩
bool jumped ;//是否已跳
bool like_bad ;//是否类反
void die (int) ;//死掉了
void clear (void) ;//清空
void del (myit&) ;//删除某迭代器指向的玩意
myit find (char) ;//找某张牌
void cut (void) ;//拿一张牌
void use (card&) ;//用掉某张牌
void hurt (int) ;//受到伤害
bool canuse (char) ;//可以使用这张牌
int find_K (void) ;//找杀的对象
int find_F (void) ;//找人决斗
bool K_respond (void) ;//反应杀
bool N_respond (void) ;//反应南猪入侵
bool W_respond (void) ;//反应万箭齐发
bool ask_J (int) ;//询问是否出无懈可击
bool back_J (int) ;//反应无懈可击
public: //公共
pig (void) ;//析构函数
int num ;//序号
bool dead ;//死没死
std::string name ;//身份
std::vector<card> cards ;//手牌
void myturn (void) ;//到我的回合了
void jump (void) ;//跳
void K (int) ;//杀
void F (int) ;//决斗
void N (void) ;//南猪入侵
void W (void) ;//万箭齐发
};
void gameover(std::string);//游戏结束
int n,m;//猪头数量和牌堆深度
int FP;//反猪数量
card stack[2005];//牌堆(之前想写栈发现不用那么做)
pig member[15];//每头猪
int top=1;//牌堆指针
定义完毕,接下来只要对着上面的定义实现每个函数就可以了。
虽然这里的宏定义名称非常长,但是在以下的代码中就可以将难以分辨的true & false用易于理解的标识符代替,使下文结构更加清晰,Debug时更加方便
主函数
int main()
{
ios::sync_with_stdio(false);//流优化
cin>>n>>m;//输入
for(register int i=1;i<=n;i++)
{
cin>>member[i].name;//输入猪头名字
member[i].num=i;//猪头序号
if(member[i].name=="FP") FP++;//反猪数量
for(register int j=1;j<=4;j++)
{
card c;
cin>>c.which;//每张手牌
c.use=CAN_USE_THIS_CARD;//可以用
member[i].cards.push_back(c);//手牌放置
}
}
for(top=1;top<=m;top++)
{//牌堆
card c;
cin>>c.which;//输入牌堆中每一张牌的信息
c.use=CAN_USE_THIS_CARD;//可以用
stack[top]=c;//把牌放进牌堆
}
top=1;//原来想写个stack,后来废了
int point=0;//轮到谁了
member[1].jump();//主猪跳出来
while(true)
{
point=point%n+1;//一个圈
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果他死了,跳过
member[point].myturn();//这是他的回合
}
}
主函数结构基本清晰,处理了输入,牌堆,手牌,还有回合的问题。
打完了这些,游戏基本格局就以确定,接下来实现游戏内容。
游戏结束处理
void gameover(std::string who)//谁赢了
{//死掉了
cout<<who<<std::endl;//输出谁赢了
for(register int i=1;i<=n;i++)
{
if(member[i].dead==THIS_PIG_IS_DEAD) cout<<"DEAD";//如果他死了
else for(myit it=member[i].cards.begin();it!=member[i].cards.end();it++)
{//输出他的牌
if(it->use==CANNOT_USE_THIS_CARD) continue;//如果这张牌已经使用过,那么不存在于牌堆
cout<<it->which<<' ';//输出手牌的信息,然后打个空格
}
cout<<std::endl;//换行
}
exit(0);//结束这个程序
}
死亡处理
void pig::die(int who)
{//死了
this->dead=THIS_PIG_IS_DEAD;//这玩意死了
if(this->name=="MP")//如果主猪死了
{
gameover("FP");//反猪赢了
}
if(this->name=="FP") FP--;//如果反猪死了,总数减减
if(FP==0) gameover("MP");//如果反猪死光了
if(this->name=="FP")//如果杀死了是反猪
{
member[who].cut();//拿牌x1
member[who].cut();//拿牌x2
member[who].cut();//拿牌x3
}
if(this->name=="ZP"&&member[who].name=="MP")
{//如果主猪杀掉了忠猪
member[who].clear();//清空
}
return ;
手牌的清空、删除、寻找和拿牌等
void pig::clear(void)
{
this->cards.clear();//自己的手牌清空
this->crossbow=CROSSBOW_DOWN;//把弩卸掉
return ;
}
void pig::del(myit& it)
{//删除某张牌
it->use=CANNOT_USE_THIS_CARD;//删除这个迭代器指向的牌,让它不可用
return ;
}
myit pig::find(char which)
{//寻找某张牌
for(myit it=this->cards.begin();it!=this->cards.end();it++)
{//搜索每一张牌
if(it->use==CANNOT_USE_THIS_CARD) continue;//如果用过了
if(it->which==which) return it;//如果这是我要找的牌
}
return this->cards.end();//找不到,输出vector的end
}
void pig::cut(void)
{
this->cards.push_back(stack[top++]);//把牌堆上面的拿掉
if(top>m) top=m;//好像有数据说牌不够
return;
}
void pig::use(card& c)
{//用某张牌
if(c.which=='K')
{//如果这是杀
int who;
who=this->find_K();//找一个人来杀
if(who!=0)//如果找到了
{
c.use=CANNOT_USE_THIS_CARD;//用掉
this->K(who);//杀它
}
}
else if(c.which=='P')
{//如果这是桃
if(this->hp<4)//如果它的生命值没有满
{
c.use=CANNOT_USE_THIS_CARD;//用掉
this->hp++;//生命值加一点
}
}
else if(c.which=='F')
{//如果这是决斗
int who;
who=this->find_F();//找个人来决斗
if(who!=0)//如果找到了
{
c.use=CANNOT_USE_THIS_CARD;//用掉
this->F(who);//决斗他
}
}
else if(c.which=='N')
{//南猪入侵
c.use=CANNOT_USE_THIS_CARD;//用掉
this->N();//使用南猪入侵
}
else if(c.which=='W')
{//万箭齐发
c.use=CANNOT_USE_THIS_CARD;//用掉
this->W();//使用万箭齐发
}
else if(c.which=='Z')
{//猪哥连弩
c.use=CANNOT_USE_THIS_CARD;//用掉
this->crossbow=CROSSBOW_ON;//装上
}
}
void pig::hurt(int who)//谁杀的
{
this->hp--;//受到伤害
if(this->hp==0)//如果他死了
{
myit t=this->find('P');//找桃
if(t==this->cards.end()) this->die(who);//找不到,死掉
else
{
this->del(t);//找到了,用掉
this->hp++;//生命值加一点
}
}
}
bool pig::canuse(char c)
{//判断能否使用
if(c=='P'&&this->hp<4) return true;//如果是桃子且生命值未满
if((c=='K'&&this->find_K()!=0)||(c=='F'&&this->find_F()!=0)||c=='N'||c=='W'||c=='Z') return true;//如果可以用
return false;//不然就不该用
}
以上内容理解起来都较为简单,下面的代码难度会有所提高
寻找’杀’的目标
对于主猪、忠猪和反猪来说,他们的目的不同,但杀的距离都相同,为
1
1,对于每头猪,我们找到他后方的第一头猪(也是它唯一能杀到的一头猪),判断是否应该对它用杀,如果该用,返回这头猪的编号;如果不该用,直接结束函数,返回
0
0(表示没有找到杀的猪)。
主猪要找的是跳反猪和类反猪,忠猪要找的是跳反猪,反猪要找的是跳忠猪或是主猪。
int pig::find_K(void)
{//找个人来杀
if(this->use_kill==THIS_TURN_USED_KILL&&this->crossbow==CROSSBOW_DOWN) return 0;//如果这回合已经用过了杀而没有装过猪哥连弩
if(this->name=="MP")
{//我是主猪
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针寻找
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果死掉了,跳过去
if(member[point].like_bad==THIS_PIG_LIKE_BAD||(member[point].name=="FP"&&member[point].jumped==THIS_PIG_IS_JUMP))
{//如果这猪是类反猪或是已经跳反
return point;//就决斗他
}//否则继续找
else return 0;
}
}
else if(this->name=="ZP")
{//我是忠猪
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针寻找
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//这玩意死了,跳过去
if(member[point].name=="FP"&&member[point].jumped==THIS_PIG_IS_JUMP)
{//如果这是已经跳反的猪头
return point;//就杀他
}
else return 0;
}
}
else if(this->name=="FP")
{//我是反猪
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针寻找
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果死掉了,先过去
if(member[point].name!="FP"&&member[point].jumped==THIS_PIG_IS_JUMP)//找到第一头猪,如果它跳忠或是是主猪
{
return point;//杀他
}
else
{
return 0;//杀不到
}
}
}
return 0;
}
寻找’决斗’的目标
与’杀’类似,对于主猪、忠猪和反猪来说,他们寻找决斗目标的目的也不同,但考虑到决斗牌无距离限制,所以我们可以直接攻击游戏上存活的任何一头猪。
主猪杀的是逆时针下去的第一头跳反猪或类反猪,忠猪杀的是逆时针下去的第一头跳反猪,反猪直接杀主猪。
int pig::find_F(void)
{//找一个人来决斗
if(this->name=="MP")
{//我是主猪
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针寻找
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果死掉了,跳过去
if(member[point].like_bad==THIS_PIG_LIKE_BAD||(member[point].name=="FP"&&member[point].jumped==THIS_PIG_IS_JUMP))
{//如果这猪是类反猪或是已经跳反
return point;//就决斗他
}//否则继续找
}
}
else if(this->name=="ZP")
{//我是忠猪
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针寻找
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//这玩意死了,跳过去
if(member[point].name=="FP"&&member[point].jumped==THIS_PIG_IS_JUMP)
{//如果这是已经跳反的猪头
return point;//就决斗他
}
}
}
else if(this->name=="FP")
{//我是反猪
return 1;//杀主猪
}
return 0;
}
攻击的反应函数
任何被使用杀、南猪入侵、万箭齐发的猪都会在背后运行这些函数。
杀的反应函数寻找手牌中是否有闪,南猪入侵的反应函数寻找手牌中是否有杀,万箭齐发的反应函数寻找手牌中是否有杀。
bool pig::K_respond(void)
{//杀的反应
myit it=this->find('D');//找一找有没有闪
if(it==this->cards.end()) return false;//没有闪,反应无效
this->del(it);//删掉闪
return true;//有效反应
}
bool pig::N_respond(void)
{//回应南猪入侵
myit it=this->find('K');//找一找杀
if(it==this->cards.end()) return false;//找不到
this->del(it);//删掉它
return true;
}
bool pig::W_respond(void)
{//回应万箭齐发
myit it=this->find('D');//找闪
if(it==this->cards.end()) return false;//没找到
this->del(it);//用掉它
return true;
}
接下来重点中的重点了
无懈可击
这里我们写两个递归函数,先看我们第一个函数,这个函数是被攻击性锦囊攻击的人调用的函数,形参表明谁伤害的自己,寻找逆时针(注意从自己开始,不是从自己后面的那一位开始,因为自己如果跳了也是会保护自己的)找到的第一头有无懈可击的同类,让它来帮你出无懈可击。
如果这头猪还没有跳是不可以寻求帮助的
bool pig::ask_J(int who)//找人给我挡牌,who对我发起攻击
{//询问无懈可击
bool check=true;
if(this->jumped==THIS_PIG_IS_NOT_JUMP) return false;//如果没有跳,那就不用
for(register int point=who;point!=who||check==true;point=point%n+1)
{//逆时针查询
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果死掉了
check=false;
if(this->name!="FP"&&member[point].name=="FP") continue;//如果有敌意,不用
if(this->name=="FP"&&member[point].name!="FP") continue;//如果要打上,不用
myit it=member[point].find('J');//找找无懈可击
if(it!=member[point].cards.end())
{
member[point].del(it);//删掉这张牌
member[point].jump();//跳
return !member[point].back_J(this->num);//对抗一下
}
}
return false;
}
考虑到上方的献殷勤可能会被表敌意给无效掉,所以才有了下面这个函数。调用过上面的函数的猪都要让帮他出牌的那头猪继续做下面的工作
内容:找到逆时针的第一头有无懈可击的敌猪,让它对自己的无懈可击出无懈可击。
其实你测过几个例子就会发现,每当主猪受到伤害后,忠猪和反猪两阵营一定有一方一张无懈可击都没有了,这是为什么?
真的是Pig思维啊
bool pig::back_J(int to)
{//无懈可击的对抗
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针扫描
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//已逝者先行
if(this->name!="FP"&&member[point].name=="ZP") continue;//如果同类,不打
if(this->name=="FP"&&member[point].name=="FP") continue;//如果同类,不打
if(this->name=="ZP"&&member[point].name!="FP") continue;//如果友好,不打
myit it=member[point].find('J');//找一张无懈可击
if(it!=member[point].cards.end())
{
member[point].del(it);//用它
member[point].jump();//同时自己得跳
return !member[to].ask_J(point);//再回击一下(很骚啊)
}
}
return false;
}
补一个构造函数
pig::pig(void)
{
this->hp=4;//生命值
this->dead=THIS_PIG_IS_NOT_DEAD;//没死
this->jumped=THIS_PIG_IS_NOT_JUMP;//有没有跳
this->crossbow=CROSSBOW_DOWN;//有没有装弩
this->like_bad=THIS_PIG_DO_NOT_LIKE_BAD;//是否类反
this->use_kill=THIS_TURN_DO_NOT_USE_KILL;//这回合没有使用过杀
}
再补一个回合函数
基于本人在最上面
c
l
a
s
s
class里面写的独特的从
p
r
i
v
a
t
e
private到
p
u
b
l
i
c
public的顺序,加上本人的强迫症(必须按照声明的顺序实现函数),所以下方的函数都比较水 *
这个回合函数很模拟,按照题意,摸两张牌,从左到右扫一遍手牌,对每张牌来个判断,能通过判断的牌用掉。
注意用掉一张牌后需要从头开始再扫一遍手牌,这是为什么?。
如果手牌是KKKKZKK,那么你就会知道为什么要这么做了。
void pig::myturn(void)
{
this->cut();//拿牌x1
this->cut();//拿牌x2
bool check=true;//有牌拿
while(check)
{//只要不结束
check=false;//有没有用牌
for(myit it=this->cards.begin();it!=this->cards.end();it++)
{//在手牌中从左到右扫一遍
if(it->use==CANNOT_USE_THIS_CARD) continue;//如果不能用了
if(canuse(it->which)==false) continue;//判断一下现在能不能用
use(*it);//用它
if(this->dead==THIS_PIG_IS_DEAD) return;//如果这玩意死了
it=this->cards.begin()-1;//从头开始重新扫
check=true;//用过牌了
}
}
member[this->num].use_kill=THIS_TURN_DO_NOT_USE_KILL;//重置是否用过杀
return;
}
跳
考虑到忠猪只会跳忠,反猪只会跳反,所以我们只要知道这头猪的身份以及他有没有跳就可以得知它是跳忠还是跳反,所以只需要对每头猪都开一个
bool标识表示这头猪有没有跳。
void pig::jump(void)
{//跳
this->jumped=THIS_PIG_IS_JUMP;//开启标识
return ;
}
杀
嗯,接下来的一些操作也比较重要 ,反正没有无懈可击难就对了
考虑到杀必然会使出杀者跳。因为杀主猪的猪一定是反猪,杀忠猪的猪也是跳反猪,杀反猪的猪一定是忠猪或是主猪。所以只要你用了杀,你就跳了。
其次,跳了之后你就可以取消类反标识了(即使你跳反了,取消了标识依旧被打)。
然后,就没有什么花头了,杀的人在前面函数中已经选定,照着打就可以了。
void pig::K(int to)
{//杀
if(this->use_kill==THIS_TURN_USED_KILL&&this->crossbow==false) return;//如果这回合使用过杀而没有使用猪哥连弩
this->jump();//必然跳
this->like_bad=THIS_PIG_DO_NOT_LIKE_BAD;//取消类反(即使成为跳反猪)
this->use_kill=THIS_TURN_USED_KILL;//用过杀了
if(member[to].K_respond()==false)
{//如果对方没有对杀产生有效反应
member[to].hurt(this->num);//伤它一滴血
}
}
决斗
与杀相似,只要你用了决斗你就必然跳,注意如果忠猪被主猪使用无懈可击,它会直接扛上一点伤害,然后结束决斗。
此外还需注意记录到底是谁出的最后一张杀,决定了谁受到另一方的伤害。
void pig::F(int to)
{//决斗
#ifdef DEBUG
printf("%d对%d使用了决斗\n",this->num,to);
#endif
this->jump();//先跳再说
this->like_bad=THIS_PIG_DO_NOT_LIKE_BAD;//类反猪取消
if(member[to].ask_J(this->num)==true) return;//询问无懈可击,如果使用,跳过
if(this->name=="MP"&&member[to].name=="ZP")
{//如果主猪对忠猪用
member[to].hurt(this->num);//忠猪受伤
return;
}
int who;//判定谁提供伤害
while(true)
{
myit t;
t=member[to].find('K');//找杀
if(t==member[to].cards.end())//没找到
{
who=2;//我对你产生伤害
break;
}
else member[to].del(t); //用掉这张牌
t=this->find('K');//从我这里找杀
if(t==this->cards.end())
{
who=1;//你对我产生伤害
break;
}
else this->del(t);//删掉这张牌
}
if(who==2)
{//你伤了我
member[to].hurt(this->num);//伤害函数
}
if(who==1)
{//我伤了你
this->hurt(to);//伤害函数
}
}
南猪入侵
然后就是南猪入侵了。从出牌者的下一位开始,逆时针旋转一周,对每个人进行无懈可击判定和南猪入侵回应判定。
void pig::N(void)
{//南猪入侵
#ifdef DEBUG
printf("%d使用了南猪入侵\n",this->num);
#endif
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针查询
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//死掉了,跳过去
if(member[point].ask_J(this->num)==true) continue;//看看有没有无懈可击
if(member[point].N_respond()==false)
{//判断是否反应
member[point].hurt(this->num);//受到伤害
if(member[point].name=="MP"&&this->jumped==THIS_PIG_IS_NOT_JUMP)
{//如果主猪受到了攻击且该猪没有跳
this->like_bad=THIS_PIG_LIKE_BAD;//定义为类反猪
}
}
}
}
万箭齐发
跟南猪入侵大体相同,就是改了几个字。
void pig::W(void)
{//万箭齐发
#ifdef DEBUG
printf("%d使用了万箭齐发\n",this->num);
#endif
for(register int point=this->num%n+1;point!=this->num;point=point%n+1)
{//逆时针搜索
if(member[point].dead==THIS_PIG_IS_DEAD) continue;//如果死了,跳过
if(member[point].ask_J(this->num)==true) continue;//询问是否有无懈可击
if(member[point].W_respond()==false)//回应万箭齐发
{
member[point].hurt(this->num);//受到伤害
if(member[point].name=="MP"&&this->jumped==THIS_PIG_IS_NOT_JUMP)
{//主猪受到攻击,这头猪没有跳
this->like_bad=THIS_PIG_LIKE_BAD;//定义为类反猪
}
}
}
}