第一个 Java 小游戏

OOP h05

某一款游戏,其主要角色如下:

  • Tank 坦克
    • Heavy Tank 重型坦克:初始生命值 200,攻击力 20
    • Medium Tank 轻型坦克:初始生命值 100,攻击力 10
  • War Factory 兵工厂:初始生命值 100,无攻击力
  • Barrack 兵营:可以训练出步枪兵、 RPG 兵、军犬,初始生命值 100,无攻击力
    • Rifle Soldier 步枪兵:初始生命值 50(对战 军犬除外),攻击力 5(对战军犬可以一次击毙军犬)
    • Rocket Soldier 火箭兵:初始生命值 50(对战 军犬除外),攻击力 10
    • Dog 军犬:初始生命值 50,攻击力 5(对战人类时候一口毙命)

此外还要能通过 Soldier.getLivingSoldierCount / Soldier.getDeadedSoldierCount 统计现在有多少个活着的和死去的士兵数量

请遵循以上游戏规则,并根据如下测试代码(见文末)设计代码

作业批改的时候,除了如下调用的方法以外,不会增加新的调用方法,但是可能有不同的组合方式。例如让一个 Dog 反复攻击 HeavyTank,判断 HeavyTank.getHealth()

任何对象有 getHealth() 方法,返回当前生命值,如果已经死亡则返回 <=0 的一个数字

2021.3.19 更新

  1. 文中定义的 Base 类可更名为 GameObject 抽象类。定义更改(和初始化)生命值的方法,其中参数为攻击力;定义死亡方法,修改生命值后调用(只需在 Soldier 类中重写);在攻击方法中先判断攻击力,再调用更改生命值的方法。
  2. 定义 Param 类,存放全部常量(生命值和攻击力)。
  3. WarFactoryBarrack 继承 Building 类。
  4. 利用 instanceof 判断参数属于何种类。

0x00 分析

阅读需求,可以发现要实现的是一个“小游戏”,游戏中有许多角色,分为「工厂」和「兵营」两大类。工厂可以建造「坦克」,分为「重型坦克」和「轻型坦克」;兵营可以训练「士兵」和「军犬」,士兵分为「步枪兵」和「火箭兵」。每种角色都有「生命值」和「攻击力」。

综合以上分析,可以发现需要将上述角色抽象为「类」,而且存在明显的继承关系:

  • 每种角色都有「生命值」和「攻击力」,即它们可以继承同一个父类,这个父类中有「生命值」和「攻击力」的属性;
  • 坦克分为重坦和轻坦,即重坦和轻坦继承自坦克类,同理步枪兵和火箭兵也继承自士兵类。

综合以上分析,可以首先创建 Base 类作为所有类的父类,再依次创建 WarFactoryBarrackTankSoldierDogHeavyTankMediumTankRifleSoldierRPGSoldier

0x01 Base

Base 作为下面所有类的父类,具有所有类共有的属性:「生命值」和「攻击力」。

类的初始化时需要指明其生命值和攻击力,所以 Base 的构造方法需要对其生命值和攻击力进行初始化,供子类继承。

阅读 Test 代码:

1
2
mediumTank1.attack(rifleSoldier1);
//被mediumTank1攻击一次,health-10

可以发现需要具有 attack 方法,攻击其他对象。结合其他示例可以发现,需要攻击的对象有 TankSoldierDog,所以在 Base 中需要进行重载。

第一次写的时候没能正确理解 attack 方法的攻击范围,错误地认为坦克只能攻击士兵和军犬、士兵只能攻击坦克和军犬……

而且当时由于错误的理解,遂即把 attack 方法写在坦克、士兵和军犬类中,为了不直接修改生命值,又对应写了“被攻击”方法……

改正思路后,attack 方法统一写在基本类中,提高了内聚性,降低了耦合性。

结合说明,任何对象都需要有 getHealth() 方法,返回当前生命值;isDestroy() 方法判断是否被消灭。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class Base {
protected int lifeValue; //生命值
protected int attackPower; //攻击力

public Base(int lv, int ap) {
this.lifeValue = lv;
this.attackPower = ap;
}

public void attack(Soldier soldier) { //Soldier为后续实现的士兵类
if (soldier.lifeValue > 0) { // 只有活着的士兵才能被攻击,下面同理
soldier.lifeValue -= this.attackPower; //攻击士兵的结果就是它的生命值减少,减少的量为攻击者的攻击力
if (soldier.lifeValue <= 0) //由于需要判断士兵死亡数量,所以每当士兵被攻击时都需要进行判断
Soldier.setDeadedSoldierCount(); //士兵类中实现的方法,使总士兵死亡数增加1,生存数减少1
}
}
public void attack(Tank tank) { //Tank为后续实现的坦克类
if (tank.lifeValue > 0)
tank.lifeValue -= this.attackPower;
}
public void attack (Dog dog) { //Dog为后续实现的军犬类
if (dog.lifeValue > 0)
dog.lifeValue -= this.attackPower;
}
public int getHealth() {
return this.lifeValue;
}
public boolean isDestroyed() { 被消灭的标志就是生命值小于等于0
return this.lifeValue <= 0;
}
}

0x02 WarFactory&Barrack

WarFactory 为工厂类,继承 Base,且可以建造坦克。

阅读 Test 代码:

1
2
//building 建造各自型号坦克
Tank mediumTank1=(MediumTank)warFactory.building(EnumObjectType.mediumTank);

可以发现建造坦克方法 building 的参数为枚举类型 EnumObjectType,则需要新建 EnumObjectType

1
2
3
4
5
6
7
8
9
package com.huawei.classroom.student.h05;

/**
* @author super
*/

public enum EnumObjectType {
RPGSoldier, dog, rifleSoldier, mediumTank, heavyTank
}

WarFactory 完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class WarFactory extends Base{
public WarFactory() {
super(100, 0); //调用Base构造方法,初始化工厂的生命值和攻击力
}
public Tank building(EnumObjectType built) {
if (built == EnumObjectType.mediumTank)
return new MediumTank();
else if (built == EnumObjectType.heavyTank)
return new HeavyTank();
else
return null;
}
}

BarrackWarFactory 类似,可以训练士兵和军犬,同样用到 EnumObjectType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class Barrack extends Base{
public Barrack() {
super(100, 0);
}


public Base traing(EnumObjectType trained) { //士兵和军犬不属于同一子类,不能像Tank一样直接返回,只能返回基本类Base,这时Test中的类型转换起作用
if (trained == EnumObjectType.rifleSoldier)
return new RifleSoldier();
else if (trained == EnumObjectType.RPGSoldier)
return new RPGSoldier();
else if (trained == EnumObjectType.dog)
return new Dog();
else
return null;
}
}

0x03 Tank&Soldier&Dog

Tank 类无需进行额外操作,只需继承 Base 的构造方法,供子类继承:

1
2
3
4
5
6
7
8
9
10
11
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class Tank extends Base{
public Tank(int lv, int ap) {
super(lv, ap);
}

}

Soldier 类需要记录生存和死亡的士兵数目,应定义为静态变量,定义 setter 和 getter 便于修改记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class Soldier extends Base{
private static int livingSoldierCount;
private static int deadSoldierCount;

public Soldier(int lv, int ap) {
super(lv, ap);
setLivingSoldierCount();
}

public static int getLivingSoldierCount() {
return livingSoldierCount;
}
public static void setLivingSoldierCount() {
livingSoldierCount++;
}
public static int getDeadedSoldierCount() {
return deadSoldierCount;
}
public static void setDeadedSoldierCount() {
deadSoldierCount++;
livingSoldierCount--;
}

}

Dog 类继承 Base 的构造方法,初始化特定的生命值和攻击力。注意到特殊要求“对战人类时候一口毙命”,则需要重写(此处和重载无区别)Baseattack 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class Dog extends Base {
public Dog() {
super(50, 5);
}

public void attack(Soldier soldier) {
if (soldier.lifeValue > 0) {
soldier.lifeValue = 0;
Soldier.setDeadedSoldierCount(); //消灭士兵需要修改死亡数量
}
}
}

0x04 HeavyTank&MediumTank

只需要继承 Tank 的构造方法,初始化特定的生命值和攻击力:

1
2
3
4
5
6
7
8
9
10
11
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class HeavyTank extends Tank {
public HeavyTank() {
super(200, 20);
}

}
1
2
3
4
5
6
7
8
9
10
11
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class MediumTank extends Tank {
public MediumTank() {
super(100, 10);
}

}

0x05 RifleSoldier&RPGSoldier

RifleSoldier “可以一次击毙军犬”,所以需要重写(重载)attack 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class RifleSoldier extends Soldier{
public RifleSoldier() {
super(50, 5);
}

public void attack(Dog dog) {
if (dog.lifeValue > 0)
dog.lifeValue = 0;
}
}

RPGSoldier 只需初始化特定生命值和攻击力即可:

1
2
3
4
5
6
7
8
9
10
11
package com.huawei.classroom.student.h05;

/**
* @author super
*/
public class RPGSoldier extends Soldier{
public RPGSoldier() {
super(50, 10);
}

}

0x06 Test

Test 测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.huawei.classroom.student.h05;

public class Test {

public Test() {
// TODO Auto-generated constructor stub
}

public static void main(String[] args) {
// TODO Auto-generated method stub
//某一款游戏,其主要角色如下:
//Tank HeavyTank 重型坦克 初始生命值200,攻击力 20
//Medium Tank 轻型坦克 初始生命值100,攻击力 10
//War Factroy 兵工厂 初始生命值100,无攻击力
//Barrack 兵营,可以训练出步枪兵、 RPG兵、军犬,初始生命值100,无攻击力
//Rifle soldier 步枪兵 初始生命值50(对战 军犬除外),攻击力 5(对战军犬可以一次击毙军犬)
//Rocket soldier 火箭兵 初始生命值50(对战 军犬除外),攻击力 10
//Dog 军犬 ,初始生命值50,攻击力5(对战人类时候一口毙命)
//此外还要能通过Soldier.getLivingSoldierCount/getDeadedSoldierCount 统计现在有多少个活着的和死去的士兵数量
//请遵循以上游戏规则,并根据如下测试代码设计代码
//作业批改的时候,除了如下调用的方法以外,不会增加新的调用方法,但是可能有不同的组合方式
//例如让一个Dog反复攻击HeavyTank,判断HeayTank.getHealth()
//任何对象有getHealth() 方法,返回当前生命值,如果已经死亡则返回 <=0的一个数字
Barrack barrack=new Barrack();
if(barrack.getHealth()==100) {
System.out.println("ok1");
}
//traing 训练出新的士兵或者狗
RifleSoldier rifleSoldier1=(RifleSoldier)barrack.traing(EnumObjectType.rifleSoldier);
if(rifleSoldier1.getHealth()==50) {
System.out.println("ok2");
}
RPGSoldier rPGSoldier1=(RPGSoldier)barrack.traing(EnumObjectType.RPGSoldier );
if(rPGSoldier1.getHealth()==50) {
System.out.println("ok3");
}
Dog dog1=(Dog)barrack.traing(EnumObjectType.dog );
if(dog1.getHealth()==50) {
System.out.println("ok4");
}
//构造新的兵工厂
WarFactory warFactory=new WarFactory();
if(warFactory.getHealth()==100) {
System.out.println("ok5");
}
//building 建造各自型号坦克
Tank mediumTank1=(MediumTank)warFactory.building(EnumObjectType.mediumTank);
if(mediumTank1.getHealth()==100) {
System.out.println("ok6");
}

Tank heavyTank1=(HeavyTank)warFactory.building(EnumObjectType.heavyTank );
if(heavyTank1.getHealth()==200) {
System.out.println("ok7");
}

heavyTank1.attack(mediumTank1);
//attacked by heavyTank1, health-20
if (mediumTank1.getHealth() == 80) {
System.out.println("ok7.3");
}

mediumTank1.attack(heavyTank1);
//attacked by mediumTank1, health-10
if (heavyTank1.getHealth() == 190) {
System.out.println("ok7.6");
}

mediumTank1.attack(rifleSoldier1);
//被mediumTank1攻击一次,health-10
if(rifleSoldier1.getHealth()==40){
System.out.println("ok8");
}

dog1.attack(rifleSoldier1);
//被 dog 攻击一次,health<=0
if(rifleSoldier1.isDestroyed()) {
System.out.println("ok9");
}

mediumTank1.attack(dog1);
//被攻击后 health-10
if(dog1.getHealth()==40){
System.out.println("ok10");
}
RifleSoldier rifleSoldier2=(RifleSoldier)barrack.traing(EnumObjectType.rifleSoldier);
//被RifleSoldier攻击 一击毙命
rifleSoldier2.attack(dog1);
if(dog1.isDestroyed()) {
System.out.println("ok11");
}
//System.out.println("getliving: " + Soldier.getLivingSoldierCount());
//System.out.println("getdead: " + Soldier.getDeadedSoldierCount());
//Soldier 当前活着的数量2(rifleSoldier2,rPGSoldier1),死亡的数量1(rifleSoldier1)
if(Soldier.getLivingSoldierCount()==2&&Soldier.getDeadedSoldierCount()==1) {
System.out.println("ok12");
}

}

}