Skip to content

Commit 3cbd32b

Browse files
committed
feat: slightly better serializer for unit actions and state, added attack action too.
1 parent 1b6e721 commit 3cbd32b

14 files changed

+854
-303
lines changed

megamek/src/megamek/ai/dataset/ActionAndState.java

-40
This file was deleted.

megamek/src/megamek/ai/dataset/TsvSerde.java megamek/src/megamek/ai/dataset/TabSeparatedValueSerializer.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,28 @@
2727
*/
2828
package megamek.ai.dataset;
2929

30+
import java.text.DecimalFormat;
31+
import java.text.DecimalFormatSymbols;
32+
import java.util.Locale;
33+
3034
/**
31-
* <p>Abstract class to serialize/deserialize objects to/from TSV format.</p>
32-
* <p>It does not have a fromTsv function because I could not find a way to make a good API for it.</p>
33-
* @param <T> type of object to serialize/deserialize
35+
* Abstract class to serialize objects to TSV format.
3436
* @author Luana Coppio
3537
*/
36-
public abstract class TsvSerde<T> {
38+
public abstract class TabSeparatedValueSerializer<T> {
39+
40+
protected final DecimalFormat LOG_DECIMAL = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.US));
3741

3842
/**
3943
* Serializes an object to TSV format.
4044
* @param obj object to serialize
41-
* @return the object serialized in TSV format
45+
* @return the serialized string formatted as a tab separated value entry
4246
*/
43-
public abstract String toTsv(T obj);
47+
public abstract String serialize(T obj);
4448

4549
/**
4650
* Returns the header line for the TSV format.
47-
* @return the header line
51+
* @return the header line for the tab separated value entry
4852
*/
4953
public abstract String getHeaderLine();
5054
}

megamek/src/megamek/ai/dataset/UnitAction.java

+71-62
Original file line numberDiff line numberDiff line change
@@ -27,80 +27,89 @@
2727
*/
2828
package megamek.ai.dataset;
2929

30+
import java.util.List;
31+
import java.util.stream.Stream;
32+
33+
import megamek.ai.utility.EntityFeatureUtils;
3034
import megamek.client.ui.SharedUtility;
31-
import megamek.common.Coords;
35+
import megamek.common.Compute;
3236
import megamek.common.Entity;
37+
import megamek.common.IAero;
3338
import megamek.common.MovePath;
3439
import megamek.common.MoveStep;
35-
36-
import java.util.List;
40+
import megamek.common.UnitRole;
3741

