diff --git a/AggroMiner/AggroMiner.ts b/AggroMiner/AggroMiner.ts index 8fd15df..c97b141 100644 --- a/AggroMiner/AggroMiner.ts +++ b/AggroMiner/AggroMiner.ts @@ -6,7 +6,7 @@ * Loadout: regen 3, thrusters 3, mine 1 * To be tougher, can substitute armor for thrusters or regen. */ -const update = function() { +update = function() { // Controls EWMA limits: for mine-only we want to keep this pretty tight const TEAM_MIN_DIST = 2.5; const TEAM_MAX_DIST = 6; diff --git a/ArtilleryMicro/ArtilleryMicro.ts b/ArtilleryMicro/ArtilleryMicro.ts index 63bce74..b0bd178 100644 --- a/ArtilleryMicro/ArtilleryMicro.ts +++ b/ArtilleryMicro/ArtilleryMicro.ts @@ -4,7 +4,7 @@ * Defense loadout: artillery 3, shield/reflect 3, thrusters 1 * Offense loadout: artillery 2, regen 3, thrusters 1, mine 1 */ -const update = function() { +update = function() { // Controls whether artillery attack without sensors const AGGRESSIVE = false; // Controls EWMA limits: these can be a bit higher than for missiles diff --git a/LaserSquad/LaserSquad.ts b/LaserSquad/LaserSquad.ts new file mode 100644 index 0000000..214a6c4 --- /dev/null +++ b/LaserSquad/LaserSquad.ts @@ -0,0 +1,270 @@ +/** + * Laser Squad + * + * A laser team that holds a diagonal formation in order to take advantage of + * the range and damage of lasers without the weaknesses. Can also lay mines. + * + * Example loadouts: + * - laser 3, shield 3, mine 1 (defensive) + * - laser 3, thrusters 3, mine 1 (fast mine carpet) + */ +type StuckSide = "edge_rear" | "edge_side"; +type Order = Direction | StuckSide | "clear" | "active"; + +// Globals +declare let TEAM_SIZE, lastHBCount; + +init = function() { + // Initialize the array. + saveOrder("active"); + initHeartbeat(); + TEAM_SIZE = 4; + debugLog("starting team size", TEAM_SIZE); +}; + +update = function() { + const SHIELD_RANGE = 5; + + // Laser squad data has 3 states: + // - 'active': default state, lay mines or fire lasers. + // - 'clear': no enemies seen, can accept a move order. + // - 'edge_rear', 'edge_side': the bot is against a wall and cannot move + // back or sideways. + // - : one bot decided to move, so we all move in this direction. + // + // Rules: + // - Move orders are always followed first if received. + // - Reverse move orders can be issued by any bot, if no one on the team is + // stuck. + // - Forward move orders are only issued if all bots in the squad show + // 'clear': + // + // In default state, do the following: + // 1. Fire at visible enemies + // 2. Lay mines + // 3. Blind-fire lasers + const observedTeamSize = updateHeartbeat(); + if (observedTeamSize < TEAM_SIZE) { + debugLog("seems someone died, team size", observedTeamSize); + resetTeamState(); + } + TEAM_SIZE = observedTeamSize; + + const order = getOrder(); + let state: Order = "active"; + + // If we need to move to maintain formation, always do that first. + if (isDirection(order)) { + if (canMove(order)) { + saveOrder(state); // active + move(order); + } + } else { + // Only if we didn't try to process a directional move + // Check if we're against a wall + if (x == 0) { + state = "edge_rear"; + saveOrder(state); + } else if (y == 0 || y == arenaHeight - 1) { + state = "edge_side"; + saveOrder(state); + } + } + + // Do we see anything nearby? + const closestEnemy = findEntity( + ENEMY, + ANYTHING, + SORT_BY_DISTANCE, + SORT_ASCENDING + ); + if (!exists(closestEnemy)) { + if (!isStuck(state)) { + state = "clear"; + saveOrder(state); + } + tryShieldFriendlyBots(SHIELD_RANGE); + tryLayMine(); + // If we don't see anything and can activate sensors, go ahead. + // tryActivateSensors(); + // Sensors and still don't see anything? + tryTeamMoveForward(); + + // If we still haven't moved at this point, just blind fire lasers. + blindFireLasers(); + } + + const closestEnemyBot = findEntity( + ENEMY, + BOT, + SORT_BY_DISTANCE, + SORT_ASCENDING + ); + if (!exists(closestEnemyBot)) { + // TODO: set to 'clear' if the enemy chip or CPU is not in firing range. + + // Not clear, chip or CPU. + tryShieldFriendlyBots(SHIELD_RANGE); + tryLayMine(); + tryFireLasers(); + blindFireLasers(); + } + + // Now we know there's a bot nearby. + if (!isStuck(state)) { + state = "active"; + saveOrder(state); + } + + const numEnemyBots = size(findEntities(ENEMY, BOT, false)); + const enemyBotDistance = getDistanceTo(closestEnemyBot); + + if (enemyBotDistance < 3.1 || numEnemyBots > 2) { + tryTeamMoveBack(); + } + + if (enemyBotDistance < 5.1) { + // Defenses + tryShieldFriendlyBots(SHIELD_RANGE); + tryReflect(); + if (!isShielded()) tryShieldSelf(); + // Weapons + tryFireLasers(); + tryLayMine(); + } + + // Last resort moves + tryFireLasers(); + tryLayMine(); + blindFireLasers(); +}; + +const initHeartbeat = function() { + if (sharedC == undefined) sharedC = 0; + sharedC = sharedC + 1; + lastHBCount = sharedC; + debugLog("starting count", lastHBCount); +}; + +const updateHeartbeat = function() { + sharedC = sharedC + 1; + const botsAlive = sharedC - lastHBCount; + lastHBCount = sharedC; + return botsAlive; +}; + +/** + * If all bots on the team show 'clear' or against edge, we can issue a move forward. + */ +const tryTeamMoveForward = function() { + // Load shared data + array1 = sharedD; + array2 = sharedE; + + debugLog("trying forward", array1); + + // Check if everyone can move forward. + let i = 0; + for (i = 0; i < size(array1); i++) { + const val = array1[i] as Order; + if (val !== "clear" && val !== "edge_rear" && val !== "edge_side") { + // We can't move forward + return; + } + } + + debugLog("Starting team forward move"); + // We can! Tell everyone to move forward. + const me = getEntityAt(x, y); + for (i = 0; i < size(array2); i++) { + // Continue statements not allowed! + if (array2[i] !== me) array1[i] = "forward"; + } + + // Save shared data + sharedD = array1; + sharedE = array2; + // Terminator + move("forward"); +}; + +const tryTeamMoveBack = function() { + // Load shared data + array1 = sharedD; + array2 = sharedE; + + // Check if everyone can move back. This basically means no one's already + // got a move order, or against the back. So everyone must be 'active', + // 'clear', or 'edge_side'. + let i = 0; + for (i = 0; i < size(array1); i++) { + const val = array1[i] as Order; + if (val !== "clear" && val !== "active" && val !== "edge_side") { + // We can't move backward + return; + } + } + + debugLog("Starting team backward move"); + // Tell everyone to move backward. + const me = getEntityAt(x, y); + for (i = 0; i < size(array2); i++) { + if (array2[i] !== me) array1[i] = "backward"; + } + + // Save shared data + sharedD = array1; + sharedE = array2; + // Terminator + move("backward"); +}; + +/** + * Reset the movement states of potentially dead bots, so they don't mess up the + * coordination algorithm. + */ +const resetTeamState = function() { + // Load shared data + array1 = sharedD; + array2 = sharedE; + + let i = 0; + for (i = 0; i < size(array1); i++) { + const val = array1[i] as Order; + if (val == "active") array1[i] = "clear"; + } + + // Save shared data + sharedD = array1; + sharedE = array2; +}; + +const getOrder = function(): Order { + return getData() as Order; +}; + +const saveOrder = function(order: Order) { + saveData(order); +}; + +const isDirection = function(order: Order): order is Direction { + return ( + order == "up" || + order == "down" || + order == "left" || + order == "right" || + order == "forward" || + order == "backward" + ); +}; + +const isStuck = function(order: Order): order is StuckSide { + return order == "edge_rear" || order == "edge_side"; +}; + +const blindFireLasers = function() { + // TODO: can improve this based on X position. + if (percentChance(50)) fireLasers("forward"); + if (y > floor(arenaHeight / 2)) fireLasers("up"); + else fireLasers("down"); +}; diff --git a/LaserSquad/tsconfig.json b/LaserSquad/tsconfig.json new file mode 100644 index 0000000..7581901 --- /dev/null +++ b/LaserSquad/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig-botland", + "compilerOptions": { + "outFile": "../build/LaserSquad.js" + }, + "files": ["LaserSquad.ts"] +} diff --git a/MissileKite/MissileKite.ts b/MissileKite/MissileKite.ts index 1e539ee..2ccad28 100644 --- a/MissileKite/MissileKite.ts +++ b/MissileKite/MissileKite.ts @@ -17,7 +17,7 @@ * Missiles 3, thrusters 2, shield 2 is possible to use against enemies with * high reflect and no thrusters (it saves 1 reflection). */ -const update = function() { +update = function() { const TEAM_MIN_DIST = 2.5; const TEAM_MAX_DIST = 6.5; diff --git a/SmartMelee/SmartMelee.ts b/SmartMelee/SmartMelee.ts index 7eef6d5..60580a1 100644 --- a/SmartMelee/SmartMelee.ts +++ b/SmartMelee/SmartMelee.ts @@ -5,7 +5,7 @@ * Note that when using thrusters with zapper, damage is dealt every time the * bot moves. So effective DPS can be twice as high (!!) as with normal zapper. */ -const update = function() { +update = function() { // Equip when we see someone, not blindly const closestEnemyBot = findEntity( ENEMY, diff --git a/Sneaktillery/Sneaktillery.ts b/Sneaktillery/Sneaktillery.ts index 276ec1f..1f6e1b4 100644 --- a/Sneaktillery/Sneaktillery.ts +++ b/Sneaktillery/Sneaktillery.ts @@ -10,7 +10,7 @@ * Sneaktillery will try to get into one of the positions that is 5 spaces away * from the CPU and just unload until it has exploded. */ -const update = function() { +update = function() { // Whether we should use sensors (useful to shoot chips from afar) const USE_SENSORS = false; // Whether we should run if an enemy happens upon us and we have TP, to try diff --git a/ZapKite/ZapKite.ts b/ZapKite/ZapKite.ts index 82b644e..d045957 100644 --- a/ZapKite/ZapKite.ts +++ b/ZapKite/ZapKite.ts @@ -14,7 +14,7 @@ * Turn 9: cloak (zap ends) * Rinse and repeat. */ -const update = function() { +update = function() { // The first thing of chaos zapping is to never disrupt the cycle. Always // get the counter and increment it before doing anything else. const CYCLEN = 9; diff --git a/lib/utils.ts b/lib/utils.ts index 2983181..801d4a1 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -110,6 +110,14 @@ const tryFireMissiles = function() { } }; +const tryFireLasers = function() { + if (willLasersHit()) { + const gank = findEntity(ENEMY, BOT, SORT_BY_LIFE, SORT_ASCENDING); + if (willLasersHit(gank)) fireLasers(gank); + fireLasers(); + } +}; + const tryFireArtillery = function() { if (willArtilleryHit()) { const gank = findEntity(ENEMY, BOT, SORT_BY_LIFE, SORT_ASCENDING); diff --git a/tsconfig.json b/tsconfig.json index 5ed23af..47405b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,9 @@ { "path": "./ArtilleryMicro" }, + { + "path": "./LaserSquad" + }, { "path": "./MissileKite" }, diff --git a/types/bot-land/index.d.ts b/types/bot-land/index.d.ts index 49362c7..a319fd4 100644 --- a/types/bot-land/index.d.ts +++ b/types/bot-land/index.d.ts @@ -37,7 +37,14 @@ declare type Direction = | "forward" | "backward"; -// Terminators +/** + * Function called once each phase. + */ +declare let init: () => void; +/** + * Function called every turn. + */ +declare let update: () => void; // Functions declare function debugLog(...stuff: any[]): void; @@ -97,7 +104,7 @@ declare function melee(entity?: Entity): void; declare function willArtilleryHit(entity?: Entity): boolean; declare function fireArtillery(entity?: Entity): void; declare function willLasersHit(entity?: Entity): boolean; -declare function fireLasers(entity?: Entity): void; +declare function fireLasers(target?: Entity | Direction): void; declare function willMissilesHit(entity?: Entity): boolean; declare function fireMissiles(entity?: Entity): void;