程设大作业之魔兽世界
【前言】:在学习了一些看上去只是能让程序更好看更清晰的类的知识之后...为了让我们真的觉得类是个好东西,丧心病狂的作业——魔兽出现了...然而...emmmm...
好吧,这篇博客主要就是记录一下自己在打这些题的一些思路和想法...由于这个题里面其实没有什么算法和数据结构的运用,感觉这篇博客成了一个教大家学习怎么样完成作业的blog hhh不过这样也挺好。虽然这么说啦,我会把我觉得我的程序里自己觉得很漂亮的部分重点讲一下,也可以分享一些面对这样一个比较复杂而冗长的代码怎样更好的提高调试效率的一些小方法。
因为直接让大家做魔兽·终极版会直接让大家放弃,所以老师很机智地设置了几个循序渐进的版本
【魔兽世界一:备战】
笔者认为呢,这个题的目的主要在于:1.营造场景 2.让我们体会一下面向对象和面向过程之间的关系 3.emmm增加一下大家的信心,方便大家入坑(雾)
有N个城市,两个指挥部,红方和蓝方。两个指挥部都会制造武士,双方会各自有一个制造武士的顺序,并且每小时制造一个武士,同时制造武士需要代价,基地会有一个初始的值来消耗,然后如果当前顺序能制造就制造,不能制造了就用顺序的下一个,直到所有的武士都不能制造,再宣称停止。
然后魔兽一出现了:它需要让你按时间输出这些制造的武士。
emm你看我上面的措辞:“输出这些制造的武士”hhh并不是“制造这些武士并输出...”
为什么会想到不去真的制造武士呢,首先是...懒hhh能少一个类就少一个,其次因为在这个题中的武士是没有任何行为的。输出行为可以放在司令部中输出
所以这题中我只设置了一个司令部的类。然后我们来思索一下这个类里面需要什么呢?一般可以先想一想它进行的行为:
【行为】1制造一个武士 2停止制造武士
那么在制造武士的时候需要知道1.当前司令部的生命元 2.当前要制造的武士是谁 3.当前武士的编号 4.现在的时间 5.这个武士需要多少的生命元 6这个武士有多少了
这里我们会发现:4和5 其实在意义上应该设置成全局变量的,因为比如时间应该是每一个函数都可以调用到的,而每个武士需要用的生命元也是一个客观的给出的条件,也应该是允许所有人调用的。
那么我们来考虑1236,其中又会发现3也不用考虑啦,因为编号就是时间+1...然后1我们可以在基地里设置一个整型的HP。而2的话会发现和顺序和上一次用的是谁有关系,所以需要一个记录顺序的数组Order[],还有一个Index来记录上一个是谁,对于6的话呢,我们可以用一个桶Count[],每次制造了一个武士who之后就能使用:Count[who]++;
嗯嗯,这样我们就解决了这个类的成员了!然后就只有一个问题啦,怎样通过Order[]数组和Index来得知下一个是谁呢?想来有很多方法啦...hhh我的代码就是尽量短一点,然后好懂啦hhh[这也是写代码很好的习惯啦,hhh比如who,Index这种变量]
1 Index++; 2 int who=Order[Index%5],tmp=0; 3 while(HP<Cost[who] && tmp<5) 4 who=Order[++Index%5],tmp++;
然后就没有难的地方啦!做完之后可以沾沾自喜了(诶,这貌似是一个贬义词Hhh...不管啦) (果然是诱惑入坑的好题...)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 5 using namespace std; 6 7 int Time_Index; 8 int Cost[6]; 9 int ord1[5]={3,4,5,2,1},ord2[5]={4,1,2,3,5}; 10 char Warrior_Name[6][7]={"\0","dragon","ninja","iceman","lion","wolf"}; 11 12 class Headquarter{ 13 public: 14 int Index; 15 int HP; 16 int Count[6]; 17 int Order[5]; 18 char Name[5]; 19 bool STOP; 20 Headquarter(int HP_,char *s,int *ord){ 21 Time_Index=-1; 22 Index=-1; 23 HP=HP_; 24 for(int i=1;i<=5;i++) Count[i]=0; 25 for(int i=0;i<5;i++) Order[i]=ord[i]; 26 memset(Name,0,sizeof(Name)); 27 strcpy(Name,s); 28 STOP=false; 29 } 30 void Build(){ 31 Time_Index++; 32 Index++; 33 if(STOP) return; 34 int who=Order[Index%5],tmp=0; 35 while(HP<Cost[who] && tmp<5) 36 who=Order[++Index%5],tmp++; 37 if(HP>=Cost[who]){ 38 HP-=Cost[who]; 39 Count[who]++; 40 printf("%03d %s %s %d born with strength %d,%d %s in %s headquarter\n",Time_Index,Name,Warrior_Name[who],Time_Index+1,Cost[who],Count[who],Warrior_Name[who],Name); 41 } 42 else{ 43 printf("%03d %s headquarter stops making warriors\n",Time_Index,Name); 44 STOP=true; 45 } 46 } 47 }; 48 49 int main(){ 50 #ifndef ONLINE_JUDGE 51 freopen("x.in","r",stdin); 52 freopen("x.out","w",stdout); 53 #endif 54 int Kase,W; 55 char s1[4]="red",s2[5]="blue"; 56 scanf("%d",&Kase); 57 for(int T=1;T<=Kase;T++){ 58 printf("Case:%d\n",T); 59 scanf("%d",&W); 60 for(int i=1;i<=5;i++) 61 scanf("%d",&Cost[i]); 62 Headquarter r(W,s1,ord1),b(W,s2,ord2); 63 while(!r.STOP || !b.STOP){ 64 r.Build(); 65 b.Build(); 66 } 67 } 68 return 0; 69 }
【魔兽世界二:装备】
魔兽二是魔兽一的一个加强版,这个里面呢,我们还是要制造和输出武士的信息,但是这个题目中最重要的就是出现了装备!
哇,简直是一个新世界啦...因为这样的话,我们就有新的对象需要出现了!
在之前的魔兽世界一,武士这个类其实是没有什么用的,他们只要提供名字和编号就可以了。但是在魔兽世界二里如果我们接着这样做的话,就会有些麻烦咯,因为需要给武士们发武器了,那么这样就一下诞生了两个对象:武器和武士...
[当然啦这个题里其实武器也是没有任何作用的,更容易的打法可以删掉武器这个类哦...不过笔者感觉要开始为魔兽三做点准备了就写了这个类]
我们注意到在魔兽一里武士们是没有任何区别的,只有HP值和Name的不同。
但是在这一题中:我们发现这些武士们开始偷偷分化了!然后就会有很多细节啦,有时候半句话就是一个小细节。
[这种题最容易错的就是细节,笔者有一个好方法:就是你可以先大概建好整个程序,然后一行一行地去看题目的描述,每看一句就去噼里啪啦敲好QwQ,这样就不会漏掉细节了哦...emmm当然!也可以画图或者画表来让题目中的信息更加的清晰]
嗯嗯这个题中因为各种武士的不一样,所以就可以开始感受虚函数的好处啦。
虚函数和多态在笔者看来是一个很实用的东西,它其实是类似 If 的一个函数,同样的一句话,if 这个函数的主体是x就执行 x 里写的函数,if 是 y 来做就执行 y 里写的函数。但是这个却不是在编译中写好的 if 而是执行的时候才去执行,这样就让代码看上去逻辑感很好,而且也很简洁啦!
然后笔者觉得逻辑这种东西,在写一个比较长比较复杂的程序的时候是非常重要的,在保证一定的可读性(这个需要变量名取得好哦...多用'_'或者首字母大写来给变量名命名会很好)的情况下,如果你的程序逻辑清楚的话,是很容易找到错误的。
所以我在用virtual的时候一般是先去思考一个一般武士的行动,然后思考是不是有特例的武士不会这么做,那么就设置成virtual,比如看着题目的输出样例,你就可以很清楚的知道有:出生->得到武器->输出自己情况 这样三个普遍的行动,但是每一个部分都是有特例的,比如出生的时候有人会获得士气和忠诚这样的属性。得到武器的时候有人会得到两把,输出自己时有的人没有武器要输出....[p.s.]不过出生的时候不一样,写在构造函数里不一样就好啦,这个就不能virtual了
然后这个题也就没有困难啦...
【魔兽二:代码】
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 6 using namespace std; 7 8 int Cost[5],Time_Index; 9 int ord1[5]={2,3,4,1,0},ord2[5]={3,0,1,2,4}; 10 char Weapon_Name[3][6]={"sword","bomb","arrow"}; 11 char Warrior_Name[5][7]={"dragon","ninja","iceman","lion","wolf"}; 12 char Headquarter_Name[2][5]={"red","blue"}; 13 14 class Weapon{ 15 char Name[6]; 16 public: 17 Weapon(int which){ 18 strcpy(Name,Weapon_Name[which]); 19 } 20 void print(){printf("It has a %s",Name);} 21 void Name_print(){printf("%s",Name);} 22 }; 23 24 class Warrior{ 25 protected: 26 int HP; 27 char Name[7]; 28 Weapon *w1,*w2; 29 public: 30 Warrior(int HP_,char *s){ 31 memset(Name,0,sizeof(Name)); 32 HP=HP_; 33 strcpy(Name,s); 34 w1=w2=NULL; 35 } 36 virtual void get_weapon(){ 37 w1=new Weapon((Time_Index+1)%3); 38 } 39 void Name_print(){ 40 printf("%s",Name); 41 } 42 virtual void Weapon_print(){ 43 w1->print(); 44 printf("\n"); 45 } 46 ~Warrior(){if(w1) delete w1;if(w2) delete w2;} 47 }; 48 //0 49 class Dragon:public Warrior{ 50 double morale; 51 public: 52 Dragon(int HP,double m_):Warrior(HP,Warrior_Name[0]){morale=m_;} 53 virtual void Weapon_print(){ 54 w1->print(); 55 printf(",and it's morale is %.2lf\n",morale); 56 } 57 }; 58 //1 59 class Ninja:public Warrior{ 60 public: 61 Ninja(int HP):Warrior(HP,Warrior_Name[1]){} 62 virtual void get_weapon(){ 63 w1=new Weapon((Time_Index+1)%3); 64 w2=new Weapon((Time_Index+2)%3); 65 } 66 virtual void Weapon_print(){ 67 w1->print(); 68 printf(" and a "); 69 w2->Name_print(); 70 putchar('\n'); 71 } 72 }; 73 //2 74 class Iceman:public Warrior{ 75 public: 76 Iceman(int HP):Warrior(HP,Warrior_Name[2]){} 77 }; 78 //3 79 class Lion:public Warrior{ 80 int loyalty; 81 public: 82 Lion(int HP,int l_):Warrior(HP,Warrior_Name[3]){loyalty=l_;} 83 virtual void get_weapon(){} 84 virtual void Weapon_print(){ 85 printf("It's loyalty is %d\n",loyalty); 86 } 87 }; 88 //4 89 class Wolf:public Warrior{ 90 public: 91 Wolf(int HP):Warrior(HP,Warrior_Name[4]){} 92 virtual void get_weapon(){}; 93 virtual void Weapon_print(){}; 94 }; 95 96 class Headquarter{ 97 private: 98 char Name[5]; 99 int HP; 100 int Order[5]; 101 int Count[5]; 102 int Warrior_Index; 103 Warrior *cur; 104 bool STOP; 105 public: 106 Headquarter(char *s,int HP_,int* O_){ 107 memset(Name,0,sizeof(Name)); 108 memset(Count,0,sizeof(Count)); 109 strcpy(Name,s); 110 HP=HP_; 111 for(int i=0;i<5;i++) 112 Order[i]=O_[i]; 113 Warrior_Index=-1; 114 cur=NULL; 115 STOP=0; 116 } 117 void Change_HP(int HP_){ 118 HP=HP_; 119 memset(Count,0,sizeof(Count)); 120 Warrior_Index=-1; 121 cur=NULL; 122 STOP=0; 123 }; 124 void Build_Warrior(){ 125 if(STOP) return; 126 Warrior_Index=(Warrior_Index+1)%5; 127 int who=Order[Warrior_Index]; 128 int temp=0; 129 while(Cost[who]>HP && temp<5){ 130 Warrior_Index=(Warrior_Index+1)%5; 131 who=Order[Warrior_Index]; 132 temp++; 133 } 134 if(HP>=Cost[who]){ 135 Count[who]++; 136 HP-=Cost[who]; 137 switch(who){ 138 case 0: cur=new Dragon(Cost[0],(double)HP/Cost[0]);break; 139 case 1: cur=new Ninja(Cost[1]);break; 140 case 2: cur=new Iceman(Cost[2]);break; 141 case 3: cur=new Lion(Cost[3],HP);break; 142 case 4: cur=new Wolf(Cost[4]);break; 143 }; 144 cur->get_weapon(); 145 printf("%03d %s ",Time_Index,Name); 146 cur->Name_print(); 147 printf(" %d born with strength %d,%d ",Time_Index+1,Cost[who],Count[who]); 148 cur->Name_print(); 149 printf(" in %s headquarter\n",Name); 150 cur->Weapon_print(); 151 delete cur; 152 cur=NULL; 153 } 154 else{ 155 printf("%03d %s headquarter stops making warriors\n",Time_Index,Name); 156 STOP=true; 157 } 158 } 159 bool Stop(){return STOP;} 160 }; 161 162 Headquarter r(Headquarter_Name[0],0,ord1),b(Headquarter_Name[1],0,ord2); 163 164 int main(){ 165 #ifndef ONLINE_JUDGE 166 freopen("x.in","r",stdin); 167 freopen("x.out","w",stdout); 168 #endif 169 int Kase,W; 170 scanf("%d",&Kase); 171 for(int T=1;T<=Kase;T++){ 172 printf("Case:%d\n",T); 173 scanf("%d",&W); 174 Time_Index=0; 175 r.Change_HP(W); 176 b.Change_HP(W); 177 for(int i=0;i<5;i++) 178 scanf("%d",&Cost[i]); 179 while(!r.Stop() || !b.Stop()){ 180 r.Build_Warrior(); 181 b.Build_Warrior(); 182 Time_Index++; 183 } 184 } 185 return 0; 186 }
【魔兽世界三:开战】
这个版本里面的魔兽世界呢...
很重要的两个任务就是让你的武士们可以移动起来,然后两个武士之间会发生互动(打架、抢武器什么的)
下面就详细讲一下啦...
先讲一讲关于武士的移动!
【武士的移动】
这里的移动涉及到的主要是武士和城市这两个单位。
而这里不同于之前两道魔兽的地方是:我不仅要输出每一个移动,并且在我程序构建出来的这个世界中,武士是真的在移动的。
那么我们还是先考虑这个部分的输出:
因为城市是分普通城市和指挥部的,所以对应的输出也是有两种
先看看普通城市的输出:(这些都是从提供数据中的out文件里提出来的哦...)
000:10 red iceman 1 marched to city 1 with 117 elements and force 50
再看到达指挥部的输出:
012:10 red wolf 3 reached blue headquarter with 5 elements and force 150 012:10 blue headquarter was taken
注意到两种的区别!首先蓝色指挥部的名字"blue headquarter"取代了"city %d",其次,在魔兽世界三中,只要有武士到达对方指挥部,对方的指挥部就会被占领,游戏也就结束,此时就需要我们输出一行"blue headquarter was taken"
【Tips1】这里有一个很容易错的地方,那就是当前Case结束输出的时机!如果最后游戏是平局,那么应该是在时间结束的时候也结束输出的。但是如果中途发生了指挥部被占领的情况就应该结束啦,也就是这个时刻之后的都不用输出啦...!而在这个地方还有一个要注意:虽然在司令部被占领之后游戏结束,但是这个时刻的输出还是要输出完的,比如说红司令部被占领了,但是这个时刻我才刚刚枚举了移动到第0个城市的武士,可是还有很多武士也在这个时刻移动,应该把他们都输出完了才能结束这个Case的输出!
【Tips2】这里提到一个打程序的小技巧:为了让我们的代码不会出错,我的建议是在要输出的地方,先写一个print("");然后把题目中提供的要输出的信息填进去。然后,再把题目中提到的特殊的信息给挖空拿掉,再在printf的后面想办法把这些用一些方法给表示出来。这样的话错误率会降到很低很低哦...
比如说对于一个普通城市的输出,我的代码呢就长这样了...
printf("%03d:10 %s %s %d marched to city %d with %d elements and force %d\n",Time_Index,Headquarter_Name[Direction<0],Name,Born_Number,City_Index,HP,MP);
hhh不过我一直以为大家都是这么打的...tips强行凑字数hhh
然后我们再来考虑一下有多个武士,最后输出这些移动信息的顺序:
大的方向呢是从0到n+1的城市方向,并且在同一座城市会先输出红色武士的信息,再输出蓝色武士的信息。【Attention】注意啦!这里的城市是指双方武士移动后所在的城市!而我们通常会把移动武士和输出武士一块写,那么这个时候请注意啦...对于第 i 城市的输出就要移动的是 i-1 城市的红方武士和 i+1 城市的蓝方武士,具体如下(笔者de出来的第一个bug就是这里写错了QwQ)...
下面是笔者的代码:嗯里面的City是一个Warrior类的指针的二维数组,第一维的下标是这个城市的编号,而第二维的[0]或[1]分别存的是red和blue方的武士指针。[emmm你们可能觉得笔者你....你居然不把City写成类!好吧...我就是懒hhh因为这题中的City好像没有什么用]
for(int i=0;i<=City_Amount+1;i++){
if(i>0)
if(City[i-1][0]!=NULL) City[i-1][0]->Process(); if(i<=City_Amount) if(City[i+1][1]!=NULL) City[i+1][1]->Process();
}
看完上面这个代码,很多人就放松了警惕(包括当初的笔者我自己...)感觉很满意呀...代码思路这么清楚了,可以安心写process去了。这时一个帅气的男生停在了你的面前:STOP!(咳咳,就是笔者我...hhhh)这样打会有一个问题,那就是当我移动我的红色方的武士的时候,他肯定会到下一个城市 (因为这里还没有开始打架...) 那么,当我枚举下一个城市里的红色战士的时候,发现:诶,你不是从上个城市过来的么?我原来那个武士呢?算了算了就移动你把...然后不知不觉一轮枚举直接把我红方的武士送进了对面的指挥部...
唔,那怎么解决呢?笔者的办法是建立一个新城市,让移动后的武士先住在新城市里,等旧城市的所有武士都走完了,再让新城市里的人回来...具体过程呢可以见最后的代码啦...。
好了,现在终于安心了,可以开始写我们的Process函数了。
首先我们注意到题目中对移动这个操作有三种人:Iceman(边走边掉血),Lion(越走越胆小),Others。
那么这个Process肯定就是一个虚函数了,那我们分别看一下这三种人。先是Others。
【Others】 我走路走的好好的!
不过好好的走需要什么呢?
需要的信息1.我当前的位置 2.我往哪边走,那么这两个信息可以变成两个值在初始化的时候搞定!
1.int City_Index 表示自己的位置 red初始值是0 ,blue 是 N+1
2.int Direction 表示自己的方向 red用1 blue用-1
这样设计的话 City_Index+=Direction; 就让这个武士走完了诶!
不行不行,你还得照顾一下那些在City里找武士的人呀...所以我们还得修改外层指向这个武士的指针:
City[City_Index][Direction<0]=NULL; City_Index+=Direction; New_City[City_Index][Direction<0]=this;
这里很多小伙伴就不知道Direction<0是什么了...hhh忽然感觉自己是科普小天使....
比如< > ==这种符号都是双目的运算符,它们也是有返回值的,是一个bool类型!如果正确呢那就是1,不正确呢那就是0
所以如果我把red定为0,blue定为1的话Direction<0就可以知道这个武士是哪一家的了!
然后上面代码的New_City就是我所设想的新城市啦...
好了,现在我们移动了武士自己,还改了城市里的指针,是不是结束啦!嗯嗯,别忘了加上之前的输出哦
1 virtual void Process(){ 2 City[City_Index][Direction<0]=NULL; 3 City_Index+=Direction; 4 New_City[City_Index][Direction<0]=this; 5 if(City_Index==0){ 6 Red_Lost=true; 7 printf("%03d:10 blue %s %d reached red headquarter with %d elements and force %d\n",Time_Index,Name,Born_Number,HP,MP); 8 printf("%03d:10 red headquarter was taken\n",Time_Index); 9 } 10 else if(City_Index==City_Amount+1){ 11 Blue_Lost=true; 12 printf("%03d:10 red %s %d reached blue headquarter with %d elements and force %d\n",Time_Index,Name,Born_Number,HP,MP); 13 printf("%03d:10 blue headquarter was taken\n",Time_Index); 14 } 15 else 16 printf("%03d:10 %s %s %d marched to city %d with %d elements and force %d\n",Time_Index,Headquarter_Name[Direction<0],Name,Born_Number,City_Index,HP,MP); 17 }
不要看代码这么长,其实主要是输出有点长啦....QwQ,最重要的只有三行而已啦...
好了,下面讨论一下Iceman
【Iceman】我前进就要掉血!但我还是要前进!
就记得移动的时候HP-=HP/10哦...注意啦这个掉血是当前血量的10%,所以不用担心Iceman行军路上失血过多而死hhh
然后是我们的Lion
【Lion】我不想走啦!再逼我我就逃跑!
嗯嗯记得就是把Loyalty-=K就好啦,然后要补一个判断,看看Loyalty是不是<0啦...
嗯嗯这个里面的话呢我的处理就是逃兵一律杀掉emmm直接让这个逃跑的被上层delete掉就好啦
【武士的战斗】
武士战斗最重要的两个信息:武士、武器
而这两个就分别是程序中最重要的两个类。
这一题中的武器数目从最多2很快提升到了10,同时武器的属于关系也不再固定,而是可以在不同的武士之间传递。而对于这样的关系,我认为指针是最好的选择了。因为武器的对象在传递的时候其实是不会消失的,所以当一个武器从A到B手上时,只用把B中一个空的指针变成A的指针,然后A中指向这个武器的指针定为空就完成了一次武器的交接。(不过笔者还打听到一种比较巧妙的方法哦:这个题里可以不要武器类的,因为武器这个对象其实没有具体的什么值,它的攻击是主人根据主人来的,它唯一具有自己的属性就是耐久度了,而这个我们用一个数组来当成武器库存耐久度也不是很麻烦,而且这样的话其实传递武器排序武器都是很简单的了...不过笔者还是讲自己的类的做法吧)
1 class Warrior{ 2 int Weapon_Count;//Weapon_Count表示武器的数量 3 Weapon* w[10]; 4 };
上面解决了武士和武器之间的关系,我们在这个基础上再来看看打斗中的各种操作应该如何实现
还是根据故事的逻辑来看,把战斗按照时间顺序分为战前、战中、和战后三个阶段:(p.s.当然战斗发生的条件是这个城市里有两个武士哈)
【战前】
战前有一件对战斗很重要的事情:那就是wolf会抢夺武器!
逻辑上:这是一件很独特的事情,可以用virtual Before_Fight()来处理,也可以if (Is_Wolf()) Rob_Weapon(Enermy)来处理
操作上:在抢夺之前:要知道抢夺发生的条件:
1,对方不是wolf,这样需要一个判断函数:virtual bool Is_Wolf();然后只有wolf类返回true其他都返回false就可以了
2.自己的武器数量<10 3.对方的武器数量>0,知道自己可以抢夺之后,还不能马上抢,这中间还有一个很重要的操作:你要抢编号最小的那一类武器,而且如果抢不完要优先抢使用次数少的。这样的话我们就要给敌人的武器按照刚才的方法排序,笔者的习惯是用系统函数sort()搭配自己写的cmp来排序,可以把自己的cmp函数发上来供大家参考:
bool cmp(const Weapon *A,const Weapon *B){//这里我们是想给Warrior中的Weapon *w[]数组排序,所以应该比较两个Weapon*的大小
if(A==NULL) return false;//我们要让NULL的元素都往后放,而sort中return true;就会把A放在前面,所以这里应该返回false
if(B==NULL) return true;
if(A->Index!=B->Index) return A->Index<B->Index;//Index是武器的种类的编号(Sword-0,Bomb-1,Arrow-2),编号小的在前面,可以看到这里还是很好的运用了<的返回值来使得代码精简(如果A的编号<B那么就会return true让A在前面)
return A->Can_use>B->Can_use;//Can_use是武器的耐久度,初始值是2,bomb每使用一次会-2,Arrow使用一次会-1,耐久度大的放前面,所以是>号
}
[ 在上面的cmp函数中,我顺带介绍了自己关于Can_use的这个设计...嗯嗯感觉我这样挺漂亮的算,一个好设计hhh,然后同时也科普了一下cmp函数的写法:return true会让A放在B的前面,然后运用<和>号可以让自己的代码很精简。]
然后就是抢夺的过程了,这个就是武器从属权的一个传递,在上面的指针部分也有介绍。如果还有不懂可以看下面的抢夺代码
1 virtual void Before_fight(Warrior *Enermy){ 2 if(Enermy->Is_Wolf()) return; 3 if(Weapon_Count<10 && Enermy->Weapon_Count>0){ 4 sort(w,w+10,cmp); 5 sort(Enermy->w,Enermy->w+10,cmp); 6 int Min_Index=Enermy->w[0]->Index,Amount=0; 7 for(int i=0;Enermy->w[i]!=NULL && Enermy->w[i]->Index==Min_Index && Weapon_Count<10 && i<Enermy->Weapon_Count+Amount;i++){ 8 w[Weapon_Count++]=Enermy->w[i],Amount++; 9 Enermy->Weapon_Count--; 10 Enermy->w[i]=NULL; 11 } 12 printf("%03d:35 %s wolf %d took %d %s from %s %s %d in city %d\n",Time_Index,Headquarter_Name[Direction<0],Born_Number,Amount,Weapon_Name[Min_Index],Headquarter_Name[Direction>0],Enermy->Name,Enermy->Born_Number,City_Index); 13 } 14 }
【战中】
战斗的过程是两个人互相使用武器的过程。
首先,还是需要先给两个人的武器排好序来确定使用的顺序,然后就类似魔兽世界一二中生产武士的方法来循环这个武器的顺序:
1 int temp=0; 2 while(w[Weapon_Index=(Weapon_Index+1)%10]==NULL && temp<10) temp++;
上面的代码就是帮助我找到下一个可以用的武器(够精简吧hhh),然后就是使用武器的过程了,这里武器的使用,只和使用者、被使用者、武器类型有关,而与具体的武器对象唯一的关联就是耐久度这个属性(所以才会诞生出不设置武器类的做法),那么我们在使用武器的时候就需要把这些因素都考虑进去,同时不同类型的武器是不一样的,所以我们需要用到虚函数来处理。在这个过程中是可能导致武士死亡的,但是我们现在不能急着把武士给delete掉,因为整个武士的最顶级指针是最上层的City数组,而且等一会还有敌人来收缴武器,所以我们可以用一个bool Die来表示一个武士是否死亡,然后延迟delete的时间,在打斗的时候不delete,等战斗发生完了以后回到上层再去删除。
然后用完了武器就会面临武器可能失效的问题,这个时候要删除这个武器,因为武器的最上层控制者就是武士,所以这个delete的操作也应该是放在武士的函数里执行的。同时记得w[i]=NULL和Weapon_Count--的操作。
战中还有一个很重要的过程就是宣布战中结束,进入战后。有两种情况是很容易判断的:1. 有一方死亡时 2. 双方都没有兵器时
还有一种情况是比较复杂的,那就是双方的攻击力<5(这时使用Sword将会造成0伤害),然后互相打到地老天荒,这个时候怎么办呢?
室友想到一种很偷懒的方法,就是执行完1000轮还不结束,我就宣告平局...emmm显然可以造数据卡掉你嘛,,不过最后数据确实没有卡(因为现在还没有看到任何一个TLE的同学...)hhh怀疑那些运行比我(2ms)慢的(24ms)可能就是用了这种方法?
我的办法会比较符合逻辑一点,就是我会有一个倒计时Time_Tick,它的初始值是两人的武器数量中大的一个的两倍,那么当倒计时执行完之后,即使是武器数量大的那个人也只可能有sword这件武器了,就进入了白热化阶段,然后你再判断一下双方的攻击力,或者判断一下场面是不是还有变化,这样就可以结束了。
【战后】
战后首先会有一个叫Dragon的兄弟,如果没死他就会yell一下,记得输出。
然后战后主要就是死亡之后的收缴兵器。因为兵器的最高控制者就是武士,所以写在武士的函数里(这里提到了很多次最高控制者,因为笔者程序中的指针往往表示的是控制关系,所以每当我想删除一个东西的时候,就需要去考虑谁的指针还是指着它的,那么我会让这个删除操作发生在哪个指挥者的函数中)
而收缴兵器的过程和wolf抢兵器的过程是几乎一样的,只是不只可以抢一类武器了,去掉那个终止条件就可以了。
那么战斗也就分析完了...
最后来看看怎么让你的程序可以AC吧:
【关于如何从CE->WA】
啊,这个过程可以说是很痛苦了...不过相信你们可以的hhh
【最后:关于怎样调试代码(WA/RE/TLE->AC)】
首先呢,笔者自己对这道题大概debug了两天左右...其实感觉自己debug已经很有方法了,只是前面写代码的时候好像太粗糙了
[其中的情绪变化大约是:[冷静] 这里有错诶?-> [急躁] 怎么还有错?!-> [抓狂] 还有哪有错呀?-> [目光呆滞] 没错了....吧...]
所以,大家自己敲第一遍代码的时候一定要谨慎!小心!要跟着自己的光标一起思考这个位置应该是什么,这样打是不是一定对?
这个过程一定要谨慎哦...为了大家调试代码的愉悦度!
调试的过程:
【关于调试工具】
emmm 笔者用的是gdb这种原始又实用的东西 hhh然后大家就各自用自己的IDE好了!
首先大家需要知道基本的【文件操作】来让自己的程序可以读一个比较大的输入文件,也能把输出的东西存到一个文件里面去
1 #ifndef ONLINE_JUDGE 2 freopen("data.in","r",stdin); 3 freopen("my_code.out","w",stdout); 4 #endif
只要把这个东西加在int main的后面就可以啦(前面需要包含<cstdio>库),其中2、3行是正式的代码啦,然后1、4行可以让你的程序在自己本机运行的时候用2、3里面的语句,但是交到openjudge上的时候又不会调用里面的语句,是不是很厉害呢hhhh
然后大家既然都输出到文件里面了,那就当然也要知道一种可以比较文件是否相同的办法!因为要肉眼比较输出有一点麻烦...【不过我就是这么做的】,如果你要这么做的话,请选一个好看的文本编辑器hhh:肉眼比较一次可以比较一版,就是将两个out调整到相同的位置(这个调整到相同位置也是很有技巧的,你要灵活的使用鼠标和光标来调整位置hhh),然后反复切换,在来回切换的时候不同点会很明显,这样你就可以找到了!
然后言归正传还是讲一讲【怎么比较文件】
首先是Linux最熟悉啦:在当前位置打开终端,然后输入:diff -c my_code.out std.out 就可以比较了(其中-c是可以帮你输出错误行号的,感觉一看就明白啦...然后my_code.out是自己的输出结果,std是正确的结果 [这个正是我用的hhh])
然后是Windows下:先敲一下cmd调出命令行,然后cd 后面加你文件存的位置,这个位置怎么找呢?就是你可以打开你文件所在的文件夹,然后单击一下,然后cd + 这个蓝色的东西...+回车 (我真是科普小天使呀hhh)
然后你就进入到了当前文件夹,然后输入命令:fc my_code.out std.out+回车就可以啦...然后这个不同可能很多,导致控制台装不下..[hhh我就经常发生这种情况]
我们可以fc my_code.out std.out > Difference.txt 这样就可以把比较信息输出到这个txt里面去了,上面Linux也是一样的哦
然后是根据读者范围友情添加的Mac大神应该怎样做呢?
Hhh我也不知道啦...不过下面这个博客提供了两种文件比较的工具,可以看一下啦 :https://blog.****.net/wowfly98/article/details/52774275
【关于数据】
想要调试好,你需要获得一些比较优秀的数据,course上提供的数据就挺棒的(因为过了那个就能A啦!hhh)
不过呢course上的数据是多组的,为了方便调试,我们可以一组一组的拆开来试【by the way 不知道blog怎么上传文件QwQ那就大家自己来吧...hhh我的经验是第一二组数据是很好的数据,后面的九十也都是比较强的,然后中间有一些是很短的数据,价值不高】
然后对于一组数据我们也是可以拆的!至于为什么要拆呢?请听下面的笔者哭诉节目:
笔者当时经常会遇到RE。RE是一种比WA更难受的东西,它有时是很良心的:会告诉你卡在哪一行了...有时不会告诉你,它就是卡住了!(太傲娇了吧)
RE在这种涉及到一堆指针的题目里面是十分常见的,以为你只要delete了一个空的地址就会报错,然后访问了一个空指针的内容也会RE,所以delete之后一定记得把指针赋值为NULL!记得要访问之前判断是不是NULL!
然后这里涉及到的指针主要是City和New_City的Warrior指针,还有就是Warrior里面放Weapon的w[]指针数组。City要记得每个Case之后要清空一下,顺带把指向的战士消除掉,然后w[]数组是很容易出错的一个地方!因为中途我会有武器损耗、武器被抢等等都可能将w[i]中间某一个位置给挖空!这样的话w[]中有指向的范围就不是连续的了!Weapon_Count也只能给你计数了,所以每次我想要一块连续的武器存储一定要记得排序,而且排序的范围一定是[0,10)哦(笔者有n次RE都是这个原因,因为需要用Weapon的地方太多了,改的时候记得一起改完)
不过也不一定都是这种情况的RE,对于RE我们有传统方案就是设置断点,在这种时间很多的题中的断点一般长这样:
1 if(Time_Index==7) 2 printf("Stop\n");
通过不停地修改Index后面的数值就可以知道在哪个时间段里RE啦,然后再单独进入里面调试。
当然啦,这个题有一个得天独厚的优势:就是输入数据中是有时间的,所以你只要修改后面的时间也可以打到通过设置断点找到RE的时间点的过程,同时你也可以通过修改时间找到一个里RE比较近的可以运行的时间去观察输出数据中关于时间的输出来方便自己设置断点来检查RE。
然后关于RE的情况在上面已经讲得比较丰富了...
下面来讲讲WA的调试方法。
魔兽三这样的题其实是很容易找到WA的问题的,因为我们是面向对象的程序,而且我们的输出一定是有一个对象的,那么你在哪一个位置WA了,一定是针对某一个对象的储存值出现了问题,而我们是可以在这个WA的地方之前找到关于这个对象所有的报告的,方法就是control+F然后输入你想找的对象名字 [这个时候又需要你有一个优秀的文本编辑器啦hhh]比如我的界面是这样子的:
比如说打架的结果不同了,我就去看一下前面汇报的两个人的血量生命值还是有武器数量什么的...然后就可以手上模拟看看程序在哪儿出问题啦..
总而言之WA虽然可能的诱因无穷无尽,但是相对于RE也是更容易找到而且找的过程更加的有趣的hhh,当然关于WA的诱因,最重要的一部分就是题里面的细节
那我就列举一些我印象深刻的害人的细节吧:
1.题面描述里:这里在40分钟和50分钟之间混入了一个10分钟输出...但事实上最后输出是需要在10分的时刻输出的(详细见上面描述行军的部分)
2.Dragon没有战死就会欢呼
3.Ninja使用炸弹自己不会掉血
4.使用炸弹可能把自己炸死而敌人不死,这时候敌人也会收缴你的武器
5.当指挥部被占领后游戏结束,但是还是要输出这个时刻所有武士的移动报告
6.Wolf不会抢Wolf的武器;Wolf只会抢一种武器;如果武器是用cnt[3]存的话,Wolf要小心一起抢光使得自己的武器多于10把的情况。
如果是和笔者一样用的数组存储武器的话,要注意下面这个程序段中的for循环中的终止条件应该写成:i<Enermy->Weapon_Count+Amount
而不能写成i<Enermy->Weapon_Count,因为每当我抢一件武器,我的Weapon_Count都会-1,但是我抢的武器确实从头抢起的,所以如果我写成了<Weapon_Count的话就会使得最后几件武器抢不到了,所以这个终止条件应该是抢武器之前的武器数量,也就是Weapon_Count+Amount了。
1 sort(w,w+10,cmp); 2 sort(Enermy->w,Enermy->w+10,cmp); 3 int Min_Index=Enermy->w[0]->Index,Amount=0; 4 for(int i=0;Enermy->w[i]!=NULL && Enermy->w[i]->Index==Min_Index && Weapon_Count<10 && i<Enermy->Weapon_Count+Amount;i++){ 5 w[Weapon_Count++]=Enermy->w[i],Amount++; 6 Enermy->Weapon_Count--; 7 Enermy->w[i]=NULL; 8 }
7.输入的时间的单位是分钟;如果双方司令部都停止生产了而时间还没有到,也得接着输出(不同于魔兽一二)
8.这个题中的指挥部生产士兵,如果下一个不能生产了就不会再生产,而不需要去找下一个能产的(不同于一二)
9.这个题中也会分发武器,但是不需要输出分发武器的信息;这一题中Lion出生也会发兵器(不同于魔兽二)。
10.使用swith case的时候一定记得break!
[上面都是笔者的痛苦教训,希望你们可以省些力气哦...]
【魔兽世界三:代码】