3842
/**
39-
* Represents a unit action.
40-
* @param id unit id
41-
* @param facing facing direction
42-
* @param fromX x coordinate of the starting position
43-
* @param fromY y coordinate of the starting position
44-
* @param toX x coordinate of the final position
45-
* @param toY y coordinate of the final position
46-
* @param hexesMoved number of hexes moved
47-
* @param distance distance moved
48-
* @param mpUsed movement points used
49-
* @param maxMp maximum movement points
50-
* @param mpP movement points percentage
51-
* @param heatP heat percentage
52-
* @param armorP armor percentage
53-
* @param internalP internal percentage
54-
* @param jumping jumping
55-
* @param prone prone
56-
* @param legal is move legal
43+
* UnitAction is a record that represents the action of a unit in the game.
5744
* @author Luana Coppio
5845
*/
59-
public record UnitAction(int id, int teamId, int playerId, String chassis, String model, int facing, int fromX, int fromY, int toX, int toY, int hexesMoved, int distance, int mpUsed,
60-
int maxMp, double mpP, double heatP, double armorP, double internalP, boolean jumping, boolean prone,
61-
boolean legal, double chanceOfFailure, List<MovePath.MoveStepType> steps, boolean bot) {
46+
public record UnitAction(int id, int teamId, int playerId, String chassis, String model, int facing,
47+
int fromX, int fromY, int toX, int toY, int hexesMoved, int distance,
48+
int mpUsed, int maxMp, double mpP, double heatP, double armorP, double internalP,
49+
boolean jumping, boolean prone, boolean legal, double chanceOfFailure,
50+
List<MovePath.MoveStepType> steps, boolean bot,
51+
boolean hasEcm, int armor, int internal, int bv, int maxRange, int totalDamage,
52+
float armorFrontP, float armorLeftP, float armorRightP, float armorBackP, UnitRole role,
53+
List<Integer> weaponDamageFacingShortMediumLongRange) {
6254

63-
public static UnitAction fromMovePath(MovePath movePath) {
55+
/**
56+
* Creates a UnitAction from a MovePath.
57+
* @param movePath
58+
* @return
59+
*/
60+
public static UnitAction fromMovePath(MovePath movePath) {
6461
Entity entity = movePath.getEntity();
65-
double chanceOfFailure = SharedUtility.getPSRList(movePath).stream().map(psr -> psr.getValue() / 36d).reduce(0.0, (a, b) -> a * b);
62+
double chanceOfFailure = SharedUtility.getPSRList(movePath).stream()
63+
.map(psr -> psr.getValue() / 36d)
64+
.reduce(0.0, (a, b) -> a * b);
65+
6666
var steps = movePath.getStepVector().stream().map(MoveStep::getType).toList();
67+
6768
return new UnitAction(
68-
entity.getId(),
69-
entity.getOwner() != null ? entity.getOwner().getTeam() : -1,
70-
entity.getOwner() != null ? entity.getOwner().getId() : -1,
71-
entity.getChassis(),
72-
entity.getModel(),
73-
movePath.getFinalFacing(),
74-
movePath.getStartCoords() != null ? movePath.getStartCoords().getX() : -1,
75-
movePath.getStartCoords() != null ? movePath.getStartCoords().getY() : -1,
76-
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getX() : -1,
77-
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getY() : -1,
78-
movePath.getHexesMoved(),
79-
movePath.getDistanceTravelled(),
80-
movePath.getMpUsed(),
81-
movePath.getMaxMP(),
82-
movePath.getMaxMP() > 0 ? (double) movePath.getMpUsed() / movePath.getMaxMP() : 0.0,
83-
entity.getHeatCapacity() > 0 ? entity.getHeat() / (double) entity.getHeatCapacity() : 0.0,
84-
entity.getArmorRemainingPercent(),
85-
entity.getInternalRemainingPercent(),
86-
movePath.isJumping(),
87-
movePath.getFinalProne(),
88-
movePath.isMoveLegal(),
89-
chanceOfFailure,
90-
steps,
91-
entity.getOwner().isBot()
69+
entity.getId(),
70+
entity.getOwner() != null ? entity.getOwner().getTeam() : -1,
71+
entity.getOwner() != null ? entity.getOwner().getId() : -1,
72+
entity.getChassis(),
73+
entity.getModel(),
74+
movePath.getFinalFacing(),
75+
movePath.getStartCoords() != null ? movePath.getStartCoords().getX() : -1,
76+
movePath.getStartCoords() != null ? movePath.getStartCoords().getY() : -1,
77+
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getX() : -1,
78+
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getY() : -1,
79+
movePath.getHexesMoved(),
80+
movePath.getDistanceTravelled(),
81+
movePath.getMpUsed(),
82+
movePath.getMaxMP(),
83+
movePath.getMaxMP() > 0 ? (double) movePath.getMpUsed() / movePath.getMaxMP() : 0.0,
84+
entity.getHeatCapacity() > 0 ? entity.getHeat() / (double) entity.getHeatCapacity() : 0.0,
85+
entity.getArmorRemainingPercent(),
86+
entity.getInternalRemainingPercent(),
87+
movePath.isJumping(),
88+
movePath.getFinalProne(),
89+
movePath.isMoveLegal(),
90+
chanceOfFailure,
91+
steps,
92+
entity.getOwner().isBot(),
93+
entity.hasActiveECM(),
94+
entity.getTotalArmor(),
95+
entity instanceof IAero aero ? aero.getSI() : entity.getTotalInternal(),
96+
entity.getInitialBV(),
97+
entity.getMaxWeaponRange(),
98+
Compute.computeTotalDamage(entity.getWeaponList()),
99+
EntityFeatureUtils.getTargetFrontHealthStats(entity),
100+
EntityFeatureUtils.getTargetLeftSideHealthStats(entity),
101+
EntityFeatureUtils.getTargetRightSideHealthStats(entity),
102+
EntityFeatureUtils.getTargetBackHealthStats(entity),
103+
entity.getRole(),
104+
entity.getWeaponList().stream().flatMap(weapon -> {
105+
int damage = Compute.computeTotalDamage(weapon);
106+
int facing = weapon.isRearMounted() ? -entity.getWeaponArc(weapon.getLocation()) :
107+
entity.getWeaponArc(weapon.getLocation());
108+
int shortRange = weapon.getType().getShortRange();
109+
int mediumRange = weapon.getType().getMediumRange();
110+
int longRange = weapon.getType().getLongRange();
111+
return Stream.of(damage, facing, shortRange, mediumRange, longRange);
112+
}).toList()
92113
);
93114
}
94-
95-
public Coords currentPosition() {
96-
return new Coords(fromX, fromY);
97-
}
98-
99-
public boolean isHuman() {
100-
return !bot;
101-
}
102-
103-
public Coords finalPosition() {
104-
return new Coords(toX, toY);
105-
}
106115
}

megamek/src/megamek/ai/dataset/UnitActionField.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,20 @@ public enum UnitActionField {
5454
STEPS("STEPS"),
5555
TEAM_ID("TEAM_ID"),
5656
CHANCE_OF_FAILURE("CHANCE_OF_FAILURE"),
57-
IS_BOT("IS_BOT");
57+
IS_BOT("IS_BOT"),
58+
HAS_ECM("HAS_ECM"),
59+
ARMOR("ARMOR"),
60+
INTERNAL("INTERNAL"),
61+
BV("BV"),
62+
MAX_RANGE("MAX_RANGE"),
63+
ARMOR_FRONT_P("ARMOR_FRONT_P"),
64+
ARMOR_LEFT_P("ARMOR_LEFT_P"),
65+
ARMOR_RIGHT_P("ARMOR_RIGHT_P"),
66+
ARMOR_BACK_P("ARMOR_BACK_P"),
67+
TOTAL_DAMAGE("TOTAL_DAMAGE"),
68+
ROLE("ROLE"),
69+
WEAPON_DMG_FACING_SHORT_MEDIUM_LONG_RANGE("WEAPON_DMG_FACING_SHORT_MEDIUM_LONG_RANGE"),
70+
;
5871

5972
private final String headerName;
6073

@@ -71,18 +84,18 @@ public String getHeaderName() {
7184
*/
7285
public static String getHeaderLine() {
7386
return Arrays.stream(values())
74-
.map(UnitActionField::getHeaderName)
75-
.collect(Collectors.joining("\t"));
87+
.map(UnitActionField::getHeaderName)
88+
.collect(Collectors.joining("\t"));
7689
}
7790

7891
/**
7992
* Builds the TSV header line (joined by tabs) by iterating over all enum constants.
8093
*/
8194
public static String getPartialHeaderLine(int startsAt, int endsAt) {
8295
return Arrays.stream(values())
83-
.skip(startsAt)
84-
.limit(endsAt - startsAt)
85-
.map(UnitActionField::getHeaderName)
86-
.collect(Collectors.joining("\t"));
96+
.skip(startsAt)
97+
.limit(endsAt - startsAt)
98+
.map(UnitActionField::getHeaderName)
99+
.collect(Collectors.joining("\t"));
87100
}
88101
}

megamek/src/megamek/ai/dataset/UnitActionSerde.java megamek/src/megamek/ai/dataset/UnitActionSerializer.java

+21-15
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,18 @@
2727
*/
2828
package megamek.ai.dataset;
2929

30-
import megamek.common.MovePath;
31-
32-
import java.text.DecimalFormat;
33-
import java.text.DecimalFormatSymbols;
34-
import java.util.Arrays;
35-
import java.util.List;
3630
import java.util.stream.Collectors;
3731

32+
import megamek.common.UnitRole;
33+
3834
/**
3935
* <p>serializer and deserializer for UnitAction to/from TSV format.</p>
4036
* @author Luana Coppio
4137
*/
42-
public class UnitActionSerde extends TsvSerde<UnitAction> {
43-
44-
private final DecimalFormat LOG_DECIMAL =
45-
new DecimalFormat("0.00", DecimalFormatSymbols.getInstance());
38+
public class UnitActionSerializer extends TabSeparatedValueSerializer<UnitAction> {
4639

4740
@Override
48-
public String toTsv(UnitAction obj) {
41+
public String serialize(UnitAction obj) {
4942
String[] row = new String[UnitActionField.values().length];
5043
row[UnitActionField.ENTITY_ID.ordinal()] = String.valueOf(obj.id());
5144
row[UnitActionField.PLAYER_ID.ordinal()] = String.valueOf(obj.playerId());
@@ -70,11 +63,24 @@ public String toTsv(UnitAction obj) {
7063
row[UnitActionField.LEGAL.ordinal()] = obj.legal() ? "1" : "0";
7164
row[UnitActionField.CHANCE_OF_FAILURE.ordinal()] = LOG_DECIMAL.format(obj.chanceOfFailure());
7265
row[UnitActionField.IS_BOT.ordinal()] = obj.bot() ? "1" : "0";
73-
74-
// For STEPS, join the list of MoveStepType values with a space.
7566
row[UnitActionField.STEPS.ordinal()] = obj.steps().stream()
76-
.map(Enum::name)
77-
.collect(Collectors.joining(" "));
67+
.map(Enum::name)
68+
.collect(Collectors.joining(" "));
69+
row[UnitActionField.HAS_ECM.ordinal()] = obj.hasEcm() ? "1" : "0";
70+
row[UnitActionField.ARMOR.ordinal()] = String.valueOf(obj.armor());
71+
row[UnitActionField.INTERNAL.ordinal()] = String.valueOf(obj.internal());
72+
row[UnitActionField.BV.ordinal()] = String.valueOf(obj.bv());
73+
row[UnitActionField.MAX_RANGE.ordinal()] = String.valueOf(obj.maxRange());
74+
row[UnitActionField.TOTAL_DAMAGE.ordinal()] = String.valueOf(obj.totalDamage());
75+
row[UnitActionField.ARMOR_FRONT_P.ordinal()] = LOG_DECIMAL.format(obj.armorFrontP());
76+
row[UnitActionField.ARMOR_LEFT_P.ordinal()] = LOG_DECIMAL.format(obj.armorLeftP());
77+
row[UnitActionField.ARMOR_RIGHT_P.ordinal()] = LOG_DECIMAL.format(obj.armorRightP());
78+
row[UnitActionField.ARMOR_BACK_P.ordinal()] = LOG_DECIMAL.format(obj.armorBackP());
79+
row[UnitStateField.ROLE.ordinal()] = obj.role() == null ? UnitRole.NONE.name() : obj.role().name();
80+
row[UnitActionField.WEAPON_DMG_FACING_SHORT_MEDIUM_LONG_RANGE.ordinal()] =
81+
obj.weaponDamageFacingShortMediumLongRange().stream()
82+
.map(Object::toString)
83+
.collect(Collectors.joining(" "));
7884

7985
return String.join("\t", row);
8086
}

0 commit comments

Comments
 (0)