/
GraphTreeHeap
/
NeuroFighter
Обзор
Документация
Войти
/
GraphTreeHeap
/
NeuroFighter
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/robotics.py
543 строки
22 KB
FedMam
moving to the new project version
11 окт 2025, 04:16
11 окт 2025, 04:16
00409b0
Код
Авторство
О чём код?
import abc import random from mechanics import * import box class Bot(abc.ABC): def __init__(self, seed: int | None=None): self.rand = random.Random(seed) @abc.abstractmethod def decide_inner(self, env: Environment, me: Character, enemy: Character, my_projs: list[Projectile], enemy_projs: list[Projectile]): raise NotImplementedError() def decide_outer(self, env: Environment, i_am_player: bool): if i_am_player: me = env.player my_projs = env.player_projectiles enemy = env.opponent enemy_projs = env.opponent_projectiles else: me = env.opponent my_projs = env.opponent_projectiles enemy = env.player enemy_projs = env.player_projectiles self.decide_inner(env, me, enemy, my_projs, enemy_projs) # Bot that does not do anything at all (serves as a placeholder for an opponent in some cases) class StallBot(Bot): def decide_inner(self, env, me, enemy, my_projs, enemy_projs): pass # Bot that does some actions very rarely class Dumbot(Bot): def __init__(self, seed=None): super().__init__(seed) self.button_press_prob = 1/20 def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if self.rand.random() < self.button_press_prob: me.buttons_held[0] = True me.buttons_held[1] = False elif self.rand.random() < self.button_press_prob: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[0] = me.buttons_held[1] = False me.buttons_held[2] = self.rand.random() < self.button_press_prob me.buttons_held[3] = self.rand.random() < self.button_press_prob # Bot that moves randomly and unpredictably class Randominator(Bot): def __init__(self, seed = None, interval_change_mean=FPS * 2, interval_change_std=FPS * 1): super().__init__(seed) self.interval_change_mean = interval_change_mean self.interval_change_std = interval_change_std self.next_run_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) self.next_jump_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) self.next_shoot_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if me.pos[0] == 0: me.buttons_held[0] = False me.buttons_held[1] = True elif me.pos[0] == ROOM_WIDTH: me.buttons_held[0] = True me.buttons_held[1] = False if self.next_run_change == 0: self.next_run_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) me.buttons_held[0], me.buttons_held[1] = { 0: (False, False), 1: (True, False), 2: (False, True) }[self.rand.randint(0, 2)] if self.next_jump_change == 0: self.next_jump_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) me.buttons_held[2] = self.rand.randint(0, 1) == 1 if self.next_shoot_change == 0: self.next_shoot_change = max(0, round(self.rand.gauss(self.interval_change_mean, self.interval_change_std))) me.buttons_held[3] = self.rand.randint(0, 1) == 1 self.next_run_change -= 1 self.next_jump_change -= 1 self.next_shoot_change -= 1 # Bot that tries to keep away from its opponent class Avoider(Bot): def __init__(self, seed: int | None = None): super().__init__(seed) self.safe_height = CHR_HITBOX_SIZE * 2 self.boundary = CHR_HITBOX_SIZE * 4 self.safe_distance = ROOM_WIDTH // 2 self.safe_distance_2 = self.safe_distance + CHR_HITBOX_SIZE * 2 self.safe_jump_distance = CHR_HITBOX_SIZE * 5 self.jump_break_prob = 1/20 def decide_inner(self, env, me, enemy, my_projs, enemy_projs): # movement if me.pos[0] == 0: me.buttons_held[0] = False me.buttons_held[1] = True elif me.pos[0] == ROOM_WIDTH: me.buttons_held[0] = True me.buttons_held[1] = False elif me.pos[0] < enemy.pos[0]: if enemy.pos[0] <= self.boundary and me.pos[1] - enemy.pos[1] >= self.safe_height: me.buttons_held[0] = False me.buttons_held[1] = True elif enemy.pos[0] - me.pos[0] > self.safe_distance: if not me.facing_right: if enemy.pos[0] - me.pos[0] > self.safe_distance_2: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[0] = True me.buttons_held[1] = False else: me.buttons_held[0] = me.buttons_held[1] = False else: me.buttons_held[0] = True me.buttons_held[1] = False elif me.pos[0] > enemy.pos[0]: if enemy.pos[0] >= ROOM_WIDTH - self.boundary and me.pos[1] - enemy.pos[1] >= self.safe_height: me.buttons_held[0] = True me.buttons_held[1] = False elif me.pos[0] - enemy.pos[0] > self.safe_distance: if me.facing_right: if me.pos[0] - enemy.pos[0] > self.safe_distance_2: me.buttons_held[0] = True me.buttons_held[1] = False else: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[0] = me.buttons_held[1] = False else: me.buttons_held[0] = False me.buttons_held[1] = True else: if me.pos[0] <= self.boundary: me.buttons_held[0] = False me.buttons_held[1] = True elif me.pos[0] >= ROOM_WIDTH - self.boundary: me.buttons_held[0] = True me.buttons_held[1] = False else: me.buttons_held[0] = self.rand.randint(0, 1) == 0 me.buttons_held[1] = not me.buttons_held[0] # jumping me.buttons_held[2] = self.rand.random() > self.jump_break_prob # shooting if (me.pos[0] <= enemy.pos[0] and me.buttons_held[1]) or (me.pos[0] >= enemy.pos[0] and me.buttons_held[0]): me.buttons_held[3] = True elif not me.buttons_held[0] and not me.buttons_held[1] and ((me.pos[0] <= enemy.pos[0] and me.facing_right) or (me.pos[0] >= enemy.pos[0] and not me.facing_right)): me.buttons_held[3] = True else: me.buttons_held[3] = False # Bot that moves back and forth, jumps occasionally and shoots constantly class Rammer(Bot): def __init__(self, seed = None): super().__init__(seed) self.padding = 16 * 2 self.jump_prob = 1 / 16 def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if me.pos[0] <= self.padding: me.buttons_held[0] = False me.buttons_held[1] = True elif me.pos[0] >= ROOM_WIDTH - self.padding: me.buttons_held[0] = True me.buttons_held[1] = False elif not me.buttons_held[0] and not me.buttons_held[1]: me.buttons_held[0] = True me.buttons_held[2] = me.jumping ^ (self.rand.random() < self.jump_prob) me.buttons_held[3] = True # The same as Rammer, but always moves towards opponent, also see the jump condition class RammerMkII(Bot): def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if me.pos[0] < enemy.pos[0]: me.buttons_held[0] = False me.buttons_held[1] = True elif me.pos[0] > enemy.pos[0]: me.buttons_held[0] = True me.buttons_held[1] = False else: me.buttons_held[0] = me.buttons_held[1] = False me.buttons_held[2] = enemy.pos[1] > me.pos[1] me.buttons_held[3] = True # A bot that moves chaotically and jumps and shoots constantly class MadJumper(Bot): def __init__(self, seed = None): super().__init__(seed) def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if self.rand.random() < 2/3: me.buttons_held[0] = enemy.pos[0] < me.pos[0] me.buttons_held[1] = enemy.pos[0] >= me.pos[0] elif self.rand.random() < 1/2: me.buttons_held[0] = enemy.pos[0] >= me.pos[0] me.buttons_held[1] = enemy.pos[0] < me.pos[0] else: me.buttons_held[0] = me.buttons_held[1] = False me.buttons_held[2] = True me.buttons_held[3] = True # A bot that tries to keep in one place and dodge all bullets class Dodgetron(Bot): def __init__(self, seed = None): super().__init__(seed) self.target_x = ROOM_WIDTH // 2 self.enemy_reaction_time = FPS // 3 def decide_inner(self, env, me, enemy, my_projs, enemy_projs): # Facing if me.pos[0] > self.target_x + CHR_HITBOX_SIZE or (not me.facing_right and me.pos[0] > self.target_x): me.buttons_held[0] = True me.buttons_held[1] = False elif me.pos[0] < self.target_x - CHR_HITBOX_SIZE or (me.facing_right and me.pos[0] < self.target_x): me.buttons_held[0] = False me.buttons_held[1] = True elif me.facing_right and enemy.pos[0] < me.pos[0]: me.buttons_held[0] = True me.buttons_held[1] = False elif not me.facing_right and enemy.pos[0] > me.pos[0]: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[0] = me.buttons_held[1] = False # Jumping for proj in enemy_projs: if proj.pos[1] <= me.pos[1] + CHR_HITBOX_HALFSIZE and \ ((proj.h_speed > 0 and proj.pos[0] >= me.pos[0] - abs(proj.h_speed) * 48 // me.v_speed and proj.pos[0] <= me.pos[0] + CHR_HITBOX_SIZE) or \ (proj.h_speed < 0 and proj.pos[0] <= me.pos[0] + abs(proj.h_speed) * 48 // me.v_speed and proj.pos[0] >= me.pos[0] - CHR_HITBOX_SIZE)): me.buttons_held[2] = True break else: me.buttons_held[2] = False # Shooting if enemy.pos[1] <= enemy.v_speed * self.enemy_reaction_time and not enemy.jumping and \ abs(enemy.pos[0] - me.pos[0]) <= me.shoot_speed * self.enemy_reaction_time + CHR_HITBOX_SIZE: me.buttons_held[3] = True if me.facing_right and enemy.pos[0] < me.pos[0]: me.buttons_held[0] = True me.buttons_held[1] = False elif not me.facing_right and enemy.pos[0] > me.pos[0]: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[3] = False # The same as above, but tries to keep at a fixed distance to the opponent class DodgetronMkII(Dodgetron): def __init__(self, seed=None): super().__init__(seed) self.padding = 16 * 2 def decide_inner(self, env, me, enemy, my_projs, enemy_projs): if enemy.pos[0] <= self.padding: self.target_x = enemy.pos[0] + me.shoot_speed * self.enemy_reaction_time elif enemy.pos[0] >= ROOM_WIDTH - self.padding: self.target_x = enemy.pos[0] - me.shoot_speed * self.enemy_reaction_time else: self.target_x = enemy.pos[0] + me.shoot_speed * self.enemy_reaction_time * (1 if me.pos[0] > enemy.pos[0] else (-1 if me.pos[0] < enemy.pos[0] else (self.rand.randint(0, 1) * 2 - 1))) super().decide_inner(env, me, enemy, my_projs, enemy_projs) # A bot that tries to get to the opponent's position and then shoot class Stinger(Bot): def decide_inner(self, env, me, enemy, my_projs, enemy_projs): # Moving if me.shoot_cooldown_ctr > me.shoot_cooldown // 2: # Move away me.buttons_held[0] = me.pos[0] < enemy.pos[0] me.buttons_held[1] = me.pos[0] > enemy.pos[0] else: me.buttons_held[0] = me.pos[0] > enemy.pos[0] me.buttons_held[1] = me.pos[0] < enemy.pos[0] # Jumping me.buttons_held[2] = enemy.pos[1] > 0 # Shooting me.buttons_held[3] = rectangle_intersects((me.pos[0] - CHR_HITBOX_HALFSIZE, me.pos[1] - CHR_HITBOX_HALFSIZE, CHR_HITBOX_SIZE, CHR_HITBOX_SIZE), (enemy.pos[0] - CHR_HITBOX_HALFSIZE, enemy.pos[1] - CHR_HITBOX_HALFSIZE, CHR_HITBOX_SIZE, CHR_HITBOX_SIZE)) def line_intersects_rect(rect: tuple[float, float, float, float], p1: tuple[float, float], p2: tuple[float, float]) -> bool: """ rect: (x, y, width, height) where (x,y) is top-left or bottom-left — function treats it as axis-aligned p1, p2: endpoints of the line segment Returns True if the segment p1-p2 intersects (or touches) the rectangle. """ rx, ry, rw, rh = rect # Normalize rectangle to (minx, miny, maxx, maxy) minx = rx maxx = rx + rw miny = ry maxy = ry + rh if rw < 0: minx, maxx = rx + rw, rx if rh < 0: miny, maxy = ry + rh, ry def point_in_rect(px: float, py: float) -> bool: return minx <= px <= maxx and miny <= py <= maxy # If either endpoint is inside rectangle -> intersects if point_in_rect(*p1) or point_in_rect(*p2): return True # Helper: orientation and on-segment for integer/float-safe checks def orient(a: tuple[float, float], b: tuple[float, float], c: tuple[float, float]) -> float: return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]) def on_segment(a: tuple[float, float], b: tuple[float, float], c: tuple[float, float]) -> bool: # check if c lies on segment ab (assuming collinear) return (min(a[0], b[0]) <= c[0] <= max(a[0], b[0]) and min(a[1], b[1]) <= c[1] <= max(a[1], b[1])) def segments_intersect(a: tuple[float, float], b: tuple[float, float], c: tuple[float, float], d: tuple[float, float]) -> bool: # proper/general segment intersection test o1 = orient(a, b, c) o2 = orient(a, b, d) o3 = orient(c, d, a) o4 = orient(c, d, b) if o1 == 0 and on_segment(a, b, c): return True if o2 == 0 and on_segment(a, b, d): return True if o3 == 0 and on_segment(c, d, a): return True if o4 == 0 and on_segment(c, d, b): return True return (o1 > 0) != (o2 > 0) and (o3 > 0) != (o4 > 0) # Rectangle edges as segments (minx,miny) -> (maxx,miny) etc. top_left = (minx, miny) top_right = (maxx, miny) bottom_left = (minx, maxy) bottom_right = (maxx, maxy) edges = [ (top_left, top_right), # top (top_right, bottom_right), # right (bottom_right, bottom_left),# bottom (bottom_left, top_left) # left ] for a, b in edges: if segments_intersect(p1, p2, a, b): return True return False # A bot with random or configurable playstyle UNIVERSAL_BOT_MAX_INTERVAL_MEAN = FPS * 3 UNIVERSAL_BOT_MAX_INTERVAL_STD = FPS * 0.75 UNIVERSAL_BOT_ZERO_WARMTH_STAND_PROB = 0.5 UNIVERSAL_BOT_JUMP_HEIGHT_STD = 0.25 UNIVERSAL_BOT_GROUND_SNIPING_REACTION_TIME = FPS // 2 class UniversalBot(Bot): def __init__(self, aggression: float | None=None, defense: float | None=None, bravery: float | None=None, warmth: float | None=None, jumpiness: float | None=None, height: float | None=None, strategy_edge_chance: float=0.1, strategy_seed: int | None=None, action_seed: int | None=None): self.rand = random.Random(action_seed) strategy_rand = random.Random(strategy_seed) def playstyle_characteristic(): return max(0.0, min(1.0, strategy_rand.random() * (1 + 2 * strategy_edge_chance) - strategy_edge_chance)) self.aggression = aggression if aggression is not None else playstyle_characteristic() self.defense = defense if defense is not None else playstyle_characteristic() self.bravery = bravery if bravery is not None else playstyle_characteristic() self.warmth = warmth if warmth is not None else playstyle_characteristic() self.jumpiness = jumpiness if jumpiness is not None else playstyle_characteristic() self.height = height if height is not None else playstyle_characteristic() self.playstyle = { 'aggression': self.aggression, 'defense': self.defense, 'bravery': self.bravery, 'warmth': self.warmth, 'jumpiness': self.jumpiness, 'height': self.height } self.current_direction = 0 self.direction_change_counter = 0 self.jump_counter = self.rand_interval(self.jumpiness) self.current_jump_height = 0 def rand_interval(self, warmth: float): return max(1, round(self.rand.gauss(UNIVERSAL_BOT_MAX_INTERVAL_MEAN * (1 - warmth), UNIVERSAL_BOT_MAX_INTERVAL_STD * (1 - warmth)))) def decide_inner(self, env, me, enemy, my_projs, enemy_projs): # direction change if self.direction_change_counter <= 0: if self.rand.random() < UNIVERSAL_BOT_ZERO_WARMTH_STAND_PROB * (1 - self.warmth): self.current_direction = 0 elif self.rand.random() < self.bravery: self.current_direction = 1 if me.pos[0] < enemy.pos[0] else -1 else: self.current_direction = -1 if me.pos[0] < enemy.pos[0] else 1 if self.rand.random() < 1 - self.bravery: if me.pos[0] <= CHR_HITBOX_SIZE * 3//2 and enemy.pos[0] <= CHR_HITBOX_SIZE * 3: self.current_direction = 1 elif me.pos[0] >= ROOM_WIDTH - CHR_HITBOX_SIZE * 3//2 and enemy.pos[0] >= ROOM_WIDTH - CHR_HITBOX_SIZE * 3: self.current_direction = -1 self.direction_change_counter = self.rand_interval(self.warmth) self.direction_change_counter -= 1 if self.current_direction == 0: me.buttons_held[0] = me.buttons_held[1] = False elif self.current_direction == 1: me.buttons_held[0] = False me.buttons_held[1] = True else: me.buttons_held[0] = True me.buttons_held[1] = False if me.pos[0] == 0: me.buttons_held[0] = False me.buttons_held[1] = True self.current_direction = 1 if self.rand.random() < self.warmth else 0 elif me.pos[0] == ROOM_WIDTH: me.buttons_held[0] = True me.buttons_held[1] = False self.current_direction = -1 if self.rand.random() < self.warmth else 0 # jump if me.pos[1] == 0: self.current_jump_height = max(0, min(me.jump_height, self.rand.gauss(self.height * me.jump_height, UNIVERSAL_BOT_JUMP_HEIGHT_STD * me.jump_height))) # on ground if self.jump_counter <= 0: me.buttons_held[2] = True self.jump_counter = self.rand_interval(self.jumpiness) self.jump_counter -= 1 jump_on_projectile_reaction_time = math.ceil(CHR_HITBOX_SIZE / me.v_speed) + 3 proj_approaching = False for proj in enemy_projs: if proj.pos[1] <= me.pos[1] + CHR_HITBOX_HALFSIZE and \ ((proj.h_speed > 0 and proj.pos[0] >= me.pos[0] - abs(proj.h_speed) * jump_on_projectile_reaction_time and proj.pos[0] <= me.pos[0] + CHR_HITBOX_SIZE) or \ (proj.h_speed < 0 and proj.pos[0] <= me.pos[0] + abs(proj.h_speed) * jump_on_projectile_reaction_time and proj.pos[0] >= me.pos[0] - CHR_HITBOX_SIZE)): proj_approaching = True break if enemy.shoot_cooldown_ctr <= jump_on_projectile_reaction_time and abs(me.pos[0] - enemy.pos[0]) <= CHR_HITBOX_SIZE and abs(me.pos[1] - enemy.pos[1]) <= CHR_HITBOX_SIZE: proj_approaching = True if proj_approaching: self.current_jump_height = max(CHR_HITBOX_SIZE, me.v_speed * (CHR_HITBOX_SIZE / enemy.shoot_speed), self.current_jump_height) if self.rand.random() < self.defense: me.buttons_held[2] = True # TODO: do NOT jump if projectile above the head else: if me.jumping and me.pos[1] < self.current_jump_height: me.buttons_held[2] = True else: me.buttons_held[2] = False # shoot if me.shoot_cooldown_ctr == 0: if self.rand.random() < self.aggression: me.buttons_held[3] = True # sniping closing_velocity = None if enemy.pos[1] > 0 and not enemy.jumping: closing_velocity = enemy.v_speed elif enemy.jumping: closing_velocity = -enemy.v_speed if closing_velocity is not None: point2 = me.pos[1] + round(abs(me.pos[0] - enemy.pos[0]) / me.shoot_speed * closing_velocity) if abs(point2 - enemy.pos[1]) < CHR_HITBOX_HALFSIZE: me.buttons_held[3] = True if rectangle_contains((enemy.pos[0] - CHR_HITBOX_HALFSIZE, enemy.pos[1] - CHR_HITBOX_HALFSIZE, CHR_HITBOX_SIZE, CHR_HITBOX_SIZE), me.pos): me.buttons_held[3] = True # ground sniping if me.pos[1] == 0 and enemy.pos[1] == 0 and self.rand.random() < (1 - abs(me.pos[0] - enemy.pos[0]) / ROOM_WIDTH * (CHARACTER_MAX_SHOOT_SPEED / me.shoot_speed)): me.buttons_held[3] = True else: me.buttons_held[3] = False # turn if me.buttons_held[3] and self.rand.random() < (1 - self.aggression): me.buttons_held[0] = me.facing_right and me.pos[0] > enemy.pos[0] me.buttons_held[1] = not me.facing_right and me.pos[0] < enemy.pos[0] ALL_BOTS = [Dumbot, Randominator, Avoider, Rammer, RammerMkII, MadJumper, Dodgetron, DodgetronMkII, Stinger] EASY_BOTS = [Dumbot, Randominator, Rammer, MadJumper] MEDIUM_BOTS = [Avoider, Rammer, MadJumper, Stinger] HARD_BOTS = [RammerMkII, Dodgetron, DodgetronMkII, Stinger]