第一个 Java 小游戏(plus)

OOP h06

请确认已经完成 h05

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

游戏中每个对象有当前 x,y 坐标,坐标值取值范围为整数

非建筑物可以通过 move(dx,dy) 来移动坐标值,dx,dy 表示 x 轴,y 轴增量,取值范围为整数

对象 A 攻击 B 的时候,要满足两个对象之间直线距离小于等于 A 的攻击范围,否则攻击无效(被攻击方不减健康值)

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

任何对象有 isDestroyed() 方法,如果生命值 <=0true,否则 false

初始生命值 攻击力 攻击范围 初始坐标值
GameBase 玩家基地 500 - - 创建时指定
WarFactroy 兵工厂 100 - - 创建时指定
Barrack 兵营 100 - - 创建时指定
HeavyTank 重型坦克 200 20 10 兵工厂
MediumTank 轻型坦克 100 10 10 兵工厂
RifleSoldier 步枪兵 50 5 5 兵营
RPGSoldier 火箭兵 50 10 10 兵营
Dog 军犬 50 5 5 兵营

其中

  • Barrack 兵营可以训练出步枪兵、RPG 兵、军犬
  • RifleSoldier 步枪兵对战军犬可以一次击毙军犬
  • Dog 军犬对战人类时候一口毙命

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

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

作业批改的时候,方法可能不同组合

例如让一个物体移动,超出或者进入攻击范围,判断是否攻击有效

让一个物体制造出来以后,不移动,然后另一个物体攻击,判断该物体是否使用了默认的 x,y

未来可能分玩家,例如玩家 A,玩家 B,玩家 C

未来可能玩家 A 战斗单元不能攻击自己的战斗单元

未来玩家之间可能要结盟、取消结盟

未来坦克攻击以后,造成的杀伤可能不是点杀伤,而是一个杀伤范围

……

0x00 分析

和 h05 相比,h06 增加的特性主要为角色的坐标和攻击范围。这两个属性和生命值、攻击力一样,需要添加到基本类中,后称为 GameObject。需要改动的是 attack 方法,攻击前需要判断二者距离,并和攻击者的攻击范围作比较。

0x01 EnumObjectType&Param

根据 Test 的调用,EnumObjectType 需要添加 barrackwarFactory

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

/**
* @author super
*/

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

可以新建 Param 类,存放所有变量的值,便于管理和更改:

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
package com.huawei.classroom.student.h06;

/**
* @author super
*/
public class Param {

public static final int BASE_HEALTH=500;
public static final int BASE_STRENGTH=0;
public static final double BASE_ATTACKRANGE=0;

public static final int DOG_HEALTH=50;
public static final int DOG_STRENGTH=5;
public static final double DOG_ATTACKRANGE=5.0;

public static final int SOLDIER_HEALTH=50;
public static final int SOLDIER_RPG_STRENGTH=10;
public static final double SOLDIER_RPG_ATTACKRANGE=10.0;
public static final int SOLDIER_RIFLE_STRENGTH=5;
public static final double SOLDIER_RIFLE_ATACKRANGE=5.0;

public static final int TANK_HEAVY_HEALTH=200;
public static final int TANK_HEAVY_STRENGTH=20;
public static final double TANK_ATTACKRANGE=10.0;


public static final int TANK_MEDIUM_HEALTH=100;
public static final int TANK_MEDIUM_STRENGTH=10;


public static final int WAR_FACTORY_HEALTH=100;
public static final int WAR_FACTORY_STRENGTH=0;
public static final double WAR_FACTORY_ATTACKRANGE=0;

public static final int BARRACK_HEALTH=100;
public static final int BARRACK_STRENGTH=0;
public static final double BARRACK_ATTACKRANGE=0;

}

0x02 GameObject

此类的作用相当于 h05 中提到的 Base 类,但做了改进。

属性

根据分析,需要增加新的属性,包括攻击范围和坐标值。

所有的属性均为 private,修改操作只在此类进行。

1
2
3
4
private int lifeValue = 0;
private final int attackPower;
private final double attackRange;
private int x, y;

构造方法

构造方法传五个参数,对应进行赋值。

1
2
3
4
5
6
7
public GameObject(int lv, int ap, double ar, int x, int y) {
this.lifeValue = lv;
this.attackPower = ap;
this.attackRange = ar;
this.x = x;
this.y = y;
}

Setter

包括 changeHealth,传入参数为某攻击力,当对象生命值为正(活着)时才可以更改其生命值,为减去攻击力后的值:

1
2
3
4
5
6
7
8
9
public void changeHealth(int strength) {
if (this.isDestroyed()) {
return;
}
this.lifeValue -= strength;
if (this.lifeValue <= 0) {
this.dead();
}
}

生命值更改后,需要判断其是否已经死亡,若死亡则调用 dead 方法。

