diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index 923aad58..ef9bc04b 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -14,6 +14,7 @@ const DEFAULT_CONFIG = { showAllRobotRadii: false, showTimelineMarkers: true, showHealthBars: true, + showPaintBars: false, showPaintMarkers: true, showMapXY: true, enableFancyPaint: true, @@ -28,6 +29,7 @@ const configDescription: Record = { showAllRobotRadii: 'Show all robot view and attack radii', showTimelineMarkers: 'Show user-generated markers on the timeline', showHealthBars: 'Show health bars below all robots', + showPaintBars: 'Show paint bars below all robots', showPaintMarkers: 'Show paint markers created using mark()', showMapXY: 'Show X,Y when hovering a tile', enableFancyPaint: 'Enable fancy paint rendering', diff --git a/client/src/constants.ts b/client/src/constants.ts index 2a23f2f4..8acbfc0f 100644 --- a/client/src/constants.ts +++ b/client/src/constants.ts @@ -1,4 +1,4 @@ -export const CLIENT_VERSION = '1.1.1' +export const CLIENT_VERSION = '1.2.0' export const SPEC_VERSION = '1' export const BATTLECODE_YEAR: number = 2025 export const MAP_SIZE_RANGE = { diff --git a/client/src/playback/Bodies.ts b/client/src/playback/Bodies.ts index 0729e88d..40e7aa4c 100644 --- a/client/src/playback/Bodies.ts +++ b/client/src/playback/Bodies.ts @@ -274,6 +274,9 @@ export class Body { if (focused || config.showHealthBars) { this.drawHealthBar(match, overlayCtx) } + if (focused || config.showPaintBars) { + this.drawPaintBar(match, overlayCtx, config.showHealthBars) + } } } @@ -435,6 +438,21 @@ export class Body { ctx.fillRect(hpBarX, hpBarY, hpBarWidth * (this.hp / this.maxHp), hpBarHeight) } + private drawPaintBar(match: Match, ctx: CanvasRenderingContext2D, healthVisible: boolean): void { + const dimension = match.currentRound.map.staticMap.dimension + const interpCoords = this.getInterpolatedCoords(match) + const renderCoords = renderUtils.getRenderCoords(interpCoords.x, interpCoords.y, dimension) + const paintBarWidth = 0.8 + const paintBarHeight = 0.1 + const paintBarYOffset = 0.4 + (healthVisible ? 0.11 : 0) + const paintBarX = renderCoords.x + 0.5 - paintBarWidth / 2 + const paintBarY = renderCoords.y + 0.5 + paintBarYOffset + ctx.fillStyle = 'rgba(0,0,0,.3)' + ctx.fillRect(paintBarX, paintBarY, paintBarWidth, paintBarHeight) + ctx.fillStyle = '#c515ed' + ctx.fillRect(paintBarX, paintBarY, paintBarWidth * (this.paint / this.maxPaint), paintBarHeight) + } + protected drawLevel(match: Match, ctx: CanvasRenderingContext2D) { if (this.level <= 1) return diff --git a/engine/src/main/battlecode/common/RobotController.java b/engine/src/main/battlecode/common/RobotController.java index 6dee1a7f..dc3425c2 100644 --- a/engine/src/main/battlecode/common/RobotController.java +++ b/engine/src/main/battlecode/common/RobotController.java @@ -123,6 +123,15 @@ public interface RobotController { */ int getMoney(); + /** + * Alias for getMoney + * + * @return the amount of money this robot's team has + * + * @battlecode.doc.costlymethod + */ + int getChips(); + /** * Returns what UnitType this robot is. * diff --git a/engine/src/main/battlecode/common/UnitType.java b/engine/src/main/battlecode/common/UnitType.java index 3cb9f2c5..242114a9 100644 --- a/engine/src/main/battlecode/common/UnitType.java +++ b/engine/src/main/battlecode/common/UnitType.java @@ -1,19 +1,19 @@ package battlecode.common; public enum UnitType { - SOLDIER(200, 250, 5, 250, -1, 200, 10, 20, 20, -1, 0, 0), - SPLASHER(300, 400, 50, 150, -1, 300, 50, 9, -1, 50, 0, 0), + SOLDIER(200, 250, 5, 250, -1, 200, 10, 9, 20, -1, 0, 0), + SPLASHER(300, 400, 50, 150, -1, 300, 50, 8, -1, 50, 0, 0), MOPPER(100, 300, 0, 50, -1, 100, 30, 2, -1, -1, 0, 0), - LEVEL_ONE_PAINT_TOWER(0, 25, 0, 1000, 1, 1000, 10, 9, 20, 10, 5, 0), + LEVEL_ONE_PAINT_TOWER(0, 100, 0, 1000, 1, 1000, 10, 9, 20, 10, 5, 0), LEVEL_TWO_PAINT_TOWER(0, 250, 0, 1500, 2, 1000, 10, 9, 20, 10, 10, 0), LEVEL_THREE_PAINT_TOWER(0, 500, 0, 2000, 3, 1000, 10, 9, 20, 10, 15, 0), - LEVEL_ONE_MONEY_TOWER(0, 25, 0, 1000, 1, 1000, 10, 9, 20, 10, 0, 10), + LEVEL_ONE_MONEY_TOWER(0, 100, 0, 1000, 1, 1000, 10, 9, 20, 10, 0, 10), LEVEL_TWO_MONEY_TOWER(0, 250, 0, 1500, 2, 1000, 10, 9, 20, 10, 0, 15), LEVEL_THREE_MONEY_TOWER(0, 500, 0, 2000, 3, 1000, 10, 9, 20, 10, 0, 20), - LEVEL_ONE_DEFENSE_TOWER(0, 25, 0, 2500, 1, 1000, 10, 20, 60, 30, 0, 0), + LEVEL_ONE_DEFENSE_TOWER(0, 100, 0, 2500, 1, 1000, 10, 20, 60, 30, 0, 0), LEVEL_TWO_DEFENSE_TOWER(0, 250, 0, 3000, 2, 1000, 10, 20, 65, 35, 0, 0), LEVEL_THREE_DEFENSE_TOWER(0, 500, 0, 3500, 3, 1000, 10, 20, 70, 40, 0, 0); diff --git a/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt b/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt index 6521aa0d..a51f3bbf 100644 --- a/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt +++ b/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt @@ -43,6 +43,7 @@ battlecode/common/RobotController/getAllLocationsWithinRadiusSquared 100 tru battlecode/common/RobotController/getHealth 1 true battlecode/common/RobotController/getID 1 true battlecode/common/RobotController/getPaint 1 true +battlecode/common/RobotController/getChips 1 true battlecode/common/RobotController/getMoney 1 true battlecode/common/RobotController/getType 1 true battlecode/common/RobotController/getLocation 1 true diff --git a/engine/src/main/battlecode/world/GameMapIO.java b/engine/src/main/battlecode/world/GameMapIO.java index 60055d1d..3522355f 100644 --- a/engine/src/main/battlecode/world/GameMapIO.java +++ b/engine/src/main/battlecode/world/GameMapIO.java @@ -236,7 +236,7 @@ public static LiveMap deserialize(battlecode.schema.GameMap raw, boolean teamsRe int[] patternArray = new int[4]; for (int i = 0; i < wallArray.length; i++) { wallArray[i] = raw.walls(i); - paintArray[i] = raw.paint(i); + paintArray[i] = possiblyReversePaint(raw.paint(i), teamsReversed); } for (int i = 0; i < patternArray.length; i++){ patternArray[i] = raw.paintPatterns(i); @@ -294,6 +294,7 @@ public static int serialize(FlatBufferBuilder builder, LiveMap gameMap) { bodyTypes.add(FlatHelpers.getRobotTypeFromUnitType(robot.type)); bodyLocsXs.add(robot.location.x); bodyLocsYs.add(robot.location.y); + ruinArray[gameMap.locationToIndex(robot.location)] = true; } for (int i = 0; i < gameMap.getWidth() * gameMap.getHeight(); i++) { @@ -365,6 +366,19 @@ private static void initInitialBodiesFromSchemaBodyTable(InitialBodyTable bodyTa } } + // No color = 0, Team A color 1 = 1, Team A color 2 = 2, Team B color 1 = 3, Team B color 2 = 4 + private static byte possiblyReversePaint(byte originalPaint, boolean teamsReversed){ + if (!teamsReversed) + return originalPaint; + switch (originalPaint){ + case 1: return 3; + case 2: return 4; + case 3: return 1; + case 4: return 2; + default: return originalPaint; + } + } + private static int createSpawnActionsVector(FlatBufferBuilder builder, ArrayList ids, ArrayList xs, ArrayList ys, ArrayList teams, ArrayList types){ InitialBodyTable.startSpawnActionsVector(builder, ids.size()); for (int i = 0; i < ids.size(); i++){ diff --git a/engine/src/main/battlecode/world/GameWorld.java b/engine/src/main/battlecode/world/GameWorld.java index 7b35bbcb..39b1eb20 100644 --- a/engine/src/main/battlecode/world/GameWorld.java +++ b/engine/src/main/battlecode/world/GameWorld.java @@ -576,12 +576,24 @@ public int getTowerPattern(UnitType towerType) { return this.patternArray[towerTypeToPatternIndex(towerType)]; } - public boolean isValidPatternCenter(MapLocation loc) { - return !(loc.x < GameConstants.PATTERN_SIZE / 2 + public boolean isValidPatternCenter(MapLocation loc, boolean isTower) { + return (!(loc.x < GameConstants.PATTERN_SIZE / 2 || loc.y < GameConstants.PATTERN_SIZE / 2 || loc.x >= gameMap.getWidth() - (GameConstants.PATTERN_SIZE - 1) / 2 || loc.y >= gameMap.getHeight() - (GameConstants.PATTERN_SIZE - 1) / 2 - ); + )) && (isTower || areaIsPaintable(loc)) ; + } + + // checks that location has no walls/ruins in the surrounding 5x5 area + public boolean areaIsPaintable(MapLocation loc){ + for (int dx = -GameConstants.PATTERN_SIZE / 2; dx < (GameConstants.PATTERN_SIZE + 1) / 2; dx++) { + for (int dy = -GameConstants.PATTERN_SIZE / 2; dy < (GameConstants.PATTERN_SIZE + 1) / 2; dy++) { + MapLocation newLoc = loc.translate(dx, dy); + if (!isPaintable(newLoc)) + return false; + } + } + return true; } public boolean isPassable(MapLocation loc) { diff --git a/engine/src/main/battlecode/world/RobotControllerImpl.java b/engine/src/main/battlecode/world/RobotControllerImpl.java index 0c574739..fb37ff65 100644 --- a/engine/src/main/battlecode/world/RobotControllerImpl.java +++ b/engine/src/main/battlecode/world/RobotControllerImpl.java @@ -151,6 +151,11 @@ public int getMoney() { return this.gameWorld.getTeamInfo().getMoney(getTeam()); } + @Override + public int getChips() { + return this.getMoney(); + } + @Override public UnitType getType(){ return this.robot.getType(); @@ -578,7 +583,7 @@ private void assertCanMarkTowerPattern(UnitType type, MapLocation loc) throws Ga + ") because the center is not a ruin"); } - if (!this.gameWorld.isValidPatternCenter(loc)) { + if (!this.gameWorld.isValidPatternCenter(loc, true)) { throw new GameActionException(CANT_DO_THAT, "Cannot mark tower pattern centered at (" + loc.x + ", " + loc.y + ") because it is too close to the edge of the map"); @@ -673,10 +678,10 @@ private void assertCanMarkResourcePattern(MapLocation loc) throws GameActionExce assertIsRobotType(this.robot.getType()); assertCanActLocation(loc, GameConstants.RESOURCE_PATTERN_RADIUS_SQUARED); - if (!this.gameWorld.isValidPatternCenter(loc)) { + if (!this.gameWorld.isValidPatternCenter(loc, false)) { throw new GameActionException(CANT_DO_THAT, "Cannot mark resource pattern centered at (" + loc.x + ", " + loc.y - + ") because it is too close to the edge of the map"); + + ") because it is blocked or too close to the edge of the map"); } if (this.robot.getPaint() < GameConstants.MARK_PATTERN_PAINT_COST){ @@ -708,6 +713,20 @@ public void markResourcePattern(MapLocation loc, int rotationAngle, boolean refl this.gameWorld.markResourcePattern(getTeam(), loc, rotationAngle, reflect); } + private UnitType getBaseUnitType(UnitType type){ + UnitType baseType; + switch (type){ + case LEVEL_TWO_DEFENSE_TOWER: baseType = UnitType.LEVEL_ONE_DEFENSE_TOWER; break; + case LEVEL_THREE_DEFENSE_TOWER: baseType = UnitType.LEVEL_ONE_DEFENSE_TOWER; break; + case LEVEL_TWO_MONEY_TOWER: baseType = UnitType.LEVEL_ONE_MONEY_TOWER; break; + case LEVEL_THREE_MONEY_TOWER: baseType = UnitType.LEVEL_ONE_MONEY_TOWER; break; + case LEVEL_TWO_PAINT_TOWER: baseType = UnitType.LEVEL_ONE_PAINT_TOWER; break; + case LEVEL_THREE_PAINT_TOWER: baseType = UnitType.LEVEL_ONE_PAINT_TOWER; break; + default: baseType = type; + } + return baseType; + } + private void assertCanCompleteTowerPattern(UnitType type, MapLocation loc) throws GameActionException { assertIsRobotType(this.robot.getType()); assertIsTowerType(type); @@ -725,7 +744,13 @@ private void assertCanCompleteTowerPattern(UnitType type, MapLocation loc) throw + ") because the center is not a ruin"); } - if (!this.gameWorld.isValidPatternCenter(loc)) { + if (getMoney() < getBaseUnitType(type).moneyCost){ + throw new GameActionException(CANT_DO_THAT, + "Cannot complete tower pattern centered at (" + loc.x + ", " + loc.y + + ") because the team does not have enough money!"); + } + + if (!this.gameWorld.isValidPatternCenter(loc, true)) { throw new GameActionException(CANT_DO_THAT, "Cannot complete tower pattern centered at (" + loc.x + ", " + loc.y + ") because it is too close to the edge of the map"); @@ -768,6 +793,7 @@ public void completeTowerPattern(UnitType type, MapLocation loc) throws GameActi assertCanCompleteTowerPattern(type, loc); this.gameWorld.completeTowerPattern(getTeam(), type, loc); InternalRobot tower = this.gameWorld.getRobot(loc); + this.gameWorld.getTeamInfo().addMoney(getTeam(), -getBaseUnitType(type).moneyCost); this.gameWorld.getMatchMaker().addSpawnAction(tower.getID(), loc, tower.getTeam(), type); this.gameWorld.getMatchMaker().addBuildAction(tower.getID()); } @@ -776,10 +802,10 @@ private void assertCanCompleteResourcePattern(MapLocation loc) throws GameAction assertIsRobotType(this.robot.getType()); assertCanActLocation(loc, GameConstants.RESOURCE_PATTERN_RADIUS_SQUARED); - if (!this.gameWorld.isValidPatternCenter(loc)) { + if (!this.gameWorld.isValidPatternCenter(loc, false)) { throw new GameActionException(CANT_DO_THAT, "Cannot complete resource pattern centered at (" + loc.x + ", " + loc.y - + ") because it is too close to the edge of the map"); + + ") because it is blocked or too close to the edge of the map"); } boolean valid = this.gameWorld.checkResourcePattern(this.robot.getTeam(), loc);