Skip to content

Commit

Permalink
fix: force princess to go for a walk
Browse files Browse the repository at this point in the history
  • Loading branch information
Scoppio committed Jan 22, 2025
1 parent 9724539 commit 32be242
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 97 deletions.
109 changes: 71 additions & 38 deletions megamek/src/megamek/client/bot/princess/BasicPathRanker.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,21 @@
*/
package megamek.client.bot.princess;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import megamek.client.bot.princess.BotGeometry.ConvexBoardArea;
import megamek.client.bot.princess.BotGeometry.CoordFacingCombo;
import megamek.client.bot.princess.BotGeometry.HexLine;
import megamek.client.bot.princess.UnitBehavior.BehaviorType;
import megamek.codeUtilities.MathUtility;
import megamek.common.*;
import megamek.common.options.OptionsConstants;
import megamek.common.planetaryconditions.PlanetaryConditions;
import megamek.logging.MMLogger;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.*;

/**
* A very "basic" path ranker
*/
Expand All @@ -54,6 +49,8 @@ public class BasicPathRanker extends PathRanker {
// what it's doing
private final int UNIT_DESTRUCTION_FACTOR = 1000;

private final static int MOVEMENT_FACTOR = 5;

protected final DecimalFormat LOG_DECIMAL = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance());
private final NumberFormat LOG_INT = NumberFormat.getIntegerInstance();
protected final NumberFormat LOG_PERCENT = NumberFormat.getPercentInstance();
Expand Down Expand Up @@ -387,23 +384,27 @@ protected double calculateAggressionMod(Entity movingUnit, MovePath path, Game g
return aggressionMod;
}

// The further I am from my teammates, the lower this path ranks (weighted by
// Herd Mentality).
// Lower this path ranking if I am moving away from my friends (weighted by Herd Mentality).
protected double calculateHerdingMod(Coords friendsCoords, MovePath path, StringBuilder formula) {
if (friendsCoords == null) {
formula.append(" - herdingMod [0 no friends]");
formula.append(" + herdingMod [0 no friends]");
return 0;
}

double distanceToAllies = friendsCoords.distance(path.getFinalCoords());
double startingDistance = friendsCoords.distance(path.getStartCoords());
double finalDistance = friendsCoords.distance(path.getFinalCoords());
// If difference is positive => we moved closer => reward
// If difference is negative => we moved farther => penalize
double difference = (startingDistance - finalDistance);
double herding = getOwner().getBehaviorSettings().getHerdMentalityValue();
double herdingMod = distanceToAllies * herding;

formula.append(" - herdingMod [").append(LOG_DECIMAL.format(herdingMod))
.append(" = ")
.append(LOG_DECIMAL.format(distanceToAllies)).append(" * ")
.append(LOG_DECIMAL.format(herding))
.append("]");
double herdingMod = difference * herding;

formula.append(" + herdingMod [")
.append(LOG_DECIMAL.format(herdingMod)).append(" = (")
.append(LOG_DECIMAL.format(startingDistance)).append(" - ")
.append(LOG_DECIMAL.format(finalDistance)).append(") * ")
.append(LOG_DECIMAL.format(herding))
.append("]");
return herdingMod;
}

Expand Down Expand Up @@ -584,21 +585,8 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal

// I can kick a different target than I shoot, so add physical to
// total damage after I've looked at all enemies
double maximumDamageDone = damageEstimate.firingDamage + damageEstimate.physicalDamage;

// My bravery modifier is based on my chance of getting to the
// firing position (successProbability), how much damage I can do
// (weighted by bravery), less the damage I might take.
double braveryValue = getOwner().getBehaviorSettings().getBraveryValue();
double braveryMod = (successProbability * (maximumDamageDone * braveryValue)) - expectedDamageTaken;
formula.append(" + braveryMod [")
.append(LOG_DECIMAL.format(braveryMod)).append(" = ")
.append(LOG_PERCENT.format(successProbability))
.append(" * ((")
.append(LOG_DECIMAL.format(maximumDamageDone)).append(" * ")
.append(LOG_DECIMAL.format(braveryValue)).append(") - ")
.append(LOG_DECIMAL.format(expectedDamageTaken)).append("]");
utility += braveryMod;
utility += getBraveryMod(successProbability, damageEstimate, expectedDamageTaken, formula);

// the only critters not subject to aggression and herding mods are
// airborne aeros on ground maps, as they move incredibly fast
Expand All @@ -612,6 +600,8 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal
utility -= calculateHerdingMod(friendsCoords, pathCopy, formula);
}

utility += calculateMovementMod(movingUnit, pathCopy, game, formula);

// Try to face the enemy.
double facingMod = calculateFacingMod(movingUnit, game, pathCopy, formula);
if (facingMod == -10000) {
Expand All @@ -628,10 +618,49 @@ protected RankedPath rankPath(MovePath path, Game game, int maxRange, double fal
utility -= utility * calculateOffBoardMod(pathCopy);

RankedPath rankedPath = new RankedPath(utility, pathCopy, formula.toString());
rankedPath.setExpectedDamage(maximumDamageDone);
rankedPath.setExpectedDamage(damageEstimate.getMaximumDamageEstimate());
return rankedPath;
}

private double getBraveryMod(double successProbability, FiringPhysicalDamage damageEstimate, double expectedDamageTaken, StringBuilder formula) {
double maximumDamageDone = damageEstimate.getMaximumDamageEstimate();
// My bravery modifier is based on my chance of getting to the
// firing position (successProbability), how much damage I can do
// (weighted by bravery), less the damage I might take.
double braveryValue = getOwner().getBehaviorSettings().getBraveryValue();
double braveryMod = (successProbability * maximumDamageDone * braveryValue) - expectedDamageTaken;
formula.append(" + braveryMod [")
.append(LOG_DECIMAL.format(braveryMod)).append(" = (")
.append(LOG_PERCENT.format(successProbability))
.append(" * ")
.append(LOG_DECIMAL.format(maximumDamageDone)).append(" * ")
.append(LOG_DECIMAL.format(braveryValue)).append(") - ")
.append(LOG_DECIMAL.format(expectedDamageTaken)).append("]");
return braveryMod;
}


private double calculateMovementMod(Entity movingUnit, MovePath pathCopy, Game game, StringBuilder formula) {
var hexMoved = (double) pathCopy.getHexesMoved();
var distanceMoved = pathCopy.getDistanceTravelled();
var tmm = Compute.getTargetMovementModifier(distanceMoved, pathCopy.isJumping(), pathCopy.isAirborne(), game);
var tmmValue = tmm.getValue();
if (tmmValue == 0 || ((hexMoved + distanceMoved) == 0)) {
formula.append(" + movementMod [0]");
return 0;
}
var movementFactor = tmmValue * (hexMoved * distanceMoved) / (hexMoved + distanceMoved);

formula.append(" + movementMod [")
.append(LOG_DECIMAL.format(movementFactor))
.append(" = tmm [").append(tmm).append("] * (distance[")
.append(LOG_DECIMAL.format(distanceMoved)).append("] * hexMoved[")
.append(LOG_DECIMAL.format(hexMoved)).append("]) / (distance[")
.append(LOG_DECIMAL.format(distanceMoved)).append("] + hexMoved[")
.append(LOG_DECIMAL.format(hexMoved)).append("])]");
return movementFactor;
}

/**
* Worker function that determines if a given enemy entity should be evaluated
* as if it has moved.
Expand Down Expand Up @@ -796,7 +825,7 @@ public double checkPathForHazards(MovePath path, Entity movingUnit, Game game) {
previousCoords = coords;
}
logMsg.append("\nCompiled Hazard for Path (")
.append(path.toString()).append("): ").append(LOG_DECIMAL.format(totalHazard));
.append(path).append("): ").append(LOG_DECIMAL.format(totalHazard));

return totalHazard;
} finally {
Expand Down Expand Up @@ -1527,5 +1556,9 @@ private double calcRubbleHazard(Hex hex, boolean endHex, Entity movingUnit,
protected class FiringPhysicalDamage {
public double firingDamage;
public double physicalDamage;

public double getMaximumDamageEstimate() {
return firingDamage + physicalDamage;
}
}
}
7 changes: 4 additions & 3 deletions megamek/src/megamek/client/bot/princess/IPathRanker.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

import megamek.common.Coords;
import megamek.common.Entity;
Expand All @@ -29,8 +30,8 @@

public interface IPathRanker {

ArrayList<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange, double fallTolerance,
List<Entity> enemies, List<Entity> friends);
TreeSet<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange, double fallTolerance,
List<Entity> enemies, List<Entity> friends);

/**
* Performs initialization to help speed later calls of rankPath for this
Expand Down Expand Up @@ -58,7 +59,7 @@ ArrayList<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRang
* @param ps The list of ranked paths to process
* @return "Best" out of those paths
*/
RankedPath getBestPath(List<RankedPath> ps);
RankedPath getBestPath(TreeSet<RankedPath> ps);

/**
* Find the closest enemy to a unit with a path
Expand Down
47 changes: 23 additions & 24 deletions megamek/src/megamek/client/bot/princess/PathRanker.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.*;

import static megamek.client.ui.SharedUtility.predictLeapDamage;
import static megamek.client.ui.SharedUtility.predictLeapFallDamage;
Expand Down Expand Up @@ -73,12 +70,12 @@ protected abstract RankedPath rankPath(MovePath path, Game game, int maxRange,
Coords friendsCoords);

@Override
public ArrayList<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange,
public TreeSet<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int maxRange,
double fallTolerance, List<Entity> enemies,
List<Entity> friends) {
// No point in ranking an empty list.
if (movePaths.isEmpty()) {
return new ArrayList<>();
return new TreeSet<>();
}

// the cached path probability data is really only relevant for one iteration
Expand All @@ -97,36 +94,38 @@ public ArrayList<RankedPath> rankPaths(List<MovePath> movePaths, Game game, int
allyCenter = calcAllyCenter(movePaths.get(0).getEntity().getId(), friends, game);
}

ArrayList<RankedPath> returnPaths = new ArrayList<>(validPaths.size());
TreeSet<RankedPath> returnPaths = new TreeSet<>(Collections.reverseOrder());

try {
final BigDecimal numberPaths = new BigDecimal(validPaths.size());
BigDecimal count = BigDecimal.ZERO;
BigDecimal interval = new BigDecimal(5);

boolean pathsHaveExpectedDamage = false;

for (MovePath path : validPaths) {
count = count.add(BigDecimal.ONE);
try {
count = count.add(BigDecimal.ONE);

RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, allyCenter);
RankedPath rankedPath = rankPath(path, game, maxRange, fallTolerance, enemies, allyCenter);

returnPaths.add(rankedPath);
returnPaths.add(rankedPath);

// we want to keep track of if any of the paths we've considered have some kind
// of damage potential
pathsHaveExpectedDamage |= (rankedPath.getExpectedDamage() > 0);
// we want to keep track of if any of the paths we've considered have some kind
// of damage potential
pathsHaveExpectedDamage |= (rankedPath.getExpectedDamage() > 0);

BigDecimal percent = count.divide(numberPaths, 2, RoundingMode.DOWN).multiply(new BigDecimal(100))
BigDecimal percent = count.divide(numberPaths, 2, RoundingMode.DOWN).multiply(new BigDecimal(100))
.round(new MathContext(0, RoundingMode.DOWN));
if (percent.compareTo(interval) >= 0) {
if (logger.isLevelLessSpecificThan(Level.INFO)) {
getOwner().sendChat("... " + percent.intValue() + "% complete.");
if (percent.compareTo(interval) >= 0) {
if (logger.isLevelLessSpecificThan(Level.INFO)) {
getOwner().sendChat("... " + percent.intValue() + "% complete.");
}
interval = percent.add(new BigDecimal(5));
}
interval = percent.add(new BigDecimal(5));
} catch (Exception e) {
logger.error(e, e.getMessage() + "while processing " + path);
}
}

Entity mover = movePaths.get(0).getEntity();
UnitBehavior behaviorTracker = getOwner().getUnitBehaviorTracker();
boolean noDamageButCanDoDamage = !pathsHaveExpectedDamage
Expand Down Expand Up @@ -162,7 +161,7 @@ private List<MovePath> validatePaths(List<MovePath> startingPathList, Game game,
Targetable closestTarget = findClosestEnemy(mover, mover.getPosition(), game);
int startingTargetDistance = (closestTarget == null) ? Integer.MAX_VALUE
: closestTarget.getPosition().distance(mover.getPosition());

boolean hasNoEnemyAvailable = (closestTarget == null);
List<MovePath> returnPaths = new ArrayList<>(startingPathList.size());
boolean inRange = maxRange >= startingTargetDistance;

Expand Down Expand Up @@ -197,7 +196,7 @@ private List<MovePath> validatePaths(List<MovePath> startingPathList, Game game,
// Skip this part if I'm an aero on the ground map, as it's kind of irrelevant
// also skip this part if I'm attempting to retreat, as engagement is not the
// point here
if (!isAirborneAeroOnGroundMap && !getOwner().wantsToFallBack(mover)) {
if (!isAirborneAeroOnGroundMap && !getOwner().wantsToFallBack(mover) && !hasNoEnemyAvailable) {
Targetable closestToEnd = findClosestEnemy(mover, finalCoords, game);
String validation = validRange(finalCoords, closestToEnd, startingTargetDistance, maxRange,
inRange);
Expand Down Expand Up @@ -250,8 +249,8 @@ private List<MovePath> validatePaths(List<MovePath> startingPathList, Game game,
* @return "Best" out of those paths
*/
@Override
public @Nullable RankedPath getBestPath(List<RankedPath> ps) {
return ps.isEmpty() ? null : Collections.max(ps);
public @Nullable RankedPath getBestPath(TreeSet<RankedPath> ps) {
return ps.isEmpty() ? null : ps.first();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion megamek/src/megamek/client/bot/princess/Princess.java
Original file line number Diff line number Diff line change
Expand Up @@ -2310,7 +2310,7 @@ protected MovePath continueMovementFor(final Entity entity) {
// fall tolerance range between 0.50 and 1.0
final double fallTolerance = getBehaviorSettings().getFallShameIndex() / 20d + 0.50d;

final List<RankedPath> rankedPaths = getPathRanker(entity).rankPaths(paths,
final TreeSet<RankedPath> rankedPaths = getPathRanker(entity).rankPaths(paths,
getGame(), getMaxWeaponRange(entity), fallTolerance, getEnemyEntities(),
getFriendEntities());

Expand Down
8 changes: 1 addition & 7 deletions megamek/src/megamek/client/bot/princess/RankedPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,7 @@ public int compareTo(RankedPath p) {
if (p.rank < rank) {
return 1;
}
if (path.getKey().hashCode() < p.path.getKey().hashCode()) {
return -1;
}
if (path.getKey().hashCode() > p.path.getKey().hashCode()) {
return 1;
}
return 0;
return Integer.compare(path.getHexesMoved(), p.path.getHexesMoved());
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions megamek/src/megamek/common/Coords.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public Coords translated(int dir, int distance) {
public Coords translated(String dir) {
int intDir = 0;

try {
if (Character.isDigit(dir.charAt(0))) {
intDir = Integer.parseInt(dir);
} catch (NumberFormatException nfe) {
} else {
if (dir.equalsIgnoreCase("N")) {
intDir = 0;
} else if (dir.equalsIgnoreCase("NE")) {
Expand Down
3 changes: 3 additions & 0 deletions megamek/src/megamek/common/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2097,6 +2097,9 @@ public void setElevation(int elevation) {
public int calcElevation(Hex current, Hex next, int assumedElevation,
boolean climb, boolean wigeEndClimbPrevious) {
int retVal = assumedElevation;
if (next == null) {
return retVal;
}
if (isAero()) {
return retVal;
}
Expand Down
Loading

0 comments on commit 32be242

Please sign in to comment.