Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import math
- import random
- import sys
- from dataclasses import dataclass, field
- from enum import Enum
- from tkinter import Tk, Canvas, YES, BOTH
- from tkinter.ttk import Label
- from typing import Any, List, Tuple
- from numpy import array, cos, sin, dot
- from scipy.linalg import norm
- def idebug(*args):
- return
- print(*args, file=sys.stderr, flush=True)
- def debug(*args):
- # return
- print(*args, file=sys.stderr, flush=True)
- class EntityType(Enum):
- SPIDER = 0
- PLAYER = 1
- OPPONENT = 2
- PLAYER_BASE = 3
- ENEMY_BASE = 4
- @dataclass
- class Action:
- type: str
- target: array = None
- entity_id: int = None
- message: str = None
- def __repr__(self):
- if self.type == 'MOVE':
- x, y = self.target
- msg: str = f' {self.message}' if self.message else ''
- return f'MOVE {round(x)} {round(y)}{msg}'
- elif self.type == 'SPELL WIND':
- x, y = self.target
- return f'{self.type} {round(x)} {round(y)}'
- elif self.type == 'SPELL SHIELD':
- return f'{self.type} {self.entity_id}'
- elif self.type == 'SPELL CONTROL':
- x, y = self.target
- return f'{self.type} {self.entity_id} {round(x)} {round(y)}'
- @dataclass
- class Entity:
- id: int
- x: int
- y: int
- vx: int
- vy: int
- radius: int
- speed: int
- type: EntityType
- health: int
- visible: bool = False
- action: Action = None
- threat_for: EntityType = field(init=False)
- velocity: array = field(init=False) # calculated value after instance creation
- label: Any = field(init=False)
- circle: Any = field(init=False)
- def __post_init__(self):
- self.location = array([self.x, self.y], dtype=float)
- self.velocity = array([self.vx, self.vy], dtype=float)
- self.threat_for = self.type
- self.label = None
- self.circle = None
- def __copy__(self):
- return Entity(id=self.id, x=self.x, y=self.y, vx=self.vx, vy=self.vy, radius=self.radius, speed=self.speed, type=self.type, health=self.health, visible=self.visible, action=self.action)
- def threat_level(self, base) -> int:
- score = 0
- if self.threat_for == base.type:
- if self.dist(base) <= 5000:
- score = 1000
- else:
- score = 500
- score += 500 / (1 + self.dist(base))
- return score
- def dist(self, other):
- return norm(self.location - other.location) if isinstance(other, Entity) else norm(self.location - other)
- def delete_from(self, canvas: Canvas):
- canvas.delete(self.circle)
- self.label.destroy()
- def move(self, canvas: Canvas = None, target: array = None):
- new_location = self.get_new_location(target=target)
- if canvas is not None:
- dx, dy = reduce(new_location - self.location)
- canvas.move(self.circle, dx, dy)
- self.location = new_location
- self.update_label()
- def create_circle(self, canvas: Canvas, color: str):
- # changed this to return the ID
- x, y = self.location
- oval = x - self.radius, y - self.radius, x + self.radius, y + self.radius
- x0, y0, x1, y1 = reduce(oval)
- self.circle = canvas.create_oval(x0, y0, x1, y1, width=1, outline=color)
- def update_label(self):
- x, y = reduce(self.location)
- self.label.place(x=x, y=y)
- if self.type == EntityType.SPIDER:
- self.label['text'] = f'P{self.id}' if self.threat_for == EntityType.PLAYER_BASE else f'E{self.id}' if self.threat_for == EntityType.ENEMY_BASE else self.health
- else:
- if self.visible:
- self.label['text'] = f"{self.health}" if self.type in [EntityType.SPIDER, EntityType.PLAYER_BASE, EntityType.ENEMY_BASE] else f"{self.id}"
- else:
- self.label['text'] = 'Fog'
- def get_new_location(self, target=None):
- if target is not None:
- if isinstance(target, Entity):
- u: array = target.location - self.location
- else:
- u: array = target - self.location
- v: array = u / norm(u) if norm(u) > 0 else 0
- speed: float = min(self.speed, self.dist(target))
- new_location = self.location + speed * v
- else:
- new_location = self.location + self.velocity
- return new_location
- def inside_map(location: array) -> bool:
- x, y = location
- return 0 <= x <= MAP_WIDTH and 0 <= y <= MAP_HEIGHT
- def spawn_spiders(entity_counter: int, spiders_max_level: int) -> Tuple[Entity, Entity]:
- hp_list = [(i + 1) * 10 for i in range(spiders_max_level)]
- hp_spider: int = random.choice(hp_list)
- top_left, top_right = 5000, MAP_WIDTH
- random_x: int = random.randint(top_left, top_right)
- dir: int = random.randint(1, 5)
- theta = dir * math.pi / 6
- # theta = radians(angle)
- c, s = cos(theta), sin(theta)
- R = array(((c, -s), (s, c)))
- u_x: array = array([1, 0])
- vx, vy = 400 * dot(R, u_x)
- spider: Entity = Entity(id=entity_counter, health=hp_spider, type=EntityType.SPIDER, x=random_x, y=0, vx=vx, vy=vy, radius=400, speed=400)
- x, y = array([MAP_WIDTH, MAP_HEIGHT]) - spider.location
- vx, vy = -spider.velocity
- symmetric_spider: Entity = Entity(id=entity_counter + 1, health=hp_spider, type=EntityType.SPIDER, x=x, y=y, vx=vx, vy=vy, radius=400, speed=400)
- return spider, symmetric_spider
- @dataclass
- class Game:
- max_turns: int
- respawn_value: int
- spiders_max_level: int
- root: Tk = Tk()
- canvas: Canvas = None
- console: Label = None
- my_base: array = None
- my_base_detect_radius: array = None
- enemy_base: array = None
- enemy_base_detect_radius: array = None
- turns = 1
- entity_counter = 0
- spawn_cooldown: int = 0
- my_mana: int = 0
- enemy_mana: int = 0
- my_wild_mana: int = 0
- enemy_wild_mana: int = 0
- heroes: List[Entity] = field(default_factory=list)
- enemies: List[Entity] = field(default_factory=list)
- spiders: List[Entity] = field(default_factory=list)
- def get_default_positions(self, player: EntityType) -> List[array]:
- # Some constants used to affect game's zones for my heroes
- base: Entity = self.my_base if player == EntityType.PLAYER else self.enemy_base
- base_is_top_left = (base.x, base.y) == (0, 0)
- map_size: array = array([MAP_WIDTH, MAP_HEIGHT])
- pos_0_topleft, pos_1_topleft, pos_2_topleft = array([15199, 6225]), array([4706, 2033]), array([2033, 4706])
- pos_0_bottom_right, pos_1_bottom_right, pos_2_bottom_right = map_size - pos_0_topleft, map_size - pos_1_topleft, map_size - pos_2_topleft
- return [pos_0_topleft, pos_1_topleft, pos_2_topleft] if base_is_top_left else [pos_0_bottom_right, pos_1_bottom_right, pos_2_bottom_right]
- def threat_for(self, spider: Entity) -> EntityType:
- base_candidates: List[Entity] = []
- for base in [self.my_base, self.enemy_base]:
- x0, y0 = base.location
- x1, y1 = spider.location
- a, b = spider.velocity
- r = 5000
- # Equation droite
- # x = x1 + a * t
- # y = y1 + b * t
- # Equation cercle
- # (x - x0) ** 2 + (y - y0) ** 2 = r * r
- A = a * a + b * b
- B = 2 * (a * x1 - a * x0 + b * y1 - b * y0)
- C = (x1 - x0) ** 2 + (y1 - y0) ** 2 - r * r
- # Intersection cercle/droite:
- # A * t^2 + B * t + C = 0
- det: float = B * B - 4 * A * C
- if det >= 0:
- t0 = (-B + math.sqrt(det)) / (2 * A)
- X0, Y0 = x1 + a * t0, y1 + b * t0
- t1 = (-B - math.sqrt(det)) / (2 * A)
- X1, Y1 = x1 + a * t1, y1 + b * t1
- if inside_map((X0, Y0)) or inside_map((X1, Y1)):
- base_candidates.append(base)
- if len(base_candidates) == 2:
- new_spider_location = spider.location + spider.velocity
- for base in base_candidates:
- if norm(new_spider_location - base.location) < spider.dist(base):
- return base.type
- else:
- return base_candidates[0].type if base_candidates else EntityType.SPIDER
- def init(self):
- """
- Initialise les entités de départ dans le jeu
- :return:
- """
- self.my_base = Entity(id=None, health=3, type=EntityType.PLAYER_BASE, x=0, y=0, vx=0, vy=0, radius=300, speed=0, visible=True)
- self.my_base_detect_radius = Entity(id=None, health=math.inf, type=EntityType.PLAYER_BASE, x=0, y=0, vx=0, vy=0, radius=5000, speed=0)
- self.enemy_base = Entity(id=None, health=3, type=EntityType.ENEMY_BASE, x=MAP_WIDTH, y=MAP_HEIGHT, vx=0, vy=0, radius=300, speed=0, visible=True)
- self.enemy_base_detect_radius = Entity(id=None, health=math.inf, type=EntityType.ENEMY_BASE, x=MAP_WIDTH, y=MAP_HEIGHT, vx=0, vy=0, radius=5000, speed=0)
- hero_1 = Entity(id=0, health=math.inf, type=EntityType.PLAYER, x=1414, y=849, vx=0, vy=0, radius=800, speed=800, visible=True)
- hero_2 = Entity(id=1, health=math.inf, type=EntityType.PLAYER, x=1131, y=1131, vx=0, vy=0, radius=800, speed=800, visible=True)
- hero_3 = Entity(id=2, health=math.inf, type=EntityType.PLAYER, x=849, y=1414, vx=0, vy=0, radius=800, speed=800, visible=True)
- self.heroes.append(hero_1)
- self.heroes.append(hero_2)
- self.heroes.append(hero_3)
- hero_1 = Entity(id=3, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 1414, y=MAP_HEIGHT - 849, vx=0, vy=0, radius=800, speed=800)
- hero_2 = Entity(id=4, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 1131, y=MAP_HEIGHT - 1131, vx=0, vy=0, radius=800, speed=800)
- hero_3 = Entity(id=5, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 849, y=MAP_HEIGHT - 1414, vx=0, vy=0, radius=800, speed=800)
- self.heroes.append(hero_1)
- self.heroes.append(hero_2)
- self.heroes.append(hero_3)
- self.entity_counter = len(self.heroes)
- spider, symmetric_spider = spawn_spiders(entity_counter=self.entity_counter, spiders_max_level=self.spiders_max_level)
- spider.threat_for = self.threat_for(spider)
- symmetric_spider.threat_for = self.threat_for(symmetric_spider)
- self.entity_counter += 2
- self.spiders.append(spider)
- self.spiders.append(symmetric_spider)
- def init_tk(self):
- """
- Initialisation de la partie graphique de l'application et entités de jeu
- :return:
- """
- self.canvas = Canvas(width=MAP_WIDTH * ZOOM, height=MAP_HEIGHT * ZOOM, bg='white')
- self.canvas.pack(expand=YES, fill=BOTH)
- self.console = Label(self.root, text="Hello World!")
- self.console.pack(pady=20)
- # Entities
- self.my_base.create_circle(canvas=self.canvas, color='green')
- self.my_base.label = Label(self.root)
- self.my_base.update_label()
- self.my_base_detect_radius.create_circle(canvas=self.canvas, color='orange')
- self.enemy_base.create_circle(canvas=self.canvas, color='green')
- self.enemy_base.label = Label(self.root)
- self.enemy_base.update_label()
- self.enemy_base_detect_radius.create_circle(canvas=self.canvas, color='orange')
- for e in self.heroes + self.spiders:
- e.label = Label(self.root)
- e.update_label()
- # e.label.pack(pady=20)
- color = 'red' if e.type == EntityType.SPIDER else 'blue' if e.type == EntityType.PLAYER else 'green'
- e.create_circle(canvas=self.canvas, color=color)
- self.canvas.bind_all("<space>", self.callback)
- self.root.mainloop()
- # All of this would do better as a subclass of Canvas with specialize methods
- def update_fog(self, player: EntityType):
- player_base: Entity = self.enemy_base if player == EntityType.OPPONENT else self.my_base
- player_heroes: List[Entity] = [h for h in self.heroes if h.type == player]
- enemy_heroes: List[Entity] = [h for h in self.heroes if h.type != player] # only handle visible enemy heroes
- for spider in self.spiders:
- if spider.dist(player_base) <= 6000 or any([h for h in player_heroes if h.type == player and spider.dist(h) <= 800]):
- spider.visible = True
- else:
- spider.visible = False
- for enemy in enemy_heroes:
- if enemy.dist(player_base) <= 6000 or any([h for h in player_heroes if h.type == player and enemy.dist(h) <= 800]):
- enemy.visible = True
- else:
- enemy.visible = False
- def update_entities_after_wind(self, hero: Entity, target: array):
- u: array = target - hero.location
- v: array = u / norm(u)
- for entity in self.heroes + self.spiders:
- if entity.dist(hero) <= 1280 and entity != hero:
- new_location = entity.location + 2200 * v
- entity.move(canvas=self.canvas, target=new_location)
- """ Referee SC2022:
- https://github1s.com/CodinGame/SpringChallenge2022/blob/main/src/main/java/com/codingame/game/Referee.java#L1009
- private void performGameUpdate(int turn) {
- doControl();
- doShield();
- moveHeroes();
- Map<Player, Integer[]> manaGain = performCombat();
- doPush();
- moveMobs();
- shieldDecay();
- spawnNewMobs(turn);
- manaGain.forEach((player, amount) -> {
- player.gainMana(amount);
- });
- }
- """
- def game_update(self):
- """
- Mise à jour des entités à chaque tour à partir des actions des héros générées par la fonction callback (IA du jeu)
- :return:
- """
- # update game counters
- self.turns += 1
- self.spawn_cooldown -= 1 if self.spawn_cooldown else 0
- # perform heroes actions and display of heroes
- for hero in self.heroes:
- if hero.action.type == 'MOVE':
- hero.move(canvas=self.canvas, target=hero.action.target)
- elif hero.action.type == 'WIND':
- self.update_entities_after_wind(hero=hero, target=hero.action.target)
- hero.label['text'] = 'WIND'
- if hero.type == EntityType.PLAYER:
- self.my_mana -= 10
- else:
- self.enemy_mana -= 10
- # Update mana, wild mana and spiders health
- for hero in self.heroes:
- for spider in self.spiders:
- if hero.dist(spider) <= hero.radius:
- if hero.type == EntityType.OPPONENT:
- self.enemy_mana += 2
- else:
- self.my_mana += 2
- spider.health -= 2
- if spider.health <= 0 and spider.dist(hero) <= 800:
- if hero.type == EntityType.PLAYER and hero.dist(self.my_base) > 5000:
- self.my_wild_mana += 1
- elif hero.type == EntityType.OPPONENT and hero.dist(self.enemy_base) > 5000:
- self.enemy_wild_mana += 1
- # Update display of spiders
- for spider in self.spiders:
- if spider.health > 0:
- if spider.dist(self.my_base) <= 300:
- self.my_base.health -= 1
- self.my_base.update_label()
- self.canvas.delete(spider.circle)
- spider.label.destroy()
- self.spiders.remove(spider)
- elif spider.dist(self.enemy_base) <= 300:
- self.enemy_base.health -= 1
- self.enemy_base.update_label()
- spider.delete_from(canvas=self.canvas)
- self.spiders.remove(spider)
- elif spider.dist(self.my_base) <= 5000:
- spider.move(canvas=self.canvas, target=self.my_base)
- elif spider.dist(self.enemy_base) <= 5000:
- spider.move(canvas=self.canvas, target=self.enemy_base)
- elif not inside_map(spider.location):
- spider.delete_from(canvas=self.canvas)
- self.spiders.remove(spider)
- else:
- spider.move(canvas=self.canvas)
- else:
- spider.delete_from(canvas=self.canvas)
- self.spiders.remove(spider)
- if self.my_base.health <= 0:
- self.console['text'] = f'OPPONENT WINS after {self.turns} turns!'
- self.canvas.unbind_all("<space>")
- elif self.enemy_base.health <= 0:
- self.console['text'] = f'PLAYER WINS after {self.turns} turns!'
- self.canvas.unbind_all("<space>")
- elif self.turns == self.max_turns:
- if self.enemy_base.health > self.my_base.health:
- self.console['text'] = f'OPPONENT WINS!'
- elif self.enemy_base.health < self.my_base.health:
- self.console['text'] = f'PLAYER WINS!'
- else:
- if self.my_wild_mana == self.enemy_wild_mana:
- self.console['text'] = f'THIS IS A DRAW!'
- else:
- if self.my_wild_mana < self.enemy_wild_mana:
- self.console['text'] = f'OPPONENT WINS!'
- elif self.my_wild_mana > self.enemy_wild_mana:
- self.console['text'] = f'PLAYER WINS!'
- self.console['text'] += f'\nWILD MANA | Player = {self.my_wild_mana} - Opponent = {self.enemy_wild_mana}'
- self.canvas.unbind_all("<space>")
- else:
- if not self.spawn_cooldown:
- self.spawn_cooldown = self.respawn_value
- spider, symmetric_spider = spawn_spiders(entity_counter=self.entity_counter, spiders_max_level=self.spiders_max_level)
- spider.threat_for = self.threat_for(spider)
- symmetric_spider.threat_for = self.threat_for(symmetric_spider)
- self.entity_counter += 2
- self.spiders.append(spider)
- self.spiders.append(symmetric_spider)
- for s in [spider, symmetric_spider]:
- s.label = Label(self.root)
- s.update_label()
- s.create_circle(canvas=self.canvas, color='red')
- self.console['text'] = f'BASE HEALTH | Player = {self.my_base.health} - Opponent = {self.my_base.health}'
- self.console['text'] += f'\nMANA | Player = {self.my_mana} - Opponent = {self.enemy_mana}'
- for hero in self.heroes:
- hero.action = None
- def wood_2_ia(self, player: EntityType):
- base: Entity = self.enemy_base if player == EntityType.OPPONENT else self.my_base
- player_heroes: List[Entity] = [h for h in self.heroes if h.type == player]
- visible_spiders: List[Entity] = [s for s in self.spiders if s.visible]
- if visible_spiders:
- spiders = sorted(self.spiders, key=lambda s: s.dist(base))
- while player_heroes and spiders:
- spider: Entity = spiders.pop(0)
- hero: Entity = min(player_heroes, key=lambda h: h.dist(spider))
- player_heroes.remove(hero)
- hero.action = Action(type='MOVE', target=spider.location)
- for hero in player_heroes:
- if not hero.action:
- hero.action = Action(type='WAIT')
- for hero in player_heroes:
- for h in self.heroes:
- if h.id == hero.id:
- # debug('prout wood 2')
- h.action = hero.action
- def wood_1_ia(self, player: EntityType):
- # ia_bronze_defense
- # spring-challenge-2022-Silver_570_defense_only
- my_health, enemy_health = (self.my_base.health, self.enemy_base.health) if player == EntityType.PLAYER else (self.enemy_base.health, self.my_base.health)
- my_mana, enemy_mana = (self.my_mana, self.enemy_mana) if player == EntityType.PLAYER else (self.enemy_mana, self.my_mana)
- my_base, enemy_base = (self.my_base, self.enemy_base) if player == EntityType.PLAYER else (self.enemy_base, self.my_base)
- player_heroes: List[Entity] = [h for h in self.heroes if h.type == player]
- spiders: List[Entity] = [s for s in self.spiders if s.visible]
- """ DEFENSE ONLY """
- defenders: List[Entity] = player_heroes
- if spiders:
- ranked_spiders: List[Entity] = sorted(spiders, key=lambda s: s.threat_level(base=my_base), reverse=True)
- while ranked_spiders and defenders:
- wind_heroes: List[Entity] = [(h, len([s for s in ranked_spiders if s.dist(my_base) <= 5000 and h.dist(s) <= 1280])) for h in defenders]
- if my_mana >= 20 and wind_heroes:
- hero = max(wind_heroes, key=lambda x: x[1])[0]
- hero.action = Action(type='SPELL WIND', target=enemy_base.location)
- my_mana -= 10
- else:
- spider: Entity = ranked_spiders.pop(0)
- hero: Entity = min(defenders, key=lambda h: h.dist(spider))
- hero.action = Action(type='MOVE', target=spider.location + spider.velocity)
- defenders.remove(hero)
- ranked_spiders: List[Entity] = sorted([s for s in ranked_spiders if s.health > 0], key=lambda s: s.threat_level(base=my_base), reverse=True)
- else:
- default_positions: List[array] = self.get_default_positions(player=player)
- for hero in defenders:
- hero.action = Action(type='MOVE', target=default_positions[player_heroes.index(hero)])
- for hero in player_heroes:
- if not hero.action:
- hero.action = Action(type='WAIT')
- for hero in player_heroes:
- for h in self.heroes:
- if h.id == hero.id:
- h.action = hero.action
- def callback(self, event):
- """
- IA for Dummies (fonction tour par tour de Codingame)
- :param event:
- :return:
- """
- self.update_fog(player=EntityType.OPPONENT)
- self.wood_2_ia(player=EntityType.OPPONENT)
- # self.wood_1_ia(player=EntityType.OPPONENT) # BUGGY :-(
- self.update_fog(player=EntityType.PLAYER)
- self.wood_2_ia(player=EntityType.PLAYER)
- self.game_update()
- def read_int_value(message: str, max_value: int) -> int:
- while True:
- try:
- print(f'{message} [1-{max_value}] ')
- value = int(input())
- if not value or not 1 <= int(value) <= max_value:
- raise ValueError
- except ValueError:
- continue
- except UnboundLocalError:
- continue
- finally:
- return int(value)
- if __name__ == "__main__":
- MAP_WIDTH, MAP_HEIGHT = 17630, 9000
- mac_display_resolution = 1920, 1080
- ZOOM = 0.1
- SPAWN_COOLDOWN = 2
- reduce = lambda x_list: map(lambda x: x * ZOOM, x_list)
- max_turns = read_int_value(message="Enter max number of rounds:", max_value=300)
- respawn_value = read_int_value(message="Frequency of respawn spiders:", max_value=10)
- spiders_max_level = read_int_value(message="Maximum level of spiders:", max_value=10)
- game: Game = Game(max_turns=int(max_turns), respawn_value=respawn_value, spiders_max_level=spiders_max_level)
- game.init()
- game.init_tk()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement