Skip to content

Commit

Permalink
Improve heuristic quality
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexProgrammerDE committed Apr 4, 2024
1 parent 733dc95 commit 39ff823
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 28 deletions.
93 changes: 81 additions & 12 deletions server/src/main/java/com/soulfiremc/server/pathfinding/Costs.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,74 @@
import java.util.OptionalInt;
import org.jetbrains.annotations.Nullable;

/**
* This class helps in calculating the costs of different actions. It is used in the pathfinding
* algorithm to determine the best path to a goal.
* The heuristic used is the distance in blocks. So getting from point A to point B is calculated
* using the distance in blocks. The cost of breaking a block is calculated using the time it takes
* in ticks to break a block and then converted to a relative heuristic.
*/
public class Costs {
/**
* A normal server runs at 20 ticks per second
*/
public static final double TICKS_PER_SECOND = 20;
/**
* Normal player walking speed in blocks per second
*/
public static final double BLOCKS_PER_SECOND = 4.317;
/**
* Multiply calculated ticks using this number to get a good relative heuristic
*/
public static final double TICKS_PER_BLOCK = TICKS_PER_SECOND / BLOCKS_PER_SECOND;
/**
* The distance in blocks between two points that are directly next to each other
*/
public static final double STRAIGHT = 1;
public static final double DIAGONAL = 1.4142135623730951;
public static final double JUMP = 0.3;
public static final double ONE_GAP_JUMP = STRAIGHT + JUMP;
public static final double FALL_1 = 0.1;
public static final double FALL_2 = 0.2;
public static final double FALL_3 = 0.3;
// 4.317 blocks per second converted to rough estimation of ticks per block
// Multiply calculated ticks using this number to get a good relative heuristic
public static final double TICKS_PER_BLOCK = 5;
// We don't want a bot that tries to break blocks instead of walking around them
/**
* The distance in blocks between two points that are diagonal to each other.
* Calculated using the Pythagorean theorem
*/
public static final double DIAGONAL = Math.sqrt(2);
/**
* It takes ~9 ticks for a player to jump up, decelerate and then land one block higher.
*/
public static final double JUMP_UP_BLOCK = 9 / TICKS_PER_BLOCK;
/**
* It takes ~8 ticks for a player to jump up, decelerate and then land on the same y level.
*/
public static final double JUMP_LAND_GROUND = 12 / TICKS_PER_BLOCK;
/**
* When you jump a gap you roughly do a full jump and walk 2 blocks in front.
*/
public static final double ONE_GAP_JUMP = JUMP_LAND_GROUND + STRAIGHT + STRAIGHT;
/**
* Falling 1 block takes ~5.63 ticks
*/
public static final double FALL_1 = 5.63 / TICKS_PER_BLOCK;
/**
* Falling 2 blocks takes ~7.79 ticks
*/
public static final double FALL_2 = 7.79 / TICKS_PER_BLOCK;
/**
* Falling 3 blocks takes ~9.48 ticks
*/
public static final double FALL_3 = 9.48 / TICKS_PER_BLOCK;
/**
* We don't want a bot that frequently tries to break blocks instead of walking around them
*/
public static final double BREAK_BLOCK_ADDITION = 2;
/**
* We don't want a bot that frequently tries to place blocks instead of finding smarter paths
*/
public static final double PLACE_BLOCK = 5;
public static final double JUMP_UP_AND_PLACE_BELOW = JUMP + PLACE_BLOCK;
// Sliding around a corner is roughly like walking two blocks
public static final double JUMP_UP_AND_PLACE_BELOW = JUMP_UP_BLOCK + PLACE_BLOCK;
/**
* Sliding around a corner is roughly like walking two blocks.
* That's why even through the distance from A to B diagonally is DIAGONAL, the cost is actually 2.
* That is why we need to add 2 - DIAGONAL to the cost of sliding around a corner as that adds
* up the cost to 2.
*/
public static final double CORNER_SLIDE = 2 - DIAGONAL;

/**
Expand All @@ -58,6 +110,23 @@ public class Costs {
public static final BlockState SOLID_PLACED_BLOCK_STATE =
BlockState.forDefaultBlockType(BlockType.STONE);

public static void main(String[] args) {
var y = 0d;
for (var i = 0; i < 32; i++) {
var acceleration = getAcceleration(i);
System.out.println(i + " acc: " + getAcceleration(i) + " y: " + y);
y += acceleration;
}
}

public static double getAcceleration(double ticks) {
if (ticks == 0) {
return 0.42;
}

return (getAcceleration(ticks - 1) - 0.08) * 0.98;
}

private Costs() {}

public static BlockMiningCosts calculateBlockBreakCost(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@ public SimpleMovement(MovementDirection direction, MovementSide side, MovementMo
case FALL_1 -> Costs.FALL_1;
case FALL_2 -> Costs.FALL_2;
case FALL_3 -> Costs.FALL_3;
case JUMP -> Costs.JUMP;
case JUMP_UP_BLOCK -> Costs.JUMP_UP_BLOCK;
};

this.targetFeetBlock = modifier.offset(direction.offset(FEET_POSITION_RELATIVE_BLOCK));
this.allowBlockActions =
!diagonal
&& (modifier == MovementModifier.JUMP
&& (modifier == MovementModifier.JUMP_UP_BLOCK
|| modifier == MovementModifier.NORMAL
|| modifier == MovementModifier.FALL_1);

Expand All @@ -108,15 +108,15 @@ private int freeCapacity() {
+ switch (modifier) {
case NORMAL -> 0;
case FALL_1 -> 1;
case FALL_2, JUMP -> 2;
case FALL_2, JUMP_UP_BLOCK -> 2;
case FALL_3 -> 3;
};
}

public List<SFVec3i> listRequiredFreeBlocks() {
var requiredFreeBlocks = new ObjectArrayList<SFVec3i>(freeCapacity());

if (modifier == MovementModifier.JUMP) {
if (modifier == MovementModifier.JUMP_UP_BLOCK) {
// Make head block free (maybe head block is a slab)
requiredFreeBlocks.add(FEET_POSITION_RELATIVE_BLOCK.add(0, 1, 0));

Expand Down Expand Up @@ -223,7 +223,7 @@ public BlockSafetyData[][] listCheckSafeMineBlocks() {
var leftDirectionSide = blockDirection.leftSide();
var rightDirectionSide = blockDirection.rightSide();

if (modifier == MovementModifier.JUMP) {
if (modifier == MovementModifier.JUMP_UP_BLOCK) {
var aboveHead = FEET_POSITION_RELATIVE_BLOCK.add(0, 2, 0);
results[requiredFreeBlocks.indexOf(aboveHead)] =
new BlockSafetyData[] {
Expand Down Expand Up @@ -319,7 +319,7 @@ public List<BotActionManager.BlockPlaceData> possibleBlocksToPlaceAgainst() {
// Right side
new BotActionManager.BlockPlaceData(
rightDirectionSide.offset(floorBlock), leftDirectionSide.direction()));
case JUMP, FALL_1 -> // 4 - no scaffolding
case JUMP_UP_BLOCK, FALL_1 -> // 4 - no scaffolding
List.of(
// Below
new BotActionManager.BlockPlaceData(floorBlock.sub(0, 1, 0), Direction.UP),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public enum MovementModifier {
FALL_1,
FALL_2,
FALL_3,
JUMP;
JUMP_UP_BLOCK;

public static final MovementModifier[] VALUES = values();

Expand All @@ -36,11 +36,11 @@ public SFVec3i offset(SFVec3i vector) {
case FALL_1 -> vector.add(0, -1, 0);
case FALL_2 -> vector.add(0, -2, 0);
case FALL_3 -> vector.add(0, -3, 0);
case JUMP -> vector.add(0, 1, 0);
case JUMP_UP_BLOCK -> vector.add(0, 1, 0);
};
}

public SFVec3i offsetIfJump(SFVec3i vector) {
return this == MovementModifier.JUMP ? vector.add(0, 1, 0) : vector;
return this == MovementModifier.JUMP_UP_BLOCK ? vector.add(0, 1, 0) : vector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
public class PhysicsData {
public double gravity = 0.08;
public float airdrag = 0.98F;
public double yawSpeed = 3.0;
public double pitchSpeed = 3.0;
public double playerSpeed = 0.1;
public double sprintSpeed = 0.3F;
public double sneakSpeed = 0.3;
public double stepHeight = 0.6F;
Expand All @@ -36,9 +33,6 @@ public class PhysicsData {
public double honeyblockJumpSpeed = 0.4;
public double climbMaxSpeed = 0.15;
public double climbSpeed = 0.2;
public float playerWidth = 0.6F;
public float playerHeight = 1.8F;
public float playerSneakHeight = 1.5F;
public float waterSpeed = 0.8F;
public float lavaSpeed = 0.5F;
public float liquidSpeed = 0.02F;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,6 @@ public void jump() {

@Override
public float height() {
return this.controlState.sneaking() ? physics.playerSneakHeight : physics.playerHeight;
return this.controlState.sneaking() ? 1.5F : 1.8F;
}
}

0 comments on commit 39ff823

Please sign in to comment.