From e3a9311c4e377a7144667a637781dd904bf52ffe Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 19:10:32 +0530 Subject: [PATCH 01/29] chokepoint detection + turret spot planner --- mapanalysis.py | 777 +++++++++++++++++++++++++++++++++++++++++++++ turretplan.py | 831 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1608 insertions(+) create mode 100644 mapanalysis.py create mode 100644 turretplan.py diff --git a/mapanalysis.py b/mapanalysis.py new file mode 100644 index 0000000..c226589 --- /dev/null +++ b/mapanalysis.py @@ -0,0 +1,777 @@ +"""chokepoint finder for the shared tile map. + +one team-wide resumable job. builders call note_structural_tile only on +passable<->blocking changes, dupes get coalesced here. results are plain +(x, y) tuples so this file doesnt need fcode. + +smoke test, two rooms + a 1 wide corridor:: + + >>> grid = [[2] * 20 for _ in range(20)] + >>> for x in range(1, 8): + ... for y in range(3, 17): grid[x][y] = 0 + >>> for x in range(12, 19): + ... for y in range(3, 17): grid[x][y] = 0 + >>> for x in range(8, 12): grid[x][10] = 0 + >>> result, _ = analyze_for_test(grid) + >>> len(result) + 1 + >>> result[0][:2] + ((10, 10), 1) + >>> open_map = [[0] * 20 for _ in range(20)] + >>> analyze_for_test(open_map)[0] + [] + +maps are map[x][y] like Player.fullMap. +""" + +from collections import deque + + +REGION_MIN_RADIUS = 3 # min clearance for a big-room region node +MAX_CHOKE_RADIUS = 2 # widest thing we still call a choke +RATIO_SMALL = 0.7 # choke/smaller-room ratio past which rooms just merge +RATIO_LARGE = 0.5 # same but vs the bigger room +RATIO_TWO_CHOKE = 0.8 # collapse 2-exit sliver regions with a wide exit +CPU_BUDGET_US = 6000 # no analysis cpu past this point in a turn +STEP_SLICE = 200 # max queue items per step() slice + +CLEARANCE = 0 +SKELETON = 1 +PRUNE = 2 +REGIONS = 3 +CHOKES = 4 +MERGE = 5 +DONE = 6 + + +struct_version = 0 +chokes = [] +region_partition = [] +region_nodes = [] +job = None +_debug_items = [] +_debug_cursor = 0 + +_map_width = 0 +_map_height = 0 +_structural_map = [] + + +def _index(x, y, height): + return x * height + y + + +def configure_map(width, height): + """Initialise the shared structural map; unexplored tiles are blocking.""" + global _map_width, _map_height, _structural_map, struct_version, job + global chokes, region_partition, region_nodes, _debug_items, _debug_cursor + if width == _map_width and height == _map_height and _structural_map: + return + _map_width = width + _map_height = height + _structural_map = [True] * (width * height) + struct_version += 1 + job = None + chokes = [] + region_partition = [] + region_nodes = [] + _debug_items = [] + _debug_cursor = 0 + + +def reset_map(width, height): + """Start a fresh match even when its dimensions match the previous one.""" + global _map_width, _map_height, _structural_map, struct_version, job + global chokes, region_partition, region_nodes, _debug_items, _debug_cursor + _map_width = width + _map_height = height + _structural_map = [True] * (width * height) + struct_version += 1 + job = None + chokes = [] + region_partition = [] + region_nodes = [] + _debug_items = [] + _debug_cursor = 0 + + +def note_structural_tile(x, y, blocking, width, height): + """Record one structural observation and bump the version only if needed.""" + global struct_version, job, chokes, region_partition, region_nodes + global _debug_items, _debug_cursor + configure_map(width, height) + idx = _index(x, y, height) + blocking = bool(blocking) + if _structural_map[idx] == blocking: + return False + _structural_map[idx] = blocking + struct_version += 1 + job = None + chokes = [] + region_partition = [] + region_nodes = [] + _debug_items = [] + _debug_cursor = 0 + return True + + +def get_job(): + """Return the current-version singleton job, creating it lazily.""" + global job + if not _structural_map: + return None + if job is None or job.version != struct_version: + job = AnalysisJob( + _map_width, _map_height, _structural_map, struct_version + ) + return job + + +def region_at(x, y): + """Return the published region id for a tile, or -1 if none is ready.""" + if (not region_partition or x < 0 or y < 0 + or x >= _map_width or y >= _map_height): + return -1 + return region_partition[_index(x, y, _map_height)] + + +def draw_debug(ct, position_factory, deadline_us=9000): + """Draw the published red chokes and green nodes in bounded spare slices.""" + global _debug_cursor + items = _debug_items + while (_debug_cursor < len(items) + and ct.get_cpu_time_elapsed() < deadline_us): + tile, red, green, blue = items[_debug_cursor] + _debug_cursor += 1 + ct.draw_indicator_dot(position_factory(tile[0], tile[1]), + red, green, blue) + + +class AnalysisJob: + """Small-slice phase machine; callers enforce the per-turn CPU deadline.""" + + def __init__(self, width, height, blocking, version): + self.width = width + self.height = height + self.count = width * height + self.blocking = blocking[:] + self.version = version + self.phase = CLEARANCE + + self.clearance = [-1] * self.count + self.skeleton = [False] * self.count + self.adj = [[] for _ in range(self.count)] + self.active = [False] * self.count + self.degree = [0] * self.count + self.raw_region_nodes = [] + self.region_index = [-1] * self.count + self.candidates = [] + + self._mode = "seed" + self._cursor = 0 + self._queue = deque() + self._candidate = -1 + self._candidate_scan = 0 + self._candidate_end = 0 + self._candidate_greater = False + self._region_cursor = 0 + self._neighbor_cursor = 0 + self._edge_seen = set() + self._walk = None + + self.parent = [] + self.root_clearance = [] + self.root_size = [] + self.keep = [] + self.partition = [] + self._partition_clearance = [] + self._partition_size = [] + self._merge_cursor = 0 + self._ratio_buckets = [] + self._ratio_bucket = -1 + self._ratio_bucket_pos = 0 + self._incidence = [] + self._merge_changed = False + self._output = [] + + def step(self): + if self.phase == CLEARANCE: + self._step_clearance() + elif self.phase == SKELETON: + self._step_skeleton() + elif self.phase == PRUNE: + self._step_prune() + elif self.phase == REGIONS: + self._step_regions() + elif self.phase == CHOKES: + self._step_chokes() + elif self.phase == MERGE: + self._step_merge() + + def _neighbors(self, idx): + h = self.height + w = self.width + x, y = divmod(idx, h) + if x: + yield idx - h + if x + 1 < w: + yield idx + h + if y: + yield idx - 1 + if y + 1 < h: + yield idx + 1 + + def _step_clearance(self): + limit = STEP_SLICE + n = self.count + if self._mode == "seed": + end = min(n, self._cursor + limit) + for idx in range(self._cursor, end): + if self.blocking[idx]: + self.clearance[idx] = 0 + self._queue.append(idx) + self._cursor = end + if end == n: + self._mode = "bfs" if self._queue else "unbounded" + self._cursor = 0 + return + + if self._mode == "unbounded": + end = min(n, self._cursor + limit) + far = self.width + self.height + 1 + for idx in range(self._cursor, end): + if not self.blocking[idx]: + self.clearance[idx] = far + self._cursor = end + if end == n: + self.phase = SKELETON + self._mode = "mark" + self._cursor = 0 + return + + pops = 0 + clearance = self.clearance + queue = self._queue + while queue and pops < limit: + idx = queue.popleft() + next_distance = clearance[idx] + 1 + for other in self._neighbors(idx): + if clearance[other] < 0: + clearance[other] = next_distance + queue.append(other) + pops += 1 + if not queue: + self.phase = SKELETON + self._mode = "mark" + self._cursor = 0 + + def _step_skeleton(self): + n = self.count + end = min(n, self._cursor + STEP_SLICE) + if self._mode == "mark": + clearance = self.clearance + blocking = self.blocking + skeleton = self.skeleton + for idx in range(self._cursor, end): + if blocking[idx]: + continue + value = clearance[idx] + is_maximum = True + for other in self._neighbors(idx): + if clearance[other] > value: + is_maximum = False + break + skeleton[idx] = is_maximum + self._cursor = end + if end == n: + self._mode = "connect" + self._cursor = 0 + return + + skeleton = self.skeleton + adj = self.adj + h = self.height + w = self.width + for idx in range(self._cursor, end): + if not skeleton[idx]: + continue + x, y = divmod(idx, h) + if x + 1 < w and skeleton[idx + h]: + adj[idx].append(idx + h) + adj[idx + h].append(idx) + if y + 1 < h and skeleton[idx + 1]: + adj[idx].append(idx + 1) + adj[idx + 1].append(idx) + self._cursor = end + if end == n: + self.phase = PRUNE + self._mode = "init" + self._cursor = 0 + + def _step_prune(self): + if self._mode == "init": + end = min(self.count, self._cursor + STEP_SLICE) + for idx in range(self._cursor, end): + if self.skeleton[idx]: + self.active[idx] = True + degree = len(self.adj[idx]) + self.degree[idx] = degree + if degree == 1: + self._queue.append(idx) + self._cursor = end + if end == self.count: + self._mode = "peel" + return + + pops = 0 + while self._queue and pops < STEP_SLICE: + idx = self._queue.popleft() + pops += 1 + if not self.active[idx] or self.degree[idx] != 1: + continue + neighbor = -1 + for other in self.adj[idx]: + if self.active[other]: + neighbor = other + break + if neighbor < 0: + continue + if self.clearance[idx] < self.clearance[neighbor]: + self.active[idx] = False + self.degree[idx] = 0 + self.degree[neighbor] -= 1 + if self.degree[neighbor] == 1: + self._queue.append(neighbor) + if not self._queue: + self.phase = REGIONS + self._mode = "scan" + self._cursor = 0 + + def _finish_region_candidate(self): + if not self._candidate_greater: + idx = self._candidate + self.region_index[idx] = len(self.raw_region_nodes) + self.raw_region_nodes.append(idx) + self._candidate = -1 + + def _step_regions(self): + ops = 0 + h = self.height + w = self.width + while ops < STEP_SLICE: + if self._candidate >= 0: + idx = self._candidate + x, y = divmod(idx, h) + radius = self.clearance[idx] + while self._candidate_scan < self._candidate_end and ops < STEP_SLICE: + pos = self._candidate_scan + self._candidate_scan += 1 + ops += 1 + ox, oy = divmod(pos, h) + if (abs(ox - x) <= radius and abs(oy - y) <= radius + and self.active[pos] + and self.clearance[pos] > radius): + self._candidate_greater = True + self._candidate_scan = self._candidate_end + if self._candidate_scan >= self._candidate_end: + self._finish_region_candidate() + continue + + if self._cursor >= self.count: + self.phase = CHOKES + self._mode = "walk" + self._region_cursor = 0 + self._neighbor_cursor = 0 + return + + idx = self._cursor + self._cursor += 1 + ops += 1 + if not self.active[idx]: + continue + degree = self.degree[idx] + if degree != 2: + self.region_index[idx] = len(self.raw_region_nodes) + self.raw_region_nodes.append(idx) + elif self.clearance[idx] >= REGION_MIN_RADIUS: + radius = self.clearance[idx] + x, y = divmod(idx, h) + x0 = max(0, x - radius) + x1 = min(w - 1, x + radius) + y0 = max(0, y - radius) + y1 = min(h - 1, y + radius) + # scan the enclosing flat range; Chebyshev bounds are checked above + self._candidate = idx + self._candidate_scan = x0 * h + y0 + self._candidate_end = x1 * h + y1 + 1 + self._candidate_greater = False + + def _edge_key(self, left, right): + if left > right: + left, right = right, left + return left * self.count + right + + def _outside_seed(self, endpoint, inward): + """Step off a local-max plateau toward its adjacent open region.""" + best = endpoint + best_clearance = -1 + for other in self._neighbors(endpoint): + if other == inward or self.blocking[other]: + continue + value = self.clearance[other] + if value > best_clearance: + best = other + best_clearance = value + return best + + def _begin_next_walk(self): + nodes = self.raw_region_nodes + while self._region_cursor < len(nodes): + start = nodes[self._region_cursor] + neighbors = self.adj[start] + while self._neighbor_cursor < len(neighbors): + other = neighbors[self._neighbor_cursor] + self._neighbor_cursor += 1 + if not self.active[other]: + continue + key = self._edge_key(start, other) + if key in self._edge_seen: + continue + self._edge_seen.add(key) + start_region = self.region_index[start] + values = [(self.clearance[start], start), + (self.clearance[other], other)] + minimum = min(values)[0] + minima = [tile for value, tile in values if value == minimum] + self._walk = [start_region, start, other, minimum, minima, + [start, other]] + return True + self._region_cursor += 1 + self._neighbor_cursor = 0 + return False + + def _step_chokes(self): + ops = 0 + while ops < STEP_SLICE: + if self._walk is None: + if not self._begin_next_walk(): + self.phase = MERGE + self._mode = "partition_raw_init" + return + start_region, previous, current, minimum, minima, path = self._walk + ops += 1 + current_region = self.region_index[current] + if current_region >= 0 and current_region != start_region: + if minimum <= MAX_CHOKE_RADIUS: + # prefer the middle of the minimum-clearance run + chosen = minima[len(minima) // 2] + left_pos = self._outside_seed(path[0], path[1]) + right_pos = self._outside_seed(path[-1], path[-2]) + self.candidates.append( + [minimum, chosen, start_region, current_region, + left_pos, right_pos] + ) + self._walk = None + continue + + next_tile = -1 + for other in self.adj[current]: + if self.active[other] and other != previous: + next_tile = other + break + if next_tile < 0: + self._walk = None + continue + key = self._edge_key(current, next_tile) + if key in self._edge_seen: + self._walk = None + continue + self._edge_seen.add(key) + value = self.clearance[next_tile] + if value < minimum: + minimum = value + minima = [next_tile] + elif value == minimum: + minima.append(next_tile) + path.append(next_tile) + self._walk = [start_region, current, next_tile, minimum, minima, path] + + def _start_partition(self, removed): + self.partition = [-1] * self.count + self._removed = removed + self._partition_cursor = 0 + self._component = -1 + self._queue.clear() + self._partition_clearance = [] + self._partition_size = [] + + def _step_partition(self, next_mode): + ops = 0 + while ops < STEP_SLICE: + if self._queue: + idx = self._queue.popleft() + ops += 1 + self._partition_size[self._component] += 1 + value = self.clearance[idx] + if value > self._partition_clearance[self._component]: + self._partition_clearance[self._component] = value + for other in self._neighbors(idx): + if (self.partition[other] < 0 and not self.blocking[other] + and other not in self._removed): + self.partition[other] = self._component + self._queue.append(other) + continue + while self._partition_cursor < self.count: + idx = self._partition_cursor + self._partition_cursor += 1 + ops += 1 + if (not self.blocking[idx] and idx not in self._removed + and self.partition[idx] < 0): + self._component += 1 + self._partition_clearance.append(0) + self._partition_size.append(0) + self.partition[idx] = self._component + self._queue.append(idx) + break + if ops >= STEP_SLICE: + return False + if self._partition_cursor >= self.count and not self._queue: + self._mode = next_mode + return True + return False + + def _find(self, node): + parent = self.parent + while parent[node] != node: + parent[node] = parent[parent[node]] + node = parent[node] + return node + + def _union(self, left, right): + left = self._find(left) + right = self._find(right) + if left == right: + return left + lc = self.root_clearance[left] + rc = self.root_clearance[right] + if rc > lc or (rc == lc and right < left): + left, right = right, left + self.parent[right] = left + self.root_clearance[left] = max(lc, rc) + self.root_size[left] += self.root_size[right] + return left + + def _candidate_components(self, candidate): + left = self.partition[candidate[4]] + right = self.partition[candidate[5]] + if left < 0: + left = self.partition[self.raw_region_nodes[candidate[2]]] + if right < 0: + right = self.partition[self.raw_region_nodes[candidate[3]]] + return left, right + + def _step_merge(self): + if self._mode == "partition_raw_init": + self._removed = set() + self._merge_cursor = 0 + self._mode = "partition_raw_remove" + return + if self._mode == "partition_raw_remove": + end = min(len(self.candidates), self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + self._removed.add(self.candidates[idx][1]) + self._merge_cursor = end + if end == len(self.candidates): + self._start_partition(self._removed) + self._mode = "partition_raw" + return + if self._mode == "partition_raw": + self._step_partition("ratio_init") + return + if self._mode == "ratio_init": + count = len(self._partition_size) + self.parent = list(range(count)) + self.root_clearance = self._partition_clearance[:] + self.root_size = self._partition_size[:] + self.keep = [False] * len(self.candidates) + self._ratio_buckets = [ + [] for _ in range(MAX_CHOKE_RADIUS + 1) + ] + self._merge_cursor = 0 + self._mode = "ratio_order" + return + if self._mode == "ratio_order": + end = min(len(self.candidates), self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + clearance = self.candidates[idx][0] + self._ratio_buckets[clearance].append(idx) + self._merge_cursor = end + if end == len(self.candidates): + self._ratio_bucket = MAX_CHOKE_RADIUS + self._ratio_bucket_pos = 0 + self._mode = "ratio" + return + if self._mode == "ratio": + ops = 0 + while self._ratio_bucket >= 0 and ops < STEP_SLICE: + bucket = self._ratio_buckets[self._ratio_bucket] + if self._ratio_bucket_pos >= len(bucket): + self._ratio_bucket -= 1 + self._ratio_bucket_pos = 0 + continue + candidate_index = bucket[self._ratio_bucket_pos] + self._ratio_bucket_pos += 1 + ops += 1 + candidate = self.candidates[candidate_index] + left, right = self._candidate_components(candidate) + if left < 0 or right < 0: + continue + left = self._find(left) + right = self._find(right) + if left == right: + continue + choke_clearance = candidate[0] + small = min(self.root_clearance[left], self.root_clearance[right]) + large = max(self.root_clearance[left], self.root_clearance[right]) + if (choke_clearance > RATIO_SMALL * small + or choke_clearance > RATIO_LARGE * large): + self._union(left, right) + else: + self.keep[candidate_index] = True + if self._ratio_bucket < 0: + self._mode = "incidence_init" + return + if self._mode == "incidence_init": + self._incidence = [[] for _ in self.parent] + self._merge_cursor = 0 + self._mode = "incidence" + return + if self._mode == "incidence": + end = min(len(self.candidates), self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + if not self.keep[idx]: + continue + left, right = self._candidate_components(self.candidates[idx]) + if left < 0 or right < 0: + self.keep[idx] = False + continue + left = self._find(left) + right = self._find(right) + if left == right: + self.keep[idx] = False + continue + self._incidence[left].append(idx) + self._incidence[right].append(idx) + self._merge_cursor = end + if end == len(self.candidates): + self._merge_cursor = 0 + self._merge_changed = False + self._mode = "two_choke" + return + if self._mode == "two_choke": + end = min(len(self.parent), self._merge_cursor + STEP_SLICE) + for region in range(self._merge_cursor, end): + if self._find(region) != region: + continue + exits = self._incidence[region] + if len(exits) != 2: + continue + wider = max(exits, key=lambda i: self.candidates[i][0]) + if (self.candidates[wider][0] + > RATIO_TWO_CHOKE * self.root_clearance[region]): + left, right = self._candidate_components(self.candidates[wider]) + self._union(left, right) + self.keep[wider] = False + self._merge_changed = True + break + self._merge_cursor = end + if self._merge_changed: + self._mode = "incidence_init" + elif end == len(self.parent): + self._mode = "final_partition_init" + return + if self._mode == "final_partition_init": + self._removed = set() + self._merge_cursor = 0 + self._mode = "final_partition_remove" + return + if self._mode == "final_partition_remove": + end = min(len(self.candidates), self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + if self.keep[idx]: + self._removed.add(self.candidates[idx][1]) + self._merge_cursor = end + if end == len(self.candidates): + self._start_partition(self._removed) + self._mode = "final_partition" + return + if self._mode == "final_partition": + self._step_partition("publish_init") + return + if self._mode == "publish_init": + self._output = [] + self._merge_cursor = 0 + self._mode = "publish" + return + if self._mode == "publish": + self._step_publish() + return + if self._mode == "debug_publish": + self._step_debug_publish() + + def _step_publish(self): + global chokes, region_partition, region_nodes + global _debug_items, _debug_cursor + h = self.height + end = min(len(self.candidates), self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + candidate = self.candidates[idx] + if not self.keep[idx]: + continue + left = self.partition[candidate[4]] + right = self.partition[candidate[5]] + if left < 0 or right < 0 or left == right: + continue + tile = divmod(candidate[1], h) + self._output.append((tile, candidate[0], + self._partition_size[left], + self._partition_size[right])) + self._merge_cursor = end + if end == len(self.candidates): + chokes = self._output[:] + region_partition = self.partition[:] + region_nodes = [] + _debug_items = [(choke[0], 255, 0, 0) for choke in chokes] + _debug_cursor = 0 + self._merge_cursor = 0 + self._mode = "debug_publish" + + def _step_debug_publish(self): + global region_nodes, _debug_items + end = min(len(self.raw_region_nodes), + self._merge_cursor + STEP_SLICE) + for idx in range(self._merge_cursor, end): + node = divmod(self.raw_region_nodes[idx], self.height) + region_nodes.append(node) + _debug_items.append((node, 0, 255, 0)) + self._merge_cursor = end + if end == len(self.raw_region_nodes): + self.phase = DONE + + +def analyze_for_test(full_map): + """Synchronously analyze an ``[x][y]`` synthetic map for doctests/tests.""" + if not full_map or not full_map[0]: + return [], [] + width = len(full_map) + height = len(full_map[0]) + blocking = [] + for x in range(width): + if len(full_map[x]) != height: + raise ValueError("full_map must be rectangular and indexed [x][y]") + for y in range(height): + blocking.append(full_map[x][y] in (-1, 2, 3)) + test_job = AnalysisJob(width, height, blocking, 0) + while test_job.phase != DONE: + test_job.step() + return chokes[:], region_partition[:] diff --git a/turretplan.py b/turretplan.py new file mode 100644 index 0000000..dd11bb4 --- /dev/null +++ b/turretplan.py @@ -0,0 +1,831 @@ +"""picks spots for defensive turrets, coordinated through the store. + +store layout (0..7 belong to the map sharing layer): + +* 8..11: two 16 bit enemy infra sightings per u32, xxxxx yyyyy ttt fff + (x, y, type, round//16 mod 8). zero = empty. +* 12..13: two 13 bit friendly turret records per u32: x, y, type. zero = empty. +* 14: current proposal (x, y, type, facing, frontier, urgent, valid, score + band, generation). +* 15: claim slot for the urgent builder (number, manhattan distance, age, + proposal fingerprint). one elected builder updates it per round so it + settles on the nearest idle bot. + +the store is a lossy cache, not the source of truth: writers retry until a +sighting shows up in the next-round snapshot, readers merge it with their +own memory. + +engine test scenario: two rooms joined by a 1 wide choke at (7, 5), our core +(2, 5), enemy core (12, 5), enemy harvesters at (10, 4..6), (5, 5) passable +on our side and outside known enemy turret range. top proposal should be a +SENTINEL at (5, 5) facing EAST, its band crosses the choke and covers the +cluster where a gunner straight ray cant. +""" + +from collections import namedtuple + +from fcode import Direction, EntityType, Position + +import mapanalysis + + +# scoring tunables +W_COVER = 3 +W_CHOKE = 4 +W_MUTUAL = 2 +W_SAFE = 1 +W_EXPOSED = 4 +W_FRONTIER_PENALTY = 6 +URGENT_THRESHOLD = 25 + +# operational tunables +CPU_BUDGET_US = 6000 +TITANIUM_RESERVE = 20 +FRESHNESS_ROUNDS = 16 +MAX_FRESH_AGE = 3 +CLAIM_MAX_AGE = 10 +CORE_SPAWN_RING_MANHATTAN = 2 # 12 surrounding tiles: d=1 plus d=2. + +INFRA_SLOT_FIRST = 8 +INFRA_SLOT_COUNT = 4 +TURRET_SLOT_FIRST = 12 +TURRET_SLOT_COUNT = 2 +PROPOSAL_SLOT = 14 +PROPOSAL_CHECK_SLOT = 15 + +INFRA_NONE = 0 +INFRA_HARVESTER = 1 +INFRA_CONVEYOR = 2 +INFRA_SPLITTER = 3 +INFRA_GUNNER = 4 +INFRA_SENTINEL = 5 +INFRA_CORE = 6 + +ENTITY_TO_INFRA = { + EntityType.HARVESTER: INFRA_HARVESTER, + EntityType.CONVEYOR: INFRA_CONVEYOR, + EntityType.SPLITTER: INFRA_SPLITTER, + EntityType.GUNNER: INFRA_GUNNER, + EntityType.SENTINEL: INFRA_SENTINEL, + EntityType.CORE: INFRA_CORE, +} +TARGET_VALUE = { + INFRA_HARVESTER: 5, + INFRA_CONVEYOR: 2, + INFRA_SPLITTER: 2, + INFRA_GUNNER: 3, + INFRA_SENTINEL: 3, + INFRA_CORE: 10, +} +TURRET_TYPES = (INFRA_GUNNER, INFRA_SENTINEL) + +FACINGS = ( + Direction.NORTH, + Direction.EAST, + Direction.SOUTH, + Direction.WEST, +) +FACING_DELTAS = ((0, -1), (1, 0), (0, 1), (-1, 0)) + +Proposal = namedtuple( + "Proposal", + "x y turret_type facing score frontier urgent frontier_choke generation", +) + + +def _manhattan(a, b): + return abs(a[0] - b[0]) + abs(a[1] - b[1]) + + +def _epoch(round_number): + return (round_number // FRESHNESS_ROUNDS) & 0x7 + + +def _age(now_epoch, then_epoch): + return (now_epoch - then_epoch) & 0x7 + + +def _pack_infra(x, y, type_code, freshness): + if type_code == INFRA_NONE: + return 0 + return ((x & 0x1F) | ((y & 0x1F) << 5) + | ((type_code & 0x7) << 10) | ((freshness & 0x7) << 13)) + + +def _unpack_infra(value, width, height): + if value == 0: + return None + x = value & 0x1F + y = (value >> 5) & 0x1F + type_code = (value >> 10) & 0x7 + freshness = (value >> 13) & 0x7 + if x >= width or y >= height or type_code not in TARGET_VALUE: + return None + return (x, y, type_code, freshness) + + +def _pack_turret(x, y, type_code): + if type_code not in TURRET_TYPES: + return 0 + return (x & 0x1F) | ((y & 0x1F) << 5) | ((type_code & 0x7) << 10) + + +def _unpack_turret(value, width, height): + if value == 0: + return None + x = value & 0x1F + y = (value >> 5) & 0x1F + type_code = (value >> 10) & 0x7 + if x >= width or y >= height or type_code not in TURRET_TYPES: + return None + return (x, y, type_code) + + +def _record_at(word, record_bits, index): + return (word >> (record_bits * index)) & ((1 << record_bits) - 1) + + +def _replace_record(word, record_bits, index, value): + mask = ((1 << record_bits) - 1) << (record_bits * index) + return (word & ~mask) | ((value << (record_bits * index)) & mask) + + +def _pack_proposal(proposal): + if proposal is None: + return 0 + # derive the generation from the proposal itself so independently running + # builder planners publish an identical word for an identical top choice. + generation = ((proposal.x * 3 + proposal.y * 5 + + proposal.turret_type * 7 + + FACINGS.index(proposal.facing) * 11 + + int(proposal.score)) & 0xF) + type_code = proposal.turret_type + facing_code = FACINGS.index(proposal.facing) + score_band = max(-512, min(511, int(proposal.score))) + 512 + value = ((proposal.x & 0x1F) + | ((proposal.y & 0x1F) << 5) + | ((type_code & 0x7) << 10) + | ((facing_code & 0x3) << 13) + | (int(bool(proposal.frontier)) << 15) + | (int(bool(proposal.urgent)) << 16) + | (1 << 17) + | ((score_band & 0x3FF) << 18) + | ((generation & 0xF) << 28)) + return value & 0xFFFFFFFF + + +def _unpack_proposal(value, width, height): + if not ((value >> 17) & 1): + return None + x = value & 0x1F + y = (value >> 5) & 0x1F + type_code = (value >> 10) & 0x7 + facing_code = (value >> 13) & 0x3 + if x >= width or y >= height or type_code not in TURRET_TYPES: + return None + score = ((value >> 18) & 0x3FF) - 512 + frontier = bool((value >> 15) & 1) + urgent = bool((value >> 16) & 1) + generation = (value >> 28) & 0xF + return Proposal(x, y, type_code, FACINGS[facing_code], score, + frontier, urgent, frontier, generation) + + +def _proposal_fingerprint(value): + return (value ^ (value >> 14)) & 0x3FFF + + +def _pack_claim(builder_num, distance, generation, round_number, proposal_word): + return (((builder_num - 1) & 0x7) + | ((min(distance, 63) & 0x3F) << 3) + | ((generation & 0xF) << 9) + | ((round_number & 0xF) << 13) + | (1 << 17) + | (_proposal_fingerprint(proposal_word) << 18)) + + +def _unpack_claim(value, proposal_word, round_number): + if not ((value >> 17) & 1): + return None + if ((value >> 18) & 0x3FFF) != _proposal_fingerprint(proposal_word): + return None + generation = (value >> 9) & 0xF + if generation != ((proposal_word >> 28) & 0xF): + return None + claim_round = (value >> 13) & 0xF + claim_age = ((round_number & 0xF) - claim_round) & 0xF + if claim_age > CLAIM_MAX_AGE: + return None + return ((value & 0x7) + 1, (value >> 3) & 0x3F) + + +def _in_envelope(origin, target, type_code, facing_index): + """Raw dev23 turret geometry; heuristic distances elsewhere are Manhattan.""" + dx = target[0] - origin[0] + dy = target[1] - origin[1] + forward_x, forward_y = FACING_DELTAS[facing_index] + forward = dx * forward_x + dy * forward_y + if forward <= 0: + return False + if type_code == INFRA_GUNNER: + lateral = abs(dx * forward_y - dy * forward_x) + return lateral == 0 and dx * dx + dy * dy <= 13 + lateral = abs(dx * forward_y - dy * forward_x) + return lateral <= 1 and dx * dx + dy * dy <= 32 + + +def _covered_by_unknown_facing(turret, tile): + origin = (turret[0], turret[1]) + type_code = turret[2] + for facing_index in range(4): + if _in_envelope(origin, tile, type_code, facing_index): + return True + return False + + +class TurretPlanner: + """Per-builder shared-cache reader and resumable scorer.""" + + def __init__(self, width, height, team_core): + self.width = width + self.height = height + self.team_core = (team_core.x, team_core.y) + + self.infra_memory = {} + self.turret_memory = {} + self.pending_infra = {} + self.pending_turrets = {} + self.current_proposal = None + self.current_claim = None + self._proposal_word = 0 + + self._infra_records = [None] * 8 + self._turret_records = [None] * 4 + self._raw_infra_words = [0] * 4 + self._raw_turret_words = [0] * 2 + + self._dependency_signature = None + self._scan_context = None + self._scan_cursor = 0 + self._scan_complete = False + self._top = [] + self.score_cache = {} + + # ------------------------------------------------------------------ store + + def sync_and_observe(self, ct): + """Read the next-round snapshot, verify visible entries, and retry one write.""" + now_epoch = _epoch(ct.get_current_round()) + writes = {} + + self._raw_infra_words = [ + ct.read_store(INFRA_SLOT_FIRST + i) for i in range(INFRA_SLOT_COUNT) + ] + self._infra_records = [] + shared_positions = set() + for word in self._raw_infra_words: + for half in range(2): + record = _unpack_infra(_record_at(word, 16, half), + self.width, self.height) + self._infra_records.append(record) + if record is None: + continue + x, y, type_code, freshness = record + if _age(now_epoch, freshness) <= MAX_FRESH_AGE: + old = self.infra_memory.get((x, y)) + if (old is None + or _age(now_epoch, freshness) < _age(now_epoch, old[1])): + self.infra_memory[(x, y)] = (type_code, freshness) + shared_positions.add((x, y, type_code)) + + for key, (_, freshness) in list(self.infra_memory.items()): + if _age(now_epoch, freshness) > MAX_FRESH_AGE: + del self.infra_memory[key] + for key, record in list(self.pending_infra.items()): + if (key[0], key[1], record[0]) in shared_positions: + del self.pending_infra[key] + + self._raw_turret_words = [ + ct.read_store(TURRET_SLOT_FIRST + i) & 0x03FFFFFF + for i in range(TURRET_SLOT_COUNT) + ] + self._turret_records = [] + shared_turrets = set() + for word in self._raw_turret_words: + for half in range(2): + record = _unpack_turret(_record_at(word, 13, half), + self.width, self.height) + self._turret_records.append(record) + if record is not None: + x, y, type_code = record + self.turret_memory[(x, y)] = type_code + shared_turrets.add(record) + for key, type_code in list(self.pending_turrets.items()): + if (key[0], key[1], type_code) in shared_turrets: + del self.pending_turrets[key] + + self._proposal_word = ct.read_store(PROPOSAL_SLOT) + self.current_proposal = _unpack_proposal( + self._proposal_word, self.width, self.height + ) + self.current_claim = None + if self.current_proposal is not None: + self.current_claim = _unpack_claim( + ct.read_store(PROPOSAL_CHECK_SLOT), self._proposal_word, + ct.get_current_round() + ) + + # empty or changed visible tiles explicitly clear stale cache records + verify_infra = {key: value[0] + for key, value in self.infra_memory.items()} + for record in self._infra_records: + if record is not None: + verify_infra[(record[0], record[1])] = record[2] + for (x, y), type_code in list(verify_infra.items()): + pos = Position(x, y) + if not ct.is_in_vision(pos): + continue + building_id = ct.get_tile_building_id(pos) + actual = None + if building_id is not None and ct.get_team(building_id) != ct.get_team(): + actual = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) + if actual != type_code: + self.infra_memory.pop((x, y), None) + self.pending_infra.pop((x, y), None) + self._clear_infra_position(x, y, writes) + + for (x, y), type_code in list(self.turret_memory.items()): + pos = Position(x, y) + if not ct.is_in_vision(pos): + continue + building_id = ct.get_tile_building_id(pos) + actual = None + if building_id is not None and ct.get_team(building_id) == ct.get_team(): + actual = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) + if actual != type_code: + del self.turret_memory[(x, y)] + self.pending_turrets.pop((x, y), None) + self._clear_turret_position(x, y, writes) + + # Any builder may originate sightings. Non-model infrastructure is ignored. + for building_id in ct.get_nearby_buildings(): + pos = ct.get_position(building_id) + type_code = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) + if type_code is None: + continue + if ct.get_team(building_id) != ct.get_team(): + self.infra_memory[(pos.x, pos.y)] = (type_code, now_epoch) + self.pending_infra[(pos.x, pos.y)] = (type_code, now_epoch) + elif type_code in TURRET_TYPES: + self.turret_memory[(pos.x, pos.y)] = type_code + self.pending_turrets[(pos.x, pos.y)] = type_code + + self._prune_memories(now_epoch) + self._flush_one_infra(writes, now_epoch) + self._flush_one_turret(writes) + for slot, word in writes.items(): + ct.write_store(slot, word & 0xFFFFFFFF) + if INFRA_SLOT_FIRST <= slot < INFRA_SLOT_FIRST + INFRA_SLOT_COUNT: + self._raw_infra_words[slot - INFRA_SLOT_FIRST] = word + elif TURRET_SLOT_FIRST <= slot < TURRET_SLOT_FIRST + TURRET_SLOT_COUNT: + self._raw_turret_words[slot - TURRET_SLOT_FIRST] = word + + def _prune_memories(self, now_epoch): + if len(self.infra_memory) > 8: + ordered = sorted( + self.infra_memory.items(), + key=lambda item: (_age(now_epoch, item[1][1]), + -TARGET_VALUE[item[1][0]], item[0]), + ) + keep = {key for key, _ in ordered[:8]} + for key in list(self.infra_memory): + if key not in keep: + del self.infra_memory[key] + self.pending_infra.pop(key, None) + if len(self.turret_memory) > 4: + enemy_core = self._enemy_core() + ordered = sorted(self.turret_memory, + key=lambda pos: (_manhattan(pos, enemy_core), pos)) + keep = set(ordered[:4]) + for key in list(self.turret_memory): + if key not in keep: + del self.turret_memory[key] + self.pending_turrets.pop(key, None) + + def _clear_infra_position(self, x, y, writes): + for index, record in enumerate(self._infra_records): + if record is None or record[:2] != (x, y): + continue + slot_offset, half = divmod(index, 2) + slot = INFRA_SLOT_FIRST + slot_offset + word = writes.get(slot, self._raw_infra_words[slot_offset]) + word = _replace_record(word, 16, half, 0) + writes[slot] = word + self._infra_records[index] = None + + def _clear_turret_position(self, x, y, writes): + for index, record in enumerate(self._turret_records): + if record is None or record[:2] != (x, y): + continue + slot_offset, half = divmod(index, 2) + slot = TURRET_SLOT_FIRST + slot_offset + word = writes.get(slot, self._raw_turret_words[slot_offset]) + word = _replace_record(word, 13, half, 0) + writes[slot] = word + self._turret_records[index] = None + + def _flush_one_infra(self, writes, now_epoch): + if not self.pending_infra: + return + key, (type_code, freshness) = min( + self.pending_infra.items(), + key=lambda item: (_age(now_epoch, item[1][1]), + -TARGET_VALUE[item[1][0]], item[0]), + ) + x, y = key + matching = [i for i, record in enumerate(self._infra_records) + if record is not None and record[:2] == (x, y)] + if matching: + index = matching[0] + else: + empty = [i for i, record in enumerate(self._infra_records) + if record is None] + if empty: + # A stable hash spreads simultaneous observers across the eight records. + start = (x * 31 + y * 7 + type_code) & 7 + index = min(empty, key=lambda i: (i - start) & 7) + else: + index = max( + range(8), + key=lambda i: (_age(now_epoch, self._infra_records[i][3]), + -TARGET_VALUE[self._infra_records[i][2]]), + ) + slot_offset, half = divmod(index, 2) + slot = INFRA_SLOT_FIRST + slot_offset + word = writes.get(slot, self._raw_infra_words[slot_offset]) + packed = _pack_infra(x, y, type_code, freshness) + writes[slot] = _replace_record(word, 16, half, packed) + self._infra_records[index] = (x, y, type_code, freshness) + + def _flush_one_turret(self, writes): + if not self.pending_turrets: + return + key, type_code = min(self.pending_turrets.items()) + x, y = key + matching = [i for i, record in enumerate(self._turret_records) + if record is not None and record[:2] == (x, y)] + if matching: + index = matching[0] + else: + empty = [i for i, record in enumerate(self._turret_records) + if record is None] + if empty: + index = empty[0] + else: + # with no freshness bits available, deterministic replacement is + # the only collision-safe policy that still admits newly built turrets. + index = (x * 5 + y * 3 + type_code) & 3 + slot_offset, half = divmod(index, 2) + slot = TURRET_SLOT_FIRST + slot_offset + word = writes.get(slot, self._raw_turret_words[slot_offset]) + packed = _pack_turret(x, y, type_code) + writes[slot] = _replace_record(word, 13, half, packed) + self._turret_records[index] = (x, y, type_code) + + # --------------------------------------------------------------- behaviour + + def can_afford(self, ct, proposal=None): + proposal = proposal or self.current_proposal + if proposal is None: + return False + if proposal.turret_type == INFRA_GUNNER: + cost = ct.get_gunner_cost() + else: + cost = ct.get_sentinel_cost() + return ct.get_global_resources() >= cost + TITANIUM_RESERVE + + def try_passive_build(self, ct): + proposal = self.current_proposal + if proposal is None or not self.can_afford(ct, proposal): + return None + pos = Position(proposal.x, proposal.y) + if ct.get_position().distance_squared(pos) > 2: + return None + if ct.get_action_cooldown() != 0: + return None + if proposal.turret_type == INFRA_GUNNER: + if not ct.can_build_gunner(pos, proposal.facing): + return None + ct.build_gunner(pos, proposal.facing) + else: + if not ct.can_build_sentinel(pos, proposal.facing): + return None + ct.build_sentinel(pos, proposal.facing) + self.record_built(ct, proposal) + return proposal + + def record_built(self, ct, proposal): + key = (proposal.x, proposal.y) + self.turret_memory[key] = proposal.turret_type + self.pending_turrets[key] = proposal.turret_type + matching = [i for i, record in enumerate(self._turret_records) + if record is not None and record[:2] == key] + if matching: + index = matching[0] + else: + empty = [i for i, record in enumerate(self._turret_records) + if record is None] + index = (empty[0] if empty else + (proposal.x * 5 + proposal.y * 3 + + proposal.turret_type) & 3) + slot_offset, half = divmod(index, 2) + packed = _pack_turret(proposal.x, proposal.y, proposal.turret_type) + word = _replace_record(self._raw_turret_words[slot_offset], + 13, half, packed) + self._raw_turret_words[slot_offset] = word + self._turret_records[index] = ( + proposal.x, proposal.y, proposal.turret_type + ) + ct.write_store(TURRET_SLOT_FIRST + slot_offset, word) + # stop same-round followers from perpetuating the now-occupied proposal + ct.write_store(PROPOSAL_SLOT, 0) + ct.write_store(PROPOSAL_CHECK_SLOT, 0) + + def should_pursue_urgent(self, ct, my_num): + proposal = self.current_proposal + if (proposal is None or my_num < 1 or my_num > 8 + or not proposal.urgent or not proposal.frontier + or not self.can_afford(ct, proposal) + or ct.get_action_cooldown() != 0 + or ct.get_move_cooldown() != 0): + return False + target = (proposal.x, proposal.y) + my_pos = ct.get_position() + my_distance = _manhattan((my_pos.x, my_pos.y), target) + if my_pos.distance_squared(Position(*target)) <= 2: + return False + candidate = (my_distance, my_num) + claimed = None + if self.current_claim is not None: + claimed = (self.current_claim[1], self.current_claim[0]) + spawn_count = ct.read_store(0) + elected = (spawn_count > 0 + and ct.get_current_round() % spawn_count + 1 == my_num) + if (elected and (claimed is None or candidate < claimed + or self.current_claim[0] == my_num)): + claim_word = _pack_claim( + my_num, my_distance, proposal.generation, + ct.get_current_round(), self._proposal_word + ) + ct.write_store(PROPOSAL_CHECK_SLOT, claim_word) + # One elected builder updates the monotone minimum each round. A new + # better claim becomes visible next round; once visible only that + # builder paths. Dead/non-idle claimants eventually age out. + return self.current_claim is not None and self.current_claim[0] == my_num + + # ---------------------------------------------------------------- scoring + + def advance(self, ct, full_map, my_num): + """Incrementally rescore only during the same 6000-us spare-cycle cap.""" + analysis_job = mapanalysis.job + if (analysis_job is None or analysis_job.phase != mapanalysis.DONE + or not mapanalysis.region_partition + or ct.get_cpu_time_elapsed() >= CPU_BUDGET_US): + return + + signature = self._make_dependency_signature(analysis_job.version) + if signature != self._dependency_signature: + self._start_scan(full_map, signature) + + total = self.width * self.height * 2 + while (self._scan_cursor < total + and ct.get_cpu_time_elapsed() < CPU_BUDGET_US): + item = self._scan_cursor + self._scan_cursor += 1 + tile_index, kind = divmod(item, 2) + x, y = divmod(tile_index, self.height) + type_code = INFRA_GUNNER if kind == 0 else INFRA_SENTINEL + proposal = self._score_tile(x, y, type_code, full_map) + self.score_cache[(x, y, type_code)] = proposal + if proposal is not None: + self._top.append(proposal) + self._top.sort(key=self._proposal_sort_key, reverse=True) + del self._top[3:] + + if self._scan_cursor >= total: + self._scan_complete = True + self._publish_if_writer(ct, my_num) + + def _make_dependency_signature(self, analysis_version): + infra = tuple(sorted((x, y, value[0]) + for (x, y), value in self.infra_memory.items())) + turrets = tuple(sorted((x, y, type_code) + for (x, y), type_code in self.turret_memory.items())) + chokes = tuple(mapanalysis.chokes) + return (analysis_version, infra, turrets, chokes) + + def _start_scan(self, full_map, signature): + self._dependency_signature = signature + self._scan_cursor = 0 + self._scan_complete = False + self._top = [] + self.score_cache = {} + self._scan_context = self._build_region_context(full_map) + + def _enemy_core(self): + cores = [(pos, value[1]) for pos, value in self.infra_memory.items() + if value[0] == INFRA_CORE] + if cores: + return min(cores, key=lambda item: _manhattan(self.team_core, item[0]))[0] + # slot 7's symmetry encoding belongs to the map layer and is not defined + # here. Until an exact core sighting arrives, rotational opposition is + # the only estimate that consumes no extra store bits. + return (self.width - 1 - self.team_core[0], + self.height - 1 - self.team_core[1]) + + def _build_region_context(self, full_map): + partition = mapanalysis.region_partition + regions = set(value for value in partition if value >= 0) + graph = {region: set() for region in regions} + choke_regions = {} + + for choke in mapanalysis.chokes: + tile, clearance = choke[0], choke[1] + adjacent = set() + max_radius = max(1, clearance + 2) + for radius in range(1, max_radius + 1): + for dx in range(-radius, radius + 1): + dy = radius - abs(dx) + for signed_dy in ({dy, -dy} if dy else {0}): + x, y = tile[0] + dx, tile[1] + signed_dy + if 0 <= x < self.width and 0 <= y < self.height: + region = partition[x * self.height + y] + if region >= 0: + adjacent.add(region) + if len(adjacent) >= 2: + break + adjacent = tuple(sorted(adjacent)) + choke_regions[tile] = adjacent + for left in adjacent: + for right in adjacent: + if left != right: + graph[left].add(right) + + own_region = self._nearest_region(self.team_core, partition) + reachable = set() + if own_region >= 0: + stack = [own_region] + reachable.add(own_region) + while stack: + region = stack.pop() + for other in graph.get(region, ()): + if other not in reachable: + reachable.add(other) + stack.append(other) + + enemy_core = self._enemy_core() + own_distance = {region: self.width + self.height + 1 for region in regions} + enemy_distance = dict(own_distance) + for x in range(self.width): + for y in range(self.height): + region = partition[x * self.height + y] + if region < 0: + continue + own_distance[region] = min(own_distance[region], + _manhattan((x, y), self.team_core)) + enemy_distance[region] = min(enemy_distance[region], + _manhattan((x, y), enemy_core)) + enemy_side = {region for region in regions + if enemy_distance[region] < own_distance[region]} + frontier_regions = set() + frontier_chokes = {} + for choke, adjacent in choke_regions.items(): + own_adjacent = [r for r in adjacent if r not in enemy_side] + enemy_adjacent = [r for r in adjacent if r in enemy_side] + if own_adjacent and enemy_adjacent: + for region in own_adjacent: + frontier_regions.add(region) + frontier_chokes.setdefault(region, set()).add(choke) + + return { + "partition": partition, + "reachable": reachable, + "enemy_core": enemy_core, + "frontier_regions": frontier_regions, + "frontier_chokes": frontier_chokes, + } + + def _nearest_region(self, origin, partition): + best = -1 + best_distance = self.width + self.height + 1 + for x in range(self.width): + for y in range(self.height): + region = partition[x * self.height + y] + if region < 0: + continue + distance = _manhattan(origin, (x, y)) + if distance < best_distance: + best, best_distance = region, distance + return best + + def _score_tile(self, x, y, type_code, full_map): + context = self._scan_context + if full_map[x][y] != 0: + return None + tile = (x, y) + if _manhattan(tile, self.team_core) <= CORE_SPAWN_RING_MANHATTAN: + return None + if tile in self.turret_memory or tile in self.infra_memory: + return None + region = context["partition"][x * self.height + y] + if region < 0 or region not in context["reachable"]: + return None + + targets = [(pos, value[0]) for pos, value in self.infra_memory.items()] + best_facing = -1 + best_face_terms = None + best_frontier_choke = False + for facing_index in range(4): + covered_value = sum( + TARGET_VALUE[target_type] + for target_pos, target_type in targets + if _in_envelope(tile, target_pos, type_code, facing_index) + ) + covered_chokes = [ + choke for choke in mapanalysis.chokes + if _in_envelope(tile, choke[0], type_code, facing_index) + ] + choke_coverage = len(covered_chokes) + frontier_choke = any( + choke[0] in context["frontier_chokes"].get(region, ()) + for choke in covered_chokes + ) + approach = self._approach_tiebreak(tile, facing_index) + terms = (W_COVER * covered_value + W_CHOKE * choke_coverage, + covered_value, choke_coverage, approach, -facing_index) + if best_face_terms is None or terms > best_face_terms: + best_face_terms = terms + best_facing = facing_index + best_frontier_choke = frontier_choke + + face_score, covered_value, choke_coverage = best_face_terms[:3] + # A defensive emplacement must attack remembered infrastructure or a + # detected corridor; safety distance alone may not create backfield spam. + if covered_value == 0 and choke_coverage == 0: + return None + + mutual = sum(1 for turret in self._friendly_turrets() + if _covered_by_unknown_facing(turret, tile)) + exposure = sum(1 for turret in self._enemy_turrets() + if _covered_by_unknown_facing(turret, tile)) + safe_distance = _manhattan(tile, context["enemy_core"]) + frontier = region in context["frontier_regions"] + score = (face_score + W_MUTUAL * mutual + W_SAFE * safe_distance + - W_EXPOSED * exposure + - (0 if frontier else W_FRONTIER_PENALTY)) + urgent = score >= URGENT_THRESHOLD and best_frontier_choke + return Proposal(x, y, type_code, FACINGS[best_facing], score, + frontier, urgent, best_frontier_choke, 0) + + def _friendly_turrets(self): + return [(x, y, type_code) + for (x, y), type_code in self.turret_memory.items()] + + def _enemy_turrets(self): + return [(x, y, value[0]) for (x, y), value in self.infra_memory.items() + if value[0] in TURRET_TYPES] + + def _approach_tiebreak(self, tile, facing_index): + enemy = self._scan_context["enemy_core"] + dx = enemy[0] - tile[0] + dy = enemy[1] - tile[1] + face_dx, face_dy = FACING_DELTAS[facing_index] + return dx * face_dx + dy * face_dy + + @staticmethod + def _proposal_sort_key(proposal): + # prefer exact model score; deterministic secondary keys keep every + # builder converged even if its cache iteration order differs. + return (proposal.score, proposal.frontier_choke, + proposal.turret_type == INFRA_SENTINEL, + -proposal.x, -proposal.y, -FACINGS.index(proposal.facing)) + + def _publish_if_writer(self, ct, my_num): + spawn_count = ct.read_store(0) + if spawn_count <= 0 or my_num < 1: + return + elected = ct.get_current_round() % spawn_count + 1 + if elected != my_num: + return + proposal = self._top[0] if self._top else None + value = _pack_proposal(proposal) + if ct.read_store(PROPOSAL_SLOT) != value: + ct.write_store(PROPOSAL_SLOT, value) + ct.write_store(PROPOSAL_CHECK_SLOT, 0) + + def draw_debug(self, ct): + proposals = self._top + if not proposals and self.current_proposal is not None: + proposals = [self.current_proposal] + for proposal in proposals[:3]: + ct.draw_indicator_dot(Position(proposal.x, proposal.y), 0, 80, 255) From 57c46bc33625d13f17c8af360843dac2a67c2f97 Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 19:10:33 +0530 Subject: [PATCH 02/29] main v2: shared map + cardinal dijkstra movement, old eco and attack wired onto it --- main.py | 1202 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 801 insertions(+), 401 deletions(-) diff --git a/main.py b/main.py index abee683..cdce961 100644 --- a/main.py +++ b/main.py @@ -1,26 +1,70 @@ -from fcode import * -from coreHelper import * -from pathfinding import * -from defend import * +"""Starter bot - a simple example to demonstrate usage of the Controller API. + +Each unit gets its own Player instance; the engine calls run() once per round. +Use Controller.get_entity_type() to branch on what kind of unit you are. +""" + import math import random -# This file contains the eco bot code, ported from Cambridge Battlecode to -# the Florent Code League (fcode) API. -Directions = [d for d in Direction if d != Direction.CENTRE] -DIRECTIONS = Directions # alias used in attack() -CardDirections = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST] +from fcode import Controller, Direction, EntityType, Environment, Position + +from coreHelper import findEnemyCore, findTeamCore, findThreats, spawnBots +from defend import Defender +import mapanalysis +import turretplan + +CARDINALS = [Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST] +DIRECTIONS = CARDINALS +CardDirections = CARDINALS + # 20 ammo = 10 gunner shots or 2 sentinel shots. GLOBAL_AMMO_BUFFER = 20 +openCost = 1 +barrierCost = 8 # to be implemented later + + +tileTypes = { # limited to 4 types to maximize caching ability + Environment.EMPTY: 0, # Only counts if they are passable, or have a team barrier on them + Environment.ORE_TITANIUM: 1, # Only counts if they dont have a harvester on them + Environment.WALL: 2, + 'Unpassable': 3 # have an unpassable non team barrier building or are threat from turrets, +} +# shared array. 0: num bots spawned 1-6: new tiles from that bot 7: the map symmetry (nothing writes it yet) + + +class DialPathfinderAdapter: + """The small Pathfinder surface expected by Defender.""" + + def __init__(self, player): + self.player = player + + def moveTo(self, ct: Controller, target: Position): + self.player.moveTo(ct, target) + + def reset(self): + # defend.py wants a pf object, just hand it the new mover + pass class Player: def __init__(self): - # Other Files - self.pf = Pathfinder() self.defender = Defender() + self.pf = DialPathfinderAdapter(self) + self.fullMap = None # bot's local version of the full map + self.newTiles = [] # shared publicly so all bots have info + self.seenTiles = None # used to get self.newTiles self.numSpawned = 0 + self.myNum = -1 # every builder bot gets its own unique number used to write to the shared array, and organize roles (1, 2, 3, ...) + self.teamCore = None + self.distMap = None + self.mapW = None + self.mapH = None + self.stuckTurns = 0 + self.turretPlanner = None + + self.randDir = Direction.NORTH # Role self.attackMode = False @@ -33,20 +77,19 @@ def __init__(self): self.spawnSentinelHere = None # Cores - self.teamCore = None # reference corner tile of the (2x2) core - self.teamCoreTiles = None # all tiles of the core footprint + self.teamCoreTiles = None self.enemyCore = None - # Launcher Proofing + # Launcher proofing self.oldPos = None - # Harvester Finding + # Harvester finding self.currentHarvester = None self.foundHarvester = None self.usedHarvesters = [] self.reachHarvesterTime = 0 - # Building Back + Sentinel Placment + # Building back and sentinel placement self.conveyorEnd = None self.surroundHarvester = False self.protectTurns = 0 @@ -54,19 +97,24 @@ def __init__(self): self.isEnemyInfastructure = None self.nextTurretCountDown = 0 - # Placing Gunner On Attack - self.turretTimeOut = 0 # Starts when turret is placed + # Placing a sentinel while extending economy + self.turretTimeOut = 0 self.enemyPos = None self.enemyType = -1 - # Exploring + + # Exploring and symmetry attack hypotheses self.explorePos = None self.exploreTime = 0 self.attackMoveTimeout = 0 - self.attackHypothesisIndex = 0 # opp core symmetry change + self.attackHypothesisIndex = 0 def run(self, ct: Controller) -> None: etype = ct.get_entity_type() if etype == EntityType.CORE: + if self.mapW is None: + self.mapW = ct.get_map_width() + self.mapH = ct.get_map_height() + mapanalysis.reset_map(self.mapW, self.mapH) self.core(ct) elif etype == EntityType.BUILDER_BOT: self.builderBot(ct) @@ -78,7 +126,7 @@ def run(self, ct: Controller) -> None: self.gunner(ct) def core(self, ct: Controller) -> None: - # spawn first so ammo conversion cannot prevent this turns spawn + # spawn first so the ammo topup doesnt eat the builder money if spawnBots(self.numSpawned, ct): corePos = ct.get_position() threats = findThreats(ct) @@ -86,55 +134,39 @@ def core(self, ct: Controller) -> None: for dx in (-1, 0, 1, 2): for dy in (-1, 0, 1, 2): - # skip the four tiles occupied by the core + # skip the core's own 4 tiles if 0 <= dx <= 1 and 0 <= dy <= 1: continue - spawnPos = Position( - corePos.x + dx, - corePos.y + dy, - ) - + spawnPos = Position(corePos.x + dx, corePos.y + dy) if not ct.can_spawn(spawnPos): continue - - isThreat = any( - spawnPos.distance_squared(threat) <= 2 - for threat in threats - ) - if isThreat: + if any(spawnPos.distance_squared(threat) <= 2 + for threat in threats): continue ct.spawn_builder(spawnPos) self.numSpawned += 1 + # slot 0 also drives builder numbering + the share rotation + ct.write_store(0, self.numSpawned) spawned = True break if spawned: break - # Turrets from ammo pool instead currentAmmo = ct.get_global_ammo() - if currentAmmo < GLOBAL_AMMO_BUFFER: - # preserve titn to afford another builder + # keep enough for the next builder spareTitanium = ( - ct.get_global_resources() - - ct.get_builder_bot_cost() + ct.get_global_resources() - ct.get_builder_bot_cost() ) - - amount = min( - GLOBAL_AMMO_BUFFER - currentAmmo, - spareTitanium, - ) - + amount = min(GLOBAL_AMMO_BUFFER - currentAmmo, spareTitanium) if amount > 0 and ct.can_convert_ammo(amount): ct.convert_ammo(amount) - # new for ts def gunner(self, ct: Controller) -> None: target = ct.get_gunner_target() - if target is not None and ct.can_fire(target): ct.fire(target) @@ -143,7 +175,6 @@ def sentinel(self, ct: Controller) -> None: self.enemyCore = findEnemyCore(ct) team = ct.get_team() - priority = { EntityType.CORE: 5, EntityType.SENTINEL: 3, @@ -152,124 +183,128 @@ def sentinel(self, ct: Controller) -> None: EntityType.LAUNCHER: 2, EntityType.CONVEYOR: 1, } - - # HARVESTER remains intentionally excluded. - targets = [ + # no HARVESTER here on purpose, stealing them is better + targets = { EntityType.CORE, EntityType.GUNNER, EntityType.SENTINEL, EntityType.BARRIER, EntityType.LAUNCHER, EntityType.CONVEYOR, - ] + } bestTile = None bestPriority = 0 - for tile in ct.get_attackable_tiles(): + if not self.isInBounds(ct, tile) or not ct.is_in_vision(tile): + continue buildingID = ct.get_tile_building_id(tile) builderID = ct.get_tile_builder_bot_id(tile) - # Do not attack a friendly builder. if builderID is not None and ct.get_team(builderID) == team: continue - - # Do not fire into a friendly building, even when an enemy - # builder is standing on it. if buildingID is not None and ct.get_team(buildingID) == team: continue tilePriority = 0 - if buildingID is not None: buildingType = ct.get_entity_type(buildingID) - - # Preserves the intentional harvester exclusion. if buildingType not in targets: continue - tilePriority = priority.get(buildingType, 1) - - # Preserve the existing bonus for an enemy builder - # standing on a targetable enemy building. if builderID is not None: tilePriority += 2 - elif builderID is not None: - # the part that got cooked by increasing prio to 2 + # not worth 10 ammo on a lone builder continue if tilePriority > bestPriority: bestPriority = tilePriority bestTile = tile - # Fire only after examining every attackable tile. if bestTile is not None and ct.can_fire(bestTile): ct.fire(bestTile) - def launcher(self, ct: Controller): + def launcher(self, ct: Controller) -> None: myLoc = ct.get_position() - - for d in Directions: + for d in CARDINALS: adjacent = myLoc.add(d) - if adjacent.x < 0 or adjacent.y < 0 or adjacent.x >= ct.get_map_width() or adjacent.y >= ct.get_map_height(): + if not self.isInBounds(ct, adjacent) or not ct.is_in_vision(adjacent): continue bot_id = ct.get_tile_builder_bot_id(adjacent) - if bot_id is not None and ct.get_team(bot_id) != ct.get_team(): - best = None - bestDist = 0 - for tile in ct.get_nearby_tiles(26): - if ct.can_launch(adjacent, tile): - dist = tile.distance_squared(myLoc) - if dist > bestDist: - bestDist = dist - best = tile - if best is not None: - ct.launch(adjacent, best) - return + if bot_id is None or ct.get_team(bot_id) == ct.get_team(): + continue + + best = None + bestDist = -1 + for tile in ct.get_nearby_tiles(26): + if ct.can_launch(adjacent, tile): + dist = self.manhattan(myLoc, tile) + if dist > bestDist: + bestDist = dist + best = tile + if best is not None: + ct.launch(adjacent, best) + return def builderBot(self, ct: Controller) -> None: - myLoc = ct.get_position() - if ct.get_hp() < ct.get_max_hp(): - if ct.can_heal(myLoc): - ct.heal(myLoc) currentRound = ct.get_current_round() + myLoc = ct.get_position() self.setUp(ct, myLoc) - if ct.get_id() < 5 and ((self.currentHarvester is None and (currentRound > 20 or self.nearCore2(myLoc) < 4))): - self.alwaysDefense = True - self.defenseMode = True - if currentRound % 120 == 1 or self.justSpawned: - self.assignRole(ct, currentRound) - self.justSpawned = False - self.defender.defendInfrastructure(ct, self.teamCore, self.pf) - if self.attackMode == True: - self.attack(ct) - return - if self.defenseMode: - self.defender.defendInfrastructure(ct, self.teamCore, self.pf) - healPos = self.defender.picHealingLoc(ct, self.teamCore, self.alwaysDefense) - if self.nearCore2(myLoc) < 4 and ct.get_global_resources() > 40: - if ct.can_build_conveyor(myLoc, myLoc.direction_to(self.teamCore)): - ct.build_conveyor(myLoc, myLoc.direction_to(self.teamCore)) - if healPos is not None: - self.pf.moveTo(ct, healPos) - return - else: - if self.currentHarvester is not None: - if self.surroundHarvester: - self.buildConveyor(ct) - else: - self.protectHarvester(ct) - else: - self.getHarvester(ct, myLoc) - if self.foundHarvester is None and self.currentHarvester is None: - if self.explorePos is None or self.exploreTime > 35 or self.explorePos.distance_squared(myLoc) <= 4: - self.pickExplore(ct, currentRound) - self.exploreTime = 0 - self.exploreTime += 1 - ct.draw_indicator_line(myLoc, self.explorePos, 255, 255, 255) - self.pf.moveTo(ct, self.explorePos) + if self.myNum == -1: + # first read is k-1 on our spawn round (writes land next round), + # +1 = unique 1..n since the core only spawns 1 per turn + self.myNum = ct.read_store(0) + 1 + if self.mapW is None or self.mapH is None: + self.mapW = ct.get_map_width() + self.mapH = ct.get_map_height() + if self.distMap is None: + self.distMap = [[4096 for _ in range(self.mapH)] + for _ in range(self.mapW)] + if self.fullMap is None: + self.fullMap = [[-1 for _ in range(self.mapH)] + for _ in range(self.mapW)] + mapanalysis.configure_map(self.mapW, self.mapH) + if self.seenTiles is None: + self.seenTiles = [[False for _ in range(self.mapH)] + for _ in range(self.mapW)] + + # order matters: comms first, real work, leftover-cpu stuff last + self.getNewTiles(ct) + self.shareTiles(ct, currentRound) + self.updateBoard(ct) + if self.teamCore is not None and self.turretPlanner is None: + self.turretPlanner = turretplan.TurretPlanner( + self.mapW, self.mapH, self.teamCore + ) + if self.turretPlanner is not None: + self.turretPlanner.sync_and_observe(ct) + + self.runRoleBehavior(ct, currentRound) + + # urgent turret proposal only if we didnt already move this turn + if (self.turretPlanner is not None + and self.turretPlanner.should_pursue_urgent(ct, self.myNum)): + proposal = self.turretPlanner.current_proposal + self.moveTo(ct, Position(proposal.x, proposal.y)) + + if self.turretPlanner is not None: + built = self.turretPlanner.try_passive_build(ct) + if built is not None: + built_pos = Position(built.x, built.y) + old_type = self.fullMap[built.x][built.y] + self.fullMap[built.x][built.y] = 3 + self.seenTiles[built.x][built.y] = True + self.newTiles.append([built_pos, 3]) + if old_type not in (2, 3): + mapanalysis.note_structural_tile( + built.x, built.y, True, self.mapW, self.mapH + ) + self.advanceMapAnalysis(ct) + if self.turretPlanner is not None: + self.turretPlanner.advance(ct, self.fullMap, self.myNum) + self.turretPlanner.draw_debug(ct) def setUp(self, ct: Controller, myLoc: Position): if self.teamCore is None: @@ -278,30 +313,104 @@ def setUp(self, ct: Controller, myLoc: Position): self.teamCoreTiles = self._computeCoreTiles(ct, self.teamCore) if self.enemyCore is None: self.enemyCore = findEnemyCore(ct) - if self.oldPos is not None and myLoc.distance_squared(self.oldPos) > 2: + if self.oldPos is not None and self.manhattan(myLoc, self.oldPos) > 1: self.pf.reset() self.oldPos = myLoc def _computeCoreTiles(self, ct: Controller, corner: Position) -> list: + if not self.isInBounds(ct, corner) or not ct.is_in_vision(corner): + return [corner] cid = ct.get_tile_building_id(corner) tiles = [] if cid is not None: for dx in (-1, 0, 1): for dy in (-1, 0, 1): q = Position(corner.x + dx, corner.y + dy) - if ct.is_in_vision(q) and self.isInBounds(ct, q) and ct.get_tile_building_id(q) == cid: + if (self.isInBounds(ct, q) and ct.is_in_vision(q) + and ct.get_tile_building_id(q) == cid): tiles.append(q) return tiles if tiles else [corner] + @staticmethod + def manhattan(a: Position, b: Position) -> int: + return abs(a.x - b.x) + abs(a.y - b.y) + def nearCore2(self, pos: Position) -> int: tiles = self.teamCoreTiles if self.teamCoreTiles else [self.teamCore] - return min(pos.distance_squared(t) for t in tiles) + tiles = [tile for tile in tiles if tile is not None] + if not tiles: + return (self.mapW + self.mapH) if self.mapW is not None else 4096 + return min(self.manhattan(pos, tile) for tile in tiles) + + @staticmethod + def cardinalDirectionTo(origin: Position, target: Position) -> Direction: + dx = target.x - origin.x + dy = target.y - origin.y + if abs(dx) >= abs(dy) and dx != 0: + return Direction.EAST if dx > 0 else Direction.WEST + if dy != 0: + return Direction.SOUTH if dy > 0 else Direction.NORTH + return Direction.CENTRE + + def runRoleBehavior(self, ct: Controller, currentRound: int): + myLoc = ct.get_position() + if ct.get_hp() < ct.get_max_hp() and ct.can_heal(myLoc): + ct.heal(myLoc) - def assignRole(self, ct: Controller, currentRound): + if (ct.get_id() < 5 + and self.currentHarvester is None + and (currentRound > 20 or self.nearCore2(myLoc) < 3)): + self.alwaysDefense = True + self.defenseMode = True + if currentRound % 120 == 1 or self.justSpawned: + self.assignRole(ct, currentRound) + self.justSpawned = False + + # old repair pass + self.defender.defendInfrastructure(ct, self.teamCore, self.pf) + + if self.attackMode: + self.attack(ct) + return + if self.defenseMode: + self.defender.defendInfrastructure(ct, self.teamCore, self.pf) + healPos = self.defender.picHealingLoc( + ct, self.teamCore, self.alwaysDefense + ) + if (self.nearCore2(ct.get_position()) < 3 + and ct.get_global_resources() >= ct.get_conveyor_cost()): + direction = self.cardinalDirectionTo( + ct.get_position(), self.teamCore + ) + if (direction != Direction.CENTRE + and ct.can_build_conveyor(ct.get_position(), direction)): + ct.build_conveyor(ct.get_position(), direction) + if healPos is not None: + self.moveTo(ct, healPos) + return + + if self.currentHarvester is not None: + if self.surroundHarvester: + self.buildConveyor(ct) + else: + self.protectHarvester(ct) + else: + self.getHarvester(ct, ct.get_position()) + if self.foundHarvester is None and self.currentHarvester is None: + myLoc = ct.get_position() + if (self.explorePos is None or self.exploreTime > 35 + or self.manhattan(self.explorePos, myLoc) <= 2): + self.pickExplore(ct, currentRound) + self.exploreTime = 0 + self.exploreTime += 1 + ct.draw_indicator_line(myLoc, self.explorePos, 255, 255, 255) + self.moveTo(ct, self.explorePos) + def assignRole(self, ct: Controller, currentRound): if currentRound <= 120: # 36% Offense, 64% Eco + 1 Defense Bot - if currentRound % 11 in [3, 5, 7, 10] and self.currentHarvester is None: + if (currentRound % 11 in [3, 5, 7, 10] + and self.currentHarvester is None): self.attackMode = True else: self.defenseMode = False @@ -312,37 +421,25 @@ def assignRole(self, ct: Controller, currentRound): self.attackMode = False if self.currentHarvester is None: role = random.random() - if role > 0.75 or self.alwaysDefense == True: + if role > 0.75 or self.alwaysDefense: self.defenseMode = True - elif role > 0.4 and currentRound > 120: + elif role > 0.4: self.attackMode = True def attack(self, ct: Controller) -> None: + if self.teamCore is None: + return myLoc = ct.get_position() - coreHypothesis = [ - #rotational sym - Position( - ct.get_map_width() - 2 - self.teamCore.x, - ct.get_map_height() - 2 - self.teamCore.y - ), - - # vertical axis reflc - Position( - ct.get_map_width() - 2 - self.teamCore.x, - self.teamCore.y - ), - - # horizontal axis reflc - Position( - self.teamCore.x, - ct.get_map_height() - 2 - self.teamCore.y - ), + Position(ct.get_map_width() - 2 - self.teamCore.x, + ct.get_map_height() - 2 - self.teamCore.y), + Position(ct.get_map_width() - 2 - self.teamCore.x, + self.teamCore.y), + Position(self.teamCore.x, + ct.get_map_height() - 2 - self.teamCore.y), ] - # an observed enemy core overrides the new hypotheses if self.enemyCore is not None: self.attackPos = self.enemyCore - # Determine attack destination elif self.attackPos is None: self.attackPos = coreHypothesis[self.attackHypothesisIndex] self.attackMoveTimeout = 0 @@ -353,201 +450,218 @@ def nextAttackHypothesis(): ) % len(coreHypothesis) self.attackPos = coreHypothesis[self.attackHypothesisIndex] self.attackMoveTimeout = 0 - - def changeAttackPos (ct: Controller): - dx = 5 * random.randint(-1, 1) # more consistently farther away - dy = 5 * random.randint(-1, 1) - new_x = max(0, min(self.attackPos.x + dx, ct.get_map_width() - 1)) - new_y = max(0, min(self.attackPos.y + dy, ct.get_map_height() - 1)) + def changeAttackPos(): + dx = 5 * random.randint(-1, 1) + dy = 5 * random.randint(-1, 1) + new_x = max(0, min(self.attackPos.x + dx, + ct.get_map_width() - 1)) + new_y = max(0, min(self.attackPos.y + dy, + ct.get_map_height() - 1)) self.attackPos = Position(new_x, new_y) self.attackMoveTimeout = 0 - - # Jitter so we don't stand still on the target - if myLoc.distance_squared(self.attackPos) < 8: - changeAttackPos(ct) - - if self.spawnSentinelHere is None: # Pick a sentinel placement spot near the attack target + if self.manhattan(myLoc, self.attackPos) < 4: + changeAttackPos() + + if self.spawnSentinelHere is None: nearbyEnemyStuff = [] - isStuff = False - - for i in ct.get_nearby_buildings(): - if ct.get_entity_type(i) == EntityType.CONVEYOR: - if ct.get_team(i) != ct.get_team() and ct.get_stored_resource(i) is not None: - nearbyEnemyStuff.append(ct.get_position(i)) - isStuff = True - nearbyEnemyStuff.sort(key=lambda p: p.distance_squared(self.attackPos)) - - if not isStuff: # no enemy infastructure was found + for building_id in ct.get_nearby_buildings(): + if (ct.get_entity_type(building_id) == EntityType.CONVEYOR + and ct.get_team(building_id) != ct.get_team() + and ct.get_stored_resource(building_id) is not None): + nearbyEnemyStuff.append(ct.get_position(building_id)) + nearbyEnemyStuff.sort( + key=lambda p: self.manhattan(p, self.attackPos) + ) + + if not nearbyEnemyStuff: self.attackMoveTimeout += 1 - self.pf.moveTo(ct, self.attackPos) - if self.attackMoveTimeout > 20: # While moving to attack pos, no enemy infastructure was found for 20 turns, change it + self.moveTo(ct, self.attackPos) + if self.attackMoveTimeout > 20: if self.enemyCore is None: nextAttackHypothesis() else: - changeAttackPos(ct) + changeAttackPos() return - else: # stuff was found, now reuse attackMoveTimeout for sentinelSpot - self.spawnSentinelHere = nearbyEnemyStuff[0] - self.attackMoveTimeout = 0 + self.spawnSentinelHere = nearbyEnemyStuff[0] + self.attackMoveTimeout = 0 - # From now on there must be a sentinel Spot - - # first check if there is already another sentinel close by - for b_id in ct.get_nearby_buildings(): - if ct.get_entity_type(b_id) == EntityType.SENTINEL and ct.get_team(b_id) == ct.get_team(): - if ct.get_position(b_id).distance_squared(self.spawnSentinelHere) <= 8: - self.spawnSentinelHere = None - self.attackMoveTimeout = 0 - self.pf.moveTo(ct, self.attackPos) - return + for building_id in ct.get_nearby_buildings(): + if (ct.get_entity_type(building_id) == EntityType.SENTINEL + and ct.get_team(building_id) == ct.get_team() + and self.manhattan(ct.get_position(building_id), + self.spawnSentinelHere) <= 4): + self.spawnSentinelHere = None + self.attackMoveTimeout = 0 + self.moveTo(ct, self.attackPos) + return - if self.attackMoveTimeout > 25: # This spot is hard to get, switch to another one + if self.attackMoveTimeout > 25: self.spawnSentinelHere = None self.attackMoveTimeout = 0 return - - if myLoc.distance_squared(self.spawnSentinelHere) < 1: # this means you are close enough to the sentinelSpot - self.attackMoveTimeout = 0 - else: - self.pf.moveTo(ct, self.spawnSentinelHere) + + if myLoc != self.spawnSentinelHere: + self.moveTo(ct, self.spawnSentinelHere) self.attackMoveTimeout += 1 return + self.attackMoveTimeout = 0 - - if ct.get_tile_building_id(self.spawnSentinelHere) is not None: # if there is something else there clear it - thingTeam = ct.get_team(ct.get_tile_building_id(self.spawnSentinelHere)) - if thingTeam == ct.get_team(): + if (not self.isInBounds(ct, self.spawnSentinelHere) + or not ct.is_in_vision(self.spawnSentinelHere)): + return + building_id = ct.get_tile_building_id(self.spawnSentinelHere) + if building_id is not None: + if ct.get_team(building_id) == ct.get_team(): if ct.can_destroy(self.spawnSentinelHere): ct.destroy(self.spawnSentinelHere) else: self.defender.placeLauncher(ct, self.spawnSentinelHere) - self.pf.moveTo(ct, self.spawnSentinelHere) - if ct.can_fire(self.spawnSentinelHere) and ct.get_global_resources() > 25: + self.moveTo(ct, self.spawnSentinelHere) + if ct.can_fire(self.spawnSentinelHere): ct.fire(self.spawnSentinelHere) - - if ct.get_tile_building_id(self.spawnSentinelHere) is None: # Tile is clear — build sentinel facing the most valuable targets - if myLoc == self.spawnSentinelHere: - self.pf.moveTo(ct, self.attackPos) - directionChoices = {d: 0 for d in DIRECTIONS} - for i in ct.get_nearby_buildings(): - if ct.get_team(i) != ct.get_team(): - d = self.spawnSentinelHere.direction_to(ct.get_position(i)) - if ct.get_entity_type(i) == EntityType.CORE: - directionChoices[d] += 25 - else: - directionChoices[d] += 1 - - directionChoice = max(directionChoices, key=lambda d: directionChoices[d]) + building_id = ct.get_tile_building_id(self.spawnSentinelHere) + if building_id is None: + if ct.get_position() == self.spawnSentinelHere: + self.moveTo(ct, self.attackPos) + directionChoices = {d: 0 for d in CARDINALS} + for enemy_id in ct.get_nearby_buildings(): + if ct.get_team(enemy_id) == ct.get_team(): + continue + direction = self.cardinalDirectionTo( + self.spawnSentinelHere, ct.get_position(enemy_id) + ) + if direction == Direction.CENTRE: + continue + if ct.get_entity_type(enemy_id) == EntityType.CORE: + directionChoices[direction] += 25 + else: + directionChoices[direction] += 1 + directionChoice = max( + directionChoices, key=lambda d: directionChoices[d] + ) if ct.can_build_sentinel(self.spawnSentinelHere, directionChoice): ct.build_sentinel(self.spawnSentinelHere, directionChoice) self.spawnSentinelHere = None self.attackPos = None - - def pickExplore(self, ct: Controller, currentRound): w = ct.get_map_width() h = ct.get_map_height() myLoc = ct.get_position() - round = ct.get_current_round() radius = (w + h) // 5 - if round > 5: + if currentRound > 5: theta = random.random() * 2 * math.pi else: - theta = (((3 * round) % 5) / 5) * 2 * math.pi + theta = (((3 * currentRound) % 5) / 5) * 2 * math.pi tx = int(max(0, min(w - 1, myLoc.x + math.cos(theta) * radius))) ty = int(max(0, min(h - 1, myLoc.y + math.sin(theta) * radius))) self.explorePos = Position(tx, ty) def getHarvester(self, ct: Controller, myLoc: Position): if self.foundHarvester is None: - nearbyOres = [] - for i in ct.get_nearby_tiles(): - if ct.is_in_vision(i): - if ct.get_tile_env(i) == Environment.ORE_TITANIUM: - nearbyOres.append(i) - nearbyOres.sort(key=lambda p: p.distance_squared(myLoc) + self.nearCore2(p)) - found = False - for t in nearbyOres: - tID = ct.get_tile_building_id(t) - willWork = False - if t not in self.usedHarvesters: - if tID is None: - willWork = True - elif ct.get_team(tID) == ct.get_team(): - willWork = True - elif ct.get_entity_type(tID) == EntityType.HARVESTER: - willWork = True - if willWork and self.foundHarvester is None: - self.foundHarvester = t + # shared map first: chase ore teammates found, no out of vision queries + used = set(self.usedHarvesters) + knownOres = { + Position(x, y) + for x in range(self.mapW) + for y in range(self.mapH) + if self.fullMap[x][y] == 1 + and Position(x, y) not in used + } + # still scan vision too: enemy harvesters sit on ore thats blocked in fullMap + for tile in ct.get_nearby_tiles(): + if (tile not in used + and ct.get_tile_env(tile) == Environment.ORE_TITANIUM): + knownOres.add(tile) + knownOres = list(knownOres) + knownOres.sort( + key=lambda p: (self.manhattan(p, myLoc) + self.nearCore2(p), + p.x, p.y) + ) + for orePos in knownOres: + willWork = True + if ct.is_in_vision(orePos): + oreID = ct.get_tile_building_id(orePos) + if oreID is not None: + willWork = ( + ct.get_team(oreID) == ct.get_team() + or ct.get_entity_type(oreID) == EntityType.HARVESTER + ) + if willWork: + self.foundHarvester = orePos self.reachHarvesterTime = 0 - found = True - if found == False: + break + if self.foundHarvester is None: return - if self.reachHarvesterTime >= 20: # Limit time spend trying to reach the harvester + + if self.reachHarvesterTime >= 20: self.removeHarvester(ct) return - if ct.is_in_vision(self.foundHarvester) and ct.get_tile_building_id(self.foundHarvester) is not None: # Checks on current ore tile + + if ct.is_in_vision(self.foundHarvester): oreID = ct.get_tile_building_id(self.foundHarvester) - if ct.get_team(oreID) == ct.get_team(): - if ct.get_entity_type(oreID) == EntityType.HARVESTER: # Already is a team harvester + if oreID is not None: + if ct.get_team(oreID) == ct.get_team(): + if ct.get_entity_type(oreID) == EntityType.HARVESTER: + self.removeHarvester(ct) + return + if (ct.can_destroy(self.foundHarvester) + and myLoc.distance_squared(self.foundHarvester) <= 2): + # destroying our own stuff is free, doesnt use the build action + ct.destroy(self.foundHarvester) + elif ct.get_entity_type(oreID) != EntityType.HARVESTER: self.removeHarvester(ct) return - else: - if ct.can_destroy(self.foundHarvester) and myLoc.distance_squared(self.foundHarvester) == 1: # Remove to have room for harvester - ct.destroy(self.foundHarvester) - elif ct.get_entity_type(oreID) != EntityType.HARVESTER: # Nothing you can do - self.removeHarvester(ct) - return + if myLoc == self.foundHarvester: possibleSpots = [] - for i in CardDirections: - pos = myLoc.add(i) - if self.isInBounds(ct, pos) and self.pf.canMove(ct, i): - possibleSpots.append((i, pos)) - possibleSpots.sort(key=lambda x: self.nearCore2(x[1])) - if len(possibleSpots) > 0: - self.pf._move(ct, possibleSpots[0][0]) + for direction in CARDINALS: + pos = myLoc.add(direction) + if self.isInBounds(ct, pos) and ct.can_move(direction): + possibleSpots.append(pos) + possibleSpots.sort(key=lambda pos: self.nearCore2(pos)) + if possibleSpots: + self.moveTo(ct, possibleSpots[0]) else: self.usedHarvesters.append(self.foundHarvester) self.foundHarvester = None return - myLoc = ct.get_position() - if myLoc.distance_squared(self.foundHarvester) > 1: - self.pf.moveTo(ct, self.foundHarvester) + + if self.manhattan(myLoc, self.foundHarvester) > 1: + self.moveTo(ct, self.foundHarvester) self.reachHarvesterTime += 1 - myLoc = ct.get_position() - if myLoc.distance_squared(self.foundHarvester) == 1: - if ct.can_build_harvester(self.foundHarvester): # Built the harvester + return + + if (ct.is_in_vision(self.foundHarvester) + and ct.get_position().distance_squared(self.foundHarvester) <= 2): + if ct.can_build_harvester(self.foundHarvester): ct.build_harvester(self.foundHarvester) - self.addHarvester(ct, myLoc) + self.addHarvester(ct, ct.get_position()) return oreID = ct.get_tile_building_id(self.foundHarvester) - if oreID is not None and ct.get_entity_type(oreID) == EntityType.HARVESTER and ct.get_team(oreID) != ct.get_team(): # Right next to an enemy harvester - self.addHarvester(ct, myLoc) - return - return + if (oreID is not None + and ct.get_entity_type(oreID) == EntityType.HARVESTER + and ct.get_team(oreID) != ct.get_team()): + self.addHarvester(ct, ct.get_position()) def removeHarvester(self, ct: Controller): - self.usedHarvesters.append(self.foundHarvester) + if self.foundHarvester is not None: + self.usedHarvesters.append(self.foundHarvester) self.foundHarvester = None self.reachHarvesterTime = 0 - def addHarvester(self, ct: Controller, myLoc): - # Book keeping + def addHarvester(self, ct: Controller, myLoc: Position): self.currentHarvester = self.foundHarvester self.usedHarvesters.append(self.foundHarvester) self.foundHarvester = None self.conveyorEnd = myLoc - # Protect Harvester self.surroundHarvester = False self.isEnemyInfastructure = None self.sentinelSpot = None - # Placing Sentinel on Offense self.turretTimeOut = 0 self.enemyPos = None self.enemyType = -1 @@ -562,175 +676,461 @@ def protectHarvester(self, ct: Controller): self.protectTurns += 1 if self.isEnemyInfastructure is None: - for i in ct.get_nearby_buildings(): - if ct.get_team(i) != ct.get_team(): - self.isEnemyInfastructure = ct.get_position(i) + for building_id in ct.get_nearby_buildings(): + if ct.get_team(building_id) != ct.get_team(): + self.isEnemyInfastructure = ct.get_position(building_id) protectSpots = [] - for i in CardDirections: - pos = self.currentHarvester.add(i) - if self.isInBounds(ct, pos) and ct.is_in_vision(pos) and pos != self.conveyorEnd: - if ct.get_tile_env(pos) != Environment.WALL: - if ct.get_tile_building_id(pos) is None: - protectSpots.append(pos) - if len(protectSpots) == 0: + for direction in CARDINALS: + pos = self.currentHarvester.add(direction) + if (self.isInBounds(ct, pos) and ct.is_in_vision(pos) + and pos != self.conveyorEnd + and ct.get_tile_env(pos) != Environment.WALL + and ct.get_tile_building_id(pos) is None): + protectSpots.append(pos) + if not protectSpots: self.protectTurns = 0 self.surroundHarvester = True self.isEnemyInfastructure = None self.sentinelSpot = None return - if self.isEnemyInfastructure and self.sentinelSpot is None: - protectSpots.sort(key=lambda c: c.distance_squared(self.isEnemyInfastructure)) + if self.isEnemyInfastructure is not None and self.sentinelSpot is None: + protectSpots.sort( + key=lambda pos: self.manhattan(pos, self.isEnemyInfastructure) + ) self.sentinelSpot = protectSpots[0] - protectSpots.sort(key=lambda c: c.distance_squared(ct.get_position())) - - for i in protectSpots: - bID = ct.get_tile_building_id(i) - etype = None - if bID is not None: - etype = ct.get_entity_type(bID) - if ct.get_position() == i and bID is None: - self.pf.moveTo(ct, self.currentHarvester) - if ct.get_position().distance_squared(i) > 2: - self.pf.moveTo(ct, i) - if i == self.sentinelSpot: - if ct.can_build_sentinel(i, i.direction_to(self.isEnemyInfastructure)): - ct.build_sentinel(i, i.direction_to(self.isEnemyInfastructure)) - return - else: - if ct.can_build_barrier(i): - ct.build_barrier(i) + myLoc = ct.get_position() + protectSpots.sort(key=lambda pos: self.manhattan(pos, myLoc)) + if myLoc in protectSpots: + self.moveTo(ct, self.currentHarvester) + return + if myLoc.distance_squared(protectSpots[0]) > 2: + self.moveTo(ct, protectSpots[0]) + return + + for pos in protectSpots: + if pos == self.sentinelSpot: + direction = self.cardinalDirectionTo( + pos, self.isEnemyInfastructure + ) + if (direction != Direction.CENTRE + and ct.can_build_sentinel(pos, direction)): + ct.build_sentinel(pos, direction) return + elif ct.can_build_barrier(pos): + ct.build_barrier(pos) + return def buildConveyor(self, ct: Controller): + if self.conveyorEnd is None: + self.currentHarvester = None + return ct.draw_indicator_dot(self.conveyorEnd, 255, 255, 255) myLoc = ct.get_position() - if self.nearCore2(self.conveyorEnd) == 0: # frontier is on the core (delivery handled in buildCloser) + if self.nearCore2(self.conveyorEnd) == 0: self.currentHarvester = None self.conveyorEnd = None return if myLoc.distance_squared(self.conveyorEnd) > 2: - self.pf.moveTo(ct, self.conveyorEnd) - - if myLoc.distance_squared(self.conveyorEnd) < 4 : - conveyorID = ct.get_tile_building_id(self.conveyorEnd) - if conveyorID is None and self.nextTurretCountDown <= 0 and self.nearCore2(self.conveyorEnd) > 2: - if myLoc.distance_squared(self.conveyorEnd) > 0 and self.enemyPos is None and self.turretTimeOut == 0: - for i in ct.get_nearby_buildings(): # If a target exists it will find it - if ct.get_team(i) != ct.get_team(): - ct.draw_indicator_dot(ct.get_position(i), 200, 20, 100) - eType = ct.get_entity_type(i) - pos = ct.get_position(i) - if eType == EntityType.CORE: - self.enemyPos = pos - self.enemyType = 3 - elif eType in [EntityType.SENTINEL, EntityType.GUNNER] and self.enemyType < 2: - self.enemyPos = pos - self.enemyType = 2 - elif eType == EntityType.CONVEYOR and self.enemyType < 1: - self.enemyPos = pos - self.enemyType = 1 - elif eType in [EntityType.LAUNCHER, EntityType.BARRIER] and self.enemyType < 0: - self.enemyPos = pos - self.enemyType = 0 - if self.enemyPos is not None and self.turretTimeOut == 0: # If it hasn't built a turret yet it waits until it can - if ct.can_build_sentinel(self.conveyorEnd, self.conveyorEnd.direction_to(self.enemyPos)): - ct.build_sentinel(self.conveyorEnd, self.conveyorEnd.direction_to(self.enemyPos)) - self.turretTimeOut += 1 - self.nextTurretCountDown = 5 - else: - return - if self.turretTimeOut > 0: - self.turretTimeOut += 1 - if self.enemyType == 3 and self.turretTimeOut > 0: # It's a core you're attacking, so leave the turret there - self.currentHarvester = None - self.conveyorEnd = None - self.enemyPos = None - self.enemyType = -1 - return - if self.turretTimeOut > 24: - self.enemyPos = None - self.enemyType = -1 - elif self.turretTimeOut > 0: + self.moveTo(ct, self.conveyorEnd) + return + if (not self.isInBounds(ct, self.conveyorEnd) + or not ct.is_in_vision(self.conveyorEnd)): + return + + conveyorID = ct.get_tile_building_id(self.conveyorEnd) + if (conveyorID is None and self.nextTurretCountDown <= 0 + and self.nearCore2(self.conveyorEnd) > 1 + and myLoc != self.conveyorEnd + and self.enemyPos is None and self.turretTimeOut == 0): + for building_id in ct.get_nearby_buildings(): + if ct.get_team(building_id) == ct.get_team(): + continue + ct.draw_indicator_dot(ct.get_position(building_id), 200, 20, 100) + enemyType = ct.get_entity_type(building_id) + pos = ct.get_position(building_id) + if enemyType == EntityType.CORE: + self.enemyPos, self.enemyType = pos, 3 + elif (enemyType in (EntityType.SENTINEL, EntityType.GUNNER) + and self.enemyType < 2): + self.enemyPos, self.enemyType = pos, 2 + elif enemyType == EntityType.CONVEYOR and self.enemyType < 1: + self.enemyPos, self.enemyType = pos, 1 + elif (enemyType in (EntityType.LAUNCHER, EntityType.BARRIER) + and self.enemyType < 0): + self.enemyPos, self.enemyType = pos, 0 + + if self.enemyPos is not None and self.turretTimeOut == 0: + direction = self.cardinalDirectionTo(self.conveyorEnd, self.enemyPos) + if (direction != Direction.CENTRE + and ct.can_build_sentinel(self.conveyorEnd, direction)): + ct.build_sentinel(self.conveyorEnd, direction) + self.turretTimeOut = 1 + self.nextTurretCountDown = 5 + else: return - self.buildCloser(ct) + if self.turretTimeOut > 0: + self.turretTimeOut += 1 + if self.enemyType == 3 and self.turretTimeOut > 0: + self.currentHarvester = None + self.conveyorEnd = None + self.enemyPos = None + self.enemyType = -1 + return + if self.turretTimeOut > 24: + self.enemyPos = None + self.enemyType = -1 + elif self.turretTimeOut > 0: + return + self.buildCloser(ct) def buildCloser(self, ct: Controller): + if self.conveyorEnd is None: + return myLoc = ct.get_position() + if (not self.isInBounds(ct, self.conveyorEnd) + or not ct.is_in_vision(self.conveyorEnd)): + self.moveTo(ct, self.conveyorEnd) + return + endID = ct.get_tile_building_id(self.conveyorEnd) if endID is not None: endEntity = ct.get_entity_type(endID) - if endEntity in [EntityType.CONVEYOR] and ct.get_team(endID) == ct.get_team(): + if (endEntity == EntityType.CONVEYOR + and ct.get_team(endID) == ct.get_team()): self.conveyorEnd = None self.currentHarvester = None return - elif ct.can_destroy(self.conveyorEnd): + if ct.can_destroy(self.conveyorEnd): ct.destroy(self.conveyorEnd) if myLoc != self.conveyorEnd: - self.pf.moveTo(ct, self.conveyorEnd) + self.moveTo(ct, self.conveyorEnd) if self.defender.destroyTurns > 5: self.defender.placeLauncher(ct, self.conveyorEnd) if ct.can_fire(self.conveyorEnd): ct.fire(self.conveyorEnd) self.defender.destroyTurns += 1 - # Candidate cardinal conveyor extensions toward the core. possibleConveyors = [] - for i in CardDirections: - end = self.conveyorEnd.add(i) - if self.isInBounds(ct, end): - if ct.can_build_conveyor(self.conveyorEnd, i) and (ct.get_tile_env(end) == Environment.EMPTY or ct.get_tile_env(end) == Environment.ORE_TITANIUM): - possibleConveyors.append(i) - possibleConveyors.sort(key=lambda c: self.nearCore2(self.conveyorEnd.add(c))) - - # Try to build a conveyor directly adjacent to the core - for i in possibleConveyors: - end = self.conveyorEnd.add(i) - if self.nearCore2(end) == 0: - if ct.can_build_conveyor(self.conveyorEnd, i): - ct.build_conveyor(self.conveyorEnd, i) - self.nextTurretCountDown = 0 - self.currentHarvester = None - self.conveyorEnd = None - return + for direction in CARDINALS: + end = self.conveyorEnd.add(direction) + if (self.isInBounds(ct, end) and ct.is_in_vision(end) + and ct.can_build_conveyor(self.conveyorEnd, direction) + and ct.get_tile_env(end) in + (Environment.EMPTY, Environment.ORE_TITANIUM)): + possibleConveyors.append(direction) + possibleConveyors.sort( + key=lambda direction: self.nearCore2( + self.conveyorEnd.add(direction) + ) + ) + + for direction in possibleConveyors: + end = self.conveyorEnd.add(direction) + if (self.nearCore2(end) == 0 + and ct.can_build_conveyor(self.conveyorEnd, direction)): + ct.build_conveyor(self.conveyorEnd, direction) + self.nextTurretCountDown = 0 + self.currentHarvester = None + self.conveyorEnd = None + return - # Try to link into an existing friendly conveyor that's closer to the core - for i in possibleConveyors: - end = self.conveyorEnd.add(i) - if self.nearCore2(end) < self.nearCore2(self.conveyorEnd): - endID = ct.get_tile_building_id(end) - if endID is not None: - endEntity = ct.get_entity_type(endID) - if endEntity == EntityType.CONVEYOR and ct.get_team(endID) == ct.get_team(): - if ct.can_build_conveyor(self.conveyorEnd, i) and ct.get_stored_resource(endID) is None: - ct.build_conveyor(self.conveyorEnd, i) - self.nextTurretCountDown = 0 - self.currentHarvester = None - self.conveyorEnd = None - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - return - - # Extend one step closer with the best available conveyor - for i in possibleConveyors: - end = self.conveyorEnd.add(i) - endID2 = ct.get_tile_building_id(end) - if endID2 is None or ((ct.get_entity_type(endID2) == EntityType.BARRIER or ct.get_entity_type(endID2) == EntityType.LAUNCHER) and ct.get_team(endID2) == ct.get_team()): - if ct.can_build_conveyor(self.conveyorEnd, i): - ct.build_conveyor(self.conveyorEnd, i) - self.nextTurretCountDown -= 1 - self.conveyorEnd = end - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - return + for direction in possibleConveyors: + end = self.conveyorEnd.add(direction) + if self.nearCore2(end) >= self.nearCore2(self.conveyorEnd): + continue + endID = ct.get_tile_building_id(end) + if (endID is not None + and ct.get_entity_type(endID) == EntityType.CONVEYOR + and ct.get_team(endID) == ct.get_team() + and ct.can_build_conveyor(self.conveyorEnd, direction) + and ct.get_stored_resource(endID) is None): + ct.build_conveyor(self.conveyorEnd, direction) + self.nextTurretCountDown = 0 + self.currentHarvester = None + self.conveyorEnd = None + self.turretTimeOut = 0 + self.enemyPos = None + self.enemyType = -1 + return + + for direction in possibleConveyors: + end = self.conveyorEnd.add(direction) + endID = ct.get_tile_building_id(end) + canReplace = ( + endID is None + or (ct.get_entity_type(endID) in + (EntityType.BARRIER, EntityType.LAUNCHER) + and ct.get_team(endID) == ct.get_team()) + ) + if (canReplace + and ct.can_build_conveyor(self.conveyorEnd, direction)): + ct.build_conveyor(self.conveyorEnd, direction) + self.nextTurretCountDown -= 1 + self.conveyorEnd = end + self.turretTimeOut = 0 + self.enemyPos = None + self.enemyType = -1 + return def isInBounds(self, ct: Controller, pos: Position) -> bool: - W = ct.get_map_width() - 1 - H = ct.get_map_height() - 1 - if pos.x == max(0, min(W, pos.x)) and pos.y == max(0, min(H, pos.y)): - return True - return False + return (0 <= pos.x < ct.get_map_width() + and 0 <= pos.y < ct.get_map_height()) + + + def checkPassable (self, ct: Controller, tile: Position): # Only team barriers, or empty tiles are passbale + if not self.isInBounds(ct, tile) or not ct.is_in_vision(tile): + return False + tileId = ct.get_tile_building_id(tile) + if tileId is not None and not ct.is_tile_passable(tile): + tTeam = ct.get_team(tileId) + tType = ct.get_entity_type(tileId) + if tTeam != ct.get_team() or tType != EntityType.BARRIER: + return False + return True + + def getNewTiles (self, ct: Controller): + for tile in ct.get_nearby_tiles(): + x = tile.x + y = tile.y + tileEnv = ct.get_tile_env(tile) + if not self.checkPassable(ct, tile) and tileEnv != Environment.WALL: # Walls clearly dont have buildings so need that + tileEnv = 'Unpassable' + tileType = tileTypes.get(tileEnv) + oldType = self.fullMap[x][y] + if self.seenTiles[x][y] == False or self.fullMap[x][y] != tileType: + self.seenTiles[x][y] = True + self.newTiles.append([Position(x, y), tileType]) + if (oldType in (-1, 2, 3)) != (tileType in (-1, 2, 3)): + mapanalysis.note_structural_tile( + x, y, tileType in (-1, 2, 3), self.mapW, self.mapH + ) + self.fullMap[x][y] = tileType + + def shareTiles(self, ct: Controller, curRound): + spawnCount = ct.read_store(0) + # store reads 0 on round one, skip so we dont % 0 + if spawnCount <= 0 or self.myNum < 1 or self.teamCore is None: + return + boundedRound = curRound % spawnCount + 1 # to bound it from 1 to n + if boundedRound == self.myNum: # means your turn to share in the shared array if the mod is correct + + # start by sorting newTiles by the 16 tiles farthest away from your core + self.newTiles.sort( + key=lambda tile: self.manhattan(tile[0], self.teamCore), + reverse=True, + ) + # convert each tile into a 12 bit integer (if theres less tiles, just send all 1s to fill in the remaining space (so it points to an out of bounds tile) + combined = 0 + needToRemove = [] + for i in range(16): + if i < len(self.newTiles): + cachedPos, cachedType = self.newTiles[i] + needToRemove.append(self.newTiles[i]) + val = ((cachedPos.x & 0x1F) << 7) | ((cachedPos.y & 0x1F) << 2) | (cachedType & 0x3) + else: + val = 0xFFF + combined = (combined << 12) | val # combine all of them to form a 196 bit integer + + # split that numebr into 6 32 bit integers, and write them to the shared array + for i in range(6): + part = (combined >> (32 * i)) & 0xFFFFFFFF + ct.write_store(6 - i, part) + for i in needToRemove: + self.newTiles.remove(i) + + def updateBoard (self, ct: Controller): + # read the 6 32 bit integers, and combine them into 1 196 bit one. + combined = 0 + for i in range(1, 7): + part = ct.read_store(i) + combined = (combined << 32) | part + # split that into 16 12 bit integers. + # retrive the X, and Y and the cached value for each integer. + # if X > mapW or Y > mapH, then pass that one + # update your table, adn check if the tile is in you newTiles. If it is remove it + for i in range(16): + val = (combined >> (12 * (15 - i))) & 0xFFF + + if val == 0xFFF: + continue + + x = (val >> 7) & 0x1F + y = (val >> 2) & 0x1F + t_type = val & 0x3 + + if self.mapW is not None and (x >= self.mapW or y >= self.mapH): # all 1s means that no actual one to share + continue + + ct.draw_indicator_line(Position(x, y), ct.get_position(), 20, 140, 200) + + oldType = self.fullMap[x][y] + if (oldType in (-1, 2, 3)) != (t_type in (-1, 2, 3)): + mapanalysis.note_structural_tile( + x, y, t_type in (-1, 2, 3), self.mapW, self.mapH + ) + self.fullMap[x][y] = t_type + if not self.seenTiles[x][y]: + self.seenTiles[x][y] = True + + self.newTiles = [t for t in self.newTiles if not (t[0].x == x and t[0].y == y)] # you dont want to broadcast the same position twice + + def advanceMapAnalysis(self, ct: Controller): + """Spend only post-action spare cycles on the shared analysis job.""" + analysisJob = mapanalysis.job + if (analysisJob is None + and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US): + analysisJob = mapanalysis.get_job() + while (analysisJob is not None + and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US + and analysisJob.phase != mapanalysis.DONE): + analysisJob.step() + + if analysisJob is not None and analysisJob.phase == mapanalysis.DONE: + mapanalysis.draw_debug(ct, Position) + + def tileCost(self, ct: Controller, tile: Position) -> int | None: + if not (0 <= tile.x < self.mapW and 0 <= tile.y < self.mapH): + return None + cachedVal = self.fullMap[tile.x][tile.y] + if cachedVal > 1: # either wall or unpassable + return None + if ct.is_in_vision(tile): + tileId = ct.get_tile_building_id(tile) + if tileId is not None: + tTeam = ct.get_team(tileId) + tType = ct.get_entity_type(tileId) + if tTeam == ct.get_team() and tType == EntityType.BARRIER: + return barrierCost + elif ct.is_tile_passable(tile) == False: + return 4096 + return openCost + + def fillInDistTable (self, ct: Controller, targetLoc: Position): + w, h = self.mapW, self.mapH + maxCost = (w + h) * barrierCost + 5 + + for x in range(w): + for y in range(h): + self.distMap[x][y] = 4096 + + buckets = [[] for _ in range(maxCost)] + buckets[0].append(targetLoc) + self.distMap[targetLoc.x][targetLoc.y] = 0 + + myLoc = ct.get_position() + + myNeighbors = [] + for d in CARDINALS: + myNeighbors.append(myLoc.add(d)) + + for dist in range(maxCost): + bucket = buckets[dist] + hitBucket = False + while bucket: + cur = bucket.pop() + + if self.distMap[cur.x][cur.y] < dist: + continue + + if (cur.x, cur.y) in myNeighbors: + hitBucket = True + continue + + for d in CARDINALS: + nextTile = cur.add(d) + nextX = nextTile.x + nextY = nextTile.y + if 0 <= nextX < w and 0 <= nextY < h: + stepCost = self.tileCost(ct, nextTile) + if stepCost is not None: + newDist = dist + stepCost + if newDist < maxCost and newDist < self.distMap[nextX][nextY]: + self.distMap[nextX][nextY] = newDist + buckets[newDist].append(nextTile) + if hitBucket: + return dist + return 4096 + + def moveTo (self, ct: Controller, target: Position): + if ct.get_move_cooldown() != 0: + return + overBudget = ct.get_cpu_time_elapsed() > 7000 + myLoc = ct.get_position() + if myLoc == target: + return + # target can be off-map on small maps + if not (0 <= target.x < self.mapW and 0 <= target.y < self.mapH): + return + if overBudget: + # low on budget: greedy step instead of the full dijkstra + bestDir = None + bestDist = 4096 + for d in CARDINALS: + nextPos = myLoc.add(d) + if 0 <= nextPos.x < self.mapW and 0 <= nextPos.y < self.mapH and ct.can_move(d): + posDist = abs(nextPos.x - target.x) + abs(nextPos.y - target.y) + if posDist < bestDist: + bestDir = d + bestDist = posDist + if bestDir is not None: + ct.move(bestDir) + self.stuckTurns = 0 + else: + self.stuckTurns += 1 + return + self.fillInDistTable(ct, target) + bestDist = 4096 + bestDir = None + for d in CARDINALS: + nextPos = myLoc.add(d) + if 0 <= nextPos.x < self.mapW and 0 <= nextPos.y < self.mapH: + posDist = self.distMap[nextPos.x][nextPos.y] + # tile queries raise outside vision + tileId = ct.get_tile_building_id(nextPos) if ct.is_in_vision(nextPos) else None + if posDist < bestDist: + if ct.can_move(d): + bestDir = d + bestDist = posDist + elif tileId is not None: + tTeam = ct.get_team(tileId) + tType = ct.get_entity_type(tileId) + if tTeam == ct.get_team() and tType == EntityType.BARRIER: + bestDir = d + bestDist = posDist + # boxed in, nothing movable + if bestDir is None: + self.stuckTurns += 1 + if self.stuckTurns > 2 + (ct.get_id() % 8): + movableDirs = [] + for i in CARDINALS: + if ct.can_move(i): + movableDirs.append(i) + if len(movableDirs) > 0: + ct.move(random.choice(movableDirs)) + return + + nextPos = myLoc.add(bestDir) + tileId = ct.get_tile_building_id(nextPos) if ct.is_in_vision(nextPos) else None + if tileId is not None: + tTeam = ct.get_team(tileId) + tType = ct.get_entity_type(tileId) + if tTeam == ct.get_team() and tType == EntityType.BARRIER: + if ct.can_destroy(nextPos): + ct.destroy(nextPos) + else: + return # dont want to increase stuckTurns for no reason + if ct.can_move(bestDir): + ct.move(bestDir) + self.stuckTurns = 0 + else: + self.stuckTurns += 1 + if self.stuckTurns > 2 + (ct.get_id() % 8): + movableDirs = [] + for i in CARDINALS: + if ct.can_move(i): + movableDirs.append(i) + if len(movableDirs) > 0: + ct.move(random.choice(movableDirs)) From 03a56ac49e38da88ce8e2df0c5e69bf954b8b13d Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 19:15:39 +0530 Subject: [PATCH 03/29] update bot package --- bot.zip | Bin 9967 -> 27133 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bot.zip b/bot.zip index 502ca8fe2d73949e9028efee899089a61cf92701..60a61b86fbe573add0308bcf6bdf45251e676804 100644 GIT binary patch delta 23809 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 z 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 delta 6511 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 V000001ONm8;{X5v_9XxS002XDX1f3Y From 1a29c70b6bc0f6fa7fe28944781cb887bc2392a5 Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 19:55:16 +0530 Subject: [PATCH 04/29] reactive home counter battery vs turret rushes --- bot.zip | Bin 27133 -> 32036 bytes homedefense.py | 449 +++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 63 ++++--- turretplan.py | 84 +++++++++ 4 files changed, 574 insertions(+), 22 deletions(-) create mode 100644 homedefense.py diff --git a/bot.zip b/bot.zip index 60a61b86fbe573add0308bcf6bdf45251e676804..ad21405961a8a97dea885d89a4afd9fe2b602b69 100644 GIT binary patch delta 22859 zcmV(|K+(Vb(*dOV0e?_S0|XQR000O8ublH-!x|+|0ww?eA-n(p2LJ#7ZDDC{E^vA6 zJ!@~{cA_+3K(RymvU zWuBE)kyMz`K@kzz+R^XpHyj{4cG}GaRE5s-=ESV&z6ALfW6Dh6pLtRlf2+>_yRj6 zLCyfdlZ-Dnui(>weqQioY5bZOJgLe7D_6;F_A;+Z0e@=BXB_$_!MvtlyimQE=Sxl{ zN?!MJm1N0cQ>LYQRjrGHSF1&m0fMiN-@iURJAV6t9kNT^yW!dS`-?Y5cZVO&KiV%& zjz3(OzkfdYaB+42-s{u%C$BC}&(9E`1`h2c#Z(tA?Qi$*?Xdk_mMoV!eAwG%*Xt|; zVwLk8nSZBX`>ezv&-g;TgR%VU+w+&lZ%4;J{d7Kh`SHgePu@e-{apl>uXy$c7*Z@zgGn^eXjW)=j zY?YK{GF|`&K$qvqCk|7w0d(h--yX32f%x+M$4nvk{Q-LdO$bOC%rf+Q=|-Z3Hw+PUo+xm z!GDLq~SH*_)vb>_9xh-G-Q5>-iuliz?CksFvcsNhQm?aaGkt<+nKy19xvwv|Y%BbO%%N36}4d2-;wfW zxZ1GW1X(Fzs~OA3zjB20>$*gKLU~jX<#AxJouUSTtWlF%D1n0rZ9LSclA=HrMSt90 z-|gNEzlMfTz*N1|6=v(j;wNB{Lw1&f?%6@s`MzX}9JCMv+)E&$Qg2um`N;tR{k$Ol z1DScnG~ce)<3&1wc~|BP`Gq)_W=WdOatB7qd8U9!MAoH{FfcfFJ8tvq~~DD}IglYc=!{GfTPLfZjU8?m9KKTX!#Vef%lPR*pMl7Gp~PiSi# zvLBO0=@nSqCY!RxDO5({*Z>9RY0WSf#hTlg9TOTLB!lRIV2My0L4J^m`3*Nl*#wTm z?=rAr3zPqyB3xNVL7;DwHQ0MFjH@EgXFlfnVrrsoR(+!t9|{Y<>IB&&tLkDw^k!(B zwv@s7q>Bhy;c^ZKyFnMZNPm}H@U4mOOQk`_FcvfcwJijaeL}X2+l4&)#5Z|yl1(Ec z2Yqxq5MmslZUqb-n^6jp539TiHu)Zp}@rerL!7hn1ZzKX~c?z2G&Lk!# zW<{SpJ|eUXjm#@Sfl1~2V09_5HD$p=!$TUr0u$AMB}>+VJ}uqmtj^ z&CQ10@^9?&bDmz$tA9`h!$TU98HD9~y4gVu(WxxwTEW4nGCuJ|ga$7^p1ys3@_zL4 z{Nf-0Nor~E^*X})aB_BWdUo{8j=Hz_{)I!SIj-dYf@$4)NNO*Mu zOwEEsz-AuQpim{BEIC-O*<=Ib>uT*`J}x zt9-RyfrG?LaFNhM!7s{gS^^Rqlf$)B-gE3R>_h*+>iR@abIFY>X{furC|n~fXaoRk zq7x~#B9{`|{yYOmYkCI;C+j^1%M2V&w9op1iA5zhT}n#Q-J~lb3-;)HVL(0=ept8) z5NembI7D<_pnofl0#dx#(2Gb*f@sLTYp-_}5d>A0-_w(X9^SD>(Bwc7ez?Kko4~}E zID&(SLo-RT5rOR~;4xCz2u7eL*+v5*Ll=Wyqw=>kn$tZo)_o+ye!LMT21)wNX{K1D z8)D%y&{INdB$>PJZG_T}!|@0PF3KSbAmuK&J}|SUMSlvWg`Psf#XvRrsCFyBgQyY_ zDT|;=uE?mYa3=2#Tx4P>JEm@^%oqrH6QYrt-FzA$vR4!3$IJC>MpjhM@*2*6ID z0NfH`kx}F@ubCg<>g(dtMm-h*GbvZD7x_3@j1Zk(-;`En=@$mNww&5wH=M5+&+_&4 zT=E@y^nZ7i8bntNp?LyqoszrR^ZH_=1)4Qq7ZYCgK(4EA67UEdQ;^nWW#EmE{VQ3b zVF*nv)2wF@{ju4>fsL3A0jH$^a)fLO;z-VWGMI}>&`uO_q`2%*sGyCh3z)agR|n@ zrUgJ`z<{A90{$}lzA93%uPapd|FBze%}e%d5G|l>CH5p%{o@%z7>e7FG7F-QM8&)8~RfC0gQ&w)@26sQ=y z#eZ=a^QtlSeRsoeN zxlVXX7{eZsMbmSqku(&fS9=Q9ANbFu2&Y1YJ_fj@C6Sh+G#hrN?odtCO^LF+%MIJ=vr$dRIFPo zjtvbR43Bvu7J(L2!HcNVYhW5QBPhS*Yasb%nP>A8+%bn&ZMA`C1^w61Kp>mr%NoFDgP4yCZ9shlbG>0%qlKVSgz~ zu%OvEc3FLG(!r`wObhGprXJQFc2_;T+`P>v%^oFrcgS*joz=l8nf{tgxG<;?+%TC= zd#*{{AoW!JsN~ZHx=TNjQq3YvL-nV&s4XoL#LAK@^p#|aWoT?6TyX3>Eu%iXPU+)g z$2kh9nv5sKcq7%!{__w9XtWEa5`V0rdo7hsWa3T*4l-Vq%~7dkJsMqEG3u^T)g#@L z1_#sVELLhRsArPX9DQq+96$iC^H?p=hk{q@A~R={5libjr=%0{9yz>bLHTw>;c|0+ zb(n%O5LbO%(S)HIeMhL6OLoXT4k31As0w^e;_REpdni7^lai4v!i)9f;(zu6#Syt| z^u9lEp*`9Il;x`5-f)<)GR1To71aUQxO$0@OSVT@ID)b!6l;|cx{tUi7rhVe{a&Dh zMu-F!^i(=;C894?aVn)m96@Kv1u@rl&? z*{rAVQtQ$Uc-2eSmzTf^-+vM-e9Xu=2*p`?y)Jl{=(y@qy~f?pDRUwG@{)p@pWUdg0=!^*{@%!*Y{NM$q4pjP%>_{eqXPoYnzp5GPgoiR1dI){5p@fh=qX zu5f_-W(S7Q8w60ama~BYdzaSxD}VSX#CLhs4?vTXT(wDQJ9RNDvS;_PnU3&DltV0g zR!xI~(%`EJ8Gux*Gk?rwc**CYhkm>OT@js{Y5utY zq<`4Jkyhty&eZ)Q5ZF2K2lmXWmP|k4nhH#nW2vMpWZFC4CKpu;Jw5Qw)I>Juw*i?J z$?a&$$Ls4_R-uV%_gYw~)KegUna!u+=ZmQfMwqx_wtX3REtFAC$Pp+(?QJs--ksfA{IdjLfK9E==l|hm$$a(T7YK>}5 z%Hd`}hz^2^%T`oYXdKPl0GvhB-y&etW5%3a68*S;r9Ud{7q&6PFxCpF7~ICJ!ehZZ;M{MEZil+;s&$byH;7f{)nL#p(bS9Ht zd9#g*h9^$#_W>1Z@6iiLXz4@fqO0!_6<3=bxqq}mg(g;gJk)dEAH1noZQyI2dm+{sz@lW;_g%}QIh3)M+00+a999-wlF zNn{EiBd0xx1o$Y|G~BH*7dt!NORC#JTYnYUYn7yh+|A^XjYv@dehCDUVhvdQ6AdNF ze)b&Z^d=@TYFLvSW8M@X?MJZ$$OqJMCR0xUrFcu!hjlr}46%-|E9Dg1h z2IOFrzu-SufXsQVeDg6z&VV7viU+|PbT+Z51)5gn5kS9#Vl-M8v)0%H z;jRst+aHeDlYoX(PraI?tTP9;T7P(>T-zAe3hJkm8?l|EQE7PfqI)%>rNT+HKsyQ+ z;kKknQh~q$;mIv8*zDH+vuE}Hq8lOVIpP4Y+yO&fwp&F`z{AqinAg4Unsz(v$@A~n z`HUp`fIWZu9XpxixKzlLneZ~NeA({W0DEX?(#8(zE-r(lESLxS42&qAc$fA*cC>HubrLHDP-ME6!(EP#6}-V*n1#RhI61z_Uz zg`xN-A~UEc_yNNop6N+%nF>u)yUEy859KxKR-QJ23$TeXmxPrTnnz7JwJjk-*T(B0 zaOv0yz5ffnZ5w1Ak@1$;!GBNOhyN_ZhIfOA7!EAE2LWu{goLk#LDzP^WrqSw`c~+) zY2CrxX=~Rs)xYf}7<#h}q|FJJ#&>6b;8nx4cQzXu5eAAU-Brg9L@&82-Y#w+Xq1+2 z`A4n-?SrUXaIp)hE*#1g`R5X5GRHiGW7Vcd6AIuwV}CSdV477FQh#-GIVqIWv24a~ zM+kbE+~Lj{9mUeUR{_}OP$^@91RR(Ld#+81?QI&{6A|96+A}7f*Pu6+dXiJu2Lhvb zrG{~WLl5#|Pt&M)=xKGgKda&XX7^ z*&9l2RvD9lbo^F_(tpe7CSVVu`#c)JP;%K+d*?PpL$b!KdzX$Xa_{O+6RyYJ4u~=@ zs({G4;hV!nvK&tnwmNXRXytK`7vAtJD(suvc$@WFGj`a~2zmxFq$D1Z1~5h#p?cT# zLz~PYH4-i#F~-#sn2Bx?*QV0637u@=4#K7DeRPv8Uoc^}pMR`sgHCJw%b%!jo^*?+ zRWo_hq-q!b&BH)W{L`rS7C(?ASiI#F1*_~fvn3vO3eKNr!KBDeUFq75AV$K2cyJhL zFqo3-P{ALdPWbs!$(@Ov^>i<`?A{4Kd?M@ek^13<`ud$Ywd3pbt;iYMhenNS_4R5~ z`xI{6XfOp=y?@9zG1TJG-j<4622g0>0FSkKTc0|y#bACGm)eA&Z8}?1p{bU&Tk%`* zM7kH?R}wce^GQAS2=P3|N!L6_`%UPeT{o za`y#GI$;9)ae0lc4<^i_22TDG&(=MxX=j=f773eA@PF}0C5Qe}zqq3PgMsjUgYnh^ zZO=85o$}HLZ5U7qcVHUpz^Q(_j)CW|qFC!@)^7UH@H-6zx1Oohv#o)BO_Rt!It?Qk z=JLpPiv+hV+j!<}TX#AHJBwtRt`Y6tEqlb~efFn6nTZi@T>Xf-W*0acRpQ5@*&o?H z{D+4H4}Vvw8IAK8SV8FZdQYH=MGf{Hdj>e+{~`b@jMiW@$B3ZZV&FqAU{Sn=YE8hK zp|<)I3c`N(O@Ebkx;l{NCZvsmRKk}gNbWM75oD?E2GEeTh-9rphB>4|>TGyX(p%<` z=ThQXue59D03E&(R}eXE8i)Sj`LGCVfF~)S`G1(tn6U2HZ);wp?zu&D{v`@~v1!mb zzt5RrH(7q0ofkZCz5+g)PC%wmRwL@XyU;eJaM4X@FI3!5SLxn&SC^Qn9Sj1X>tVwI$U?m7YMbRqH4}=!4j}|T&fT#V6bn-7AZZ%%ylN(NIULxt+0!{m6 zV}C&`suRWE?YC)n@$8d07eVN|@S(~v^Fea~?Vb*7u{GHQbB25RMXX@A$dZ?msF%4P zx%pbF4sQ2~HjxZnPVM`HqZaJ58sh+MZh<|1&WoGqVw+H&md7QRr6ZJvuZ@Rd=tb>N zW!!K;_1h0WaIlY9c^J_{xeF)R{PcbZl-z4J^3JsWDo z&MpmAkY{E&cy$-|_{5_s0Zfh#{kWtoi~dX`!6w>FFgRdi?(%ILzlK0~6zamXjt3Os z!g!2!YxZlHINeAbB6kG2UMM=zNHzCCaWAYM?-0Hau*NJQeDFwg!i5gCUl()?qJP*J zoZ@-(9Bp%Pj3nrx`WQ);OyVcq;p2uMf-R&v(P{=_0x($bzNMQCECXwVqiDoERxWdIeBM}nGV8Dsc%<6?&Q(AdS$Sy|O z-PGl{@xs;E0K*ps7Mu>z8ms)}?tj%5p*Y*}RbA`GmSJ_``aHY z#5bls;sI(q_}oP=ge`B+ll=nmvij4O3QEu?S4OpN9l#{l;D+Dm z)dY>ijBG~gM9*$C#mCncKF+p|sST3=B4MZK%Ts?kXX!K8KPWncRFt)AOGzTsH?tdG1>cP^8hFVTHrh2>5P+=%)_rale30F4=rm7@J4ax2% z)x(ynOg%CS{u@lgwrS>{Mqo7(o`@DPJ4Ela#qaDWj$+mvl?&0rM2QlW>M9-9Qv42e zL0`llZ59%Z!PITzBiC$q=W+Xv$B2{fPy+pipgu7jC#OmqQ=yz%iGP@ALpOy7SFgVt z1NL<{6gKmcU*{WdC)6vMgx3}u2xAqSLlWjY6(OmSk=GKEtZQvG5_cxPGa;fAoRJm>g-=s1ntxd{mW5^vzbxJEP=_(1=td30+Zr0WuIoiG{%=%?v&m6xh0;z^ zY_zI$L)u~Suy$M>z}KbZH#DlhI3p=Mr5p2Y+5YY>{tq`x?uKH>hWFNzS{w_!HXHf@ z0vT;~O)#>CbzuCIsFQ{W?D8iC(5p-0Sw*4y3;atDqN@rvmwyq|7cKjW+Yk@j03>eL ziV{j}!3g6LyH=?aDuG$pLPLFlL*Uwn?TF~j#jP+8Zs0K`LF}|tQyRE?v$_~zzDl*4 z4!E43VOhnkD!=(=6DyKo7J(3nSh^vj*dCMcrH)QIU}Pa8cBqNn_EFfpunUDlE!OmU z5@U-<17FX6X@8Ef*(HoP+vkvK-`%bDg>Bb6Qrl=2%%NVo#yFydt1`=2UcfrELP% z1-r~?liW`KLb(biiNn%3THJ#lwS~K;?l(fG62_5GgnyNa!Mn+_x{0<;8;wnRJ1*L( z6)xL`oYfmQhg_|bEBF1g!_YjNGJkMBD+0~YDGEiPdGsin_&HA7DR={x6`UrN(ntbJ z&kR|g{PgbPmq9$2G8m9{2SG)pL#XI8+*#&_1G)c%VGE7g(P3+lBs31eSV5ca_hpDw z&R>u>Q7h%It=Ipl(#}0;K;qP}lJ;*g725Iuta6S1f?X=&ui*2qnSI+&4dAf%oQ#`r zHn^TPVOrp2Ao1zapiaFjG()#$c2M6<7IRdY;eSIBVBPuuvl``bJUU^|3?7Op|H~%^ zI>Ux-81jWfhj)+jd|^;oor+DjudnFrALCr&$i-S}fOVyfT}f@zBg?187VQ7Y&AX&5 z@iJ8w!5Al=nYzVx*g4(?LstRI#kX>RvxhHN;4g|(!STv)Jb`zdm{-a*oV~yG&8(78 zPJhZ3mOO3RukCF7k*AlRPQAEejR(P4Niqd-sV@9`kp2Zu%U3$s4?!kD*Em>i{Fe$L@Mld=D9ln@HpW~x${Tw>?ed0UtP@F;4o54Ja zX%@5i+u$-BUIjFe!-FjTbFx^JY_foU0DrGfq2FwteBye+sbpo&GR~*8S( zGsQdK`~8yqp#CiBnpj!c=~?7b8QM4)HeBnQVf5}oac7%4l+oUN_@KrkdTfgeF|ub& z%6R9B8G8@oK(}_)XTCyH-Bp)X(6uv^bJf#?v%dw9ZoQZ$tSk|~8a=z2@a;4{oPSX2 zkF(iRw;+nv)6ckuE-aM6uJpEU)BbVNrp&+Wu`Y%?LUEHPel9^{2GP~@w+5nMyA&-A5BCSMq*zHfO*<`{9{Q+=nW>>wauG>OO1?yxEsrCD0B zS&=W{CA_kO>NdDT14*3SOP*vU+kfDmAaMyuE=W#SL(I9_u?)&Bp=;0Z-7+T~G06)w zZS2n2zmUR7QL*ucVXy;tAE~K;?-1DDbEzk1P;ibntR}b66!nJCns~>F+f%ANcTMPR z$)GCx3twLDUb#031$Lbl{1fPPen=NGHCmrLB7*@sjH%*E697pQwztp5DSvMCt@t%K zKRrQ8++quTCMhhv7DmgQ&^e3_&fk`Y*8f`&?tL%+%aE)`BP zwP&1$y$oHTJIR;hRNNcj7= zsrb{B?5gr*8aXuC#MsJvH-BnI{Ebg^xm~ZP5di!04{Y~t@5erS@q+!(Xa8e;-I!nZ z>8pV-etQz!?-o5WL?Ezx_v4R0GQWf|N80B6Orsf6gAWbU+XTo4*MWlTl(SU5ga4S*4fy<5?>vZ1yKqO` zg^WatP*)8l(U*o{PY)odjA0EcJbm4{IRFNBGkyiZZGGtWF~)XJi0u%Br9W#95;#A1 z1YJ3_iBJ{kC$Ycu?|-7-zX%f)uX3izgq*z=mn)GBm2fODAY%T`ju>5s1m%7af4>i#AmB$U(2YRFD!C}`n};0^c@r@hDSQq)+S zzK5LN+k1wr+pihENt*(W1W5{w&Y@Kw5*=NPumF=E`ckm@E`NwBvL7x|k-G-vuowxz zz)_&uMD*Os-klC0tD)SOZ{CoHEft}#Q-rL_aa;15?qcQIU}++T!{Xr^3tW@jTVIzc zX-_FPZPz>YbStN%(WVz<{l*r3ppma?&lCK(L+ZFs2LTPZm7CRIA)+Ny3D+F^Yk!kC z^(wFh!2p1{b$=%$68@$o|4si2*bt=wxdFEcUJ49$cabO4Nm5oq6(&p06sxf6Hl5g~ z>-)F9wyBJV4!LcvKjnRkU>5k7L(rU}7_10y0kLaAuj8{K#ASSY6A5kF zqbn?hzkk)2Q!C*k+PV_|Zk*dgbtT+`4>Ovl=zzg|M&r3)YaN^FfI;ANm0&P*%Mh#t z?T29l(!X<{RTSPX*s2f_ZMXqfl8=ED-+08;Af$|wBb$Z1B~95ts98ttnp+57h?#>d z6>Zk+j-K^t_Bt)Ai^i}&T|*h&wtYLGqX3&#=YKM}!`6+H{{CoE0$v&UrM6{h9JqEH zO8hrYS(``G$#7eVsh({b8L_AJepG{(o#K}ww?a&yci|0F{09jBf;!W>r6UNoN1!PFI^5u* zut~@WNVCUbGp?=S*3ncCt*WMUPZMG&#xh>?@9a=laTjCEa=)16HsNwW9MWzA?hAHX zD?9<>1)Db=M+H`blocX0sgyFRaMgCh`F~M3qnIsOn|S z7h1^CnxTGc@gLFTivIcKss$w?Mzk^w1hvr)MNL9kLCdNLHK9|QQ{&5rQ>$r9_tYvz zB(pYamgvc%YS|iKj3zmFjQK4dh8?|zxPAvUikIu@HLq&H8L)uC`QP2$?Idaqw14!G zSrYwbRCZ)B%t>Km&hpvpF`j>lj?5BmFZfYf@D1BhT@f9&9@X{Gt@fh@zNU?LJnJIA zMZCu1E4wZ@pKfR-fXIP=lBo={(lMaG0l(gW&z(4;pO@9^G@RcgzR@(ZH84J%WE-;V zJNN9cu=S=T&O4bq5ul;^Y!T5vRDZ1DFfrL3ynwrI>#-g>b)kFRkp31rVOFB9;J(ll zYmgN+!5a=xH7GBEdvjjFX%?D57ov$Q9X-Oj0`U|GoxO|3nv^aRxIulg$>-I zj2Gk9R%YzNh+o2l;dE=+2Tv^!^eg?R8*#&DM+A4}A6|I5{qNYH zgYjg65VRjU-4Y+Y(9vdnJAaItHZiKvtg_1j3vUP#*OZOY{lJzz^(ZMV&&8k6bpuxA z*3D=k0p|<-ZzdqROt^2KZ1)u=lfTDIbvr(GZpl!js4Wb%W5?^$Y*KIxp&D~zMoE^q zHe2Slk!W(S8g!?*W<#LS2#DQ@{{M6PtIh5I1yD-^1QY-O00;oq3$s-vt|Jato%3A! zHdC$*~Gj$XBl6g5hUPWqA$YeRdmVY20TO)R$mmuA&@%!n>r5>a@sv zyoNofA&b&EtFoddVIgZd5_Yvrvlu81^5y&rk1`I+*5Nlagz;8rUC3T&_+af1g@+2ygs%B}N zCG4uGNjVx-VQIx8mK9aCV$*1P0|e#J5PDxM%W1-j8LP`>eY4N*%Ct_(3WQLXE4BoN zWh??k6MqT;%2eD{KpZHFsQJDg2;%Z+0k46tWfH}RWS#(zumyK%O{$xgj~^9xIlPz` z<%+0G^6M0uUnBX|G|53%3$7kzQOqm$w*p=RTUOA75g>{aHoYl6B`gAsf6-@qy*`6R zFfPMjhd<%UjL%pwGM+(axP`iqJj5{C?z88^;eQYCKG;Jzwuq`Ky2=0srySeB(Mg?+K%PVBbqgh9;Kecn$`fSZ+4%VU{qgt%Rmv;kddbFzXXkxjQbp>= zrX|P;B#X=y4aKL$Em8~*A3vIAOQfIO-6YVwh+QvpP*@Ok8KIPb78G-iO0c!Hg**n$ zpML|23{?!&8kB0jD9W1U(L9OkAr-ktnq3_qUZkDo8jFAhJz zuO0k#c6fwuM)-aF*N-P>2f%IRrH0(_fH= z9e8|%SdYhNXW{9|#qnE&A3>!*AO1C@SK;ALW8SJa9}eID9O4W9XauF-oScrsvwx3= zzaEFD?~i|ix5w`e&!Hm3&e&71iL3%O!cIgCOM$|D79Wh*0z?g8cBrSkKR!A=49`BC zoQFs6PtVSw_)FvI8zAnS^XdHJ^c0$A0FQRe=Mo$qe>^<_CLeyF7YO=Xd-mqt_|0FM zH+zbdY;3p4&OjmAy|&JF-61UlhgBe zefud{QTyT9$%O~>*D-ov*@Y{qRZ_G8?Y<*(__!XqY-~Jn&_yKMKQez$ohobhq2HZ@7VX>Gus^Y*q_+; z{R<5z_&Wg%k{ov*hpn7{w^27hK$r&QAY4S#Pa*lHnrj#0ScQIFQh(j#NmWVY=}f(5 z2M0`9D*>QUux-*4+*8$~qaOP^{8}}?UcG`LTb0HS0;qb3VuBlO^TG{X`n%np&iiGq zH4MDbB{TpJ*KMqsNC{ znq2Dt#QqbJaaH0{X)BBkBG^Zdh;*ENg)}?ihD9`?3Z}KAUVMz&+B)TR2*m~ceBOS*E+^b(fMVd3IV@%~n6RQ+&^#%yeO5sSeyH&W zY+4Vg8B|l?cz?dnbiBQ$FjjaIWiwUNj)q#JaTqNYNgfBrJN%dy*-^dG4}GejXEV}3 zWQbGLC}gu(US(-@V~9u2tzG%_8Tb;0^^)@X$nj9*pqZP)9C>;Z;e7fUrP zJO!2@&nh1IX@urAgt}GsbzORpUaVK?St!Pt|nZg2-Jg!haWHCyXqNW178@0npWDcEGH zf(k3HnvFEsaqp@ESD=@@zVpVa&-RK1V=mLlZ z_8Tlp=D6wa8yW7#Qh&*4`Y?WZ$6f?{_y~*2V38mvEJm!;CR2mZJn$j2GG^Sy1lQxQWyM`m4z4H!4=A9X z1;E4jE28GPSkqB6IO@2X+eWC!I{AVDdi`K_?a6=B7BP>3-f^2`#UcrcG6jtWvk{fo zXnpi)8wEj}=G@&R(4vw>GOR9wB_jGA34c@!s7r9g>~?3DT_?pH*D7?6Midk7jEE&U zp3G4MhTdQSG-4C#1;Qa2&_J}MXU#w5GC&}|Xmhz{i9o1r>umEWG_s^M!5cjq#3)pc z&lIM*N}yZf0ufM!P77#2wqoTn&w)INPOdJ2CgY+(B^^>y6D*QW0T~$r?CQo-Tz}k{ z-W2H+*B%j_Mi9<|5WbA+y8%dH04_B?PDDmQ-)o3GX$b0xmU^JaGezy8mNgKFu#*}1;?4LsU5?Lt z4L6VM7%62n1Y%6U@xHI+EGSw;Vn^eTH1yy&VUrN(_;}Qd+ZH!?juueW_k+|p-T`gP z(%Mu1>;W`ZEEK3?u3D`Kynpbs-E6xsDxRP&XRWXUMj?}%UuPAdl)vLZd!HP%XpUEW zUi2+}t2(PAo^co0Ym1I_Fm!bfT0}k2QQ5?*;;)&pT)#<} zI#4$F1&+}$d@DMY0&DuPp-(C7hY`+3$2htZey;F{rMvqh7~f?Hoqs2tHhs3D(1l0~ zf&YG%i-beU13{EAjw8H!zCg%Hg74z}O1&Rx??>W2IE8l+%-X;X%CD=&4H65fmwH>w zau|F-@327wER0v$gHly<#e;nJuGK!VTCECZQ4KnZi?+BHYEZF)b{bUN_nZow z;y?~X)(At~Gywhwlz)!IaQP*bSrs9+_KJ3QG!>)iQ4)t&E3iJ4oD^~q1YBwQlu{zK zic1vx4u1=>?+fkXE5it+`wrsWjuNGzttu4oT<_g!o9+WCYc=0u{&~4H`=Jm&{)6re zQ`&Q)@gtwrs32SXK}|$RtK}S%g7~0a4u3_?6!iw=E>M=%VHn{2 z$_eq*Ni+}1W!ET2$RYQXgvVsP6MmC)pscqIPLh%Qfk6!QW7U&1HpO@}foIlX63Ay8 zEuYD>1K^~QO9I0M9RMOh)dwO&{|l8Pp^T^l#j`5CAfY*iP?nJmVytdq9GZg4&_`a{ zkfQA#(C0R28Gqdg#LbGub}OL3YeMe0l*$?TbcNxbJfc-Jf4i$`l!q9VO781UkY!mF zC1;Em!+en?HO*0#t<*RIlX+Y!v7YmZ6YnG_-bYP8;Qhy1QN%JmC$Pd3Vnk$a)`a9} z3Cb0mKkn^a(hxE2r)==mAHIgE_48_#PeU*WVR2O@<$rA=`unujdp3otqPRWt6%27Y zTd~`;O3|c%l9y>x^@XLulv;}GVsKFn%J|uQHBFTvG_o6RpvR_d-C!MV?b4gVXe*N2 zIdurGOAC=lim$7r4m5Q7;+?lW)rUFDS`Jv zXtV>=I|FY&aJdPlU|t1Id%7j~daVHCB=hhANPnAnh*fr85ovf89aIK@fm~7iJDNyj zl)s2Zz~W&SsYaE8)=ft5F8k;vRFi?k`QG-fj0ft)Ez{{FUU%6}*IjXqr*gtJGM#!` zzme?}d26om+&u3e?PSj`Bb%jXtGsa+VozPG(zOdbc)gfm8+ry`{XbdK^7lDP3?X;TVI?iX1) zO>tq7QXw#Z242cZHFZXXjyuC@J_(Al&wm#&ns}4%)2KWTtVuKG8|^-u9i?-g;r57) zOoHBs%b*#Qn(+yIh)H83njnd!h}!~R@73|P^;(cFpqAUMI4-9ZNIOV6tuWkz;sQv@ zJn<-m*E^&L35}PcpQ6&ByhH5`p)|`y(|YNd;R~{KtaetKtRsoU>H~+Pn6>`S*MEkl zR%`nYxs#%BLK|ji(bmB4GEZQ8qSEI z(&spf?TY4(Q2(!PLfsDjnCjMyv|hhrjiyOQ;DNd(s#>pXPuXF7>^=jBWAIRDWSZgt?RqZf(9=HM&tbQsP=Gpd0#iIhgReXZKBn zs&%>6xw*e9;A+%Ym_pC|)J*WT=BX-aa}#Uzg~*c3WJzl(fKjuh=}yfZp0w8|?Zn`^ zs>?j#b*D)c_*%vRXW6p42|>alZp6EXpBHusFVRj5zVc;<+JU|{vDr~MO50@v2PF#N&Y44zs<;ztMVW8X?M$RQY15~@k~jye zqE3Z_ZKdGRU?pgCr7KHvHzb4Mm6sNEPzx^A;jFzV>m>I0|Ej!Md zG9upyt8JOkZxX_C1mHSu)DUYV)kSZZFqHjsiyodfE*AEj#+Mq|J)iV8ke$hkZz4Q%gxFAex-+E9Pk-y@URRse% zO1)K#DkjAK5yiv$OP+?(%o`lh+n({^qtZ6i^RYCmEt$K-ywh7 zof8=zj#Foj^=Px)A%DV~xT2o%wH)u7k=Uc=X^D5~q;K2kZ|MO%Z1mT}gZc^$0czB< zjrTSB45pvbjW;^lQ)PXZqtzSTt&R$SwgOtC_(q!^H*b<>Tcl$y_})mjJj}&00*mq4 zo2a^B)dC~O70kou%M6D3q*zu=xXk!zwqg2Tui1;~9XR;D%uT#ztK+m_+nIRL>669>8S)2?;NN8Q_HGmH zJeH%W-n8E9#(uFo)SghcjP2`A20;Xt%@-4L-RQ5g^!~*$udUakOxP7V7wf*M9krZ~ z{}YCEqc(kwC4ZH~(|c!is>LWh<>wmag_cW0Cv!x%QJSGy?t{|RNeK$TGqr`z&vAcB zTKO=daV~OPUn{bVqU(bynk6tj%hKrzUPqvlb(&=?isx8Yp4??T9$U+kLp}CfwvX)j zo-pUTwmIMZN=vk{JNa!~Ny`?socQxl3$(k&0=2o9-hU%(xKO_~SMe&jiEh(kS?ZRTo39vo^sQ(cy}e_vDt_F2e5;(Xud_WpA3syv_uhoP z`LUKEKGcIFp1x?7!>!M0o@7o*L8?Tb6<1LPV&q4lf*HmgZG`F>v(JIl`CgZ+5YGrr zZ^eq0H-A%9vA5G4>gj-VO&BZZmMNe%X?Q5HJ&3Kl*kkT83blRB8!VVK#UXJ?G15!fi|hNO5`9(O6U}ie%YIKy(v!~YDnYi z;d+FfI_slOYVJ3ruN$Rh+T`I-I(=-3{IRYL z+D$oMp3>Tq|43)c3~K(b_OztCA^oYw_agdQu$m_b?Aa#>?6wqZIYfXQnlGMP`F#t& z!uI|3JMNC{aBSf1te<_Z^;bIaJ7KgnK8{JwL)hUB8(FKFYy3yP(5V%Fin}-#tcvD| z*nf#J1Fsp+L#g-?8H=P`fWeCBlpWf%QQQrR>2$eBLAdT%Y|&$6DVp)fHb|AG$KjQE z$Sc;N`8dHVI98UJeE7vTM=}6 zEZ$O`-a%K1d2yTA@GVY}<(-OCWHuRqfPZKH{1OvJx=7f`)goKKOCxgejWV>TcGKw+ zuN_aT@!Y->rD)Sdi{oXr5~L8Pc!vNsRHLy`H4PL~QOVkMr@ZnM*7kf!g(q;!QO>dF z@wPsts}|86YQZwEot)uq!=vK2C16cjk=H(zfsd92W@W>&e=_QP23-Y4VuwT6!+&I* zh%Asz)q0y^_$$eY4(n}Cz9ciW>YdyV+PN+Vlg=b!j-k&Cxi=cF9qr&2_4CP_b8Hs2 z`u8RlQnkpi>L(rdVbHB61t=gLrIzOD9Bf(=O>g+I3PZ*YF(#s;yGm-jFM(_C6^vfqzP#pA^+MnC12s)bsni$Om-u2GtPdb;Xif;N3FH z00o{jP%LXLC?ih>>+D*jW@|L$n?)D~^0o$jz>4Bq)EjM#nk};o?AyXOT5WA@z0ap5 z#kQ#3Ftesy2jXMAEaec4qcvT(Fj(UKH(=)m)76x+$QS0GNO_|~__yMU+<)EXj)@^W zF*~OLt945K`EYR)RS6ChI;yuP$K%%VOb0BJYaF70;#ypP;VC40&te(YU;)A;ixyQ9 z^XP(c>4bl*GP2XOt-`$b&7)6=cQdik{vodR@H1pI7Ab5`Bsw1^-xh|DmTPEZaiDad zDPrV@)7ZGVuK(04Z}NIr1)HNWDB93JbI+~x7BZWd#_z2T)+o43gt zG0*5e3mt~?mBqB-Q%dKR$hid6;jXufxW0hVOSkWK(w7j7J2EhNhU&z#C~!Vy-Jsxi z(zS-;9&e<g1fsrgL`lb z!JP!S`EJ$L-QC*l{?S!^s=Lm=Q~maHUSRx-bzTxWUA4&Pxh*TJLV^%w%0U#){&0TQ zWBWU&4uj7IX&=wo_B|-hRa4QOXrQG=bvM4w8JX?ef7;rkL4s?i5_gQLZb^4ZU4>0M)gI8z%lpVh4W}JY zpEVWvS0R4)y$QLf$(S}$CuFuj&1(ETZ&&GPi*r88v(%u{fHM6IFMQuin$t<$w%$;; z!`bxtfWPCRROG!d`x(O|z6O2Cj;CRt%(zX=tdbKs-w$YL-|J^!@nG8$km!UDn&*tS z7Q-rLU{rQFg4UL+6R@j?ap+CEB0v9b;Zw;v7=&=Hu;U*t@&B*~$z`7ks1YCbI*ILw z7b|N5=BfxTAvFbWJX$4ra^>f(YdaGq;CqeNciB!=HfI?syVcnfDXaK;54$4tkfp+i z8%$}-`9h=S(FwEMB*o`H7WM$yp+jPYnOX|LZY7#pUrk8&p z0|<=VdbisTId{m4Jf9DAUPYAAbvlLEFIp#>qvGM;CP1Con{4GNRblm(fo*D=JcU-f z>o-(kNa@!5XzduE^^Ax1ho-|`+>PB=6_YFC7~OXf2Ht5)5It!zY@`G2d-0p~5&H+6 zKW`)~hW1*cJW#>(-%W8;e=lK~6V+5UQV%0gZApm{U*3ub-ehh%h_{Ni+zt#4Z8B(5 z_-Mt+_1w2xgl!kQ7&H5HE(xA;Nol ztN^{H7TKllqzUy>=T-yYWx;}U1WC}?V%GAM***sYfjA<>H*5u=l*bTQUdhk5?5x+X z`1xT<1NM{@Cm$_DR^N#-1=aEF7MjR>K^T8AqE)0JJsbTsmsi(AP@y7ATQn_-DyhEy zVW)4X=Lmr*6k%ir?&1xa7@WCi^X*h-Ih{-e^yc^d zj%&w=duwUPe`g^X&CYGMMDZ=VjLKu)DbE4!WS<8}^?P{cyR*{<{}R%QF?reIOBepZ z{HDc|7_)7^gcrSm^zgdF+8v}_7{ieYa;bIxseF>(rs-cziha9#|u%k!jno<`$qq5qyPm^hz$FBu?yG5C$pH3 z;nY@>Ox$5bECd!mHMk5CV`_eVp-Zo~Z-&zt+?7t!w0)$T^8IlYh8LjQA{3}NkGJn~ zB|O2hKwC#P+B`!zViglclQEP8BFFCNLYAWPqnKlsYvCuM4fXU0ik}&VDN?2$TUE~? zqmb^Ii(#YLy_2!buq=j+up)7H`JDlc5eJpx0vGwE1%{p`X&-62i$G5T+|4NL7%NnG0zdl;Oa%5eGr~s;$x1p$KU^AfZf66v#)yUn?CKYB z#RT7T$R<_zvnJvpnxnI!MB$`<$2+MmJuHcp>e8M3#hi?yjH#J&cK}BRnOEptoj~&; zuIGPg+C}cH{qg5IXVz>8f3}Lfm7i` zJkhx01;1Yg4jz40H=->qW8TGJ;)}St5YudK!Z73v=Dvab>({$`1^liYyLg=@we&kX zox4k@o7et6!g31D{3-B2c-Kfa4e;%K&#G{e9(rxEooUh=z7;c_iX;bO>%Xy*aN#bp z+G6zua$SgOB4>Z-r#loCH|GWG2~H(#^9No1id|LLO|m5|$p}i=8~(kev0?t9Ek}?} zX4@fMaI2aN0tZ!uxPfx^k?3+fTPZPD>`YAv)V3QcCKc3Pq92>h@!k!O>Dd)Bo;sUP z?b@FFqY^d5_SfGP?s|(>8ca+qDJCW^BW9_2xX}xc8bCaFy9vSxPzi13ozP5EQDq6N zEX1=ob&c4GwK$EQxUH=oqP>oTQu}hT&HQ#rC?yYKiZ18#-pQ%yS|X8)GQC8=!kOv3 zg?qkHjKKVh0~t+∓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 C<2Y^r delta 17928 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%0Aqh( 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 DCxx$L diff --git a/homedefense.py b/homedefense.py new file mode 100644 index 0000000..94c3fae --- /dev/null +++ b/homedefense.py @@ -0,0 +1,449 @@ +"""fast home-siege detection and builder counter-battery.""" + +from fcode import Direction, EntityType, Environment, Position + +import turretplan + + +# home turrets this close trigger the interrupt. +HOME_TURRET_MANHATTAN = 5 +# only builders already near home may respond. +HOME_RESPONDER_MANHATTAN = 10 +# fresh sightings keep the interrupt alive this long locally. +HOME_SIGHTING_FRESH_ROUNDS = 8 +# large home groups are thinned by a deterministic parity filter. +HOME_PARITY_SPAWN_THRESHOLD = 2 +# reactive builds preserve one builder's titanium. +HOME_BUILDER_RESERVE = 15 +# no more than this many live counter-turrets are wanted. +HOME_COUNTER_TURRET_CAP = 2 +# emergency ammo supports twenty gunner shots. +HOME_AMMO_BUFFER = 40 + + +# cardinal facings in store-compatible order. +FACINGS = ( + Direction.NORTH, + Direction.EAST, + Direction.SOUTH, + Direction.WEST, +) + + +def _manhattan(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + +def _core_tiles(corner, width, height): + tiles = [] + for dx in (0, 1): + for dy in (0, 1): + x, y = corner.x + dx, corner.y + dy + if 0 <= x < width and 0 <= y < height: + tiles.append(Position(x, y)) + return tiles if tiles else [corner] + + +def _near_core(pos, core_tiles): + return min(_manhattan(pos, tile) for tile in core_tiles) + + +def _direction_to(origin, target): + if origin.x == target.x: + if target.y > origin.y: + return Direction.SOUTH + if target.y < origin.y: + return Direction.NORTH + if origin.y == target.y: + if target.x > origin.x: + return Direction.EAST + if target.x < origin.x: + return Direction.WEST + return None + + +class HomeDefense: + def __init__(self): + self.current_targets = [] + self.fresh_until = {} + self.reactive_turrets = {} + + def observe_from_core(self, ct): + """Publish one nearest visible turret and return the live emergency.""" + width = ct.get_map_width() + height = ct.get_map_height() + corner = ct.get_position() + core_tiles = _core_tiles(corner, width, height) + visible = [] + for building_id in ct.get_nearby_buildings(): + if ct.get_team(building_id) == ct.get_team(): + continue + entity_type = ct.get_entity_type(building_id) + type_code = turretplan.ENTITY_TO_INFRA.get(entity_type) + if type_code not in turretplan.TURRET_TYPES: + continue + pos = ct.get_position(building_id) + visible.append((pos, type_code)) + + visible.sort(key=lambda item: ( + _near_core(item[0], core_tiles), item[0].x, item[0].y, + item[1], + )) + wrote_slot = None + if visible: + pos, type_code = visible[0] + wrote_slot = turretplan.publish_enemy_infra_sighting( + ct, width, height, pos, type_code + ) + + # core vision owns liveness for every home-range record. + if wrote_slot is None: + live_positions = {(pos.x, pos.y) for pos, _ in visible} + records = turretplan.read_enemy_infra_records(ct, width, height) + for record in records: + if record is None or record[2] not in turretplan.TURRET_TYPES: + continue + pos = Position(record[0], record[1]) + if (_near_core(pos, core_tiles) > HOME_TURRET_MANHATTAN + or not ct.is_in_vision(pos) + or (pos.x, pos.y) in live_positions): + continue + turretplan.clear_enemy_infra_sighting( + ct, width, height, pos + ) + break + + return any(_near_core(pos, core_tiles) <= HOME_TURRET_MANHATTAN + for pos, _ in visible) + + def home_emergency(self, ct, planner, core_tiles): + """Return whether shared sightings and local core vision demand action.""" + self.current_targets = [] + if planner is None or not core_tiles: + return False + now = ct.get_current_round() + core_damaged = None + shared = {(record[0], record[1], record[2]) + for record in planner.shared_infra_records()} + for (x, y), (type_code, freshness) in planner.infra_memory.items(): + if type_code not in turretplan.TURRET_TYPES: + continue + pos = Position(x, y) + if _near_core(pos, core_tiles) > HOME_TURRET_MANHATTAN: + continue + if core_damaged is None: + core_damaged = self._core_damaged(ct, core_tiles) + age = turretplan.infra_sighting_age_rounds(now, freshness) + present = ((x, y, type_code) in shared or ct.is_in_vision(pos)) + if age == 0 and present: + self.fresh_until[(x, y)] = now + HOME_SIGHTING_FRESH_ROUNDS + fresh = now <= self.fresh_until.get((x, y), -1) + if (bool(core_damaged) and present) or fresh: + self.current_targets.append((pos, type_code)) + + known = {(pos.x, pos.y) for pos, _ in self.current_targets} + for key in list(self.fresh_until): + if key not in planner.infra_memory or ( + key not in known and now > self.fresh_until[key]): + del self.fresh_until[key] + self.current_targets.sort(key=lambda item: ( + _near_core(item[0], core_tiles), item[0].x, item[0].y, + item[1], + )) + return bool(self.current_targets) + + def should_respond(self, ct, my_num, core_tiles): + if not self.current_targets or my_num < 1: + return False + if _near_core(ct.get_position(), core_tiles) > HOME_RESPONDER_MANHATTAN: + return False + spawn_count = ct.read_store(0) + if spawn_count <= HOME_PARITY_SPAWN_THRESHOLD: + return True + target = self.current_targets[0][0] + return (my_num & 1) == ((target.x + target.y) & 1) + + def respond(self, ct, planner, my_num, core_tiles, full_map, move_to): + """Run one elected builder's emergency turn.""" + if not self.home_emergency(ct, planner, core_tiles): + return False, None + if not self.should_respond(ct, my_num, core_tiles): + return False, None + + self._prune_reactive_memory(ct) + counter_count = self._counter_turret_count(ct, planner) + wanted = min(HOME_COUNTER_TURRET_CAP, len(self.current_targets)) + if counter_count < wanted: + built = self._try_build(ct, planner, core_tiles, full_map) + if built is not None: + return True, built + + self._heal_core(ct, core_tiles) + target = self._staging_tile(ct, core_tiles, full_map) + if target is None: + target = min(core_tiles, key=lambda tile: ( + _manhattan(ct.get_position(), tile), tile.x, tile.y + )) + move_to(ct, target) + return True, None + + def _core_damaged(self, ct, core_tiles): + checked = set() + for tile in core_tiles: + if not ct.is_in_vision(tile): + continue + building_id = ct.get_tile_building_id(tile) + if building_id is None or building_id in checked: + continue + checked.add(building_id) + if (ct.get_team(building_id) == ct.get_team() + and ct.get_entity_type(building_id) == EntityType.CORE + and ct.get_hp(building_id) < ct.get_max_hp(building_id)): + return True + return False + + def _try_build(self, ct, planner, core_tiles, full_map): + reserve = max(HOME_BUILDER_RESERVE, ct.get_builder_bot_cost()) + resources = ct.get_global_resources() + gunner_cost = ct.get_gunner_cost() + if resources >= gunner_cost + reserve: + candidates = self._gunner_candidates( + ct, core_tiles, full_map, local_only=True + ) + for _, pos, facing, target in candidates: + if (not ct.can_build_gunner(pos, facing) + and not self._clear_allied_build_tile(ct, pos)): + continue + if not ct.can_build_gunner(pos, facing): + continue + ct.build_gunner(pos, facing) + self._record_built( + ct, planner, pos, turretplan.INFRA_GUNNER, facing + ) + return pos + + sentinel_cost = ct.get_sentinel_cost() + if resources >= sentinel_cost + reserve: + candidates = self._sentinel_candidates( + ct, core_tiles, full_map, local_only=True + ) + for _, pos, facing, target in candidates: + if (not ct.can_build_sentinel(pos, facing) + and not self._clear_allied_build_tile(ct, pos)): + continue + if not ct.can_build_sentinel(pos, facing): + continue + ct.build_sentinel(pos, facing) + self._record_built( + ct, planner, pos, turretplan.INFRA_SENTINEL, facing + ) + return pos + return None + + def _record_built(self, ct, planner, pos, type_code, facing): + self.reactive_turrets[(pos.x, pos.y)] = (type_code, facing) + proposal = turretplan.Proposal( + pos.x, pos.y, type_code, facing, 0, False, False, False, 0 + ) + planner.record_built(ct, proposal) + + def _local_tiles(self, ct): + return sorted(ct.get_nearby_tiles(2), key=lambda pos: (pos.x, pos.y)) + + def _gunner_candidates(self, ct, core_tiles, full_map, local_only): + if local_only: + tiles = self._local_tiles(ct) + else: + tiles = self._global_candidate_tiles(ct, full_map) + candidates = [] + for target_index, (target, target_type) in enumerate(self.current_targets): + for pos in tiles: + facing = _direction_to(pos, target) + distance_squared = pos.distance_squared(target) + if (facing is None or distance_squared > 13 + or not self._ray_clear(ct, full_map, pos, target)): + continue + exposed = self._in_inferred_home_ray( + pos, target, target_type, core_tiles + ) + move_distance = _manhattan(ct.get_position(), pos) + score = (int(exposed), target_index, move_distance, + -distance_squared, pos.x, pos.y) + candidates.append((score, pos, facing, target)) + candidates.sort(key=lambda item: item[0]) + return candidates + + def _sentinel_candidates(self, ct, core_tiles, full_map, local_only): + if local_only: + tiles = self._local_tiles(ct) + else: + tiles = self._global_candidate_tiles(ct, full_map) + candidates = [] + for target_index, (target, target_type) in enumerate(self.current_targets): + for pos in tiles: + for facing in FACINGS: + if not turretplan.in_turret_envelope( + (pos.x, pos.y), (target.x, target.y), + turretplan.INFRA_SENTINEL, facing): + continue + exposed = self._in_inferred_home_ray( + pos, target, target_type, core_tiles + ) + move_distance = _manhattan(ct.get_position(), pos) + score = (int(exposed), target_index, move_distance, + -pos.distance_squared(target), pos.x, pos.y, + FACINGS.index(facing)) + candidates.append((score, pos, facing, target)) + candidates.sort(key=lambda item: item[0]) + return candidates + + def _global_candidate_tiles(self, ct, full_map): + width = ct.get_map_width() + height = ct.get_map_height() + tiles = set() + for target, _ in self.current_targets: + for dx in range(-5, 6): + for dy in range(-5, 6): + x, y = target.x + dx, target.y + dy + if not (0 <= x < width and 0 <= y < height): + continue + if dx * dx + dy * dy > 32: + continue + if full_map is not None and full_map[x][y] in (2, 3): + continue + tiles.add((x, y)) + return [Position(x, y) for x, y in sorted(tiles)] + + def _staging_tile(self, ct, core_tiles, full_map): + gunners = self._gunner_candidates( + ct, core_tiles, full_map, local_only=False + ) + if gunners: + return gunners[0][1] + sentinels = self._sentinel_candidates( + ct, core_tiles, full_map, local_only=False + ) + return sentinels[0][1] if sentinels else None + + def _ray_clear(self, ct, full_map, origin, target): + facing = _direction_to(origin, target) + if facing is None: + return False + dx = 0 if origin.x == target.x else (1 if target.x > origin.x else -1) + dy = 0 if origin.y == target.y else (1 if target.y > origin.y else -1) + x, y = origin.x + dx, origin.y + dy + width = ct.get_map_width() + height = ct.get_map_height() + while (x, y) != (target.x, target.y): + if not (0 <= x < width and 0 <= y < height): + return False + pos = Position(x, y) + if ct.is_in_vision(pos): + if (ct.get_tile_env(pos) == Environment.WALL + or ct.get_tile_building_id(pos) is not None): + return False + elif full_map is None or full_map[x][y] in (-1, 2, 3): + return False + x += dx + y += dy + return True + + def _in_inferred_home_ray(self, pos, enemy, enemy_type, core_tiles): + core = min(core_tiles, key=lambda tile: ( + _manhattan(enemy, tile), tile.x, tile.y + )) + facing = _direction_to(enemy, core) + if facing is None: + dx = core.x - enemy.x + dy = core.y - enemy.y + if abs(dx) >= abs(dy): + facing = Direction.EAST if dx > 0 else Direction.WEST + else: + facing = Direction.SOUTH if dy > 0 else Direction.NORTH + index = FACINGS.index(facing) + forward_x, forward_y = turretplan.FACING_DELTAS[index] + dx = pos.x - enemy.x + dy = pos.y - enemy.y + forward = dx * forward_x + dy * forward_y + lateral = abs(dx * forward_y - dy * forward_x) + range_squared = 13 if enemy_type == turretplan.INFRA_GUNNER else 32 + return (forward > 0 and lateral <= 1 + and dx * dx + dy * dy <= range_squared) + + def _counter_turret_count(self, ct, planner): + turrets = dict(planner.turret_memory) + turrets.update({key: value[0] + for key, value in self.reactive_turrets.items()}) + for building_id in ct.get_nearby_buildings(): + if ct.get_team(building_id) != ct.get_team(): + continue + type_code = turretplan.ENTITY_TO_INFRA.get( + ct.get_entity_type(building_id) + ) + if type_code in turretplan.TURRET_TYPES: + pos = ct.get_position(building_id) + turrets[(pos.x, pos.y)] = type_code + + count = 0 + for (x, y), type_code in turrets.items(): + covers = False + for target, _ in self.current_targets: + for facing in FACINGS: + if turretplan.in_turret_envelope( + (x, y), (target.x, target.y), type_code, facing): + covers = True + break + if covers: + break + if covers: + count += 1 + return min(HOME_COUNTER_TURRET_CAP, count) + + def _prune_reactive_memory(self, ct): + for key, (type_code, facing) in list(self.reactive_turrets.items()): + pos = Position(*key) + if not ct.is_in_vision(pos): + continue + building_id = ct.get_tile_building_id(pos) + actual = None + if building_id is not None and ct.get_team(building_id) == ct.get_team(): + actual = turretplan.ENTITY_TO_INFRA.get( + ct.get_entity_type(building_id) + ) + if actual != type_code: + del self.reactive_turrets[key] + + def _clear_allied_build_tile(self, ct, pos): + if not ct.is_in_vision(pos): + return False + building_id = ct.get_tile_building_id(pos) + if building_id is None or ct.get_team(building_id) != ct.get_team(): + return False + protected = ( + EntityType.CORE, + EntityType.HARVESTER, + EntityType.GUNNER, + EntityType.SENTINEL, + ) + if ct.get_entity_type(building_id) in protected or not ct.can_destroy(pos): + return False + ct.destroy(pos) + return True + + def _heal_core(self, ct, core_tiles): + checked = set() + for tile in core_tiles: + if (ct.get_position().distance_squared(tile) > 2 + or not ct.is_in_vision(tile)): + continue + building_id = ct.get_tile_building_id(tile) + if building_id is None or building_id in checked: + continue + checked.add(building_id) + if (ct.get_team(building_id) == ct.get_team() + and ct.get_entity_type(building_id) == EntityType.CORE + and ct.get_hp(building_id) < ct.get_max_hp(building_id) + and ct.can_heal(tile)): + ct.heal(tile) + return True + return False diff --git a/main.py b/main.py index cdce961..3b7510a 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ from coreHelper import findEnemyCore, findTeamCore, findThreats, spawnBots from defend import Defender +import homedefense import mapanalysis import turretplan @@ -63,6 +64,7 @@ def __init__(self): self.mapH = None self.stuckTurns = 0 self.turretPlanner = None + self.homeDefense = homedefense.HomeDefense() self.randDir = Direction.NORTH @@ -126,6 +128,8 @@ def run(self, ct: Controller) -> None: self.gunner(ct) def core(self, ct: Controller) -> None: + homeEmergency = self.homeDefense.observe_from_core(ct) + # spawn first so the ammo topup doesnt eat the builder money if spawnBots(self.numSpawned, ct): corePos = ct.get_position() @@ -155,13 +159,15 @@ def core(self, ct: Controller) -> None: if spawned: break + ammoBuffer = (homedefense.HOME_AMMO_BUFFER if homeEmergency + else GLOBAL_AMMO_BUFFER) currentAmmo = ct.get_global_ammo() - if currentAmmo < GLOBAL_AMMO_BUFFER: + if currentAmmo < ammoBuffer: # keep enough for the next builder spareTitanium = ( ct.get_global_resources() - ct.get_builder_bot_cost() ) - amount = min(GLOBAL_AMMO_BUFFER - currentAmmo, spareTitanium) + amount = min(ammoBuffer - currentAmmo, spareTitanium) if amount > 0 and ct.can_convert_ammo(amount): ct.convert_ammo(amount) @@ -281,31 +287,44 @@ def builderBot(self, ct: Controller) -> None: if self.turretPlanner is not None: self.turretPlanner.sync_and_observe(ct) - self.runRoleBehavior(ct, currentRound) - - # urgent turret proposal only if we didnt already move this turn - if (self.turretPlanner is not None - and self.turretPlanner.should_pursue_urgent(ct, self.myNum)): - proposal = self.turretPlanner.current_proposal - self.moveTo(ct, Position(proposal.x, proposal.y)) - - if self.turretPlanner is not None: - built = self.turretPlanner.try_passive_build(ct) - if built is not None: - built_pos = Position(built.x, built.y) - old_type = self.fullMap[built.x][built.y] - self.fullMap[built.x][built.y] = 3 - self.seenTiles[built.x][built.y] = True - self.newTiles.append([built_pos, 3]) - if old_type not in (2, 3): - mapanalysis.note_structural_tile( - built.x, built.y, True, self.mapW, self.mapH - ) + responding, reactiveBuilt = self.homeDefense.respond( + ct, self.turretPlanner, self.myNum, + self.teamCoreTiles or ([self.teamCore] if self.teamCore else []), + self.fullMap, self.moveTo, + ) + if reactiveBuilt is not None: + self.noteBuiltTile(reactiveBuilt) + + if not responding: + self.runRoleBehavior(ct, currentRound) + + # urgent turret proposal only if we didnt already move this turn + if (self.turretPlanner is not None + and self.turretPlanner.should_pursue_urgent( + ct, self.myNum + )): + proposal = self.turretPlanner.current_proposal + self.moveTo(ct, Position(proposal.x, proposal.y)) + + if self.turretPlanner is not None: + built = self.turretPlanner.try_passive_build(ct) + if built is not None: + self.noteBuiltTile(Position(built.x, built.y)) self.advanceMapAnalysis(ct) if self.turretPlanner is not None: self.turretPlanner.advance(ct, self.fullMap, self.myNum) self.turretPlanner.draw_debug(ct) + def noteBuiltTile(self, builtPos: Position): + oldType = self.fullMap[builtPos.x][builtPos.y] + self.fullMap[builtPos.x][builtPos.y] = 3 + self.seenTiles[builtPos.x][builtPos.y] = True + self.newTiles.append([builtPos, 3]) + if oldType not in (2, 3): + mapanalysis.note_structural_tile( + builtPos.x, builtPos.y, True, self.mapW, self.mapH + ) + def setUp(self, ct: Controller, myLoc: Position): if self.teamCore is None: self.teamCore = findTeamCore(ct) diff --git a/turretplan.py b/turretplan.py index dd11bb4..76c84ff 100644 --- a/turretplan.py +++ b/turretplan.py @@ -150,6 +150,86 @@ def _replace_record(word, record_bits, index, value): return (word & ~mask) | ((value << (record_bits * index)) & mask) +def read_enemy_infra_records(ct, width, height): + """Return the eight decoded enemy-infrastructure store records.""" + records = [] + for slot_offset in range(INFRA_SLOT_COUNT): + word = ct.read_store(INFRA_SLOT_FIRST + slot_offset) + for half in range(2): + records.append( + _unpack_infra(_record_at(word, 16, half), width, height) + ) + return records + + +def publish_enemy_infra_sighting(ct, width, height, pos, type_code): + """Publish one sighting by changing at most one word in slots 8..11.""" + if type_code not in TARGET_VALUE: + return None + now_epoch = _epoch(ct.get_current_round()) + words = [ct.read_store(INFRA_SLOT_FIRST + i) + for i in range(INFRA_SLOT_COUNT)] + records = [] + for word in words: + for half in range(2): + records.append( + _unpack_infra(_record_at(word, 16, half), width, height) + ) + + matching = [i for i, record in enumerate(records) + if record is not None and record[:2] == (pos.x, pos.y)] + if matching: + index = matching[0] + else: + empty = [i for i, record in enumerate(records) if record is None] + if empty: + start = (pos.x * 31 + pos.y * 7 + type_code) & 7 + index = min(empty, key=lambda i: (i - start) & 7) + else: + index = max( + range(8), + key=lambda i: (_age(now_epoch, records[i][3]), + -TARGET_VALUE[records[i][2]], -i), + ) + + slot_offset, half = divmod(index, 2) + packed = _pack_infra(pos.x, pos.y, type_code, now_epoch) + word = _replace_record(words[slot_offset], 16, half, packed) + if word != words[slot_offset]: + slot = INFRA_SLOT_FIRST + slot_offset + ct.write_store(slot, word & 0xFFFFFFFF) + return slot + return None + + +def clear_enemy_infra_sighting(ct, width, height, pos): + """Clear one matching sighting by changing at most one word.""" + for slot_offset in range(INFRA_SLOT_COUNT): + slot = INFRA_SLOT_FIRST + slot_offset + word = ct.read_store(slot) + for half in range(2): + record = _unpack_infra( + _record_at(word, 16, half), width, height + ) + if record is None or record[:2] != (pos.x, pos.y): + continue + ct.write_store(slot, _replace_record(word, 16, half, 0)) + return slot + return None + + +def infra_sighting_age_rounds(round_number, freshness): + """Return the lower-bound age represented by an infra freshness epoch.""" + return _age(_epoch(round_number), freshness) * FRESHNESS_ROUNDS + + +def in_turret_envelope(origin, target, type_code, facing): + """Return whether a turret facing covers a target under dev23 geometry.""" + if facing not in FACINGS: + return False + return _in_envelope(origin, target, type_code, FACINGS.index(facing)) + + def _pack_proposal(proposal): if proposal is None: return 0 @@ -271,6 +351,10 @@ def __init__(self, width, height, team_core): self._top = [] self.score_cache = {} + def shared_infra_records(self): + return tuple(record for record in self._infra_records + if record is not None) + # ------------------------------------------------------------------ store def sync_and_observe(self, ct): From ffb3e39b13e4b840b584962e24d77684ebfcc513 Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 20:06:51 +0530 Subject: [PATCH 05/29] standing home guard on big maps --- bot.zip | Bin 32036 -> 32886 bytes homedefense.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 10 +++++++ 3 files changed, 80 insertions(+) diff --git a/bot.zip b/bot.zip index ad21405961a8a97dea885d89a4afd9fe2b602b69..d372c5659a926c33e8266ba592ed8081881162b2 100644 GIT binary patch delta 14878 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 abs(dy): + face = Direction.EAST if dx > 0 else Direction.WEST + else: + face = Direction.SOUTH if dy > 0 else Direction.NORTH + fdx, fdy = face.delta() + + # spot: two out from the core footprint toward the approach, slight + # side offsets as fallbacks + cands = [] + for c in core_tiles: + p2 = Position(c.x + 2 * fdx, c.y + 2 * fdy) + for q in (p2, Position(p2.x + fdy, p2.y + fdx), + Position(p2.x - fdy, p2.y - fdx)): + if 0 <= q.x < w and 0 <= q.y < h and q not in cands: + cands.append(q) + my = ct.get_position() + for q in cands: + if full_map[q.x][q.y] != 0: + continue # only known-empty ground + if my.distance_squared(q) <= 2: + if ct.can_build_gunner(q, face): + ct.build_gunner(q, face) + planner.pending_turrets[(q.x, q.y)] = turretplan.INFRA_GUNNER + return True, q + continue + if _manhattan(my, q) <= STANDING_GUARD_WALK_MANHATTAN: + move_to(ct, q) + return True, None + return False, None diff --git a/main.py b/main.py index 3b7510a..21fac31 100644 --- a/main.py +++ b/main.py @@ -393,6 +393,16 @@ def runRoleBehavior(self, ct: Controller, currentRound: int): return if self.defenseMode: self.defender.defendInfrastructure(ct, self.teamCore, self.pf) + # big-map standing guard comes before idle heal-parking + guardEngaged, guardBuilt = self.homeDefense.maintain_standing_guard( + ct, self.turretPlanner, + self.teamCoreTiles or ([self.teamCore] if self.teamCore else []), + self.enemyCore, self.fullMap, self.moveTo, + ) + if guardBuilt is not None: + self.noteBuiltTile(guardBuilt) + if guardEngaged: + return healPos = self.defender.picHealingLoc( ct, self.teamCore, self.alwaysDefense ) From 0eadf34eee23aa3df08833d324f5bb6b3c793128 Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 20:14:11 +0530 Subject: [PATCH 06/29] cap defense launchers at 2 --- bot.zip | Bin 32886 -> 32992 bytes defend.py | 9 ++++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bot.zip b/bot.zip index d372c5659a926c33e8266ba592ed8081881162b2..0bdb841342903a26464acb7f472ea20fb6d76f0d 100644 GIT binary patch delta 1387 zcmV-x1(f>sfCAuw0ZBj`B!(L%@ulH8YQ-&g&P;XLh91I}T zCvd4GD#kHS2cS7yU#gT3AgQ`v@1`n5qoMW}TnCBDgceGA{X#;_n=y*a1X&uPXRcBZ zAf&Yg5;rZdQ}6fx{L*PxdiGQk)8CtThnh6zoLP;P*48b&{Znv>kRVr$DX zNRX=NMe`E~e^~-cwS_g4JJ@nqah?E+#bcQlq627gGvgz_!lHjd&c{Hq(`?Ty@^Gis@{dVVcyW|AiU{xNwif6Lp)WR67A_ajZ z^D3yne|&fIe)8en-Jp6Et}pK1Pwr=v`s)4UX7=`GGVO?to)RsNv=C$%`M0Ta-C*i+ zrjUWft;gKMM|-+DhvS2+-5A80Qs}+qxh1r}Ud3`GqN}}KH2FJ2?r$HrOiI^*^U%H~jyf>ilonGevlfe^U-#cw~tu=H{|-4v+=6Y?qlCAM*+i zO2xKzeKdROo0`dnz3L+TExu?budS($3_M+t~+$1<|HOdng*G zJ+ByFl3*($y~MHmHB=UtTr8FvSC8PgKD>DYzc^}qm`okBcAZS!P$=_d$-rUAJuaCY ze@U$Me6sVbVy(Mdo&zjahrr$B0JAv{Q|-ybt)-B&CtG9{74*Mit@aX9>ie*}? zXmgCC;5yp6GrsN-@<4q5U3diXXN5Kze-6JhxDtfe5@tv&%}*v;zYh`TxN$V2zhkfdck`0hInTT0-c6>zO ziP@ZlAs1YQp_j8{?w1?*;Cm1k3O6z*12U0HrovR_TyZHp--z&e(@~xm zo>t7@GnX0RgbQs;e=wTOHsNf|lbDP9-18d>rSVc6J@mLw27}s)Pawp4Ws#!9xqe;I zuiGVyVM}~1*W|ukbCxl>d?ExwDq9#pwg_qv^ITC8u`vH{O*0mIbrU|hAbpGD={#7; z2R4;nq&kgb8{QV1X+k45rfU%`SoXBeHsBEz;eyem>667De~`9GY8*ZzJYbRMP!r(; z&oK#4`%nV?%4AUSgzcae@>X!INZXmQ1GpAecUkI(B?i2JLT$W?;1Zy_t>+)elHBI5 zL(@=19UXRVfND4+H)Tm2NDc=Vy@?qUimOeiHYqFXepIYXVIde_{xca~k8Z2g?_!9( zv{EVcc15OGe}?A+YaQZ~F`!gI$P1!FNN57o5N`KuDfsl+EK3DCwhfHi zvK=>sb?)^oL0j(ez6g>9orwofBY$8bA9yCk3Q`>W zdpRCE!mEQ8&@=@EThA=2tUOgyN(&UQ|6Q;s3?ksdf2Fy46^CfZt_EIcO>_e?=T+c3 zd_Vax`uP5~Q^oN0r6y&FxYZzrR%%RhV!Y;2)H>`?ST$In_~NbyBN!$g-q zb(Bhb(6}I{`?;z)lr+V_f!5(|scn6k^_a(3Thn0mZwj1exwQ$7^WLR@aApCOs@c-6 zz0nyxe@hRl`Zmqh>~{&eey=nQ9$iDr2POdicw(S5oudQX_((T-F#$t_$xO;b=g}u8 z7duJvf3y8U+#tSGF&j@>`pcFldvDy`WyeUmwHQVky}OxyEP%n=oT=}HW2Wcr`gj^X z@>nfk9DPM9iwnl@7YbHS$X`Br{hIt~%l3Yzf3?k^b*6Qo!URpTgw2#=awxV0GTJkk z(9@c=o~=ocK+YTjx1)WuXTMNw&P47tVhvkmyVfw<38r0*LX z2OF16%>|vf$0G4fwCded+HhQC^f{B#K|Sv9q+)B^mrqB=8lliB>z}&5sZ4ORY;#6K zfB#%FfYn7kCLZSEuk;Wxf0%c@Zuf`%t3VRE#uO;0^;LlzcM%{Tc20U#6~no%Ld#3N zMh`;8FQ^CwbkDqmfj{*-`ZmOZ^JuJcyH8&}x5c(5DuSg=t1$99h*}69Pjh@8qRu%v z?Yg>faqCwD0-tqW>CH>x^?>}lLAMXTNYMgtO1GAQ()w2W!o=OyC`_`reOG6!uvTNn zTJuE^;*9cx1ljuzg6vi6!P0E7mFa7_``J(T%yx+OYu_O|nSF&fI9-`Nt1toIRf%)le&N)2FiW_0002`nsaLa diff --git a/defend.py b/defend.py index 0df8efc..fc87df5 100644 --- a/defend.py +++ b/defend.py @@ -33,10 +33,17 @@ def defendInfrastructure(self, ct: Controller, teamCore, pf): def placeLauncher(self, ct: Controller, pos: Position): myLoc = ct.get_position() + # launchers still yeet the siege's support builders, but 13 of them + # was pure waste: cap how many we keep alive + team = ct.get_team() + launchers = 0 for i in ct.get_nearby_buildings(): - if ct.get_entity_type(i) == EntityType.LAUNCHER and ct.get_team(i) == ct.get_team(): + if ct.get_team(i) == team and ct.get_entity_type(i) == EntityType.LAUNCHER: + launchers += 1 if ct.get_position(i).distance_squared(pos) <= 2: return # Launcher already built + if launchers >= 2: + return # enough launchers alive, dont spam them topCands = [] otherCands = [] for k in ct.get_nearby_tiles(2): From 5c5d7b016dd8dc8d473ca9b17f9d24c1842e0a7d Mon Sep 17 00:00:00 2001 From: Subodh Date: Sun, 19 Jul 2026 20:33:26 +0530 Subject: [PATCH 07/29] fix counter battery vs flanks, gunner rotation, replacement spawning --- bot.zip | Bin 32992 -> 33512 bytes coreHelper.py | 8 +++++++ homedefense.py | 26 +++++++++++------------ main.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/bot.zip b/bot.zip index 0bdb841342903a26464acb7f472ea20fb6d76f0d..300ce0cbe745dc3e89640dd7d3b81a9e0c2d5f10 100644 GIT binary patch delta 15761 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

w)Cu^za> zr2$NL4J_!5aa`8@wq3*3>-M%0(fPJ|Miq(5&B2wX5@CwVFn#2P+@r=ELeK%fG>Ygm zjtp&idO5ibZDE&tV+p4wPrGAoM#(mx$I;(JDN_oh{`cll@Q$H4!SM)G=vlSN69FKj z5~8GEzB>0^9O5d1r!2(O4yy{3YUgAuA;8LT*^7?$Y1KLn?`Rq-sEZCJ8 zJIuW}`GxVxLKCGaq_~9h@18_SHr!GG_qJJbXa|9v*&KF4W zJl3*Pex+KyDi`Lv>iO3>R9?e#9Xq$41+8Qehk%{ofE_j7jqVCjyk{vT8E1F8T3 delta 15262 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 diff --git a/coreHelper.py b/coreHelper.py index b095a30..08ef183 100644 --- a/coreHelper.py +++ b/coreHelper.py @@ -1,5 +1,10 @@ from fcode import * +# replace the workforce only after the whole team is gutted. +REPLACE_UNIT_THRESHOLD = 6 +# keep this titanium after paying for a replacement builder. +REPLACE_RESERVE = 60 + def spawnBots(numSpawned: int, ct: Controller) -> bool: currentTitanium = ct.get_global_resources() @@ -7,6 +12,9 @@ def spawnBots(numSpawned: int, ct: Controller) -> bool: return True if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: return True + if (ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD + and currentTitanium >= ct.get_builder_bot_cost() + REPLACE_RESERVE): + return True return False diff --git a/homedefense.py b/homedefense.py index d2cda1d..744f689 100644 --- a/homedefense.py +++ b/homedefense.py @@ -377,9 +377,9 @@ def _in_inferred_home_ray(self, pos, enemy, enemy_type, core_tiles): and dx * dx + dy * dy <= range_squared) def _counter_turret_count(self, ct, planner): - turrets = dict(planner.turret_memory) - turrets.update({key: value[0] - for key, value in self.reactive_turrets.items()}) + turrets = {key: (type_code, None) + for key, type_code in planner.turret_memory.items()} + turrets.update(self.reactive_turrets) for building_id in ct.get_nearby_buildings(): if ct.get_team(building_id) != ct.get_team(): continue @@ -388,21 +388,19 @@ def _counter_turret_count(self, ct, planner): ) if type_code in turretplan.TURRET_TYPES: pos = ct.get_position(building_id) - turrets[(pos.x, pos.y)] = type_code + turrets[(pos.x, pos.y)] = ( + type_code, ct.get_direction(building_id) + ) count = 0 - for (x, y), type_code in turrets.items(): - covers = False + for (x, y), (type_code, facing) in turrets.items(): + if facing is None: + continue for target, _ in self.current_targets: - for facing in FACINGS: - if turretplan.in_turret_envelope( - (x, y), (target.x, target.y), type_code, facing): - covers = True - break - if covers: + if turretplan.in_turret_envelope( + (x, y), (target.x, target.y), type_code, facing): + count += 1 break - if covers: - count += 1 return min(HOME_COUNTER_TURRET_CAP, count) def _prune_reactive_memory(self, ct): diff --git a/main.py b/main.py index 21fac31..0946904 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,8 @@ # 20 ammo = 10 gunner shots or 2 sentinel shots. GLOBAL_AMMO_BUFFER = 20 +# rotate only with enough titanium left for follow-up work. +GUNNER_ROTATE_MIN_TITANIUM = 25 openCost = 1 barrierCost = 8 # to be implemented later @@ -173,8 +175,58 @@ def core(self, ct: Controller) -> None: def gunner(self, ct: Controller) -> None: target = ct.get_gunner_target() - if target is not None and ct.can_fire(target): - ct.fire(target) + if target is not None: + builder_id = ct.get_tile_builder_bot_id(target) + building_id = ct.get_tile_building_id(target) + target_id = builder_id if builder_id is not None else building_id + if (target_id is not None + and ct.get_team(target_id) != ct.get_team()): + if ct.can_fire(target): + ct.fire(target) + return + + if (ct.get_global_resources() < GUNNER_ROTATE_MIN_TITANIUM + or ct.get_action_cooldown() != 0): + return + + my_pos = ct.get_position() + current_facing = ct.get_direction() + candidates = [] + for entity_id in ct.get_nearby_entities(13): + entity_type = ct.get_entity_type(entity_id) + if entity_type not in ( + EntityType.GUNNER, EntityType.SENTINEL, + EntityType.BUILDER_BOT): + continue + if ct.get_team(entity_id) == ct.get_team(): + continue + enemy_pos = ct.get_position(entity_id) + dx = enemy_pos.x - my_pos.x + dy = enemy_pos.y - my_pos.y + if dx != 0 and dy != 0: + continue + if dx > 0: + facing = Direction.EAST + elif dx < 0: + facing = Direction.WEST + elif dy > 0: + facing = Direction.SOUTH + elif dy < 0: + facing = Direction.NORTH + else: + continue + if facing == current_facing: + continue + priority = 0 if entity_type in ( + EntityType.GUNNER, EntityType.SENTINEL + ) else 1 + candidates.append((priority, my_pos.distance_squared(enemy_pos), + CARDINALS.index(facing), facing)) + + if candidates: + facing = min(candidates)[3] + if ct.can_rotate(facing): + ct.rotate(facing) def sentinel(self, ct: Controller) -> None: if self.enemyCore is None: From 4772ecf1b40474648dfc5ea613b1be3567e2605e Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 11:39:04 +0530 Subject: [PATCH 08/29] fix emergency build gates: flat reserve, no replacement spawns in the opening --- bot.zip | Bin 33512 -> 33603 bytes coreHelper.py | 5 ++++- homedefense.py | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index 300ce0cbe745dc3e89640dd7d3b81a9e0c2d5f10..c2b618d31a610810f2897d469cbb9cd1f3e86b45 100644 GIT binary patch delta 5593 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 diff --git a/coreHelper.py b/coreHelper.py index 08ef183..30a6dec 100644 --- a/coreHelper.py +++ b/coreHelper.py @@ -2,6 +2,8 @@ # replace the workforce only after the whole team is gutted. REPLACE_UNIT_THRESHOLD = 6 +# dont let the replacement path fire during the normal opening +REPLACE_MIN_ROUND = 30 # keep this titanium after paying for a replacement builder. REPLACE_RESERVE = 60 @@ -12,7 +14,8 @@ def spawnBots(numSpawned: int, ct: Controller) -> bool: return True if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: return True - if (ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD + if (ct.get_current_round() >= REPLACE_MIN_ROUND + and ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD and currentTitanium >= ct.get_builder_bot_cost() + REPLACE_RESERVE): return True return False diff --git a/homedefense.py b/homedefense.py index 744f689..0e34547 100644 --- a/homedefense.py +++ b/homedefense.py @@ -209,7 +209,9 @@ def _core_damaged(self, ct, core_tiles): return False def _try_build(self, ct, planner, core_tiles, full_map): - reserve = max(HOME_BUILDER_RESERVE, ct.get_builder_bot_cost()) + # flat reserve: the counter turret IS the survival spend, dont + # price it against a scaled builder replacement + reserve = HOME_BUILDER_RESERVE resources = ct.get_global_resources() gunner_cost = ct.get_gunner_cost() if resources >= gunner_cost + reserve: From 90fe140ac1a7f1201da2b551509d6e640aae1361 Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 11:51:23 +0530 Subject: [PATCH 09/29] standing guard on every map, up by r8 --- bot.zip | Bin 33603 -> 33619 bytes homedefense.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index c2b618d31a610810f2897d469cbb9cd1f3e86b45..8970b52dc9af18f731a30fab08ee527ac9119414 100644 GIT binary patch delta 4814 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 delta 4798 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 YZN8z-&k_It_fnG^gE9t}fdBvi03?z=B>(^b diff --git a/homedefense.py b/homedefense.py index 0e34547..5e93b94 100644 --- a/homedefense.py +++ b/homedefense.py @@ -6,9 +6,9 @@ # big maps keep one gunner standing near the core before any rush shows up -STANDING_GUARD_MIN_AREA = 400 +STANDING_GUARD_MIN_AREA = 0 # every map: small maps get rushed the fastest STANDING_GUARD_RESERVE = 40 # ti to keep above the gunner cost -STANDING_GUARD_MIN_ROUND = 12 # let the opening economy seed first +STANDING_GUARD_MIN_ROUND = 8 # up before the earliest rush arrives STANDING_GUARD_WALK_MANHATTAN = 8 # defense bots further than this dont bother # home turrets this close trigger the interrupt. From deabb45abc82f86648fe7095edd74ff8e7e66a85 Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 19:24:20 +0530 Subject: [PATCH 10/29] turtle mode: stop attacking core-killer opponents, survive and win on econ --- bot.zip | Bin 33619 -> 33950 bytes homedefense.py | 20 ++++++++++++++++++++ main.py | 21 +++++++++++++++++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/bot.zip b/bot.zip index 8970b52dc9af18f731a30fab08ee527ac9119414..5b92fc8395e9ef9889775d276f6d5149a015e328 100644 GIT binary patch delta 15513 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 Pj>+^PM3DEv|3CkKMA7(? delta 15171 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 diff --git a/homedefense.py b/homedefense.py index 5e93b94..3e8e5d8 100644 --- a/homedefense.py +++ b/homedefense.py @@ -5,6 +5,10 @@ import turretplan +# early enemy turret near our half = hyper aggressive opponent, stop attacking +TURTLE_TRIGGER_MANHATTAN = 14 +TURTLE_TRIGGER_ROUND = 120 + # big maps keep one gunner standing near the core before any rush shows up STANDING_GUARD_MIN_AREA = 0 # every map: small maps get rushed the fastest STANDING_GUARD_RESERVE = 40 # ti to keep above the gunner cost @@ -517,3 +521,19 @@ def maintain_standing_guard(self, ct, planner, core_tiles, enemy_core, move_to(ct, q) return True, None return False, None + + + def should_turtle(self, ct, planner, core_tiles): + """sticky per unit: an early enemy turret near our side means this + opponent kills cores. every ti we spend attacking is stolen from + survival, and a game we survive is a game we win on econ.""" + if planner is None or not core_tiles: + return False + if ct.get_current_round() > TURTLE_TRIGGER_ROUND: + return False + for (x, y), (type_code, _f) in planner.infra_memory.items(): + if type_code not in turretplan.TURRET_TYPES: + continue + if _near_core(Position(x, y), core_tiles) <= TURTLE_TRIGGER_MANHATTAN: + return True + return False diff --git a/main.py b/main.py index 0946904..cd47836 100644 --- a/main.py +++ b/main.py @@ -66,6 +66,7 @@ def __init__(self): self.mapH = None self.stuckTurns = 0 self.turretPlanner = None + self.turtled = False # opponent kills cores, we stop attacking self.homeDefense = homedefense.HomeDefense() self.randDir = Direction.NORTH @@ -339,6 +340,12 @@ def builderBot(self, ct: Controller) -> None: if self.turretPlanner is not None: self.turretPlanner.sync_and_observe(ct) + if (not self.turtled and self.turretPlanner is not None + and self.homeDefense.should_turtle( + ct, self.turretPlanner, + self.teamCoreTiles or ([self.teamCore] if self.teamCore else []))): + self.turtled = True + responding, reactiveBuilt = self.homeDefense.respond( ct, self.turretPlanner, self.myNum, self.teamCoreTiles or ([self.teamCore] if self.teamCore else []), @@ -441,8 +448,13 @@ def runRoleBehavior(self, ct: Controller, currentRound: int): self.defender.defendInfrastructure(ct, self.teamCore, self.pf) if self.attackMode: - self.attack(ct) - return + if self.turtled: + # attacking this opponent just feeds it our workforce + self.attackMode = False + self.defenseMode = True + else: + self.attack(ct) + return if self.defenseMode: self.defender.defendInfrastructure(ct, self.teamCore, self.pf) # big-map standing guard comes before idle heal-parking @@ -491,7 +503,8 @@ def assignRole(self, ct: Controller, currentRound): if currentRound <= 120: # 36% Offense, 64% Eco + 1 Defense Bot if (currentRound % 11 in [3, 5, 7, 10] - and self.currentHarvester is None): + and self.currentHarvester is None + and not self.turtled): self.attackMode = True else: self.defenseMode = False @@ -504,7 +517,7 @@ def assignRole(self, ct: Controller, currentRound): role = random.random() if role > 0.75 or self.alwaysDefense: self.defenseMode = True - elif role > 0.4: + elif role > 0.4 and not self.turtled: self.attackMode = True def attack(self, ct: Controller) -> None: From 4b03eef8bfe7d167643f0e21ce62f92d2e247460 Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 19:38:12 +0530 Subject: [PATCH 11/29] turtled attackers mine instead of parking --- bot.zip | Bin 33950 -> 34021 bytes main.py | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index 5b92fc8395e9ef9889775d276f6d5149a015e328..c28298092ac4118e993dd0e5530b7976278037ed 100644 GIT binary patch delta 10526 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Â$_-u=r@7vqV-#fjem(8bvDl&>A}S&?-mbLfWw$*sNMTUJ!RQDMLsna zvIRf-NlK7Oc8!KVEefh&HL@1t;~0`MAzu?sYlY9<{aJv%qy`OlqYr$Vzs{=qyfy4j zmu|*372k~L{J7RBah2R+%hvgDf3ZmbubOO2$2hdk?7Cr+-K~SV7O})D+vp<#c>K$PO>4Aw*^`^Z@!a6njeIc@j@DcMH2z7B4@ac zGUfAzTJnXgiW+(A2#{frTT;~_C0vM-8i0i+3s!P1xkXmL)TP3^WaR86YcuxwrZlj} z>%y5V^kXu}6uF@e5*q&j5`RCZZggx~;9w324n;u6TU=(fsmH^!d&=5j?TvPgo_=Vh zGNTK15IH$^(UN}?N2Z3i@L?DG%`UcOivdw+s*+Hji<@Tlk`SJYMa#)XFxM+hAftnN ze#N<1w;Vr=2uE2%=ylwf?AQ&eIB1f1`Jn;PS_|iFRq?seU$h`+-hVm_NA$R)e}21c zM~Da!txH2dO>_d$;-<qaLshATMcdwY95l&FQ0Ie1B7-iF4mDukIFVw#HL>Wc0; zMXzIpmJ|G_Y~YsFr+;s@=IuUxt#y~>X9I7D;vdwxEN&2|sr)Fe%QT&DiS0qLz(2`c zML6kXNEq|qzko}exH>(p>em^Y1|&a`L^+cwK7G^|vgZf?=qc9nGHT_6$~<6br$0Ny z3(usApGBp*f)|E2O+7Lrrth3@+A-glgGLqN8tijJthh>OD1RM4P17Q|0_}^ohSI7Q zfg(f=RXBEBaSb|xKir_@04%FkuNwnA<5RAY6I~m$Vw@IX&MxNb!FYd$>DU}&g?^y# z3HYV?a~m$l?8=a;IyV;#und>bH+7ZGQ*{<>=-_m@S*pDu=eC(|bdfO|+Td9`j7zDq zeI(Ew&wGo2et*b&>f<*WlHI|7Z0g+EkF==rE&WNCI_(r}%CS`371QFD4h*Kt)EBrO z3M1zP0bE5B+1}r#2~3ViQ|5&@02Ql~8#A29e-!`5#c?Gv&^UAMAwFg~p~H6eI5TVE zOsh?8U4;u;2oP6it-`}_vzdOh5>|(u&*MuQqa)@QWTG}5A(>q_I-cdTGR5enB?(uQbchR`RpFR=Hn-}bb{lJ*+1qCI$hFh| zE5mk01PjlIRF3v delta 10448 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 Date: Mon, 20 Jul 2026 20:05:49 +0530 Subject: [PATCH 12/29] lower spawn gates, the 350 hoard was starving the workforce mid-war --- bot.zip | Bin 34021 -> 34096 bytes coreHelper.py | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index c28298092ac4118e993dd0e5530b7976278037ed..4e808f5f1666354e29ae659e7d86ef300585050b 100644 GIT binary patch delta 743 zcmVU;i^g532ukUWZ!3aJRF4xL|L|5%MBstPq!Q2uuVGwv}gj!Ss zN}HTX&;=?wS|k6snI!SuY~n#r2ZZ{7SkMBAaKbSq%Dhoou$9Lm^}x31=B1Dv%|e_&7Qt9G`h%KYBnARmHI?g&yhgfUq$T1qq_e7JDd65ni&q(G4qIgCwu1LQ!Cq2^r-NW z9FkB}78@W-NpbJ^0Sn4lfYeW4He1ZpZ&avM=LmTzohYP0e{)ou^$+t3N@TDx4Hs|* zOz}NumuqVcMKaeeNwsyo5Z=S_$)HZeUj|)i6iiKtFECt^xmOrY&IfSV^4OF~ObVn# zlqdNJ$AiC<^qS2EIWf9aoHhx0uTN_ne^qko-BJl}tE?z5HjCR8-{1bXxOS=-zHY0z z|IQ@6`MRu`e^7k7G{~r~FSJt%>uNk#4^ban$Wt>vOc@bK7R4 zf8SomN~ADf{TE(#-`QKeJOi_;F$_`;V4(C|k)Yxde*ypil?Ic zO>dh(5Qgvkim7^mv~{VPRFxGhr7l$>xpqZxFI9!sX0TUTcG0pZ;@@YMj~MKv^09l{ zW#*apof$H%a>%wSMc{d%w1Er9c?%i~!L|rykMOAUL#8y{loU0vf6Sn6KkSvD5M(*< z5_Xj_m`8)U94;j}8X{EW` zH33rToC#0`N_u)G|GZl!(Q3W4(9b?m9}o*lAQ6TeCV7>2DhpOy7O4j|Dz~Y)NKqfu zlFTsr9a@>b<2Wg1e^3_eQGQdVbY+#_+Y6>s;L^N@t(n4{tY{?!Y7ailV55|nIzVSz zX-zfaUP?gWlO395C)9?CL}RHcYG>(sO+3%wPynvs3Ybh!C^PbU<+l~=(P%0yA=VYX zK$*eiYm`ghhl^fPxn%;A0!fZEt_L6ClmG7`ZkO4S6Rj$le^MrqPv-N?v8R#K=2nsu zfD&!V>qFvrWlpnC&2+$SoXJL6ZfUXa;Apho>$bZ44<<_NhM#en#(Ry7#{a^!1usp@ zGi?w}`On{e{E_X+VeUt*YNG5)a$2T7Mk3Rjx-~*8Te@EddIxPM4mWjT>H=NvK@dRL zyu|b{S=_9bS@YX4vfUbWuhG>4?C)wb-vpx bool: currentTitanium = ct.get_global_resources() - if numSpawned < 5 and currentTitanium >= 350: + # old 350 hoard starved the workforce at 4 builders whenever defense + # spending kept ti low; builders are the income multiplier, spawn them + if numSpawned < 5 and currentTitanium >= ct.get_builder_bot_cost() + 120: return True - if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: + if numSpawned > 4 and currentTitanium >= 280 * ct.get_scale_percent() / 100: return True if (ct.get_current_round() >= REPLACE_MIN_ROUND and ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD From b5fe821ff17c94290e128b54c81d4a742609b898 Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 20:13:47 +0530 Subject: [PATCH 13/29] restore lean opening, keep only the mid-war spawn unlock --- bot.zip | Bin 34096 -> 34146 bytes coreHelper.py | 11 +++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bot.zip b/bot.zip index 4e808f5f1666354e29ae659e7d86ef300585050b..2e2c1d92fccaa4bf10e1abdafac4e10d20e2cbdb 100644 GIT binary patch delta 793 zcmV+!1Lpj&iUQ(_0eiTG>P>QXr|g{`)RvDwgduNN?rA z0O{G;nITt&RwWdf$`N>3DQ)2}2o6AFC0K@F7YIwGp9-aEr=(Z`e=98N^&bl*2!gBx zZeU(pi+L}YB;Q9j!z6ti-%Zo$?Id})y&rvn0bCMquA~KlwyF0`N~E)j*##6_BjmN_ za^3)>(j^n1DwK5e!u;)SoKEf^$By(YCe|m!iV8@C<(5famtJMXR*s9*1KVmhtGUQg zZ`6{^Wbz|%GUFf!e{w9qRBS0fD{Deomk(}(c^|m6$B@}R49SXCLZFV|R#`2;Q5cP3J<*m`F7mRa2ZMj2ip_oL$7QP)z5I6VrJ2rYS5YArxu}CSwAR zfux}{2t_S(r-o%V>UknpSn~X2$uwkw)egpgqi3Wa4F%((f7aNC^ELzn#xnivnlbB% z^yyiA1T)MU4o7E5Pwp@hpNWR<(4`GCmOX)TCuE{2<^^o>f-ATHMnc2gRIUdv744#1 z3Vl=_ooYoc!^oj8x4~{X;#YV*hqsr`6!9rziROq{qNx8uq|w$|!c^DziuP;D=r1kK z;}{OxM$N^fe-+Z5O4sEhoW}8+P2RA%_sD}i$Lrc!a_6gVT3&4mx94uZ`rq=}nU6QJ z^ZTz%81xfVaG6gR8X3*dDlN2GOYe{MF9T#AUJ>o}H)~l$&c#GtgorGCfjU;s&TGNpsw=;m=eSG-LoF0H*6=Ly_5&sBaHY@KwU zR>xM4Rc?6N?v*m!awUl%>L%yW-Ry{Ts5I_|*MFb;ZS(K(KD?hK-8a_y9PIeqw(0fn z+v`|KB$liH!prWRz1Pbhv-L3yQVwyU^jz|8>IAU@001fnlMjST0w*$)T!b|NR+Ey1 XI|0&@&V)Jv-IEE0AqKXC000001IB!~ delta 742 zcmVU;i^g532ukUWZ!3aJRF4xL|L|5%MBstPq!Q2uuVGwv}gj!Ss zN}HTX&;=?wS|k6snI!SuY~n#r2ZZ{7SkMBAaKbSq%Dhoou$9Lm^}x31=B1Dv%|e_&7Qt9G`h%KYBnARmHI?g&yhgfUq$T1qq_e7JDd65ni&q(G4qIgCwu1LQ!Cq2^r-NW z9FkB}78@W-NpbJ^0Sn4lfYeW4He1ZpZ&avM=LmTzohYP0e{)ou^$+t3N@TDx4Hs|* zOz}NumuqVcMKaeeNwsyo5Z=S_$)HZeUj|)i6iiKtFECt^xmOrY&IfSV^4OF~ObVn# zlqdNJ$AiC<^qS2EIWf9aoHhx0uTN_ne^qko-BJl}tE?z5HjCR8-{1bXxOS=-zHY0z z|IQ@6`MRu`e^7k7G{~r~FSJt%>uNk#4^ban$Wt>vOc@bK7R4 zf8SomN~ADf{TE(#-`QKeJOi^QG7M4k00necl>h($ diff --git a/coreHelper.py b/coreHelper.py index 869358e..fb208e6 100644 --- a/coreHelper.py +++ b/coreHelper.py @@ -10,11 +10,14 @@ def spawnBots(numSpawned: int, ct: Controller) -> bool: currentTitanium = ct.get_global_resources() - # old 350 hoard starved the workforce at 4 builders whenever defense - # spending kept ti low; builders are the income multiplier, spawn them - if numSpawned < 5 and currentTitanium >= ct.get_builder_bot_cost() + 120: + # 350 hoard keeps the opening lean (low scale, defense fund intact). + # the mid-war clause fixes the other failure: 4 builders starving at + # r130+ because war spending never lets ti back over 350 + if numSpawned < 5 and (currentTitanium >= 350 + or (ct.get_current_round() >= 60 + and currentTitanium >= ct.get_builder_bot_cost() + 60)): return True - if numSpawned > 4 and currentTitanium >= 280 * ct.get_scale_percent() / 100: + if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: return True if (ct.get_current_round() >= REPLACE_MIN_ROUND and ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD From 556338cffe50b4ad6b751ee3ac5e30ee66ad2912 Mon Sep 17 00:00:00 2001 From: Subodh Date: Mon, 20 Jul 2026 20:16:35 +0530 Subject: [PATCH 14/29] back to the it7 spawn config, test changes under pinned maps from now on --- bot.zip | Bin 34146 -> 34021 bytes coreHelper.py | 7 +------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/bot.zip b/bot.zip index 2e2c1d92fccaa4bf10e1abdafac4e10d20e2cbdb..c1ff83e5bbcc54586dbac518d86c7d48aa0482b6 100644 GIT binary patch delta 667 zcmV;M0%ZN-iUQ?{0 zO>dh(5Qgvkim7^mv~{VPRFxGhr7l$>xpqZxFI9!sX0TUTcG0pZ;@@YMj~MKv^09l{ zW#*apof$H%a>%wSMc{d%w1Er9c?%i~!L|rykMOAUL#8y{loU0vf6Sn6KkSvD5M(*< z5_Xj_m`8)U94;j}8X{EW` zH33rToC#0`N_u)G|GZl!(Q3W4(9b?m9}o*lAQ6TeCV7>2DhpOy7O4j|Dz~Y)NKqfu zlFTsr9a@>b<2Wg1e^3_eQGQdVbY+#_+Y6>s;L^N@t(n4{tY{?!Y7ailV55|nIzVSz zX-zfaUP?gWlO395C)9?CL}RHcYG>(sO+3%wPynvs3Ybh!C^PbU<+l~=(P%0yA=VYX zK$*eiYm`ghhl^fPxn%;A0!fZEt_L6ClmG7`ZkO4S6Rj$le^MrqPv-N?v8R#K=2nsu zfD&!V>qFvrWlpnC&2+$SoXJL6ZfUXa;Apho>$bZ44<<_NhM#en#(Ry7#{a^!1usp@ zGi?w}`On{e{E_X+VeUt*YNG5)a$2T7Mk3Rjx-~*8Te@EddIxPM4mWjT>H=NvK@dRL zyu|b{S=_9bS@YX4vfUbWuhG>4?C)wb-vpxeiTG>P>QXr|g{`)RvDwgduNN?rA z0O{G;nITt&RwWdf$`N>3DQ)2}2o6AFC0K@F7YIwGp9-aEr=(Z`e=98N^&bl*2!gBx zZeU(pi+L}YB;Q9j!z6ti-%Zo$?Id})y&rvn0bCMquA~KlwyF0`N~E)j*##6_BjmN_ za^3)>(j^n1DwK5e!u;)SoKEf^$By(YCe|m!iV8@C<(5famtJMXR*s9*1KVmhtGUQg zZ`6{^Wbz|%GUFf!e{w9qRBS0fD{Deomk(}(c^|m6$B@}R49SXCLZFV|R#`2;Q5cP3J<*m`F7mRa2ZMj2ip_oL$7QP)z5I6VrJ2rYS5YArxu}CSwAR zfux}{2t_S(r-o%V>UknpSn~X2$uwkw)egpgqi3Wa4F%((f7aNC^ELzn#xnivnlbB% z^yyiA1T)MU4o7E5Pwp@hpNWR<(4`GCmOX)TCuE{2<^^o>f-ATHMnc2gRIUdv744#1 z3Vl=_ooYoc!^oj8x4~{X;#YV*hqsr`6!9rziROq{qNx8uq|w$|!c^DziuP;D=r1kK z;}{OxM$N^fe-+Z5O4sEhoW}8+P2RA%_sD}i$Lrc!a_6gVT3&4mx94uZ`rq=}nU6QJ z^ZTz%81xfVaG6gR8X3*dDlN2GOYe{MF9T#AUJ>o}H)~l$&c#GtgorGCfjU;s&TGNpsw=;m=eSG-LoF0H*6=Ly_5&sBaHY@KwU zR>xM4Rc?6N?v*m!awUl%>L%yW-Ry{Ts5I_|*MFb;ZS(K(KD?hK-8a_y9PIeqw(0fn z+v`|KB$liH!prWRz1Pbhv#K!+QVwyU^jz|8>IAU@001fnlf;8d0w*$)5rj1YRyUJK YggODzlY@jh0o{|ngdqmDg8%>k02?-aU;qFB diff --git a/coreHelper.py b/coreHelper.py index fb208e6..30a6dec 100644 --- a/coreHelper.py +++ b/coreHelper.py @@ -10,12 +10,7 @@ def spawnBots(numSpawned: int, ct: Controller) -> bool: currentTitanium = ct.get_global_resources() - # 350 hoard keeps the opening lean (low scale, defense fund intact). - # the mid-war clause fixes the other failure: 4 builders starving at - # r130+ because war spending never lets ti back over 350 - if numSpawned < 5 and (currentTitanium >= 350 - or (ct.get_current_round() >= 60 - and currentTitanium >= ct.get_builder_bot_cost() + 60)): + if numSpawned < 5 and currentTitanium >= 350: return True if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: return True From a7e0391233f061a2a6f16f30a883c6f8a84a0adc Mon Sep 17 00:00:00 2001 From: Subodh Date: Tue, 21 Jul 2026 09:53:14 +0530 Subject: [PATCH 15/29] cap replacement respawns so a sentinel wall cant chain-drain our titanium --- bot.zip | Bin 34021 -> 34261 bytes coreHelper.py | 4 ---- main.py | 18 +++++++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/bot.zip b/bot.zip index c1ff83e5bbcc54586dbac518d86c7d48aa0482b6..fa0f2d183f1b93a15a52cd4954fd7541241b67cc 100644 GIT binary patch delta 11416 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Â$_-u=r@7vqV-#fjem(8bvDl&>A}S&?-mbLfWw$*sNMTUJ!RQDMLsna zvIRf-NlK7Oc8!KVEefh&HL@1t;~0`MAzu?sYlY9<{aJv%qy`OlqYr$Vzs{=qyfy4j zmu|*372k~L{J7RBah2R+%hvgDf3ZmbubOO2$2hdk?7Cr+-K~SV7O})D+vp<#c>K$PO>4Aw*^`^Z@!a6njeIc@j@DcMH2z7B4@ac zGUfAzTJnXgiW+(A2#{frTT;~_C0vM-8i0i+3s!P1xkXmL)TP3^WaR86YcuxwrZlj} z>%y5V^kXu}6uF@e5*q&j5`RCZZggx~;9w324n;u6TU=(fsmH^!d&=5j?TvPgo_=Vh zGNTK15IH$^(UN}?N2Z3i@L?DG%`UcOivdw+s*+Hji<@Tlk`SJYMa#)XFxM+hAftnN ze#N<1w;Vr=2uE2%=ylwf?AQ&eIB1f1`Jn;PS_|iFRq?seU$h`+-hVm_NA$R)e}21c zM~Da!txH2dO>_d$;-<qaLshATMcdwY95l&FQ0Ie1B7-iF4mDukIFVw#HL>Wc0; zMXzIpmJ|G_Y~YsFr+;s@=IuUxt#y~>X9I7D;vdwxEN&2|sr)Fe%QT&DiS0qLz(2`c zML6kXNEq|qzko}exH>(p>em^Y1|&a`L^+cwK7G^|vgZf?=qc9nGHT_6$~<6br$0Ny z3(usApGBp*f)|E2O+7Lrrth3@+A-glgGLqN8tijJthh>OD1RM4P17Q|0_}^ohSI7Q zfg(f=RXBEBaSb|xKir_@04%FkuNwnA<5RAY6I~m$Vw@IX&MxNb!FYd$>DU}&g?^y# z3HYV?a~m$l?8=a;IyV;#und>bH+7ZGQ*{<>=-_m@S*pDu=eC(|bdfO|+Td9`j7zDq zeI(Ew&wGo2et*b&>f<*WlHI|7Z0g+EkF==rE&WNCI_(r}%CS`371QFD4h*Kt)EBrO z3M1zP0bE5B+1}r#2~3ViQ|5&@02Ql~8#A29e-!`5#c?Gv&^UAMAwFg~p~H6eI5TVE zOsh?8U4;u;2oP6it-`}_vzdOh5>|(u&*MuQqa)@QWTG}5A(>q_I-cdTGR5enB?(uQbchR`RpFR=Hn-}bb{lJ*+1qCI$hFh| zE5mO>dh( z5Qgvkim7^mv~{VPRFxGhr7l$>xpqZxFI9!sX0TUTcG0pZ;@@YMj~MKv^09l{W#*ap zof$H%a>%wSMc{d%w1Er9c?%i~!L|rykMOAUL#8y{loU0vf6Sn6KkSvD5M(*<5_Xj_ zm`8)U94;j}8X{EW`H33rT zoC#0`N_u)G|GZl!(Q3W4(9b?m9}o*lAQ6TeCV7>2DhpOy7O4j|Dz~Y)NKqfulFTsr z9a@>b<2Wg1e^3_eQGQdVbY+#_+Y6>s;L^N@t(n4{tY{?!Y7ailV55|nIzVSzX-zfa zUP?gWlO395C)9?CL}RHcYG>(sO+3%wPynvs3Ybh!C^PbU<+l~=(P%0yA=VYXK$*ei zYm`ghhl^fPxn%;A0!fZEt_L6ClmG7`ZkO4S6Rj$le^MrqPv-N?v8R#K=2nsufD&!V z>qFvrWlpnC&2+$SoXJL6ZfUXa;Apho>$bZ44<<_NhM#en#(Ry7#{a^!1usp@Gi?w} z`On{e{E_X+VeUt*YNG5)a$2T7Mk3Rjx-~*8Te@EddIxPM4mWjT>H=NvK@dRLyu|b{ zS=_9bS@YX4vfUbWuhG>4?C)wb-vpxH3Hc-lQ@Ms0#!|ubcHzrU~iMJg&+nwg8%>k05fdS1poj5 diff --git a/coreHelper.py b/coreHelper.py index 30a6dec..07fc171 100644 --- a/coreHelper.py +++ b/coreHelper.py @@ -14,10 +14,6 @@ def spawnBots(numSpawned: int, ct: Controller) -> bool: return True if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: return True - if (ct.get_current_round() >= REPLACE_MIN_ROUND - and ct.get_unit_count() <= REPLACE_UNIT_THRESHOLD - and currentTitanium >= ct.get_builder_bot_cost() + REPLACE_RESERVE): - return True return False diff --git a/main.py b/main.py index 7a2ee55..24ea3bd 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,8 @@ # 20 ammo = 10 gunner shots or 2 sentinel shots. GLOBAL_AMMO_BUFFER = 20 +REPLACE_CAP = 8 # max lifetime replacement respawns +REPLACE_COOLDOWN = 25 # min rounds between them # rotate only with enough titanium left for follow-up work. GUNNER_ROTATE_MIN_TITANIUM = 25 @@ -67,6 +69,8 @@ def __init__(self): self.stuckTurns = 0 self.turretPlanner = None self.turtled = False # opponent kills cores, we stop attacking + self.replacements = 0 # capped respawns so deaths cant chain-drain + self.lastReplaceRound = -999 self.homeDefense = homedefense.HomeDefense() self.randDir = Direction.NORTH @@ -133,8 +137,17 @@ def run(self, ct: Controller) -> None: def core(self, ct: Controller) -> None: homeEmergency = self.homeDefense.observe_from_core(ct) + # replacement respawn: capped + cooldowned so a sentinel wall cant + # farm our titanium one dead builder at a time (string tiebreak: 41 + # dead builders = ~1200 ti gone) + rnd = ct.get_current_round() + wantReplace = (rnd >= 30 and self.replacements < REPLACE_CAP + and rnd - self.lastReplaceRound >= REPLACE_COOLDOWN + and ct.get_unit_count() <= 6 + and ct.get_global_resources() >= ct.get_builder_bot_cost() + 60) # spawn first so the ammo topup doesnt eat the builder money - if spawnBots(self.numSpawned, ct): + if spawnBots(self.numSpawned, ct) or wantReplace: + _isReplace = not spawnBots(self.numSpawned, ct) corePos = ct.get_position() threats = findThreats(ct) spawned = False @@ -154,6 +167,9 @@ def core(self, ct: Controller) -> None: ct.spawn_builder(spawnPos) self.numSpawned += 1 + if _isReplace: + self.replacements += 1 + self.lastReplaceRound = rnd # slot 0 also drives builder numbering + the share rotation ct.write_store(0, self.numSpawned) spawned = True From eb8d7ddaaa291e3ca087f316cd800986c4908316 Mon Sep 17 00:00:00 2001 From: Subodh Date: Tue, 21 Jul 2026 10:02:12 +0530 Subject: [PATCH 16/29] harvesters-first: gate discretionary sentinels behind a ti floor --- bot.zip | Bin 34261 -> 34312 bytes main.py | 3 +++ turretplan.py | 3 +++ 3 files changed, 6 insertions(+) diff --git a/bot.zip b/bot.zip index fa0f2d183f1b93a15a52cd4954fd7541241b67cc..9f3aaa387bf3f183e5ab631323bc989442668096 100644 GIT binary patch delta 19506 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≤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 F005lV_b31W diff --git a/main.py b/main.py index 24ea3bd..b2451b4 100644 --- a/main.py +++ b/main.py @@ -23,6 +23,7 @@ GLOBAL_AMMO_BUFFER = 20 REPLACE_CAP = 8 # max lifetime replacement respawns REPLACE_COOLDOWN = 25 # min rounds between them +MILITARY_FLOOR = 150 # below this ti, harvesters/conveyors win the money, not extra sentinels # rotate only with enough titanium left for follow-up work. GUNNER_ROTATE_MIN_TITANIUM = 25 @@ -828,6 +829,7 @@ def protectHarvester(self, ct: Controller): pos, self.isEnemyInfastructure ) if (direction != Direction.CENTRE + and ct.get_global_resources() >= MILITARY_FLOOR and ct.can_build_sentinel(pos, direction)): ct.build_sentinel(pos, direction) return @@ -877,6 +879,7 @@ def buildConveyor(self, ct: Controller): if self.enemyPos is not None and self.turretTimeOut == 0: direction = self.cardinalDirectionTo(self.conveyorEnd, self.enemyPos) if (direction != Direction.CENTRE + and ct.get_global_resources() >= MILITARY_FLOOR and ct.can_build_sentinel(self.conveyorEnd, direction)): ct.build_sentinel(self.conveyorEnd, direction) self.turretTimeOut = 1 diff --git a/turretplan.py b/turretplan.py index 76c84ff..18483d5 100644 --- a/turretplan.py +++ b/turretplan.py @@ -597,6 +597,9 @@ def try_passive_build(self, ct): return None if ct.get_action_cooldown() != 0: return None + # harvesters/conveyors first when ti is scarce (long econ games) + if ct.get_global_resources() < 150: + return None if proposal.turret_type == INFRA_GUNNER: if not ct.can_build_gunner(pos, proposal.facing): return None From 244db106021432003a49d42d35402abb0bd598e7 Mon Sep 17 00:00:00 2001 From: Subodh Date: Tue, 21 Jul 2026 10:20:00 +0530 Subject: [PATCH 17/29] cache the dijkstra table, recompute only on map/target change (ic3d flagged big-map TLE) --- bot.zip | Bin 34312 -> 34537 bytes main.py | 14 +++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bot.zip b/bot.zip index 9f3aaa387bf3f183e5ab631323bc989442668096..23ad602997084be4f1a9b862ac5d70cdeaa3cf28 100644 GIT binary patch delta 10825 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 x(|KPTpd@+^b6%&_BRcCm2RsX$l5~5oQ1I#C` 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 diff --git a/main.py b/main.py index b2451b4..74bc54a 100644 --- a/main.py +++ b/main.py @@ -65,6 +65,8 @@ def __init__(self): self.myNum = -1 # every builder bot gets its own unique number used to write to the shared array, and organize roles (1, 2, 3, ...) self.teamCore = None self.distMap = None + self._cacheTarget = None # dijkstra reuse: skip recompute on same target+map + self._cacheVer = -1 self.mapW = None self.mapH = None self.stuckTurns = 0 @@ -1196,7 +1198,17 @@ def moveTo (self, ct: Controller, target: Position): else: self.stuckTurns += 1 return - self.fillInDistTable(ct, target) + # cache: only rerun dijkstra when target or the shared map changed, or + # we walked into cells this table never filled (early-exit is bot-anchored) + ver = mapanalysis.struct_version + fresh = self._cacheTarget == target and self._cacheVer == ver and any( + 0 <= myLoc.add(d).x < self.mapW and 0 <= myLoc.add(d).y < self.mapH + and self.distMap[myLoc.add(d).x][myLoc.add(d).y] < 4096 + for d in CARDINALS) + if not fresh: + self.fillInDistTable(ct, target) + self._cacheTarget = target + self._cacheVer = ver bestDist = 4096 bestDir = None for d in CARDINALS: From 8fdc21b296937fd60563d8643738da03109f85bc Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 13:30:01 +0530 Subject: [PATCH 18/29] Tier A+B1-B3: symmetry prediction, launcher throw fix, ray-spawn fan, defensive spawn, choke blocker, threat-cost pathing, ore claims, memoryless roles Co-Authored-By: Claude Opus 4.8 --- main.py | 311 +++++++++++++++++++++++++++++++++++++++++----------- symmetry.py | 116 ++++++++++++++++++++ 2 files changed, 366 insertions(+), 61 deletions(-) create mode 100644 symmetry.py diff --git a/main.py b/main.py index 74bc54a..9ec8338 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ from defend import Defender import homedefense import mapanalysis +import symmetry import turretplan CARDINALS = [Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST] @@ -27,8 +28,11 @@ # rotate only with enough titanium left for follow-up work. GUNNER_ROTATE_MIN_TITANIUM = 25 -openCost = 1 +openCost = 1 barrierCost = 8 # to be implemented later +# Khaos soft-threat cost class: stepping through an enemy turret envelope is +# legal but expensive, so paths detour unless there is no alternative. +THREAT_COST = 12 tileTypes = { # limited to 4 types to maximize caching ability @@ -67,6 +71,7 @@ def __init__(self): self.distMap = None self._cacheTarget = None # dijkstra reuse: skip recompute on same target+map self._cacheVer = -1 + self._threatTiles = set() # enemy turret soft-threat tiles for pathing self.mapW = None self.mapH = None self.stuckTurns = 0 @@ -91,6 +96,7 @@ def __init__(self): # Cores self.teamCoreTiles = None self.enemyCore = None + self.enemyCorePredicted = None # symmetry guess before we ever see it # Launcher proofing self.oldPos = None @@ -119,6 +125,7 @@ def __init__(self): self.exploreTime = 0 self.attackMoveTimeout = 0 self.attackHypothesisIndex = 0 + self.symTracker = None # symmetry.SymmetryTracker, predicts enemy core def run(self, ct: Controller) -> None: etype = ct.get_entity_type() @@ -153,33 +160,30 @@ def core(self, ct: Controller) -> None: _isReplace = not spawnBots(self.numSpawned, ct) corePos = ct.get_position() threats = findThreats(ct) - spawned = False + # collect every legal spawn tile on the core's adjacent ring + candidates = [] for dx in (-1, 0, 1, 2): for dy in (-1, 0, 1, 2): - # skip the core's own 4 tiles if 0 <= dx <= 1 and 0 <= dy <= 1: - continue - + continue # skip the core's own 2x2 footprint spawnPos = Position(corePos.x + dx, corePos.y + dy) if not ct.can_spawn(spawnPos): continue if any(spawnPos.distance_squared(threat) <= 2 for threat in threats): continue - - ct.spawn_builder(spawnPos) - self.numSpawned += 1 - if _isReplace: - self.replacements += 1 - self.lastReplaceRound = rnd - # slot 0 also drives builder numbering + the share rotation - ct.write_store(0, self.numSpawned) - spawned = True - break - - if spawned: - break + candidates.append(spawnPos) + + spawnPos = self._pickSpawnTile(ct, corePos, candidates, _isReplace) + if spawnPos is not None: + ct.spawn_builder(spawnPos) + self.numSpawned += 1 + if _isReplace: + self.replacements += 1 + self.lastReplaceRound = rnd + # slot 0 also drives builder numbering + the share rotation + ct.write_store(0, self.numSpawned) ammoBuffer = (homedefense.HOME_AMMO_BUFFER if homeEmergency else GLOBAL_AMMO_BUFFER) @@ -193,6 +197,50 @@ def core(self, ct: Controller) -> None: if amount > 0 and ct.can_convert_ammo(amount): ct.convert_ammo(amount) + def _pickSpawnTile(self, ct, corePos, candidates, isReplace): + """Choose which adjacent tile to spawn on (Khaos ray-spawn opening). + + Priority: (1) defensive spawn toward a visible enemy when we're thin, + (2) fan the first 4 builders across the interior-facing arc for ore + coverage, (3) fall back to the first legal tile. + """ + if not candidates: + return None + ccx, ccy = corePos.x + 0.5, corePos.y + 0.5 + + # (1) defensive: a visible enemy and few of us -> spawn toward the threat + if ct.get_unit_count() < 10: + enemyPos = None + enemyDist = 1e9 + for eid in ct.get_nearby_units(): + if ct.get_team(eid) == ct.get_team(): + continue + ep = ct.get_position(eid) + d = (ep.x - ccx) ** 2 + (ep.y - ccy) ** 2 + if d < enemyDist: + enemyDist = d + enemyPos = ep + if enemyPos is not None: + return min(candidates, + key=lambda p: self.manhattan(p, enemyPos)) + + # (2) fan the first four builders outward (skip for replacements) + if not isReplace and self.numSpawned < 4: + w, h = ct.get_map_width(), ct.get_map_height() + primary = math.atan2(h / 2 - ccy, w / 2 - ccx) + offsets = (-1.178, -0.393, 0.393, 1.178) # ~ -67.5..67.5 deg + target = primary + offsets[self.numSpawned] + tx, ty = math.cos(target), math.sin(target) + + def align(p): + vx, vy = p.x + 0.5 - ccx, p.y + 0.5 - ccy + norm = math.hypot(vx, vy) or 1.0 + return -(vx * tx + vy * ty) / norm # smaller = better aligned + return min(candidates, key=align) + + # (3) default: first legal tile (preserves old behaviour) + return candidates[0] + def gunner(self, ct: Controller) -> None: target = ct.get_gunner_target() if target is not None: @@ -305,25 +353,35 @@ def sentinel(self, ct: Controller) -> None: def launcher(self, ct: Controller) -> None: myLoc = ct.get_position() - for d in CARDINALS: - adjacent = myLoc.add(d) - if not self.isInBounds(ct, adjacent) or not ct.is_in_vision(adjacent): - continue - bot_id = ct.get_tile_builder_bot_id(adjacent) - if bot_id is None or ct.get_team(bot_id) == ct.get_team(): - continue + if self.teamCore is None: + self.teamCore = findTeamCore(ct) + # throw enemies AWAY from our core (Khaos: keep them out of our + # territory). Falling back to away-from-launcher if core unknown. + anchor = self.teamCore if self.teamCore is not None else myLoc + # pickup radius is sqrt(2): scan all 8 neighbours, not just cardinals. + for dx in (-1, 0, 1): + for dy in (-1, 0, 1): + if dx == 0 and dy == 0: + continue + adjacent = Position(myLoc.x + dx, myLoc.y + dy) + if (not self.isInBounds(ct, adjacent) + or not ct.is_in_vision(adjacent)): + continue + bot_id = ct.get_tile_builder_bot_id(adjacent) + if bot_id is None or ct.get_team(bot_id) == ct.get_team(): + continue - best = None - bestDist = -1 - for tile in ct.get_nearby_tiles(26): - if ct.can_launch(adjacent, tile): - dist = self.manhattan(myLoc, tile) - if dist > bestDist: - bestDist = dist - best = tile - if best is not None: - ct.launch(adjacent, best) - return + best = None + bestDist = -1 + for tile in ct.get_nearby_tiles(26): + if ct.can_launch(adjacent, tile): + dist = self.manhattan(anchor, tile) + if dist > bestDist: + bestDist = dist + best = tile + if best is not None: + ct.launch(adjacent, best) + return def builderBot(self, ct: Controller) -> None: currentRound = ct.get_current_round() @@ -352,6 +410,19 @@ def builderBot(self, ct: Controller) -> None: self.getNewTiles(ct) self.shareTiles(ct, currentRound) self.updateBoard(ct) + # symmetry prediction: eliminate inconsistent hypotheses from the shared + # map, predict the enemy core corner. Cheap, only recomputes on struct + # change; feeds attack() targeting. + if self.teamCore is not None: + if self.symTracker is None: + self.symTracker = symmetry.SymmetryTracker(self.mapW, self.mapH) + self.symTracker.update( + self.fullMap, mapanalysis.struct_version, self.teamCore + ) + if self.enemyCore is None and self.symTracker.resolved(): + cx, cy = self.symTracker.enemy_core(self.teamCore) + if 0 <= cx < self.mapW - 1 and 0 <= cy < self.mapH - 1: + self.enemyCorePredicted = Position(cx, cy) if self.teamCore is not None and self.turretPlanner is None: self.turretPlanner = turretplan.TurretPlanner( self.mapW, self.mapH, self.teamCore @@ -388,11 +459,56 @@ def builderBot(self, ct: Controller) -> None: built = self.turretPlanner.try_passive_build(ct) if built is not None: self.noteBuiltTile(Position(built.x, built.y)) + self.tryChokeBlock(ct) self.advanceMapAnalysis(ct) if self.turretPlanner is not None: self.turretPlanner.advance(ct, self.fullMap, self.myNum) self.turretPlanner.draw_debug(ct) + def tryChokeBlock(self, ct: Controller): + """Khaos passive choke blocker: after the main behaviour, if the action + is free and we're standing next to an unblocked chokepoint on OUR side + of the map, plug it — barrier when the corridor is 1 tile wide, launcher + when wider (its 3x3 fling zone seals what a barrier can't). + + Our own barriers stay traversable (moveTo break-walks them), so this + turns the corridor into a door only our builders can pass — exactly the + anti-rush geometry Besvikomat's opening has to walk through. + """ + if ct.get_action_cooldown() != 0: + return + if not mapanalysis.chokes or self.teamCore is None: + return + myLoc = ct.get_position() + enemyRef = self.enemyCore or self.enemyCorePredicted + ti = ct.get_global_resources() + for choke in mapanalysis.chokes: + tile, clearance = choke[0], choke[1] + pos = Position(tile[0], tile[1]) + if myLoc.distance_squared(pos) > 2: + continue # outside builder action radius + # defensive perimeter only: strictly closer to our core + if (enemyRef is not None + and self.manhattan(pos, self.teamCore) + >= self.manhattan(pos, enemyRef)): + continue + if not self.isInBounds(ct, pos) or not ct.is_in_vision(pos): + continue + if ct.get_tile_building_id(pos) is not None: + continue # already blocked + if ct.get_tile_env(pos) == Environment.ORE_TITANIUM: + continue # never brick an ore tile + if clearance <= 1.2: + if (ti >= ct.get_barrier_cost() + 30 + and ct.can_build_barrier(pos)): + ct.build_barrier(pos) + self.noteBuiltTile(pos) + return + elif ti >= 100 and ct.can_build_launcher(pos): + ct.build_launcher(pos) + self.noteBuiltTile(pos) + return + def noteBuiltTile(self, builtPos: Position): oldType = self.fullMap[builtPos.x][builtPos.y] self.fullMap[builtPos.x][builtPos.y] = 3 @@ -459,8 +575,7 @@ def runRoleBehavior(self, ct: Controller, currentRound: int): and (currentRound > 20 or self.nearCore2(myLoc) < 3)): self.alwaysDefense = True self.defenseMode = True - if currentRound % 120 == 1 or self.justSpawned: - self.assignRole(ct, currentRound) + self.assignRole(ct, currentRound) self.justSpawned = False # old repair pass @@ -521,38 +636,50 @@ def runRoleBehavior(self, ct: Controller, currentRound: int): self.moveTo(ct, self.explorePos) def assignRole(self, ct: Controller, currentRound): - if currentRound <= 120: - # 36% Offense, 64% Eco + 1 Defense Bot - if (currentRound % 11 in [3, 5, 7, 10] - and self.currentHarvester is None - and not self.turtled): - self.attackMode = True - else: - self.defenseMode = False - self.attackMode = False - if currentRound % 120 == 1 and currentRound > 120: - # 25% Defense, 35% Offense, 40% Eco - self.defenseMode = False + """Memoryless role choice (Khaos): recompute from live state every + round instead of dicing every 120. Hysteresis on the money gate so an + attacker mid-siege doesn't flicker back to eco at the threshold, and a + deterministic myNum spread replaces the random mix.""" + if self.turtled or self.alwaysDefense: self.attackMode = False - if self.currentHarvester is None: - role = random.random() - if role > 0.75 or self.alwaysDefense: - self.defenseMode = True - elif role > 0.4 and not self.turtled: - self.attackMode = True + self.defenseMode = self.alwaysDefense + return + if self.currentHarvester is not None or self.foundHarvester is not None: + # committed econ claim: finish it first + self.attackMode = False + self.defenseMode = False + return + ti = ct.get_global_resources() + stayGate = MILITARY_FLOOR - 50 if self.attackMode else MILITARY_FLOOR + 50 + self.attackMode = (currentRound > 25 + and self.myNum % 3 == 0 + and ti >= stayGate) + self.defenseMode = False def attack(self, ct: Controller) -> None: if self.teamCore is None: return myLoc = ct.get_position() - coreHypothesis = [ + # symmetry-ordered core hypotheses: surviving guesses first (most-likely + # leading), then the full reflection/rotation set as a fallback so we + # still cycle if symmetry hasn't resolved. + coreHypothesis = [] + if self.symTracker is not None: + for cx, cy in self.symTracker.enemy_core_candidates(self.teamCore): + if 0 <= cx < self.mapW - 1 and 0 <= cy < self.mapH - 1: + coreHypothesis.append(Position(cx, cy)) + for fallback in ( Position(ct.get_map_width() - 2 - self.teamCore.x, ct.get_map_height() - 2 - self.teamCore.y), Position(ct.get_map_width() - 2 - self.teamCore.x, self.teamCore.y), Position(self.teamCore.x, ct.get_map_height() - 2 - self.teamCore.y), - ] + ): + if fallback not in coreHypothesis: + coreHypothesis.append(fallback) + if self.attackHypothesisIndex >= len(coreHypothesis): + self.attackHypothesisIndex = 0 if self.enemyCore is not None: self.attackPos = self.enemyCore elif self.attackPos is None: @@ -697,7 +824,26 @@ def getHarvester(self, ct: Controller, myLoc: Position): key=lambda p: (self.manhattan(p, myLoc) + self.nearCore2(p), p.x, p.y) ) + # Voronoi claim partition (Khaos): skip ore a visible teammate + # would reach first (id tiebreak), so builders stop contesting + # the same tile and fan out instead. + friends = [] + myId = ct.get_id() + for uid in ct.get_nearby_units(): + if (uid != myId and ct.get_team(uid) == ct.get_team() + and ct.get_entity_type(uid) == EntityType.BUILDER_BOT): + friends.append((ct.get_position(uid), uid)) for orePos in knownOres: + if friends: + myDist = self.manhattan(orePos, myLoc) + contested = False + for fpos, fid in friends: + fd = self.manhattan(fpos, orePos) + if fd < myDist or (fd == myDist and fid < myId): + contested = True + break + if contested: + continue willWork = True if ct.is_in_vision(orePos): oreID = ct.get_tile_building_id(orePos) @@ -1108,26 +1254,69 @@ def advanceMapAnalysis(self, ct: Controller): if analysisJob is not None and analysisJob.phase == mapanalysis.DONE: mapanalysis.draw_debug(ct, Position) + def _buildThreatSet(self): + """Soft-threat tiles from remembered ENEMY turrets (facing unknown, so + approximate: gunner = cardinal rays len 3; sentinel = 3-wide cardinal + bands len 5 + adjacent ring). Walls clip the rays.""" + threat = set() + planner = self.turretPlanner + if planner is None: + return threat + w, h = self.mapW, self.mapH + fullMap = self.fullMap + for (x, y), (code, _f) in planner.infra_memory.items(): + if code == turretplan.INFRA_GUNNER: + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + for k in range(1, 4): + tx, ty = x + dx * k, y + dy * k + if not (0 <= tx < w and 0 <= ty < h): + break + if fullMap[tx][ty] == 2: + break + threat.add((tx, ty)) + elif code == turretplan.INFRA_SENTINEL: + for ddx in (-1, 0, 1): + for ddy in (-1, 0, 1): + bx, by = x + ddx, y + ddy + if 0 <= bx < w and 0 <= by < h: + threat.add((bx, by)) + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + for k in range(1, 6): + tx, ty = x + dx * k, y + dy * k + if not (0 <= tx < w and 0 <= ty < h): + break + if fullMap[tx][ty] == 2: + break + for off in (-1, 0, 1): + bx, by = (tx, ty + off) if dx else (tx + off, ty) + if 0 <= bx < w and 0 <= by < h: + threat.add((bx, by)) + return threat + def tileCost(self, ct: Controller, tile: Position) -> int | None: if not (0 <= tile.x < self.mapW and 0 <= tile.y < self.mapH): return None cachedVal = self.fullMap[tile.x][tile.y] if cachedVal > 1: # either wall or unpassable return None + cost = openCost if ct.is_in_vision(tile): tileId = ct.get_tile_building_id(tile) if tileId is not None: tTeam = ct.get_team(tileId) tType = ct.get_entity_type(tileId) if tTeam == ct.get_team() and tType == EntityType.BARRIER: - return barrierCost + cost = barrierCost elif ct.is_tile_passable(tile) == False: return 4096 - return openCost + if (tile.x, tile.y) in self._threatTiles: + cost += THREAT_COST + return cost def fillInDistTable (self, ct: Controller, targetLoc: Position): w, h = self.mapW, self.mapH - maxCost = (w + h) * barrierCost + 5 + self._threatTiles = self._buildThreatSet() + maxCost = (w + h) * (barrierCost + THREAT_COST) + 5 for x in range(w): for y in range(h): diff --git a/symmetry.py b/symmetry.py new file mode 100644 index 0000000..f3001b0 --- /dev/null +++ b/symmetry.py @@ -0,0 +1,116 @@ +"""Map-symmetry prediction (Khaos blueprint Tier A, section 3). + +Florent maps are symmetric by reflection or rotation. By comparing observed +static terrain (wall / ore / empty) against its mirror under each hypothesis we +eliminate the inconsistent ones, then predict the enemy core corner from the +surviving symmetry. Default to ROTATIONAL while ambiguous (Khaos default). + +fullMap tile codes (from main.py): + -1 unseen · 0 empty/passable · 1 ore · 2 wall · 3 building/unpassable(dynamic) + +Only codes {0,1,2} are static terrain we can trust for symmetry; code 3 hides +the underlying terrain (a harvester sits on ore), so we skip those tiles. +""" + +# symmetry ids, in preference order (rotational first, Khaos default) +ROTATIONAL = 0 # 180 rotation +HORIZONTAL = 1 # flip x +VERTICAL = 2 # flip y +_ALL = (ROTATIONAL, HORIZONTAL, VERTICAL) + + +def _terrain(code: int) -> int: + """Collapse a fullMap code to a static-terrain class, or -1 if unknown.""" + # wall stays wall; ore vs empty are the two passable classes. + if code == 2: + return 2 + if code == 1: + return 1 + if code == 0: + return 0 + return -1 # -1 unseen or 3 building -> terrain unknown + + +def _mirror(x: int, y: int, w: int, h: int, sym: int): + if sym == ROTATIONAL: + return w - 1 - x, h - 1 - y + if sym == HORIZONTAL: + return w - 1 - x, y + return x, h - 1 - y # VERTICAL + + +def _core_corner(cx: int, cy: int, w: int, h: int, sym: int): + """Predicted enemy core top-left corner for a 2x2 core mirrored under sym.""" + if sym == ROTATIONAL: + return w - 2 - cx, h - 2 - cy + if sym == HORIZONTAL: + return w - 2 - cx, cy + return cx, h - 2 - cy # VERTICAL + + +class SymmetryTracker: + def __init__(self, w: int, h: int): + self.w = w + self.h = h + self.alive = list(_ALL) # surviving hypotheses + self._ver = -1 # struct_version this was computed at + self._core = None # cached (cx, cy) our core corner + + def update(self, full_map, struct_version: int, core_corner) -> None: + """Recompute survivors when the shared structural map changed. + + Cheap: <=900 tiles x 3 symmetries, only on struct_version change. + """ + if core_corner is not None: + self._core = (core_corner.x, core_corner.y) + if struct_version == self._ver and len(self.alive) <= 1: + return # already resolved, nothing new can help + if struct_version == self._ver: + return + self._ver = struct_version + w, h = self.w, self.h + alive = [] + for sym in self.alive: + ok = True + for x in range(w): + col = full_map[x] + for y in range(h): + t = _terrain(col[y]) + if t < 0: + continue + mx, my = _mirror(x, y, w, h, sym) + mt = _terrain(full_map[mx][my]) + if mt < 0: + continue + if t != mt: + ok = False + break + if not ok: + break + if ok: + alive.append(sym) + # never let noise eliminate everything; keep rotational as a floor + self.alive = alive if alive else [ROTATIONAL] + + def resolved(self) -> bool: + return len(self.alive) == 1 + + def best(self) -> int: + return self.alive[0] if self.alive else ROTATIONAL + + def enemy_core(self, core_corner): + """Best-guess enemy core corner (Position-like via factory in caller).""" + cx, cy = core_corner.x, core_corner.y + return _core_corner(cx, cy, self.w, self.h, self.best()) + + def enemy_core_candidates(self, core_corner): + """All surviving enemy-core corners, most-likely first (as (x,y)).""" + cx, cy = core_corner.x, core_corner.y + seen = set() + out = [] + for sym in self.alive: + c = _core_corner(cx, cy, self.w, self.h, sym) + if c not in seen: + seen.add(c) + out.append(c) + return out From c0f00e475d12dbccf901a011c0214c3689acae7e Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 13:42:22 +0530 Subject: [PATCH 19/29] Tier B/C: single-claim ore Voronoi, role gate fix, TTL ore cache, sentinel one-shot pref + idle self-destruct, unseen-biased explore, Dial's perf rewrite, getNewTiles hoist, chain deadlock fix (sprint 0-mined bug) Co-Authored-By: Claude Opus 4.8 --- __pycache__/coreHelper.cpython-311.pyc | Bin 0 -> 2507 bytes __pycache__/defend.cpython-311.pyc | Bin 0 -> 8439 bytes __pycache__/homedefense.cpython-311.pyc | Bin 0 -> 31800 bytes __pycache__/main.cpython-311.pyc | Bin 0 -> 69308 bytes __pycache__/mapanalysis.cpython-311.pyc | Bin 0 -> 36193 bytes __pycache__/turretplan.cpython-311.pyc | Bin 0 -> 55921 bytes main.py | 297 ++++++++++++++++-------- 7 files changed, 203 insertions(+), 94 deletions(-) create mode 100644 __pycache__/coreHelper.cpython-311.pyc create mode 100644 __pycache__/defend.cpython-311.pyc create mode 100644 __pycache__/homedefense.cpython-311.pyc create mode 100644 __pycache__/main.cpython-311.pyc create mode 100644 __pycache__/mapanalysis.cpython-311.pyc create mode 100644 __pycache__/turretplan.cpython-311.pyc diff --git a/__pycache__/coreHelper.cpython-311.pyc b/__pycache__/coreHelper.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c29a9e8a45dce48473dd608737f3c35b2e357466 GIT binary patch literal 2507 zcmds2O=ufO6rNrE#=DX%%T-bv8#(?-6eBx{Q!pXaIIdD)DupBWb{^|5u?h>yVd2puo8xC88ty3K*jVLpPqwhB$+Wi;kgh32dDQ@8-2 zbx7&;NGg6)60pW5RLx8THBF=h3zLmz1j7LTgRcR0P=?7t|7v>(t|v|RZ8ig2S<}bF znqe`!23vBV9vNkc@gozoC!D?I%;pNoVS1hn{ROl9p=Io*rGL9q4eT)povx} zv$zFj1%vgTm({vg1U0sjl$YYk7!edXO%j5lIjL7k#FIiSC4fg#y#``|M~D?bb0pG) zfHNwpaY;-A`oR!NiWIx#^Q{8Q%cHltSl}kWGpNuWFpK8Ngy3d zZD>andcqfFRUuu#0P9D}b$~1?yN~U=eMPsg;2tlz$Frfb#kn>8>7DGI-SNkfV$bP( z&*`$mwW+rFpPt>HxKy0DRG646P0SU1bI<$*U#Qp<%J+n7(Ol=|I|q;fB@Q1et-W5z zAb5As3cAf?P>zPZ(pnFm7W4H5!puY9deY1?gFgD?wgI9K3#iGSF|Hs>^{6 z*7Rpd1Xy&*G$I;(JIkBmTKBLj>zBD7CFurssqH z*+5vc(lAwsuWRObDkT6NPdzDF5mix^yvCYXE$R&-#8pMJLhi7hmJtF=O1;=p7F`S((+Z_A0p`vXl_hG?yqGUT! zMF!Kj4x3Dwx9{`)MZSOc;{rcW;s+iql!hmZ{A8Y=EO&P8aNFGO^q2Op?78EQM!y@q z=ezGKc8=#e$IHCyi{R(M-Lv0LeKYm=Orh6T>h%@)u@XO)=f_@@xvp%GxL{c=@}+yx z<@+}na1Z?}>4G3vbeT6L3J*71rcJ(TQ-lbu>Vl!6bwOT)jECl%Byc0PisDUiZGx=H z{3t}fUgmAA36?<-boT}Z++%`!W^xpkK#oy(8$dJqL*W4FrQ<$;JysV6@qn$+Tn!cQ zx(Yy0ez>K`37DbZgM*s@AcIE13$~GxZKR4=Qw^ZpoNb@$D{_6i@dD>5ah?a4?}v)q zM4p@Yf&GR1iTic>NBhtAXUG2>{eAQ|-ygo>HTFQVVtZ(SGJO%T5;7J-yU!b6u*G&31Nppt- zA6%LC2V(QVxo9jp8xBNfLsw=rXT$&gTrd_6%?D>RN5cg%f$-IUW)LM+>+s8xO5|ix zAewPWmXrF|Ofx1$Mb&Q;T}y;6Vy~86zja<(FRaTrofO_B=U|GyhsyT=RRhB?W#q~? zepNH->CGee^KN$@IiGiRXB}0unQ`t8RuOc$^LqDmNblOA%fh&}E>{tBxpR8g4&4T3 XWNQJgE(QeM3Ot=!ZTGeY>BspG55fC( literal 0 HcmV?d00001 diff --git a/__pycache__/defend.cpython-311.pyc b/__pycache__/defend.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e8bb18c9058af2f926fedbf46ce27b6c43d026c GIT binary patch literal 8439 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 bX1l9=c1o6T9O=7v@pbyyAHRh}(cr%Uz6Iz8 literal 0 HcmV?d00001 diff --git a/__pycache__/homedefense.cpython-311.pyc b/__pycache__/homedefense.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32132c544c3cc4740c10abdcca342601ac1364f8 GIT binary patch literal 31800 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 literal 0 HcmV?d00001 diff --git a/__pycache__/mapanalysis.cpython-311.pyc b/__pycache__/mapanalysis.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2d8890cfd88a5b6ceb0fa9d5829f489257c76a5 GIT binary patch literal 36193 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⁡Di=sW1AZC1_lLS!xnqYRr}vD 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 literal 0 HcmV?d00001 diff --git a/__pycache__/turretplan.cpython-311.pyc b/__pycache__/turretplan.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4086e01349f2d1dbc7beb8030ce21303e7ea61ea GIT binary patch literal 55921 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 literal 0 HcmV?d00001 diff --git a/main.py b/main.py index 9ec8338..9bad2a5 100644 --- a/main.py +++ b/main.py @@ -25,8 +25,9 @@ REPLACE_CAP = 8 # max lifetime replacement respawns REPLACE_COOLDOWN = 25 # min rounds between them MILITARY_FLOOR = 150 # below this ti, harvesters/conveyors win the money, not extra sentinels -# rotate only with enough titanium left for follow-up work. -GUNNER_ROTATE_MIN_TITANIUM = 25 +# rotate only with enough titanium left for follow-up work (Khaos used 60 so +# gunners can't burn the economy by spinning). +GUNNER_ROTATE_MIN_TITANIUM = 60 openCost = 1 barrierCost = 8 # to be implemented later @@ -82,6 +83,7 @@ def __init__(self): self.homeDefense = homedefense.HomeDefense() self.randDir = Direction.NORTH + self.idleTurns = 0 # sentinel: consecutive turns with no enemy seen # Role self.attackMode = False @@ -104,7 +106,9 @@ def __init__(self): # Harvester finding self.currentHarvester = None self.foundHarvester = None - self.usedHarvesters = [] + # TTL failure cache (Khaos): ore we gave up on comes back after 100 + # rounds instead of being blacklisted forever. {Position: expiry_round} + self.usedHarvesters = {} self.reachHarvesterTime = 0 # Building back and sentinel placement @@ -208,19 +212,26 @@ def _pickSpawnTile(self, ct, corePos, candidates, isReplace): return None ccx, ccy = corePos.x + 0.5, corePos.y + 0.5 - # (1) defensive: a visible enemy and few of us -> spawn toward the threat - if ct.get_unit_count() < 10: - enemyPos = None - enemyDist = 1e9 - for eid in ct.get_nearby_units(): - if ct.get_team(eid) == ct.get_team(): - continue - ep = ct.get_position(eid) - d = (ep.x - ccx) ** 2 + (ep.y - ccy) ** 2 - if d < enemyDist: - enemyDist = d - enemyPos = ep - if enemyPos is not None: + # (1) defensive: an enemy actually AT our base with no ally covering it + # -> spawn toward the intruder (Khaos). Tight gate: on small maps the + # core sees most of the board, and reacting to every distant scout + # hijacks the whole opening (sprint 0-mined regression). + enemyPos = None + enemyDist = 1e9 + allies = [] + for eid in ct.get_nearby_units(): + ep = ct.get_position(eid) + if ct.get_team(eid) == ct.get_team(): + if ct.get_entity_type(eid) == EntityType.BUILDER_BOT: + allies.append(ep) + continue + d = (ep.x - ccx) ** 2 + (ep.y - ccy) ** 2 + if d < enemyDist: + enemyDist = d + enemyPos = ep + if enemyPos is not None and enemyDist <= 18: + covered = any(a.distance_squared(enemyPos) <= 8 for a in allies) + if not covered: return min(candidates, key=lambda p: self.manhattan(p, enemyPos)) @@ -299,8 +310,34 @@ def gunner(self, ct: Controller) -> None: def sentinel(self, ct: Controller) -> None: if self.enemyCore is None: self.enemyCore = findEnemyCore(ct) + if self.teamCore is None: + self.teamCore = findTeamCore(ct) team = ct.get_team() + + # Khaos: an idle FORWARD sentinel is +20% scale of dead weight; the + # scale refunds on death, so self-destruct after a long quiet spell. + # Home guards (near core) and harvester rings are exempt. + seesEnemy = False + for eid in ct.get_nearby_entities(): + if ct.get_team(eid) != team: + seesEnemy = True + break + if seesEnemy: + self.idleTurns = 0 + else: + self.idleTurns += 1 + if self.idleTurns > 40 and ct.get_current_round() > 150: + nearHarv = any( + ct.get_team(b) == team + and ct.get_entity_type(b) == EntityType.HARVESTER + for b in ct.get_nearby_buildings(8)) + nearCore = (self.teamCore is not None + and self.manhattan(ct.get_position(), + self.teamCore) <= 6) + if not nearHarv and not nearCore: + ct.self_destruct() + return priority = { EntityType.CORE: 5, EntityType.SENTINEL: 3, @@ -333,19 +370,25 @@ def sentinel(self, ct: Controller) -> None: continue tilePriority = 0 + targetHp = 0 if buildingID is not None: buildingType = ct.get_entity_type(buildingID) if buildingType not in targets: continue tilePriority = priority.get(buildingType, 1) + targetHp = ct.get_hp(buildingID) if builderID is not None: tilePriority += 2 elif builderID is not None: # not worth 10 ammo on a lone builder continue - if tilePriority > bestPriority: - bestPriority = tilePriority + # Khaos tiebreaks: same priority -> prefer a clean one-shot + # (hp <= 18 dies now, before their builders can heal it), then + # the lowest-hp target. + key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, -targetHp) + if bestTile is None or key > bestPriority: + bestPriority = key bestTile = tile if bestTile is not None and ct.can_fire(bestTile): @@ -649,8 +692,11 @@ def assignRole(self, ct: Controller, currentRound): self.attackMode = False self.defenseMode = False return + # walking to the enemy is free; the sentinel build itself is + # affordability-gated downstream. Only a token gate here so a fresh + # attacker isn't dispatched while we're utterly broke. ti = ct.get_global_resources() - stayGate = MILITARY_FLOOR - 50 if self.attackMode else MILITARY_FLOOR + 50 + stayGate = 30 if self.attackMode else 60 self.attackMode = (currentRound > 25 and self.myNum % 3 == 0 and ti >= stayGate) @@ -794,6 +840,24 @@ def pickExplore(self, ct: Controller, currentRound): w = ct.get_map_width() h = ct.get_map_height() myLoc = ct.get_position() + # Khaos frontier sampling (cheap form): sample random tiles and head + # for the nearest genuinely UNSEEN one, so exploration eats the + # unknown map instead of re-walking known ground. + if self.seenTiles is not None and currentRound > 5: + best = None + bestDist = 1 << 30 + for _ in range(24): + tx = random.randrange(w) + ty = random.randrange(h) + if self.seenTiles[tx][ty]: + continue + d = abs(tx - myLoc.x) + abs(ty - myLoc.y) + if d < bestDist: + bestDist = d + best = Position(tx, ty) + if best is not None: + self.explorePos = best + return radius = (w + h) // 5 if currentRound > 5: theta = random.random() * 2 * math.pi @@ -806,6 +870,9 @@ def pickExplore(self, ct: Controller, currentRound): def getHarvester(self, ct: Controller, myLoc: Position): if self.foundHarvester is None: # shared map first: chase ore teammates found, no out of vision queries + rnd = ct.get_current_round() + self.usedHarvesters = {p: exp for p, exp in + self.usedHarvesters.items() if exp > rnd} used = set(self.usedHarvesters) knownOres = { Position(x, y) @@ -824,26 +891,29 @@ def getHarvester(self, ct: Controller, myLoc: Position): key=lambda p: (self.manhattan(p, myLoc) + self.nearCore2(p), p.x, p.y) ) - # Voronoi claim partition (Khaos): skip ore a visible teammate - # would reach first (id tiebreak), so builders stop contesting - # the same tile and fan out instead. - friends = [] + # Voronoi claim partition (Khaos): each visible teammate claims + # only its single NEAREST ore (guards/attackers passing through + # must not veto every ore they happen to stand near). Skip an ore + # if a strictly-closer teammate has it as their nearest. myId = ct.get_id() - for uid in ct.get_nearby_units(): - if (uid != myId and ct.get_team(uid) == ct.get_team() - and ct.get_entity_type(uid) == EntityType.BUILDER_BOT): - friends.append((ct.get_position(uid), uid)) - for orePos in knownOres: - if friends: - myDist = self.manhattan(orePos, myLoc) - contested = False - for fpos, fid in friends: - fd = self.manhattan(fpos, orePos) - if fd < myDist or (fd == myDist and fid < myId): - contested = True - break - if contested: + claimed = set() + if knownOres: + for uid in ct.get_nearby_units(): + if (uid == myId or ct.get_team(uid) != ct.get_team() + or ct.get_entity_type(uid) + != EntityType.BUILDER_BOT): continue + fpos = ct.get_position(uid) + nearest = min( + knownOres, + key=lambda p: (self.manhattan(p, fpos), p.x, p.y)) + fd = self.manhattan(nearest, fpos) + myDist = self.manhattan(nearest, myLoc) + if fd < myDist or (fd == myDist and uid < myId): + claimed.add(nearest) + for orePos in knownOres: + if orePos in claimed: + continue willWork = True if ct.is_in_vision(orePos): oreID = ct.get_tile_building_id(orePos) @@ -888,7 +958,8 @@ def getHarvester(self, ct: Controller, myLoc: Position): if possibleSpots: self.moveTo(ct, possibleSpots[0]) else: - self.usedHarvesters.append(self.foundHarvester) + self.usedHarvesters[self.foundHarvester] = ( + ct.get_current_round() + 100) self.foundHarvester = None return @@ -911,13 +982,16 @@ def getHarvester(self, ct: Controller, myLoc: Position): def removeHarvester(self, ct: Controller): if self.foundHarvester is not None: - self.usedHarvesters.append(self.foundHarvester) + self.usedHarvesters[self.foundHarvester] = ( + ct.get_current_round() + 100) self.foundHarvester = None self.reachHarvesterTime = 0 def addHarvester(self, ct: Controller, myLoc: Position): self.currentHarvester = self.foundHarvester - self.usedHarvesters.append(self.foundHarvester) + # own build claims are near-permanent, not a 100-round failure entry + self.usedHarvesters[self.foundHarvester] = ( + ct.get_current_round() + 100000) self.foundHarvester = None self.conveyorEnd = myLoc self.surroundHarvester = False @@ -1033,7 +1107,12 @@ def buildConveyor(self, ct: Controller): self.turretTimeOut = 1 self.nextTurretCountDown = 5 else: - return + # can't place the screen sentinel (broke, or spot blocked): + # drop the request and keep routing. Returning here deadlocked + # the whole chain whenever enemy infra stayed visible while we + # were poor -> harvesters built but 0 delivered (sprint bug). + self.enemyPos = None + self.enemyType = -1 if self.turretTimeOut > 0: self.turretTimeOut += 1 if self.enemyType == 3 and self.turretTimeOut > 0: @@ -1155,22 +1234,48 @@ def checkPassable (self, ct: Controller, tile: Position): # Only team barriers, return True def getNewTiles (self, ct: Controller): + # hottest per-turn loop (Khaos update_at analog): hoist lookups. + fullMap = self.fullMap + seenTiles = self.seenTiles + newTiles = self.newTiles + getEnv = ct.get_tile_env + getBuildingId = ct.get_tile_building_id + isPassable = ct.is_tile_passable + getTeam = ct.get_team + getType = ct.get_entity_type + myTeam = ct.get_team() + WALL = Environment.WALL + ORE = Environment.ORE_TITANIUM + BARRIER = EntityType.BARRIER for tile in ct.get_nearby_tiles(): x = tile.x y = tile.y - tileEnv = ct.get_tile_env(tile) - if not self.checkPassable(ct, tile) and tileEnv != Environment.WALL: # Walls clearly dont have buildings so need that - tileEnv = 'Unpassable' - tileType = tileTypes.get(tileEnv) - oldType = self.fullMap[x][y] - if self.seenTiles[x][y] == False or self.fullMap[x][y] != tileType: - self.seenTiles[x][y] = True - self.newTiles.append([Position(x, y), tileType]) + tileEnv = getEnv(tile) + if tileEnv == WALL: + tileType = 2 + else: + # inline checkPassable: empty or own-barrier tiles count + tileId = getBuildingId(tile) + blocked = False + if tileId is not None and not isPassable(tile): + if (getTeam(tileId) != myTeam + or getType(tileId) != BARRIER): + blocked = True + if blocked: + tileType = 3 + elif tileEnv == ORE: + tileType = 1 + else: + tileType = 0 + oldType = fullMap[x][y] + if seenTiles[x][y] == False or oldType != tileType: + seenTiles[x][y] = True + newTiles.append([Position(x, y), tileType]) if (oldType in (-1, 2, 3)) != (tileType in (-1, 2, 3)): mapanalysis.note_structural_tile( x, y, tileType in (-1, 2, 3), self.mapW, self.mapH ) - self.fullMap[x][y] = tileType + fullMap[x][y] = tileType def shareTiles(self, ct: Controller, curRound): spawnCount = ct.read_store(0) @@ -1293,69 +1398,73 @@ def _buildThreatSet(self): threat.add((bx, by)) return threat - def tileCost(self, ct: Controller, tile: Position) -> int | None: - if not (0 <= tile.x < self.mapW and 0 <= tile.y < self.mapH): - return None - cachedVal = self.fullMap[tile.x][tile.y] - if cachedVal > 1: # either wall or unpassable - return None - cost = openCost - if ct.is_in_vision(tile): - tileId = ct.get_tile_building_id(tile) - if tileId is not None: - tTeam = ct.get_team(tileId) - tType = ct.get_entity_type(tileId) - if tTeam == ct.get_team() and tType == EntityType.BARRIER: - cost = barrierCost - elif ct.is_tile_passable(tile) == False: - return 4096 - if (tile.x, tile.y) in self._threatTiles: - cost += THREAT_COST - return cost - def fillInDistTable (self, ct: Controller, targetLoc: Position): + # Khaos perf pass: locals hoisted, raw int coords in the buckets, and + # an inline vision test — Controller calls only for the handful of + # tiles actually inside vision. w, h = self.mapW, self.mapH self._threatTiles = self._buildThreatSet() + threat = self._threatTiles maxCost = (w + h) * (barrierCost + THREAT_COST) + 5 - + distMap = self.distMap + fullMap = self.fullMap + blank = [4096] * h for x in range(w): - for y in range(h): - self.distMap[x][y] = 4096 - + distMap[x][:] = blank + buckets = [[] for _ in range(maxCost)] - buckets[0].append(targetLoc) - self.distMap[targetLoc.x][targetLoc.y] = 0 - - myLoc = ct.get_position() + buckets[0].append((targetLoc.x, targetLoc.y)) + distMap[targetLoc.x][targetLoc.y] = 0 - myNeighbors = [] - for d in CARDINALS: - myNeighbors.append(myLoc.add(d)) + myLoc = ct.get_position() + myx, myy = myLoc.x, myLoc.y + myNeighbors = {(myx + 1, myy), (myx - 1, myy), + (myx, myy + 1), (myx, myy - 1)} + myTeam = ct.get_team() + getBuildingId = ct.get_tile_building_id + getTeam = ct.get_team + getType = ct.get_entity_type + isPassable = ct.is_tile_passable + BARRIER = EntityType.BARRIER for dist in range(maxCost): bucket = buckets[dist] hitBucket = False while bucket: cur = bucket.pop() + cx, cy = cur - if self.distMap[cur.x][cur.y] < dist: + if distMap[cx][cy] < dist: continue - if (cur.x, cur.y) in myNeighbors: + if cur in myNeighbors: hitBucket = True continue - for d in CARDINALS: - nextTile = cur.add(d) - nextX = nextTile.x - nextY = nextTile.y - if 0 <= nextX < w and 0 <= nextY < h: - stepCost = self.tileCost(ct, nextTile) - if stepCost is not None: - newDist = dist + stepCost - if newDist < maxCost and newDist < self.distMap[nextX][nextY]: - self.distMap[nextX][nextY] = newDist - buckets[newDist].append(nextTile) + for nx, ny in ((cx + 1, cy), (cx - 1, cy), + (cx, cy + 1), (cx, cy - 1)): + if not (0 <= nx < w and 0 <= ny < h): + continue + if fullMap[nx][ny] > 1: + continue # wall or unpassable + cost = openCost + dxv = nx - myx + dyv = ny - myy + if dxv * dxv + dyv * dyv <= 20: # builder vision r^2 + pos = Position(nx, ny) + tileId = getBuildingId(pos) + if tileId is not None: + if (getTeam(tileId) == myTeam + and getType(tileId) == BARRIER): + cost = barrierCost + elif not isPassable(pos): + continue + if (nx, ny) in threat: + cost += THREAT_COST + newDist = dist + cost + if newDist < maxCost and newDist < distMap[nx][ny]: + distMap[nx][ny] = newDist + buckets[newDist].append((nx, ny)) if hitBucket: return dist return 4096 From 33207728fa35a17021a3989044bddb7e3162cee5 Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 13:47:08 +0530 Subject: [PATCH 20/29] Launcher trap-throw (Khaos): fling intruders back onto their own trail; rebuild bot.zip with symmetry.py Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 34537 -> 40878 bytes main.py | 27 +++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/bot.zip b/bot.zip index 23ad602997084be4f1a9b862ac5d70cdeaa3cf28..c57c872706927f023d67af9ca5c862e533f92569 100644 GIT binary patch delta 17619 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 delta 11256 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 x(|KPTpd@+^b6%&_B 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 diff --git a/main.py b/main.py index 9bad2a5..4400d54 100644 --- a/main.py +++ b/main.py @@ -84,6 +84,7 @@ def __init__(self): self.randDir = Direction.NORTH self.idleTurns = 0 # sentinel: consecutive turns with no enemy seen + self.enemyBotHistory = {} # launcher: enemy builder position trails # Role self.attackMode = False @@ -401,6 +402,20 @@ def launcher(self, ct: Controller) -> None: # throw enemies AWAY from our core (Khaos: keep them out of our # territory). Falling back to away-from-launcher if core unknown. anchor = self.teamCore if self.teamCore is not None else myLoc + # Khaos trap throw: remember where each enemy builder has walked so we + # can fling it BACK along its own path - several turns of lost walking + # per launcher action. + team = ct.get_team() + hist = self.enemyBotHistory + for uid in ct.get_nearby_units(): + if (ct.get_team(uid) != team + and ct.get_entity_type(uid) == EntityType.BUILDER_BOT): + ep = ct.get_position(uid) + lst = hist.setdefault(uid, []) + if not lst or lst[-1] != ep: + lst.append(ep) + if len(lst) > 12: + lst.pop(0) # pickup radius is sqrt(2): scan all 8 neighbours, not just cardinals. for dx in (-1, 0, 1): for dy in (-1, 0, 1): @@ -414,13 +429,17 @@ def launcher(self, ct: Controller) -> None: if bot_id is None or ct.get_team(bot_id) == ct.get_team(): continue + # prefer a tile this bot already walked (trap: forced retrace), + # weighted by distance from our core; else plain away-throw. + walked = set(hist.get(bot_id, ())[:-3]) # skip its last steps best = None - bestDist = -1 + bestKey = (-1, -1) for tile in ct.get_nearby_tiles(26): if ct.can_launch(adjacent, tile): - dist = self.manhattan(anchor, tile) - if dist > bestDist: - bestDist = dist + key = (1 if tile in walked else 0, + self.manhattan(anchor, tile)) + if key > bestKey: + bestKey = key best = tile if best is not None: ct.launch(adjacent, best) From d0596b9c6675f826f32b7989591ab8747e07b7ea Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 14:10:48 +0530 Subject: [PATCH 21/29] Khaos route state: Dijkstra conveyor routing (threat-aware dist-to-core gradient, struct-cached) replaces greedy manhattan descent; skip barrier phase on quiet harvesters Co-Authored-By: Claude Opus 4.8 --- main.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index 4400d54..59907f1 100644 --- a/main.py +++ b/main.py @@ -73,6 +73,8 @@ def __init__(self): self._cacheTarget = None # dijkstra reuse: skip recompute on same target+map self._cacheVer = -1 self._threatTiles = set() # enemy turret soft-threat tiles for pathing + self._routeDist = None # Khaos route state: dist-to-core table + self._routeVer = -1 self.mapW = None self.mapH = None self.stuckTurns = 0 @@ -1034,6 +1036,16 @@ def protectHarvester(self, ct: Controller): if ct.get_team(building_id) != ct.get_team(): self.isEnemyInfastructure = ct.get_position(building_id) + if self.isEnemyInfastructure is None: + # quiet neighbourhood: the barrier ring is wasted tempo (v30 + # lesson) and enemy builders can't hurt a harvester anyway -> + # route the chain NOW. Screens/protection can come later if + # enemy infra actually shows up. + self.protectTurns = 0 + self.surroundHarvester = True + self.sentinelSpot = None + return + protectSpots = [] for direction in CARDINALS: pos = self.currentHarvester.add(direction) @@ -1147,6 +1159,26 @@ def buildConveyor(self, ct: Controller): return self.buildCloser(ct) + def _routeTable(self, ct: Controller): + """Khaos route state: full-map dist-to-core over placeable tiles, + threat-aware, cached until the shared map structurally changes. Lets + chains round walls and dodge sentinel bands instead of greedy-walking + the manhattan gradient into them.""" + ver = mapanalysis.struct_version + if self._routeDist is not None and self._routeVer == ver: + return self._routeDist + if self._routeDist is None: + self._routeDist = [[4096] * self.mapH for _ in range(self.mapW)] + seeds = [s for s in (self.teamCoreTiles + or ([self.teamCore] if self.teamCore else [])) + if s is not None] + if not seeds: + return self._routeDist + self.fillInDistTable(ct, seeds[0], table=self._routeDist, + seeds=seeds, earlyExit=False) + self._routeVer = ver + return self._routeDist + def buildCloser(self, ct: Controller): if self.conveyorEnd is None: return @@ -1182,11 +1214,14 @@ def buildCloser(self, ct: Controller): and ct.get_tile_env(end) in (Environment.EMPTY, Environment.ORE_TITANIUM)): possibleConveyors.append(direction) - possibleConveyors.sort( - key=lambda direction: self.nearCore2( - self.conveyorEnd.add(direction) - ) - ) + # Khaos route ordering: follow the Dijkstra dist-to-core gradient + # (routes around walls/threat); manhattan only as tiebreak/fallback. + routeD = self._routeTable(ct) + + def routeKey(direction): + end = self.conveyorEnd.add(direction) + return (routeD[end.x][end.y], self.nearCore2(end)) + possibleConveyors.sort(key=routeKey) for direction in possibleConveyors: end = self.conveyorEnd.add(direction) @@ -1198,9 +1233,16 @@ def buildCloser(self, ct: Controller): self.conveyorEnd = None return + curRoute = routeD[self.conveyorEnd.x][self.conveyorEnd.y] for direction in possibleConveyors: end = self.conveyorEnd.add(direction) - if self.nearCore2(end) >= self.nearCore2(self.conveyorEnd): + # merge only downhill along the route gradient (manhattan fallback + # when the table hasn't reached one of the tiles) + endRoute = routeD[end.x][end.y] + if endRoute < 4096 and curRoute < 4096: + if endRoute >= curRoute: + continue + elif self.nearCore2(end) >= self.nearCore2(self.conveyorEnd): continue endID = ct.get_tile_building_id(end) if (endID is not None @@ -1417,23 +1459,29 @@ def _buildThreatSet(self): threat.add((bx, by)) return threat - def fillInDistTable (self, ct: Controller, targetLoc: Position): + def fillInDistTable (self, ct: Controller, targetLoc: Position, + table=None, seeds=None, earlyExit=True): # Khaos perf pass: locals hoisted, raw int coords in the buckets, and # an inline vision test — Controller calls only for the handful of - # tiles actually inside vision. + # tiles actually inside vision. With table/seeds/earlyExit=False this + # doubles as the conveyor-ROUTING solver (full map dist-to-core). w, h = self.mapW, self.mapH self._threatTiles = self._buildThreatSet() threat = self._threatTiles maxCost = (w + h) * (barrierCost + THREAT_COST) + 5 - distMap = self.distMap + distMap = table if table is not None else self.distMap fullMap = self.fullMap blank = [4096] * h for x in range(w): distMap[x][:] = blank buckets = [[] for _ in range(maxCost)] - buckets[0].append((targetLoc.x, targetLoc.y)) - distMap[targetLoc.x][targetLoc.y] = 0 + if seeds is None: + seeds = [targetLoc] + for s in seeds: + if 0 <= s.x < w and 0 <= s.y < h: + buckets[0].append((s.x, s.y)) + distMap[s.x][s.y] = 0 myLoc = ct.get_position() myx, myy = myLoc.x, myLoc.y @@ -1456,7 +1504,7 @@ def fillInDistTable (self, ct: Controller, targetLoc: Position): if distMap[cx][cy] < dist: continue - if cur in myNeighbors: + if earlyExit and cur in myNeighbors: hitBucket = True continue From c628185706c57cf38f5a5d5fe448045041abfa69 Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 20:46:54 +0530 Subject: [PATCH 22/29] khaos rebuild replaces botv2: memoryless 7-state spine, bitmask map/path/planes layers (P1 barrier break-walk + P2 score planes incl). botv2 fallback = d0596b9 Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 40878 -> 25479 bytes coreHelper.py | 39 - defend.py | 126 --- homedefense.py | 539 ---------- main.py | 2542 ++++++++++++++++++++---------------------------- mapinfo.py | 273 ++++++ path.py | 173 ++++ pathfinding.py | 156 --- planes.py | 99 ++ turretplan.py | 918 ----------------- 10 files changed, 1606 insertions(+), 3259 deletions(-) delete mode 100644 coreHelper.py delete mode 100644 defend.py delete mode 100644 homedefense.py create mode 100644 mapinfo.py create mode 100644 path.py delete mode 100644 pathfinding.py create mode 100644 planes.py delete mode 100644 turretplan.py diff --git a/bot.zip b/bot.zip index c57c872706927f023d67af9ca5c862e533f92569..d71de6a567dbc94f113940e8ad4de62faf56e443 100644 GIT binary patch delta 18273 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 diff --git a/coreHelper.py b/coreHelper.py deleted file mode 100644 index 07fc171..0000000 --- a/coreHelper.py +++ /dev/null @@ -1,39 +0,0 @@ -from fcode import * - -# replace the workforce only after the whole team is gutted. -REPLACE_UNIT_THRESHOLD = 6 -# dont let the replacement path fire during the normal opening -REPLACE_MIN_ROUND = 30 -# keep this titanium after paying for a replacement builder. -REPLACE_RESERVE = 60 - - -def spawnBots(numSpawned: int, ct: Controller) -> bool: - currentTitanium = ct.get_global_resources() - if numSpawned < 5 and currentTitanium >= 350: - return True - if numSpawned > 4 and currentTitanium >= 400 * ct.get_scale_percent() / 100: - return True - return False - - -def findThreats(ct: Controller) -> list: - threats = [] - curRound = ct.get_current_round() - for i in ct.get_nearby_buildings(8): - if (ct.get_entity_type(i) == EntityType.LAUNCHER and - ct.get_team(i) != ct.get_team()): - threats.append(ct.get_position(i)) - return threats - - -def findTeamCore(ct: Controller): - for i in ct.get_nearby_buildings(): - if ct.get_entity_type(i) == EntityType.CORE and ct.get_team(i) == ct.get_team(): - return ct.get_position(i) - - -def findEnemyCore(ct: Controller): - for i in ct.get_nearby_buildings(): - if ct.get_entity_type(i) == EntityType.CORE and ct.get_team(i) != ct.get_team(): - return ct.get_position(i) diff --git a/defend.py b/defend.py deleted file mode 100644 index fc87df5..0000000 --- a/defend.py +++ /dev/null @@ -1,126 +0,0 @@ -from fcode import * -import random - - -class Defender: - def __init__(self): - self.defendPos = None - self.destroyTurns = 0 - - def defendInfrastructure(self, ct: Controller, teamCore, pf): - myLoc = ct.get_position() - if self.defendPos is None: - for i in ct.get_nearby_buildings(): - if ct.get_team(i) == ct.get_team() and ct.get_hp(i) < ct.get_max_hp(i): - self.defendPos = ct.get_position(i) - break - if self.defendPos is not None: - if myLoc.distance_squared(self.defendPos) > 2: - pf.moveTo(ct, self.defendPos) - self.placeLauncher(ct, self.defendPos) - if ct.can_heal(self.defendPos): - ct.heal(self.defendPos) - - if self.defendPos is not None: - if ct.is_in_vision(self.defendPos): - defendID = ct.get_tile_building_id(self.defendPos) - if defendID is None: - self.defendPos = None - elif ct.get_hp(defendID) == ct.get_max_hp(defendID): - self.defendPos = None - else: - self.defendPos = None - - def placeLauncher(self, ct: Controller, pos: Position): - myLoc = ct.get_position() - # launchers still yeet the siege's support builders, but 13 of them - # was pure waste: cap how many we keep alive - team = ct.get_team() - launchers = 0 - for i in ct.get_nearby_buildings(): - if ct.get_team(i) == team and ct.get_entity_type(i) == EntityType.LAUNCHER: - launchers += 1 - if ct.get_position(i).distance_squared(pos) <= 2: - return # Launcher already built - if launchers >= 2: - return # enough launchers alive, dont spam them - topCands = [] - otherCands = [] - for k in ct.get_nearby_tiles(2): - if k.distance_squared(pos) <= 2 and k.distance_squared(myLoc) <= 2: - bid = ct.get_tile_building_id(k) - if bid is None and ct.get_tile_env(k) != Environment.WALL: - topCands.append(k) - elif bid is not None and ct.get_team(bid) == ct.get_team(): - etype_k = ct.get_entity_type(bid) - if etype_k not in (EntityType.CONVEYOR, EntityType.HARVESTER, EntityType.SENTINEL): - otherCands.append(k) - for a in topCands: - if ct.can_build_launcher(a): - if self.destroyTurns > 5: - self.destroyTurns = 0 - ct.build_launcher(a) - return - didDestroy = False - for a in otherCands: - if ct.can_destroy(a) and didDestroy == False: - ct.destroy(a) - didDestroy = True - if ct.can_build_launcher(a): - if self.destroyTurns > 5: - self.destroyTurns = 0 - ct.build_launcher(a) - return - return - - def picHealingLoc(self, ct: Controller, teamCore: Position, alwaysDefense: bool = False) -> Position | None: - candidates = [] - for i in ct.get_nearby_tiles(16): - if ct.get_tile_env(i) == Environment.EMPTY and i.distance_squared(teamCore) < ((ct.get_map_width() + ct.get_map_height() - 8) // 4): - bid = ct.get_tile_building_id(i) - if bid is None: - candidates.append(i) - else: - entype = ct.get_entity_type(bid) - if (entype == EntityType.CONVEYOR or entype == EntityType.CORE): - candidates.append(i) - - if not candidates: - return Position(teamCore.x + random.randint(-1, 2), teamCore.y + random.randint(-1, 2)) - - buildingweights = [] - buildingpos = [] - for bid in ct.get_nearby_buildings(): - if ct.get_team(bid) == ct.get_team(): - entype = ct.get_entity_type(bid) - if entype == EntityType.CONVEYOR: - buildingweights.append(25) - buildingpos.append(ct.get_position(bid)) - elif entype == EntityType.LAUNCHER: - buildingweights.append(5) - buildingpos.append(ct.get_position(bid)) - elif entype == EntityType.CORE: - buildingweights.append(100) - buildingpos.append(ct.get_position(bid)) - - bestTile = None - bestScore = -1 - - for i in candidates: - score = 0 - for j in range(len(buildingweights)): - pose = buildingpos[j] - weight = buildingweights[j] - dist = i.distance_squared(pose) - if dist < 50: - score += max(weight * (1 - 0.02 * dist), 0) - - if score > bestScore: - bestTile = i - bestScore = score - if alwaysDefense == True: - return Position(teamCore.x + random.randint(-1, 1), teamCore.y + random.randint(-1, 1)) - else: - if bestTile is None: - return Position(teamCore.x + random.randint(-1, 1), teamCore.y + random.randint(-1, 1)) - return Position(bestTile.x + random.randint(-1, 1), bestTile.y + random.randint(-1, 1)) diff --git a/homedefense.py b/homedefense.py deleted file mode 100644 index 3e8e5d8..0000000 --- a/homedefense.py +++ /dev/null @@ -1,539 +0,0 @@ -"""fast home-siege detection and builder counter-battery.""" - -from fcode import Direction, EntityType, Environment, Position - -import turretplan - - -# early enemy turret near our half = hyper aggressive opponent, stop attacking -TURTLE_TRIGGER_MANHATTAN = 14 -TURTLE_TRIGGER_ROUND = 120 - -# big maps keep one gunner standing near the core before any rush shows up -STANDING_GUARD_MIN_AREA = 0 # every map: small maps get rushed the fastest -STANDING_GUARD_RESERVE = 40 # ti to keep above the gunner cost -STANDING_GUARD_MIN_ROUND = 8 # up before the earliest rush arrives -STANDING_GUARD_WALK_MANHATTAN = 8 # defense bots further than this dont bother - -# home turrets this close trigger the interrupt. -HOME_TURRET_MANHATTAN = 5 -# only builders already near home may respond. -HOME_RESPONDER_MANHATTAN = 10 -# fresh sightings keep the interrupt alive this long locally. -HOME_SIGHTING_FRESH_ROUNDS = 8 -# large home groups are thinned by a deterministic parity filter. -HOME_PARITY_SPAWN_THRESHOLD = 2 -# reactive builds preserve one builder's titanium. -HOME_BUILDER_RESERVE = 15 -# no more than this many live counter-turrets are wanted. -HOME_COUNTER_TURRET_CAP = 2 -# emergency ammo supports twenty gunner shots. -HOME_AMMO_BUFFER = 40 - - -# cardinal facings in store-compatible order. -FACINGS = ( - Direction.NORTH, - Direction.EAST, - Direction.SOUTH, - Direction.WEST, -) - - -def _manhattan(a, b): - return abs(a.x - b.x) + abs(a.y - b.y) - - -def _core_tiles(corner, width, height): - tiles = [] - for dx in (0, 1): - for dy in (0, 1): - x, y = corner.x + dx, corner.y + dy - if 0 <= x < width and 0 <= y < height: - tiles.append(Position(x, y)) - return tiles if tiles else [corner] - - -def _near_core(pos, core_tiles): - return min(_manhattan(pos, tile) for tile in core_tiles) - - -def _direction_to(origin, target): - if origin.x == target.x: - if target.y > origin.y: - return Direction.SOUTH - if target.y < origin.y: - return Direction.NORTH - if origin.y == target.y: - if target.x > origin.x: - return Direction.EAST - if target.x < origin.x: - return Direction.WEST - return None - - -class HomeDefense: - def __init__(self): - self.current_targets = [] - self.fresh_until = {} - self.reactive_turrets = {} - - def observe_from_core(self, ct): - """Publish one nearest visible turret and return the live emergency.""" - width = ct.get_map_width() - height = ct.get_map_height() - corner = ct.get_position() - core_tiles = _core_tiles(corner, width, height) - visible = [] - for building_id in ct.get_nearby_buildings(): - if ct.get_team(building_id) == ct.get_team(): - continue - entity_type = ct.get_entity_type(building_id) - type_code = turretplan.ENTITY_TO_INFRA.get(entity_type) - if type_code not in turretplan.TURRET_TYPES: - continue - pos = ct.get_position(building_id) - visible.append((pos, type_code)) - - visible.sort(key=lambda item: ( - _near_core(item[0], core_tiles), item[0].x, item[0].y, - item[1], - )) - wrote_slot = None - if visible: - pos, type_code = visible[0] - wrote_slot = turretplan.publish_enemy_infra_sighting( - ct, width, height, pos, type_code - ) - - # core vision owns liveness for every home-range record. - if wrote_slot is None: - live_positions = {(pos.x, pos.y) for pos, _ in visible} - records = turretplan.read_enemy_infra_records(ct, width, height) - for record in records: - if record is None or record[2] not in turretplan.TURRET_TYPES: - continue - pos = Position(record[0], record[1]) - if (_near_core(pos, core_tiles) > HOME_TURRET_MANHATTAN - or not ct.is_in_vision(pos) - or (pos.x, pos.y) in live_positions): - continue - turretplan.clear_enemy_infra_sighting( - ct, width, height, pos - ) - break - - return any(_near_core(pos, core_tiles) <= HOME_TURRET_MANHATTAN - for pos, _ in visible) - - def home_emergency(self, ct, planner, core_tiles): - """Return whether shared sightings and local core vision demand action.""" - self.current_targets = [] - if planner is None or not core_tiles: - return False - now = ct.get_current_round() - core_damaged = None - shared = {(record[0], record[1], record[2]) - for record in planner.shared_infra_records()} - for (x, y), (type_code, freshness) in planner.infra_memory.items(): - if type_code not in turretplan.TURRET_TYPES: - continue - pos = Position(x, y) - if _near_core(pos, core_tiles) > HOME_TURRET_MANHATTAN: - continue - if core_damaged is None: - core_damaged = self._core_damaged(ct, core_tiles) - age = turretplan.infra_sighting_age_rounds(now, freshness) - present = ((x, y, type_code) in shared or ct.is_in_vision(pos)) - if age == 0 and present: - self.fresh_until[(x, y)] = now + HOME_SIGHTING_FRESH_ROUNDS - fresh = now <= self.fresh_until.get((x, y), -1) - if (bool(core_damaged) and present) or fresh: - self.current_targets.append((pos, type_code)) - - known = {(pos.x, pos.y) for pos, _ in self.current_targets} - for key in list(self.fresh_until): - if key not in planner.infra_memory or ( - key not in known and now > self.fresh_until[key]): - del self.fresh_until[key] - self.current_targets.sort(key=lambda item: ( - _near_core(item[0], core_tiles), item[0].x, item[0].y, - item[1], - )) - return bool(self.current_targets) - - def should_respond(self, ct, my_num, core_tiles): - if not self.current_targets or my_num < 1: - return False - if _near_core(ct.get_position(), core_tiles) > HOME_RESPONDER_MANHATTAN: - return False - spawn_count = ct.read_store(0) - if spawn_count <= HOME_PARITY_SPAWN_THRESHOLD: - return True - target = self.current_targets[0][0] - return (my_num & 1) == ((target.x + target.y) & 1) - - def respond(self, ct, planner, my_num, core_tiles, full_map, move_to): - """Run one elected builder's emergency turn.""" - if not self.home_emergency(ct, planner, core_tiles): - return False, None - if not self.should_respond(ct, my_num, core_tiles): - return False, None - - self._prune_reactive_memory(ct) - counter_count = self._counter_turret_count(ct, planner) - wanted = min(HOME_COUNTER_TURRET_CAP, len(self.current_targets)) - if counter_count < wanted: - built = self._try_build(ct, planner, core_tiles, full_map) - if built is not None: - return True, built - - self._heal_core(ct, core_tiles) - target = self._staging_tile(ct, core_tiles, full_map) - if target is None: - target = min(core_tiles, key=lambda tile: ( - _manhattan(ct.get_position(), tile), tile.x, tile.y - )) - move_to(ct, target) - return True, None - - def _core_damaged(self, ct, core_tiles): - checked = set() - for tile in core_tiles: - if not ct.is_in_vision(tile): - continue - building_id = ct.get_tile_building_id(tile) - if building_id is None or building_id in checked: - continue - checked.add(building_id) - if (ct.get_team(building_id) == ct.get_team() - and ct.get_entity_type(building_id) == EntityType.CORE - and ct.get_hp(building_id) < ct.get_max_hp(building_id)): - return True - return False - - def _try_build(self, ct, planner, core_tiles, full_map): - # flat reserve: the counter turret IS the survival spend, dont - # price it against a scaled builder replacement - reserve = HOME_BUILDER_RESERVE - resources = ct.get_global_resources() - gunner_cost = ct.get_gunner_cost() - if resources >= gunner_cost + reserve: - candidates = self._gunner_candidates( - ct, core_tiles, full_map, local_only=True - ) - for _, pos, facing, target in candidates: - if (not ct.can_build_gunner(pos, facing) - and not self._clear_allied_build_tile(ct, pos)): - continue - if not ct.can_build_gunner(pos, facing): - continue - ct.build_gunner(pos, facing) - self._record_built( - ct, planner, pos, turretplan.INFRA_GUNNER, facing - ) - return pos - - sentinel_cost = ct.get_sentinel_cost() - if resources >= sentinel_cost + reserve: - candidates = self._sentinel_candidates( - ct, core_tiles, full_map, local_only=True - ) - for _, pos, facing, target in candidates: - if (not ct.can_build_sentinel(pos, facing) - and not self._clear_allied_build_tile(ct, pos)): - continue - if not ct.can_build_sentinel(pos, facing): - continue - ct.build_sentinel(pos, facing) - self._record_built( - ct, planner, pos, turretplan.INFRA_SENTINEL, facing - ) - return pos - return None - - def _record_built(self, ct, planner, pos, type_code, facing): - self.reactive_turrets[(pos.x, pos.y)] = (type_code, facing) - proposal = turretplan.Proposal( - pos.x, pos.y, type_code, facing, 0, False, False, False, 0 - ) - planner.record_built(ct, proposal) - - def _local_tiles(self, ct): - return sorted(ct.get_nearby_tiles(2), key=lambda pos: (pos.x, pos.y)) - - def _gunner_candidates(self, ct, core_tiles, full_map, local_only): - if local_only: - tiles = self._local_tiles(ct) - else: - tiles = self._global_candidate_tiles(ct, full_map) - candidates = [] - for target_index, (target, target_type) in enumerate(self.current_targets): - for pos in tiles: - facing = _direction_to(pos, target) - distance_squared = pos.distance_squared(target) - if (facing is None or distance_squared > 13 - or not self._ray_clear(ct, full_map, pos, target)): - continue - exposed = self._in_inferred_home_ray( - pos, target, target_type, core_tiles - ) - move_distance = _manhattan(ct.get_position(), pos) - score = (int(exposed), target_index, move_distance, - -distance_squared, pos.x, pos.y) - candidates.append((score, pos, facing, target)) - candidates.sort(key=lambda item: item[0]) - return candidates - - def _sentinel_candidates(self, ct, core_tiles, full_map, local_only): - if local_only: - tiles = self._local_tiles(ct) - else: - tiles = self._global_candidate_tiles(ct, full_map) - candidates = [] - for target_index, (target, target_type) in enumerate(self.current_targets): - for pos in tiles: - for facing in FACINGS: - if not turretplan.in_turret_envelope( - (pos.x, pos.y), (target.x, target.y), - turretplan.INFRA_SENTINEL, facing): - continue - exposed = self._in_inferred_home_ray( - pos, target, target_type, core_tiles - ) - move_distance = _manhattan(ct.get_position(), pos) - score = (int(exposed), target_index, move_distance, - -pos.distance_squared(target), pos.x, pos.y, - FACINGS.index(facing)) - candidates.append((score, pos, facing, target)) - candidates.sort(key=lambda item: item[0]) - return candidates - - def _global_candidate_tiles(self, ct, full_map): - width = ct.get_map_width() - height = ct.get_map_height() - tiles = set() - for target, _ in self.current_targets: - for dx in range(-5, 6): - for dy in range(-5, 6): - x, y = target.x + dx, target.y + dy - if not (0 <= x < width and 0 <= y < height): - continue - if dx * dx + dy * dy > 32: - continue - if full_map is not None and full_map[x][y] in (2, 3): - continue - tiles.add((x, y)) - return [Position(x, y) for x, y in sorted(tiles)] - - def _staging_tile(self, ct, core_tiles, full_map): - gunners = self._gunner_candidates( - ct, core_tiles, full_map, local_only=False - ) - if gunners: - return gunners[0][1] - sentinels = self._sentinel_candidates( - ct, core_tiles, full_map, local_only=False - ) - return sentinels[0][1] if sentinels else None - - def _ray_clear(self, ct, full_map, origin, target): - facing = _direction_to(origin, target) - if facing is None: - return False - dx = 0 if origin.x == target.x else (1 if target.x > origin.x else -1) - dy = 0 if origin.y == target.y else (1 if target.y > origin.y else -1) - x, y = origin.x + dx, origin.y + dy - width = ct.get_map_width() - height = ct.get_map_height() - while (x, y) != (target.x, target.y): - if not (0 <= x < width and 0 <= y < height): - return False - pos = Position(x, y) - if ct.is_in_vision(pos): - if (ct.get_tile_env(pos) == Environment.WALL - or ct.get_tile_building_id(pos) is not None): - return False - elif full_map is None or full_map[x][y] in (-1, 2, 3): - return False - x += dx - y += dy - return True - - def _in_inferred_home_ray(self, pos, enemy, enemy_type, core_tiles): - core = min(core_tiles, key=lambda tile: ( - _manhattan(enemy, tile), tile.x, tile.y - )) - facing = _direction_to(enemy, core) - if facing is None: - dx = core.x - enemy.x - dy = core.y - enemy.y - if abs(dx) >= abs(dy): - facing = Direction.EAST if dx > 0 else Direction.WEST - else: - facing = Direction.SOUTH if dy > 0 else Direction.NORTH - index = FACINGS.index(facing) - forward_x, forward_y = turretplan.FACING_DELTAS[index] - dx = pos.x - enemy.x - dy = pos.y - enemy.y - forward = dx * forward_x + dy * forward_y - lateral = abs(dx * forward_y - dy * forward_x) - range_squared = 13 if enemy_type == turretplan.INFRA_GUNNER else 32 - return (forward > 0 and lateral <= 1 - and dx * dx + dy * dy <= range_squared) - - def _counter_turret_count(self, ct, planner): - turrets = {key: (type_code, None) - for key, type_code in planner.turret_memory.items()} - turrets.update(self.reactive_turrets) - for building_id in ct.get_nearby_buildings(): - if ct.get_team(building_id) != ct.get_team(): - continue - type_code = turretplan.ENTITY_TO_INFRA.get( - ct.get_entity_type(building_id) - ) - if type_code in turretplan.TURRET_TYPES: - pos = ct.get_position(building_id) - turrets[(pos.x, pos.y)] = ( - type_code, ct.get_direction(building_id) - ) - - count = 0 - for (x, y), (type_code, facing) in turrets.items(): - if facing is None: - continue - for target, _ in self.current_targets: - if turretplan.in_turret_envelope( - (x, y), (target.x, target.y), type_code, facing): - count += 1 - break - return min(HOME_COUNTER_TURRET_CAP, count) - - def _prune_reactive_memory(self, ct): - for key, (type_code, facing) in list(self.reactive_turrets.items()): - pos = Position(*key) - if not ct.is_in_vision(pos): - continue - building_id = ct.get_tile_building_id(pos) - actual = None - if building_id is not None and ct.get_team(building_id) == ct.get_team(): - actual = turretplan.ENTITY_TO_INFRA.get( - ct.get_entity_type(building_id) - ) - if actual != type_code: - del self.reactive_turrets[key] - - def _clear_allied_build_tile(self, ct, pos): - if not ct.is_in_vision(pos): - return False - building_id = ct.get_tile_building_id(pos) - if building_id is None or ct.get_team(building_id) != ct.get_team(): - return False - protected = ( - EntityType.CORE, - EntityType.HARVESTER, - EntityType.GUNNER, - EntityType.SENTINEL, - ) - if ct.get_entity_type(building_id) in protected or not ct.can_destroy(pos): - return False - ct.destroy(pos) - return True - - def _heal_core(self, ct, core_tiles): - checked = set() - for tile in core_tiles: - if (ct.get_position().distance_squared(tile) > 2 - or not ct.is_in_vision(tile)): - continue - building_id = ct.get_tile_building_id(tile) - if building_id is None or building_id in checked: - continue - checked.add(building_id) - if (ct.get_team(building_id) == ct.get_team() - and ct.get_entity_type(building_id) == EntityType.CORE - and ct.get_hp(building_id) < ct.get_max_hp(building_id) - and ct.can_heal(tile)): - ct.heal(tile) - return True - return False - - - def maintain_standing_guard(self, ct, planner, core_tiles, enemy_core, - full_map, move_to): - """big maps only: one cheap pre-built gunner near the core so a rush - meets fire before the reactive battery spins up. returns - (engaged, built_tile).""" - if planner is None or not core_tiles: - return False, None - w, h = ct.get_map_width(), ct.get_map_height() - if w * h <= STANDING_GUARD_MIN_AREA: - return False, None - if ct.get_current_round() < STANDING_GUARD_MIN_ROUND: - return False, None - if ct.get_global_resources() < ct.get_gunner_cost() + STANDING_GUARD_RESERVE: - return False, None - # already guarded? shared records first, then own vision - for x, y, _t in planner._friendly_turrets(): - if _near_core(Position(x, y), core_tiles) <= HOME_TURRET_MANHATTAN: - return False, None - team = ct.get_team() - for b in ct.get_nearby_buildings(): - if (ct.get_team(b) == team - and ct.get_entity_type(b) in (EntityType.GUNNER, - EntityType.SENTINEL) - and _near_core(ct.get_position(b), core_tiles) - <= HOME_TURRET_MANHATTAN): - return False, None - - # face the likely approach: sighted enemy core, else rotational guess - base = core_tiles[0] - if enemy_core is None: - enemy_core = Position(w - 2 - base.x, h - 2 - base.y) - dx = enemy_core.x - base.x - dy = enemy_core.y - base.y - if abs(dx) >= abs(dy): - face = Direction.EAST if dx > 0 else Direction.WEST - else: - face = Direction.SOUTH if dy > 0 else Direction.NORTH - fdx, fdy = face.delta() - - # spot: two out from the core footprint toward the approach, slight - # side offsets as fallbacks - cands = [] - for c in core_tiles: - p2 = Position(c.x + 2 * fdx, c.y + 2 * fdy) - for q in (p2, Position(p2.x + fdy, p2.y + fdx), - Position(p2.x - fdy, p2.y - fdx)): - if 0 <= q.x < w and 0 <= q.y < h and q not in cands: - cands.append(q) - my = ct.get_position() - for q in cands: - if full_map[q.x][q.y] != 0: - continue # only known-empty ground - if my.distance_squared(q) <= 2: - if ct.can_build_gunner(q, face): - ct.build_gunner(q, face) - planner.pending_turrets[(q.x, q.y)] = turretplan.INFRA_GUNNER - return True, q - continue - if _manhattan(my, q) <= STANDING_GUARD_WALK_MANHATTAN: - move_to(ct, q) - return True, None - return False, None - - - def should_turtle(self, ct, planner, core_tiles): - """sticky per unit: an early enemy turret near our side means this - opponent kills cores. every ti we spend attacking is stolen from - survival, and a game we survive is a game we win on econ.""" - if planner is None or not core_tiles: - return False - if ct.get_current_round() > TURTLE_TRIGGER_ROUND: - return False - for (x, y), (type_code, _f) in planner.infra_memory.items(): - if type_code not in turretplan.TURRET_TYPES: - continue - if _near_core(Position(x, y), core_tiles) <= TURTLE_TRIGGER_MANHATTAN: - return True - return False diff --git a/main.py b/main.py index 59907f1..901b2cf 100644 --- a/main.py +++ b/main.py @@ -1,326 +1,1054 @@ -"""Starter bot - a simple example to demonstrate usage of the Controller API. +"""Khaos rebuild — memoryless state-scoring bot (Pantheon architecture). -Each unit gets its own Player instance; the engine calls run() once per round. -Use Controller.get_entity_type() to branch on what kind of unit you are. +Every builder, every turn: score seven candidate states in descending ceiling +order with early exit, run the single best. No mode flags, no multi-turn +commitments — targets are re-derived each turn from the bitmask map layer, +deconflicted through a Voronoi claim partition, with TTL caches for targets +that recently failed. + +States (ceiling): attack 9 · heal 8 · route 7.75 · secure 7.5 · harvest 4 · +disrupt 2 · explore 1. """ -import math import random from fcode import Controller, Direction, EntityType, Environment, Position -from coreHelper import findEnemyCore, findTeamCore, findThreats, spawnBots -from defend import Defender -import homedefense import mapanalysis +import mapinfo as mapinfo_mod +import path +import planes import symmetry -import turretplan CARDINALS = [Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST] -DIRECTIONS = CARDINALS -CardDirections = CARDINALS +DELTA_TO_DIR = { + (0, -1): Direction.NORTH, (0, 1): Direction.SOUTH, + (1, 0): Direction.EAST, (-1, 0): Direction.WEST, +} -# 20 ammo = 10 gunner shots or 2 sentinel shots. GLOBAL_AMMO_BUFFER = 20 -REPLACE_CAP = 8 # max lifetime replacement respawns -REPLACE_COOLDOWN = 25 # min rounds between them -MILITARY_FLOOR = 150 # below this ti, harvesters/conveyors win the money, not extra sentinels -# rotate only with enough titanium left for follow-up work (Khaos used 60 so -# gunners can't burn the economy by spinning). -GUNNER_ROTATE_MIN_TITANIUM = 60 - -openCost = 1 -barrierCost = 8 # to be implemented later -# Khaos soft-threat cost class: stepping through an enemy turret envelope is -# legal but expensive, so paths detour unless there is no alternative. -THREAT_COST = 12 - - -tileTypes = { # limited to 4 types to maximize caching ability - Environment.EMPTY: 0, # Only counts if they are passable, or have a team barrier on them - Environment.ORE_TITANIUM: 1, # Only counts if they dont have a harvester on them - Environment.WALL: 2, - 'Unpassable': 3 # have an unpassable non team barrier building or are threat from turrets, -} -# shared array. 0: num bots spawned 1-6: new tiles from that bot 7: the map symmetry (nothing writes it yet) - - -class DialPathfinderAdapter: - """The small Pathfinder surface expected by Defender.""" +MAX_OPEN_BUILDERS = 5 +TTL_ROUNDS = 100 +CPU_SOFT_STOP = 6500 # stop scoring lower states past this many us - def __init__(self, player): - self.player = player - def moveTo(self, ct: Controller, target: Position): - self.player.moveTo(ct, target) +def manhattan(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) - def reset(self): - # defend.py wants a pf object, just hand it the new mover - pass class Player: def __init__(self): - self.defender = Defender() - self.pf = DialPathfinderAdapter(self) - - self.fullMap = None # bot's local version of the full map - self.newTiles = [] # shared publicly so all bots have info - self.seenTiles = None # used to get self.newTiles + self.mi = None + self.myNum = -1 self.numSpawned = 0 - self.myNum = -1 # every builder bot gets its own unique number used to write to the shared array, and organize roles (1, 2, 3, ...) self.teamCore = None - self.distMap = None - self._cacheTarget = None # dijkstra reuse: skip recompute on same target+map - self._cacheVer = -1 - self._threatTiles = set() # enemy turret soft-threat tiles for pathing - self._routeDist = None # Khaos route state: dist-to-core table - self._routeVer = -1 - self.mapW = None - self.mapH = None - self.stuckTurns = 0 - self.turretPlanner = None - self.turtled = False # opponent kills cores, we stop attacking - self.replacements = 0 # capped respawns so deaths cant chain-drain - self.lastReplaceRound = -999 - self.homeDefense = homedefense.HomeDefense() - - self.randDir = Direction.NORTH - self.idleTurns = 0 # sentinel: consecutive turns with no enemy seen - self.enemyBotHistory = {} # launcher: enemy builder position trails - - # Role - self.attackMode = False - self.alwaysDefense = False - self.defenseMode = False - self.justSpawned = True - - # Attacking - self.attackPos = None - self.spawnSentinelHere = None - - # Cores self.teamCoreTiles = None self.enemyCore = None - self.enemyCorePredicted = None # symmetry guess before we ever see it - - # Launcher proofing - self.oldPos = None - - # Harvester finding - self.currentHarvester = None - self.foundHarvester = None - # TTL failure cache (Khaos): ore we gave up on comes back after 100 - # rounds instead of being blacklisted forever. {Position: expiry_round} - self.usedHarvesters = {} - self.reachHarvesterTime = 0 - - # Building back and sentinel placement - self.conveyorEnd = None - self.surroundHarvester = False - self.protectTurns = 0 - self.sentinelSpot = None - self.isEnemyInfastructure = None - self.nextTurretCountDown = 0 - - # Placing a sentinel while extending economy - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - - # Exploring and symmetry attack hypotheses - self.explorePos = None - self.exploreTime = 0 - self.attackMoveTimeout = 0 - self.attackHypothesisIndex = 0 - self.symTracker = None # symmetry.SymmetryTracker, predicts enemy core - + self.symTracker = None + self.fullmap = None + self.fullmapVer = -1 + self.routeField = None + self.routeVer = -1 + self._connected = set() # own conveyors whose chain reaches the core + self._homeDisc = None # cached home-guard disc for builder #1 + self.brokenBarriers = [] # own barriers we broke while walking + self._plantCache = None # score-plane best placement cache + self.ttl = {} # (state, x, y) -> expiry round + self.stuck = 0 + self.lastPos = None + self.exploreTarget = None + # turret / launcher state + self.idleTurns = 0 + self.enemyBotHistory = {} + + # ------------------------------------------------------------------ def run(self, ct: Controller) -> None: etype = ct.get_entity_type() if etype == EntityType.CORE: - if self.mapW is None: - self.mapW = ct.get_map_width() - self.mapH = ct.get_map_height() - mapanalysis.reset_map(self.mapW, self.mapH) self.core(ct) elif etype == EntityType.BUILDER_BOT: - self.builderBot(ct) + self.builder(ct) elif etype == EntityType.SENTINEL: self.sentinel(ct) - elif etype == EntityType.LAUNCHER: - self.launcher(ct) elif etype == EntityType.GUNNER: self.gunner(ct) + elif etype == EntityType.LAUNCHER: + self.launcher(ct) + # ------------------------------------------------------------------ + # Core: fan-spawn opening, defensive spawn, ammo buffer + # ------------------------------------------------------------------ def core(self, ct: Controller) -> None: - homeEmergency = self.homeDefense.observe_from_core(ct) - - # replacement respawn: capped + cooldowned so a sentinel wall cant - # farm our titanium one dead builder at a time (string tiebreak: 41 - # dead builders = ~1200 ti gone) + import math + pos = ct.get_position() + if self.mi is None: + w, h = ct.get_map_width(), ct.get_map_height() + self.mi = mapinfo_mod.MapInfo(w, h) + mapanalysis.reset_map(w, h) rnd = ct.get_current_round() - wantReplace = (rnd >= 30 and self.replacements < REPLACE_CAP - and rnd - self.lastReplaceRound >= REPLACE_COOLDOWN - and ct.get_unit_count() <= 6 - and ct.get_global_resources() >= ct.get_builder_bot_cost() + 60) - # spawn first so the ammo topup doesnt eat the builder money - if spawnBots(self.numSpawned, ct) or wantReplace: - _isReplace = not spawnBots(self.numSpawned, ct) - corePos = ct.get_position() - threats = findThreats(ct) - - # collect every legal spawn tile on the core's adjacent ring + ti = ct.get_global_resources() + # Pantheon spawn policy: baseline 200 until 12 allies, then 400 — + # spawn whenever titanium clears baseline + the builder's own cost. + # (Our old 350/400*scale gates stalled workforce growth exactly + # during establishment.) + baseline = 400 if ct.get_unit_count() >= 12 else 200 + wantSpawn = ( + ti >= baseline + ct.get_builder_bot_cost() + or (rnd >= 30 and ct.get_unit_count() <= 6 + and ti >= ct.get_builder_bot_cost() + 60)) + if wantSpawn: candidates = [] for dx in (-1, 0, 1, 2): for dy in (-1, 0, 1, 2): if 0 <= dx <= 1 and 0 <= dy <= 1: - continue # skip the core's own 2x2 footprint - spawnPos = Position(corePos.x + dx, corePos.y + dy) - if not ct.can_spawn(spawnPos): continue - if any(spawnPos.distance_squared(threat) <= 2 - for threat in threats): + p = Position(pos.x + dx, pos.y + dy) + if ct.can_spawn(p): + candidates.append(p) + if candidates: + ccx, ccy = pos.x + 0.5, pos.y + 0.5 + pick = None + # defensive: intruder at the base with no ally on it + enemyPos, enemyDist, allies = None, 1e9, [] + for eid in ct.get_nearby_units(): + ep = ct.get_position(eid) + if ct.get_team(eid) == ct.get_team(): + if ct.get_entity_type(eid) == EntityType.BUILDER_BOT: + allies.append(ep) continue - candidates.append(spawnPos) - - spawnPos = self._pickSpawnTile(ct, corePos, candidates, _isReplace) - if spawnPos is not None: - ct.spawn_builder(spawnPos) + d = (ep.x - ccx) ** 2 + (ep.y - ccy) ** 2 + if d < enemyDist: + enemyDist, enemyPos = d, ep + if enemyPos is not None and enemyDist <= 18 and not any( + a.distance_squared(enemyPos) <= 8 for a in allies): + pick = min(candidates, key=lambda p: manhattan(p, enemyPos)) + elif self.numSpawned < 4: + w, h = ct.get_map_width(), ct.get_map_height() + primary = math.atan2(h / 2 - ccy, w / 2 - ccx) + target = primary + (-1.178, -0.393, 0.393, 1.178)[self.numSpawned] + tx, ty = math.cos(target), math.sin(target) + + def align(p): + vx, vy = p.x + 0.5 - ccx, p.y + 0.5 - ccy + norm = math.hypot(vx, vy) or 1.0 + return -(vx * tx + vy * ty) / norm + pick = min(candidates, key=align) + else: + pick = candidates[0] + ct.spawn_builder(pick) self.numSpawned += 1 - if _isReplace: - self.replacements += 1 - self.lastReplaceRound = rnd - # slot 0 also drives builder numbering + the share rotation ct.write_store(0, self.numSpawned) - - ammoBuffer = (homedefense.HOME_AMMO_BUFFER if homeEmergency - else GLOBAL_AMMO_BUFFER) - currentAmmo = ct.get_global_ammo() - if currentAmmo < ammoBuffer: - # keep enough for the next builder - spareTitanium = ( - ct.get_global_resources() - ct.get_builder_bot_cost() - ) - amount = min(ammoBuffer - currentAmmo, spareTitanium) + # distress: hurt core or enemy building in core vision -> broadcast on + # slot 7 so fanned-out builders converge to heal/counter-battery. + distress = ct.get_hp() < ct.get_max_hp() + if not distress: + for bid in ct.get_nearby_buildings(): + if ct.get_team(bid) != ct.get_team(): + distress = True + break + if distress: + ct.write_store(7, rnd) + # ammo buffer (raised while distressed so counter-turrets can fire) + buffer = 40 if distress else GLOBAL_AMMO_BUFFER + cur = ct.get_global_ammo() + if cur < buffer: + spare = ct.get_global_resources() - ct.get_builder_bot_cost() + amount = min(buffer - cur, spare) if amount > 0 and ct.can_convert_ammo(amount): ct.convert_ammo(amount) - def _pickSpawnTile(self, ct, corePos, candidates, isReplace): - """Choose which adjacent tile to spawn on (Khaos ray-spawn opening). - - Priority: (1) defensive spawn toward a visible enemy when we're thin, - (2) fan the first 4 builders across the interior-facing arc for ore - coverage, (3) fall back to the first legal tile. - """ - if not candidates: - return None - ccx, ccy = corePos.x + 0.5, corePos.y + 0.5 - - # (1) defensive: an enemy actually AT our base with no ally covering it - # -> spawn toward the intruder (Khaos). Tight gate: on small maps the - # core sees most of the board, and reacting to every distant scout - # hijacks the whole opening (sprint 0-mined regression). - enemyPos = None - enemyDist = 1e9 - allies = [] - for eid in ct.get_nearby_units(): - ep = ct.get_position(eid) - if ct.get_team(eid) == ct.get_team(): - if ct.get_entity_type(eid) == EntityType.BUILDER_BOT: - allies.append(ep) - continue - d = (ep.x - ccx) ** 2 + (ep.y - ccy) ** 2 - if d < enemyDist: - enemyDist = d - enemyPos = ep - if enemyPos is not None and enemyDist <= 18: - covered = any(a.distance_squared(enemyPos) <= 8 for a in allies) - if not covered: - return min(candidates, - key=lambda p: self.manhattan(p, enemyPos)) + # ------------------------------------------------------------------ + # Builder: the memoryless spine + # ------------------------------------------------------------------ + def builder(self, ct: Controller) -> None: + mi = self._setup(ct) + mi.update_vision(ct) + mi.absorb(ct) + self._updateCores(ct) + self._updateSymmetry(ct) - # (2) fan the first four builders outward (skip for replacements) - if not isReplace and self.numSpawned < 4: + rnd = ct.get_current_round() + pos = ct.get_position() + if self.lastPos == pos: + self.stuck += 1 + else: + self.stuck = 0 + self.lastPos = pos + + # ttl sweep + if rnd % 16 == 0: + self.ttl = {k: v for k, v in self.ttl.items() if v > rnd} + + # claims context: my bit vs other friendly bots + my_bit = mi.bit(pos.x, pos.y) + others = mi.own_bots & ~my_bit + + states = ( + (9.0, self._score_attack, self._run_attack), + (8.0, self._score_heal, self._run_heal), + (7.75, self._score_route, self._run_route), + (7.5, self._score_secure, self._run_secure), + (4.0, self._score_harvest, self._run_harvest), + (2.0, self._score_disrupt, self._run_disrupt), + (1.0, self._score_explore, self._run_explore), + ) + best, bestScore, bestData = None, 0.0, None + for ceil, scorefn, runfn in states: + if bestScore >= ceil: + break + if ct.get_cpu_time_elapsed() > CPU_SOFT_STOP and best is not None: + break + s, data = scorefn(ct, mi, my_bit, others) + if s > bestScore: + best, bestScore, bestData = runfn, s, data + if best is not None: + best(ct, mi, bestData) + + # passive extras with leftover action/cpu + self._rebuildBroken(ct, mi) + self._tryChokeBlock(ct, mi) + self._healAdjacent(ct) + mi.share(ct, self.myNum, self.teamCore) + self._advanceAnalysis(ct) + + # ---- setup helpers ---- + def _setup(self, ct): + if self.mi is None: w, h = ct.get_map_width(), ct.get_map_height() - primary = math.atan2(h / 2 - ccy, w / 2 - ccx) - offsets = (-1.178, -0.393, 0.393, 1.178) # ~ -67.5..67.5 deg - target = primary + offsets[self.numSpawned] - tx, ty = math.cos(target), math.sin(target) + self.mi = mapinfo_mod.MapInfo(w, h) + mapanalysis.configure_map(w, h) + self.mi.on_block_change = ( + lambda x, y, blocking: mapanalysis.note_structural_tile( + x, y, blocking, w, h)) + if self.myNum == -1: + self.myNum = ct.read_store(0) + 1 + return self.mi - def align(p): - vx, vy = p.x + 0.5 - ccx, p.y + 0.5 - ccy - norm = math.hypot(vx, vy) or 1.0 - return -(vx * tx + vy * ty) / norm # smaller = better aligned - return min(candidates, key=align) + def _updateCores(self, ct): + if self.teamCore is None: + for bid in ct.get_nearby_buildings(): + if (ct.get_entity_type(bid) == EntityType.CORE + and ct.get_team(bid) == ct.get_team()): + self.teamCore = ct.get_position(bid) + if self.enemyCore is None: + for bid in ct.get_nearby_buildings(): + if (ct.get_entity_type(bid) == EntityType.CORE + and ct.get_team(bid) != ct.get_team()): + self.enemyCore = ct.get_position(bid) - # (3) default: first legal tile (preserves old behaviour) - return candidates[0] + def _updateSymmetry(self, ct): + mi = self.mi + if self.teamCore is None: + return + if self.symTracker is None: + self.symTracker = symmetry.SymmetryTracker(mi.w, mi.h) + if self.fullmapVer != mi.struct_version or self.fullmap is None: + fm = [[-1] * mi.h for _ in range(mi.w)] + for x in range(mi.w): + col = fm[x] + for y in range(mi.h): + b = 1 << (x + y * mi.w) + if not (mi.seen & b): + continue + if mi.walls & b: + col[y] = 2 + elif mi.blocked & b: + col[y] = 3 + elif mi.ore & b: + col[y] = 1 + else: + col[y] = 0 + self.fullmap = fm + self.fullmapVer = mi.struct_version + self.symTracker.update(self.fullmap, mi.struct_version, self.teamCore) - def gunner(self, ct: Controller) -> None: - target = ct.get_gunner_target() - if target is not None: - builder_id = ct.get_tile_builder_bot_id(target) - building_id = ct.get_tile_building_id(target) - target_id = builder_id if builder_id is not None else building_id - if (target_id is not None - and ct.get_team(target_id) != ct.get_team()): - if ct.can_fire(target): - ct.fire(target) + def _enemyCoreGuess(self): + if self.enemyCore is not None: + return self.enemyCore + if self.symTracker is not None and self.teamCore is not None: + cx, cy = self.symTracker.enemy_core(self.teamCore) + if 0 <= cx < self.mi.w - 1 and 0 <= cy < self.mi.h - 1: + return Position(cx, cy) + return None + + def _homeMask(self): + """Tiles within squared distance 100 of the core (Pantheon: the very + first builder restricts all its targets to this disc = home guard).""" + mi = self.mi + if self._homeDisc is not None or self.teamCore is None: + return self._homeDisc or 0 + cx, cy = self.teamCore.x, self.teamCore.y + m = 0 + for x in range(max(0, cx - 10), min(mi.w, cx + 11)): + for y in range(max(0, cy - 10), min(mi.h, cy + 11)): + if (x - cx) ** 2 + (y - cy) ** 2 <= 100: + m |= mi.bit(x, y) + self._homeDisc = m + return m + + def _coreMask(self): + mi = self.mi + if self.teamCore is None: + return 0 + m = 0 + for dx in (0, 1): + for dy in (0, 1): + x, y = self.teamCore.x + dx, self.teamCore.y + dy + if 0 <= x < mi.w and 0 <= y < mi.h: + m |= mi.bit(x, y) + return m + + # ---- movement ---- + def _moveToward(self, ct, targets_mask, avoid=0): + if ct.get_move_cooldown() != 0: + return + mi = self.mi + pos = ct.get_position() + step = path.next_step(mi, (pos.x, pos.y), targets_mask, avoid) + if step is not None: + d = DELTA_TO_DIR[step] + dest = pos.add(d) + # break-walk own barriers (Pantheon): destroy is free, remember + # the tile so we rebuild it once we've stepped off. + if (self._inBounds(dest) + and mi.own_barriers & mi.bit(dest.x, dest.y) + and not ct.can_move(d) + and ct.is_in_vision(dest) and ct.can_destroy(dest)): + ct.destroy(dest) + mi.own_barriers &= ~mi.bit(dest.x, dest.y) + mi.note_tile(dest.x, dest.y, mapinfo_mod.T_EMPTY) + if len(self.brokenBarriers) < 4: + self.brokenBarriers.append(dest) + if ct.can_move(d): + ct.move(d) + self.stuck = 0 return + self.stuck += 1 + if self.stuck > 2 + (ct.get_id() % 8): + dirs = [d for d in CARDINALS if ct.can_move(d)] + if dirs: + ct.move(random.choice(dirs)) + + # ------------------------------------------------------------------ + # STATE: attack (9) — plant turrets on enemy buildings / push their core + # ------------------------------------------------------------------ + def _score_attack(self, ct, mi, my_bit, others): + rnd = ct.get_current_round() + ti = ct.get_global_resources() + if ti < ct.get_gunner_cost() + 40: + return 0.0, None + # DEFENSIVE counter-battery: enemy building near OUR core — every + # builder may respond (no role gate), claims still deconflict. + if self.teamCore is not None: + home = 0 + for (x, y) in mi.enemy_buildings: + if self.ttl.get(('atk', x, y), 0) > rnd: + continue + if abs(x - self.teamCore.x) + abs(y - self.teamCore.y) <= 10: + home |= mi.bit(x, y) + if home: + mine = path.claims(mi, my_bit, others, home) + if mine: + return 9.0, ('bld', mine) + if rnd < 30 or self.myNum % 3 != 0 or self.myNum == 1: + return 0.0, None # builder #1 is the home guard, never sieges + guess = self._enemyCoreGuess() + # bit-sliced plane placement (Pantheon): score EVERY legal gunner + # placement board-wide, take the best above threshold. + plant = self._bestPlant(ct, mi) + if plant is not None: + score, px, py, fk = plant + # geographic money gate on the PLACEMENT tile + if self.teamCore is not None and guess is not None: + dHome = abs(px - self.teamCore.x) + abs(py - self.teamCore.y) + dEnemy = abs(px - guess.x) + abs(py - guess.y) + deep = dHome > dEnemy + else: + deep = False + if not deep or ti >= 150: + mine = path.claims(mi, my_bit, others, mi.bit(px, py)) + if mine: + return 9.0, ('plant', (px, py, fk)) + # fallback: walk at remembered enemy buildings (old greedy pathway) + cands = 0 + for (x, y) in mi.enemy_buildings: + key = ('atk', x, y) + if self.ttl.get(key, 0) > rnd: + continue + if self.teamCore is not None and guess is not None: + dHome = abs(x - self.teamCore.x) + abs(y - self.teamCore.y) + dEnemy = abs(x - guess.x) + abs(y - guess.y) + onOurHalf = dHome <= dEnemy + else: + onOurHalf = True + if onOurHalf or ti >= 150: + cands |= mi.bit(x, y) + if cands: + mine = path.claims(mi, my_bit, others, cands) + if mine: + return 8.9, ('bld', mine) + return 0.0, None + if guess is not None: + return 6.0, ('core', guess) + return 0.0, None + + PLANT_VALUES = None # built lazily: EntityType -> placement value + + def _bestPlant(self, ct, mi): + """Best gunner placement from the score planes, cached per + (struct, enemy-set). Threshold 20 = better than a lone barrier.""" + if Player.PLANT_VALUES is None: + Player.PLANT_VALUES = { + EntityType.CORE: 60, EntityType.GUNNER: 40, + EntityType.SENTINEL: 40, EntityType.LAUNCHER: 20, + EntityType.CONVEYOR: 15, EntityType.SPLITTER: 18, + EntityType.HARVESTER: 8, EntityType.BARRIER: 5, + } + key = (mi.struct_version, + tuple(sorted((k, str(v)) + for k, v in mi.enemy_buildings.items()))) + if self._plantCache is not None and self._plantCache[0] == key: + return self._plantCache[1] + by_value = {} + for (x, y), etype in mi.enemy_buildings.items(): + v = Player.PLANT_VALUES.get(etype, 5) + by_value.setdefault(v, [0])[0] = by_value.get(v, [0])[0] \ + | mi.bit(x, y) + class_masks = [(v, m[0]) for v, m in by_value.items()] + valid = (mi.seen & ~mi.walls & ~mi.blocked & ~mi.ore + & ~mi.own_conveyors & ~mi.own_barriers + & ~mi.expand(mi.expand(mi.enemy_bots))) + best = None + if class_masks and valid: + best = planes.best_placement(mi, class_masks, valid) + if best is not None and best[0] < 20: + best = None + self._plantCache = (key, best) + return best - if (ct.get_global_resources() < GUNNER_ROTATE_MIN_TITANIUM - or ct.get_action_cooldown() != 0): + def _run_attack(self, ct, mi, data): + kind, payload = data + pos = ct.get_position() + rnd = ct.get_current_round() + if kind == 'core': + m = mi.bit(payload.x, payload.y) + self._moveToward(ct, mi.expand(mi.expand(m))) + return + if kind == 'plant': + px, py, fk = payload + tile = Position(px, py) + facing = {'E': Direction.EAST, 'W': Direction.WEST, + 'N': Direction.NORTH, 'S': Direction.SOUTH}[fk] + tb = mi.bit(px, py) + if pos == tile: + # standing on the placement: step aside + self._moveToward(ct, mi.expand(tb) & mi.passable() & ~tb) + return + if pos.distance_squared(tile) <= 2 \ + and ct.get_action_cooldown() == 0: + if ct.is_in_vision(tile) and ct.can_build_gunner(tile, facing): + ct.build_gunner(tile, facing) + self._plantCache = None + return + self.ttl[('atk', px, py)] = rnd + 20 + self._plantCache = None + return + self._moveToward(ct, mi.expand(tb) & mi.passable()) + return + targets = payload + tb = targets & -targets + tx, ty = mi.xy(tb) + tpos = Position(tx, ty) + dist = manhattan(pos, tpos) + if dist <= 3 and ct.get_action_cooldown() == 0: + # place a turret on an adjacent tile with line to the target + facing = self._cardinalTo(pos, tpos) + placed = False + for d in CARDINALS: + p = pos.add(d) + if not self._inBounds(p) or not ct.is_in_vision(p): + continue + f = self._cardinalTo(p, tpos) + if f == Direction.CENTRE: + continue + if ct.can_build_gunner(p, f): + ct.build_gunner(p, f) + placed = True + break + if (ct.get_global_resources() >= ct.get_sentinel_cost() + 40 + and ct.can_build_sentinel(p, f)): + ct.build_sentinel(p, f) + placed = True + break + if placed: + self.ttl[('atk', tx, ty)] = rnd + 40 + return + if facing == Direction.CENTRE: + self.ttl[('atk', tx, ty)] = rnd + TTL_ROUNDS + return + if dist <= 1: + # cannot place and already adjacent: back off this target a while + self.ttl[('atk', tx, ty)] = rnd + 30 + self._moveToward(ct, mi.expand(targets) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: heal (8) — repair damaged own buildings, deter intruders + # ------------------------------------------------------------------ + def _score_heal(self, ct, mi, my_bit, others): + damaged = 0 + # core distress broadcast: converge on the core even when it's out of + # our own vision — 2 medics out-heal a sentinel's grind (8 vs 6/round). + distress = ct.read_store(7) + if (distress > 0 and ct.get_current_round() - distress < 12 + and self.teamCore is not None): + damaged |= self._coreMask() + for bid in ct.get_nearby_buildings(): + if ct.get_team(bid) != ct.get_team(): + continue + if ct.get_hp(bid) + 2 < ct.get_max_hp(bid): + p = ct.get_position(bid) + damaged |= mi.bit(p.x, p.y) + if damaged: + mine = path.claims(mi, my_bit, others, damaged) + if mine: + return 8.0, ('heal', mine) + intruders = mi.enemy_bots & mi.expand(mi.expand(mi.own_conveyors)) + if intruders: + mine = path.claims(mi, my_bit, others, intruders) + if mine: + return 7.9, ('chase', mine) + return 0.0, None + + def _run_heal(self, ct, mi, data): + kind, mask = data + pos = ct.get_position() + tb = mask & -mask + tx, ty = mi.xy(tb) + tpos = Position(tx, ty) + if kind == 'heal': + if pos.distance_squared(tpos) <= 2: + if ct.can_heal(tpos): + ct.heal(tpos) + return + self._moveToward(ct, mi.expand(tb) & mi.passable()) + return + # chase: stand next to the intruder; the launcher/turret net does the + # rest, and our body denies conveyor-chip tiles + if pos.distance_squared(tpos) <= 2: + if ct.get_action_cooldown() == 0: + for d in CARDINALS: + p = pos.add(d) + if (self._inBounds(p) and ct.is_in_vision(p) + and manhattan(p, tpos) <= 1 + and ct.can_build_launcher(p) + and ct.get_global_resources() + >= ct.get_launcher_cost() + 60): + ct.build_launcher(p) + return return + self._moveToward(ct, mi.expand(tb) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: route (7.75) — stateless chain building down the core gradient + # ------------------------------------------------------------------ + def _connectedConveyors(self, mi): + """Own conveyors whose chain (by facing) reaches the core — legal + merge targets for new routes (Pantheon: routes may end on existing + conveyors). Fixpoint over remembered facings.""" + coreMask = self._coreMask() + core_xy = set() + m = coreMask + while m: + lsb = m & -m + core_xy.add(mi.xy(lsb)) + m ^= lsb + connected = set() + changed = True + while changed: + changed = False + for (x, y), (dx, dy) in mi.own_conv_facing.items(): + if (x, y) in connected: + continue + out = (x + dx, y + dy) + if out in core_xy or out in connected: + connected.add((x, y)) + changed = True + return connected + + def _routeFieldCached(self, ct): + mi = self.mi + key = (mi.struct_version, len(mi.own_conv_facing)) + if self.routeField is not None and self.routeVer == key: + return self.routeField + coreMask = self._coreMask() + if not coreMask: + return None + # Pantheon merge targets: the core PLUS every conveyor already + # connected to it — new chains join existing trunks instead of each + # harvester paying for a private line the whole way home. + self._connected = self._connectedConveyors(mi) + targets = coreMask + for (x, y) in self._connected: + targets |= mi.bit(x, y) + self.routeField = path.dist_field(mi, targets) + self.routeVer = key + return self.routeField - my_pos = ct.get_position() - current_facing = ct.get_direction() - candidates = [] - for entity_id in ct.get_nearby_entities(13): - entity_type = ct.get_entity_type(entity_id) - if entity_type not in ( - EntityType.GUNNER, EntityType.SENTINEL, - EntityType.BUILDER_BOT): + @staticmethod + def _chainRoot(mi, hx, hy): + """Adjacent own conveyor whose output is NOT the harvester — a real + chain root. Inward-facing guards don't count (Khaos guard mask).""" + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + cx, cy = hx + dx, hy + dy + f = mi.own_conv_facing.get((cx, cy)) + if f is None: continue - if ct.get_team(entity_id) == ct.get_team(): + if (cx + f[0], cy + f[1]) != (hx, hy): + return (cx, cy) + return None + + def _downhillLink(self, mi, field, x, y): + """Best empty neighbour to lay the next conveyor on, plus the facing + that conveyor should have (toward ITS best downhill neighbour).""" + best = None + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + nx, ny = x + dx, y + dy + if not (0 <= nx < mi.w and 0 <= ny < mi.h): continue - enemy_pos = ct.get_position(entity_id) - dx = enemy_pos.x - my_pos.x - dy = enemy_pos.y - my_pos.y - if dx != 0 and dy != 0: + b = mi.bit(nx, ny) + if (mi.walls | mi.blocked | mi.own_conveyors) & b: continue - if dx > 0: - facing = Direction.EAST - elif dx < 0: - facing = Direction.WEST - elif dy > 0: - facing = Direction.SOUTH - elif dy < 0: - facing = Direction.NORTH + d = field[nx + ny * mi.w] + if best is None or d < best[0]: + best = (d, nx, ny) + if best is None or best[0] >= 4096: + return None, None + _, lx, ly = best + fbest = None + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + fx, fy = lx + dx, ly + dy + if not (0 <= fx < mi.w and 0 <= fy < mi.h): + continue + fd = field[fx + fy * mi.w] + if fbest is None or fd < fbest[0]: + fbest = (fd, dx, dy) + if fbest is None: + return None, None + return Position(lx, ly), DELTA_TO_DIR[(fbest[1], fbest[2])] + + def _orphanHarvesters(self, ct, mi): + rnd = ct.get_current_round() + orphans = 0 + for (x, y) in mi.own_harvesters: + key = ('route', x, y) + if self.ttl.get(key, 0) > rnd: + continue + if self._chainRoot(mi, x, y) is None: + orphans |= mi.bit(x, y) + return orphans + + def _deadEnds(self, ct, mi): + """Own conveyors whose output tile has no continuation (Khaos + dead-end route targets). Guards (facing into a harvester) excluded.""" + rnd = ct.get_current_round() + coreMask = self._coreMask() + ends = 0 + for (x, y), (dx, dy) in mi.own_conv_facing.items(): + if self.ttl.get(('route', x, y), 0) > rnd: + continue + ox, oy = x + dx, y + dy + if (ox, oy) in mi.own_harvesters: + continue # inward guard, carries nothing + if not (0 <= ox < mi.w and 0 <= oy < mi.h): + ends |= mi.bit(x, y) + continue + ob = mi.bit(ox, oy) + if ob & coreMask: + continue # delivers into the core: chain complete + if mi.own_conveyors & ob: + continue # continues into another conveyor/splitter + ends |= mi.bit(x, y) + return ends + + def _score_route(self, ct, mi, my_bit, others): + if self.teamCore is None: + return 0.0, None + cands = self._orphanHarvesters(ct, mi) | self._deadEnds(ct, mi) + if not cands: + return 0.0, None + mine = path.claims(mi, my_bit, others, cands) + if not mine: + return 0.0, None + near_enemy = mi.expand(mi.expand(mi.enemy_bots)) + important = mine & near_enemy + return (7.75 if important else 5.0), mine + + def _run_route(self, ct, mi, mask): + field = self._routeFieldCached(ct) + rnd = ct.get_current_round() + hb = mask & -mask + hx, hy = mi.xy(hb) + if field is None: + return + coreMask = self._coreMask() + link = facing = None + if (hx, hy) in mi.own_conv_facing: + root = (hx, hy) # dead-end conveyor target: walk from it + else: + root = self._chainRoot(mi, hx, hy) + if root is None: + link, facing = self._downhillLink(mi, field, hx, hy) + else: + # follow the actual chain by facings until it breaks / reaches core + cur = root + visited = set() + for _ in range(mi.w + mi.h): + if cur in visited: + self.ttl[('route', hx, hy)] = rnd + 30 + return + visited.add(cur) + f = mi.own_conv_facing.get(cur) + if f is None: + self.ttl[('route', hx, hy)] = rnd + 15 + return + ox, oy = cur[0] + f[0], cur[1] + f[1] + if not (0 <= ox < mi.w and 0 <= oy < mi.h): + link, facing = self._downhillLink(mi, field, + cur[0], cur[1]) + break + ob = mi.bit(ox, oy) + if ob & coreMask or (ox, oy) in self._connected: + self.ttl[('route', hx, hy)] = rnd + 30 # chain complete + return + if (ox, oy) in mi.own_conv_facing: + cur = (ox, oy) + continue + if mi.own_conveyors & ob: + # splitter / facing not yet observed: assume connected + self.ttl[('route', hx, hy)] = rnd + 15 + return + if (mi.walls | mi.blocked) & ob: + link, facing = self._downhillLink(mi, field, + cur[0], cur[1]) + break + # empty output tile: that's the missing link + link = Position(ox, oy) + fbest = None + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + fx, fy = ox + dx, oy + dy + if not (0 <= fx < mi.w and 0 <= fy < mi.h): + continue + fd = field[fx + fy * mi.w] + if fbest is None or fd < fbest[0]: + fbest = (fd, dx, dy) + if fbest is None: + return + facing = DELTA_TO_DIR[(fbest[1], fbest[2])] + break + if link is None: + self.ttl[('route', hx, hy)] = rnd + 20 + return + pos = ct.get_position() + lb = mi.bit(link.x, link.y) + if pos == link: + # standing on the link tile: step aside so we can build it + self._moveToward(ct, mi.expand(lb) & mi.passable() & ~lb) + return + if pos.distance_squared(link) <= 2 and ct.get_action_cooldown() == 0: + if ct.is_in_vision(link) and ct.can_build_conveyor(link, facing): + ct.build_conveyor(link, facing) + mi.own_conveyors |= lb + mi.own_conv_facing[(link.x, link.y)] = ( + mapinfo_mod.DIR_DELTA[facing]) + return + self.ttl[('route', hx, hy)] = rnd + 10 + return + self._moveToward(ct, mi.expand(lb) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: secure (7.5) — guard-conveyor exposed harvesters near enemies + # ------------------------------------------------------------------ + def _score_secure(self, ct, mi, my_bit, others): + if not mi.enemy_buildings and not mi.enemy_bots: + return 0.0, None + danger = 0 + for (x, y) in mi.enemy_buildings: + danger |= mi.bit(x, y) + danger = mi.expand(mi.expand(mi.expand(danger))) | \ + mi.expand(mi.expand(mi.enemy_bots)) + exposed = 0 + for (x, y) in mi.own_harvesters: + b = mi.bit(x, y) + if not (danger & mi.expand(b)): + continue + open_sides = mi.expand(b) & ~b & mi.passable() & ~mi.own_conveyors + if open_sides: + exposed |= open_sides + if not exposed: + return 0.0, None + mine = path.claims(mi, my_bit, others, exposed) + if not mine: + return 0.0, None + return 7.5, mine + + def _run_secure(self, ct, mi, mask): + pos = ct.get_position() + tb = mask & -mask + tx, ty = mi.xy(tb) + tpos = Position(tx, ty) + if pos.distance_squared(tpos) <= 2 and ct.get_action_cooldown() == 0: + if ct.is_in_vision(tpos): + # find the harvester this side belongs to + harv = None + for d in CARDINALS: + hp = tpos.add(d) + if (self._inBounds(hp) + and (hp.x, hp.y) in mi.own_harvesters): + harv = (hp, d) + break + if harv is None: + return + hp, inward = harv + # Khaos: if this side is the harvester's downhill side, face + # OUTWARD so the guard doubles as the chain root; otherwise + # face INTO the harvester so it never carries resources. + facing = inward + field = self._routeFieldCached(ct) + if field is not None: + dlink, dfacing = self._downhillLink(mi, field, hp.x, hp.y) + if dlink is not None and dlink == tpos: + facing = dfacing + if ct.can_build_conveyor(tpos, facing): + ct.build_conveyor(tpos, facing) + mi.own_conveyors |= mi.bit(tpos.x, tpos.y) + mi.own_conv_facing[(tpos.x, tpos.y)] = ( + mapinfo_mod.DIR_DELTA[facing]) + return + return + self._moveToward(ct, mi.expand(tb) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: harvest (4) + # ------------------------------------------------------------------ + def _score_harvest(self, ct, mi, my_bit, others): + rnd = ct.get_current_round() + # exclude only ore in the CLOSE disc of an enemy turret. The full + # facing-agnostic band mask vetoed most of the ore field on turret-y + # maps (quarry: 5-10k mined vs botv2's 18k) — band-edge mining is + # viable with heal upkeep (v29 lesson). + hot = 0 + for (x, y) in mi.enemy_turrets: + hot |= mi.bit(x, y) + hot = mi.expand(mi.expand(hot)) + cands = mi.ore & mi.seen & ~mi.blocked & ~mi.walls & ~hot + pruned = 0 + for lsb in mi.iter_bits(cands): + x, y = mi.xy(lsb) + if (x, y) in mi.own_harvesters: + continue + if self.ttl.get(('harv', x, y), 0) > rnd: + continue + pruned |= lsb + if self.myNum == 1: + pruned &= self._homeMask() # home guard mines home ore only + if not pruned: + return 0.0, None + mine = path.claims(mi, my_bit, others, pruned) + if not mine: + return 0.0, None + if ct.get_global_resources() < ct.get_harvester_cost(): + return 0.5, mine + return 4.0, mine + + def _run_harvest(self, ct, mi, mask): + pos = ct.get_position() + target = path.closest(mi, mi.bit(pos.x, pos.y), mask) + if not target: + target = mask & -mask + tx, ty = mi.xy(target) + tpos = Position(tx, ty) + rnd = ct.get_current_round() + if pos == tpos: + # standing on the ore: sidestep so we can build it + self._moveToward(ct, mi.expand(target) & mi.passable() & ~target) + return + if pos.distance_squared(tpos) <= 2 and ct.get_action_cooldown() == 0: + if ct.is_in_vision(tpos) and ct.can_build_harvester(tpos): + ct.build_harvester(tpos) + mi.own_harvesters[(tx, ty)] = True + return + self.ttl[('harv', tx, ty)] = rnd + 25 + return + self._moveToward(ct, mi.expand(target) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: disrupt (2) — barrier enemy-half ore when rich + # ------------------------------------------------------------------ + def _score_disrupt(self, ct, mi, my_bit, others): + if self.myNum == 1: + return 0.0, None # home guard stays home + if ct.get_global_resources() < 5 * ct.get_harvester_cost(): + return 0.0, None + guess = self._enemyCoreGuess() + if guess is None or self.teamCore is None: + return 0.0, None + rnd = ct.get_current_round() + cands = 0 + for lsb in mi.iter_bits(mi.ore & mi.seen & ~mi.blocked + & ~mi.threat()): + x, y = mi.xy(lsb) + p = Position(x, y) + if manhattan(p, guess) < manhattan(p, self.teamCore) - 2: + if self.ttl.get(('dis', x, y), 0) <= rnd: + cands |= lsb + if not cands: + return 0.0, None + mine = path.claims(mi, my_bit, others, cands) + if not mine: + return 0.0, None + return 2.0, mine + + def _run_disrupt(self, ct, mi, mask): + pos = ct.get_position() + tb = mask & -mask + tx, ty = mi.xy(tb) + tpos = Position(tx, ty) + rnd = ct.get_current_round() + if pos.distance_squared(tpos) <= 2 and pos != tpos \ + and ct.get_action_cooldown() == 0: + if ct.is_in_vision(tpos) and ct.can_build_barrier(tpos): + ct.build_barrier(tpos) + self.ttl[('dis', tx, ty)] = rnd + 1000 + return + self.ttl[('dis', tx, ty)] = rnd + 50 + return + if pos == tpos: + self._moveToward(ct, mi.expand(tb) & mi.passable() & ~tb) + return + self._moveToward(ct, mi.expand(tb) & mi.passable()) + + # ------------------------------------------------------------------ + # STATE: explore (1) + # ------------------------------------------------------------------ + def _score_explore(self, ct, mi, my_bit, others): + return 1.0, None + + def _run_explore(self, ct, mi, _): + pos = ct.get_position() + if (self.exploreTarget is not None + and manhattan(pos, self.exploreTarget) <= 2): + self.exploreTarget = None + if self.exploreTarget is None or self.stuck > 4: + frontier = mi.frontier() & mi.passable() + if self.myNum == 1: + frontier &= self._homeMask() # guard patrols home only + picked = None + if frontier: + bits = list(mi.iter_bits(frontier)) + picked = random.choice(bits[:64]) + if picked: + x, y = mi.xy(picked) + self.exploreTarget = Position(x, y) + elif self.myNum == 1 and self.teamCore is not None: + self.exploreTarget = Position( + max(0, min(mi.w - 1, + self.teamCore.x + random.randint(-4, 4))), + max(0, min(mi.h - 1, + self.teamCore.y + random.randint(-4, 4)))) else: + self.exploreTarget = Position(random.randrange(mi.w), + random.randrange(mi.h)) + t = self.exploreTarget + self._moveToward(ct, mi.bit(t.x, t.y)) + + # ------------------------------------------------------------------ + # passive extras + # ------------------------------------------------------------------ + def _rebuildBroken(self, ct, mi): + """Replace own barriers we destroyed to walk through (Pantheon).""" + if not self.brokenBarriers or ct.get_action_cooldown() != 0: + return + pos = ct.get_position() + for p in list(self.brokenBarriers): + if pos == p: + continue # still standing on it; rebuild after stepping off + if pos.distance_squared(p) > 2: + self.brokenBarriers.remove(p) # walked away: let it go + continue + if ct.is_in_vision(p) and ct.can_build_barrier(p): + ct.build_barrier(p) + mi.own_barriers |= mi.bit(p.x, p.y) + mi.note_tile(p.x, p.y, mapinfo_mod.T_BLOCK) + self.brokenBarriers.remove(p) + return + + def _tryChokeBlock(self, ct, mi): + if ct.get_action_cooldown() != 0 or not mapanalysis.chokes \ + or self.teamCore is None: + return + pos = ct.get_position() + enemyRef = self._enemyCoreGuess() + ti = ct.get_global_resources() + for choke in mapanalysis.chokes: + tile, clearance = choke[0], choke[1] + p = Position(tile[0], tile[1]) + if pos.distance_squared(p) > 2: + continue + if (enemyRef is not None + and manhattan(p, self.teamCore) >= manhattan(p, enemyRef)): + continue + if not self._inBounds(p) or not ct.is_in_vision(p): + continue + if ct.get_tile_building_id(p) is not None: continue - if facing == current_facing: + if ct.get_tile_env(p) == Environment.ORE_TITANIUM: continue - priority = 0 if entity_type in ( - EntityType.GUNNER, EntityType.SENTINEL - ) else 1 - candidates.append((priority, my_pos.distance_squared(enemy_pos), - CARDINALS.index(facing), facing)) + if clearance <= 1.2: + if ti >= ct.get_barrier_cost() + 30 and ct.can_build_barrier(p): + ct.build_barrier(p) + return + elif ti >= 100 and ct.can_build_launcher(p): + ct.build_launcher(p) + return - if candidates: - facing = min(candidates)[3] - if ct.can_rotate(facing): - ct.rotate(facing) + def _healAdjacent(self, ct): + if ct.get_action_cooldown() != 0: + return + pos = ct.get_position() + if ct.get_hp() < ct.get_max_hp() and ct.can_heal(pos): + ct.heal(pos) + return + best = None + for bid in ct.get_nearby_buildings(2): + if ct.get_team(bid) != ct.get_team(): + continue + dmg = ct.get_max_hp(bid) - ct.get_hp(bid) + if dmg >= 4: + p = ct.get_position(bid) + if best is None or dmg > best[0]: + best = (dmg, p) + if best is not None and ct.can_heal(best[1]): + ct.heal(best[1]) + + def _advanceAnalysis(self, ct): + job = mapanalysis.job + if job is None and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US: + job = mapanalysis.get_job() + while (job is not None + and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US + and job.phase != mapanalysis.DONE): + job.step() + # ------------------------------------------------------------------ + # Turrets (ported from botv2 — proven handlers) + # ------------------------------------------------------------------ def sentinel(self, ct: Controller) -> None: - if self.enemyCore is None: - self.enemyCore = findEnemyCore(ct) if self.teamCore is None: - self.teamCore = findTeamCore(ct) - + for bid in ct.get_nearby_buildings(): + if (ct.get_entity_type(bid) == EntityType.CORE + and ct.get_team(bid) == ct.get_team()): + self.teamCore = ct.get_position(bid) team = ct.get_team() - - # Khaos: an idle FORWARD sentinel is +20% scale of dead weight; the - # scale refunds on death, so self-destruct after a long quiet spell. - # Home guards (near core) and harvester rings are exempt. seesEnemy = False for eid in ct.get_nearby_entities(): if ct.get_team(eid) != team: @@ -336,77 +1064,98 @@ def sentinel(self, ct: Controller) -> None: and ct.get_entity_type(b) == EntityType.HARVESTER for b in ct.get_nearby_buildings(8)) nearCore = (self.teamCore is not None - and self.manhattan(ct.get_position(), - self.teamCore) <= 6) + and manhattan(ct.get_position(), + self.teamCore) <= 6) if not nearHarv and not nearCore: ct.self_destruct() return priority = { - EntityType.CORE: 5, - EntityType.SENTINEL: 3, - EntityType.GUNNER: 3, - EntityType.BARRIER: 2, - EntityType.LAUNCHER: 2, + EntityType.CORE: 5, EntityType.SENTINEL: 3, EntityType.GUNNER: 3, + EntityType.BARRIER: 2, EntityType.LAUNCHER: 2, EntityType.CONVEYOR: 1, } - # no HARVESTER here on purpose, stealing them is better - targets = { - EntityType.CORE, - EntityType.GUNNER, - EntityType.SENTINEL, - EntityType.BARRIER, - EntityType.LAUNCHER, - EntityType.CONVEYOR, - } - - bestTile = None - bestPriority = 0 + targets = set(priority) + bestTile, bestKey = None, None for tile in ct.get_attackable_tiles(): - if not self.isInBounds(ct, tile) or not ct.is_in_vision(tile): + if not self._inBounds(tile) or not ct.is_in_vision(tile): continue buildingID = ct.get_tile_building_id(tile) builderID = ct.get_tile_builder_bot_id(tile) - if builderID is not None and ct.get_team(builderID) == team: continue if buildingID is not None and ct.get_team(buildingID) == team: continue - - tilePriority = 0 - targetHp = 0 + tilePriority, targetHp = 0, 0 if buildingID is not None: - buildingType = ct.get_entity_type(buildingID) - if buildingType not in targets: + btype = ct.get_entity_type(buildingID) + if btype not in targets: continue - tilePriority = priority.get(buildingType, 1) + tilePriority = priority.get(btype, 1) targetHp = ct.get_hp(buildingID) if builderID is not None: tilePriority += 2 elif builderID is not None: - # not worth 10 ammo on a lone builder continue - - # Khaos tiebreaks: same priority -> prefer a clean one-shot - # (hp <= 18 dies now, before their builders can heal it), then - # the lowest-hp target. key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, -targetHp) - if bestTile is None or key > bestPriority: - bestPriority = key - bestTile = tile - + if bestTile is None or key > bestKey: + bestKey, bestTile = key, tile if bestTile is not None and ct.can_fire(bestTile): ct.fire(bestTile) + def gunner(self, ct: Controller) -> None: + target = ct.get_gunner_target() + if target is not None: + builder_id = ct.get_tile_builder_bot_id(target) + building_id = ct.get_tile_building_id(target) + tid = builder_id if builder_id is not None else building_id + if tid is not None and ct.get_team(tid) != ct.get_team(): + if ct.can_fire(target): + ct.fire(target) + return + if ct.get_global_resources() < 60 or ct.get_action_cooldown() != 0: + return + my_pos = ct.get_position() + current = ct.get_direction() + cands = [] + for eid in ct.get_nearby_entities(13): + et = ct.get_entity_type(eid) + if et not in (EntityType.GUNNER, EntityType.SENTINEL, + EntityType.BUILDER_BOT): + continue + if ct.get_team(eid) == ct.get_team(): + continue + ep = ct.get_position(eid) + dx, dy = ep.x - my_pos.x, ep.y - my_pos.y + if dx != 0 and dy != 0: + continue + if dx > 0: + f = Direction.EAST + elif dx < 0: + f = Direction.WEST + elif dy > 0: + f = Direction.SOUTH + elif dy < 0: + f = Direction.NORTH + else: + continue + if f == current: + continue + pr = 0 if et in (EntityType.GUNNER, EntityType.SENTINEL) else 1 + cands.append((pr, my_pos.distance_squared(ep), + CARDINALS.index(f), f)) + if cands: + f = min(cands)[3] + if ct.can_rotate(f): + ct.rotate(f) + def launcher(self, ct: Controller) -> None: myLoc = ct.get_position() if self.teamCore is None: - self.teamCore = findTeamCore(ct) - # throw enemies AWAY from our core (Khaos: keep them out of our - # territory). Falling back to away-from-launcher if core unknown. + for bid in ct.get_nearby_buildings(): + if (ct.get_entity_type(bid) == EntityType.CORE + and ct.get_team(bid) == ct.get_team()): + self.teamCore = ct.get_position(bid) anchor = self.teamCore if self.teamCore is not None else myLoc - # Khaos trap throw: remember where each enemy builder has walked so we - # can fling it BACK along its own path - several turns of lost walking - # per launcher action. team = ct.get_team() hist = self.enemyBotHistory for uid in ct.get_nearby_units(): @@ -418,209 +1167,38 @@ def launcher(self, ct: Controller) -> None: lst.append(ep) if len(lst) > 12: lst.pop(0) - # pickup radius is sqrt(2): scan all 8 neighbours, not just cardinals. for dx in (-1, 0, 1): for dy in (-1, 0, 1): if dx == 0 and dy == 0: continue adjacent = Position(myLoc.x + dx, myLoc.y + dy) - if (not self.isInBounds(ct, adjacent) + if (not self._inBounds(adjacent) or not ct.is_in_vision(adjacent)): continue bot_id = ct.get_tile_builder_bot_id(adjacent) - if bot_id is None or ct.get_team(bot_id) == ct.get_team(): + if bot_id is None or ct.get_team(bot_id) == team: continue - - # prefer a tile this bot already walked (trap: forced retrace), - # weighted by distance from our core; else plain away-throw. - walked = set(hist.get(bot_id, ())[:-3]) # skip its last steps - best = None - bestKey = (-1, -1) + walked = set(hist.get(bot_id, ())[:-3]) + best, bestKey = None, (-1, -1) for tile in ct.get_nearby_tiles(26): if ct.can_launch(adjacent, tile): key = (1 if tile in walked else 0, - self.manhattan(anchor, tile)) + manhattan(anchor, tile)) if key > bestKey: - bestKey = key - best = tile + bestKey, best = key, tile if best is not None: ct.launch(adjacent, best) return - def builderBot(self, ct: Controller) -> None: - currentRound = ct.get_current_round() - myLoc = ct.get_position() - self.setUp(ct, myLoc) - - if self.myNum == -1: - # first read is k-1 on our spawn round (writes land next round), - # +1 = unique 1..n since the core only spawns 1 per turn - self.myNum = ct.read_store(0) + 1 - if self.mapW is None or self.mapH is None: - self.mapW = ct.get_map_width() - self.mapH = ct.get_map_height() - if self.distMap is None: - self.distMap = [[4096 for _ in range(self.mapH)] - for _ in range(self.mapW)] - if self.fullMap is None: - self.fullMap = [[-1 for _ in range(self.mapH)] - for _ in range(self.mapW)] - mapanalysis.configure_map(self.mapW, self.mapH) - if self.seenTiles is None: - self.seenTiles = [[False for _ in range(self.mapH)] - for _ in range(self.mapW)] - - # order matters: comms first, real work, leftover-cpu stuff last - self.getNewTiles(ct) - self.shareTiles(ct, currentRound) - self.updateBoard(ct) - # symmetry prediction: eliminate inconsistent hypotheses from the shared - # map, predict the enemy core corner. Cheap, only recomputes on struct - # change; feeds attack() targeting. - if self.teamCore is not None: - if self.symTracker is None: - self.symTracker = symmetry.SymmetryTracker(self.mapW, self.mapH) - self.symTracker.update( - self.fullMap, mapanalysis.struct_version, self.teamCore - ) - if self.enemyCore is None and self.symTracker.resolved(): - cx, cy = self.symTracker.enemy_core(self.teamCore) - if 0 <= cx < self.mapW - 1 and 0 <= cy < self.mapH - 1: - self.enemyCorePredicted = Position(cx, cy) - if self.teamCore is not None and self.turretPlanner is None: - self.turretPlanner = turretplan.TurretPlanner( - self.mapW, self.mapH, self.teamCore - ) - if self.turretPlanner is not None: - self.turretPlanner.sync_and_observe(ct) - - if (not self.turtled and self.turretPlanner is not None - and self.homeDefense.should_turtle( - ct, self.turretPlanner, - self.teamCoreTiles or ([self.teamCore] if self.teamCore else []))): - self.turtled = True - - responding, reactiveBuilt = self.homeDefense.respond( - ct, self.turretPlanner, self.myNum, - self.teamCoreTiles or ([self.teamCore] if self.teamCore else []), - self.fullMap, self.moveTo, - ) - if reactiveBuilt is not None: - self.noteBuiltTile(reactiveBuilt) - - if not responding: - self.runRoleBehavior(ct, currentRound) - - # urgent turret proposal only if we didnt already move this turn - if (self.turretPlanner is not None - and self.turretPlanner.should_pursue_urgent( - ct, self.myNum - )): - proposal = self.turretPlanner.current_proposal - self.moveTo(ct, Position(proposal.x, proposal.y)) - - if self.turretPlanner is not None: - built = self.turretPlanner.try_passive_build(ct) - if built is not None: - self.noteBuiltTile(Position(built.x, built.y)) - self.tryChokeBlock(ct) - self.advanceMapAnalysis(ct) - if self.turretPlanner is not None: - self.turretPlanner.advance(ct, self.fullMap, self.myNum) - self.turretPlanner.draw_debug(ct) - - def tryChokeBlock(self, ct: Controller): - """Khaos passive choke blocker: after the main behaviour, if the action - is free and we're standing next to an unblocked chokepoint on OUR side - of the map, plug it — barrier when the corridor is 1 tile wide, launcher - when wider (its 3x3 fling zone seals what a barrier can't). - - Our own barriers stay traversable (moveTo break-walks them), so this - turns the corridor into a door only our builders can pass — exactly the - anti-rush geometry Besvikomat's opening has to walk through. - """ - if ct.get_action_cooldown() != 0: - return - if not mapanalysis.chokes or self.teamCore is None: - return - myLoc = ct.get_position() - enemyRef = self.enemyCore or self.enemyCorePredicted - ti = ct.get_global_resources() - for choke in mapanalysis.chokes: - tile, clearance = choke[0], choke[1] - pos = Position(tile[0], tile[1]) - if myLoc.distance_squared(pos) > 2: - continue # outside builder action radius - # defensive perimeter only: strictly closer to our core - if (enemyRef is not None - and self.manhattan(pos, self.teamCore) - >= self.manhattan(pos, enemyRef)): - continue - if not self.isInBounds(ct, pos) or not ct.is_in_vision(pos): - continue - if ct.get_tile_building_id(pos) is not None: - continue # already blocked - if ct.get_tile_env(pos) == Environment.ORE_TITANIUM: - continue # never brick an ore tile - if clearance <= 1.2: - if (ti >= ct.get_barrier_cost() + 30 - and ct.can_build_barrier(pos)): - ct.build_barrier(pos) - self.noteBuiltTile(pos) - return - elif ti >= 100 and ct.can_build_launcher(pos): - ct.build_launcher(pos) - self.noteBuiltTile(pos) - return - - def noteBuiltTile(self, builtPos: Position): - oldType = self.fullMap[builtPos.x][builtPos.y] - self.fullMap[builtPos.x][builtPos.y] = 3 - self.seenTiles[builtPos.x][builtPos.y] = True - self.newTiles.append([builtPos, 3]) - if oldType not in (2, 3): - mapanalysis.note_structural_tile( - builtPos.x, builtPos.y, True, self.mapW, self.mapH - ) - - def setUp(self, ct: Controller, myLoc: Position): - if self.teamCore is None: - self.teamCore = findTeamCore(ct) - if self.teamCore is not None: - self.teamCoreTiles = self._computeCoreTiles(ct, self.teamCore) - if self.enemyCore is None: - self.enemyCore = findEnemyCore(ct) - if self.oldPos is not None and self.manhattan(myLoc, self.oldPos) > 1: - self.pf.reset() - self.oldPos = myLoc - - def _computeCoreTiles(self, ct: Controller, corner: Position) -> list: - if not self.isInBounds(ct, corner) or not ct.is_in_vision(corner): - return [corner] - cid = ct.get_tile_building_id(corner) - tiles = [] - if cid is not None: - for dx in (-1, 0, 1): - for dy in (-1, 0, 1): - q = Position(corner.x + dx, corner.y + dy) - if (self.isInBounds(ct, q) and ct.is_in_vision(q) - and ct.get_tile_building_id(q) == cid): - tiles.append(q) - return tiles if tiles else [corner] - - @staticmethod - def manhattan(a: Position, b: Position) -> int: - return abs(a.x - b.x) + abs(a.y - b.y) - - def nearCore2(self, pos: Position) -> int: - tiles = self.teamCoreTiles if self.teamCoreTiles else [self.teamCore] - tiles = [tile for tile in tiles if tile is not None] - if not tiles: - return (self.mapW + self.mapH) if self.mapW is not None else 4096 - return min(self.manhattan(pos, tile) for tile in tiles) + # ---- misc ---- + def _inBounds(self, p): + mi = self.mi + if mi is None: + return False + return 0 <= p.x < mi.w and 0 <= p.y < mi.h @staticmethod - def cardinalDirectionTo(origin: Position, target: Position) -> Direction: + def _cardinalTo(origin, target): dx = target.x - origin.x dy = target.y - origin.y if abs(dx) >= abs(dy) and dx != 0: @@ -628,1001 +1206,3 @@ def cardinalDirectionTo(origin: Position, target: Position) -> Direction: if dy != 0: return Direction.SOUTH if dy > 0 else Direction.NORTH return Direction.CENTRE - - def runRoleBehavior(self, ct: Controller, currentRound: int): - myLoc = ct.get_position() - if ct.get_hp() < ct.get_max_hp() and ct.can_heal(myLoc): - ct.heal(myLoc) - - if (ct.get_id() < 5 - and self.currentHarvester is None - and (currentRound > 20 or self.nearCore2(myLoc) < 3)): - self.alwaysDefense = True - self.defenseMode = True - self.assignRole(ct, currentRound) - self.justSpawned = False - - # old repair pass - self.defender.defendInfrastructure(ct, self.teamCore, self.pf) - - if self.attackMode: - if self.turtled: - # attacking this opponent just feeds it our workforce. - # go MINE, not park: econ margin is the whole win condition, - # and defense capacity comes from the guard + interrupts - self.attackMode = False - self.defenseMode = False - else: - self.attack(ct) - return - if self.defenseMode: - self.defender.defendInfrastructure(ct, self.teamCore, self.pf) - # big-map standing guard comes before idle heal-parking - guardEngaged, guardBuilt = self.homeDefense.maintain_standing_guard( - ct, self.turretPlanner, - self.teamCoreTiles or ([self.teamCore] if self.teamCore else []), - self.enemyCore, self.fullMap, self.moveTo, - ) - if guardBuilt is not None: - self.noteBuiltTile(guardBuilt) - if guardEngaged: - return - healPos = self.defender.picHealingLoc( - ct, self.teamCore, self.alwaysDefense - ) - if (self.nearCore2(ct.get_position()) < 3 - and ct.get_global_resources() >= ct.get_conveyor_cost()): - direction = self.cardinalDirectionTo( - ct.get_position(), self.teamCore - ) - if (direction != Direction.CENTRE - and ct.can_build_conveyor(ct.get_position(), direction)): - ct.build_conveyor(ct.get_position(), direction) - if healPos is not None: - self.moveTo(ct, healPos) - return - - if self.currentHarvester is not None: - if self.surroundHarvester: - self.buildConveyor(ct) - else: - self.protectHarvester(ct) - else: - self.getHarvester(ct, ct.get_position()) - if self.foundHarvester is None and self.currentHarvester is None: - myLoc = ct.get_position() - if (self.explorePos is None or self.exploreTime > 35 - or self.manhattan(self.explorePos, myLoc) <= 2): - self.pickExplore(ct, currentRound) - self.exploreTime = 0 - self.exploreTime += 1 - ct.draw_indicator_line(myLoc, self.explorePos, 255, 255, 255) - self.moveTo(ct, self.explorePos) - - def assignRole(self, ct: Controller, currentRound): - """Memoryless role choice (Khaos): recompute from live state every - round instead of dicing every 120. Hysteresis on the money gate so an - attacker mid-siege doesn't flicker back to eco at the threshold, and a - deterministic myNum spread replaces the random mix.""" - if self.turtled or self.alwaysDefense: - self.attackMode = False - self.defenseMode = self.alwaysDefense - return - if self.currentHarvester is not None or self.foundHarvester is not None: - # committed econ claim: finish it first - self.attackMode = False - self.defenseMode = False - return - # walking to the enemy is free; the sentinel build itself is - # affordability-gated downstream. Only a token gate here so a fresh - # attacker isn't dispatched while we're utterly broke. - ti = ct.get_global_resources() - stayGate = 30 if self.attackMode else 60 - self.attackMode = (currentRound > 25 - and self.myNum % 3 == 0 - and ti >= stayGate) - self.defenseMode = False - - def attack(self, ct: Controller) -> None: - if self.teamCore is None: - return - myLoc = ct.get_position() - # symmetry-ordered core hypotheses: surviving guesses first (most-likely - # leading), then the full reflection/rotation set as a fallback so we - # still cycle if symmetry hasn't resolved. - coreHypothesis = [] - if self.symTracker is not None: - for cx, cy in self.symTracker.enemy_core_candidates(self.teamCore): - if 0 <= cx < self.mapW - 1 and 0 <= cy < self.mapH - 1: - coreHypothesis.append(Position(cx, cy)) - for fallback in ( - Position(ct.get_map_width() - 2 - self.teamCore.x, - ct.get_map_height() - 2 - self.teamCore.y), - Position(ct.get_map_width() - 2 - self.teamCore.x, - self.teamCore.y), - Position(self.teamCore.x, - ct.get_map_height() - 2 - self.teamCore.y), - ): - if fallback not in coreHypothesis: - coreHypothesis.append(fallback) - if self.attackHypothesisIndex >= len(coreHypothesis): - self.attackHypothesisIndex = 0 - if self.enemyCore is not None: - self.attackPos = self.enemyCore - elif self.attackPos is None: - self.attackPos = coreHypothesis[self.attackHypothesisIndex] - self.attackMoveTimeout = 0 - - def nextAttackHypothesis(): - self.attackHypothesisIndex = ( - self.attackHypothesisIndex + 1 - ) % len(coreHypothesis) - self.attackPos = coreHypothesis[self.attackHypothesisIndex] - self.attackMoveTimeout = 0 - - def changeAttackPos(): - dx = 5 * random.randint(-1, 1) - dy = 5 * random.randint(-1, 1) - new_x = max(0, min(self.attackPos.x + dx, - ct.get_map_width() - 1)) - new_y = max(0, min(self.attackPos.y + dy, - ct.get_map_height() - 1)) - self.attackPos = Position(new_x, new_y) - self.attackMoveTimeout = 0 - - if self.manhattan(myLoc, self.attackPos) < 4: - changeAttackPos() - - if self.spawnSentinelHere is None: - nearbyEnemyStuff = [] - for building_id in ct.get_nearby_buildings(): - if (ct.get_entity_type(building_id) == EntityType.CONVEYOR - and ct.get_team(building_id) != ct.get_team() - and ct.get_stored_resource(building_id) is not None): - nearbyEnemyStuff.append(ct.get_position(building_id)) - nearbyEnemyStuff.sort( - key=lambda p: self.manhattan(p, self.attackPos) - ) - - if not nearbyEnemyStuff: - self.attackMoveTimeout += 1 - self.moveTo(ct, self.attackPos) - if self.attackMoveTimeout > 20: - if self.enemyCore is None: - nextAttackHypothesis() - else: - changeAttackPos() - return - self.spawnSentinelHere = nearbyEnemyStuff[0] - self.attackMoveTimeout = 0 - - for building_id in ct.get_nearby_buildings(): - if (ct.get_entity_type(building_id) == EntityType.SENTINEL - and ct.get_team(building_id) == ct.get_team() - and self.manhattan(ct.get_position(building_id), - self.spawnSentinelHere) <= 4): - self.spawnSentinelHere = None - self.attackMoveTimeout = 0 - self.moveTo(ct, self.attackPos) - return - - if self.attackMoveTimeout > 25: - self.spawnSentinelHere = None - self.attackMoveTimeout = 0 - return - - if myLoc != self.spawnSentinelHere: - self.moveTo(ct, self.spawnSentinelHere) - self.attackMoveTimeout += 1 - return - self.attackMoveTimeout = 0 - - if (not self.isInBounds(ct, self.spawnSentinelHere) - or not ct.is_in_vision(self.spawnSentinelHere)): - return - building_id = ct.get_tile_building_id(self.spawnSentinelHere) - if building_id is not None: - if ct.get_team(building_id) == ct.get_team(): - if ct.can_destroy(self.spawnSentinelHere): - ct.destroy(self.spawnSentinelHere) - else: - self.defender.placeLauncher(ct, self.spawnSentinelHere) - self.moveTo(ct, self.spawnSentinelHere) - if ct.can_fire(self.spawnSentinelHere): - ct.fire(self.spawnSentinelHere) - - building_id = ct.get_tile_building_id(self.spawnSentinelHere) - if building_id is None: - if ct.get_position() == self.spawnSentinelHere: - self.moveTo(ct, self.attackPos) - directionChoices = {d: 0 for d in CARDINALS} - for enemy_id in ct.get_nearby_buildings(): - if ct.get_team(enemy_id) == ct.get_team(): - continue - direction = self.cardinalDirectionTo( - self.spawnSentinelHere, ct.get_position(enemy_id) - ) - if direction == Direction.CENTRE: - continue - if ct.get_entity_type(enemy_id) == EntityType.CORE: - directionChoices[direction] += 25 - else: - directionChoices[direction] += 1 - - directionChoice = max( - directionChoices, key=lambda d: directionChoices[d] - ) - if ct.can_build_sentinel(self.spawnSentinelHere, directionChoice): - ct.build_sentinel(self.spawnSentinelHere, directionChoice) - self.spawnSentinelHere = None - self.attackPos = None - - def pickExplore(self, ct: Controller, currentRound): - w = ct.get_map_width() - h = ct.get_map_height() - myLoc = ct.get_position() - # Khaos frontier sampling (cheap form): sample random tiles and head - # for the nearest genuinely UNSEEN one, so exploration eats the - # unknown map instead of re-walking known ground. - if self.seenTiles is not None and currentRound > 5: - best = None - bestDist = 1 << 30 - for _ in range(24): - tx = random.randrange(w) - ty = random.randrange(h) - if self.seenTiles[tx][ty]: - continue - d = abs(tx - myLoc.x) + abs(ty - myLoc.y) - if d < bestDist: - bestDist = d - best = Position(tx, ty) - if best is not None: - self.explorePos = best - return - radius = (w + h) // 5 - if currentRound > 5: - theta = random.random() * 2 * math.pi - else: - theta = (((3 * currentRound) % 5) / 5) * 2 * math.pi - tx = int(max(0, min(w - 1, myLoc.x + math.cos(theta) * radius))) - ty = int(max(0, min(h - 1, myLoc.y + math.sin(theta) * radius))) - self.explorePos = Position(tx, ty) - - def getHarvester(self, ct: Controller, myLoc: Position): - if self.foundHarvester is None: - # shared map first: chase ore teammates found, no out of vision queries - rnd = ct.get_current_round() - self.usedHarvesters = {p: exp for p, exp in - self.usedHarvesters.items() if exp > rnd} - used = set(self.usedHarvesters) - knownOres = { - Position(x, y) - for x in range(self.mapW) - for y in range(self.mapH) - if self.fullMap[x][y] == 1 - and Position(x, y) not in used - } - # still scan vision too: enemy harvesters sit on ore thats blocked in fullMap - for tile in ct.get_nearby_tiles(): - if (tile not in used - and ct.get_tile_env(tile) == Environment.ORE_TITANIUM): - knownOres.add(tile) - knownOres = list(knownOres) - knownOres.sort( - key=lambda p: (self.manhattan(p, myLoc) + self.nearCore2(p), - p.x, p.y) - ) - # Voronoi claim partition (Khaos): each visible teammate claims - # only its single NEAREST ore (guards/attackers passing through - # must not veto every ore they happen to stand near). Skip an ore - # if a strictly-closer teammate has it as their nearest. - myId = ct.get_id() - claimed = set() - if knownOres: - for uid in ct.get_nearby_units(): - if (uid == myId or ct.get_team(uid) != ct.get_team() - or ct.get_entity_type(uid) - != EntityType.BUILDER_BOT): - continue - fpos = ct.get_position(uid) - nearest = min( - knownOres, - key=lambda p: (self.manhattan(p, fpos), p.x, p.y)) - fd = self.manhattan(nearest, fpos) - myDist = self.manhattan(nearest, myLoc) - if fd < myDist or (fd == myDist and uid < myId): - claimed.add(nearest) - for orePos in knownOres: - if orePos in claimed: - continue - willWork = True - if ct.is_in_vision(orePos): - oreID = ct.get_tile_building_id(orePos) - if oreID is not None: - willWork = ( - ct.get_team(oreID) == ct.get_team() - or ct.get_entity_type(oreID) == EntityType.HARVESTER - ) - if willWork: - self.foundHarvester = orePos - self.reachHarvesterTime = 0 - break - if self.foundHarvester is None: - return - - if self.reachHarvesterTime >= 20: - self.removeHarvester(ct) - return - - if ct.is_in_vision(self.foundHarvester): - oreID = ct.get_tile_building_id(self.foundHarvester) - if oreID is not None: - if ct.get_team(oreID) == ct.get_team(): - if ct.get_entity_type(oreID) == EntityType.HARVESTER: - self.removeHarvester(ct) - return - if (ct.can_destroy(self.foundHarvester) - and myLoc.distance_squared(self.foundHarvester) <= 2): - # destroying our own stuff is free, doesnt use the build action - ct.destroy(self.foundHarvester) - elif ct.get_entity_type(oreID) != EntityType.HARVESTER: - self.removeHarvester(ct) - return - - if myLoc == self.foundHarvester: - possibleSpots = [] - for direction in CARDINALS: - pos = myLoc.add(direction) - if self.isInBounds(ct, pos) and ct.can_move(direction): - possibleSpots.append(pos) - possibleSpots.sort(key=lambda pos: self.nearCore2(pos)) - if possibleSpots: - self.moveTo(ct, possibleSpots[0]) - else: - self.usedHarvesters[self.foundHarvester] = ( - ct.get_current_round() + 100) - self.foundHarvester = None - return - - if self.manhattan(myLoc, self.foundHarvester) > 1: - self.moveTo(ct, self.foundHarvester) - self.reachHarvesterTime += 1 - return - - if (ct.is_in_vision(self.foundHarvester) - and ct.get_position().distance_squared(self.foundHarvester) <= 2): - if ct.can_build_harvester(self.foundHarvester): - ct.build_harvester(self.foundHarvester) - self.addHarvester(ct, ct.get_position()) - return - oreID = ct.get_tile_building_id(self.foundHarvester) - if (oreID is not None - and ct.get_entity_type(oreID) == EntityType.HARVESTER - and ct.get_team(oreID) != ct.get_team()): - self.addHarvester(ct, ct.get_position()) - - def removeHarvester(self, ct: Controller): - if self.foundHarvester is not None: - self.usedHarvesters[self.foundHarvester] = ( - ct.get_current_round() + 100) - self.foundHarvester = None - self.reachHarvesterTime = 0 - - def addHarvester(self, ct: Controller, myLoc: Position): - self.currentHarvester = self.foundHarvester - # own build claims are near-permanent, not a 100-round failure entry - self.usedHarvesters[self.foundHarvester] = ( - ct.get_current_round() + 100000) - self.foundHarvester = None - self.conveyorEnd = myLoc - self.surroundHarvester = False - self.isEnemyInfastructure = None - self.sentinelSpot = None - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - - def protectHarvester(self, ct: Controller): - if self.protectTurns > 12: - self.protectTurns = 0 - self.surroundHarvester = True - self.isEnemyInfastructure = None - self.sentinelSpot = None - return - - self.protectTurns += 1 - if self.isEnemyInfastructure is None: - for building_id in ct.get_nearby_buildings(): - if ct.get_team(building_id) != ct.get_team(): - self.isEnemyInfastructure = ct.get_position(building_id) - - if self.isEnemyInfastructure is None: - # quiet neighbourhood: the barrier ring is wasted tempo (v30 - # lesson) and enemy builders can't hurt a harvester anyway -> - # route the chain NOW. Screens/protection can come later if - # enemy infra actually shows up. - self.protectTurns = 0 - self.surroundHarvester = True - self.sentinelSpot = None - return - - protectSpots = [] - for direction in CARDINALS: - pos = self.currentHarvester.add(direction) - if (self.isInBounds(ct, pos) and ct.is_in_vision(pos) - and pos != self.conveyorEnd - and ct.get_tile_env(pos) != Environment.WALL - and ct.get_tile_building_id(pos) is None): - protectSpots.append(pos) - if not protectSpots: - self.protectTurns = 0 - self.surroundHarvester = True - self.isEnemyInfastructure = None - self.sentinelSpot = None - return - - if self.isEnemyInfastructure is not None and self.sentinelSpot is None: - protectSpots.sort( - key=lambda pos: self.manhattan(pos, self.isEnemyInfastructure) - ) - self.sentinelSpot = protectSpots[0] - - myLoc = ct.get_position() - protectSpots.sort(key=lambda pos: self.manhattan(pos, myLoc)) - if myLoc in protectSpots: - self.moveTo(ct, self.currentHarvester) - return - if myLoc.distance_squared(protectSpots[0]) > 2: - self.moveTo(ct, protectSpots[0]) - return - - for pos in protectSpots: - if pos == self.sentinelSpot: - direction = self.cardinalDirectionTo( - pos, self.isEnemyInfastructure - ) - if (direction != Direction.CENTRE - and ct.get_global_resources() >= MILITARY_FLOOR - and ct.can_build_sentinel(pos, direction)): - ct.build_sentinel(pos, direction) - return - elif ct.can_build_barrier(pos): - ct.build_barrier(pos) - return - - def buildConveyor(self, ct: Controller): - if self.conveyorEnd is None: - self.currentHarvester = None - return - ct.draw_indicator_dot(self.conveyorEnd, 255, 255, 255) - myLoc = ct.get_position() - if self.nearCore2(self.conveyorEnd) == 0: - self.currentHarvester = None - self.conveyorEnd = None - return - if myLoc.distance_squared(self.conveyorEnd) > 2: - self.moveTo(ct, self.conveyorEnd) - return - if (not self.isInBounds(ct, self.conveyorEnd) - or not ct.is_in_vision(self.conveyorEnd)): - return - - conveyorID = ct.get_tile_building_id(self.conveyorEnd) - if (conveyorID is None and self.nextTurretCountDown <= 0 - and self.nearCore2(self.conveyorEnd) > 1 - and myLoc != self.conveyorEnd - and self.enemyPos is None and self.turretTimeOut == 0): - for building_id in ct.get_nearby_buildings(): - if ct.get_team(building_id) == ct.get_team(): - continue - ct.draw_indicator_dot(ct.get_position(building_id), 200, 20, 100) - enemyType = ct.get_entity_type(building_id) - pos = ct.get_position(building_id) - if enemyType == EntityType.CORE: - self.enemyPos, self.enemyType = pos, 3 - elif (enemyType in (EntityType.SENTINEL, EntityType.GUNNER) - and self.enemyType < 2): - self.enemyPos, self.enemyType = pos, 2 - elif enemyType == EntityType.CONVEYOR and self.enemyType < 1: - self.enemyPos, self.enemyType = pos, 1 - elif (enemyType in (EntityType.LAUNCHER, EntityType.BARRIER) - and self.enemyType < 0): - self.enemyPos, self.enemyType = pos, 0 - - if self.enemyPos is not None and self.turretTimeOut == 0: - direction = self.cardinalDirectionTo(self.conveyorEnd, self.enemyPos) - if (direction != Direction.CENTRE - and ct.get_global_resources() >= MILITARY_FLOOR - and ct.can_build_sentinel(self.conveyorEnd, direction)): - ct.build_sentinel(self.conveyorEnd, direction) - self.turretTimeOut = 1 - self.nextTurretCountDown = 5 - else: - # can't place the screen sentinel (broke, or spot blocked): - # drop the request and keep routing. Returning here deadlocked - # the whole chain whenever enemy infra stayed visible while we - # were poor -> harvesters built but 0 delivered (sprint bug). - self.enemyPos = None - self.enemyType = -1 - if self.turretTimeOut > 0: - self.turretTimeOut += 1 - if self.enemyType == 3 and self.turretTimeOut > 0: - self.currentHarvester = None - self.conveyorEnd = None - self.enemyPos = None - self.enemyType = -1 - return - if self.turretTimeOut > 24: - self.enemyPos = None - self.enemyType = -1 - elif self.turretTimeOut > 0: - return - self.buildCloser(ct) - - def _routeTable(self, ct: Controller): - """Khaos route state: full-map dist-to-core over placeable tiles, - threat-aware, cached until the shared map structurally changes. Lets - chains round walls and dodge sentinel bands instead of greedy-walking - the manhattan gradient into them.""" - ver = mapanalysis.struct_version - if self._routeDist is not None and self._routeVer == ver: - return self._routeDist - if self._routeDist is None: - self._routeDist = [[4096] * self.mapH for _ in range(self.mapW)] - seeds = [s for s in (self.teamCoreTiles - or ([self.teamCore] if self.teamCore else [])) - if s is not None] - if not seeds: - return self._routeDist - self.fillInDistTable(ct, seeds[0], table=self._routeDist, - seeds=seeds, earlyExit=False) - self._routeVer = ver - return self._routeDist - - def buildCloser(self, ct: Controller): - if self.conveyorEnd is None: - return - myLoc = ct.get_position() - if (not self.isInBounds(ct, self.conveyorEnd) - or not ct.is_in_vision(self.conveyorEnd)): - self.moveTo(ct, self.conveyorEnd) - return - - endID = ct.get_tile_building_id(self.conveyorEnd) - if endID is not None: - endEntity = ct.get_entity_type(endID) - if (endEntity == EntityType.CONVEYOR - and ct.get_team(endID) == ct.get_team()): - self.conveyorEnd = None - self.currentHarvester = None - return - if ct.can_destroy(self.conveyorEnd): - ct.destroy(self.conveyorEnd) - if myLoc != self.conveyorEnd: - self.moveTo(ct, self.conveyorEnd) - if self.defender.destroyTurns > 5: - self.defender.placeLauncher(ct, self.conveyorEnd) - if ct.can_fire(self.conveyorEnd): - ct.fire(self.conveyorEnd) - self.defender.destroyTurns += 1 - - possibleConveyors = [] - for direction in CARDINALS: - end = self.conveyorEnd.add(direction) - if (self.isInBounds(ct, end) and ct.is_in_vision(end) - and ct.can_build_conveyor(self.conveyorEnd, direction) - and ct.get_tile_env(end) in - (Environment.EMPTY, Environment.ORE_TITANIUM)): - possibleConveyors.append(direction) - # Khaos route ordering: follow the Dijkstra dist-to-core gradient - # (routes around walls/threat); manhattan only as tiebreak/fallback. - routeD = self._routeTable(ct) - - def routeKey(direction): - end = self.conveyorEnd.add(direction) - return (routeD[end.x][end.y], self.nearCore2(end)) - possibleConveyors.sort(key=routeKey) - - for direction in possibleConveyors: - end = self.conveyorEnd.add(direction) - if (self.nearCore2(end) == 0 - and ct.can_build_conveyor(self.conveyorEnd, direction)): - ct.build_conveyor(self.conveyorEnd, direction) - self.nextTurretCountDown = 0 - self.currentHarvester = None - self.conveyorEnd = None - return - - curRoute = routeD[self.conveyorEnd.x][self.conveyorEnd.y] - for direction in possibleConveyors: - end = self.conveyorEnd.add(direction) - # merge only downhill along the route gradient (manhattan fallback - # when the table hasn't reached one of the tiles) - endRoute = routeD[end.x][end.y] - if endRoute < 4096 and curRoute < 4096: - if endRoute >= curRoute: - continue - elif self.nearCore2(end) >= self.nearCore2(self.conveyorEnd): - continue - endID = ct.get_tile_building_id(end) - if (endID is not None - and ct.get_entity_type(endID) == EntityType.CONVEYOR - and ct.get_team(endID) == ct.get_team() - and ct.can_build_conveyor(self.conveyorEnd, direction) - and ct.get_stored_resource(endID) is None): - ct.build_conveyor(self.conveyorEnd, direction) - self.nextTurretCountDown = 0 - self.currentHarvester = None - self.conveyorEnd = None - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - return - - for direction in possibleConveyors: - end = self.conveyorEnd.add(direction) - endID = ct.get_tile_building_id(end) - canReplace = ( - endID is None - or (ct.get_entity_type(endID) in - (EntityType.BARRIER, EntityType.LAUNCHER) - and ct.get_team(endID) == ct.get_team()) - ) - if (canReplace - and ct.can_build_conveyor(self.conveyorEnd, direction)): - ct.build_conveyor(self.conveyorEnd, direction) - self.nextTurretCountDown -= 1 - self.conveyorEnd = end - self.turretTimeOut = 0 - self.enemyPos = None - self.enemyType = -1 - return - - def isInBounds(self, ct: Controller, pos: Position) -> bool: - return (0 <= pos.x < ct.get_map_width() - and 0 <= pos.y < ct.get_map_height()) - - - def checkPassable (self, ct: Controller, tile: Position): # Only team barriers, or empty tiles are passbale - if not self.isInBounds(ct, tile) or not ct.is_in_vision(tile): - return False - tileId = ct.get_tile_building_id(tile) - if tileId is not None and not ct.is_tile_passable(tile): - tTeam = ct.get_team(tileId) - tType = ct.get_entity_type(tileId) - if tTeam != ct.get_team() or tType != EntityType.BARRIER: - return False - return True - - def getNewTiles (self, ct: Controller): - # hottest per-turn loop (Khaos update_at analog): hoist lookups. - fullMap = self.fullMap - seenTiles = self.seenTiles - newTiles = self.newTiles - getEnv = ct.get_tile_env - getBuildingId = ct.get_tile_building_id - isPassable = ct.is_tile_passable - getTeam = ct.get_team - getType = ct.get_entity_type - myTeam = ct.get_team() - WALL = Environment.WALL - ORE = Environment.ORE_TITANIUM - BARRIER = EntityType.BARRIER - for tile in ct.get_nearby_tiles(): - x = tile.x - y = tile.y - tileEnv = getEnv(tile) - if tileEnv == WALL: - tileType = 2 - else: - # inline checkPassable: empty or own-barrier tiles count - tileId = getBuildingId(tile) - blocked = False - if tileId is not None and not isPassable(tile): - if (getTeam(tileId) != myTeam - or getType(tileId) != BARRIER): - blocked = True - if blocked: - tileType = 3 - elif tileEnv == ORE: - tileType = 1 - else: - tileType = 0 - oldType = fullMap[x][y] - if seenTiles[x][y] == False or oldType != tileType: - seenTiles[x][y] = True - newTiles.append([Position(x, y), tileType]) - if (oldType in (-1, 2, 3)) != (tileType in (-1, 2, 3)): - mapanalysis.note_structural_tile( - x, y, tileType in (-1, 2, 3), self.mapW, self.mapH - ) - fullMap[x][y] = tileType - - def shareTiles(self, ct: Controller, curRound): - spawnCount = ct.read_store(0) - # store reads 0 on round one, skip so we dont % 0 - if spawnCount <= 0 or self.myNum < 1 or self.teamCore is None: - return - boundedRound = curRound % spawnCount + 1 # to bound it from 1 to n - if boundedRound == self.myNum: # means your turn to share in the shared array if the mod is correct - - # start by sorting newTiles by the 16 tiles farthest away from your core - self.newTiles.sort( - key=lambda tile: self.manhattan(tile[0], self.teamCore), - reverse=True, - ) - # convert each tile into a 12 bit integer (if theres less tiles, just send all 1s to fill in the remaining space (so it points to an out of bounds tile) - combined = 0 - needToRemove = [] - for i in range(16): - if i < len(self.newTiles): - cachedPos, cachedType = self.newTiles[i] - needToRemove.append(self.newTiles[i]) - val = ((cachedPos.x & 0x1F) << 7) | ((cachedPos.y & 0x1F) << 2) | (cachedType & 0x3) - else: - val = 0xFFF - combined = (combined << 12) | val # combine all of them to form a 196 bit integer - - # split that numebr into 6 32 bit integers, and write them to the shared array - for i in range(6): - part = (combined >> (32 * i)) & 0xFFFFFFFF - ct.write_store(6 - i, part) - for i in needToRemove: - self.newTiles.remove(i) - - def updateBoard (self, ct: Controller): - # read the 6 32 bit integers, and combine them into 1 196 bit one. - combined = 0 - for i in range(1, 7): - part = ct.read_store(i) - combined = (combined << 32) | part - # split that into 16 12 bit integers. - # retrive the X, and Y and the cached value for each integer. - # if X > mapW or Y > mapH, then pass that one - # update your table, adn check if the tile is in you newTiles. If it is remove it - for i in range(16): - val = (combined >> (12 * (15 - i))) & 0xFFF - - if val == 0xFFF: - continue - - x = (val >> 7) & 0x1F - y = (val >> 2) & 0x1F - t_type = val & 0x3 - - if self.mapW is not None and (x >= self.mapW or y >= self.mapH): # all 1s means that no actual one to share - continue - - ct.draw_indicator_line(Position(x, y), ct.get_position(), 20, 140, 200) - - oldType = self.fullMap[x][y] - if (oldType in (-1, 2, 3)) != (t_type in (-1, 2, 3)): - mapanalysis.note_structural_tile( - x, y, t_type in (-1, 2, 3), self.mapW, self.mapH - ) - self.fullMap[x][y] = t_type - if not self.seenTiles[x][y]: - self.seenTiles[x][y] = True - - self.newTiles = [t for t in self.newTiles if not (t[0].x == x and t[0].y == y)] # you dont want to broadcast the same position twice - - def advanceMapAnalysis(self, ct: Controller): - """Spend only post-action spare cycles on the shared analysis job.""" - analysisJob = mapanalysis.job - if (analysisJob is None - and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US): - analysisJob = mapanalysis.get_job() - while (analysisJob is not None - and ct.get_cpu_time_elapsed() < mapanalysis.CPU_BUDGET_US - and analysisJob.phase != mapanalysis.DONE): - analysisJob.step() - - if analysisJob is not None and analysisJob.phase == mapanalysis.DONE: - mapanalysis.draw_debug(ct, Position) - - def _buildThreatSet(self): - """Soft-threat tiles from remembered ENEMY turrets (facing unknown, so - approximate: gunner = cardinal rays len 3; sentinel = 3-wide cardinal - bands len 5 + adjacent ring). Walls clip the rays.""" - threat = set() - planner = self.turretPlanner - if planner is None: - return threat - w, h = self.mapW, self.mapH - fullMap = self.fullMap - for (x, y), (code, _f) in planner.infra_memory.items(): - if code == turretplan.INFRA_GUNNER: - for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): - for k in range(1, 4): - tx, ty = x + dx * k, y + dy * k - if not (0 <= tx < w and 0 <= ty < h): - break - if fullMap[tx][ty] == 2: - break - threat.add((tx, ty)) - elif code == turretplan.INFRA_SENTINEL: - for ddx in (-1, 0, 1): - for ddy in (-1, 0, 1): - bx, by = x + ddx, y + ddy - if 0 <= bx < w and 0 <= by < h: - threat.add((bx, by)) - for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): - for k in range(1, 6): - tx, ty = x + dx * k, y + dy * k - if not (0 <= tx < w and 0 <= ty < h): - break - if fullMap[tx][ty] == 2: - break - for off in (-1, 0, 1): - bx, by = (tx, ty + off) if dx else (tx + off, ty) - if 0 <= bx < w and 0 <= by < h: - threat.add((bx, by)) - return threat - - def fillInDistTable (self, ct: Controller, targetLoc: Position, - table=None, seeds=None, earlyExit=True): - # Khaos perf pass: locals hoisted, raw int coords in the buckets, and - # an inline vision test — Controller calls only for the handful of - # tiles actually inside vision. With table/seeds/earlyExit=False this - # doubles as the conveyor-ROUTING solver (full map dist-to-core). - w, h = self.mapW, self.mapH - self._threatTiles = self._buildThreatSet() - threat = self._threatTiles - maxCost = (w + h) * (barrierCost + THREAT_COST) + 5 - distMap = table if table is not None else self.distMap - fullMap = self.fullMap - blank = [4096] * h - for x in range(w): - distMap[x][:] = blank - - buckets = [[] for _ in range(maxCost)] - if seeds is None: - seeds = [targetLoc] - for s in seeds: - if 0 <= s.x < w and 0 <= s.y < h: - buckets[0].append((s.x, s.y)) - distMap[s.x][s.y] = 0 - - myLoc = ct.get_position() - myx, myy = myLoc.x, myLoc.y - myNeighbors = {(myx + 1, myy), (myx - 1, myy), - (myx, myy + 1), (myx, myy - 1)} - myTeam = ct.get_team() - getBuildingId = ct.get_tile_building_id - getTeam = ct.get_team - getType = ct.get_entity_type - isPassable = ct.is_tile_passable - BARRIER = EntityType.BARRIER - - for dist in range(maxCost): - bucket = buckets[dist] - hitBucket = False - while bucket: - cur = bucket.pop() - cx, cy = cur - - if distMap[cx][cy] < dist: - continue - - if earlyExit and cur in myNeighbors: - hitBucket = True - continue - - for nx, ny in ((cx + 1, cy), (cx - 1, cy), - (cx, cy + 1), (cx, cy - 1)): - if not (0 <= nx < w and 0 <= ny < h): - continue - if fullMap[nx][ny] > 1: - continue # wall or unpassable - cost = openCost - dxv = nx - myx - dyv = ny - myy - if dxv * dxv + dyv * dyv <= 20: # builder vision r^2 - pos = Position(nx, ny) - tileId = getBuildingId(pos) - if tileId is not None: - if (getTeam(tileId) == myTeam - and getType(tileId) == BARRIER): - cost = barrierCost - elif not isPassable(pos): - continue - if (nx, ny) in threat: - cost += THREAT_COST - newDist = dist + cost - if newDist < maxCost and newDist < distMap[nx][ny]: - distMap[nx][ny] = newDist - buckets[newDist].append((nx, ny)) - if hitBucket: - return dist - return 4096 - - def moveTo (self, ct: Controller, target: Position): - if ct.get_move_cooldown() != 0: - return - overBudget = ct.get_cpu_time_elapsed() > 7000 - myLoc = ct.get_position() - if myLoc == target: - return - # target can be off-map on small maps - if not (0 <= target.x < self.mapW and 0 <= target.y < self.mapH): - return - if overBudget: - # low on budget: greedy step instead of the full dijkstra - bestDir = None - bestDist = 4096 - for d in CARDINALS: - nextPos = myLoc.add(d) - if 0 <= nextPos.x < self.mapW and 0 <= nextPos.y < self.mapH and ct.can_move(d): - posDist = abs(nextPos.x - target.x) + abs(nextPos.y - target.y) - if posDist < bestDist: - bestDir = d - bestDist = posDist - if bestDir is not None: - ct.move(bestDir) - self.stuckTurns = 0 - else: - self.stuckTurns += 1 - return - # cache: only rerun dijkstra when target or the shared map changed, or - # we walked into cells this table never filled (early-exit is bot-anchored) - ver = mapanalysis.struct_version - fresh = self._cacheTarget == target and self._cacheVer == ver and any( - 0 <= myLoc.add(d).x < self.mapW and 0 <= myLoc.add(d).y < self.mapH - and self.distMap[myLoc.add(d).x][myLoc.add(d).y] < 4096 - for d in CARDINALS) - if not fresh: - self.fillInDistTable(ct, target) - self._cacheTarget = target - self._cacheVer = ver - bestDist = 4096 - bestDir = None - for d in CARDINALS: - nextPos = myLoc.add(d) - if 0 <= nextPos.x < self.mapW and 0 <= nextPos.y < self.mapH: - posDist = self.distMap[nextPos.x][nextPos.y] - # tile queries raise outside vision - tileId = ct.get_tile_building_id(nextPos) if ct.is_in_vision(nextPos) else None - if posDist < bestDist: - if ct.can_move(d): - bestDir = d - bestDist = posDist - elif tileId is not None: - tTeam = ct.get_team(tileId) - tType = ct.get_entity_type(tileId) - if tTeam == ct.get_team() and tType == EntityType.BARRIER: - bestDir = d - bestDist = posDist - # boxed in, nothing movable - if bestDir is None: - self.stuckTurns += 1 - if self.stuckTurns > 2 + (ct.get_id() % 8): - movableDirs = [] - for i in CARDINALS: - if ct.can_move(i): - movableDirs.append(i) - if len(movableDirs) > 0: - ct.move(random.choice(movableDirs)) - return - - nextPos = myLoc.add(bestDir) - tileId = ct.get_tile_building_id(nextPos) if ct.is_in_vision(nextPos) else None - if tileId is not None: - tTeam = ct.get_team(tileId) - tType = ct.get_entity_type(tileId) - if tTeam == ct.get_team() and tType == EntityType.BARRIER: - if ct.can_destroy(nextPos): - ct.destroy(nextPos) - else: - return # dont want to increase stuckTurns for no reason - if ct.can_move(bestDir): - ct.move(bestDir) - self.stuckTurns = 0 - else: - self.stuckTurns += 1 - if self.stuckTurns > 2 + (ct.get_id() % 8): - movableDirs = [] - for i in CARDINALS: - if ct.can_move(i): - movableDirs.append(i) - if len(movableDirs) > 0: - ct.move(random.choice(movableDirs)) diff --git a/mapinfo.py b/mapinfo.py new file mode 100644 index 0000000..adee395 --- /dev/null +++ b/mapinfo.py @@ -0,0 +1,273 @@ +"""Bitmask map-info layer (Pantheon/Khaos rebuild). + +The whole map lives in Python big-int masks: bit n = x + y*W. Geometric +neighbour expansion is 4 shifts; derived masks (threat, placeable, frontier) +are boolean algebra over whole boards. On <=30x30 maps a board is <=900 bits. + +Static knowledge (seen/walls/ore) accumulates and is shared through the comm +store with the same 12-bit tile protocol botv2 uses (slots 1..6, 16 tiles per +broadcast, rotation keyed on slot 0's spawn counter). + +>>> m = MapInfo(4, 3) +>>> b = m.bit(1, 1) +>>> sorted(m.xy(t) for t in m.iter_bits(m.expand(b) & ~b)) +[(0, 1), (1, 0), (1, 2), (2, 1)] +>>> m.xy(m.east(b)) +(2, 1) +>>> m.east(m.bit(3, 1)) # east edge does not wrap +0 +""" + +from fcode import Direction, EntityType, Environment, Position + +DIR_DELTA = { + Direction.NORTH: (0, -1), Direction.SOUTH: (0, 1), + Direction.EAST: (1, 0), Direction.WEST: (-1, 0), +} + +# tile type codes shared through the store (identical to botv2) +T_EMPTY, T_ORE, T_WALL, T_BLOCK = 0, 1, 2, 3 + +GUNNER_RAY = 3 # threat approximation: gunner cross length +SENT_BAND = 5 # sentinel band length (3 wide) + + +class MapInfo: + def __init__(self, w: int, h: int): + self.w = w + self.h = h + self.n = w * h + self.board = (1 << self.n) - 1 + col0 = 0 + for y in range(h): + col0 |= 1 << (y * w) + self.not_col0 = self.board & ~col0 + self.not_colL = self.board & ~(col0 << (w - 1)) + + # accumulated static knowledge + self.seen = 0 + self.walls = 0 + self.ore = 0 + self.blocked = 0 # non-walkable buildings (shared, any team) + self.struct_version = 0 + + # remembered point features (local to this unit, refreshed on sight) + self.enemy_turrets = {} # (x,y) -> 'G' | 'S' + self.enemy_buildings = {} # (x,y) -> EntityType + self.own_harvesters = {} # (x,y) -> True + self._threat_cache = (None, 0) + + # per-turn dynamic masks + self.own_bots = 0 + self.enemy_bots = 0 + self.own_conveyors = 0 + self.own_barriers = 0 # break-walkable at cost (Pantheon 15) + # remembered facings of own conveyors: (x,y) -> (dx,dy) output delta + self.own_conv_facing = {} + + # outgoing share queue: list of (x, y, code) + self.out_tiles = [] + # optional hook: called (x, y, blocking) on structural changes so the + # chokepoint analyzer can invalidate incrementally + self.on_block_change = None + + # ---- geometry ---- + def bit(self, x: int, y: int) -> int: + return 1 << (x + y * self.w) + + def xy(self, single_bit: int): + n = single_bit.bit_length() - 1 + return n % self.w, n // self.w + + def east(self, m): + return (m << 1) & self.not_col0 + + def west(self, m): + return (m >> 1) & self.not_colL + + def south(self, m): + return (m << self.w) & self.board + + def north(self, m): + return m >> self.w + + def expand(self, m): + return m | self.east(m) | self.west(m) | self.south(m) | self.north(m) + + def iter_bits(self, m): + while m: + lsb = m & -m + yield lsb + m ^= lsb + + # ---- derived masks ---- + def passable(self): + """Tiles a builder could stand on (unknown treated optimistically).""" + return self.board & ~self.walls & ~self.blocked + + def threat(self): + """Soft-threat mask from remembered enemy turrets (facing unknown: + gunner = cardinal cross len 3, sentinel = 3-wide bands len 5).""" + key = tuple(sorted(self.enemy_turrets.items())) + cached, ver = self._threat_cache + if cached is not None and ver == key: + return cached + t = 0 + shifts = (self.east, self.west, self.north, self.south) + for (x, y), kind in self.enemy_turrets.items(): + b = self.bit(x, y) + if kind == 'G': + for shift in shifts: + ray = b + for _ in range(GUNNER_RAY): + ray = shift(ray) & ~self.walls + t |= ray + else: + t |= self.expand(b) + for shift in shifts: + ray = b + acc = 0 + for _ in range(SENT_BAND): + ray = shift(ray) & ~self.walls + acc |= ray + t |= self.expand(acc) # widen the ray into a 3-wide band + t &= self.board + self._threat_cache = (t, key) + return t + + def frontier(self): + """Seen tiles adjacent to unseen ones — exploration targets.""" + unseen = self.board & ~self.seen + return self.expand(unseen) & self.seen & ~self.walls + + # ---- knowledge updates ---- + def note_tile(self, x, y, code, share=True): + b = self.bit(x, y) + old_wall = self.walls & b + old_block = self.blocked & b + newly = not (self.seen & b) + self.seen |= b + if code == T_WALL: + self.walls |= b + elif code == T_BLOCK: + self.blocked |= b + else: + changed = old_block != 0 + self.blocked &= ~b + if code == T_ORE: + self.ore |= b + if changed: + self.struct_version += 1 + if self.on_block_change: + self.on_block_change(x, y, False) + if (code in (T_WALL, T_BLOCK)) and not (old_wall or old_block): + self.struct_version += 1 + if self.on_block_change: + self.on_block_change(x, y, True) + if share and (newly or (code == T_BLOCK) != bool(old_block)): + self.out_tiles.append((x, y, code)) + if len(self.out_tiles) > 96: + del self.out_tiles[:32] + + def update_vision(self, ct): + """Per-turn scan: static tiles into masks, dynamic entities rebuilt.""" + my_team = ct.get_team() + get_env = ct.get_tile_env + get_bid = ct.get_tile_building_id + note = self.note_tile + self.own_bots = 0 + self.enemy_bots = 0 + self.own_conveyors = 0 + self.own_barriers = 0 + WALKABLE = (EntityType.CONVEYOR, EntityType.SPLITTER) + for tile in ct.get_nearby_tiles(): + x, y = tile.x, tile.y + env = get_env(tile) + if env == Environment.WALL: + note(x, y, T_WALL) + continue + bid = get_bid(tile) + if bid is None: + note(x, y, T_ORE if env == Environment.ORE_TITANIUM else T_EMPTY) + self.enemy_buildings.pop((x, y), None) + self.enemy_turrets.pop((x, y), None) + self.own_harvesters.pop((x, y), None) + self.own_conv_facing.pop((x, y), None) + continue + etype = ct.get_entity_type(bid) + team = ct.get_team(bid) + if etype in WALKABLE: + note(x, y, T_ORE if env == Environment.ORE_TITANIUM else T_EMPTY) + if team == my_team: + self.own_conveyors |= self.bit(x, y) + if etype == EntityType.CONVEYOR: + d = DIR_DELTA.get(ct.get_direction(bid)) + if d is not None: + self.own_conv_facing[(x, y)] = d + else: + self.enemy_buildings[(x, y)] = etype + continue + note(x, y, T_BLOCK) + if team != my_team: + self.enemy_buildings[(x, y)] = etype + if etype == EntityType.GUNNER: + self.enemy_turrets[(x, y)] = 'G' + elif etype == EntityType.SENTINEL: + self.enemy_turrets[(x, y)] = 'S' + else: + self.enemy_buildings.pop((x, y), None) + if etype == EntityType.HARVESTER: + self.own_harvesters[(x, y)] = True + elif etype == EntityType.BARRIER: + self.own_barriers |= self.bit(x, y) + for uid in ct.get_nearby_units(): + if ct.get_entity_type(uid) != EntityType.BUILDER_BOT: + continue + p = ct.get_position(uid) + if ct.get_team(uid) == my_team: + self.own_bots |= self.bit(p.x, p.y) + else: + self.enemy_bots |= self.bit(p.x, p.y) + + # ---- comm store share (botv2 12-bit protocol, slots 1..6) ---- + def share(self, ct, my_num: int, core: Position): + spawn_count = ct.read_store(0) + if spawn_count <= 0 or my_num < 1 or core is None: + return + if ct.get_current_round() % spawn_count + 1 != my_num: + return + self.out_tiles.sort( + key=lambda t: abs(t[0] - core.x) + abs(t[1] - core.y), + reverse=True) + combined = 0 + for i in range(16): + if i < len(self.out_tiles): + x, y, code = self.out_tiles[i] + val = ((x & 0x1F) << 7) | ((y & 0x1F) << 2) | (code & 0x3) + else: + val = 0xFFF + combined = (combined << 12) | val + for i in range(6): + ct.write_store(6 - i, (combined >> (32 * i)) & 0xFFFFFFFF) + del self.out_tiles[:16] + + def absorb(self, ct): + combined = 0 + for i in range(1, 7): + combined = (combined << 32) | ct.read_store(i) + for i in range(16): + val = (combined >> (12 * (15 - i))) & 0xFFF + if val == 0xFFF: + continue + x = (val >> 7) & 0x1F + y = (val >> 2) & 0x1F + if x >= self.w or y >= self.h: + continue + self.note_tile(x, y, val & 0x3, share=False) + self.out_tiles = [t for t in self.out_tiles + if not (t[0] == x and t[1] == y)] + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/path.py b/path.py new file mode 100644 index 0000000..7ed41d3 --- /dev/null +++ b/path.py @@ -0,0 +1,173 @@ +"""Bitmask pathfinding (Pantheon/Khaos rebuild). + +All searches operate on MapInfo big-int masks. The Dial's variant runs in +REVERSE from a target mask with a circular array of frontier bitmasks as the +bucket queue (Khaos figure 12): plain step cost 1, stepping into soft threat ++THREAT, so paths detour around enemy turret envelopes unless forced. + +dist_field() unrolls the same search into a per-tile int list for gradient +consumers (conveyor routing). +""" + +THREAT_EXTRA = 12 # entering a threat tile costs 1 + THREAT_EXTRA +BARRIER_EXTRA = 14 # entering an OWN barrier costs 1 + 14 (Pantheon 15: + # the mover destroys it, spending its action) +RING = THREAT_EXTRA + BARRIER_EXTRA + 3 # ring must exceed max edge cost + + +def next_step(mi, start_xy, targets_mask, avoid_mask=0, max_iters=None): + """Reverse Dial's from targets; returns (dx, dy) for the best first step + from start, or None if unreachable. avoid_mask tiles are impassable. + Own barriers are break-walkable at +BARRIER_EXTRA.""" + barriers = mi.own_barriers & ~avoid_mask + passable = (mi.passable() | barriers) & ~avoid_mask + targets = targets_mask & mi.board + if not targets: + return None + threat = mi.threat() + sx, sy = start_xy + sbit = mi.bit(sx, sy) + nbrs = [] + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + nx, ny = sx + dx, sy + dy + if 0 <= nx < mi.w and 0 <= ny < mi.h: + nbrs.append((mi.bit(nx, ny), dx, dy)) + nbr_all = 0 + for b, _, _ in nbrs: + nbr_all |= b + # the start tile itself counts as reachable ground for the wavefront + passable |= sbit | targets + + ring = [0] * RING + ring[0] = targets + visited = 0 + found = {} + if max_iters is None: + max_iters = (mi.w + mi.h) * (THREAT_EXTRA + 1) + 5 + for dist in range(max_iters): + cur = ring[dist % RING] & ~visited + ring[dist % RING] = 0 + if not cur: + if not any(ring): + break + continue + visited |= cur + hit = cur & nbr_all + if hit: + for b, dx, dy in nbrs: + if (hit & b) and b not in found: + found[b] = dist + if len(found) == len(nbrs): + break + nxt = mi.expand(cur) & passable & ~visited + plain = nxt & ~threat & ~barriers + hot = nxt & threat & ~barriers + barr = nxt & barriers & ~threat + both = nxt & barriers & threat + if plain: + ring[(dist + 1) % RING] |= plain + if hot: + ring[(dist + 1 + THREAT_EXTRA) % RING] |= hot + if barr: + ring[(dist + 1 + BARRIER_EXTRA) % RING] |= barr + if both: + ring[(dist + 1 + THREAT_EXTRA + BARRIER_EXTRA) % RING] |= both + if not found: + return None + best = min(found.items(), key=lambda kv: kv[1])[0] + for b, dx, dy in nbrs: + if b == best: + return (dx, dy) + return None + + +def dist_field(mi, targets_mask, avoid_mask=0): + """Full-map cost-to-target list indexed [x + y*w]; 4096 = unreachable.""" + barriers = mi.own_barriers & ~avoid_mask + passable = (mi.passable() | barriers + | (targets_mask & mi.board)) & ~avoid_mask + threat = mi.threat() + field = [4096] * mi.n + ring = [0] * RING + ring[0] = targets_mask & mi.board + visited = 0 + max_iters = (mi.w + mi.h) * (THREAT_EXTRA + 1) + 5 + for dist in range(max_iters): + cur = ring[dist % RING] & ~visited + ring[dist % RING] = 0 + if not cur: + if not any(ring): + break + continue + visited |= cur + m = cur + while m: + lsb = m & -m + field[lsb.bit_length() - 1] = dist + m ^= lsb + nxt = mi.expand(cur) & passable & ~visited + plain = nxt & ~threat & ~barriers + hot = nxt & threat & ~barriers + barr = nxt & barriers & ~threat + both = nxt & barriers & threat + if plain: + ring[(dist + 1) % RING] |= plain + if hot: + ring[(dist + 1 + THREAT_EXTRA) % RING] |= hot + if barr: + ring[(dist + 1 + BARRIER_EXTRA) % RING] |= barr + if both: + ring[(dist + 1 + THREAT_EXTRA + BARRIER_EXTRA) % RING] |= both + return field + + +def closest(mi, start_mask, candidates_mask, avoid_mask=0, max_steps=None): + """Unweighted BFS over passable tiles; returns the candidate bit reached + first (lowest bit as tiebreak) or 0.""" + passable = mi.passable() & ~avoid_mask + front = start_mask & mi.board + visited = front + cands = candidates_mask & mi.board + if front & cands: + hit = front & cands + return hit & -hit + if max_steps is None: + max_steps = mi.w + mi.h + for _ in range(max_steps): + front = mi.expand(front) & (passable | cands) & ~visited + if not front: + return 0 + visited |= front + hit = front & cands + if hit: + return hit & -hit + return 0 + + +def claims(mi, my_bit, others_mask, candidates_mask, max_steps=None): + """Khaos claim partition: simultaneous competing floodfills. Returns the + subset of candidates MY region reaches strictly first (ties -> others, + keeping the claim conservative).""" + passable = mi.passable() + cands = candidates_mask & mi.board + if not cands: + return 0 + my_front = my_bit + other_front = others_mask & mi.board + claimed = my_front | other_front + mine = my_front & cands + cands &= ~other_front + if max_steps is None: + max_steps = mi.w + mi.h + remaining = cands & ~mine + for _ in range(max_steps): + if not remaining or not (my_front or other_front): + break + other_front = mi.expand(other_front) & (passable | cands) & ~claimed + claimed |= other_front + remaining &= ~other_front + my_front = mi.expand(my_front) & (passable | cands) & ~claimed + claimed |= my_front + mine |= my_front & remaining + remaining &= ~my_front + return mine diff --git a/pathfinding.py b/pathfinding.py deleted file mode 100644 index 9ba6647..0000000 --- a/pathfinding.py +++ /dev/null @@ -1,156 +0,0 @@ -from fcode import * - -DIRECTIONS = [ - Direction.NORTH, - Direction.EAST, - Direction.SOUTH, - Direction.WEST, -] - - -class Pathfinder: - def __init__(self): - self.minDistToTarget = 2**10 - self.objectOnRight = True - self.currentObstacle = None - self.currentTarget = None - self.canRotate = True - self.visitedStates = set() - - def reset(self): - self.currentTarget = None - self.minDistToTarget = 2**10 - self.objectOnRight = True - self.currentObstacle = None - self.canRotate = True - self.visitedStates = set() - - def isInBounds(self, c: Controller, pos: Position) -> bool: - W = c.get_map_width() - 1 - H = c.get_map_height() - 1 - return pos.x == max(0, min(W, pos.x)) and pos.y == max(0, min(H, pos.y)) - - def canMove(self, c: Controller, direction: Direction) -> bool: - dest = c.get_position().add(direction) - if not self.isInBounds(c, dest): - return False - return c.can_move(direction) - - def _move(self, c: Controller, direction: Direction): - if c.can_move(direction): - c.move(direction) - - @staticmethod - def _diagonalDist(a: Position, b: Position) -> int: - return abs(a.x - b.x) + abs(a.y - b.y) # cardinal update - - def _isKnownPassable(self, c: Controller, pos: Position) -> bool: - # Only trust this if we currently have vision on the tile -- otherwise we can't - # tell whether the remembered obstacle has actually cleared. - if not self.isInBounds(c, pos) or not c.is_in_vision(pos): - return False - if c.get_tile_env(pos) == Environment.WALL: - return False - return c.get_tile_building_id(pos) is None - - def _setInitialDirection(self, c: Controller): - myLoc = c.get_position() - forward = myLoc.direction_to(self.currentTarget) - left = forward.rotate_left() - right = forward.rotate_right() - - for _ in range(8): - if self.canMove(c, left): break - left = left.rotate_left() - for _ in range(8): - if self.canMove(c, right): break - right = right.rotate_right() - - leftLoc = myLoc.add(left) - rightLoc = myLoc.add(right) - leftDist = self._diagonalDist(leftLoc, self.currentTarget) - rightDist = self._diagonalDist(rightLoc, self.currentTarget) - - if leftDist < rightDist: - self.objectOnRight = True - elif rightDist < leftDist: - self.objectOnRight = False - else: - # Tie on Chebyshev distance -- break it with actual distance from the bot. - self.objectOnRight = myLoc.distance_squared(leftLoc) < myLoc.distance_squared(rightLoc) - - self.currentObstacle = (myLoc.add(left.rotate_right()) - if self.objectOnRight - else myLoc.add(right.rotate_left())) - - def _followWall(self, c: Controller, myLoc: Position, canRotate: bool = None): - if canRotate is None: - canRotate = self.canRotate - direction = myLoc.direction_to(self.currentObstacle) - for _ in range(8): - direction = (direction.rotate_left() if self.objectOnRight else direction.rotate_right()) - if self.canMove(c, direction): - self._move(c, direction) - return - loc = myLoc.add(direction) - if not self.isInBounds(c, loc): - if canRotate: - # Hit the map edge -- flip which side we're hugging and restart the - # sweep from scratch (rather than continuing this loop with a direction - # that points off the map), locking further flips until the next reset. - self.objectOnRight = not self.objectOnRight - self.canRotate = False - self._followWall(c, myLoc, canRotate=False) - return - continue - if not self.canMove(c, direction): - self.currentObstacle = loc - - def _state(self, c: Controller, target: Position): - myLoc = c.get_position() - ref = self.currentObstacle if self.currentObstacle is not None else target - direction = myLoc.direction_to(ref) - return (myLoc.x, myLoc.y, direction, self.objectOnRight) - - def moveTo(self, c: Controller, target: Position): - if self.currentTarget is None or self.currentTarget != target: - self.reset() - self.currentTarget = target - - if not any(self.canMove(c, d) for d in DIRECTIONS): - return - - myLoc = c.get_position() - - dist = self._diagonalDist(myLoc, target) - if dist < self.minDistToTarget: - self.reset() - self.minDistToTarget = dist - - # If the obstacle we're routing around has turned out to be passable (vision - # updated, a unit moved off it, etc.), stop treating it as blocking and retry - # a direct line instead of continuing to wall-follow around nothing. - if self.currentObstacle is not None and self._isKnownPassable(c, self.currentObstacle): - self.reset() - self.minDistToTarget = dist - - # Cycle detection: if we've already been in this exact (position, facing-toward- - # obstacle-or-target, which-side-we're-hugging) state on this trip, the wall-follow - # sweep is looping around an obstacle it can't escape -- force a reset instead of - # repeating the same loop forever. - state = self._state(c, target) - if state in self.visitedStates: - self.reset() - self.minDistToTarget = dist - else: - self.visitedStates.add(state) - - self.currentTarget = target - - if self.currentObstacle is None: - forward = myLoc.direction_to(target) - if self.canMove(c, forward): - self._move(c, forward) - return - self._setInitialDirection(c) - self._followWall(c, myLoc) diff --git a/planes.py b/planes.py new file mode 100644 index 0000000..27c280b --- /dev/null +++ b/planes.py @@ -0,0 +1,99 @@ +"""Bit-sliced score planes (Pantheon figure 14) for gunner placement. + +A plane stack is a list of NUM_PLANES big-int bitmasks; the integer score of +tile n is sum(((planes[i] >> n) & 1) << i). add_const() adds a constant to +every tile of a mask in O(NUM_PLANES) whole-board ops — cost independent of +how many tiles are in the mask. argmax_mask() extracts the best-scoring tiles +in one MSB-down pass. + +Scoring: for each facing, shift every enemy-class mask BACKWARD along the +facing k=1..3 steps with wall clipping; tiles that survive are placements +whose forward ray hits that enemy at range k. Score is discounted with range +(gunners can't shoot over buildings, so adjacency is king). +""" + +NUM_PLANES = 8 +MAX_SCORE = (1 << NUM_PLANES) - 1 + +# enemy class -> base placement value (hit at range 1) +# range discount ~0.9^k like Pantheon +RAY_STEPS = 3 +DISCOUNT = (1.0, 0.9, 0.81) + + +def add_const(planes, c, mask): + """Add constant c to every tile in mask (ripple-carry over planes).""" + if not mask or c <= 0: + return + i = 0 + while c and i < NUM_PLANES: + if c & 1: + carry = planes[i] & mask + planes[i] ^= mask + j = i + 1 + while carry and j < NUM_PLANES: + new_carry = planes[j] & carry + planes[j] ^= carry + carry = new_carry + j += 1 + c >>= 1 + i += 1 + + +def argmax_mask(planes, cands): + """(best_score, mask_of_best_tiles) among cands; (0, 0) if none.""" + if not cands: + return 0, 0 + best = 0 + cur = cands + for i in range(NUM_PLANES - 1, -1, -1): + t = cur & planes[i] + if t: + cur = t + best |= 1 << i + return best, cur + + +def gunner_planes(mi, class_masks, valid_mask): + """Per-facing plane stacks for gunner placements. + + class_masks: list of (value, enemy_mask). Returns {facing_key: planes} + with facing_key in 'E','W','N','S' — the direction the built gunner faces. + A placement facing E hits targets to its EAST, so target masks are shifted + WEST (backward) onto placement tiles. + """ + shifts_back = { + 'E': mi.west, 'W': mi.east, 'N': mi.south, 'S': mi.north, + } + out = {} + nw = ~mi.walls + for fk, back in shifts_back.items(): + planes = [0] * NUM_PLANES + for value, emask in class_masks: + if not emask: + continue + s = emask + for k in range(RAY_STEPS): + s = back(s) & nw + if not s: + break + v = int(value * DISCOUNT[k]) + if v <= 0: + continue + add_const(planes, v, s & valid_mask) + out[fk] = planes + return out + + +def best_placement(mi, class_masks, valid_mask): + """(score, x, y, facing_key) of the best gunner placement, or None.""" + stacks = gunner_planes(mi, class_masks, valid_mask) + best = None + for fk, planes in stacks.items(): + score, mask = argmax_mask(planes, valid_mask) + if score > 0 and mask: + lsb = mask & -mask + x, y = mi.xy(lsb) + if best is None or score > best[0]: + best = (score, x, y, fk) + return best diff --git a/turretplan.py b/turretplan.py deleted file mode 100644 index 18483d5..0000000 --- a/turretplan.py +++ /dev/null @@ -1,918 +0,0 @@ -"""picks spots for defensive turrets, coordinated through the store. - -store layout (0..7 belong to the map sharing layer): - -* 8..11: two 16 bit enemy infra sightings per u32, xxxxx yyyyy ttt fff - (x, y, type, round//16 mod 8). zero = empty. -* 12..13: two 13 bit friendly turret records per u32: x, y, type. zero = empty. -* 14: current proposal (x, y, type, facing, frontier, urgent, valid, score - band, generation). -* 15: claim slot for the urgent builder (number, manhattan distance, age, - proposal fingerprint). one elected builder updates it per round so it - settles on the nearest idle bot. - -the store is a lossy cache, not the source of truth: writers retry until a -sighting shows up in the next-round snapshot, readers merge it with their -own memory. - -engine test scenario: two rooms joined by a 1 wide choke at (7, 5), our core -(2, 5), enemy core (12, 5), enemy harvesters at (10, 4..6), (5, 5) passable -on our side and outside known enemy turret range. top proposal should be a -SENTINEL at (5, 5) facing EAST, its band crosses the choke and covers the -cluster where a gunner straight ray cant. -""" - -from collections import namedtuple - -from fcode import Direction, EntityType, Position - -import mapanalysis - - -# scoring tunables -W_COVER = 3 -W_CHOKE = 4 -W_MUTUAL = 2 -W_SAFE = 1 -W_EXPOSED = 4 -W_FRONTIER_PENALTY = 6 -URGENT_THRESHOLD = 25 - -# operational tunables -CPU_BUDGET_US = 6000 -TITANIUM_RESERVE = 20 -FRESHNESS_ROUNDS = 16 -MAX_FRESH_AGE = 3 -CLAIM_MAX_AGE = 10 -CORE_SPAWN_RING_MANHATTAN = 2 # 12 surrounding tiles: d=1 plus d=2. - -INFRA_SLOT_FIRST = 8 -INFRA_SLOT_COUNT = 4 -TURRET_SLOT_FIRST = 12 -TURRET_SLOT_COUNT = 2 -PROPOSAL_SLOT = 14 -PROPOSAL_CHECK_SLOT = 15 - -INFRA_NONE = 0 -INFRA_HARVESTER = 1 -INFRA_CONVEYOR = 2 -INFRA_SPLITTER = 3 -INFRA_GUNNER = 4 -INFRA_SENTINEL = 5 -INFRA_CORE = 6 - -ENTITY_TO_INFRA = { - EntityType.HARVESTER: INFRA_HARVESTER, - EntityType.CONVEYOR: INFRA_CONVEYOR, - EntityType.SPLITTER: INFRA_SPLITTER, - EntityType.GUNNER: INFRA_GUNNER, - EntityType.SENTINEL: INFRA_SENTINEL, - EntityType.CORE: INFRA_CORE, -} -TARGET_VALUE = { - INFRA_HARVESTER: 5, - INFRA_CONVEYOR: 2, - INFRA_SPLITTER: 2, - INFRA_GUNNER: 3, - INFRA_SENTINEL: 3, - INFRA_CORE: 10, -} -TURRET_TYPES = (INFRA_GUNNER, INFRA_SENTINEL) - -FACINGS = ( - Direction.NORTH, - Direction.EAST, - Direction.SOUTH, - Direction.WEST, -) -FACING_DELTAS = ((0, -1), (1, 0), (0, 1), (-1, 0)) - -Proposal = namedtuple( - "Proposal", - "x y turret_type facing score frontier urgent frontier_choke generation", -) - - -def _manhattan(a, b): - return abs(a[0] - b[0]) + abs(a[1] - b[1]) - - -def _epoch(round_number): - return (round_number // FRESHNESS_ROUNDS) & 0x7 - - -def _age(now_epoch, then_epoch): - return (now_epoch - then_epoch) & 0x7 - - -def _pack_infra(x, y, type_code, freshness): - if type_code == INFRA_NONE: - return 0 - return ((x & 0x1F) | ((y & 0x1F) << 5) - | ((type_code & 0x7) << 10) | ((freshness & 0x7) << 13)) - - -def _unpack_infra(value, width, height): - if value == 0: - return None - x = value & 0x1F - y = (value >> 5) & 0x1F - type_code = (value >> 10) & 0x7 - freshness = (value >> 13) & 0x7 - if x >= width or y >= height or type_code not in TARGET_VALUE: - return None - return (x, y, type_code, freshness) - - -def _pack_turret(x, y, type_code): - if type_code not in TURRET_TYPES: - return 0 - return (x & 0x1F) | ((y & 0x1F) << 5) | ((type_code & 0x7) << 10) - - -def _unpack_turret(value, width, height): - if value == 0: - return None - x = value & 0x1F - y = (value >> 5) & 0x1F - type_code = (value >> 10) & 0x7 - if x >= width or y >= height or type_code not in TURRET_TYPES: - return None - return (x, y, type_code) - - -def _record_at(word, record_bits, index): - return (word >> (record_bits * index)) & ((1 << record_bits) - 1) - - -def _replace_record(word, record_bits, index, value): - mask = ((1 << record_bits) - 1) << (record_bits * index) - return (word & ~mask) | ((value << (record_bits * index)) & mask) - - -def read_enemy_infra_records(ct, width, height): - """Return the eight decoded enemy-infrastructure store records.""" - records = [] - for slot_offset in range(INFRA_SLOT_COUNT): - word = ct.read_store(INFRA_SLOT_FIRST + slot_offset) - for half in range(2): - records.append( - _unpack_infra(_record_at(word, 16, half), width, height) - ) - return records - - -def publish_enemy_infra_sighting(ct, width, height, pos, type_code): - """Publish one sighting by changing at most one word in slots 8..11.""" - if type_code not in TARGET_VALUE: - return None - now_epoch = _epoch(ct.get_current_round()) - words = [ct.read_store(INFRA_SLOT_FIRST + i) - for i in range(INFRA_SLOT_COUNT)] - records = [] - for word in words: - for half in range(2): - records.append( - _unpack_infra(_record_at(word, 16, half), width, height) - ) - - matching = [i for i, record in enumerate(records) - if record is not None and record[:2] == (pos.x, pos.y)] - if matching: - index = matching[0] - else: - empty = [i for i, record in enumerate(records) if record is None] - if empty: - start = (pos.x * 31 + pos.y * 7 + type_code) & 7 - index = min(empty, key=lambda i: (i - start) & 7) - else: - index = max( - range(8), - key=lambda i: (_age(now_epoch, records[i][3]), - -TARGET_VALUE[records[i][2]], -i), - ) - - slot_offset, half = divmod(index, 2) - packed = _pack_infra(pos.x, pos.y, type_code, now_epoch) - word = _replace_record(words[slot_offset], 16, half, packed) - if word != words[slot_offset]: - slot = INFRA_SLOT_FIRST + slot_offset - ct.write_store(slot, word & 0xFFFFFFFF) - return slot - return None - - -def clear_enemy_infra_sighting(ct, width, height, pos): - """Clear one matching sighting by changing at most one word.""" - for slot_offset in range(INFRA_SLOT_COUNT): - slot = INFRA_SLOT_FIRST + slot_offset - word = ct.read_store(slot) - for half in range(2): - record = _unpack_infra( - _record_at(word, 16, half), width, height - ) - if record is None or record[:2] != (pos.x, pos.y): - continue - ct.write_store(slot, _replace_record(word, 16, half, 0)) - return slot - return None - - -def infra_sighting_age_rounds(round_number, freshness): - """Return the lower-bound age represented by an infra freshness epoch.""" - return _age(_epoch(round_number), freshness) * FRESHNESS_ROUNDS - - -def in_turret_envelope(origin, target, type_code, facing): - """Return whether a turret facing covers a target under dev23 geometry.""" - if facing not in FACINGS: - return False - return _in_envelope(origin, target, type_code, FACINGS.index(facing)) - - -def _pack_proposal(proposal): - if proposal is None: - return 0 - # derive the generation from the proposal itself so independently running - # builder planners publish an identical word for an identical top choice. - generation = ((proposal.x * 3 + proposal.y * 5 - + proposal.turret_type * 7 - + FACINGS.index(proposal.facing) * 11 - + int(proposal.score)) & 0xF) - type_code = proposal.turret_type - facing_code = FACINGS.index(proposal.facing) - score_band = max(-512, min(511, int(proposal.score))) + 512 - value = ((proposal.x & 0x1F) - | ((proposal.y & 0x1F) << 5) - | ((type_code & 0x7) << 10) - | ((facing_code & 0x3) << 13) - | (int(bool(proposal.frontier)) << 15) - | (int(bool(proposal.urgent)) << 16) - | (1 << 17) - | ((score_band & 0x3FF) << 18) - | ((generation & 0xF) << 28)) - return value & 0xFFFFFFFF - - -def _unpack_proposal(value, width, height): - if not ((value >> 17) & 1): - return None - x = value & 0x1F - y = (value >> 5) & 0x1F - type_code = (value >> 10) & 0x7 - facing_code = (value >> 13) & 0x3 - if x >= width or y >= height or type_code not in TURRET_TYPES: - return None - score = ((value >> 18) & 0x3FF) - 512 - frontier = bool((value >> 15) & 1) - urgent = bool((value >> 16) & 1) - generation = (value >> 28) & 0xF - return Proposal(x, y, type_code, FACINGS[facing_code], score, - frontier, urgent, frontier, generation) - - -def _proposal_fingerprint(value): - return (value ^ (value >> 14)) & 0x3FFF - - -def _pack_claim(builder_num, distance, generation, round_number, proposal_word): - return (((builder_num - 1) & 0x7) - | ((min(distance, 63) & 0x3F) << 3) - | ((generation & 0xF) << 9) - | ((round_number & 0xF) << 13) - | (1 << 17) - | (_proposal_fingerprint(proposal_word) << 18)) - - -def _unpack_claim(value, proposal_word, round_number): - if not ((value >> 17) & 1): - return None - if ((value >> 18) & 0x3FFF) != _proposal_fingerprint(proposal_word): - return None - generation = (value >> 9) & 0xF - if generation != ((proposal_word >> 28) & 0xF): - return None - claim_round = (value >> 13) & 0xF - claim_age = ((round_number & 0xF) - claim_round) & 0xF - if claim_age > CLAIM_MAX_AGE: - return None - return ((value & 0x7) + 1, (value >> 3) & 0x3F) - - -def _in_envelope(origin, target, type_code, facing_index): - """Raw dev23 turret geometry; heuristic distances elsewhere are Manhattan.""" - dx = target[0] - origin[0] - dy = target[1] - origin[1] - forward_x, forward_y = FACING_DELTAS[facing_index] - forward = dx * forward_x + dy * forward_y - if forward <= 0: - return False - if type_code == INFRA_GUNNER: - lateral = abs(dx * forward_y - dy * forward_x) - return lateral == 0 and dx * dx + dy * dy <= 13 - lateral = abs(dx * forward_y - dy * forward_x) - return lateral <= 1 and dx * dx + dy * dy <= 32 - - -def _covered_by_unknown_facing(turret, tile): - origin = (turret[0], turret[1]) - type_code = turret[2] - for facing_index in range(4): - if _in_envelope(origin, tile, type_code, facing_index): - return True - return False - - -class TurretPlanner: - """Per-builder shared-cache reader and resumable scorer.""" - - def __init__(self, width, height, team_core): - self.width = width - self.height = height - self.team_core = (team_core.x, team_core.y) - - self.infra_memory = {} - self.turret_memory = {} - self.pending_infra = {} - self.pending_turrets = {} - self.current_proposal = None - self.current_claim = None - self._proposal_word = 0 - - self._infra_records = [None] * 8 - self._turret_records = [None] * 4 - self._raw_infra_words = [0] * 4 - self._raw_turret_words = [0] * 2 - - self._dependency_signature = None - self._scan_context = None - self._scan_cursor = 0 - self._scan_complete = False - self._top = [] - self.score_cache = {} - - def shared_infra_records(self): - return tuple(record for record in self._infra_records - if record is not None) - - # ------------------------------------------------------------------ store - - def sync_and_observe(self, ct): - """Read the next-round snapshot, verify visible entries, and retry one write.""" - now_epoch = _epoch(ct.get_current_round()) - writes = {} - - self._raw_infra_words = [ - ct.read_store(INFRA_SLOT_FIRST + i) for i in range(INFRA_SLOT_COUNT) - ] - self._infra_records = [] - shared_positions = set() - for word in self._raw_infra_words: - for half in range(2): - record = _unpack_infra(_record_at(word, 16, half), - self.width, self.height) - self._infra_records.append(record) - if record is None: - continue - x, y, type_code, freshness = record - if _age(now_epoch, freshness) <= MAX_FRESH_AGE: - old = self.infra_memory.get((x, y)) - if (old is None - or _age(now_epoch, freshness) < _age(now_epoch, old[1])): - self.infra_memory[(x, y)] = (type_code, freshness) - shared_positions.add((x, y, type_code)) - - for key, (_, freshness) in list(self.infra_memory.items()): - if _age(now_epoch, freshness) > MAX_FRESH_AGE: - del self.infra_memory[key] - for key, record in list(self.pending_infra.items()): - if (key[0], key[1], record[0]) in shared_positions: - del self.pending_infra[key] - - self._raw_turret_words = [ - ct.read_store(TURRET_SLOT_FIRST + i) & 0x03FFFFFF - for i in range(TURRET_SLOT_COUNT) - ] - self._turret_records = [] - shared_turrets = set() - for word in self._raw_turret_words: - for half in range(2): - record = _unpack_turret(_record_at(word, 13, half), - self.width, self.height) - self._turret_records.append(record) - if record is not None: - x, y, type_code = record - self.turret_memory[(x, y)] = type_code - shared_turrets.add(record) - for key, type_code in list(self.pending_turrets.items()): - if (key[0], key[1], type_code) in shared_turrets: - del self.pending_turrets[key] - - self._proposal_word = ct.read_store(PROPOSAL_SLOT) - self.current_proposal = _unpack_proposal( - self._proposal_word, self.width, self.height - ) - self.current_claim = None - if self.current_proposal is not None: - self.current_claim = _unpack_claim( - ct.read_store(PROPOSAL_CHECK_SLOT), self._proposal_word, - ct.get_current_round() - ) - - # empty or changed visible tiles explicitly clear stale cache records - verify_infra = {key: value[0] - for key, value in self.infra_memory.items()} - for record in self._infra_records: - if record is not None: - verify_infra[(record[0], record[1])] = record[2] - for (x, y), type_code in list(verify_infra.items()): - pos = Position(x, y) - if not ct.is_in_vision(pos): - continue - building_id = ct.get_tile_building_id(pos) - actual = None - if building_id is not None and ct.get_team(building_id) != ct.get_team(): - actual = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) - if actual != type_code: - self.infra_memory.pop((x, y), None) - self.pending_infra.pop((x, y), None) - self._clear_infra_position(x, y, writes) - - for (x, y), type_code in list(self.turret_memory.items()): - pos = Position(x, y) - if not ct.is_in_vision(pos): - continue - building_id = ct.get_tile_building_id(pos) - actual = None - if building_id is not None and ct.get_team(building_id) == ct.get_team(): - actual = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) - if actual != type_code: - del self.turret_memory[(x, y)] - self.pending_turrets.pop((x, y), None) - self._clear_turret_position(x, y, writes) - - # Any builder may originate sightings. Non-model infrastructure is ignored. - for building_id in ct.get_nearby_buildings(): - pos = ct.get_position(building_id) - type_code = ENTITY_TO_INFRA.get(ct.get_entity_type(building_id)) - if type_code is None: - continue - if ct.get_team(building_id) != ct.get_team(): - self.infra_memory[(pos.x, pos.y)] = (type_code, now_epoch) - self.pending_infra[(pos.x, pos.y)] = (type_code, now_epoch) - elif type_code in TURRET_TYPES: - self.turret_memory[(pos.x, pos.y)] = type_code - self.pending_turrets[(pos.x, pos.y)] = type_code - - self._prune_memories(now_epoch) - self._flush_one_infra(writes, now_epoch) - self._flush_one_turret(writes) - for slot, word in writes.items(): - ct.write_store(slot, word & 0xFFFFFFFF) - if INFRA_SLOT_FIRST <= slot < INFRA_SLOT_FIRST + INFRA_SLOT_COUNT: - self._raw_infra_words[slot - INFRA_SLOT_FIRST] = word - elif TURRET_SLOT_FIRST <= slot < TURRET_SLOT_FIRST + TURRET_SLOT_COUNT: - self._raw_turret_words[slot - TURRET_SLOT_FIRST] = word - - def _prune_memories(self, now_epoch): - if len(self.infra_memory) > 8: - ordered = sorted( - self.infra_memory.items(), - key=lambda item: (_age(now_epoch, item[1][1]), - -TARGET_VALUE[item[1][0]], item[0]), - ) - keep = {key for key, _ in ordered[:8]} - for key in list(self.infra_memory): - if key not in keep: - del self.infra_memory[key] - self.pending_infra.pop(key, None) - if len(self.turret_memory) > 4: - enemy_core = self._enemy_core() - ordered = sorted(self.turret_memory, - key=lambda pos: (_manhattan(pos, enemy_core), pos)) - keep = set(ordered[:4]) - for key in list(self.turret_memory): - if key not in keep: - del self.turret_memory[key] - self.pending_turrets.pop(key, None) - - def _clear_infra_position(self, x, y, writes): - for index, record in enumerate(self._infra_records): - if record is None or record[:2] != (x, y): - continue - slot_offset, half = divmod(index, 2) - slot = INFRA_SLOT_FIRST + slot_offset - word = writes.get(slot, self._raw_infra_words[slot_offset]) - word = _replace_record(word, 16, half, 0) - writes[slot] = word - self._infra_records[index] = None - - def _clear_turret_position(self, x, y, writes): - for index, record in enumerate(self._turret_records): - if record is None or record[:2] != (x, y): - continue - slot_offset, half = divmod(index, 2) - slot = TURRET_SLOT_FIRST + slot_offset - word = writes.get(slot, self._raw_turret_words[slot_offset]) - word = _replace_record(word, 13, half, 0) - writes[slot] = word - self._turret_records[index] = None - - def _flush_one_infra(self, writes, now_epoch): - if not self.pending_infra: - return - key, (type_code, freshness) = min( - self.pending_infra.items(), - key=lambda item: (_age(now_epoch, item[1][1]), - -TARGET_VALUE[item[1][0]], item[0]), - ) - x, y = key - matching = [i for i, record in enumerate(self._infra_records) - if record is not None and record[:2] == (x, y)] - if matching: - index = matching[0] - else: - empty = [i for i, record in enumerate(self._infra_records) - if record is None] - if empty: - # A stable hash spreads simultaneous observers across the eight records. - start = (x * 31 + y * 7 + type_code) & 7 - index = min(empty, key=lambda i: (i - start) & 7) - else: - index = max( - range(8), - key=lambda i: (_age(now_epoch, self._infra_records[i][3]), - -TARGET_VALUE[self._infra_records[i][2]]), - ) - slot_offset, half = divmod(index, 2) - slot = INFRA_SLOT_FIRST + slot_offset - word = writes.get(slot, self._raw_infra_words[slot_offset]) - packed = _pack_infra(x, y, type_code, freshness) - writes[slot] = _replace_record(word, 16, half, packed) - self._infra_records[index] = (x, y, type_code, freshness) - - def _flush_one_turret(self, writes): - if not self.pending_turrets: - return - key, type_code = min(self.pending_turrets.items()) - x, y = key - matching = [i for i, record in enumerate(self._turret_records) - if record is not None and record[:2] == (x, y)] - if matching: - index = matching[0] - else: - empty = [i for i, record in enumerate(self._turret_records) - if record is None] - if empty: - index = empty[0] - else: - # with no freshness bits available, deterministic replacement is - # the only collision-safe policy that still admits newly built turrets. - index = (x * 5 + y * 3 + type_code) & 3 - slot_offset, half = divmod(index, 2) - slot = TURRET_SLOT_FIRST + slot_offset - word = writes.get(slot, self._raw_turret_words[slot_offset]) - packed = _pack_turret(x, y, type_code) - writes[slot] = _replace_record(word, 13, half, packed) - self._turret_records[index] = (x, y, type_code) - - # --------------------------------------------------------------- behaviour - - def can_afford(self, ct, proposal=None): - proposal = proposal or self.current_proposal - if proposal is None: - return False - if proposal.turret_type == INFRA_GUNNER: - cost = ct.get_gunner_cost() - else: - cost = ct.get_sentinel_cost() - return ct.get_global_resources() >= cost + TITANIUM_RESERVE - - def try_passive_build(self, ct): - proposal = self.current_proposal - if proposal is None or not self.can_afford(ct, proposal): - return None - pos = Position(proposal.x, proposal.y) - if ct.get_position().distance_squared(pos) > 2: - return None - if ct.get_action_cooldown() != 0: - return None - # harvesters/conveyors first when ti is scarce (long econ games) - if ct.get_global_resources() < 150: - return None - if proposal.turret_type == INFRA_GUNNER: - if not ct.can_build_gunner(pos, proposal.facing): - return None - ct.build_gunner(pos, proposal.facing) - else: - if not ct.can_build_sentinel(pos, proposal.facing): - return None - ct.build_sentinel(pos, proposal.facing) - self.record_built(ct, proposal) - return proposal - - def record_built(self, ct, proposal): - key = (proposal.x, proposal.y) - self.turret_memory[key] = proposal.turret_type - self.pending_turrets[key] = proposal.turret_type - matching = [i for i, record in enumerate(self._turret_records) - if record is not None and record[:2] == key] - if matching: - index = matching[0] - else: - empty = [i for i, record in enumerate(self._turret_records) - if record is None] - index = (empty[0] if empty else - (proposal.x * 5 + proposal.y * 3 - + proposal.turret_type) & 3) - slot_offset, half = divmod(index, 2) - packed = _pack_turret(proposal.x, proposal.y, proposal.turret_type) - word = _replace_record(self._raw_turret_words[slot_offset], - 13, half, packed) - self._raw_turret_words[slot_offset] = word - self._turret_records[index] = ( - proposal.x, proposal.y, proposal.turret_type - ) - ct.write_store(TURRET_SLOT_FIRST + slot_offset, word) - # stop same-round followers from perpetuating the now-occupied proposal - ct.write_store(PROPOSAL_SLOT, 0) - ct.write_store(PROPOSAL_CHECK_SLOT, 0) - - def should_pursue_urgent(self, ct, my_num): - proposal = self.current_proposal - if (proposal is None or my_num < 1 or my_num > 8 - or not proposal.urgent or not proposal.frontier - or not self.can_afford(ct, proposal) - or ct.get_action_cooldown() != 0 - or ct.get_move_cooldown() != 0): - return False - target = (proposal.x, proposal.y) - my_pos = ct.get_position() - my_distance = _manhattan((my_pos.x, my_pos.y), target) - if my_pos.distance_squared(Position(*target)) <= 2: - return False - candidate = (my_distance, my_num) - claimed = None - if self.current_claim is not None: - claimed = (self.current_claim[1], self.current_claim[0]) - spawn_count = ct.read_store(0) - elected = (spawn_count > 0 - and ct.get_current_round() % spawn_count + 1 == my_num) - if (elected and (claimed is None or candidate < claimed - or self.current_claim[0] == my_num)): - claim_word = _pack_claim( - my_num, my_distance, proposal.generation, - ct.get_current_round(), self._proposal_word - ) - ct.write_store(PROPOSAL_CHECK_SLOT, claim_word) - # One elected builder updates the monotone minimum each round. A new - # better claim becomes visible next round; once visible only that - # builder paths. Dead/non-idle claimants eventually age out. - return self.current_claim is not None and self.current_claim[0] == my_num - - # ---------------------------------------------------------------- scoring - - def advance(self, ct, full_map, my_num): - """Incrementally rescore only during the same 6000-us spare-cycle cap.""" - analysis_job = mapanalysis.job - if (analysis_job is None or analysis_job.phase != mapanalysis.DONE - or not mapanalysis.region_partition - or ct.get_cpu_time_elapsed() >= CPU_BUDGET_US): - return - - signature = self._make_dependency_signature(analysis_job.version) - if signature != self._dependency_signature: - self._start_scan(full_map, signature) - - total = self.width * self.height * 2 - while (self._scan_cursor < total - and ct.get_cpu_time_elapsed() < CPU_BUDGET_US): - item = self._scan_cursor - self._scan_cursor += 1 - tile_index, kind = divmod(item, 2) - x, y = divmod(tile_index, self.height) - type_code = INFRA_GUNNER if kind == 0 else INFRA_SENTINEL - proposal = self._score_tile(x, y, type_code, full_map) - self.score_cache[(x, y, type_code)] = proposal - if proposal is not None: - self._top.append(proposal) - self._top.sort(key=self._proposal_sort_key, reverse=True) - del self._top[3:] - - if self._scan_cursor >= total: - self._scan_complete = True - self._publish_if_writer(ct, my_num) - - def _make_dependency_signature(self, analysis_version): - infra = tuple(sorted((x, y, value[0]) - for (x, y), value in self.infra_memory.items())) - turrets = tuple(sorted((x, y, type_code) - for (x, y), type_code in self.turret_memory.items())) - chokes = tuple(mapanalysis.chokes) - return (analysis_version, infra, turrets, chokes) - - def _start_scan(self, full_map, signature): - self._dependency_signature = signature - self._scan_cursor = 0 - self._scan_complete = False - self._top = [] - self.score_cache = {} - self._scan_context = self._build_region_context(full_map) - - def _enemy_core(self): - cores = [(pos, value[1]) for pos, value in self.infra_memory.items() - if value[0] == INFRA_CORE] - if cores: - return min(cores, key=lambda item: _manhattan(self.team_core, item[0]))[0] - # slot 7's symmetry encoding belongs to the map layer and is not defined - # here. Until an exact core sighting arrives, rotational opposition is - # the only estimate that consumes no extra store bits. - return (self.width - 1 - self.team_core[0], - self.height - 1 - self.team_core[1]) - - def _build_region_context(self, full_map): - partition = mapanalysis.region_partition - regions = set(value for value in partition if value >= 0) - graph = {region: set() for region in regions} - choke_regions = {} - - for choke in mapanalysis.chokes: - tile, clearance = choke[0], choke[1] - adjacent = set() - max_radius = max(1, clearance + 2) - for radius in range(1, max_radius + 1): - for dx in range(-radius, radius + 1): - dy = radius - abs(dx) - for signed_dy in ({dy, -dy} if dy else {0}): - x, y = tile[0] + dx, tile[1] + signed_dy - if 0 <= x < self.width and 0 <= y < self.height: - region = partition[x * self.height + y] - if region >= 0: - adjacent.add(region) - if len(adjacent) >= 2: - break - adjacent = tuple(sorted(adjacent)) - choke_regions[tile] = adjacent - for left in adjacent: - for right in adjacent: - if left != right: - graph[left].add(right) - - own_region = self._nearest_region(self.team_core, partition) - reachable = set() - if own_region >= 0: - stack = [own_region] - reachable.add(own_region) - while stack: - region = stack.pop() - for other in graph.get(region, ()): - if other not in reachable: - reachable.add(other) - stack.append(other) - - enemy_core = self._enemy_core() - own_distance = {region: self.width + self.height + 1 for region in regions} - enemy_distance = dict(own_distance) - for x in range(self.width): - for y in range(self.height): - region = partition[x * self.height + y] - if region < 0: - continue - own_distance[region] = min(own_distance[region], - _manhattan((x, y), self.team_core)) - enemy_distance[region] = min(enemy_distance[region], - _manhattan((x, y), enemy_core)) - enemy_side = {region for region in regions - if enemy_distance[region] < own_distance[region]} - frontier_regions = set() - frontier_chokes = {} - for choke, adjacent in choke_regions.items(): - own_adjacent = [r for r in adjacent if r not in enemy_side] - enemy_adjacent = [r for r in adjacent if r in enemy_side] - if own_adjacent and enemy_adjacent: - for region in own_adjacent: - frontier_regions.add(region) - frontier_chokes.setdefault(region, set()).add(choke) - - return { - "partition": partition, - "reachable": reachable, - "enemy_core": enemy_core, - "frontier_regions": frontier_regions, - "frontier_chokes": frontier_chokes, - } - - def _nearest_region(self, origin, partition): - best = -1 - best_distance = self.width + self.height + 1 - for x in range(self.width): - for y in range(self.height): - region = partition[x * self.height + y] - if region < 0: - continue - distance = _manhattan(origin, (x, y)) - if distance < best_distance: - best, best_distance = region, distance - return best - - def _score_tile(self, x, y, type_code, full_map): - context = self._scan_context - if full_map[x][y] != 0: - return None - tile = (x, y) - if _manhattan(tile, self.team_core) <= CORE_SPAWN_RING_MANHATTAN: - return None - if tile in self.turret_memory or tile in self.infra_memory: - return None - region = context["partition"][x * self.height + y] - if region < 0 or region not in context["reachable"]: - return None - - targets = [(pos, value[0]) for pos, value in self.infra_memory.items()] - best_facing = -1 - best_face_terms = None - best_frontier_choke = False - for facing_index in range(4): - covered_value = sum( - TARGET_VALUE[target_type] - for target_pos, target_type in targets - if _in_envelope(tile, target_pos, type_code, facing_index) - ) - covered_chokes = [ - choke for choke in mapanalysis.chokes - if _in_envelope(tile, choke[0], type_code, facing_index) - ] - choke_coverage = len(covered_chokes) - frontier_choke = any( - choke[0] in context["frontier_chokes"].get(region, ()) - for choke in covered_chokes - ) - approach = self._approach_tiebreak(tile, facing_index) - terms = (W_COVER * covered_value + W_CHOKE * choke_coverage, - covered_value, choke_coverage, approach, -facing_index) - if best_face_terms is None or terms > best_face_terms: - best_face_terms = terms - best_facing = facing_index - best_frontier_choke = frontier_choke - - face_score, covered_value, choke_coverage = best_face_terms[:3] - # A defensive emplacement must attack remembered infrastructure or a - # detected corridor; safety distance alone may not create backfield spam. - if covered_value == 0 and choke_coverage == 0: - return None - - mutual = sum(1 for turret in self._friendly_turrets() - if _covered_by_unknown_facing(turret, tile)) - exposure = sum(1 for turret in self._enemy_turrets() - if _covered_by_unknown_facing(turret, tile)) - safe_distance = _manhattan(tile, context["enemy_core"]) - frontier = region in context["frontier_regions"] - score = (face_score + W_MUTUAL * mutual + W_SAFE * safe_distance - - W_EXPOSED * exposure - - (0 if frontier else W_FRONTIER_PENALTY)) - urgent = score >= URGENT_THRESHOLD and best_frontier_choke - return Proposal(x, y, type_code, FACINGS[best_facing], score, - frontier, urgent, best_frontier_choke, 0) - - def _friendly_turrets(self): - return [(x, y, type_code) - for (x, y), type_code in self.turret_memory.items()] - - def _enemy_turrets(self): - return [(x, y, value[0]) for (x, y), value in self.infra_memory.items() - if value[0] in TURRET_TYPES] - - def _approach_tiebreak(self, tile, facing_index): - enemy = self._scan_context["enemy_core"] - dx = enemy[0] - tile[0] - dy = enemy[1] - tile[1] - face_dx, face_dy = FACING_DELTAS[facing_index] - return dx * face_dx + dy * face_dy - - @staticmethod - def _proposal_sort_key(proposal): - # prefer exact model score; deterministic secondary keys keep every - # builder converged even if its cache iteration order differs. - return (proposal.score, proposal.frontier_choke, - proposal.turret_type == INFRA_SENTINEL, - -proposal.x, -proposal.y, -FACINGS.index(proposal.facing)) - - def _publish_if_writer(self, ct, my_num): - spawn_count = ct.read_store(0) - if spawn_count <= 0 or my_num < 1: - return - elected = ct.get_current_round() % spawn_count + 1 - if elected != my_num: - return - proposal = self._top[0] if self._top else None - value = _pack_proposal(proposal) - if ct.read_store(PROPOSAL_SLOT) != value: - ct.write_store(PROPOSAL_SLOT, value) - ct.write_store(PROPOSAL_CHECK_SLOT, 0) - - def draw_debug(self, ct): - proposals = self._top - if not proposals and self.current_proposal is not None: - proposals = [self.current_proposal] - for proposal in proposals[:3]: - ct.draw_indicator_dot(Position(proposal.x, proposal.y), 0, 80, 255) From e0ad1324b76cc8ca4b67bed3cf90f4ef21bb19de Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 21:15:18 +0530 Subject: [PATCH 23/29] khaos P3-P6: vision-delta, attack-route, combat micro, econ polish Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 25479 -> 27463 bytes main.py | 151 +++++++++++++++++++++++++++++++++++++++++++++++++---- mapinfo.py | 24 ++++++++- 3 files changed, 162 insertions(+), 13 deletions(-) diff --git a/bot.zip b/bot.zip index d71de6a567dbc94f113940e8ad4de62faf56e443..cdc5ac5bcfc4aeb5b3139e0bb319280f0189849e 100644 GIT binary patch delta 16806 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 wJA6DcJlwmY_McWm39@1L5gsi~TMdFov4+81X% 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 expiry round self.stuck = 0 self.lastPos = None @@ -60,6 +62,7 @@ def __init__(self): # turret / launcher state self.idleTurns = 0 self.enemyBotHistory = {} + self.tapWait = 0 # sentinel one-tap coordination hold counter # ------------------------------------------------------------------ def run(self, ct: Controller) -> None: @@ -426,11 +429,69 @@ def _bestPlant(self, ct, mi): self._plantCache = (key, best) return best + def _enemyFieldCached(self, mi, guess): + key = (mi.struct_version, guess.x, guess.y) + if self.enemyField is not None and self.enemyFieldVer == key: + return self.enemyField + m = 0 + for dx in (0, 1): + for dy in (0, 1): + x, y = guess.x + dx, guess.y + dy + if 0 <= x < mi.w and 0 <= y < mi.h: + m |= mi.bit(x, y) + self.enemyField = path.dist_field(mi, mi.expand(m)) + self.enemyFieldVer = key + return self.enemyField + def _run_attack(self, ct, mi, data): kind, payload = data pos = ct.get_position() rnd = ct.get_current_round() if kind == 'core': + # ATTACK ROUTE (Pantheon override): while marching on the enemy + # core, lay a conveyor trail once we're geographically ahead + # (dist-to-them <= 1.5x dist-home). Walkable for us, area denial + # for them; planes convert the head into a gunner on contact. + if (ct.get_action_cooldown() == 0 + and ct.get_global_resources() + >= ct.get_conveyor_cost() + 120): + ef = self._enemyFieldCached(mi, payload) + hf = self._routeFieldCached(ct) + idx = pos.x + pos.y * mi.w + if (ef is not None and hf is not None + and ef[idx] < 4096 and hf[idx] < 4096 + and ef[idx] <= 1.5 * hf[idx]): + best = None + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + nx, ny = pos.x + dx, pos.y + dy + if not (0 <= nx < mi.w and 0 <= ny < mi.h): + continue + b = mi.bit(nx, ny) + if (mi.walls | mi.blocked | mi.own_conveyors + | mi.own_barriers | mi.ore) & b: + continue + d = ef[nx + ny * mi.w] + if best is None or d < best[0]: + best = (d, nx, ny) + if best is not None and best[0] < ef[idx]: + _, lx, ly = best + link = Position(lx, ly) + # face further downhill toward the enemy core + fb = None + for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): + fx, fy = lx + dx, ly + dy + if not (0 <= fx < mi.w and 0 <= fy < mi.h): + continue + fd = ef[fx + fy * mi.w] + if fb is None or fd < fb[0]: + fb = (fd, dx, dy) + if fb is not None: + facing = DELTA_TO_DIR[(fb[1], fb[2])] + if (ct.is_in_vision(link) + and ct.can_build_conveyor(link, facing)): + ct.build_conveyor(link, facing) + mi.own_conveyors |= mi.bit(lx, ly) + mi.own_conv_facing[(lx, ly)] = (fb[1], fb[2]) m = mi.bit(payload.x, payload.y) self._moveToward(ct, mi.expand(mi.expand(m))) return @@ -512,7 +573,13 @@ def _score_heal(self, ct, mi, my_bit, others): mine = path.claims(mi, my_bit, others, damaged) if mine: return 8.0, ('heal', mine) - intruders = mi.enemy_bots & mi.expand(mi.expand(mi.own_conveyors)) + # Pantheon chase zone: 8 pathing steps around our conveyor network + # (was 2 tiles) — intruders get hunted before they reach the chain. + zone = mi.own_conveyors + passable = mi.passable() + for _ in range(8): + zone = mi.expand(zone) & (passable | zone) + intruders = mi.enemy_bots & zone if intruders: mine = path.claims(mi, my_bit, others, intruders) if mine: @@ -585,11 +652,15 @@ def _routeFieldCached(self, ct): return None # Pantheon merge targets: the core PLUS every conveyor already # connected to it — new chains join existing trunks instead of each - # harvester paying for a private line the whole way home. + # harvester paying for a private line the whole way home. LOADED + # trunk tiles are excluded (P6): a full conveyor can't accept a new + # stack, so merging there jams the junction (their +4 penalty, made + # binary by dev23's hold-1-stack rule). self._connected = self._connectedConveyors(mi) targets = coreMask for (x, y) in self._connected: targets |= mi.bit(x, y) + targets &= ~mi.own_loaded | coreMask self.routeField = path.dist_field(mi, targets) self.routeVer = key return self.routeField @@ -696,6 +767,15 @@ def _run_route(self, ct, mi, mask): root = (hx, hy) # dead-end conveyor target: walk from it else: root = self._chainRoot(mi, hx, hy) + # P6 cost gate (Pantheon cost(d)): before STARTING a fresh chain, + # check we can afford ~the whole line. 0.65 discount — income + # keeps arriving while we build. + d = field[hx + hy * mi.w] + if d < 4096: + est = 3 * d * ct.get_scale_percent() / 100 * 0.65 + if ct.get_global_resources() < est: + self.ttl[('route', hx, hy)] = rnd + 30 + return if root is None: link, facing = self._downhillLink(mi, field, hx, hy) else: @@ -1018,6 +1098,11 @@ def _healAdjacent(self, ct): if ct.get_hp() < ct.get_max_hp() and ct.can_heal(pos): ct.heal(pos) return + # Pantheon 6-tier heal priority: (very damaged?, type tier, damage) + TIER = {EntityType.CORE: 6, EntityType.GUNNER: 5, + EntityType.SENTINEL: 5, EntityType.LAUNCHER: 5, + EntityType.HARVESTER: 4, EntityType.CONVEYOR: 3, + EntityType.SPLITTER: 2, EntityType.BARRIER: 2} best = None for bid in ct.get_nearby_buildings(2): if ct.get_team(bid) != ct.get_team(): @@ -1025,8 +1110,10 @@ def _healAdjacent(self, ct): dmg = ct.get_max_hp(bid) - ct.get_hp(bid) if dmg >= 4: p = ct.get_position(bid) - if best is None or dmg > best[0]: - best = (dmg, p) + key = (1 if dmg >= 12 else 0, + TIER.get(ct.get_entity_type(bid), 1), dmg) + if best is None or key > best[0]: + best = (key, p) if best is not None and ct.can_heal(best[1]): ct.heal(best[1]) @@ -1099,7 +1186,30 @@ def sentinel(self, ct: Controller) -> None: key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, -targetHp) if bestTile is None or key > bestKey: bestKey, bestTile = key, tile - if bestTile is not None and ct.can_fire(bestTile): + if bestTile is None: + return + # Pantheon one-tap lock: if the target survives one shot and a + # LOWER-id ally sentinel also covers it, hold fire — lower id acts + # first, so next turn both shots land the same round (36 dmg kills + # anything but a core chunk) before enemy medics can heal. Bounded + # wait so a dead ally can't deadlock us. + targetHp = 0 + bid = ct.get_tile_building_id(bestTile) \ + if ct.is_in_vision(bestTile) else None + if bid is not None: + targetHp = ct.get_hp(bid) + if targetHp > 18 and self.tapWait < 2: + myId = ct.get_id() + myPos = ct.get_position() + for aid in ct.get_nearby_entities(): + if (aid < myId and ct.get_team(aid) == ct.get_team() + and ct.get_entity_type(aid) == EntityType.SENTINEL + and ct.get_position(aid).distance_squared(bestTile) + <= 32): + self.tapWait += 1 + return + self.tapWait = 0 + if ct.can_fire(bestTile): ct.fire(bestTile) def gunner(self, ct: Controller) -> None: @@ -1179,13 +1289,32 @@ def launcher(self, ct: Controller) -> None: if bot_id is None or ct.get_team(bot_id) == team: continue walked = set(hist.get(bot_id, ())[:-3]) - best, bestKey = None, (-1, -1) + # Pantheon region-minimizing throw: flood the area the bot + # could navigate from each destination (enemy-POV, unseen = + # impassable) and throw into the SMALLEST pocket. Trail tiles + # (trap: forced retrace) outrank everything. + mi = self.mi + if mi is None: + w, h = ct.get_map_width(), ct.get_map_height() + self.mi = mi = mapinfo_mod.MapInfo(w, h) + mi.update_vision(ct) + epass = mi.seen & ~mi.walls & ~mi.blocked + best, bestKey = None, None for tile in ct.get_nearby_tiles(26): - if ct.can_launch(adjacent, tile): - key = (1 if tile in walked else 0, - manhattan(anchor, tile)) - if key > bestKey: - bestKey, best = key, tile + if not ct.can_launch(adjacent, tile): + continue + seed = mi.bit(tile.x, tile.y) + region = seed + for _ in range(12): + grown = mi.expand(region) & epass + if grown == region: + break + region = grown + size = bin(region).count('1') + key = (1 if tile in walked else 0, + -size, manhattan(anchor, tile)) + if bestKey is None or key > bestKey: + bestKey, best = key, tile if best is not None: ct.launch(adjacent, best) return diff --git a/mapinfo.py b/mapinfo.py index adee395..4f7f7c6 100644 --- a/mapinfo.py +++ b/mapinfo.py @@ -62,6 +62,7 @@ def __init__(self, w: int, h: int): self.enemy_bots = 0 self.own_conveyors = 0 self.own_barriers = 0 # break-walkable at cost (Pantheon 15) + self.own_loaded = 0 # own conveyors holding a stack (jam-avoid) # remembered facings of own conveyors: (x,y) -> (dx,dy) output delta self.own_conv_facing = {} @@ -170,7 +171,12 @@ def note_tile(self, x, y, code, share=True): del self.out_tiles[:32] def update_vision(self, ct): - """Per-turn scan: static tiles into masks, dynamic entities rebuilt.""" + """Per-turn scan: static tiles into masks, dynamic entities rebuilt. + + Vision-delta optimization (Pantheon update_move): env is immutable, so + for already-seen tiles we skip get_tile_env entirely — walls skip ALL + queries (nothing can stand on them), other seen tiles derive env from + the ore mask and only pay the building-id query.""" my_team = ct.get_team() get_env = ct.get_tile_env get_bid = ct.get_tile_building_id @@ -179,10 +185,22 @@ def update_vision(self, ct): self.enemy_bots = 0 self.own_conveyors = 0 self.own_barriers = 0 + self.own_loaded = 0 # own conveyors currently holding a stack + get_stored = ct.get_stored_resource WALKABLE = (EntityType.CONVEYOR, EntityType.SPLITTER) + seen = self.seen + walls = self.walls + ore = self.ore for tile in ct.get_nearby_tiles(): x, y = tile.x, tile.y - env = get_env(tile) + b = 1 << (x + y * self.w) + if seen & b: + if walls & b: + continue # immutable, uninhabitable: zero queries + env = (Environment.ORE_TITANIUM if ore & b + else Environment.EMPTY) + else: + env = get_env(tile) if env == Environment.WALL: note(x, y, T_WALL) continue @@ -200,6 +218,8 @@ def update_vision(self, ct): note(x, y, T_ORE if env == Environment.ORE_TITANIUM else T_EMPTY) if team == my_team: self.own_conveyors |= self.bit(x, y) + if get_stored(bid) is not None: + self.own_loaded |= self.bit(x, y) if etype == EntityType.CONVEYOR: d = DIR_DELTA.get(ct.get_direction(bid)) if d is not None: From c29a4aea631f4c4452c5f1fc28ec4d4c76747caa Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 22:24:13 +0530 Subject: [PATCH 24/29] khaos deviation register cleared Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 27463 -> 29704 bytes main.py | 184 +++++++++++++++++++++++++++++++++++++++++++++++------ mapinfo.py | 12 ++++ planes.py | 47 ++++++++++++-- 4 files changed, 219 insertions(+), 24 deletions(-) diff --git a/bot.zip b/bot.zip index cdc5ac5bcfc4aeb5b3139e0bb319280f0189849e..0f200b0e4ac6dd3419d4f29b21e8a56e1106984b 100644 GIT binary patch delta 20490 zcmV)GK)%1n)&Yp<0e?_S0|XQR000O8iL&-wzYicA7&-s|Z~6cL2LJ#7ZDDC{E^vA6 zed}`DM%LheJw;Dk6M++wk>pDpjg?*5R$|vEl1rkKo$`8FAPI_yKmZGXk~ou@+K1S8 z+5h_}`y_iV-RKJ%AZ5!t^HnXX5>r6;>D%dZ@6%s>_0?Y%VSiSNBATq?WG4R4|M;I` z87;G7okV3R$||g)K{?HeIK2{+tP;VyFs&9*mWr^LF5)VhR;wcF4R>}2uxJ; zMMQtX$MjGjfJl_^DizZ(oy9W%%fL$!r(zbB(+FNe8`CIG;NP9B04U-%t`;H+i)1aL zySVC$VwDPLUVoHOCW*u(DyyM5%fvF9MPiZ`AIcH(8870?Y>lx(@PLt~#$aWdtXS6NyW zS(4zAzJHFPyF9Ry6o$ASt@8+f-^4I}oZ`NCmz6a38Z^van1;!^j7#HHoX#^5mg?si z)=z!S!)j6gPQo;*>y_)}GOCL8&d#gji`Q?@j!!Sek@!$|Yj}2kF?wV4b9j0F-hOd% zd^s}z{`KT?^l|6)$?52LJUSo0etQ8B|Gp#O34iwbVz3YM-++%_><`=ySKIH4J?kU4 z84Ma9u<`!RKX!J0Iz4}Rd^$e<`RDWT%lAM2c!Iq+*xUK}_`i(L-<_Pn%eSYmPcCq{ zPj>*H#uw-B&tBu7`+Ivkuim{MU!MOs8efji-@&_QPxtnOcmx|P%Z1v!Np=exO76Kl z1b^(P7BMbBx)!T)X9sY5j(;r>-O?cJi%IX0MpQ(!FTzO~gu^>A5R>6uPkbX^tm(zN z2M_@Lfo8m;y~0%y=6D>ZaWx(XWt7Y%NPO_m;WCE)o@Hs|yk4KJmhfe;Z@*7h%ga2x zO=0&Pi9P#U6@|-Jh}%AZ>f;D@S?MhirGL?K-C7?|Z&bi8gemlWp0APw$wbGuU#O}( zAlk)0#!)i!)}_z>nq#0uX+(tR2;Njd50^Z<1qz#`H_z%6k-}7>EX_L*3_I` z9u^Fu8dTYUJ`adD&8bizNi9u#uML)eJ*p}}((w;rJ&!~{Tdpte`eNM^gCCFt$G{n( zjdbQ9S+#=38;A|Nssgg=$6BW6BY)y4Ziz?8&|vq9Z-IZUQkWh|0BjiXEQv<2!^>vV zH0YOE^#*1PWC9wXGwd+`HH@)oCONPYaT+B8`Z0iSfNH?W<1|Eq2Z#idJcWT*QL(eb zT^xM&Q&Soc4v5I~#k4v!I2TPj&Wt8LQ3b3EIz6q1FmdCE*x49g272A&xqm8mWH7wp ztMiK!4XK_9#G_zZ>FQC^0zgvp@yqj(2aeoX+kv_~IUBt_J2~~hQA@EMpr78Kot<2G zz+A0>%xu}4)8qGNuikjUCvx9~eaz;<~}yQKz{Jry%wlaAqqK(}1UilO!$| z$kT>;0_r{;(RctAzT@DQQ0H%C|6gBsW3rz*w}#A4YbimngAj;{9gCk`UCR_^Dri)4)T_M+p{$cb0Cp3D81tXtIIUuOsCMH z=@i8P)y2KxQ{6}SqfsZ1Nu;sl67@XN^!X5`zbaNJQHB-oI>1TRpFy98U9<*~Lc~=A z2#F{#b$$K?GJh;+p}hZPyRe?o_k9;V@WGXj;u$WzM8*^dz+_Em4CKc@l~L}Y(g3Dw zfv_y2*)Pbb16X@}aR4+38F#Gn2o4gd%6 z-J0I5`CZ$j%)|@5V(ncv7FDe@v^;~q@mwOkbh}bvCxluw1z{w^f|r50-*i| zt&N96R29N>8jZ{U4*E|76DAuV>h_#4C`A0>X>D0axe1nW8tDDi7uV7HC<&L7St#;D zL-)@0SbNRoA?eH2hF^#${-!@eCa8N~#LJL`f0R~+A$0K|Sb$P-!0X%>xAh-)eh`(? zPZX?gkbl+=_rH7I7lXaw-Q|DiZSR9Kz~distcHf$}N3$1~9iAgHmdXH~#3d&o)mhkLCRrR`+^ z<;9<1ywDo-1b#u)Zwc&u5srrI!>TgsL|zT&!+&0LH2{~0|0r%AV6A2=F4}$rd(j8$ zwumd(a}^5Iz>l43rdWs@6a>cdP%Ks;Pm?l_R2}yZOKvDtA%Vv)H*tx2C`jXzA`53z zG=pWSDH$d}6TTB=hS~%)HON+#5&%oKtN=QRDkDqmw^ElKOkm?i#ahdSs#|*NEpn8I z^na$iqc^6EgXE|lPD7NL`1q6>pqE!UoM(be;}2VL9;5f8qJ;rWiYUA`yYKJ1vzXuY zQ7teR(9p<0rVQf}#bvS!DS+?-)0$qM=_5J!%QQApnz=lhsEKhiM6f=s!2l! z!Cu*`gQZ@`HXQvn4~wWvi$UVBl~^4vaet6<!tK&Xp0CwTPe-k8VYL5Aqnh08xdI?Eh^k(1l!prp?`rL zV_Ty+w7iWBDGfS>gZrb{e})~}^E4uDsMm+$hG^b3@JJ+j>f;ay$r3jdz`6mf2YCN5 z8=XbKU?q+nf?OuepSP^TSKT>;$uo}2#--ze7 zh~ferlI6r_Q)=4r%>^TeDe=`Rx`VBXJUgz$O)0Vpt=01)1}z12KOmT;-hX+(_+loL zL-@h^vQ$HL7YIVhr81OE0EGBj{F7_zUPzaiYZ(c?A1cXXOkRgEI|tOeVwK7_y}n)X zxmyuM1+yCd?N&lZu3d|q5N0*@N;K*@)z}kk*5fyB#V3tkveVe=D8F*69=KJdi`lFy zU%6HH-Kx@oVOEu|jH(8Yr+*&e|1YOh4S&7{m8917_pk-a+<-d*z3F}S9?eqi=ytvuSMd@?mxMX&akR_{+e?e=njBb$qPk5ZAjQvk zxNpQ3uNLtAWs*&=Tgu=*Jf8g)VhD!I z+DpLwi0WyFbl>(RyWqmv4Vns$rCr?hbO@3!KyOHLq)V32B4rOHvl!Cazp2e1!(ieo zAS)ibzX5xg0o}zF7=KR}VR{ucs4PCx1cDAyKocwt8qJ~A9N-sf5XB0kjX=0YHt9Fp z*iia3KfR`8Bm)f4jq8y^RQLhRAZ);yQbf@(tpz2in#+;y=(RM3zm65H;}yK?`pGI^ z;IS7@n)X6;Vz%48v_W61j;_(NMR#;#FEp;p)*{lVL`dcxS^! zomw;SV2{>U0O#Ck>_#a@*hVEIw8dMk8ULc2jC{uT-CaM4_1BMjybB$M%W z9s{K(n+zgi!;m+?S zSX&SlE$cnOh<_~c;)Mv%Qn%(#-nKdhkZmHkGKx|VO($Kxn=WHKD+RY4K7c zSff<$H_MZ08HH*jpP+c^xgMZ{UE448 z;Fs3sbBS$uhxYp^dI|UU&@B_El1ogHLhkRooYSSX62RI4u%LH70CKVda!eY2Npea` zpCtJJ_xAi`Whs8IEejO1=V}c)QgZ2H-_k%mM1LC}>U}l`H>SHe`7+{xBa|C6E5jT- zU-7}wHkV!oA~@?$Aql>TrwxsJowj+s#n=31ao;(&Cst}wOVAG~=F?W9@bxIWg$-3p zPJOlc#u&=q7vW77&yMzNimMbQs1B=^C9~`{4M@h@>y)6pE4j%wTUHc5fKl$l6!hLP zzJCZ%L$>W$-k#fH6+&ty+h|5McvP2xqgv31E1{)`tOx=i2eRH7)EN zDsw9KV_rm2ALeKoEhjR(PQyZ?Nyg1GLwl?;n8xUT19E#C?P8D~bTJ1ioXzJ$t3GaM z-hFZUk}RSDHtZLy5a*Q5Pj&fggsPZf_ULhj#(6n#U*r1ISHs| zrawl`5r6P|Vyq&K0*%oeiGOZ69w3e}-(@8Ml@m!sp+NqxX5_`XLcq{w0~l%Ew; zGuxS7if=`}Di^q=<3gV$cu+5%WpX#DxyR6{=kOr|PGNzdWbLnK_l%BDeB$K{9^>;P zAi_VMoL#>CTR&05)L$Q9LFg#(90z2vc>j*F?2P`-mUN zzO}y67aTZU#?e)z{c=|;ju~N2?I^lt%m+-$pai4@R?kOy^eG-ww%}uFr{1sTx zRhW3)w37^aG`Njtyk+tt!hf&{90kZ37$qWIvv1yBiqZRvGZBQ+<`1|xS)l-81!7}Z zILIZtrFeDz^Sk4VB7`BRzG1+}vZpsK6J<5G*nxNWrs3Hj8h_GOtWTdwpgh8j zdzS;32^dRz+Ba%PB_Tl9bP#ZNq>_U8g~dMXeMEm5eZ$|Rve}rXhJnS+R%&>DLR7L( zDo5!UcnYdQ8m);mA}7N&e0O^M>g4B>vk@(ayT4m0 zBaNFeywhbjd&5K%34c)DL7Z|Qb!tL8Ar@hPqb@jL_?@qMM9K#D=?4kTD&;q?$$CGA z@YS6m3RA=Hc$S$0`S+iC2^(UpxA)sj6=B%jakN!$g-h&gvq?dX*_tbEmE7c8)1zwPx^Z|X7jSQ$qDidg|pOK?gpya zKmz{;kjVdh_efd+v7qYgcxum@>cp^&D%e}$Dyf2- zzJOu%Xn$zB0Kzc7{MW|h|L&)LlmcSRo9#$^Kp0B|LL ze;S_6e_FoDf3kP6i5K$It&D`GR0#T=vZA%sQ&O?@i`OHo%AO@l*a4>%dz65bgRr?O zY7@#VhW=uV_=L_B$UxFLDDe91?^d=8LKhTh`hVgY=KY$`<}UYz$TmXSSX@35QcU7Q zQBb!sURVC@1SaV;0;NzzGCbIEo2*ffyNzYcZ-ok%-SSo4w>FzcpC{7XrgEA6GjGm5 z=zrq7KkeSr%z;gpi+Em<=LQ=MOX7dS+erRMfmSZq3!Sa1w%CLsT#F9dhS{DGQ1Q4U zCV?SR=K}cMGk0riB{jt$KS3RP_E)J?=%P~Q9T@XpBrF-I)n~kNW6nsml=T$~bwj`| z%O2UlBzgzXjXps(DsXxILjLU?a(gSD@PBBQ@MjbySx++a7K*&oLXp<&ZQH2U?{Q<= zBGOAcUrHdAYL1wjhf|7|0|fnj_j0#46eC_ICTb~YKsBs`I~LOZY^M8w%|(`zGQLXV zc?^;lrnQzML+prf=%)7CLcL_wOzT9T31jA$10 z(KV2uCW?%rojZ-w^VOS_k0jeQRL~OkP?Q%6iLDE{6b&Rf6`$uZk`*k9n zd5rz|*BA*TBe3kaISz zYH5;w2IA}M7|4TEe`hv`B(lpP(L1jWCq%Zh@#9&ti(x@hUAR3 zP2GH}5~0ip{igB23aR2`P+mFTN$|8m%-5ysB3~pJLyk)ZVaUP#zY4x^=YLHaFfd4J zOc}G-h@mCrUTa;Bvx6yhQO2E3jTicajl<-KK^|JP07zW8PO@<3lH^U{7+cBRrua;D z5PKpYiR01e_|;#;#rgZuiIE}`sYC&SYwu9X{7XujnO-Sdbks0iJrc~7@q}1dr*cDC zHBRbK2~Z`piJNj`go`MgIe&lxmFEmvT9V&u`1FpGpOGVKD1Hr->u@4ZxvfAZ0wpDc z$xq`jae$HK1mOHxl0kVEnsaqvTKz|l=1{F@8!hyoR zEge9=8vNFJ)hcR}iZ}lN?I9U|viJQnS=)TIU2Q@h=%uXN7XHj^r?pc|6Z?wtajMKZ zHKt0e*;lDIJ5-MpprmFZ8ar1`YdsZoO=K}`Qp!}3%8s`2=?urFsnEI>_qE-Ag^OGp zIes_euYVV=9iy{T_R_SYWfXR=I}CfPQ6a4aoJeuTQ-!tu+#GUbu2_MNYAGai z_eJ7TfX?QQ3G^I7tIyW4_#$z*y|uAA?<$UjS71_45~mnrREHqTQ?jk)(boh7d%h~@ zFe?(d1qNkQ6r-pafSr%iedd#{Ekz|BY+DgDfHcRsNYuVcHh*j?e5{Q%Z*HvlmKzJ7 zjc7>Sl(;y@Ud}h;qUag9boU;PQ)I>VWYaMd3cqv8KHmyUBi4M_N+^Dx<$$zRzkE4X~Ofm|^0ogO*bpw*K zgu2tsbYVCIia9&z(DXay;kLX!)@G+>{QDDoR%6)ZZc8_u?r2(2XOYDw>YdziAWM+| zf5p~%a^Ff@`Uiy>A00!5`?TelXe;koI`!y?T^@i;e0$$<`>K7?sDJw6qc#$Ma0rUg zJg5CQXn(3>+qUg#^Zq-qlXR+MRZW6OBu0JyS`5^c3-$LpZ(KaQTLT$OO0fGn0gXx*(PS0Y4GuVH~1r_W;%Hd-n5bGut1{ooF@T8N@QFj571BSdDf>P zrQuFSnXkwAP`fj4)K@dUy}1$c_LC%XPEZ-WU=dImh-RNb7h-l-$`|FT=5^=A+glSYEav7k4TYtdK0}kS0 z9$E80f%3otPb8wXe2#RLBIpXf3U47Vj?x&1{SZeDBVGMnaeFSKdB268MrfIPo>s$` z!UVoRNg}8r|4xMrB32LP$OxO|M2~-SA`|PfpR~+%?fWh?Md#B(HdfjnJYW01J#vKC z^G(r4_jG%+5e_SYdThlJeSd~-Y}zsjwHs8%ZzIcPI(^U9)(RHQ1{H19HFS}Gfi>b0 z@76=MP6|xzptN&^JAbCX)J;g=N_r2QR?ITKnazM!P;_S~xA-Ij*&ZG9%JU98Z!lfN zId5%q3GOw=S{&Kd`nZiP0G|!^0NJ>k)giAFCpgxCjaEzR`hNO^(wi*|jNA0lgYFG<)m%m%ORw*U(U4`nPrR5D*n+S-M+^E6n@3%84h1c@8?xbYZ~5W=S}be2!vRaM}qT>9!;d zD&fl}1U43~E?QzD`8C zD&{1^-}2OyW?;O0gLyw96bVwi54)_)Yg6m~^oHWc_%6?YBvS;UdA^yuR$8I+%0wbM zY$jyby5sC%=5Za$k}HeXFJ-Kwy=#(^L{5@7tq&43Q3sYep{OHkEyaI3LI~zy?aOcV z7fLkTFeG!IpPY=gnVm2_u4wr^QFFQj-@w5GmAVNYC`hv^nd-Oydt7j{-p=NG9#-^0^(X+ z5&1##LX@f$zX64)$%ugIO|MHlX;)!1Z#EZ^Es@E~0AQl>aE&$iYK**yZ}5OMyCapk zOWXpb!YJdRI6Xgpee&8A?XV?zt_V*>Mt9Q$M8_G>yJvXay}-PxdR)MgL0k-{(~CtlO*&+~(LBUF8={@o4YFw`k8q$lM<^wfHfvYy`#!3V zy`qDhLP{I$y1T?5<*z(Do@(8({l>PT@BbP3e1AM$M%5yl8N3&f@*>MBYHb1IS*&fQ zt5Wq_mz+!H3vJ{p4m3RkaQD<4swFKH`yjt`Vkn34%1*X%4u3Vc z4e6R?F_Z9vP7D6pTztwtgqXR_kiNZcjuWY8&w6Nvg*pNZBY%>E2xZE~aY&OLPVq&r~ zf8>*@g#g8{kxfzt+nY-%ho0vYWjm)+kc@f4v|cV zw?*n9-PAJAiYioh#o`Ss8kW0~WMU-`cwLLG#C`pXYVSb-lK;#mzv@h$6mK0 zqKj64G%FspKYB5-g$8BU*?8U2He^~J@T)_o-Q-3}3-VKHr+MJ0LMu)(TNHCAeXN@p zl*~jqFS4bGwNTm2Vklwy_2G;P8IlZIXb==VT4u4 zX{ne_^&M!iwCHx@th(VGYR)hjjxzb61!xFZ{_H8;a6-B>N1On?iyTCer;eAt{E0@; zfS?`ao}lU%*nr=vY6RCR4lS3;8(wRzb|V~^*58Zl<~h3>Lo8+6P_tcJX(Ae)_fL}S zmN-{9tyW>8oHEM2Q3_b!7%#Ztcq&W_uN)qY^Q?TucHySxLYJP;hL4ZuVlSAg7PcSr z{oHY?Kz`}Pa7I8TbARYTQ`YFP+<41(8*a88#C{WjI|fk;f6yaNDkDum@aO(VX$JE3 z^pnLjeC~s?UI3dP9*-KxHo<`Plx&a&>&ed8$K23I2f~~FpCdfkEh41a??6k%DemXYCK`qcTn z-|lF&gY`N_^ly#akEBJ_P_7TjKDx`6(`8(i*l!#}qq3U>p$$E}Cb9bCgJdkIO=OwU zTC9nG!V2(KoT@%F+A`d_8E zXDQORmX+!L0e{qM&|E-HtyjnI-krRbhXEXLGRzDEB=F`viQ#3&q0Q_{r2EC=>f7k9 z!Ui~UzlsNCX;&;1PUS>;)Yp#O4v7t@>9Q7wG$}) z2?x;esbz+Rb{Q?!ZL)&wz(!G_W9QLDo*Su-dh)E?>wgS@_Vf+|@AjIZv(WMu@niV- z!qu?$wiR{onM7l%y(trsia;~#bk=Aj2-+y=IJNbKVqC$!NbKe5`K!Np@?~sb%g$kQ zhca!7Xip4@4G9j$*3rMVmqDhMz%MQ-a??r>*<$9X(@Mzu>-4=can&4Xdw^Zy&oYrX zStgpQpMN*1n{vExc8Pm#V^0nV2DW7~7>`l%p1EIqxn7b#a>>@>*6Pi23i9D z8WS`7z+gNQQ(KtjCULS8Z|{!~Uo7IXj6hbTkV_eHNyhj=ZHY{vx`$br&8%*g{QBO;4u;!mik+dAq3Cwnb$?#wA_25n)ob|O+?|U7{{~IN&eU=r z^RQaTPj*2XwAAZ;BtcpZ>1(5F6YuqwUUy3#rDG(hr8OtC8~!=*(a6qJc&Lll5Iy!k zIU%q*x|oYmve3i&2qe(Y@e25h!AOYQzWx zqklvQ#Kb32k|D*YvPMlT-l?{3ao}NrvTAkzBM*x$4mUs{D3-SPv^!GtAGCsy8_FRgxaHdh#f23veZwJ0G54s~(ZPg<9QH zX6KAv^<06PQC8|g2Fm&4oN`9+?u5wz6l+v9;H?w_D!jc?u7;_sGY_4Yu_iNuLpLx?Qxl_s(aZ_Kt(n9meg5n`j;I32X-O(Cj{KsEp30_R^6wh0D?c2x zF!h#w`EOu|y9dwR$h)wqKQLczx#jJPa5v@L+{D|ga*bjJPhv!Eu%!LHy{*#1Zie)< z(<{^^;XF*D`hQ=00Dr|_pdV`g%h>~D8(Z50cx&&sWMcC}8{cDJ*T!B6YQ!>j*DX#? zCL6S1H6ifM?0FE#Zm1c{?(dDoN6Tt8y#{6TiE}csKs{4Av(#U~#uP`LVu%H}MeSsX z1n7K`B}(Ts^-Wrcm@EJu13nr{Wdok$qcNSyPhwJ`3@)y!cz+^l>ZS@PE_Mo+bmZ4rOl{O8eu3vM_pA zp9``2v61|ffq$E<)Wh``p!i4(Fwa|bR~6xx2p}jT;`=X)>^e$2&X!(8oGP7Cf~&x% zTMRQSftsv2MGBq50f{?XT`lx!8T;(Nd5^(_T79X&VXl%k+2;=p+2@|&O{itiMejtS zJk2)`h)Mw??><0SR^&`GY$|c}XCdcRgmdzARZ*VP*MIrk(@Ill%+W^aQ5Zb^9u^Uz zC04=Q$gl*k7~yTWJ`@S^q)J>howOaPwVh;f?V_abhKYt8>2b)mhtD;WY#ViS(3~o^ zZP;AfeEHBx?~JNgzgj@Wm#A;H?ToJUpNO*Rb`I!hn1;!^j7wy5*O5>7zmNL8HQtl< zf8pNE>3=f^YAz0&>rmIti*#=&e-XI(x zmM7^=3=$t+CN@03I2n)Lj*idXzW@0iaBF0tOMg@SRuk(6a!E;avdzbP_oG_Woh>LA zJ|10`Bt`BK9)Rr1gxNtcyAohFh!c7mRXQhMF1Sr}>9a_Rbp=DOarc77GAN~V)8?R( zO*gCz?OAr84M?>iA8TI3SrJ#j;sW!Yg8zgjggg>hP~EVg!H?daT)?>gegb$`tw&IB z_y*)cQ#d7BR)8qGNuik8~|K|AOmy^p8 z7JOm>cy)gE%gJBQ;r-+84!k>kJEHmrR{fX97Z-2o^*`$BZOL^uT;k!X#SeIk$833} zQOvlr19T7jb`Q4$SP_#BHQU+xau1mvW`ExRw|~GV`uAG33Pc`0bjnRcG~;7LH)@5C zTLCz4kdb>#3A^w^_cgxigc7Bu$*s-jy|35YZ}N~)%brtT4RR9BZjj<0OIGfqD8KQE zPJ`dW8?#qfKn>BntR`Opk5~eaNW#2~W&s<@HJDfL-j83tfBn<*{u z4M*^X>Dq68vlgG09)GrK13r{<5w2$SUMPg_UC932V>!h_63c>Cb7ZH1%zsv88nWJf ztZR-E1XBAm|LMuB(o3L@ny8Vq3grlWOjf4V)J<`r9#?*q(jf|aR{4hHXUlF{rhDv_ z2pxgUD|EF8g9oiuwY+B-UA5?@)7@f*4>d9~#uDW#OyY@%gwZ^;z>Lg(a2o>m!8pt) z$Cc$XKK9KM^Hl*a%4&cNgnurxL#qrB6MMIlGW!zIlCKN;pcVHpN!Bm3syyS9xO>)z zOUWhPrkC;6qS~IMty1(KIP{E6!=7Cid1S=~;X7(0d{7txTif>K`?sgBPcFtU&qsdb zv_{>aTGZLZ2Uww5$4%0>NH`~2x&}qPh3)v~xIw=Ced!#ewc^7t&wrzI)*ux)v3llF zwj7bo2Y>yAByCUDNy1f>+w`I%A(~#J~TyQm+P=a7d9QyZJcO}OrR1R&BIkv z9ew|OBb%CsHDOlxLs4GGIi^PobN*JY$|{Ug3axi9A7@xWoqwTG8ti&9p#@)5S>NN^ z7w^wbPcARfS(UA-bcGjrIIxmzid3{u7lp$QeKTfUQX(QEssY~h%0?wV$OIiNdv1<5 z$%RXd$cH5uRCv}Pu2AB<4c9|=Qv@A*)LQn|1PWNvSiu*{O8>GJl2c7xwgdm)%8=s;{n~f0H-KsAM^Q`^sP33n}oCANsm3 zW#4B%gKKFqVRxEM4g8i1kBFr&0h3|j<*K-eZ=wb)e?7Sv#4`sM0oyQn zFK0!CDZ$GX#5`8l;$*jYIr9v&&6(#@pa9MvROK7>Fn>%3xYB}}E=3~cK~aXwNbpr* z!Q*FS7`u*>!~_K>%ok`*R(O#FU8FHxtT5|=OoPbBc;rp77($LqI26RJjgU=rQ^u54 zCZs$yJWx(9gMZ_+iIs5#(Bv#y)`({vyjm??&k?gcPEK8vkkJkFA!9@vQT0>)*gjcd zZABx7B!A3*#d&z)daIV}x5m)onWOeB*Y7r1=<%W3MebuNkev;%(hKf`D=CCNbI3Nv z6qAXEO`D7oiMIh&4;SGyy?^y$w!)%Ia64@{a$USKJwCqBKvl`aP<;p!x2f)0!!B!o z)`r=uRhmY{w)VC9+=+7HQtdIn4m=pyj@C+k^?z?ML$LLn=`eO-mn`k6Sy2t6Z7tZ0 z?M=u#)qv;j@hWcKR%BvrwVm_ASwY$9R%3-(s8OaF!P<2S;Mv~i#ltMu<4t}%X*Sbk zX3+hqTS&(18|waMBjo<$&rsX(N>>))gN6~tYp>}tR{3?^PjH)?MuboCctt+{RAcxu zr+?oO6d&SE-n@z!?*cE@`b8tL{Ope7P3R2y+JhclV}!~-c(1C({B;T^h9}3Dqo(39 z!)#<5>;8J;t-Ic)-OKa$qc`5lTQq!je$lAx3qfp~nmMgEZWy!xziP%-2Rm%Z+Lr6nY`Q5Z;r~0A>V~F^tWXhOdPY1`WHVHnW-U*2`KT6b zF5r+^vH)b&8+ex$nl|BVueLVr8naAotqfmPtN!X)mQ{0Ex=Fv6G_QQ20||{8hkrgS zqe>b&uxKCnW2@5xYtb3E>ce3FBMvF@#FR?-YO_Zz@kf{rG)e;~Nq#O*c$5h&&+=f; zj@<)-jYNqu#iB8*xj|%7*_INr69DGOU^wlGE?Z4)%0gpC{1E~URW@4wtK+K9z+)0t zVA~ad7ISf3u}!{f8wkt7-GXv^uYXX+WdMc&!y*&(wZk;q**O^y%>E<7Try=6%)S76 z^Wkvt*n_)ZV>Yl>K<5XTn#3zB@o&VAotCUWB9(iEJOLan~C!JZ)7ky34<ScK^{UlB@1k7hn1?|rMrs>1iqR8O~k zu`q+6^6_mvs}@18Z@ydL6?$!sPuV(gX^xbs!=Zl;^SAIjz*fBR)T?|3Yf-0J^M(6^| zI&!^RS*Ak8?QO*M44JlKubtRwX0tm7P_}`tShF*@fxdD07Uq|q3M&*#vp*K7%}8S} zo$8=Z@+_zArB{c#(RzleshfcswNp+(-F-=ISb&rrCZjCKiufu{mDkYdFG?2jE$P5q zaCoN+taX94E@0)&34bSLFuS9WeEPX&W+ByN?|^MhSL?-$mMyw0E23M#BrR6dO0Nsl z)>9j`UxAVcn#KPGP)h>@6aWAK2mtZ1_FVLXXD=WQ002rY000UA003=aaA|I4Z!U0o z#Twgg+eY?XUvcVSYqFIT*=|x)8yRpKB{k~WfvvPCf-FIiBY%rEFDf~-%r-6dBlZjT zOZJ>Iyv&f4?H1czJ=oIBoa>xBxwp6XGLVs*-9Y4SMnSwvAao0ng7wyom-I{aE*8*5YlXlKPAL4gFE;fiqC!+ z!yA!ALZ*Q?h<`}Ya#G$H0QaMcdc zeKGrLG9i95l<`WsGVtIgPPU=&uLW3{5b@Kk8;04_B!3k)xSp3sdFV=%-i;YUwni>| zKt7ZFdX4`Dcu5ouGKmnd4WwGj+(^K|;fPct17vQKCNlAo5P6ZehmdE8(#pa_W^gbb zKOe%ub3T~CMx=vfnz+8_W~d2-LpD)uH=;no_=TWgvX8uM+-;2H^H`#KP@?1GV~Fq~ zzH>LHcz?~-7ehF*d3A}a(HOb04v>^u%aT+I--^a}g(YoRB`HX{HgZI{iPz-@kP0Ig=^#737KPWB;DFUMi76GZfa*J0LH%GK=13Up*1il78 zK`q26<2H3SgUMiTZ*MR_D~Mp_CBA?l+92ofDt}0YN17SJ91TPki((__`)z<06Nwm= z_cqA_Ix-l%I=ysW&CeDmsEA(&fS($Ee137c_+|&C&~sSG6*C>uNf%|vH~AD4?H&nnJ8ni2McHZ-P^^FLs&Q$mvj2} z!+*)y8U25GcJcMMC=xMC{MjU(XC6HuN7?glOKE{NEsrf{9d zF*=Z!CRqldh_B^(a5XeCo#6!dh$m)aO6WfDu{+lHA%YRubuyST!EdQE0LE z=vn8e#R!PPmbgV{sR5s85y(daG2@kqpJJCGoYE)-v+gokT4!+?CflV;EEXVz8?_XZ$u#$*gTa|YKGD*oN<}21r63R3B$P;2XqtIS zArW~{rZ3kRZF96841r>Wd+Rd#VM38DY9ba<;UI=oNRsx~-j2Zcx_kL?<@ikAXN57bfkDaXj_2f{ZIIVvJJp6jj28m#jq78J7do5Ex?LNqb> zMeIgseVnJeqAe5IV`im-d-n*+OX6EmB z;F;Y;6Jjpa%=!uIrXh{B16@Cs3Fu(n4Ol<9(a60`0^ja8*_G?Dxg;wS)3muv%fB1? z_%+GpCP(FqP`dr%I|>`ygsymmyiRC?9SwfYMJ}dTh@rSh7YRWz*Pi#-*pjb@e3sp zG`k?ab;H2Nc!gg*Qd%sLQqf`(@0KV!ZJc>D)PcLkCzT|1u(vNJ}OWY2C4Lo(U- zrEFQkHxyaJSjHCB7(3aLp)8}Z#xS_{6Ha9X)B;=bjt~eb&(06?~TX;tjP!MW9glh}75NcQ$2w)3u z4r*M~Yyx<$wrjP$5W2--({sd6X`o|itz-oib(Cn^(CCl|>_WOMs@>)7$8>yogF1Eg z%PUMcUTSi@Ia1wFhH7c};3ZBFknfMTElUybOOK+ZTNy%0|2gZq)-FM-;mE&ucd2@+&K`S9D>#c#M z4@K5_5GuYASbdqGFC{Z}Adk>pf3}q|0K*!OwFZ*aq0(^IZ%>%mZxbA<{QJEF(RL9s z)|qZ)Y`O{k0p8EATh!?hif6=QFmins}h`D?fR`n?_NHx&-Xi1 z3$InoD>0kHX~n8g5e(L!dx#6Hjft1Kd8d5TI_M*id}!UtwL}swD#LBGo*n1%ON9j@ zB9y!*pZYa^jtcw0IkC%f*jp zrqw_k!*k_W)g>M(lwu5UzH67^-zj8CU|PXVEDD7wi48R!*W&Y5ldFoun7s@5IMPBH z!ou8jjs6jrJ<`(zWJai@zL$T;No~ZIpX9*Q$F)b6rr?CW$@7AFCj zbb$|`Uo6L;8Kh)LEZulRE=Ac^_ckVR&|&%wLhVIVJ?C=29~l`r6sS9uk~Ov{ zEq+H)GO*V?W6H5h!@0?V<|;hH5DKIWF}{=W)1qCElyXe@85?zDJ~V2QF~6K?*;x$7i*!qX&+0f0i*5=`u4nsmieyF2Dg$tMP_(dy z`Cg5tUjg+QJyX^(SmaMxyq=|-1pHmqzxX$oUVC^Mn6(vH*vIFEC1x-9fCOP z)+e>PMTLk%9jyK@CvJtlT**Hxz3hnB^-8Gx{KC)2DHy)Y2>iRAtC`RDOQfA5T^;(q z*(KciUTk_F+m-USJDW6~Q)MXjgkP~nK@A~heIEtFhe zA8$#NauRrCas5Cr*%a79cPJb9Q+BTrbiB%5y;&invz6U?4%lz%)`~ZrNgJ^;h#t6F zQhusILPyBmPnVfFqlA;+PaR=hgUM4S91}b`T0sJkMaecTk%7RTcC`)#m$=m|We@fz>STYKdHLRBWE>YR9mlX)G<4DNGMjBO zaCWaLm(FrA&u<%Ztzp`5e_>cJY;VNsUtdbDw)N_l>Yaj`)u0-Fh0Ty}OQWwkR$l}6 z1c;#)cgFXwZ#P6n=mVy)GynV#3#5xpJliZa8-$mDXWA@D*7xIKdcGE<%AROwAhWN>AAg z26hU})whMlZ};tu{*^tAXQX`+a}KKSxoZdpGA;z>s3}9JG$jZm!~g{-VVMl z0T=r6tJSIR55?=}cBXzk&$Mk^1U0OR3|B!WZnR%)U@yr*TB+9>w7ZjB`j~zx>9#o1 z*NRM`*EjANHe^1zMY_Z^N1y&TqD|%X!NI|x#h7fffeWRuqycrJt-A-GDjPnxPm=&> zPfPfn|8AiY)}e+OR=Maqm*pFxf?|pu<+NJ}6hz7K@mb-b9VrN~n)Kc5)1UTNoPx-3 zeCMpE)WASKs1H1q1`{0;mloD2_NUIpVt%R8YIzN1;$z7O62m$Her4!&R05u_PC}o? zr(EXkITn-cu56*hx9QwW$@&sU1@JYNUVj*ue4n8brz54ZpqatJOmi7H-4-#>*v%YV z)SS;2wu<(s)ujG@P22J&$IzWrTLoNePU7)%R`-siZXO&lD z*T;TWqFHXy(Kyh?`a8#`+#WT@A}}wRVU98gXjwa6s4>4r#Un*^ysZ9g7bu)Lkd6eU zv0)v>FZYA%XW&i8>{fud&W?RM>Q*rs=D8d1p@BS-melQ@~u(1O}HJzp-)*Q zs$FbNu&_*dA$jQpaVLq6b?=s7O8 z;$*93W@2Xuq_dzWn^7~c+;34-Q}H->0UWNn9(nE?OM0z8X`%Oc_#DcCf(-WNc6ZQA zld@n0Lq*(E_RDpo2}B0lm7M&L)W-G{{^Z+n!+xN{qF1L9UiSHbKx)Tjx%pUmo-EAW zu8S9->O9CM3ZC7m{dV+<0%dkDgRz+ETF09)O;fQ7momwl-=FrJBN685(Ik$IqYiI& zxUYfB_m_lE zz9yD~wj$RV2L$qbB!laRv|IGSjz87XZA8>jS)}?KtvDVmm7guuLoybh@P<%W%3zXy zj~Iz|<5d`g*jqnnZ0ueNZ@2f0qgPE_5fbKM)BT~#SOJLUS4?KXx>*DhpjCCwE!BfA z-~3R+Ej0r%O`Q9ViggTOwsxNU!AX-JSSqkIY~CeNe~~(J=rt#{xLF?$IFmO-tQs5> zb2uj%8GFdVy)~xwR|jX1gDihPK?5avI`Lmib-zKq{#0$?AM|k*hlcP<6UYW~^v=+? z{7E6d3m`4?*}|DPKLgRtzMdj0G`cL<`a1Q~a64(u*Ri*4z=zSX!{~F3e7r9=!8XV$ zUp_F^%6!Yy-3n)oMA)XRo3q`Nx*wg)%7Zp)A>BGTPZOG-9R(K)ckx<^K_J1$QM2K6*IRSNmE?i0ASuv1Mh>Z45WXJUlPl%iq*4mGmaVy z)*sC>py+8Pk*Kg%k`!(k9`z&gQpK1?la11!t9|nvm+PB9SG!IOYhBxTQk@C}=Dj{h z4vw?e7HQ?I%8~hFh5i;ajX!05=4O5Ju47BC`8Pd);HTyc1)Jf4M zPOk_G{Zp+FfRG?kf&YFmhE9AfIJe8f29!#|D$BfN(-1pzaDrN((D-#XyG8Gixj6*g zd?H>$%CV=rXZ?mi`&iro_Y&|;EFA|R6m+)!A-65@&+jv{^wQFWw`1Vo&RN+r-3OK@ zIep8e#JrES?Qx#3d%xc_Wf@$cPi{tzySfW_hs2=sayFc-yJt{b&1E#&Hmrw`x$>DY z71Th6cDfZ?I#*JxlDQB6z^4Vz?kU}D*a!YL^*_yglMX|?N$V|}VmEfee-Nz=e5tA= z{CJhC%+O7N)%d?_*FD~$sOqE-Iduj}dF{Hqe*_1ASU(=zd`@okc*M6GfPnP@6XEIw z;y2zaZVpt{N(k@9onhAeCy%3}9#16tSVNyDRn0xBe_)((1M>e1_t2*vB7FbJ(Y#>J z|Bjk6TH8|*_Tt_Jg&YFiFBoKIc!`1;^gsQ5{|Q$AcL@R!P0_WUP>LH!^lwiXg{Uo> N-;0kD>;K;i{{rm!TFbwaZ6$WCBDo|wxhQA@p0A$%n(m&jzWVB~i+?aHSrJWEaWZ57`@jDW zwv3ipu}-40WMvgr(V(1WMVwx=UE{;uv0C<3VHwouV8rd zFixTwOyeb=l|W2H?~sL66;5y15A1*aPqv7{ggwV!&^FBB_rvd>;$LMng=K(0>90jt z+``PTC-BG4EG~;xUa}7@#7Tt+54=dvDcts_QEtw)@593ui|u`v9Ods z$FP3#YaUjM`ganhQC+WGFPBkOtao-^A78wAcXoVw$&T1n-L2u-`Nin1*3aSP`3LjG z$?@e#|NGaI%hAW3Hz%i~X(pgMTO3>$AZ=%)bL4znC9*JG|O{pY0hR zd7Ht&`GAf0cmB1r^V8}1tK-x0@y|b>k6(TG@y8SF#lhar&&U6HeE$CA3|_uFeRFbw z!+p8~_%yya|8VvO|J>i(+j;%|!}#+2$I_V7A-{<)%NsvsmeEWr} z+5@6p{9_y?GjCn`?5{ZnN|Z)Kh>qY*74&e)vpb-$S$Z3-v!Y~oi>!>;bP)oLEh0{W zkg_2;wxBJtW%MR4r=lD9;Sr}=Glt&=*Q>CYu^ARYDkkVWdt`NMQe-z#`YJ4nIDaZ| zrmsHAeof@dJD{yt4EhJl!S2H321&lzA>8`aYwUpP(j(4E21FK!_#=gwMo4;iXFY4G z&n_Pp45AuT*?>L|h&RotP#{PxO?$5imVZ5}DnZimFJ?TCSU_8@&+hwd-D887NP=VF zjL=3pvyiM>LE{c$!>+1;topH*(|_|3@f5qnBV=f>d)aruzg8(sk01ahjChtrBiP}k z+cXXORaU))83UPs251dC%zq7IY?_lCScy1|5(fPkz&AiO;N)={BEbVhf=Qmjz^ka( z+2LIreD+gQ8W0YM$n@E?I@CB9O*_tvB0f>V=mzMg4`*j5 z7alOzD0x zi)Ym$==Jq?izvQcR2tZNjffd*64~(QFn9sy9=lL}@i9 zvZpm(#j4D8l1;*74Dhm5F^x*C*dtc!9ej!N45aY&Autu_I-og-OMiO|2uqx>{R0*z zNgRPH05wwf1ipaeufgIV?%*S8DbNRycw!JZrU~d8rGoh_*8}*5+${xn1(idC6`ZdM zhIH-m)4lJYsc*_@2u$soLc4mlZd_HK*{t$nH?5xeuox_G@rvD-zRyr-hRw zE*HqthH3)pJ{{3`0Dl$4q^(f6BAN;kz{?{XeUy|mA9dwBm^+f2pmt!aBTPTk(FQD_ ziZPEtJbI*E8_G9)XGRwKB;i z4dWY1DYJW2FS+OqGJBsL^bVaKQNeYGf}$^bI52<=|J|qI@P8L;df~4{ea(;_uOe>& z6dk4N6u{O8Is02^qmMKJL~QuI?ltuX<`L#$Oh_H%4gEG}YZ&H0B4{(lQ(SkOZG{uk}SdPYC=ZS=qgS3Zhoxby-UQy>77HK8$(AOBQFxra&v zn6?GNvZ#YlIpmu9yOt$WwRI6i0_h@C*bko?i2zaV?=?QH2#cg0YLh+)c+q3ue8Ua^ z2k_mR-mUq&rb(Hx7iz_tyR0p$Txn=|27l#F9h6FB8Gm4qX;yKeANObthj!_6dWQu- z{S6u$4~M8Kgy}RIm;Vj)p9m&QG(gntIbl$U_`|2QVI{>TSjK6f_E(?XMC+p@Tux>o z%MUf(J6B`vxywV+m#q!IU{Cx_e}+s@_r8diAqoE|tqeox;z6(grQ(3EbD!PSf86^) zR8l{Yuz$WqT07kT{&}Ab_J)stcnoxx|3hzkSJnXCy-Q$$9#wV6fO!PGAs8IJDPh9J zn;kzEgVYu#@pU`hyoF|NiRww3$HxkkPtrX;6O9nktXRr!F4lQg1srA%IqCjzud$-A zy$ql{`v%4ftwB%V7gYU@z;26hG+ZB6l~F75Dt|avd+urgE)oBc+&sWqZYwt0ehYii z2kWkgE7)@t3e>=lt!lbhh#M3H#`2IYRv=H4GLKXp_YarcP^v-#k6&)%67^7!#wSG< z&ZcMv%Tir3On@eQ&&mw73217Nttu%1mfW%e=)|gwEV17SU3M^mjT;qfB^SzWsjauj zQGX&*o9>?8=rRtHqk1@wC^PZ#DLFtdud+DL1ewO4I&mJY_oJeL0ZfW0ywSVw@4B^^ z-}g~1&=*kC$UvqH;}XSXvI|Lo@B-FDPBk}al!%XP9v7OKMgTzpS?>=Qhg}nET@O?f zM+d=PnX7}PUWhg<{WcGas7;GO;?PN~4u6+8NU?Fna0b|NpEu%2YofwSRtw=M334>8 z_#k)@&q8BiPr(OQ&R_8{ACgq3pDlr&89lrbE(uUaJ)Cr2ZgB)Hc9k23j%7St68k_7?)RSZ&UyZAH38PEG9M~ZmdYF0Wht!|WLo~goOCul@&iHV}V1OEd@ErHW zuILNAcQ{W8U8slUX{lvmX}0>ZX`Mc_j{v{c-IMTo`u@+ul8n8u`HQgR)`KLPR~cIQ zLOOK)4kptg$HMpF6`fp(27hdl4RreT0=~aWvgu7j8Qh1*v%iOUqHlAG5^z7Fdg^?$ zZyrtB;KJE0YM#f!%xW9X8L=x6Lz5iolEDK7E0ZixWBz|o6Eb>L#MeMpJZ5AK_Ampw ziz_gmF2eLWa;PjmLZim>Hb4_B4Fd9^(H!6x3Z%sfomW6st3cfJTYmveD1Dlrp37*( zc{QF2df1vg&4w9-4LFl}4ca*s-xZpk80n5$OI?;|S<(9V+qLPMs=?nFs=N>EETO~ZoRfdx6e0g>6w>qy42#bdGo}hyd zd+~w=X!u$4PTn=znUQTGxH5`T5KSj-M*(fNT>ypcgh_%D?ti2WN^-USh`vhRB4nvV zp&H32Xz|@(J#K}C=-3U8YgpoNn(SOgggR-jbdK2~0W=$Ec^{!M{xjNeOur+cg4kU>oYgrE$#4{e<#Ub%{F>M{T!Az_B?;})mPlJ z3`I`V1NdnHlg9_>rpK~*?R=%&Xt`MgePxw`5f^1`bcBuoF2qyxSWOZZ!@tslwaT~) z-;BF$Vc!^eY4wKa+bO}dl6d9!r7x!>o?RG#4F~F~&3|JqwgiXTwfRB~eraqzo7jf; zXbzs@q0{~zTKVHtaEU2W$o+krbK10409ac97WB>sKulIZ=Umz`7oBm5BQ82p+S~J! zl_mSFwk(hjn5{J^$GfGCeM=4X5N&*@_t_j=n{IdV#Q{2Zcy(r0oSJ*S;)A1gmtLG} zTkB6J+<(4_rwxsJowj+s#n=4JV%s@4Cst@uOVAI=h0s)@@bxIWgAG+nPJOxg#^@Q| zXW?xY&yMy?iYpZ*s1B=^C9~`<4M@h@Yn7n9E4j(kS5|Z!jZV|U6!hLPz6el5Htks6 zo||G7LTV)2XqD96q_42JDcj6Yd*`kJ;cOOI=YQdkxbCFdDp7sqJ8`!joP^=lS$f9V+~tw>o_ zn1eH>kM@wUdhUfplhaLsokRvyf$9ABanP;;l z`;O(Sa)DbqE|hEfgB}VQCU=dRdkmdw4jA^`=R^C@8;u(5{KEy8IB_W=L(Y6u%b3e>jdEt5!H9!2V?`!B7}4y<&wo@( z*zpNd@lQhs)i;a&B%#`y3Xz>NX~cM-FI*3((Zef_)dxZ_nL)4{RyVuC!yaR7_{or; zk~Av5c$zvXNzSt*78&cZc0R3pT=erdMZ;|pL`0KV#;v{Nj-JHS`TPeiruM0_m%w6u zf1dH{jlDXmJ=#b7K=!Tl zl|JKsxMdt&N9tVedc|E3IH$H0UEPHMCS_0pQUa^zks&H5M3*gi6ynJ*Cl`MM7IYmZ zjBi5?lBzh#pksr(cow1LbQ5u(M3ivC2@q5a{)f2)15+>}TfM4TBPu~vnFi9rB=ME{d zicyp)7oww?!+ZmDUB3oV8+17w!(FINFZz6yj!xK}THi#tqy~NVeJ z`lCBkxrAG~R`oom+j=an*XpkoxZ3-O5+Mww#aOHr-&cz4WId*>VMduMj?))0hpqiL zTfN1w8W#tN*$}Wkduq;_?8FeZ2<*;ql~loPpTV$tG&EHJVQ63ei!=GZ`ROM`$&(e1 z)P01pL?AR${C@)nShpgErDh3U$1{m0{Gji5?X>Q9GT)mT) zp{9D#eGY3Mn7{aXWL2r7(1hCrZNjY20b&si%JudUVsBIq@vm`B_=vS=F!~&T!&hv7 zqS{Rg7wiSl0>9&uH`3Y+Nav!;#IQXH6vvnL0UgDSQ-57)OU4m!R&%&{|4SL7?@A8F z2_Fx2_ij-ue0{?XRi%}6`b(S*h;fNC0WrA$TW10Gyb0+A&+NvO4}eQB3dwtob@{O` zOsS0w_G~J=P!6A#Jqn%Tbfkz=P>}O*on#^Q(eRnubUM(0wn7#z}3t z2T4XLwiNjnE~0Q|0SaUm2WV+YW|-mAd!E{X>>@+KU^w4~@139(KfnA_Y!RaD7JM8&D+a3Kb=pyES$Xejqv@onh< z`eoCrh4HFUrlF|5`4zN>wEW5556?tx{Z+TxggnqoQMbt($HN-*httHqqNnvO)NA(&>NF~(m4Fi|&Uh-Z)*pE&MkWOe={13NQz2ip z_eJ1RfVR(;3EV8h2pn#1Z4A^FT1eKU;eVBG0JVIQIK9!M(0Q%qg+)UX5bXJ?px6o| zatk~osOZRCF#t2$kp0XjZCi>;JlM7(XaH%BbCJk>m2B8l_!t{&?ryAk$Bl*0Ml_^u z3S69HFXx+aQFIb!Q^UqNqT_tB5gDmLT!&x|_{itFbwl%Jn(b)@;Bbl}f#p3AK!3OP zKlZVA2OoPI#}(Lz;)ty6>aKjWgL6_AYidI<`l5fE&;}oX(gtB;eMjL^g6XEc<^eJ0 z&0fj6A8GycSvBo*T6_~{_2e5Oep*d)cCY!raM{$O_w{-{Yn)fK}Vy}M_-o^*e^m%C1`zkglLZ%nMMG@j_| zC7$~5P&^!yi*1;$FXTBeh4@^nyNBF2CoDnOcIs_dt7_8Y-YOUygVJz-fBz1z8|DOenGe?T-n&=ycuT)i(LbMH`n0~Otwa_g*Bn3AqK#CCjaSeryHP zx6p7-X+4B|hj;v;efTKPbnu*e@D))W#z3mVWq6J5wCKDm&t1^_6Hg6g`mXX}JES@9 zVVAnP?#Zs|7B_BtRDWqf>I^W4DzA%>yr>{$E5{sxEUqy3-wK$VPeySv;k}1De9H&WH*it_zffgXfr#;MsR%hV3<_jL`x`zc*N5P?yuPet68gY@i^% zVEb-_R%^_T?VBp*^S7EA$Y=!Bx9y`UKP^E`lNX4}vqONs#ecXAE8Px$@Uz&~uu{(~ zNI`$dlR%k53NQ9a{t=??wjL7Xz<>znxJYP;Gp7qosrL`m*x7UH3DRFwEifBCVh!PR zZIw@>3iDQKpn|)wglR=#spO=g-u4X1-~t^|(K{DPziL~0im`-Jp8I@E|T*d zG^v;*6KhLJr|DUrDI7k~Oo=2iYq=ryou*(~FE-Zf|i z<>Ls&7N2Aw(%?auJn4d+H<&KsoNsM?3AUPJMTNU6{asWbd^V^-qH#N@C+~S9YFct$ z;zI#HdNRg(xc-o0y`CY=*Ez2{G`QXQpL~?sA=@%i=(YqpOBJF^A3f;q3xnAb`@fLn z%I`&?1b;}JMNv*3$~eX^La5^-5HXdwcU^?DI1(<_4=cacH#NMLT&{KkF}?iG8*gt2 zCTnT7a&B^6be(#oS69?pPEvSJr-{;{v$%#km5BvWu@Q7Q1_=dWIx9D zc?R@{j{bG8V&1jV@Dr3GB5RQuNwB}?QKEW28Gp)_B8t~9g_D7a)JaLahZv9XL4YQT z!I=*fwTSML{nsOepbyr(Q$u~BgfosJ&HE%iT7m9$4Zr0_I<*Djz(x4AS@wOuUmHFY zl`K86>yB?}X+f>#h^LpBkx!04(=<8{LrIZF6ITodP`}yKH2+L1!F%i{H4RfNmtJVU91;kLJb`Zb4Ztie+%=S6&r!7SVyyTH3*GJm=&W=(-ZLw0(8{O07128deX0t{X572Qu0 zkdr{+e*X+_(PEfiLyZgA^lrt%=`_mGeggy0V3i^@Uq(|!;{YBRvA>5)PIvzfT$8Re z33%Azw@+9erD0N080<`+^+}v!#^VV}T(<|0cT2hkWw1Xe`CT)sB+{$^a=mprWPiL- zJVcKvQbv1+Y?>h(7BmxWB$OtPx!U%BRJ)4e0Rj1PIPKcI#P41wEnA*y?M`*twxRF; z3Hf|HT}IU+n`yikk@6zTxXYIH@k50M!w zwkvL0g4TxYU5bLxKv+2`&aDKI1Amc@0ykN}Z{FkgDxuP3rtSDVw}(3M#zK_5Pvf?|R&ym^^cz=suU9wzc zD?s%{cncz^aD;g`y5vqgwTDdIq`Bfg|FX}+g4&PFzb$?wEJ3Ex{vFSTbOxP>#af83el}S;htjIy6eJeRe*%aT^ z$xE99+YpV3yQk(*4Z)biIKOx}rp0(gC!08j9NdPqJ))RS_(7;VK~qn2=$ucf7JQvf z{TCsM?{YN!qB_o-Ybe;t02O3zT#z!$kp0BjdLX#Ga1yUqpjQi_27jUNwBB8GfT%e< z_tW%BH?@L+En8zl2Gz373@G5`x?r*16iU}IiCHp_z?9sANgZ40Tw1iFXj2lvRZrYX{R@s#SO1?3FlCA#(q~O zAG81s0n48~rGHG9q&xGYXP|eH1?wi_r`bh(i*qe57#4Rpnz33+A|)Mp(I}D5L@beq zEs*5-7%I--|Iz^r?|&TUQ9*g&@tP(~{`n1!z=5Dn%sftIFR+1@glOEkNOEYgR9^QY zW3?T3C|Z9nvg;1+HHH|8bLfF9YqVHyyye>s zH(dv@?;>!^AZp>&ed8a@?iaSZM#%;JL={cS~22s!ox{Xpqo)A%SNzVM4J z3E9zJG#b8VRJ4~!Z;XPmOmOTDwAt?_@dtk;R`8cA5qiD8DCUv}eQgHo8Hrq(YZ6yxUV*`gq1#G&aQ1my09d{o2CX#H}xO!l^o~w_!}_XrY;* z@=>(g@>?@23II*Uwi*BS9^VIicO~`@}~hTT@e@h+aeVm<7ay!0M=CHb#j;4{JIUKtIQLSi71( z^&0W~yT`QR{kz=7H~1<)0M}~p7=HxvSvs@)LnuCsnD``0GNc$)=G4UEtwyMAI?RpoN zK3J4g<=xrn+(M_4eC>FmY&(UQMC;@ID!*T`%#k;EhED*y^%g8tQ%=oD$d>wnYp%M*Uf zS2oA21^j4_2mD|b--ujWHAs9V2jO*^l~p`t6CTn64EHvwGLTG`czPr5Bg2;XzD7-y zfDUwbUqF_U1;|bDj)|v({k>ABIDfvW{8m!xx z0`Dl%0>#I!ay+qN!R^5h41Z%ZvQ+bhUC^=AMiE(DEn;p@tlK0VbGSYc7rtnMtvo7T z%4-LgM#@z)&1#*1b)lU`WL_>2H;XMw?5Dn>>iQ5fm&@Tv-3m;^<06PQC2cRDuqIuw~Y~a zd&0!Iv@xn0a3_U;3U^luy5>KXnVB3h!nMAk-mSy?{J8CdWs?z0X1Jj)Da!o);VjNtARB74w$^=^t~(Pe5xETI#N%YG~pxz5ueBwj9jMA#-^c>H` z^tZ-LV(t=u^u8sk0CF0V%8R3xi@)ThCYE6GM|I*89}XIrdc(f_0~q4A;JF)l7dG`L z&X+xj2EQ106V!GiZ!^j{#WbG85!GQy`+IwxvSn?C^tAOirE_+d1xO|kj zQ{J9o%1CO`5Z}fYzE_VasDQ2`FMXw45Px|ahuh;WKGMij{LX+#} zChbY}-SXBxO&Vb3;o2~i=EnzRq4lnI7dQH`k^GZ^o2=Bs^%tP{NDSgzjQp>P@Jl!l zh-=1Q(SJqMEg@$Y5zioLTyuUG$=l6&<^*!i2Z=jdT`#nIO6|MW>h!MW_18!?8)cu3 zSIuu?AgE>JsACX`@-*+pjR^rGZ;vbD*9hx2mALv#eSLd4r;{$cww=Dt=blzvp)p4r zrAJ}#^m|xDbRTw3Bp#Ol79+e1*M}@Yo>Z}G_kWPKCF7u#SE^Z*^d;VP1f7d{?cmLx@S`G4%5r)e!AT?@r*OIzmACSzdf8qBhZ` z&mt*CX4;;UlGb1ulycIwIml#`j+Mbl8d-<@E_hwe_#jH;|-pl&5;ab-A`r4hUPx}7rS|7J}puWBJz<+-y z>w&s(v?=GthTO)sTKs^?jc3big<@JNW6(X!q{ns#up;Inaog#f^H^k!*w?`AAMik5 z&rQ1GLFD1FXuOGtW_*n3My>ED3;^eKGP0u=unRBSuho))3zV@UH#VR5{w#gJiP(dN zJtx0vxxtlURY{>}rvG=F{%Z}eVa0XalHhj+dL9r&HS72Vh z|1f^_;muDcqwxpJ9nftPA;MdY81Z=6K(yY(zCXD`K0wgcFsIDZe=>Xb=KSoW=L}(p z(tq&qy5WdBiLfB2Ta$TIGn%u5W2S^`*V-w z6blI~3mVOltp+kvm2qUfZLDjS5(HBFGcOawe$`7LLrvsJ8ijI%J|-*EYHCNm%g2^q zg>;C*o>AVB{7l(R$#jps5}_rKd4;Y9Vep`_s*?9KqpK3#R2EDO{C}4tQ`3OVd_+n% z{`xD4HlFNXbQ>@-N`+|rre-{8ARW#}IXt>3zrUt(5W_jlJ_wLxucf{Jb129E2!KmwU*^esxhdwuIB=EmN5 z&TdF)I`R@T)l;urOMf*HGCKB(N*e zduwzG7?OF_4C(tFp?SwGG!Sz!Ngvq*S+pJ-+CQF4#YEms_cnL&TF?N$hVfTBQ!_r| zLtoXUw6xsPX6qU{>{i2prmaI!=y1-^cq(F2WR^Kru8Q0EHh(Iy5at)H_`OsbnA7uL zPc8;AURIT?YdI7CDlw}aPZw78DeEws$1-1hc89k%%`gp%?x76@@TLf;%I}=OgK8iX z3u?M#Nhso+%5WJmerZhb_!+5&H*u2apg?TkSIkUS72Wn;U=+*><9sUqjHsvPw10$#`nbYAg_P`w50ocx#=mje*h&k6(YLxH{_(7Zc*>>gS*B)>nO_wp zY_pHLNgQv&a{pG0P2b|W&`U&Vb&4A1zv4W+ulSvU2`lG=4WM^5SJfz)G%w$Yk7=8wa5H*;K9gdw3Y&ee~W37hR3T} z+l6hyji+WsHMF)Brx}}@kndCtp1sGb*iGZFV{O&Vd}pno=yapFt`}-DW1FBG{a~N% zecp-ia(_MEWXTqaqbf6#_cnSwR;F~**q1gW_tKP0erQNsy5=E;iE07{aj*MH+6paaXZJlHe6SAbw6QKA%TXkBnOh{(3q zP(pM9z#M4|$A23jK8?1I`mT1(=YAjvS`|lQ<0;e$HKio z|E14XDaAY;d0@p$=_AfvEvPRlfBNOm$ET+tZLk~zr7)bn2;;=M2U9@{sv^t}5$*9- zb&#P8Sf(DPJS@WWhTok^e-86^@H@a(ydH(Cd8geB*Ka2p5J z^mEObg;0~d12#2Xr5AIwY|v%7BH95aX|b$UdTpSlp6b+o4N4+t7JvT-P)h>@6aWAK z2mo!V_FT&662OHG004_B000UA003=aaA|I4Z!U0o#aiob+eQ}t&!?C=*oJH+MYfw1 z)kX%KMoEpjc3>+liXcmj$g#zmvEWwvRt53x_UPqOEn;bn%TZ0}+(`e93RIk!3U zoeR0QxA!Vi3D0j>!hbj8D7}&_=B3D(`;Mn-EoA!a`!$z2%fzaP;?SEo&T=i-b}eIp zdsrOZikw9$dsnKpOxY^B20Dr%(R>C!6-(Ki-LWrO`NdBY_EyM5s4NPcR7BV7l`JwQ z?lwHlBiJ3~>?@Y9qbrsFnS~+);-MzRT(!;wS0lEGc_8>I7JnmlmB~~^BJ&)c3AU0T z6i*qCuf;0kOx}VbTGEx|S(s1QdCFeS52tsBQxraDT+blim-BC?QFHEntw(`AlxRCN(M3pS=8+TD{>&|=CM>c+n-EcjM)AQT9~tq$edLsc^L2< zECKsKOfcJxD1kA2!Ch?n6lB@(Z3^s*RDpRwqoboEmcT*$z&9sw%Q)Ja#2BrtpxHNd)wKv=U6xxqpT3%446iKUSXSd~l~oV#JWt zwEBO5{||8dqm}}R!e&sx#Z7vynxauH%^?nZj6GpE$mkeC306o!$8E+p&eYl4+jAW7 zf`nZKG88OIHXu2B9c3aw%Z%6p97L7NawG8jZ3G^Zh!l+XPUaCVah%sD7yj$T>GBv1 z@teco(|_!roSk1Rznd}CYmADuGneP@>lv8nY+D>(E@zDu+VP(jG(Of7&hL)%MEigO z0U4?==tkOu+$aReqkzXuN$o72v-B4~yj%V}VoU%0VuAmDIzBzc|F2HZzx^ImLIL3v z;Jh8@?fbK{#f5)y{4-1(GWtAW`ere{0Z+V(5`Xfk8M`jh6fRJZ$vkJVNUzn}xm=t9 z;p4N{u>Co!_va{9D!_s9Cg`QiJp`8zh37a<5OW|=dESgn6^bk7`%xMx--o*uUyayy z1|9|nv!?%^UWXr?o@{~R)*f2J(ApkCsM)eFy2JXC&*4DW%a_$kkB!-Wvkb1^6m8WE zpnuDk=<*p)uZ6odvsKDAe}1--(TTH!8scj@Z+;PuVB-^z!M@wpJ;<(b(7GO2>TL z)Cc_`C<0IpF>yV>pu>?@HlZ8y9dh457tYEvkHomlZ<^?gl^ zPRuks?7WsZL2k|dT8Ki-AngHlfqySBV&#aCyQ^@ad>yst?88Ts;0Ey(Jb5kU&5VIh z!c7%kpMUK9v52B*)Y4xA5F?`syPS{HN)nGRUXbszU6>OIpw$bSDzkfwbxOeEb z8Yn3Z1hCWqR5>`(gOos%%R%{AV205R0|*uEET#y)4mGX~SUZ})G-1N$MN>Fqyl_vhdt>xTrW3{_L1$k38VA=$Zy1c}g7 zTPJPzZ2DAT2Bl(N{gc#fe}8J}+uvH0#5mG6Q|$BFt}*FJ?3fzol!}6`>u z3MYU7n2W`wH^DTtV@5lW8v(ceRfW{nUOEa4NOvi()VRvgs8*wd*MGo4LU&fjIJW|_ zO6ZnUmC(%rA2I-1M3p+XV~}h&SsY}BV@%yBozv6LZ5^TBfK{s4pfz==&=0m)DkQn< zc`YzfgoDiBmc-}>zh+@{RjtLk3iEx0C91P%>l`Jpou@Lp-qp+~JDyQti4nGO@KNjB zBg2tLhLgAAV@^mo4SzX3fOLrI;cmTc`BqJG2gpe@tZ7h~D1vH`k=R>n)+9dhBt80z z$POHjW;Hm6MEo|VZHobKm{v=VIJ)rNv%PueHigMCthW}3I2V0Rv{ak1t|$LH>O;b8 zxkHzMVVb7S`OjMhr5LbH{pnc))>9FTH!`Ja5E(;glmJ-9MSnGX{&TaAcp%pVw}-pr zb>U03xKuY;hL8yh3!UWf@E0Bcf?$4Lq*QUhmCe~d{`PkaIkC)it3vVY8ZU_nC)N6H z_0!ZYgVL#Nn;)Vr}l1R#x|q^-_vn&fVz)4w@p?}iQTxz&)& z`Y1rXqDKFfwSR)5KtrUevdl%P*v9B*2!gJu;;LI#;qWKZ_Yp*~DFT?Hy8^Z^8&X-@ zL~L!OHp0NpTG-Cs?hV(8B_{o?x}SA#p{@O#{b65VOP=$KMgLAVmFuVO<;T5RgdMv& z<>;4lBb79W45iD%*f6xR%I@EAu#aaRkgMBPkYjWjH-DZ-;f{{IaU}q$#){s+nLMP+ zqxHB)2f6{K3c4;rVO{O47$rOYYj^+Kf+^5I!8@~j`_eJO*G7%W>Xf z@>a0?CfYy=rIA22#irfqZcftMEm`h?s*`&iDUIG~qOhGPY zu+zCWNs1Y28;~H<;N7I$Rnq|W8;D{!Q}ZwydIAn1 z!*;&X(^|2L!p@4Cs~@!i64Aeg;wBROA37}eH%_`;lK`{MRDnf1{S(Uq)Im0kpnv++ZE;P$JwN+#@$>nG*-xKbzB@fxE*BSOAZo+76}anO<3N6- z`+to!fLkt2opHd%O6FAXY*p&(+fA<#Ca@%dxe0uy|8|0fp?bIpVXh*C)bX{CA~4xR z@nP%_FjYzmfyZQA)gq13HHRDlf6myiB9rwU>ygpk>>AH1CJ^%c<;n8+?Bx9qC6$f@CMZ(gF;(BtFNtPpA+5H z>edd+fJ*=iu_kD?UvfVcnvy>tV`$-Buo$MtkSc~>iwLs+f%$D!{$?5CGh5zb&*_Uz9!#6Dq zZ>kp?i0}^n1l7d&K6tcKUegL2OtLnU@-az8H$5JvV|Hoqt(oeb2VGpc`hU%s>ZO_b zeVGw!Uy||cMhEUQbS52ecF{S3&4u7}UB3^rc^U5!tu$$b<%l{7Z!U2FY~ zDCaRxRv~9<#`r3C)raXvHh(6eCwCt3(u4cWU|GH5(z3)RL)8y!X5ipfQK~By>yAWi zoM|)^3LD3VWLH9#EP1VSCHsj3weS*6z)|7HSxir9rl7 zETL@($)He8p=j!o@anwp`{6z3KF_D;oag!aJLmU&_(!4%`{(w3LGbqE(B99GNb{;j zg_0E8A2UHoWAt6Y7y<4kFKRz0V|#ykYkZUd!L5>qZs<`|Y=>y38(dDQdGsp94=wOu zoAOL5Q)G1(S=@~1Kiv@^Kxp{hFGq@zeKAr+#Ixoe9Rh}_JyWMI_*DA-sO}x!6RZ8q z_g=U4CG^pfC(42!9UGFZFtwN0_TaP6vdyf^-NH8f0+_?ueIG7IGj*8m3`Y~7xu6NF z@{}lrU|Ct4PZPzlAXX&|%+jX{B^#$D=Q1!#IyRP&8Z~8^a zDU7Dy&yy+_D?$7-7W)FN*PS6kBx3D%*wJE^#;L;qK(ROgAadYvF&IP?5_h0*H+^3p zD4b;Cdp~hlzm{TFUcPNz2h!ey2Q~2(A4?9BKSEdYV+T&M7n?Q2B2tq;eJvsE;s94z zrrxa7bEH1b4wnuFgZ)Nh-Kvd>Qy-7IGI9TEGm)GZjzry`H1M3zB&}EONeS7q@*e682H?1+>ZbJ**GP=~3zE?|1h0%y7`aI` z>lb}-oG(mPf>}>$xpFGy8&x+91sxxe%e~dSeYi_K1fyw$-eUTn7&mfi;7w)<0Ruxvo56nN4`Jvy*&(iuwt&uTRRO5<)-@%+_Rsi-I(<)s1 zO|ltz{=F~+3!)pnzPg#Cir+HK6G=?bLLoC>4K~0Z`kbTaDP*^^u(s8QEMMo{wTJ?{ z#NhEVBmtG0_%C{l_5A|MJgG~0QO8;nzSX|gA0c_oFM+%HvNMft5KL~78uW6!;b59v z<^+7V7qvu=<}45EpIrulH!&4^c5^=O0Sg{!Ht=rZP`Ab}<)}sis|R&=~!*no(u(z|P$ec!UP!&LEVAtF6OH#snPxd;Ne<^M=4*YsAg;Uz%E7WhmXuPQ%Aiz>fN{)pUBtfcHs_eQhdJ%TSe>3loYZxhe$vH}Lx++OA)MR}< zM$wju)-~W2MCxt-b=%!l&Hpj2+cnf5)dGE7f==h#bQKMj;EWbQtRAh5oF)XkDKKSV z-WYQ6Vm(9q3(o3Kn*~b?iQ*U~Z|-&h@RXbuj~vi9!CS1}5nf1GrFT1g?6|<`)!J%b zI2*wUyheXw=gjft-X_gisV?0=7Z*mDH<^k06BOB0M@|uw=;7xQJ~^%xMf|}wZmzra zz{3~Bn6Na|uo`aNIM+#Lr4cUx%|)AF6Of5U);ZeH~UN?8uN&gs;YO z>s8weU3S!Mo@ToH>Nf;PxhK&Dy_sZo$9Z9jLMYXTSiL1<$P!nIiqpSruG)_d{9DL zZu!)ZOL=Ol*Q7@R=;Tj{Rwdser6d2JPE**6)mHtdci91L|67X2g@zs92Wew PEqKr#d;q{w#INb!9ui*0 diff --git a/main.py b/main.py index 80e67d7..da6347c 100644 --- a/main.py +++ b/main.py @@ -182,6 +182,15 @@ def builder(self, ct: Controller) -> None: if rnd % 16 == 0: self.ttl = {k: v for k, v in self.ttl.items() if v > rnd} + # death memory: a turret we built got destroyed -> ban that placement + # tile for a long while (stops the build-die-rebuild Ti pump the + # sprint-vs-Ijti replay exposed: same gunner tile fed every 4 rounds) + if mi.turret_losses: + for (lx, ly) in mi.turret_losses: + self.ttl[('plant', lx, ly)] = rnd + 80 + mi.turret_losses.clear() + self._plantCache = None + # claims context: my bit vs other friendly bots my_bit = mi.bit(pos.x, pos.y) others = mi.own_bots & ~my_bit @@ -356,11 +365,16 @@ def _score_attack(self, ct, mi, my_bit, others): if rnd < 30 or self.myNum % 3 != 0 or self.myNum == 1: return 0.0, None # builder #1 is the home guard, never sieges guess = self._enemyCoreGuess() - # bit-sliced plane placement (Pantheon): score EVERY legal gunner - # placement board-wide, take the best above threshold. + # bit-sliced plane placement (Pantheon): score EVERY legal placement + # board-wide — prefer one we can build THIS TURN (adjacent), but only + # when it's COMPARABLE to the global best (their immediacy rule is + # among preferred tiles, not a license for low-grade adjacent spam). plant = self._bestPlant(ct, mi) + near = self._bestPlant(ct, mi, near_mask=mi.expand(my_bit) & ~my_bit) + if near is not None and (plant is None or near[0] >= plant[0] - 10): + plant = near if plant is not None: - score, px, py, fk = plant + score, px, py, fk, pkind = plant # geographic money gate on the PLACEMENT tile if self.teamCore is not None and guess is not None: dHome = abs(px - self.teamCore.x) + abs(py - self.teamCore.y) @@ -371,7 +385,7 @@ def _score_attack(self, ct, mi, my_bit, others): if not deep or ti >= 150: mine = path.claims(mi, my_bit, others, mi.bit(px, py)) if mine: - return 9.0, ('plant', (px, py, fk)) + return 9.0, ('plant', (px, py, fk, pkind)) # fallback: walk at remembered enemy buildings (old greedy pathway) cands = 0 for (x, y) in mi.enemy_buildings: @@ -397,9 +411,8 @@ def _score_attack(self, ct, mi, my_bit, others): PLANT_VALUES = None # built lazily: EntityType -> placement value - def _bestPlant(self, ct, mi): - """Best gunner placement from the score planes, cached per - (struct, enemy-set). Threshold 20 = better than a lone barrier.""" + def _plantStacks(self, ct, mi): + """Cached plane stacks + candidate masks for turret placement.""" if Player.PLANT_VALUES is None: Player.PLANT_VALUES = { EntityType.CORE: 60, EntityType.GUNNER: 40, @@ -421,13 +434,60 @@ def _bestPlant(self, ct, mi): valid = (mi.seen & ~mi.walls & ~mi.blocked & ~mi.ore & ~mi.own_conveyors & ~mi.own_barriers & ~mi.expand(mi.expand(mi.enemy_bots))) - best = None + # death memory: tiles where our turrets died are banned while fresh + rnd = ct.get_current_round() + for (kind, kx, ky), exp in self.ttl.items(): + if kind == 'plant' and exp > rnd: + valid &= ~mi.bit(kx, ky) + data = None if class_masks and valid: - best = planes.best_placement(mi, class_masks, valid) - if best is not None and best[0] < 20: - best = None - self._plantCache = (key, best) - return best + threat = mi.threat() + # sentinels never inside enemy GUNNER cover (Pantheon: they shoot + # too slowly to win that trade) + gthreat = 0 + for (x, y), kind in mi.enemy_turrets.items(): + if kind != 'G': + continue + for shift in (mi.east, mi.west, mi.north, mi.south): + ray = mi.bit(x, y) + for _ in range(3): + ray = shift(ray) & ~mi.walls + gthreat |= ray + gun = planes.gunner_planes(mi, class_masks, valid) + sent = planes.sentinel_planes(mi, class_masks, valid & ~gthreat) + data = (gun, sent, valid & ~threat, valid & threat, + valid & ~gthreat) + self._plantCache = (key, data) + return data + + def _bestPlant(self, ct, mi, near_mask=None): + """Best placement (score, x, y, facing, 'G'|'S'). Threat tiles need + 2x value; sentinel only when significantly better than the best + gunner (Pantheon: 'when a gunner would be significantly worse').""" + data = self._plantStacks(ct, mi) + if data is None: + return None + gun, sent, plain, hot, sval = data + if near_mask is not None: + plain &= near_mask + hot &= near_mask + sval &= near_mask + best = planes.best_over(gun, plain, mi) + hotbest = planes.best_over(gun, hot, mi) + if hotbest is not None and hotbest[0] >= 40 \ + and (best is None or hotbest[0] > best[0]): + best = hotbest + kind = 'G' + ti = ct.get_global_resources() + if ti >= ct.get_sentinel_cost() + 40: + sbest = planes.best_over(sent, sval, mi) + if sbest is not None and sbest[0] >= 40 \ + and (best is None or sbest[0] >= best[0] + 15): + best = sbest + kind = 'S' + if best is None or best[0] < 20: + return None + return (best[0], best[1], best[2], best[3], kind) def _enemyFieldCached(self, mi, guess): key = (mi.struct_version, guess.x, guess.y) @@ -496,7 +556,7 @@ def _run_attack(self, ct, mi, data): self._moveToward(ct, mi.expand(mi.expand(m))) return if kind == 'plant': - px, py, fk = payload + px, py, fk, pkind = payload tile = Position(px, py) facing = {'E': Direction.EAST, 'W': Direction.WEST, 'N': Direction.NORTH, 'S': Direction.SOUTH}[fk] @@ -507,10 +567,18 @@ def _run_attack(self, ct, mi, data): return if pos.distance_squared(tile) <= 2 \ and ct.get_action_cooldown() == 0: - if ct.is_in_vision(tile) and ct.can_build_gunner(tile, facing): - ct.build_gunner(tile, facing) - self._plantCache = None - return + if ct.is_in_vision(tile): + if pkind == 'S' \ + and ct.can_build_sentinel(tile, facing): + ct.build_sentinel(tile, facing) + mi.own_turrets[(px, py)] = 'S' + self._plantCache = None + return + if ct.can_build_gunner(tile, facing): + ct.build_gunner(tile, facing) + mi.own_turrets[(px, py)] = 'G' + self._plantCache = None + return self.ttl[('atk', px, py)] = rnd + 20 self._plantCache = None return @@ -573,6 +641,22 @@ def _score_heal(self, ct, mi, my_bit, others): mine = path.claims(mi, my_bit, others, damaged) if mine: return 8.0, ('heal', mine) + # Pantheon: an own sentinel TRADING with an enemy sentinel counts as + # very damaged — medics pre-park beside it. Scores 7 — BELOW route's + # 7.75, exactly as the paper has it (an 8.0 here parked the whole + # workforce during mirror turret wars and starved the economy). + trading = 0 + for (x, y), kind in mi.own_turrets.items(): + if kind != 'S': + continue + for (ex, ey), ekind in mi.enemy_turrets.items(): + if ekind == 'S' and (x - ex) ** 2 + (y - ey) ** 2 <= 32: + trading |= mi.bit(x, y) + break + if trading: + mine = path.claims(mi, my_bit, others, trading) + if mine: + return 7.0, ('heal', mine) # Pantheon chase zone: 8 pathing steps around our conveyor network # (was 2 tiles) — intruders get hunted before they reach the chain. zone = mi.own_conveyors @@ -613,6 +697,25 @@ def _run_heal(self, ct, mi, data): ct.build_launcher(p) return return + # Pantheon counter-launcher fallback: if the chase cannot PATH to the + # intruder (their launcher zone / barriers), don't give up — approach + # the ring around them and zone back with our own launcher. + step = path.next_step(mi, (pos.x, pos.y), mi.expand(tb) & mi.passable()) + if step is None: + ring2 = mi.expand(mi.expand(tb)) & mi.passable() + if (manhattan(pos, tpos) <= 3 and ct.get_action_cooldown() == 0 + and ct.get_global_resources() + >= ct.get_launcher_cost() + 60): + for d in CARDINALS: + p = pos.add(d) + if (self._inBounds(p) and ct.is_in_vision(p) + and manhattan(p, tpos) <= 2 + and ct.can_build_launcher(p)): + ct.build_launcher(p) + mi.own_turrets[(p.x, p.y)] = 'L' + return + self._moveToward(ct, ring2) + return self._moveToward(ct, mi.expand(tb) & mi.passable()) # ------------------------------------------------------------------ @@ -826,6 +929,23 @@ def _run_route(self, ct, mi, mask): facing = DELTA_TO_DIR[(fbest[1], fbest[2])] break if link is None: + # Pantheon: an unroutable dead-end near enemies gets CAPPED with a + # barrier on its output so they can't exploit/extend it (anti-seed) + f = mi.own_conv_facing.get((hx, hy)) + if f is not None and mi.enemy_bots: + near = mi.expand(mi.expand(mi.expand(mi.expand( + mi.bit(hx, hy))))) + if near & mi.enemy_bots and ct.get_action_cooldown() == 0: + ox, oy = hx + f[0], hy + f[1] + if 0 <= ox < mi.w and 0 <= oy < mi.h: + cap = Position(ox, oy) + pos0 = ct.get_position() + if (pos0.distance_squared(cap) <= 2 + and ct.is_in_vision(cap) + and ct.can_build_barrier(cap)): + ct.build_barrier(cap) + mi.own_barriers |= mi.bit(ox, oy) + mi.note_tile(ox, oy, mapinfo_mod.T_BLOCK) self.ttl[('route', hx, hy)] = rnd + 20 return pos = ct.get_position() @@ -1162,6 +1282,21 @@ def sentinel(self, ct: Controller) -> None: EntityType.CONVEYOR: 1, } targets = set(priority) + # context for the Pantheon heal-waste exceptions and the + # furthest-from-healers tiebreak + enemyBuilders = [] + allyBotsNear = 0 + allySentinelInSight = False + for eid in ct.get_nearby_units(): + et = ct.get_entity_type(eid) + if ct.get_team(eid) == team: + if et == EntityType.BUILDER_BOT: + allyBotsNear += 1 + elif et == EntityType.SENTINEL \ + and eid != ct.get_id(): + allySentinelInSight = True + elif et == EntityType.BUILDER_BOT: + enemyBuilders.append(ct.get_position(eid)) bestTile, bestKey = None, None for tile in ct.get_attackable_tiles(): if not self._inBounds(tile) or not ct.is_in_vision(tile): @@ -1183,7 +1318,18 @@ def sentinel(self, ct: Controller) -> None: tilePriority += 2 elif builderID is not None: continue - key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, -targetHp) + healerDist = min( + (manhattan(tile, e) for e in enemyBuilders), default=99) + # Pantheon heal-waste rule: skip cheap heal-sustainable targets + # (conveyor/barrier) with a medic adjacent, UNLESS we outnumber + # locally, an ally sentinel is in sight (out-damage the heals), + # or the shot one-taps it anyway. + if (buildingID is not None and tilePriority <= 2 + and healerDist <= 1 and targetHp > 18 + and not allySentinelInSight and allyBotsNear < 2): + continue + key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, + healerDist, -targetHp) if bestTile is None or key > bestKey: bestKey, bestTile = key, tile if bestTile is None: diff --git a/mapinfo.py b/mapinfo.py index 4f7f7c6..be187dc 100644 --- a/mapinfo.py +++ b/mapinfo.py @@ -55,6 +55,8 @@ def __init__(self, w: int, h: int): self.enemy_turrets = {} # (x,y) -> 'G' | 'S' self.enemy_buildings = {} # (x,y) -> EntityType self.own_harvesters = {} # (x,y) -> True + self.own_turrets = {} # (x,y) -> True (for death detection) + self.turret_losses = [] # tiles where an own turret just died self._threat_cache = (None, 0) # per-turn dynamic masks @@ -211,6 +213,10 @@ def update_vision(self, ct): self.enemy_turrets.pop((x, y), None) self.own_harvesters.pop((x, y), None) self.own_conv_facing.pop((x, y), None) + if self.own_turrets.pop((x, y), None): + # our turret stood here and is gone: it DIED. Tell the + # placement layer so it stops feeding this tile. + self.turret_losses.append((x, y)) continue etype = ct.get_entity_type(bid) team = ct.get_team(bid) @@ -240,6 +246,12 @@ def update_vision(self, ct): self.own_harvesters[(x, y)] = True elif etype == EntityType.BARRIER: self.own_barriers |= self.bit(x, y) + elif etype == EntityType.GUNNER: + self.own_turrets[(x, y)] = 'G' + elif etype == EntityType.SENTINEL: + self.own_turrets[(x, y)] = 'S' + elif etype == EntityType.LAUNCHER: + self.own_turrets[(x, y)] = 'L' for uid in ct.get_nearby_units(): if ct.get_entity_type(uid) != EntityType.BUILDER_BOT: continue diff --git a/planes.py b/planes.py index 27c280b..0cff7e7 100644 --- a/planes.py +++ b/planes.py @@ -85,15 +85,52 @@ def gunner_planes(mi, class_masks, valid_mask): return out -def best_placement(mi, class_masks, valid_mask): - """(score, x, y, facing_key) of the best gunner placement, or None.""" - stacks = gunner_planes(mi, class_masks, valid_mask) +SENT_RANGE = 5 + + +def sentinel_planes(mi, class_masks, valid_mask): + """Per-facing plane stacks for SENTINEL placements. Band = forward 1..5 + along the facing, +-1 lateral; sentinels shoot OVER tiles so no wall + clipping. No range discount (the whole band hits equally).""" + shifts_back = { + 'E': mi.west, 'W': mi.east, 'N': mi.south, 'S': mi.north, + } + lateral = { + 'E': (mi.north, mi.south), 'W': (mi.north, mi.south), + 'N': (mi.east, mi.west), 'S': (mi.east, mi.west), + } + out = {} + for fk, back in shifts_back.items(): + la, lb = lateral[fk] + stack = [0] * NUM_PLANES + for value, emask in class_masks: + if not emask: + continue + s = emask + for _ in range(SENT_RANGE): + s = back(s) + if not s: + break + spots = (s | la(s) | lb(s)) & valid_mask + add_const(stack, value, spots) + out[fk] = stack + return out + + +def best_over(stacks, cands, mi): + """(score, x, y, facing_key) argmax across facing stacks within cands.""" best = None - for fk, planes in stacks.items(): - score, mask = argmax_mask(planes, valid_mask) + for fk, stack in stacks.items(): + score, mask = argmax_mask(stack, cands) if score > 0 and mask: lsb = mask & -mask x, y = mi.xy(lsb) if best is None or score > best[0]: best = (score, x, y, fk) return best + + +def best_placement(mi, class_masks, valid_mask): + """(score, x, y, facing_key) of the best gunner placement, or None.""" + stacks = gunner_planes(mi, class_masks, valid_mask) + return best_over(stacks, valid_mask, mi) From 679178395708b4deb65776b3b598bc85dce3c2da Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 22:32:25 +0530 Subject: [PATCH 25/29] promote minimal Ijti build (beats v65 7-3) Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 29704 -> 27776 bytes main.py | 182 +++++++++-------------------------------------------- mapinfo.py | 15 ++--- planes.py | 47 ++------------ 4 files changed, 38 insertions(+), 206 deletions(-) diff --git a/bot.zip b/bot.zip index 0f200b0e4ac6dd3419d4f29b21e8a56e1106984b..ee3787682a98ee7f5a29b38ef9e6b7ee20694239 100644 GIT binary patch delta 24478 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 delta 26430 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 gCCNdIqk9SrPWTk(GjTD^E3TTR diff --git a/main.py b/main.py index da6347c..d15e332 100644 --- a/main.py +++ b/main.py @@ -182,9 +182,7 @@ def builder(self, ct: Controller) -> None: if rnd % 16 == 0: self.ttl = {k: v for k, v in self.ttl.items() if v > rnd} - # death memory: a turret we built got destroyed -> ban that placement - # tile for a long while (stops the build-die-rebuild Ti pump the - # sprint-vs-Ijti replay exposed: same gunner tile fed every 4 rounds) + # death memory (Ijti fix): ban tiles where our turrets died if mi.turret_losses: for (lx, ly) in mi.turret_losses: self.ttl[('plant', lx, ly)] = rnd + 80 @@ -365,16 +363,11 @@ def _score_attack(self, ct, mi, my_bit, others): if rnd < 30 or self.myNum % 3 != 0 or self.myNum == 1: return 0.0, None # builder #1 is the home guard, never sieges guess = self._enemyCoreGuess() - # bit-sliced plane placement (Pantheon): score EVERY legal placement - # board-wide — prefer one we can build THIS TURN (adjacent), but only - # when it's COMPARABLE to the global best (their immediacy rule is - # among preferred tiles, not a license for low-grade adjacent spam). + # bit-sliced plane placement (Pantheon): score EVERY legal gunner + # placement board-wide, take the best above threshold. plant = self._bestPlant(ct, mi) - near = self._bestPlant(ct, mi, near_mask=mi.expand(my_bit) & ~my_bit) - if near is not None and (plant is None or near[0] >= plant[0] - 10): - plant = near if plant is not None: - score, px, py, fk, pkind = plant + score, px, py, fk = plant # geographic money gate on the PLACEMENT tile if self.teamCore is not None and guess is not None: dHome = abs(px - self.teamCore.x) + abs(py - self.teamCore.y) @@ -385,7 +378,7 @@ def _score_attack(self, ct, mi, my_bit, others): if not deep or ti >= 150: mine = path.claims(mi, my_bit, others, mi.bit(px, py)) if mine: - return 9.0, ('plant', (px, py, fk, pkind)) + return 9.0, ('plant', (px, py, fk)) # fallback: walk at remembered enemy buildings (old greedy pathway) cands = 0 for (x, y) in mi.enemy_buildings: @@ -411,8 +404,9 @@ def _score_attack(self, ct, mi, my_bit, others): PLANT_VALUES = None # built lazily: EntityType -> placement value - def _plantStacks(self, ct, mi): - """Cached plane stacks + candidate masks for turret placement.""" + def _bestPlant(self, ct, mi): + """Best gunner placement from the score planes, cached per + (struct, enemy-set). Threshold 20 = better than a lone barrier.""" if Player.PLANT_VALUES is None: Player.PLANT_VALUES = { EntityType.CORE: 60, EntityType.GUNNER: 40, @@ -434,60 +428,26 @@ def _plantStacks(self, ct, mi): valid = (mi.seen & ~mi.walls & ~mi.blocked & ~mi.ore & ~mi.own_conveyors & ~mi.own_barriers & ~mi.expand(mi.expand(mi.enemy_bots))) - # death memory: tiles where our turrets died are banned while fresh + # death memory (Ijti fix): freshly-fatal tiles are banned rnd = ct.get_current_round() for (kind, kx, ky), exp in self.ttl.items(): if kind == 'plant' and exp > rnd: valid &= ~mi.bit(kx, ky) - data = None + best = None if class_masks and valid: + # threat penalty (Ijti fix): unthreatened placements first; + # threatened only at 2x value (trade, not feeding tube) threat = mi.threat() - # sentinels never inside enemy GUNNER cover (Pantheon: they shoot - # too slowly to win that trade) - gthreat = 0 - for (x, y), kind in mi.enemy_turrets.items(): - if kind != 'G': - continue - for shift in (mi.east, mi.west, mi.north, mi.south): - ray = mi.bit(x, y) - for _ in range(3): - ray = shift(ray) & ~mi.walls - gthreat |= ray - gun = planes.gunner_planes(mi, class_masks, valid) - sent = planes.sentinel_planes(mi, class_masks, valid & ~gthreat) - data = (gun, sent, valid & ~threat, valid & threat, - valid & ~gthreat) - self._plantCache = (key, data) - return data - - def _bestPlant(self, ct, mi, near_mask=None): - """Best placement (score, x, y, facing, 'G'|'S'). Threat tiles need - 2x value; sentinel only when significantly better than the best - gunner (Pantheon: 'when a gunner would be significantly worse').""" - data = self._plantStacks(ct, mi) - if data is None: - return None - gun, sent, plain, hot, sval = data - if near_mask is not None: - plain &= near_mask - hot &= near_mask - sval &= near_mask - best = planes.best_over(gun, plain, mi) - hotbest = planes.best_over(gun, hot, mi) - if hotbest is not None and hotbest[0] >= 40 \ - and (best is None or hotbest[0] > best[0]): - best = hotbest - kind = 'G' - ti = ct.get_global_resources() - if ti >= ct.get_sentinel_cost() + 40: - sbest = planes.best_over(sent, sval, mi) - if sbest is not None and sbest[0] >= 40 \ - and (best is None or sbest[0] >= best[0] + 15): - best = sbest - kind = 'S' - if best is None or best[0] < 20: - return None - return (best[0], best[1], best[2], best[3], kind) + best = planes.best_placement(mi, class_masks, valid & ~threat) + if best is None or best[0] < 20: + hot = planes.best_placement(mi, class_masks, valid & threat) + if hot is not None and hot[0] >= 40 \ + and (best is None or hot[0] > best[0]): + best = hot + if best is not None and best[0] < 20: + best = None + self._plantCache = (key, best) + return best def _enemyFieldCached(self, mi, guess): key = (mi.struct_version, guess.x, guess.y) @@ -556,7 +516,7 @@ def _run_attack(self, ct, mi, data): self._moveToward(ct, mi.expand(mi.expand(m))) return if kind == 'plant': - px, py, fk, pkind = payload + px, py, fk = payload tile = Position(px, py) facing = {'E': Direction.EAST, 'W': Direction.WEST, 'N': Direction.NORTH, 'S': Direction.SOUTH}[fk] @@ -567,18 +527,10 @@ def _run_attack(self, ct, mi, data): return if pos.distance_squared(tile) <= 2 \ and ct.get_action_cooldown() == 0: - if ct.is_in_vision(tile): - if pkind == 'S' \ - and ct.can_build_sentinel(tile, facing): - ct.build_sentinel(tile, facing) - mi.own_turrets[(px, py)] = 'S' - self._plantCache = None - return - if ct.can_build_gunner(tile, facing): - ct.build_gunner(tile, facing) - mi.own_turrets[(px, py)] = 'G' - self._plantCache = None - return + if ct.is_in_vision(tile) and ct.can_build_gunner(tile, facing): + ct.build_gunner(tile, facing) + self._plantCache = None + return self.ttl[('atk', px, py)] = rnd + 20 self._plantCache = None return @@ -641,22 +593,6 @@ def _score_heal(self, ct, mi, my_bit, others): mine = path.claims(mi, my_bit, others, damaged) if mine: return 8.0, ('heal', mine) - # Pantheon: an own sentinel TRADING with an enemy sentinel counts as - # very damaged — medics pre-park beside it. Scores 7 — BELOW route's - # 7.75, exactly as the paper has it (an 8.0 here parked the whole - # workforce during mirror turret wars and starved the economy). - trading = 0 - for (x, y), kind in mi.own_turrets.items(): - if kind != 'S': - continue - for (ex, ey), ekind in mi.enemy_turrets.items(): - if ekind == 'S' and (x - ex) ** 2 + (y - ey) ** 2 <= 32: - trading |= mi.bit(x, y) - break - if trading: - mine = path.claims(mi, my_bit, others, trading) - if mine: - return 7.0, ('heal', mine) # Pantheon chase zone: 8 pathing steps around our conveyor network # (was 2 tiles) — intruders get hunted before they reach the chain. zone = mi.own_conveyors @@ -697,25 +633,6 @@ def _run_heal(self, ct, mi, data): ct.build_launcher(p) return return - # Pantheon counter-launcher fallback: if the chase cannot PATH to the - # intruder (their launcher zone / barriers), don't give up — approach - # the ring around them and zone back with our own launcher. - step = path.next_step(mi, (pos.x, pos.y), mi.expand(tb) & mi.passable()) - if step is None: - ring2 = mi.expand(mi.expand(tb)) & mi.passable() - if (manhattan(pos, tpos) <= 3 and ct.get_action_cooldown() == 0 - and ct.get_global_resources() - >= ct.get_launcher_cost() + 60): - for d in CARDINALS: - p = pos.add(d) - if (self._inBounds(p) and ct.is_in_vision(p) - and manhattan(p, tpos) <= 2 - and ct.can_build_launcher(p)): - ct.build_launcher(p) - mi.own_turrets[(p.x, p.y)] = 'L' - return - self._moveToward(ct, ring2) - return self._moveToward(ct, mi.expand(tb) & mi.passable()) # ------------------------------------------------------------------ @@ -929,23 +846,6 @@ def _run_route(self, ct, mi, mask): facing = DELTA_TO_DIR[(fbest[1], fbest[2])] break if link is None: - # Pantheon: an unroutable dead-end near enemies gets CAPPED with a - # barrier on its output so they can't exploit/extend it (anti-seed) - f = mi.own_conv_facing.get((hx, hy)) - if f is not None and mi.enemy_bots: - near = mi.expand(mi.expand(mi.expand(mi.expand( - mi.bit(hx, hy))))) - if near & mi.enemy_bots and ct.get_action_cooldown() == 0: - ox, oy = hx + f[0], hy + f[1] - if 0 <= ox < mi.w and 0 <= oy < mi.h: - cap = Position(ox, oy) - pos0 = ct.get_position() - if (pos0.distance_squared(cap) <= 2 - and ct.is_in_vision(cap) - and ct.can_build_barrier(cap)): - ct.build_barrier(cap) - mi.own_barriers |= mi.bit(ox, oy) - mi.note_tile(ox, oy, mapinfo_mod.T_BLOCK) self.ttl[('route', hx, hy)] = rnd + 20 return pos = ct.get_position() @@ -1282,21 +1182,6 @@ def sentinel(self, ct: Controller) -> None: EntityType.CONVEYOR: 1, } targets = set(priority) - # context for the Pantheon heal-waste exceptions and the - # furthest-from-healers tiebreak - enemyBuilders = [] - allyBotsNear = 0 - allySentinelInSight = False - for eid in ct.get_nearby_units(): - et = ct.get_entity_type(eid) - if ct.get_team(eid) == team: - if et == EntityType.BUILDER_BOT: - allyBotsNear += 1 - elif et == EntityType.SENTINEL \ - and eid != ct.get_id(): - allySentinelInSight = True - elif et == EntityType.BUILDER_BOT: - enemyBuilders.append(ct.get_position(eid)) bestTile, bestKey = None, None for tile in ct.get_attackable_tiles(): if not self._inBounds(tile) or not ct.is_in_vision(tile): @@ -1318,18 +1203,7 @@ def sentinel(self, ct: Controller) -> None: tilePriority += 2 elif builderID is not None: continue - healerDist = min( - (manhattan(tile, e) for e in enemyBuilders), default=99) - # Pantheon heal-waste rule: skip cheap heal-sustainable targets - # (conveyor/barrier) with a medic adjacent, UNLESS we outnumber - # locally, an ally sentinel is in sight (out-damage the heals), - # or the shot one-taps it anyway. - if (buildingID is not None and tilePriority <= 2 - and healerDist <= 1 and targetHp > 18 - and not allySentinelInSight and allyBotsNear < 2): - continue - key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, - healerDist, -targetHp) + key = (tilePriority, 1 if 0 < targetHp <= 18 else 0, -targetHp) if bestTile is None or key > bestKey: bestKey, bestTile = key, tile if bestTile is None: diff --git a/mapinfo.py b/mapinfo.py index be187dc..47ea4c2 100644 --- a/mapinfo.py +++ b/mapinfo.py @@ -55,7 +55,7 @@ def __init__(self, w: int, h: int): self.enemy_turrets = {} # (x,y) -> 'G' | 'S' self.enemy_buildings = {} # (x,y) -> EntityType self.own_harvesters = {} # (x,y) -> True - self.own_turrets = {} # (x,y) -> True (for death detection) + self.own_turrets = {} # (x,y) -> True (death detection) self.turret_losses = [] # tiles where an own turret just died self._threat_cache = (None, 0) @@ -212,11 +212,9 @@ def update_vision(self, ct): self.enemy_buildings.pop((x, y), None) self.enemy_turrets.pop((x, y), None) self.own_harvesters.pop((x, y), None) - self.own_conv_facing.pop((x, y), None) if self.own_turrets.pop((x, y), None): - # our turret stood here and is gone: it DIED. Tell the - # placement layer so it stops feeding this tile. self.turret_losses.append((x, y)) + self.own_conv_facing.pop((x, y), None) continue etype = ct.get_entity_type(bid) team = ct.get_team(bid) @@ -246,12 +244,9 @@ def update_vision(self, ct): self.own_harvesters[(x, y)] = True elif etype == EntityType.BARRIER: self.own_barriers |= self.bit(x, y) - elif etype == EntityType.GUNNER: - self.own_turrets[(x, y)] = 'G' - elif etype == EntityType.SENTINEL: - self.own_turrets[(x, y)] = 'S' - elif etype == EntityType.LAUNCHER: - self.own_turrets[(x, y)] = 'L' + elif etype in (EntityType.GUNNER, EntityType.SENTINEL, + EntityType.LAUNCHER): + self.own_turrets[(x, y)] = True for uid in ct.get_nearby_units(): if ct.get_entity_type(uid) != EntityType.BUILDER_BOT: continue diff --git a/planes.py b/planes.py index 0cff7e7..27c280b 100644 --- a/planes.py +++ b/planes.py @@ -85,52 +85,15 @@ def gunner_planes(mi, class_masks, valid_mask): return out -SENT_RANGE = 5 - - -def sentinel_planes(mi, class_masks, valid_mask): - """Per-facing plane stacks for SENTINEL placements. Band = forward 1..5 - along the facing, +-1 lateral; sentinels shoot OVER tiles so no wall - clipping. No range discount (the whole band hits equally).""" - shifts_back = { - 'E': mi.west, 'W': mi.east, 'N': mi.south, 'S': mi.north, - } - lateral = { - 'E': (mi.north, mi.south), 'W': (mi.north, mi.south), - 'N': (mi.east, mi.west), 'S': (mi.east, mi.west), - } - out = {} - for fk, back in shifts_back.items(): - la, lb = lateral[fk] - stack = [0] * NUM_PLANES - for value, emask in class_masks: - if not emask: - continue - s = emask - for _ in range(SENT_RANGE): - s = back(s) - if not s: - break - spots = (s | la(s) | lb(s)) & valid_mask - add_const(stack, value, spots) - out[fk] = stack - return out - - -def best_over(stacks, cands, mi): - """(score, x, y, facing_key) argmax across facing stacks within cands.""" +def best_placement(mi, class_masks, valid_mask): + """(score, x, y, facing_key) of the best gunner placement, or None.""" + stacks = gunner_planes(mi, class_masks, valid_mask) best = None - for fk, stack in stacks.items(): - score, mask = argmax_mask(stack, cands) + for fk, planes in stacks.items(): + score, mask = argmax_mask(planes, valid_mask) if score > 0 and mask: lsb = mask & -mask x, y = mi.xy(lsb) if best is None or score > best[0]: best = (score, x, y, fk) return best - - -def best_placement(mi, class_masks, valid_mask): - """(score, x, y, facing_key) of the best gunner placement, or None.""" - stacks = gunner_planes(mi, class_masks, valid_mask) - return best_over(stacks, valid_mask, mi) From 98a09eeee54715c70794f6bef0b94963fe2e8a86 Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 22:45:50 +0530 Subject: [PATCH 26/29] gate 3: threat hard-avoid routing promoted Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 27776 -> 27981 bytes main.py | 11 +++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index ee3787682a98ee7f5a29b38ef9e6b7ee20694239..ad028275bf4c4625a0e4d1f5802929b75f1e8f04 100644 GIT binary patch delta 13367 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 delta 13122 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 Date: Wed, 22 Jul 2026 22:54:56 +0530 Subject: [PATCH 27/29] gate 5: medic pre-park at 7.0 promoted Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 27981 -> 28169 bytes main.py | 15 +++++++++++++++ mapinfo.py | 5 +++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bot.zip b/bot.zip index ad028275bf4c4625a0e4d1f5802929b75f1e8f04..884319f1027801d4443a8a0e19161efe8ea4fb7f 100644 GIT binary patch delta 16021 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 HbKU;|2;kC% delta 15909 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 Q`gbdUf`FXa{O9Ta0ItJ{zyJUM diff --git a/main.py b/main.py index b88a626..a3e552e 100644 --- a/main.py +++ b/main.py @@ -593,6 +593,21 @@ def _score_heal(self, ct, mi, my_bit, others): mine = path.claims(mi, my_bit, others, damaged) if mine: return 8.0, ('heal', mine) + # Pantheon: own sentinel trading with an enemy sentinel counts as very + # damaged — medics pre-park. Scores 7, BELOW route's 7.75 (the paper's + # exact number; 8.0 parked the workforce and starved the economy). + trading = 0 + for (x, y), tk in mi.own_turrets.items(): + if tk != 'S': + continue + for (ex, ey), ekind in mi.enemy_turrets.items(): + if ekind == 'S' and (x - ex) ** 2 + (y - ey) ** 2 <= 32: + trading |= mi.bit(x, y) + break + if trading: + mine = path.claims(mi, my_bit, others, trading) + if mine: + return 7.0, ('heal', mine) # Pantheon chase zone: 8 pathing steps around our conveyor network # (was 2 tiles) — intruders get hunted before they reach the chain. zone = mi.own_conveyors diff --git a/mapinfo.py b/mapinfo.py index 47ea4c2..66dc0e5 100644 --- a/mapinfo.py +++ b/mapinfo.py @@ -244,8 +244,9 @@ def update_vision(self, ct): self.own_harvesters[(x, y)] = True elif etype == EntityType.BARRIER: self.own_barriers |= self.bit(x, y) - elif etype in (EntityType.GUNNER, EntityType.SENTINEL, - EntityType.LAUNCHER): + elif etype == EntityType.SENTINEL: + self.own_turrets[(x, y)] = 'S' + elif etype in (EntityType.GUNNER, EntityType.LAUNCHER): self.own_turrets[(x, y)] = True for uid in ct.get_nearby_units(): if ct.get_entity_type(uid) != EntityType.BUILDER_BOT: From 1c0565e3fb6df24e5e800213ba47f38e4b693ea9 Mon Sep 17 00:00:00 2001 From: Subodh Date: Wed, 22 Jul 2026 23:07:10 +0530 Subject: [PATCH 28/29] register closed: gates 3/5/7/8 champion Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 28169 -> 28528 bytes main.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/bot.zip b/bot.zip index 884319f1027801d4443a8a0e19161efe8ea4fb7f..33a337d1532cab50c3290b6b7b11a63e896c2ed6 100644 GIT binary patch delta 14471 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 delta 14071 zcmZX*Q*hwV6Yd=w8*gmeww-Kj+jhROt&KO%#@g7nZQFKoe*cTN>YRG#=IN@ZYo;%z zYPzcDbF&2+xdHl9NfsOe9Rvgf24tb>N%u?iQpA@X1VsBN@h3hoDs@L5XZ)(Os&2eY zl{Q|GoO9=`MFblaAp`;+ki?ZNeJOg;e(~*>;+Mq#+dj_$L|SrdRGr?Mq9ovIzIpz5 z={WmqFPibI38vu}`m8AiCF^bX_xtycamx&hb21uPWpyJA#4#)Nfz#r2(^QzPFssIh zdex9YR}_uef7%#;vO`O=hc`Zkjj&-s0VBy9nm~yp7^ql5h(=j@*eP8us3w$t6c<0D4RoS2Iv*4LiYtBEYf5Y%36qnS#op@T4YDk zo?1`ha6oku4T0gl&%AAGOodsBI(ck0T=Y1MfA~xx362mzd+Wv)EKve~R+WymnzWWK z)L7erI?jY8-4Z5zIay$pHkItDEu3ntY+5lM3NtvZl~}ArS0eqBQJer%O?Oah?f5HR z!h-4S6l{oR8g4PL(;7v62K=WdWPet;6Y8WP4x^_!7&l5MDT=xpS10Orhr{h1{#=&{zjUvuxdE#%L-4OPNsjs4& zwWLjIg!?Z-t&s3qujjkR ztmFoGCpv+JO`I-nfYZD9(Z$p0Ys=Tk+nIUp`|I$*_`&J=@O1UWl#hq7+xHWR=*va1 z2kqi{a0ldQJ22j00(k>TgZCubZ=bXo8zCEc#S^$T@bMAM%@Gvj#{Kj0F#r1U`cCN2 zH3;bAU0r{=-u)Rqc2L~z8DF+wRNUTmB)kL_h~3WV2JTV6`Bt{KeQuvVU-mx2XJ3q6 zzc9D>{r`0{4)CuSYB4Mv$=BnqC^=6b5*tc3MmT}aHEO2%_ynxGeAon@q(K=O$azb$ z6_6D8p(u1NKYp=v`PU?#;lR5|giUP-1FsF5BB|p4g2HH;Y0ygbx zopbQBSh$r?rqHhGd$lB@jFXoqR<%ZGvG$j6E*bN!wue;%7lah5*oYta{aG|8yFii! zr;t&@1CO?aIJG^d7Ix$*2?UOgm|RdoJu!Dv9(}#p0}DfGJ?_gcfl5_Ik3;adn&!j+ zLNpOZva9|^LmNAFeFc>t0j4+}2G=~HK)JvC!Z>bTl_tU)^fs^}913TDBJ3qNd|0Pb*kyWVAv;?ozR?rYt_*a^F3y|a*|85CK zKy-*I+UsLYo1pG75iXG>VaD9R&hYBj%}4 zX5vA8!BxQ(2ug`j`Gm;Q52z73qM|zaul9D31=)*1cLO5i4CE?EV_jzd?N6``P?iU{ z373{G{-ChOL*d2UAJ5Zz_N*Smw?T0D@0x5{ftTgN4ip+{vo97FK?W2Z?H?bLBIC6- zG=aN0y_r9}O`c{z$7x4hf&iZ{Uz1t$9t2FkX^B{Mo{laKKC`fDItaBi0b)d0t+5cXbb*9fGW;}cftG~N3dM>MS}4>ev$lHOAfYF@B@bTOiv+8 zx4@JEk{XQ=V;*A4uCM>Ngb@=FWQTh{%Tt{`NZ$Zxj@TO)FAl6)jsVi@C=w9?xclWB zUZEayFZps@Oy$wMDra#X8*f%|nG|&s5+Ch-wT+Zkxq9jJ7*PEtniAzQjCM-v&fwo% z(?W0tgrF*c)!>=IE&mt;Y6l{@xX_J~22lQjDUo6z@4`!c;m|=jffFjr1_}fWpau6Z z%L^@2;ielj5cJ^Q`2*Cgw14q%DtCOChfQ&>kNWfbrRE|_~^6AoLYr)IczENuI8Czp_POc*5E>E3(AN<`FMwB zC8eeBl{LBoTS;4Ct)Yi_`W{5_?Rrql@$P@Bb}`4Th+zEy0RwdP|Gpa<1l;XhPOAJ6 zkF|a=G_r4Oq&^wi|I7`1euR;WsY#ViV~cU1DxR=c?lhgnHGS&hy1?h=F9t|eg=rf3Z%OvL47vBP-;-Oij4yZzQep;T9AhZIDSh?}pv^htfAf4z> zPv9BRd9#~ZVge=t#isHeauF*T_WLpv@z zNz=c|HS@)=)jQ5XD7646KLYnz95FZ)4Wu8a%7$leAMmD{Ga_E!v12lyjqQ#(c%V0)Y3&MZ#oB{byz2PMGMmH33&{c_&TEbkM zP!Q#-Yj%MgnCK#W>tq2K&sj7BKXN2c=$p;Vt*TeO^`eP^dGo=w_DcG4Z7WMJDBw^5 zKSf3r+c3dOy>2Gn=Uh2moQq`8h}rJsn4?47iC=*dqkhyuLC<&agDbq zz!1sAkX@V{76Vb|Y~B zKkn~p#|e-f0MI#+Ytz=FVw(*5^&fiMsQ2KWbVI?|Z%isO88G*tAOq|8P@^$M=8Gms z0kz@J{v=!(HL0E3aW~xp0=T)CqLIE;^hrala@jE0nGgr@jC>AIdBl3PVu+q3dfEVm zg0T_Q_t*t9Xtb3UY!OYJaxbg*pjF`dls9_ zfFyG~{G=RdU|vAwbNHaq@__a-_~O8O_*IZ`hjog9=mrPJh2?ladv?nGGybs(~tMga(u&>y!9RDT=Z*$jYK&FL6 zNat*~=)Qgs<=={${ZF2>TgOz=NnF<5TEVbn7FvoS9jL*^89L=A|IUO`>{xM#KUFD5 zc8HLThY1kwe_|u(;-E0xv3uQ{q%92<*Ox4j=p9JpWr7uUcfM$l2u#AC_+zDT&lVF~ zC^9`D`A{Qs-?$(|>IV=ZYpX%e0nn}P2MG%lCe26_63Hs`v|0$3M1f&-o0?-!Q?0)UVon z%;X@rCkY5>0?Dy96wAlKQ>2l0YP0kg5T=n%&#|%?FWOuGO&co%Vd=3lo;J_|>QCEsP4)uG7cK zD8-AYk;q5umu5ZKY3WIA)BWX&y4meA3$^`V^-hBBI$8l&?@^0-3CY(~y~mkq8b!y) z^1J`So@$zD=!gG8bv3{we?j!)dN8GYcltNc?Q;2pK$D^6eGBPLZQFD>>wSvC=`pq4 z##kd+4`u}}5$lfyH;-6q{Sogdx}ypw3`QeY>GWXFfG|cQf*=ykqI+U_P_OY7bVabvjJ&kZ(ls-yx~#P+#WzWNYTSTl^p4!F@B3P^eNNji z61vsQB(ZxM(FzNx@R{u%Q|$}sw;;aKWWLpa$-V%GPR*X@V`+{~YwF-}h((>i(=cma+ZCqWhb_w|pvCN#yUOz8ALbEjKhiuXKDV~-w*Vjux?=yUfmYiO0SHMP*awz zDU-Yh)4Kq24nDPyh=BIH{_x&j$yRIAg!3GF}C zUm8J;X6u11G*?el#XnIXh(*bYjDoJ1t=xVe{tCtTfl{T=;WPg0lrA`6B*&eQYv(w&A0s_G6TGFJhDwzdm z?W+5Z?s-76io)Zcy%km3sOd-w^)^cu;Lt^XlhwZG-13M`<{`^FaeN&cCfyL5Ya|$Y zn94tG;Cl<{vz9*+=?(HH7sf3+$Ud4RYkA5tR);w&a>k=YnQ5<}GPZUGr6<%gil9T9 z#29$d@On_kZfBcXiA8*;7_q716f@9(tWF-_^mlx7rrB#n1h3r{+BCmUkFM&;cem@Z zgz9GF_#CkZkWi6-Pt`e+`qxM>c$UlBKXouo9T3ciDQF=Db)jSbwK~F_ z*HKbI9^>`x=j7Dlm#QYZ1jXzscDA?(SzO>f4d;w_?M3Mt*~QVWZFBTD666KQ#YE); z$&K$IwB$KMC*N`N(jSEnKQu*LoM#@pd++F)*Gc1LZca*d>tC`n$fLz`ss%TXRPBL? zYO)G`jjux|)Z19QZkW??&+<_f=;VKqD$>XsE+`PChSslB@;Yc+M<2A*UHRe_4*CJ~ zhb`tV7$s4TOUFD&K|?2%ri}cjscO_+TTIO_(C3vmCZTJMdH$x7 z&`#1IWmB7r;f{y`fr(ibZH1y|ZE*NV3Tr#hU(RTYyLik54xp`_kynMtc&^OQG})8K zqJ;wXmRo*KZ+Lc-W&w!rkhBBbX7_-K-q$Z9EcJfr5*X69*lYcqfO`pw;4V@>zI$s^}mIco6q&P@=q7 zX-~~}KB@RAS{=^rBGoyeB&l5H6f~Jx)_qkBuQk|}!Zmh?{mEsXPH5+n00*`V2GJ@E z8qz<9E#$Nl>p6@Fr_u%1M2F5U!4Cs|@46-qs{BPV^YH-g+?aW=bCzy-dp-E!gh- zUK8IgXt9^@c}}bvKmcRO2>3|6gb0v8bo%fv*I-YTO2d{5*fG+lRC^sfn|G zM*1O`;K%tUV^C}0j4kbS?-tb z+tqbX5JSJ%4aWizJAgrn?P}|YHcgU}T}|>dD+WuM4I(^fIP!PkCC;3bDPQGtu-@S| z)TeV|q@Kj88Ol+#Cmf3{PLVH!m1Y3ZEaXXz>jXzG@wC$zHJ&Csy6`9OsSrf*k3kM?Rb~_w~(A45w@k z^p9!l0oFWHJHx(_kBVX2jkh#aDYP`88;G)u6#_?gp=?;%c|38wd7z300Zm=lv48-# z58$mFuFaBMA%^-zsvv}CqYyHKSYbrJY*arN8--Xp13;?Gx4qzTBR9|@{=#pYu8b`l zey53HU)I#=figR_>t<_h%rXZ$Ai7;Qw<^MWtPU6t)=Yia2~mzNr$Wn z9o0}8%sP+9x1%Dse&(O`CZc?9KU z!0hfw1C${el-K?Ooq0)$2r$!%Tb(|!HG~))#m)ur%hL`hmCJGkOCKgSmckeIU~BIC_=WV3se4*DkF(a4!lxE z6DHnwhbHe>BMAm1)COWhzuS>)_QgnITa=X?V3(#W=)5by=+V+r9yBE1@0D@t;QOfb zPYQcLrDM?*X$(17e&QY>S5F%TccppSjvJqfWgoO(w@s;AH}0`r_kCnWG}hgGtZQ!Z z$?H*0JwoGRw7rYwd(X$SA18fw!U2r} zAe`C~wAw{t+B)gil@jNIsVWm>dEwqVA9CB>HnlTE6%Vy5{cQX3HY?uNj4)%+{ z3WKQTP#K3Jm0K_Ge14e9j6GaSXtY#(+hB(Uf?6DGPaiD)~Z~tgG+jG@fUJ*9y-Z8BLQ@mc%f1>=H{W3GP&oa*P zG{BT8vlhd6nQSHlBJ8$E3^Tjr|+d|Gj#|22b8)3XajC?oP?pkY4^2XdAKM zUP#(c3fFRW{BB07yYNaj<|N>Vr>=vJ-a>S{YFsh{Ak%;0VqdJ<-W1%bHX)RqOqP*C zChqg;Qffu`v1Rjf;@jAXKf3M7&$Y`dC`uG2V*zN_Q`_+hjdJ=YNZJDpFn?(zx9F&w zv1Wv=cp~va&vOlv5k^pnaA|V^>VT9XX}<%AiHf=rB^Za6h`tO+Dy37+ykhcISWLq^7+g%CO*$3D`CW+Ry4=qCF=EwY7w7hfeP zx8Z4ajPau+H%$81zl|Q&T@yhN{?`)qKUE3J|Ln-@AA<3BY`I=_oZVmpK+n)&>l8@R z*Eb5vR!s4eFiHgkKuq&tAC7)HxVPNdor1g_)*|eyQI1C(5$5q19U?z>`_S2(*EChg zkAQckxV68^*_5VJJYgK}$LPC3F{XZ3)UbrOwWJnpm%LS6drYUKZ5OM3=XVICW~)bL=XViK`&8 zrJROTLL0w+7<04e7z+n?r!cm?lQ>Ze75WWB*ryqhAvHI1s5^<8W>Rg+!JJ9&J;*3h z5*Nm5j~9lb!#w$_DJlIxZG|MTS35YLWddQfOaGjp)YFNQ*;s7?NGw6D;#?B{`lZ^g zsJ{HfwYjpdd`-iJ{}XE|wVBewHLO3yxnFQWHc=85>moM%YD&uTE4T%z7w;zVxXne> zh4nJb5ZsYVJh8{w2R2U!n(%$4y+6J)b|22x0ZDAho;TK;Egrevd0`>U@WJexBOg%! zq!_+$=q;`|4@Hg>xMW9;VCH-$o+$vE)M7^**ES?*@OHLN609o8*tCh$q090{!0gPO zj=8{~Uu7^oe7@++Wi*Q-k>H<|NXrfIq@Jw#EXDy9vh z0%fFiftq`ue4u~bEpK0QFw?p-zSZ&`pE*Wk>&%oRJrmXj+xtw;e9xknBL_-c%HhV?lY7({1T*MiZq_CMh?7UF{^D2{c|$r?nhNG zSa4f*TGz127Dh1Co{G~>7^XW^)~dpyvj9+$5eQevjEw975B>E|PQ~s@R=$r8?13E$ zUCXQCCiu^w`;e%QxiIXT=+u!p9JTI6GMz(UkWNL=-^NmR(1Dku#g;bEh!(s-^tu~lrsUTnLA{PQPyU&l0bTwJ?l zBUrRMB(J1k+)kTcL4rf;HTgzkX%i9X-h=~gMS@sRzlEZcJw^C?i3_Y_2#}>7BPfr zLNRSAi{ej)phvB;@J}B_nbWHr{CMeA_h1a+ib3F7qN%*f&rj5b15gf;6Sq%)I+$g z#)Bi$m9`a^B`t=e>j}VDl)mNc*>$xlX;p4|`&R6l#GJ@O9r^rYpIJa^MvOWARY*Dd zgkccimwqmOqKCd>M zGOgfIhXCsx^}T^tfHJPquOpWsj9}0O2M+ehKUU-|!~VbKSq6b$)n&UU&aszD4R+RL z?ux<)a2^`@%a`x=Hq-*wZkJ=sHNi$M$170VeNZzBI`Z0Wi&%`lv_s8HcnHqU3qNw3 zEv^|4Vv`$}i0J*pR;BD{5)AjEMlg~{3v7#DFsNkY-GxB4i#0rX$xq0MkL2=bN!Tq} zd?=yvLToB~JwSm2aok$foToRg?KtTs5F-q%@Czb&JDjt_k({4+4xxH^JT%-;Y zv{;*_g3JqIgXT&omM!##8x;i!A`-8Y=BvXTD%ge)u_^Wrj_;QlwHjgW9E`L)J4;3a zl;(A1+oLx;ahP=HG>BA@&fJQbioN-@6nel#yBeb60_(llg+QY&S$WvpuM(y3XAT{j06w}`M6g!zZ^hu5AB0mg$^d8@T6>u2H{lRFy`C4=qdWO7_-V>Hvw|E z5jn;K1i+TuuWrPk0=HJ;<>Va@8D+?2D6~$TWCN_=N5|UfOb9<94G)a%qO@pu%b9(C zZ3J=LVZ0aR#IN`@QgBJNh@f}l2+Qi)4_jP$A5{fJp&!}>UB+Ng}%f+V~Oc6+Mc~#d+oy;6%96vTK5FvR|LZ#8;>if zXIIbfNA;_>Ah&lW+oGXuqH)WcwaB>tZ2>t1rS0As?w)Dx5HQNVQy%d;#Wb{3EsJHY z{?Al(Im1VUw=)m84_Sw&&V6JKQZ~Pex6Rh!%DC^mb=Gd0^iX(F3LoGucG_4%PXFwBT%!wdsF9pu_eg&Cdf5?pP{| z^#k>*Bz}hYS3GV>L`Sc&8u@0D_f8s{8jeq`BnUU!vVz04Y;b5+a)#DB$Qh@}tg;ph zLH+uR#q&Gul8uet#Y%fgc)p-|0}a?@&8rjTmTA{eIvB^4-ypc8laazQmQ%@mPDJM` zU1d=$7+Xys02MeltoS8cx?Nw)bl5I~9^o*?7s<>V3|i!pc7Qp=vi7xD4Z6C7Yu3zG zpa6xGDiFq*Dq27e32GDqzW_0m%oIsSWxtfTR3b=^A89+n4`*u!%`eWyFbQzo*~Z2= zWEQ+9h`POFP&C>buF^_Nxbb{c+RTz0#D}qjSNR>C=$S3}WExEtU$uybY^aO5&U&Md zMoIdix2C6y1lLd4O)zyMW~9{#H8xb>ht~Wf5-e`YoTi%SsO}p@Natlo$s5QsEhlGj zwe)Cev+^dBBiv4#{5g0X*$l*=LYYrpEQZw18O>LQYw+J@d=H)+Y@R%35zhLCMPj5Y zJknd%{+p-$=d)wyyo+Z&#YAR+eK`}x2|)*ZL?p>1UrkIw30;%GFR2ZD!HfIKUvZfx zbSExLX3ZAIiplWY6{wNqjGjm$>ky6JgQ{ltey5Jp==1YW6$Vx7_5!#h%+pUAY+%qL zE)r)>!G2MQFPA5oyhVF6&Sf1)i9^=BNZJ3s^i7vVovjg=Q|#m?o16y4*$@yFk}kry zG`OnNjSAv+7XsC(uaVAOOyU5-l+D>+0L4wEvGOP(TrGMy3 zb{pWwyg_aKJ^8GO)?We_RqBOtQqT;gLR_cYDTqAQb`~4(Wn`w4IA)dk>CexxHgvdu zbNGz(tc59x^N3(Kp_|{fQl_NCiHR9Q>@DGts#BPSY{g5G&;jF%EOQFzf^GsH>-bgf zwfe&mUkf9CirT8|hi?%Vh0m+bZ878jjr)QfY1VJ&>`r#sBNqv;$y!=D63t|6CV7JX zdpFWsStGn;=QEc6=*e5U$?BE-<9P9Rq?g*p>AI*ZTy?{r))vWlI?v{~P^Yz?IlG}C z92O%zSV4Z!Ed>Di{lXaKLB=LB)8W!NOV4Sx+p;?fQ1qBZJ+LdGb^a<;pvBraw4x0) ze|b8-V1QfBZP-&-k%!*JmK@J^UJ+l8W!|o&RC@=rW#0N(L~C-4HXvoUaWA$wKDocJjyO)h-~jE} z2HFNq$KYZk?hz3ujM+>Uei9)yzF<9VJ7Yi-%#yWf@n*eJ@FP0<(cAKhg>>4Z4yP>i z;~*I}Hq9HEJfeGy*ssjZZm-jFz$SFE&@~34k?knNz5g1GF!n;OB$&h-an=Mg0S&BT z9=(BC2Ec67na_uKlZ**>bL=IpL7DFMk)z$}rWG;__ISg0tV;VC=tK0Um~~t*ghV?s zQn7$cG)mb_L7r}ldeVLuv0KRF#o7aS8N`e`QG)T>1}nVl;B6HxPc;GmK_uP8xrjrg z@F*xgHUM?lI>Rh{OZOB1246Wqy;I=~35LDp9WdK(P@XG>PcIw=zxen(!O|qXtJ5BN zC(S#v7TF3hL%R`cD!=w>-x?@J#w-r>{xo;B&sUM&O7f))xUX%bMz<)@#|Mh+a_Rr@;G5H$7LP&lC#|%Cb+ZPyOvs7E9aeI(iytvzue? ze~fIo5l6FKRKESSdDG5TpOu9@d9HixeRO~)MiFlsV?@Ij^izu0SRVVPtcDvV16Xc` z)vP;At`fJ!Q8<<-ZybgWdNB=0=HA(!5XxNXO5BpcAhGc)`zh{MNT1`t;lYBkmHGju z0pSrvf5uYTj!}VqM3`94nSEbo3vc~sx}gv6NS>iXNMKw+kk}cjeM!~eVx8t)Ei_3* zn>v?~#F>uegX-zichLHexg6(49e@MH*wUw7;V@|!BR`Hnsm_V}u#+kjrQIZKm>%8r zR2kDEwOe}K&bw`KZ^KbpS1r({tCwlY*ud#~B%b;8?s-q=rZYwM>3j!{FL0JQp{P?f zO)=P0fLVc8lrB1`PAyu~L2c2yOfH_SR2pF>wQ&5y%~aCeZEEchQy{j>1Q>yvf3~AO zE*!cwmOZmdKB9Mcn0K&W`xJ1F&oQH=RJzu)5V+0PkvLtrzuldqIkTW-CR>KBUG}#K znZ9R7^rR^1W;aJ|pntt**DQb|DkU!C#EQ~;x{3+dRm)t0Oxpb6J2OL>aAwM|7A7F6LOTdTdpT(0Ui5L&)DUj%B;r>he-_w|xT_itQua{ZB`VP;V)gd7}U4VnA09R0{p52RCDT#>DK zor|$zphMjS*GJ@9736%UeiM#Y0Tpq@YtTs<3bu)mlQ@{^;|E<&qw(S|| zK~hIQ@ayj()Wg-=+T$Ve`&ZU1p{qw;7uDws@cC-w((Iu#6^ppKV%PYBG+#HJ_8T$E zYIIrnnQ8vtI(|=7dh~#u#W~$2i_JhF)YT4<0Pe^CdubdxG2CRfB{IU?H|9j5$`KtZ z;GPG2bryal)M670w7ds$OfwNTYuac3PuI5Th46T;iUGSb>Qr^&Uapk-RUuWkbKdWv z>)qHbTDrWjzuP#k-~sq>6nRMdyL3$(pxYNpa1?LQ@~;KHIPMJ&f#77%ZG$)HnD5bFRpTh5S6`XlJV19)tsD3whw0V_ATcza1x7FU&0mJ zZPy+NRCqyU-4NqJ*`YeX@fuM-S}=(!ZkF9~tV22E6D>S7e1|6~rU!aS6&dWa%QY@~ zaZ7BvUnezvwY@)9%Mo9crX*T27k%=7w0RJ}qs1Sh!%Ne_YfmQTK(VN#I+=C?dX+Y5 zs1U#2O&HMz{1;{OMC2Ewv_qXk1Sn|dt*s@yDu&Yxid`f6a+S9dKjc=c5>E*{$wg#| z?&0+4`*w_M7A3akW2$3MmJXN)djR%&bDV^Xc&X7L1AugPDETw`icwNxl?Z2>KAsY> zsQ;BJToUUL7fWYB`K_fG;_naO<54yiVXciC)U`r9v>P**+cdSCWK7;oFaBoX!dtC; z>vR3QdM%HGLwL~L4t)4gog{FK{|%0O`?Jf;-~L)YQ+^U?`A6WP5p-P|+K9CC!;ydX3& zNaklsXgcd{Yx(V2fR5&tbyTr2Xd?~&fitvyp}YPvg8W&+P+V*#n^k3VQp<}7o!NvW zp5Napv|kn@t*mt<)=E>ofBv_~wuiipVP4)FC%xg`Kw%rvxI>?D_9`1pnQDJwq`1e# zasrqBsIHBesJ++c$UGeg6Mvow1M}pBQ9aa=xXHsA83@W`5Jw&3DrM1?2&#dFBjBT0 zyrEG*B|Ua-JCY5hpq)sR=k6`w+SqVYu!Tp_EZ=4Ga2Pdh{j5Lj=HgPLykX>YY}$YI5Wdh1#?#xk(1ZH5;`8v_mBhPI@QU z1+FepI7i((e0+_UjpL6tc9ypj#vkL4>xdjQzEny-5M0H{C~HeQ&TMmAdy%a& z96q0TjXEEc;JC&`_aj~#NV8I%LAOq&EbMiN%`@nC&+qsg7{=v&`83-yE)FyEvl6ZN z;j{3gBI^bm|C^pnQha(h?H^t-*`AXa-13~qqDhRRmAfRERk{l|yz11z*; z<7W>Apc543i>Yn#!m>Kaj83?B8P*Zyxd+dY+C69NOvfuaY&*N#h2}lmajhim>O2kC>^yOTVNPj+ovJ1v(RhZ9C zWk{{FB_!Y~Lnn8lW9bN`z0ngyB0sA^G^vPxZ!)zTf7IgGbftU#o~&>aoflW9`V($0 z!KY%%qMCUmk$X}<$FNuLgd(EYWw@1S| zA2P^to<1iXrC#6AHqz7tQx4nq5e$d_&SWCA-Pu2VkXQm64hs}h* zB5c5GYnGFf%;01zOg=%rj@o+xpT|iU2ZhYnQlZ5fb%J_i5pcUb{zB;csg}5j7wMH` zX3GzPi9Qz}nc9{rEPslb%GMD_-OrS>I15bnTCT;ZWEB1}+1<+h-oUCzG0u(SuiY!Y zL$T0+Ka))s{M;)|ZeUH|5Q_`^^;5wh#L0NmcTEVsoMa*%La%^d6uT|J?96DapG2&rgSWBgynm)ZG1@oQy^I}Gqa zJz>y)4We`Fjb43+T^TCkZ{VK*Lz&4x-U#!LhoOrU$4=yV)_v_@aldX-mZI1FwSSm? zT{+ad4x$fCymN9T<+%(S$LL=JMJR>{38BSDGkGwy14>_b+72JB;;nENB-o#EF#OoZ z7$^in6EaI>Z5v}HQVoGLKWhg_LI9{Huuz4V7gW0yRKm*$NT7P>DvH_a{S1EpDx1Ss zTIiD{Y3zlE`~%9dvW#@Cxs-UmImUuPWA8v(Z6Vc+;d42l&YE?39#D_eW;{8y&;K@r zgpls(MuT7x682dqRQ0~*b*c*OmozJPG}{hmBX*8mUgEBWuksqI%-cN} zON?XcrD4p{8zy1WS{E-vS)Y|DTw>*s1)1LhApbA&1=5&}dV~l9Vo3)A0{0&XW@qGR zWpD1l;ON!B#Cd}Vw$Ri-A}$F1KQ)n4g8ZL0D#JnjPkS{qFp>UWY%H0=p7K8{S zone them back + # instead of giving up. + step = path.next_step(mi, (pos.x, pos.y), + mi.expand(tb) & mi.passable()) + if step is None: + if (manhattan(pos, tpos) <= 3 and ct.get_action_cooldown() == 0 + and ct.get_global_resources() + >= ct.get_launcher_cost() + 60): + for d in CARDINALS: + p = pos.add(d) + if (self._inBounds(p) and ct.is_in_vision(p) + and manhattan(p, tpos) <= 2 + and ct.can_build_launcher(p)): + ct.build_launcher(p) + return + self._moveToward(ct, mi.expand(mi.expand(tb)) & mi.passable()) + return self._moveToward(ct, mi.expand(tb) & mi.passable()) # ------------------------------------------------------------------ @@ -868,6 +886,23 @@ def _run_route(self, ct, mi, mask): facing = DELTA_TO_DIR[(fbest[1], fbest[2])] break if link is None: + # Pantheon: an unroutable dead-end near enemies gets CAPPED with + # a barrier on its output so they can't exploit/extend it + f = mi.own_conv_facing.get((hx, hy)) + if f is not None and mi.enemy_bots: + near = mi.expand(mi.expand(mi.expand(mi.expand( + mi.bit(hx, hy))))) + if near & mi.enemy_bots and ct.get_action_cooldown() == 0: + ox, oy = hx + f[0], hy + f[1] + if 0 <= ox < mi.w and 0 <= oy < mi.h: + cap = Position(ox, oy) + pos0 = ct.get_position() + if (pos0.distance_squared(cap) <= 2 + and ct.is_in_vision(cap) + and ct.can_build_barrier(cap)): + ct.build_barrier(cap) + mi.own_barriers |= mi.bit(ox, oy) + mi.note_tile(ox, oy, mapinfo_mod.T_BLOCK) self.ttl[('route', hx, hy)] = rnd + 20 return pos = ct.get_position() From 0d252335928b1b3431da9c1a616d85bfd19c86ef Mon Sep 17 00:00:00 2001 From: Subodh Date: Thu, 23 Jul 2026 09:59:12 +0530 Subject: [PATCH 29/29] gate 9: core ring promoted Co-Authored-By: Claude Opus 4.8 --- bot.zip | Bin 28528 -> 28971 bytes main.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/bot.zip b/bot.zip index 33a337d1532cab50c3290b6b7b11a63e896c2ed6..9d1853fe0ddb537601f3c7f6d5fd97f54c688664 100644 GIT binary patch delta 14747 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 kN&Y9wyWw;T-8T>j%K-#J{{KX2)<}YKB7sJ<|DV