dead 方法在此类中为空,供子类重写,便于一些角色在死亡后需要进一步操作,如士兵。

按照以前的思路,士兵死亡需要额外记录数量,自然想到对 changeHealth 方法直接重写。但私有属性不应在子类中修改,所以改进了重写的方法,即另写 dead

1
2
3
public void dead() {

}

Getter

getAttackPower

DogrifleSoldier 都有特殊的攻击效果,以前是在它们的类中重写了 attack,本次按照上述分析做出了改进。

特殊的攻击是因为它们攻击特定角色时可以导致其直接死亡,可以想象为它们遇到特定角色时攻击力增强。所以此类中新建此方法,默认返回 -1DogrifleSoldier 重写为更大的值,具体用法见下文 attack

1
2
3
public int getAttackPower(GameObject obj) {
return -1;
}

其他

包括 getHealthgetPosXgetPosYgetDistance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getHealth() {
return lifeValue;
}

public int getPosX() {
return this.x;
}

public int getPosY() {
return this.y;
}

public double getDistance(GameObject obj) {
return Math.sqrt(Math.pow(this.x - obj.x, 2) + Math.pow(this.y - obj.y, 2));
}

move

移动角色,即更改其坐标:

1
2
3
4
public void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}

attack

被攻击者为参数,首先判断其是否死亡,死亡的角色不能再次被攻击。

然后获取其 getAttackPower,若小于零(默认为 -1)则正常,否则进一步判断攻击距离。

满足攻击距离在攻击范围内,则 changeHealth 改变的就是 getAttackPower 值。

1
2
3
4
5
6
7
8
9
10
11
12
public void attack(GameObject obj) {
if (obj.isDestroyed()) {
return;
}
int ap = getAttackPower(obj);
if (ap < 0) {
ap = this.attackPower;
}
if (this.attackRange >= this.getDistance(obj)) { //>=!!!
obj.changeHealth(ap);
}
}

isDestroyed

是否死亡:

1
2
3
public boolean isDestroyed() {
return lifeValue <= 0;
}

完整代码

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
package com.huawei.classroom.student.h06;

/**
* @author super
*/
public abstract class GameObject {

private int lifeValue = 0;
private final int attackPower;
private final double attackRange;
private int x, y;

public GameObject(int lv, int ap, double ar, int x, int y) {
this.lifeValue = lv;
this.attackPower = ap;
this.attackRange = ar;
this.x = x;
this.y = y;
}

public void changeHealth(int strength) {
if (this.isDestroyed()) {
return;
}
this.lifeValue -= strength;
if (this.lifeValue <= 0) {
this.dead();
}
}

public void dead() {

}

public int getHealth() {
return lifeValue;
}

public int getPosX() {
return this.x;
}

public int getPosY() {
return this.y;
}

public double getDistance(GameObject obj) {
return Math.sqrt(Math.pow(this.x - obj.x, 2) + Math.pow(this.y - obj.y, 2));
}

public void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}

public void attack(GameObject obj) {
if (obj.isDestroyed()) {
return;
}
int ap = getAttackPower(obj);
if (ap < 0) {
ap = this.attackPower;
}
if (this.attackRange >= this.getDistance(obj)) {
obj.changeHealth(ap);
}
}

public boolean isDestroyed() {
return lifeValue <= 0;
}

public int getAttackPower(GameObject obj) {
return -1;
}
}

0x03 GameBase

继承 GameObject

构造方法需要传参坐标,其中需要调用 GameObject 的五参数构造方法。

通过 Test 知需要静态方法 createGameBase 来创建一个游戏基地。

游戏基地可以建造建筑,参数为要建造的建筑类型和坐标。

对应建筑的创建均需要传参坐标。

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.h06;

/**
* @author super
*/
public class GameBase extends GameObject{
public GameBase(int x, int y) {
super(Param.BASE_HEALTH, Param.BASE_STRENGTH, Param.BASE_ATTACKRANGE, x, y);
}
public static GameBase createGameBase(int x, int y) {
return new GameBase(x, y);
}
public Building building(EnumObjectType enumObjectType, int x, int y) {
if (enumObjectType == EnumObjectType.barrack) {
return new Barrack(x, y);
} else if (enumObjectType == EnumObjectType.warFactory) {
return new WarFactory(x, y);
} else {
return null;
}
}
}

0x04 Building

建筑类,继承 GameObject,不被创建,子类包括兵营和军工厂。但兵营和军工厂的创建不通过 Building 而是通过 GameBase

建筑不可移动,故重写 move

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

/**
* @author super
*/
public class Building extends GameObject {

public Building(int health, int strength, double attackRange, int x, int y) {
super(health, strength, attackRange, x, y);
}

@Override
public void move(int dx, int dy) {
}

}

0x05 Barrack&WarFactory

