魔兽世界(三)
在魔兽(三)中,最重要的是加入了武士在城市之间的移动和战斗。
具体题目见:http://cxsjsx.openjudge.cn/hw202006/E/
框架搭建
Cweapon类:用来存放某个武士拥有的武器状态。(包括各种武器分别的数量、总数量、以及pick_weapon成员函数用来为交手时选择武器)
Warrior类:武士类,包含生命值、攻击力、位置、编号、武器状况基本信息。
Dragon类:Warrior类的派生类。
Ninja类:Warrior类的派生类。
Iceman类:Warrior类的派生类。
Lion类:Warrior类的派生类。
Wolf类:Warrior类的派生类。
City类:代表一个城市。包含一个warrior** 类的指针h,h是个长度为4的数组,用来存放当前城市的warrior的指针,在武士移动、战斗过程中十分重要。其中,0位储存当前城市red warrior信息,2位储存临时red信息,1位储存当前blue warrior信息,3位储存临时blue信息。
Battleground类:战场构建。成员变量有Lifetime2、TotalWarrior2、curent2、produce_end2、warrior5
成员函数如下:
void print_time():输出当前时间
void create_warrior(int color):制造武士
void clear():更新战场(临时储存的更替)
bool march():武士移动,返回false即已经到达基地
void escape():lion逃离
void rub_weapon(Warrior* p,Warrior q):胜利者抢夺失败者的武器
void print_weapon(int t):输出武器名
void wolf_rob_weapon(Warriorp,Warrior*q):wolf抢夺非wolf敌人的武器
void wolf_rub():判断是否需要wolf抢
void fight():战争开始
void headquarter_report():司令部报告
void warrior_report():武士报告
bool check_time():检查是否到达结束时间,返回false即结束
bool run_a_round():运行一个回合
关键函数思路
先看一个回合内函数的运行顺序,run_a_round要是返回false代表游戏结束。
bool run_a_round
bool run_a_round()//运行一个回合
{
Minute = 0;
if (!check_time()) return false;
create_warrior(0);
create_warrior(1);
Minute = 5;
if (!check_time()) return false;
escape();
Minute = 10;
if (!check_time()) return false;
if(!march()) return false; //到基地了
Minute = 35;
if (!check_time()) return false;
wolf_rub();
Minute = 40;
if (!check_time()) return false;
fight();
Minute = 50;
if (!check_time()) return false;
headquarter_report();
Minute = 55;
if (!check_time()) return false;
warrior_report();
Hour++;
return true;
}
*** bool march
武士移动至下一个城市的临时位置(还未renew)
关于指针,笔者犯过很多错误,总结来说有以下注意事项
1.防止出现野指针:(直接避免是最好的方法)
a.定义指针要初始化,即使=NULL
b.分配后和使用前要检测,使用时不要越界
c.释放后一定 = NULL
2.delete可以使用在空指针上(系统会自动跳过),但不能使用在野指针上。
注:删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身(指针p本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放)。
3.不要让内存泄漏了:比如直接使new 的指针delete前就NULL
但是如果是赋值反而不能delete,笔者写的时候debug很久的一个错误就是clear函数中赋值指针:city[i].h[0]=city[i].h[2];delete city[i].h[2];city[i].h[2]=NULL;2赋值给了0,这时候2直接置NULL也不会内存泄漏,否则会把0指向的空间一起删了。
4.访问指针指向地址是一定要判断是否是NULL
在march函数中移动武士中需要判断是不是Iceman,这时候用typeid函数比较方便确认对象类型(头文件typeinfo):if(typeid(a)==typeid(Iceman))
fight()
fight函数是处理武士战斗的,是本程序最麻烦的也是最重要的过程之一。此处参考了另外一位同学的方法。
题目描述在奇数号城市红武士先进行攻击,偶数号城市蓝武士先进行攻击。利用这一点,对任意城市i,设r为城市/2的余数,即r方先进行攻击,l方受到攻击。若下一次使l攻击,再使r攻击,代码稍显繁琐。这时采用异或运算,每次下一回合攻击时让r和l分别异或1,这时每次都是r进行攻击。
还有一个重要的问题就是有一种特殊的情况就是双方都有武器,但是由于攻击力太低,导致武器的伤害=0(去尾取整),这时候不加以判断就会导致程序TLE死循环了,于是采用局势比较,两个战斗回合双方属性不变就结束战斗。但是每个回合都检查并储存属性信息未免过于费时间,毕竟这种情况是极端情况,于是每8个round检查一次。
void fight()//战争开始
{
for (int i = 1; i <= CityNumber; ++i)
{
if (city[i].h[0] == NULL || city[i].h[1] == NULL)
continue;
int r = i % 2, L = r ^ 1;
int n[2] = {1, 1}, w, flag = 5, check[4] = {0}, round = 0;
while (true) //交战至回合结束
{
//判断是否局势改变,若不改变则为平局
r = r ^ 1;//r方进行攻击
L = L ^ 1;//两者交互
if (round == 0)
{
if (check[0] == city[i].h[0]->wLifetime && check[1] == city[i].h[1]->wLifetime && check[2] == city[i].h[0]->Weapon.amount && check[3] == city[i].h[1]->Weapon.amount)
{
flag = 0;//all alive 都还活着
break;
}
check[0] = city[i].h[0]->wLifetime;
check[1] = city[i].h[1]->wLifetime;
check[2] = city[i].h[0]->Weapon.amount;
check[3] = city[i].h[1]->Weapon.amount;
round = 8; //8个回合检查一次是否局势不改变了
}
round--;
if (city[i].h[L]->Weapon.amount == 0 && city[i].h[r]->Weapon.amount == 0)
{
flag = 0;//alive
break;
}
if(city[i].h[r]->Weapon.amount == 0)
continue;
w = city[i].h[r]->Weapon.pick_weapon(n[r]); //本轮用的武器编号
int j;
switch (w) //攻击
{
case 0://sword
city[i].h[L]->wLifetime -= city[i].h[r]->force / 5; break;
case 1://bomb
j = city[i].h[r]->force * 2 / 5;
city[i].h[L]->wLifetime -= j;
if(typeid(*city[i].h[r]) != typeid(Ninja))
city[i].h[r]->wLifetime -= j / 2;
break;
case 2://arrow
city[i].h[L]->wLifetime -= city[i].h[r]->force * 3 / 10; break;
default:
break;
}
if (city[i].h[L]->wLifetime <= 0 && city[i].h[r]->wLifetime <= 0)
{
flag = 3;//all died
break;
}
else if (city[i].h[L]->wLifetime <= 0)
{
if (L == 0)
{
flag = 2; //blue win
break;
}
else
{
flag = 1; //red win
break;
}
}
else if (city[i].h[r]->wLifetime <= 0)
{
if (r == 0)
{
flag = 2; //blue win
break;
}
else
{
flag = 1; //red win
break;
}
}
else
flag=0;//都活着:本来不需要写出来,程序已经保证,但写出来逻辑更清晰
}
print_time();
switch (flag)
{
case 0: //双方都活着——平局
cout << " both red ";
city[i].h[0]->print_name();
cout << " and blue ";
city[i].h[1]->print_name();
cout << " were alive in city " << i << endl;
break;
case 3: //双方都死了——平局
cout << " both red ";
city[i].h[0]->print_name();
cout << " and blue ";
city[i].h[1]->print_name();
cout << " died in city " << i << endl;
delete city[i].h[0];
delete city[i].h[1];
city[i].h[0] = NULL;
city[i].h[1] = NULL;
break;
case 1: //红方胜利
cout << " red ";
city[i].h[0]->print_name();
cout << " killed blue ";
city[i].h[1]->print_name();
printf(" in city %d remaining %d elements\n", i, city[i].h[0]->wLifetime);
rub_weapon(city[i].h[0], city[i].h[1]);
delete city[i].h[1];
city[i].h[1] = NULL;
break;
case 2: //蓝方胜利
cout << " blue ";
city[i].h[1]->print_name();
cout << " killed red ";
city[i].h[0]->print_name();
printf(" in city %d remaining %d elements\n", i, city[i].h[1]->wLifetime);
rub_weapon(city[i].h[1], city[i].h[0]);
delete city[i].h[0];
city[i].h[0] = NULL;
break;
default:
break;
}
for (int t = 0; t < 2; ++t)//dragon没有战死就会欢呼
{
if (city[i].h[t] != NULL && typeid(*city[i].h[t]) == typeid(Dragon))
{
print_time(); cout << ' ' << HeadquarterName[t] << ' ';
city[i].h[t]->print_name();
cout << " yelled in city " << i << endl;
}
}
}
}
pick_weapon
本题中还有一个比较麻烦的函数,就是使用序号为n的武器。比较特殊的是需要判断除了sword外所有武器都使用完时需要再次使用sword。
int pick_weapon(int& n) //决定第n次交手应该使用的武器,n已经考虑过经过循环处理为当前武器序号
{
//进入这个函数保证amount>0
bool flag = false;
if (amount <= n) flag = true;//除了sword以外的武器都已经用完了
if (n <= a[0])
{
++n;
if(flag) n = 1;
return 0;
}
else if (n <= a[0] + a[1])
{
a[1]--;
amount--;
if(flag) n = 1;
return 1;
}
else if (n <= a[3] + a[0] + a[1])
{
a[3]--;
amount--;
if(flag) n = 1;
return 2;
}
else if (n <= amount)
{
a[2]--; //2是没有用过的arrow
a[3]++; //3是用过的arrow
++n;
if(flag) n = 1;
return 2;
}
}