Skip to content

Feat/caspar ai #6820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
446e83e
feat: added the default FFNN model trained for tests and the tensorfl…
Scoppio Apr 3, 2025
2b181ef
feat: added clamp01 to mathutility
Scoppio Apr 3, 2025
1717dec
feat: cube coords expanded, added pathing interface and unit path
Scoppio Apr 3, 2025
8ff7743
feat: updated serde for game action data
Scoppio Apr 3, 2025
4d1c6eb
feat: functional model testing and FFNN loading up correctly
Scoppio Apr 3, 2025
6b1b2e2
feat: improvements in the ergonomics of the NN class
Scoppio Apr 3, 2025
6e29575
chore: input normalization now load itself, making the NN load proces…
Scoppio Apr 3, 2025
0f64812
feat: adding test-eval cases, but they are failing 39 percent of the …
Scoppio Apr 3, 2025
512b9db
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 3, 2025
4cf8825
feat: added tests for evaluating if the model is working, loading, etc.
Scoppio Apr 5, 2025
a6468a1
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 5, 2025
9db1413
chore: refactor the test setup, moving inside the Brain all the load …
Scoppio Apr 5, 2025
fab4781
chore: removed testNN and moved the brain test setup back to the test…
Scoppio Apr 5, 2025
3898ac6
feat: put Pathing as interface to MovePath
Scoppio Apr 5, 2025
1d55840
Merge branch 'MegaMek:master' into feat/caspar-ai
Scoppio Apr 6, 2025
d7f4627
fix: update float and int for test data
Scoppio Apr 6, 2025
971ca3b
fix: fixed merge conflicts
Scoppio Apr 25, 2025
8020041
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 26, 2025
0fd5582
fix: removed UnitPath
Scoppio Apr 26, 2025
5b009af
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 27, 2025
ac8b0fc
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 27, 2025
31158e5
feat: small improvements
Scoppio Apr 27, 2025
692aebb
Merge branch 'master' into feat/caspar-ai
Scoppio Apr 28, 2025
3f00f6f
Merge branch 'master' into feat/caspar-ai
Scoppio May 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ java {

allprojects {
repositories {
google()
mavenCentral()
}
}
Expand Down
8 changes: 5 additions & 3 deletions megamek/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ dependencies {

implementation 'com.ibm.icu:icu4j:77.1'

implementation 'org.tensorflow:tensorflow-core-platform:1.0.0'

runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:4.0.5'

testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.1'
Expand Down Expand Up @@ -478,13 +480,13 @@ tasks.register('unitFileMigrationTool', JavaExec) {
mainClass = 'megamek.utilities.UnitFileMigrationTool'
}

tasks.register('testAi', JavaExec) {
tasks.register('runAiGame', JavaExec) {
dependsOn jar
description = 'Test AI'
description = 'Start a game from a save and load all players as AIss'
group = 'utility'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'megamek.utilities.QuickGameRunner'
args(project.hasProperty("testAiArgs") ? project.property("testAiArgs").split(' ') : "")
args(project.hasProperty("testAiArgs") ? project.property("testAiArgs").split(" ") : "")

jvmArgs = mmJvmOptions
}
1 change: 1 addition & 0 deletions megamek/data/ai/brains/default/fingerprint.pb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ñž¬¥ÕðŒº¥Š…ƒ®¿•Œ©f‘€¨›ˆ”¸Ó¶ ô÷Ü¿æô£‰L(̹ŽÇ¸ÍýÔ2
31 changes: 31 additions & 0 deletions megamek/data/ai/brains/default/keras_metadata.pb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
feature,min,max
0,0.0,3.0
1,0.0,1.7
2,-1.0,1.0
3,0.48,1.0
4,0.0,1.0
5,0.0,58.52349955359813
6,0.0,64.0
7,0.0,1.0
8,0.0,1.0
9,0.0,12.0
10,0.0,17.0
11,0.0,1.0
12,-1.0,293.0
13,-0.16666666666666666,48.833333333333336
14,-0.16666666666666666,48.833333333333336
15,-0.1,0.1
16,0.0,84.0
17,0.3333333333333333,1.0
18,0.0,1.0
19,0.0,800.7000000000048
20,0.0,27.0
21,0.0,0.8625
22,0.0,1.0
23,0.0,0.99
24,0.0,1.0
25,0.36,0.76
26,0.0,6.0
27,0.0,1.0
28,0.0,73.40980860893181
29,0.0,1.0
30,0.0,1.0
31,-7.5103256576259945,17.2
32,0.0,0.7272727272727273
33,0.0,2.5298129391644855
34,0.0,1.516082016182458
35,0.0,0.8571428571428571
36,0.0,0.711111111111111
37,0.0,1.0
38,0.0,0.9176470588235294
39,0.0,1.0
40,0.42293749999999997,0.6427499999999999
41,0.0,459.0
42,0.0,1.0
43,0.0,46.87216658103186
44,0.0,27.0
45,-1.0,25.0
46,-1.0,97.0
47,0.0,1.0
48,0.0,1.0
49,0.0,1.0
50,0.0,1.0
51,0.0,1.0
52,0.0,1.0
53,0.0,1.0
54,0.0,1.0
Binary file added megamek/data/ai/brains/default/saved_model.pb
Binary file not shown.
300 changes: 300 additions & 0 deletions megamek/data/ai/brains/default/test_inputs.csv

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
9 changes: 8 additions & 1 deletion megamek/src/megamek/ai/dataset/ActionAndState.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
package megamek.ai.dataset;


import megamek.common.Entity;

import java.util.List;

/**
Expand All @@ -37,4 +39,9 @@
* @param boardUnitState state of the board when the action is performed
* @author Luana Coppio
*/
public record ActionAndState(int round, UnitAction unitAction, List<UnitState> boardUnitState){}
public record ActionAndState(int round, UnitAction unitAction, List<UnitState> boardUnitState) {
public Entity getEntity() {
var unitStateOpt = boardUnitState.stream().filter(unitState -> unitState.id() == unitAction.id()).findFirst();
return unitStateOpt.map(UnitState::entity).orElse(null);
}
}
67 changes: 41 additions & 26 deletions megamek/src/megamek/ai/dataset/UnitAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
*/
package megamek.ai.dataset;

import megamek.common.Pathing;
import megamek.client.ui.SharedUtility;
import megamek.common.Coords;
import megamek.common.CubeCoords;
import megamek.common.Entity;
import megamek.common.Game;
import megamek.common.MovePath;
import megamek.common.MoveStep;

Expand Down Expand Up @@ -57,50 +60,62 @@
* @author Luana Coppio
*/
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,
int maxMp, double mpP, double heatP, double armorP, double internalP, boolean jumping, boolean prone,
boolean legal, double chanceOfFailure, List<MovePath.MoveStepType> steps, boolean bot) {
int maxMp, double mpP, double heatP, double armorP, double internalP, boolean jumping, boolean prone,
boolean legal, double chanceOfFailure, List<MovePath.MoveStepType> steps, boolean bot) {

public static UnitAction fromMovePath(MovePath movePath) {
Entity entity = movePath.getEntity();
double chanceOfFailure = SharedUtility.getPSRList(movePath).stream().map(psr -> psr.getValue() / 36d).reduce(0.0, (a, b) -> a * b);
var steps = movePath.getStepVector().stream().map(MoveStep::getType).toList();
return new UnitAction(
entity.getId(),
entity.getOwner() != null ? entity.getOwner().getTeam() : -1,
entity.getOwner() != null ? entity.getOwner().getId() : -1,
entity.getChassis(),
entity.getModel(),
movePath.getFinalFacing(),
movePath.getStartCoords() != null ? movePath.getStartCoords().getX() : -1,
movePath.getStartCoords() != null ? movePath.getStartCoords().getY() : -1,
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getX() : -1,
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getY() : -1,
movePath.getHexesMoved(),
movePath.getDistanceTravelled(),
movePath.getMpUsed(),
movePath.getMaxMP(),
movePath.getMaxMP() > 0 ? (double) movePath.getMpUsed() / movePath.getMaxMP() : 0.0,
entity.getHeatCapacity() > 0 ? entity.getHeat() / (double) entity.getHeatCapacity() : 0.0,
entity.getArmorRemainingPercent(),
entity.getInternalRemainingPercent(),
movePath.isJumping(),
movePath.getFinalProne(),
movePath.isMoveLegal(),
chanceOfFailure,
steps,
entity.getOwner().isBot()
entity.getId(),
entity.getOwner() != null ? entity.getOwner().getTeam() : -1,
entity.getOwner() != null ? entity.getOwner().getId() : -1,
entity.getChassis(),
entity.getModel(),
movePath.getFinalFacing(),
movePath.getStartCoords() != null ? movePath.getStartCoords().getX() : -1,
movePath.getStartCoords() != null ? movePath.getStartCoords().getY() : -1,
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getX() : -1,
movePath.getFinalCoords() != null ? movePath.getFinalCoords().getY() : -1,
movePath.getHexesMoved(),
movePath.getDistanceTravelled(),
movePath.getMpUsed(),
movePath.getMaxMP(),
movePath.getMaxMP() > 0 ? (double) movePath.getMpUsed() / movePath.getMaxMP() : 0.0,
entity.getHeatCapacity() > 0 ? entity.getHeat() / (double) entity.getHeatCapacity() : 0.0,
entity.getArmorRemainingPercent(),
entity.getInternalRemainingPercent(),
movePath.isJumping(),
movePath.getFinalProne(),
movePath.isMoveLegal(),
chanceOfFailure,
steps,
entity.getOwner().isBot()
);
}

public Coords currentPosition() {
return new Coords(fromX, fromY);
}

public CubeCoords currentCubePosition() {
return currentPosition().toCube();
}

public CubeCoords finalCubePosition() {
return finalPosition().toCube();
}

public boolean isHuman() {
return !bot;
}

public Coords finalPosition() {
return new Coords(toX, toY);
}

public Pathing movePath(Entity entity) {
return new UnitPath(this, entity);
}
}
12 changes: 6 additions & 6 deletions megamek/src/megamek/ai/dataset/UnitActionField.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ public String getHeaderName() {
*/
public static String getHeaderLine() {
return Arrays.stream(values())
.map(UnitActionField::getHeaderName)
.collect(Collectors.joining("\t"));
.map(UnitActionField::getHeaderName)
.collect(Collectors.joining("\t"));
}

/**
* Builds the TSV header line (joined by tabs) by iterating over all enum constants.
*/
public static String getPartialHeaderLine(int startsAt, int endsAt) {
return Arrays.stream(values())
.skip(startsAt)
.limit(endsAt - startsAt)
.map(UnitActionField::getHeaderName)
.collect(Collectors.joining("\t"));
.skip(startsAt)
.limit(endsAt - startsAt)
.map(UnitActionField::getHeaderName)
.collect(Collectors.joining("\t"));
}
}
10 changes: 3 additions & 7 deletions megamek/src/megamek/ai/dataset/UnitActionSerde.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,8 @@
*/
package megamek.ai.dataset;

import megamek.common.MovePath;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
Expand All @@ -42,7 +38,7 @@
public class UnitActionSerde extends TsvSerde<UnitAction> {

private final DecimalFormat LOG_DECIMAL =
new DecimalFormat("0.00", DecimalFormatSymbols.getInstance());
new DecimalFormat("0.00", DecimalFormatSymbols.getInstance());

@Override
public String toTsv(UnitAction obj) {
Expand Down Expand Up @@ -73,8 +69,8 @@ public String toTsv(UnitAction obj) {

// For STEPS, join the list of MoveStepType values with a space.
row[UnitActionField.STEPS.ordinal()] = obj.steps().stream()
.map(Enum::name)
.collect(Collectors.joining(" "));
.map(Enum::name)
.collect(Collectors.joining(" "));

return String.join("\t", row);
}
Expand Down
112 changes: 112 additions & 0 deletions megamek/src/megamek/ai/dataset/UnitPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (C) 2025 The MegaMek Team. All Rights Reserved.
*
* This file is part of MegaMek.
*
* MegaMek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPL),
* version 2 or (at your option) any later version,
* as published by the Free Software Foundation.
*
* MegaMek is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* A copy of the GPL should have been included with this project;
* if not, see <https://www.gnu.org/licenses/>.
*
* NOTICE: The MegaMek organization is a non-profit group of volunteers
* creating free software for the BattleTech community.
*
* MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks
* of The Topps Company, Inc. All Rights Reserved.
*
* Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of
* InMediaRes Productions, LLC.
*/
package megamek.ai.dataset;

import megamek.common.Pathing;
import megamek.common.Coords;
import megamek.common.CubeCoords;
import megamek.common.Entity;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class UnitPath implements Pathing {

private final UnitAction unitAction;
private final Entity entity;
private final Set<Coords> coords;

public UnitPath(UnitAction unitAction, Entity entity) {
this.unitAction = unitAction;
this.entity = entity;
this.coords = new HashSet<>();
List<CubeCoords> cubeCoordsLine = unitAction.currentCubePosition().lineTo(unitAction.finalCubePosition());
cubeCoordsLine.forEach(c -> coords.add(c.toOffset()));
}

@Override
public Coords getStartCoords() {
return unitAction.currentPosition();
}

@Override
public Coords getFinalCoords() {
return unitAction.finalPosition();
}

@Override
public int getFinalFacing() {
return unitAction.facing();
}

@Override
public Entity getEntity() {
return entity;
}

@Override
public Set<Coords> getCoordsSet() {
return coords;
}

@Override
public int getHexesMoved() {
return unitAction.hexesMoved();
}

@Override
public int getDistanceTravelled() {
return unitAction.distance();
}

@Override
public boolean hasWaypoint() {
return false;
}

@Override
public Coords getWaypoint() {
return null;
}

@Override
public boolean isJumping() {
return unitAction.jumping();
}

@Override
public boolean getFinalProne() {
return unitAction.prone();
}

@Override
public int getMpUsed() {
return unitAction.mpUsed();
}
}
Loading