兵营和军工厂,继承 Building,由 GameBase 创建。构造方法传参坐标(创建时),调用父类构造方法(继承)。

兵营可训练出步枪兵、火箭兵和军犬,它们的坐标就是兵营的坐标(传参):

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

/**
* @author super
*/
public class Barrack extends Building {

public Barrack(int x, int y) {
super( Param.BARRACK_LV,Param.BARRACK_AP, Param.BARRACK_AR, x, y);
}

public GameObject traing(EnumObjectType type) {
if (type == EnumObjectType.rifleSoldier) {
return new RifleSoldier(this.getPosX(), this.getPosY());
}else if (type == EnumObjectType.RPGSoldier) {
return new RPGSoldier(this.getPosX(), this.getPosY());
}else if (type == EnumObjectType.dog) {
return new Dog(this.getPosX(), this.getPosY());
}
return null;
}

}

军工厂可建造重坦和轻坦,它们的坐标就是军工厂的坐标(传参):

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.h06;

/**
* @author super
*/
public class WarFactory extends Building {

public WarFactory(int x, int y) {
super(Param.WAR_FACTORY_LV, Param.WAR_FACTORY_AP, Param.WAR_FACTORY_AR, x, y);
}

public Tank building(EnumObjectType type) {

if (type == EnumObjectType.mediumTank) {
return new MediumTank(this.getPosX(), this.getPosY());
} else if (type == EnumObjectType.heavyTank) {
return new HeavyTank(this.getPosX(), this.getPosY());
}
return null;

}
}

0x06 Soldier

士兵类,继承 GameObject,不被创建,子类包括步枪兵和火箭兵。但步枪兵和火箭兵的创建不通过 Soldier 而是通过 Barrack

最重要的就是记录生存和死亡的士兵数量,静态变量。重写 dead 以当士兵死亡时改变生存和死亡数量。

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.h06;

/**
* @author super
*/
public abstract class Soldier extends GameObject {

private static int livingSoldierCount=0;
private static int deadedSoldierCount=0;

public Soldier(int lv,int ap, double ar, int x, int y) {
super(lv, ap, ar, x, y);
livingSoldierCount++;
}
public static int getLivingSoldierCount() {
return livingSoldierCount;
}
public static int getDeadedSoldierCount() {
return deadedSoldierCount;
}

@Override
public void dead() {
livingSoldierCount--;
deadedSoldierCount++;
}


}

0x07 Tank

坦克类,继承 GameObject,不被创建,子类包括重坦和轻坦。但重坦和轻坦的创建不通过 Tank 而是通过 WarFactory

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

/**
* @author super
*/
public class Tank extends GameObject {

public Tank(int lv, int ap, double ar, int x, int y) {
super(lv, ap, ar, x, y);
}

}

0x08 RifleSoldier&RPGSoldier&Dog

步枪兵类,继承 Soldier,由 Barrack 创建,构造方法传参坐标(创建时),调用父类构造方法(继承)。需要重写 getAttackPower 针对 Dog 类:

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

/**
* @author super
*/
public class RifleSoldier extends Soldier {

public RifleSoldier(int x, int y) {
super(Param.SOLDIER_LV, Param.SOLDIER_RIFLE_AP, Param.SOLDIER_RIFLE_AR, x, y);
}

@Override
public int getAttackPower(GameObject obj) {
if(obj instanceof Dog) {
return 1000;
}
return -1 ;
}

}

火箭兵类,继承 Soldier,由 Barrack 创建,构造方法传参坐标(创建时),调用父类构造方法(继承):

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

/**
* @author super
*/
public class RPGSoldier extends Soldier {

public RPGSoldier(int x, int y) {
super(Param.SOLDIER_LV, Param.SOLDIER_RPG_AP, Param.SOLDIER_RPG_AR, x, y);
}

}

军犬类,继承 GameBase,由 Barrack 创建,构造方法传参坐标(创建时),调用父类构造方法(继承)。需要重写 getAttackPower 针对 Soldier 类:

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

/**
* @author super
*/
public class Dog extends GameObject {

public Dog(int x, int y) {
super(Param.DOG_LV, Param.DOG_AP, Param.DOG_AR, x, y);
}

@Override
public int getAttackPower(GameObject obj) {
if(obj instanceof Soldier) {
return 1000;
}
return -1;
}

}

0x09 HeavyTank&MediumTank

重坦和轻坦类,继承 Tank,由 WarFactory 创建,构造方法传参坐标(创建时),调用父类构造方法(继承):

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

/**
* @author super
*/
public class HeavyTank extends Tank {

public HeavyTank(int x, int y) {
super(Param.TANK_HEAVY_LV, Param.TANK_HEAVY_AP, Param.TANK_AR, x, y);
}


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

/**
* @author super
*/
public class MediumTank extends Tank {

public MediumTank(int x, int y) {
super(Param.TANK_MEDIUM_LV, Param.TANK_MEDIUM_AP, Param.TANK_AR, x, y);
}

}

0x0A 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
102
103
package com.huawei.classroom.student.h06;

public class Test {

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

public static void main(String[] args) {
// TODO Auto-generated method stub
//某一款游戏,其主要角色如下:
//游戏中每个对象有当前 x,y坐标,坐标值 取值范围为整数。
//非建筑物可以通过move(dx,dy)来移动坐标值,dx,dy表示x轴,y轴增量, 取值范围为整数。
//对象A攻击B的时候,要满足两个对象之间直线距离小于等于A的攻击范围,否则攻击无效(被攻击方不减健康值)
//任何对象有getHealth() 方法,返回当前生命值,如果已经死亡则返回 <=0的一个数字
//任何对象有isDestroyed() 方法,如果生命值<=0则true,否则false

//GameBase 玩家基地 初始生命值500,无攻击力,初始x,y值在创建时指定
//HeavyTank 重型坦克 初始生命值200,攻击力 20,攻击范围10,初始x,y值就是兵工厂的x,y
//Medium Tank 轻型坦克 初始生命值100,攻击力 10,攻击范围10,初始x,y值就是兵工厂的x,y
//War Factroy 兵工厂 初始生命值100,无攻击力 ,初始x,y值在创建时指定
//Barrack 兵营,可以训练出步枪兵、 RPG兵、军犬,初始生命值100,无攻击力,初始x,y值在创建时指定
//Rifle soldier 步枪兵 初始生命值50(对战 军犬除外),攻击力 5(对战军犬可以一次击毙军犬),攻击范围5,初始x,y值就是兵营的x,y
//Rocket soldier 火箭兵 初始生命值50(对战 军犬除外),攻击力 10 ,攻击范围10,初始x,y值就是兵营的x,y
//Dog 军犬 ,初始生命值50,攻击力5(对战人类时候一口毙命),攻击范围5,初始x,y值就是兵营的x,y
//此外还要能通过Soldier.getLivingSoldierCount/getDeadedSoldierCount 统计现在有多少个活着的和死去的士兵数量
//请遵循以上游戏规则,并根据如下测试代码设计代码
//作业批改的时候,方法可能不同组合
//例如让一个物体移动,超出或者进入攻击范围,判断是否攻击有效
//让一个物体制造出来以后,不移动,然后另一个物体攻击,判断该物体是否使用了默认的x,y

//未来可能分玩家,例如玩家A,玩家B,玩家C
//未来可能玩家A战斗单元不能攻击自己的战斗单元
//未来玩家之间可能要结盟、取消结盟
//未来坦克攻击以后,造成的杀伤可能不是点杀伤,而是一个杀伤范围
//....



GameBase gameBase=GameBase.createGameBase(10,10);
if(gameBase.getHealth()==500) {
System.out.println("ok1");
}
Barrack barrack=(Barrack)gameBase.building(EnumObjectType.barrack, 20, 20) ;
if(barrack.getHealth()==100) {
System.out.println("ok2");
}
//traing 训练出新的士兵或者狗
RifleSoldier rifleSoldier1=(RifleSoldier)barrack.traing(EnumObjectType.rifleSoldier);
if(rifleSoldier1.getHealth()==50) {
System.out.println("ok3");
}
RPGSoldier rPGSoldier1=(RPGSoldier)barrack.traing(EnumObjectType.RPGSoldier );
if(rPGSoldier1.getHealth()==50) {
System.out.println("ok4");
}
Dog dog1=(Dog)barrack.traing(EnumObjectType.dog );
if(dog1.getHealth()==50) {
System.out.println("ok5");
}
//构造新的兵工厂
WarFactory warFactory=(WarFactory)gameBase.building(EnumObjectType.warFactory, 30, 30) ;
if(warFactory.getHealth()==100) {
System.out.println("ok6");
}
//building 建造各自型号坦克
Tank mediumTank1=(MediumTank)warFactory.building(EnumObjectType.mediumTank);
if(mediumTank1.getHealth()==100) {
System.out.println("ok7");
}

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

//移动的是坐标值增量
heavyTank1.move(10, 10);
rifleSoldier1.move(5, 5);
dog1.move(0, 0);
mediumTank1.attack( heavyTank1);

//攻击无效,距离太远,health
if(heavyTank1.getHealth()==200){
System.out.println("ok9");
}

mediumTank1.attack(rifleSoldier1);
if(rifleSoldier1.getHealth()==40 ) {
System.out.println("ok10");
}
mediumTank1.attack(rifleSoldier1);
if(rifleSoldier1.getHealth()==30 ) {
System.out.println("ok12");
}
if( Soldier.getLivingSoldierCount()==2 ) {
System.out.println("ok11");
}


}

}