From 7df6a14b0e24101e71563c72f1872c63431131a6 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 12:40:26 +0100 Subject: [PATCH 1/9] fix BuildingPlacer division before substraction and default case fallthrough bugs --- src/main/java/bwapi/BuildingPlacer.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/bwapi/BuildingPlacer.java b/src/main/java/bwapi/BuildingPlacer.java index 0aaaa9a4..549e9796 100644 --- a/src/main/java/bwapi/BuildingPlacer.java +++ b/src/main/java/bwapi/BuildingPlacer.java @@ -244,8 +244,7 @@ private static void ReserveAllStructures(final PlacementReserve reserve, final U } private static void ReserveExistingAddonPlacement(final PlacementReserve reserve, final TilePosition desiredPosition, final Game game) { - final TilePosition start = desiredPosition.subtract(new TilePosition(MAX_RANGE, MAX_RANGE)).divide(2); - + final TilePosition start = desiredPosition.subtract(new TilePosition(MAX_RANGE, MAX_RANGE).divide(2)); //Exclude addon placement locations reserve.backup(); game.self().getUnits().stream() @@ -274,15 +273,14 @@ private static void ReserveDefault(final PlacementReserve reserve, final UnitTyp case Terran_Missile_Turret: case Protoss_Robotics_Facility: case Protoss_Gateway: - case Protoss_Photon_Cannon: + default: + ReserveStructure(reserve, it, 2, type, desiredPosition); + break; case Terran_Barracks: case Terran_Bunker: case Zerg_Creep_Colony: ReserveStructure(reserve, it, 1, type, desiredPosition); break; - default: - ReserveStructure(reserve, it, 2, type, desiredPosition); - break; } } From 35ca7d9a8a34d9f602d56ecacb7887368fdf1ab6 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 12:56:55 +0100 Subject: [PATCH 2/9] nit: IsTransport should not have capitalized P, keep for backwards compat --- src/main/java/bwapi/UnitFilter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/bwapi/UnitFilter.java b/src/main/java/bwapi/UnitFilter.java index a50c2533..3dfe3e13 100644 --- a/src/main/java/bwapi/UnitFilter.java +++ b/src/main/java/bwapi/UnitFilter.java @@ -3,7 +3,9 @@ import java.util.function.Predicate; public interface UnitFilter extends Predicate { - UnitFilter IsTransPort = u -> u.getType().spaceProvided() > 0 && u.getType() != UnitType.Terran_Bunker; + UnitFilter IsTransport = u -> u.getType().spaceProvided() > 0 && u.getType() != UnitType.Terran_Bunker; + @Deprecated // backwards compatible + UnitFilter IsTransPort = IsTransport; UnitFilter CanProduce = u -> u.getType().canProduce(); UnitFilter CanAttack = u -> u.getType().canAttack(); UnitFilter CanMove = u -> u.getType().canMove(); From f62fa6343f4a95beb8651a439a2f10e9f3ff4ca8 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 13:17:01 +0100 Subject: [PATCH 3/9] setMap should return false on empty mapFileName not throw --- src/main/java/bwapi/Game.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 411765ad..ef4c4244 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -2380,10 +2380,9 @@ public int getLastEventTime() { * parameter. */ final public boolean setMap(final String mapFileName) { - if (mapFileName == null || mapFileName.length() >= 260 || mapFileName.charAt(0) == 0) { + if (mapFileName == null || mapFileName.isEmpty() || mapFileName.length() >= 260 || mapFileName.charAt(0) == 0) { return false; } - addCommand(CommandType.SetMap, mapFileName, 0); return true; } From e40ee50d7e90584e3b551c11d0062b73bc4f5567 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 13:37:46 +0100 Subject: [PATCH 4/9] fix getUnitX conditions --- src/main/java/bwapi/Game.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index ef4c4244..8e16ff08 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -680,10 +680,12 @@ public List getUnitsOnTile(final int tileX, final int tileY) { * given build tile. */ public List getUnitsOnTile(final int tileX, final int tileY, final UnitFilter pred) { - return getAllUnits().stream().filter(u -> { - final TilePosition tp = u.getTilePosition(); - return tp.x == tileX && tp.y == tileY && pred.test(u); - }).collect(Collectors.toList()); + final TilePosition tile = new TilePosition(tileX, tileY); + if (!tile.isValid(this)) { + return Collections.emptyList(); + } + final Position p = tile.toPosition(); + return getUnitsInRectangle(p.x, p.y, p.x + 32, p.y + 32, pred); } public List getUnitsInRectangle(final int left, final int top, final int right, final int bottom) { @@ -740,7 +742,7 @@ public List getUnitsInRadius(final Position center, final int radius) { public List getUnitsInRadius(final Position center, final int radius, final UnitFilter pred) { return getAllUnits().stream() - .filter(u -> center.getApproxDistance(u.getPosition()) <= radius && pred.test(u)) + .filter(u -> u.getDistance(center) <= radius && pred.test(u)) .collect(Collectors.toList()); } From f4f43ea69d246f87d45e5e25943985fd317c1a16 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 13:44:05 +0100 Subject: [PATCH 5/9] fix getUnitsInWeaponRange boolean condition --- src/main/java/bwapi/Unit.java | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 47181e1e..23ddbdb9 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -1301,30 +1301,28 @@ public List getUnitsInWeaponRange(final WeaponType weapon, final UnitFilte getRight() + max, getBottom() + max, u -> { - if (!pred.test(u)) { - return false; - } // Unit check and unit status if (u == this || u.isInvincible()) { return false; } - // Weapon distance check final int dist = getDistance(u); if ((weapon.minRange() != 0 && dist < weapon.minRange()) || dist > max) { return false; } - // Weapon behavioural checks final UnitType ut = u.getType(); - return (!weapon.targetsOwn() || u.getPlayer().equals(getPlayer())) && - (weapon.targetsAir() || u.isFlying()) && - (weapon.targetsGround() || !u.isFlying()) && - (!weapon.targetsMechanical() || !ut.isMechanical()) && - (!weapon.targetsOrganic() || !ut.isOrganic()) && - (!weapon.targetsNonBuilding() || ut.isBuilding()) && - (!weapon.targetsNonRobotic() || ut.isRobotic()) && - (!weapon.targetsOrgOrMech() || (!ut.isOrganic() && !ut.isMechanical())); + if ((weapon.targetsOwn() && !u.getPlayer().equals(getPlayer())) || + (!weapon.targetsAir() && !u.isFlying()) || + (!weapon.targetsGround() && u.isFlying()) || + (weapon.targetsMechanical() && ut.isMechanical()) || + (weapon.targetsOrganic() && ut.isOrganic()) || + (weapon.targetsNonBuilding() && !ut.isBuilding()) || + (weapon.targetsNonRobotic() && !ut.isRobotic()) || + (weapon.targetsOrgOrMech() && (ut.isOrganic() || ut.isMechanical()))) { + return false; + } + return pred.test(u); }); } From 1731bc1f57f42f394b216249387d572cc0595d36 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 31 Oct 2026 14:11:37 +0100 Subject: [PATCH 6/9] fix player color --- src/main/java/bwapi/Player.java | 4 ++-- src/main/java/bwapi/Unit.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/bwapi/Player.java b/src/main/java/bwapi/Player.java index 263e6770..994df8e4 100644 --- a/src/main/java/bwapi/Player.java +++ b/src/main/java/bwapi/Player.java @@ -551,7 +551,7 @@ public Text getTextColor() { return Text.Blue; case 159: // teal return Text.Teal; - case 164: // purp + case 164: // purple return Text.Purple; case 156: // orange with fix from @n00byEdge return Text.Orange; @@ -561,7 +561,7 @@ public Text getTextColor() { return Text.PlayerWhite; case 135: // yellow return Text.PlayerYellow; - case 185: // green p9 + case 117: // green return Text.DarkGreen; case 136: // p10 return Text.LightYellow; diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 23ddbdb9..1261ab5b 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -4719,7 +4719,7 @@ public boolean canGather(Unit targetUnit, boolean checkCanTargetUnit, boolean ch return false; } - if (!hasPath(getPosition())) { + if (!hasPath(targetUnit.getPosition())) { return false; } @@ -6169,7 +6169,7 @@ public boolean canUseTechUnit(TechType tech, Unit targetUnit, boolean checkCanTa if (!targetType.isOrganic() || targetType.isFlyer()) { return false; } - if (!targetUnit.getPlayer().isNeutral() && getPlayer().isEnemy(getPlayer())) { + if (!targetUnit.getPlayer().isNeutral() && getPlayer().isEnemy(targetUnit.getPlayer())) { return false; } break; From 8665f78820c16a77c54f3eb9ce3307b78b444728 Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sun, 1 Nov 2026 21:01:50 +0100 Subject: [PATCH 7/9] Exclude self from units in radius --- src/main/java/bwapi/Unit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 1261ab5b..9f642016 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -1270,7 +1270,7 @@ public List getUnitsInRadius(final int radius, final UnitFilter pred) { getTop() - radius, getRight() + radius, getBottom() + radius, - u -> getDistance(u) <= radius && pred.test(u)); + u -> u != this && getDistance(u) <= radius && pred.test(u)); } public List getUnitsInWeaponRange(final WeaponType weapon) { From 8476ce491f4205baab2a5b3c472f4c1030eb5ebe Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sun, 1 Nov 2026 21:28:20 +0100 Subject: [PATCH 8/9] Fix more unit vs targetUnit bugs --- src/main/java/bwapi/Unit.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 9f642016..38f7202c 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -3911,7 +3911,7 @@ public boolean canAttackUnitGrouped(Unit targetUnit, boolean checkCanTargetUnit, return false; } - if (isInvincible()) { + if (targetUnit.isInvincible()) { return false; } @@ -4722,9 +4722,7 @@ public boolean canGather(Unit targetUnit, boolean checkCanTargetUnit, boolean ch if (!hasPath(targetUnit.getPosition())) { return false; } - - return !uType.isRefinery() || getPlayer().equals(game.self()); - + return !targetUnit.getType().isRefinery() || targetUnit.getPlayer().equals(game.self()); } public boolean canReturnCargo() { From 810617e41ef76ea30447c450ddcd1be3c367dc4a Mon Sep 17 00:00:00 2001 From: Jasper Geurtz Date: Sat, 7 Nov 2026 19:25:03 +0100 Subject: [PATCH 9/9] Fix targetUnit.isCompleted() in Unit.canGather and add unit tests for PR #102 fixes # Conflicts: # src/test/java/bwapi/GameTest.java --- src/main/java/bwapi/BuildingPlacer.java | 4 +- src/main/java/bwapi/Game.java | 2 +- src/main/java/bwapi/Player.java | 2 +- src/main/java/bwapi/Unit.java | 5 +- src/test/java/bwapi/BuildingPlacerTest.java | 63 +++++++ src/test/java/bwapi/FilterTest.java | 16 ++ src/test/java/bwapi/GameTest.java | 58 ++++++ src/test/java/bwapi/PlayerTest.java | 10 + src/test/java/bwapi/PointTest.java | 16 +- src/test/java/bwapi/UnitTest.java | 192 ++++++++++++++++++++ 10 files changed, 352 insertions(+), 16 deletions(-) create mode 100644 src/test/java/bwapi/BuildingPlacerTest.java diff --git a/src/main/java/bwapi/BuildingPlacer.java b/src/main/java/bwapi/BuildingPlacer.java index 549e9796..4eb40155 100644 --- a/src/main/java/bwapi/BuildingPlacer.java +++ b/src/main/java/bwapi/BuildingPlacer.java @@ -243,7 +243,7 @@ private static void ReserveAllStructures(final PlacementReserve reserve, final U reserve.restoreIfInvalid(); } - private static void ReserveExistingAddonPlacement(final PlacementReserve reserve, final TilePosition desiredPosition, final Game game) { + static void ReserveExistingAddonPlacement(final PlacementReserve reserve, final TilePosition desiredPosition, final Game game) { final TilePosition start = desiredPosition.subtract(new TilePosition(MAX_RANGE, MAX_RANGE).divide(2)); //Exclude addon placement locations reserve.backup(); @@ -258,7 +258,7 @@ private static void ReserveExistingAddonPlacement(final PlacementReserve reserve reserve.restoreIfInvalid(); } - private static void ReserveDefault(final PlacementReserve reserve, final UnitType type, final TilePosition desiredPosition, final Game game) { + static void ReserveDefault(final PlacementReserve reserve, final UnitType type, final TilePosition desiredPosition, final Game game) { reserve.backup(); final PlacementReserve original = reserve; diff --git a/src/main/java/bwapi/Game.java b/src/main/java/bwapi/Game.java index 8e16ff08..fda65689 100644 --- a/src/main/java/bwapi/Game.java +++ b/src/main/java/bwapi/Game.java @@ -16,7 +16,7 @@ * game state information from Starcraft Broodwar. Game state information includes all units, * resources, players, forces, bullets, terrain, fog of war, regions, etc. */ -public final class Game { +public class Game { private static final int[][] damageRatio = { // Ind, Sml, Med, Lrg, Non, Unk {0, 0, 0, 0, 0, 0}, // Independent diff --git a/src/main/java/bwapi/Player.java b/src/main/java/bwapi/Player.java index 994df8e4..16e932a3 100644 --- a/src/main/java/bwapi/Player.java +++ b/src/main/java/bwapi/Player.java @@ -544,7 +544,7 @@ public Color getColor() { * @return character code to use for text in Broodwar. */ public Text getTextColor() { - switch (color.id) { + switch (getColor().id) { case 111: // red return Text.BrightRed; case 165: // blue diff --git a/src/main/java/bwapi/Unit.java b/src/main/java/bwapi/Unit.java index 38f7202c..5e51a48a 100644 --- a/src/main/java/bwapi/Unit.java +++ b/src/main/java/bwapi/Unit.java @@ -1311,6 +1311,7 @@ public List getUnitsInWeaponRange(final WeaponType weapon, final UnitFilte return false; } // Weapon behavioural checks + // NOTE: The target flag checks below (after targetsOwn) are inverted, matching an upstream bug in C++ BWAPI's UnitInterface::getUnitsInWeaponRange (BWAPILIB/Source/Unit.cpp). final UnitType ut = u.getType(); if ((weapon.targetsOwn() && !u.getPlayer().equals(getPlayer())) || (!weapon.targetsAir() && !u.isFlying()) || @@ -4715,14 +4716,14 @@ public boolean canGather(Unit targetUnit, boolean checkCanTargetUnit, boolean ch return false; } - if (!isCompleted()) { + if (!targetUnit.isCompleted()) { return false; } if (!hasPath(targetUnit.getPosition())) { return false; } - return !targetUnit.getType().isRefinery() || targetUnit.getPlayer().equals(game.self()); + return !uType.isRefinery() || targetUnit.getPlayer().equals(game.self()); } public boolean canReturnCargo() { diff --git a/src/test/java/bwapi/BuildingPlacerTest.java b/src/test/java/bwapi/BuildingPlacerTest.java new file mode 100644 index 00000000..3f2968e1 --- /dev/null +++ b/src/test/java/bwapi/BuildingPlacerTest.java @@ -0,0 +1,63 @@ +package bwapi; + +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BuildingPlacerTest { + + @Test + public void reserveExistingAddonPlacementShouldUseHalfMaxRangeOffset() { + Game game = mock(Game.class); + Player self = mock(Player.class); + when(game.self()).thenReturn(self); + + TilePosition desired = new TilePosition(40, 40); + // start should be desired - (64, 64)/2 = (8, 8). + // Factory at (40, 40) has its 2x2 addon at world tiles (44, 41)..(45, 42), + // which corresponds to reserve coordinates (36, 33)..(37, 34). + Unit factory = mock(Unit.class); + when(factory.exists()).thenReturn(true); + when(factory.getType()).thenReturn(UnitType.Terran_Factory); + when(factory.getTilePosition()).thenReturn(new TilePosition(40, 40)); + when(self.getUnits()).thenReturn(Collections.singletonList(factory)); + + BuildingPlacer.PlacementReserve reserve = new BuildingPlacer.PlacementReserve(64); + reserve.setRange(0, 0, 64, 64, (byte) 1); + + BuildingPlacer.ReserveExistingAddonPlacement(reserve, desired, game); + + assertEquals(0, reserve.getValue(36, 33)); + assertEquals(0, reserve.getValue(37, 34)); + } + + @Test + public void reserveDefaultShouldUsePaddingTwoForFactory() { + Game game = mock(Game.class); + Player self = mock(Player.class); + when(game.self()).thenReturn(self); + + TilePosition desired = new TilePosition(40, 40); + Unit factory = mock(Unit.class); + when(factory.exists()).thenReturn(true); + when(factory.getType()).thenReturn(UnitType.Terran_Factory); + when(factory.getTilePosition()).thenReturn(new TilePosition(40, 40)); + when(factory.getPosition()).thenReturn(new TilePosition(40, 40).toPosition()); + when(self.getUnits()).thenReturn(Collections.singletonList(factory)); + + BuildingPlacer.PlacementReserve reserve = new BuildingPlacer.PlacementReserve(64); + reserve.setRange(0, 0, 64, 64, (byte) 1); + + BuildingPlacer.ReserveDefault(reserve, UnitType.Terran_Supply_Depot, desired, game); + + // For a 4x3 Factory at (40, 40) (reserve 32, 32) and a 3x2 Supply Depot: + // padding=1 reserves x in [25, 35), y in [27, 35) + // padding=2 reserves x in [24, 36), y in [26, 36) + assertEquals(0, reserve.getValue(24, 26)); + assertEquals(0, reserve.getValue(35, 35)); + } +} diff --git a/src/test/java/bwapi/FilterTest.java b/src/test/java/bwapi/FilterTest.java index 58f45af7..ec5c28af 100644 --- a/src/test/java/bwapi/FilterTest.java +++ b/src/test/java/bwapi/FilterTest.java @@ -39,4 +39,20 @@ public void testFilterCombinations() { assertTrue(units.stream().noneMatch(IsMineralField.and(IsRefinery))); assertTrue(units.stream().noneMatch(IsMineralField.negate())); } + + @Test + public void isTransportShouldMatchDropshipAndExcludeBunker() { + Unit dropship = mock(Unit.class); + when(dropship.getType()).thenReturn(UnitType.Terran_Dropship); + + Unit bunker = mock(Unit.class); + when(bunker.getType()).thenReturn(UnitType.Terran_Bunker); + + Unit marine = mock(Unit.class); + when(marine.getType()).thenReturn(UnitType.Terran_Marine); + + assertTrue(IsTransport.test(dropship)); + assertFalse(IsTransport.test(bunker)); + assertFalse(IsTransport.test(marine)); + } } diff --git a/src/test/java/bwapi/GameTest.java b/src/test/java/bwapi/GameTest.java index 8d7b1b48..396d2c81 100644 --- a/src/test/java/bwapi/GameTest.java +++ b/src/test/java/bwapi/GameTest.java @@ -13,10 +13,15 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; @RunWith(Theories.class) public class GameTest { @@ -116,4 +121,57 @@ public void hasPowerOnTile() throws IOException { // tile (31, 15) is pixel (992, 480), right next to the pylon assertTrue(game.hasPower(31, 15)); } + + @Test + public void getUnitsOnTileShouldUseUnitBoundingBoxAndValidateTile() { + Game game = spy(new Game()); + doReturn(128 * 32).when(game).mapPixelWidth(); + doReturn(128 * 32).when(game).mapPixelHeight(); + + // Command Center at top-left tile (10, 10) spans pixels [320..447, 320..415] (tiles 10..13, 10..12) + Unit commandCenter = mock(Unit.class); + when(commandCenter.getTilePosition()).thenReturn(new TilePosition(10, 10)); + when(commandCenter.getLeft()).thenReturn(320); + when(commandCenter.getTop()).thenReturn(320); + when(commandCenter.getRight()).thenReturn(447); + when(commandCenter.getBottom()).thenReturn(415); + + game.setAllUnits(Collections.singletonList(commandCenter)); + + // Tile (11, 11) is covered by the Command Center even though its top-left tile is (10, 10) + List onTile = game.getUnitsOnTile(11, 11); + assertEquals(Collections.singletonList(commandCenter), onTile); + + // Invalid tile should return empty list + assertTrue(game.getUnitsOnTile(-1, -1).isEmpty()); + } + + @Test + public void getUnitsInRadiusShouldUseUnitEdgeDistance() { + Game game = new Game(); + + // Unit centered at (100, 100) with bounding box [80..120, 80..120] + Unit unit = mock(Unit.class); + Position unitCenter = new Position(100, 100); + Position queryCenter = new Position(150, 100); + when(unit.getPosition()).thenReturn(unitCenter); + when(unit.getLeft()).thenReturn(80); + when(unit.getRight()).thenReturn(120); + when(unit.getTop()).thenReturn(80); + when(unit.getBottom()).thenReturn(120); + when(unit.exists()).thenReturn(true); + when(unit.getDistance(queryCenter)).thenCallRealMethod(); + + game.setAllUnits(Collections.singletonList(unit)); + + // Center-to-center distance is 50 (> 35), but edge-to-point distance is 30 (<= 35) + List result = game.getUnitsInRadius(queryCenter, 35); + assertEquals(Collections.singletonList(unit), result); + } + + @Test + public void setMapWithEmptyStringShouldReturnFalseInsteadOfThrowing() { + Game game = new Game(); + assertFalse(game.setMap("")); + } } \ No newline at end of file diff --git a/src/test/java/bwapi/PlayerTest.java b/src/test/java/bwapi/PlayerTest.java index 31daac74..83da83db 100644 --- a/src/test/java/bwapi/PlayerTest.java +++ b/src/test/java/bwapi/PlayerTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -18,4 +19,13 @@ public void neutralIsNotEnemy() { assertFalse(self.isEnemy(neutral)); } + + @Test + public void getTextColorShouldReturnDarkGreenForColorGreen() { + Player player = mock(Player.class); + when(player.getColor()).thenReturn(Color.Green); // Color(117) + when(player.getTextColor()).thenCallRealMethod(); + + assertEquals(Text.DarkGreen, player.getTextColor()); + } } diff --git a/src/test/java/bwapi/PointTest.java b/src/test/java/bwapi/PointTest.java index 0bc463af..24a8c788 100644 --- a/src/test/java/bwapi/PointTest.java +++ b/src/test/java/bwapi/PointTest.java @@ -6,6 +6,8 @@ import java.util.TreeSet; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class PointTest { @@ -52,16 +54,10 @@ public void alternativeConstructorTest() { @Test public void isValidChecks() { - Game game = new Game(); - game.botClientData().setBuffer(GameBuilder.binToBufferUnchecked(GameBuilder.DEFAULT_BUFFER_PATH)); - game.botClientData().gameData().setMapWidth(256); - game.botClientData().gameData().setMapHeight(256); - game.init(); - - assertEquals(256, game.mapHeight()); - assertEquals(256, game.mapWidth()); - assertEquals(32 * 256, game.mapPixelHeight()); - assertEquals(32 * 256, game.mapPixelWidth()); + Game game = mock(Game.class); + when(game.mapPixelWidth()).thenReturn(32 * 256); + when(game.mapPixelHeight()).thenReturn(32 * 256); + assertTrue(new Position(0,0).isValid(game)); assertTrue(new Position(1, 1).isValid(game)); diff --git a/src/test/java/bwapi/UnitTest.java b/src/test/java/bwapi/UnitTest.java index 75734425..dc31a7e3 100644 --- a/src/test/java/bwapi/UnitTest.java +++ b/src/test/java/bwapi/UnitTest.java @@ -2,12 +2,18 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -77,4 +83,190 @@ private Unit createUnit(UnitType type, Position position) { when(u.getY()).thenCallRealMethod(); return u; } + + @Test + public void getUnitsInRadiusShouldExcludeSelf() { + Game game = spy(new Game()); + doReturn(0).when(game).getFrameCount(); + + Unit selfUnit = spy(new Unit(mock(ClientData.UnitData.class), 0, game)); + Position pos = new Position(100, 100); + doReturn(pos).when(selfUnit).getPosition(); + doReturn(90).when(selfUnit).getLeft(); + doReturn(110).when(selfUnit).getRight(); + doReturn(90).when(selfUnit).getTop(); + doReturn(110).when(selfUnit).getBottom(); + doReturn(true).when(selfUnit).exists(); + doReturn(0).when(selfUnit).getDistance(any(Unit.class)); + doReturn(0).when(selfUnit).getDistance(any(Position.class)); + + Unit neighbor = mock(Unit.class); + when(neighbor.getPosition()).thenReturn(new Position(105, 100)); + when(neighbor.getLeft()).thenReturn(95); + when(neighbor.getRight()).thenReturn(115); + when(neighbor.getTop()).thenReturn(90); + when(neighbor.getBottom()).thenReturn(110); + when(neighbor.exists()).thenReturn(true); + when(neighbor.getDistance(any(Position.class))).thenReturn(0); + + game.setAllUnits(Arrays.asList(selfUnit, neighbor)); + + List result = selfUnit.getUnitsInRadius(50, u -> true); + assertEquals(Collections.singletonList(neighbor), result); + } + + @Test + public void getUnitsInWeaponRangeShouldOnlyEvaluatePredicateForValidTargets() { + Game game = spy(new Game()); + doReturn(0).when(game).getFrameCount(); + Player selfPlayer = mock(Player.class); + + Unit selfUnit = spy(new Unit(mock(ClientData.UnitData.class), 0, game)); + doReturn(true).when(selfUnit).exists(); + doReturn(selfPlayer).when(selfUnit).getPlayer(); + doReturn(100).when(selfUnit).getLeft(); + doReturn(100).when(selfUnit).getTop(); + doReturn(120).when(selfUnit).getRight(); + doReturn(120).when(selfUnit).getBottom(); + + Unit invincibleUnit = mock(Unit.class); + when(invincibleUnit.getLeft()).thenReturn(110); + when(invincibleUnit.getTop()).thenReturn(110); + when(invincibleUnit.getRight()).thenReturn(130); + when(invincibleUnit.getBottom()).thenReturn(130); + when(invincibleUnit.isInvincible()).thenReturn(true); + + Unit outOfRangeUnit = mock(Unit.class); + when(outOfRangeUnit.getLeft()).thenReturn(110); + when(outOfRangeUnit.getTop()).thenReturn(110); + when(outOfRangeUnit.getRight()).thenReturn(130); + when(outOfRangeUnit.getBottom()).thenReturn(130); + when(outOfRangeUnit.isInvincible()).thenReturn(false); + doReturn(9999).when(selfUnit).getDistance(outOfRangeUnit); + + game.setAllUnits(Arrays.asList(selfUnit, invincibleUnit, outOfRangeUnit)); + + AtomicInteger predicateCalls = new AtomicInteger(0); + selfUnit.getUnitsInWeaponRange(WeaponType.Gauss_Rifle, u -> { + predicateCalls.incrementAndGet(); + return true; + }); + + assertEquals(0, predicateCalls.get()); + } + + @Test + public void canAttackUnitGroupedShouldCheckTargetUnitInvincibility() { + Unit attacker = mock(Unit.class); + Unit target = mock(Unit.class); + Player selfPlayer = mock(Player.class); + + when(attacker.getPlayer()).thenReturn(selfPlayer); + when(attacker.getType()).thenReturn(UnitType.Terran_Marine); + when(attacker.isInvincible()).thenReturn(false); + + when(target.isInvincible()).thenReturn(true); + when(target.isFlying()).thenReturn(false); + when(target.getType()).thenReturn(UnitType.Terran_SCV); + + when(attacker.canAttackUnitGrouped(target, false, false, false, false)).thenCallRealMethod(); + + assertFalse(attacker.canAttackUnitGrouped(target, false, false, false, false)); + } + + @Test + public void canGatherShouldCheckPathToTargetPosition() { + Game game = mock(Game.class); + Player selfPlayer = mock(Player.class); + when(game.self()).thenReturn(selfPlayer); + + Position workerPos = new Position(100, 100); + Position unreachableMineralPos = new Position(500, 500); + + Unit worker = spy(new Unit(mock(ClientData.UnitData.class), 0, game)); + doReturn(workerPos).when(worker).getPosition(); + doReturn(selfPlayer).when(worker).getPlayer(); + doReturn(true).when(worker).isCompleted(); + doReturn(true).when(worker).hasPath(workerPos); + doReturn(false).when(worker).hasPath(unreachableMineralPos); + + Unit unreachableMineral = mock(Unit.class); + when(unreachableMineral.getType()).thenReturn(UnitType.Resource_Mineral_Field); + when(unreachableMineral.getPosition()).thenReturn(unreachableMineralPos); + when(unreachableMineral.isCompleted()).thenReturn(true); + + assertFalse(worker.canGather(unreachableMineral, false, false, false)); + } + + @Test + public void canGatherShouldCheckTargetRefineryOwner() { + Game game = mock(Game.class); + Player selfPlayer = mock(Player.class); + Player enemyPlayer = mock(Player.class); + when(game.self()).thenReturn(selfPlayer); + + Position workerPos = new Position(100, 100); + Position reachableRefineryPos = new Position(150, 150); + + Unit worker = spy(new Unit(mock(ClientData.UnitData.class), 0, game)); + doReturn(workerPos).when(worker).getPosition(); + doReturn(selfPlayer).when(worker).getPlayer(); + doReturn(true).when(worker).isCompleted(); + doReturn(true).when(worker).hasPath(workerPos); + doReturn(true).when(worker).hasPath(reachableRefineryPos); + + Unit enemyRefinery = mock(Unit.class); + when(enemyRefinery.getType()).thenReturn(UnitType.Terran_Refinery); + when(enemyRefinery.getPosition()).thenReturn(reachableRefineryPos); + when(enemyRefinery.getPlayer()).thenReturn(enemyPlayer); + when(enemyRefinery.isCompleted()).thenReturn(true); + + assertFalse(worker.canGather(enemyRefinery, false, false, false)); + } + + @Test + public void canGatherShouldCheckTargetUnitCompleted() { + Game game = mock(Game.class); + Player selfPlayer = mock(Player.class); + when(game.self()).thenReturn(selfPlayer); + + Position workerPos = new Position(100, 100); + Position refineryPos = new Position(150, 150); + + Unit worker = spy(new Unit(mock(ClientData.UnitData.class), 0, game)); + doReturn(workerPos).when(worker).getPosition(); + doReturn(selfPlayer).when(worker).getPlayer(); + doReturn(true).when(worker).isCompleted(); + doReturn(true).when(worker).hasPath(refineryPos); + + Unit incompleteRefinery = mock(Unit.class); + when(incompleteRefinery.getType()).thenReturn(UnitType.Terran_Refinery); + when(incompleteRefinery.getPosition()).thenReturn(refineryPos); + when(incompleteRefinery.getPlayer()).thenReturn(selfPlayer); + when(incompleteRefinery.isCompleted()).thenReturn(false); + + assertFalse(worker.canGather(incompleteRefinery, false, false, false)); + } + + @Test + public void canUseTechUnitHealingShouldRejectEnemyTarget() { + Player selfPlayer = mock(Player.class); + Player enemyPlayer = mock(Player.class); + when(enemyPlayer.isNeutral()).thenReturn(false); + when(selfPlayer.isEnemy(selfPlayer)).thenReturn(false); + when(selfPlayer.isEnemy(enemyPlayer)).thenReturn(true); + + Unit medic = mock(Unit.class); + when(medic.getPlayer()).thenReturn(selfPlayer); + + Unit enemyMarine = mock(Unit.class); + when(enemyMarine.getPlayer()).thenReturn(enemyPlayer); + when(enemyMarine.getType()).thenReturn(UnitType.Terran_Marine); + when(enemyMarine.isFlying()).thenReturn(false); + when(enemyMarine.getHitPoints()).thenReturn(20); + + when(medic.canUseTechUnit(TechType.Healing, enemyMarine, false, false, false, false)).thenCallRealMethod(); + + assertFalse(medic.canUseTechUnit(TechType.Healing, enemyMarine, false, false, false, false)); + } }