Advertisement
Guest User

SC2022 Fork's referee in Python muahahaaha

a guest
May 19th, 2022
54
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.85 KB | None | 0 0
  1. import math
  2. import random
  3. import sys
  4. from dataclasses import dataclass, field
  5. from enum import Enum
  6. from tkinter import Tk, Canvas, YES, BOTH
  7. from tkinter.ttk import Label
  8. from typing import Any, List, Tuple
  9.  
  10. from numpy import array, cos, sin, dot
  11. from scipy.linalg import norm
  12.  
  13.  
  14. def idebug(*args):
  15.     return
  16.     print(*args, file=sys.stderr, flush=True)
  17.  
  18.  
  19. def debug(*args):
  20.     # return
  21.     print(*args, file=sys.stderr, flush=True)
  22.  
  23.  
  24. # class EntityType(Enum):
  25. #     SPIDER = 0
  26. #     HERO = 1
  27. #     BASE = 2
  28. #
  29. class EntityType(Enum):
  30.     SPIDER = 0
  31.     PLAYER = 1
  32.     OPPONENT = 2
  33.     PLAYER_BASE = 3
  34.     ENEMY_BASE = 4
  35.  
  36.  
  37. @dataclass
  38. class Action:
  39.     type: str
  40.     target: array = None
  41.     entity_id: int = None
  42.     message: str = None
  43.  
  44.     def __repr__(self):
  45.         if self.type == 'MOVE':
  46.             x, y = self.target
  47.             msg: str = f' {self.message}' if self.message else ''
  48.             return f'MOVE {round(x)} {round(y)}{msg}'
  49.         elif self.type == 'SPELL WIND':
  50.             x, y = self.target
  51.             return f'{self.type} {round(x)} {round(y)}'
  52.         elif self.type == 'SPELL SHIELD':
  53.             return f'{self.type} {self.entity_id}'
  54.         elif self.type == 'SPELL CONTROL':
  55.             x, y = self.target
  56.             return f'{self.type} {self.entity_id} {round(x)} {round(y)}'
  57.  
  58.  
  59. @dataclass
  60. class Entity:
  61.     id: int
  62.     x: int
  63.     y: int
  64.     vx: int
  65.     vy: int
  66.     radius: int
  67.     speed: int
  68.     type: EntityType
  69.     health: int
  70.     action: Action = None
  71.     velocity: array = field(init=False)  # calculated value after instance creation
  72.     label: Any = field(init=False)
  73.     circle: Any = field(init=False)
  74.  
  75.     def __post_init__(self):
  76.         self.location = array([self.x, self.y], dtype=float)
  77.         self.velocity = array([self.vx, self.vy], dtype=float)
  78.  
  79.     def dist(self, other):
  80.         return norm(self.location - other.location) if isinstance(other, Entity) else norm(self.location - other)
  81.  
  82.     def move(self, canvas: Canvas = None, target: array = None):
  83.         new_location = self.get_new_location(target=target)
  84.         if canvas is not None:
  85.             dx, dy = reduce(new_location - self.location)
  86.             # debug(self.circle)
  87.             canvas.move(self.circle, dx, dy)
  88.         self.location = new_location
  89.         self.update_label()
  90.  
  91.     def create_circle(self, canvas: Canvas, color: str):
  92.         # changed this to return the ID
  93.         x, y = self.location
  94.         oval = x - self.radius, y - self.radius, x + self.radius, y + self.radius
  95.         x0, y0, x1, y1 = reduce(oval)
  96.         self.circle = canvas.create_oval(x0, y0, x1, y1, width=1, outline=color)
  97.  
  98.     def update_label(self):
  99.         x, y = reduce(self.location)
  100.         self.label.place(x=x, y=y)
  101.         self.label['text'] = f"{self.health}" if self.type in [EntityType.SPIDER, EntityType.PLAYER_BASE, EntityType.ENEMY_BASE] else f"{self.id}"
  102.  
  103.     def get_new_location(self, target=None):
  104.         if target is not None:
  105.             if isinstance(target, Entity):
  106.                 u: array = target.location - self.location
  107.             else:
  108.                 u: array = target - self.location
  109.             v: array = u / norm(u) if norm(u) > 0 else 0
  110.             speed: float = min(self.speed, self.dist(target))
  111.             new_location = self.location + speed * v
  112.         else:
  113.             new_location = self.location + self.velocity
  114.         return new_location
  115.  
  116.  
  117. def inside_map(e: Entity) -> bool:
  118.     x, y = e.location
  119.     return 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT
  120.  
  121.  
  122. def spawn_spiders(entity_counter: int, spiders_max_level: int) -> Tuple[Entity, Entity]:
  123.     hp_list = [(i + 1) * 10 for i in range(spiders_max_level)]
  124.     hp_spider: int = random.choice(hp_list)
  125.     top_left, top_right = 5000, MAP_WIDTH
  126.     random_x: int = random.randint(top_left, top_right)
  127.     dir: int = random.randint(1, 5)
  128.     theta = dir * math.pi / 6
  129.     # theta = radians(angle)
  130.     c, s = cos(theta), sin(theta)
  131.     R = array(((c, -s), (s, c)))
  132.     u_x: array = array([1, 0])
  133.     vx, vy = 400 * dot(R, u_x)
  134.     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)
  135.     x, y = array([MAP_WIDTH, MAP_HEIGHT]) - spider.location
  136.     vx, vy = -spider.velocity
  137.     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)
  138.     return spider, symmetric_spider
  139.  
  140.  
  141. @dataclass
  142. class Game:
  143.     max_turns: int
  144.     respawn_value: int
  145.     spiders_max_level: int
  146.     root: Tk = Tk()
  147.     canvas: Canvas = None
  148.     dbg_label: Label = None
  149.     base: array = None
  150.     detect_range: array = None
  151.     enemy_base: array = None
  152.     enemy_detect_range: array = None
  153.     map_width: int = None
  154.     map_height: int = None
  155.     reduce = None
  156.     player_kills = 0
  157.     enemy_kills = 0
  158.     turns = 1
  159.     entity_counter = 0
  160.     spawn_cooldown: int = 0
  161.     heroes: List[Entity] = field(default_factory=list)
  162.     enemies: List[Entity] = field(default_factory=list)
  163.     spiders: List[Entity] = field(default_factory=list)
  164.  
  165.     def init(self):
  166.         """
  167.            Initialise les entités de départ dans le jeu
  168.        :return:
  169.        """
  170.         self.base = Entity(id=None, health=3, type=EntityType.PLAYER_BASE, x=0, y=0, vx=0, vy=0, radius=300, speed=0)
  171.         self.detect_range = Entity(id=None, health=math.inf, type=EntityType.PLAYER_BASE, x=0, y=0, vx=0, vy=0, radius=5000, speed=0)
  172.         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)
  173.         self.enemy_detect_range = Entity(id=None, health=math.inf, type=EntityType.ENEMY_BASE, x=MAP_WIDTH, y=MAP_HEIGHT, vx=0, vy=0, radius=5000, speed=0)
  174.         hero_1 = Entity(id=0, health=math.inf, type=EntityType.PLAYER, x=1414, y=849, vx=0, vy=0, radius=800, speed=800)
  175.         hero_2 = Entity(id=1, health=math.inf, type=EntityType.PLAYER, x=1131, y=1131, vx=0, vy=0, radius=800, speed=800)
  176.         hero_3 = Entity(id=2, health=math.inf, type=EntityType.PLAYER, x=849, y=1414, vx=0, vy=0, radius=800, speed=800)
  177.         self.heroes.append(hero_1)
  178.         self.heroes.append(hero_2)
  179.         self.heroes.append(hero_3)
  180.         self.entity_counter = len(self.heroes)
  181.         hero_1 = Entity(id=0, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 1414, y=MAP_HEIGHT - 849, vx=0, vy=0, radius=800, speed=800)
  182.         hero_2 = Entity(id=1, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 1131, y=MAP_HEIGHT - 1131, vx=0, vy=0, radius=800, speed=800)
  183.         hero_3 = Entity(id=2, health=math.inf, type=EntityType.OPPONENT, x=MAP_WIDTH - 849, y=MAP_HEIGHT - 1414, vx=0, vy=0, radius=800, speed=800)
  184.         self.enemies.append(hero_1)
  185.         self.enemies.append(hero_2)
  186.         self.enemies.append(hero_3)
  187.         self.entity_counter += len(self.enemies)
  188.         spider, symmetric_spider = spawn_spiders(entity_counter=self.entity_counter, spiders_max_level=self.spiders_max_level)
  189.         self.entity_counter += 2
  190.         self.spiders.append(spider)
  191.         self.spiders.append(symmetric_spider)
  192.  
  193.     def init_tk(self):
  194.         """
  195.            Initialisation de la partie graphique de l'application et entités de jeu
  196.        :return:
  197.        """
  198.         self.canvas = Canvas(width=MAP_WIDTH * ZOOM, height=MAP_HEIGHT * ZOOM, bg='white')
  199.         self.canvas.pack(expand=YES, fill=BOTH)
  200.  
  201.         self.dbg_label = Label(self.root, text="Hello World!")
  202.         self.dbg_label.pack(pady=20)
  203.  
  204.         # Entities
  205.         self.base.create_circle(canvas=self.canvas, color='green')
  206.         self.base.label = Label(self.root)
  207.         self.base.update_label()
  208.         self.detect_range.create_circle(canvas=self.canvas, color='orange')
  209.         self.enemy_base.create_circle(canvas=self.canvas, color='green')
  210.         self.enemy_base.label = Label(self.root)
  211.         self.enemy_base.update_label()
  212.         self.enemy_detect_range.create_circle(canvas=self.canvas, color='orange')
  213.  
  214.         for e in self.heroes + self.enemies + self.spiders:
  215.             e.label = Label(self.root)
  216.             e.update_label()
  217.             # e.label.pack(pady=20)
  218.             color = 'red' if e.type == EntityType.SPIDER else 'blue' if e.type == EntityType.PLAYER else 'green'
  219.             e.create_circle(canvas=self.canvas, color=color)
  220.  
  221.         self.canvas.bind_all("<space>", self.callback)
  222.         self.root.mainloop()
  223.  
  224.     # All of this would do better as a subclass of Canvas with specialize methods
  225.  
  226.     def update(self):
  227.         """
  228.            Mise à jour des entités à chaque tour à partir des actions des héros générées par la fonction callback (IA du jeu)
  229.        :return:
  230.        """
  231.         self.turns += 1
  232.         self.spawn_cooldown -= 1 if self.spawn_cooldown else 0
  233.         for hero in self.heroes + self.enemies:
  234.             if hero.action.type != 'WAIT':
  235.                 hero.move(canvas=self.canvas, target=hero.action.target)
  236.             for spider in self.spiders:
  237.                 try:
  238.                     if hero.dist(spider) <= hero.radius:
  239.                         spider.health -= 2
  240.                         if spider.health <= 0:
  241.                             self.canvas.delete(spider.circle)
  242.                             # spider.circle.destroy()
  243.                             spider.label.destroy()
  244.                             self.spiders.remove(spider)
  245.                             if hero.type == EntityType.PLAYER:
  246.                                 self.player_kills += 1
  247.                             else:
  248.                                 self.enemy_kills += 1
  249.                 except TypeError:
  250.                     debug(f'{spider.type} {spider.location} - {hero.type} {hero.location}')
  251.  
  252.         for spider in self.spiders:
  253.             if spider.dist(self.base) <= 300:
  254.                 self.base.health -= 1
  255.                 self.base.update_label()
  256.                 self.canvas.delete(spider.circle)
  257.                 spider.label.destroy()
  258.                 self.spiders.remove(spider)
  259.             elif spider.dist(self.enemy_base) <= 300:
  260.                 self.enemy_base.health -= 1
  261.                 self.enemy_base.update_label()
  262.                 self.canvas.delete(spider.circle)
  263.                 spider.label.destroy()
  264.                 self.spiders.remove(spider)
  265.             elif spider.dist(self.base) <= 5000:
  266.                 spider.move(canvas=self.canvas, target=self.base)
  267.             elif spider.dist(self.enemy_base) <= 5000:
  268.                 spider.move(canvas=self.canvas, target=self.enemy_base)
  269.             elif not inside_map(spider):
  270.                 spider.label.destroy()
  271.                 self.spiders.remove(spider)
  272.             else:
  273.                 spider.move(canvas=self.canvas)
  274.  
  275.         if self.base.health <= 0:
  276.             self.dbg_label['text'] = f'OPPONENT WINS after {self.turns} turns!'
  277.             self.canvas.unbind_all("<space>")
  278.         elif self.enemy_base.health <= 0:
  279.             self.dbg_label['text'] = f'PLAYER WINS after {self.turns} turns!'
  280.             self.canvas.unbind_all("<space>")
  281.         elif self.turns >= self.max_turns:
  282.             if self.enemy_base.health > self.base.health:
  283.                 self.dbg_label['text'] = f'OPPONENT WINS!'
  284.             elif self.enemy_base.health < self.base.health:
  285.                 self.dbg_label['text'] = f'PLAYER WINS!'
  286.             else:
  287.                 if self.player_kills < self.enemy_kills:
  288.                     self.dbg_label['text'] = f'OPPONENT WINS! SPIDERS KILLED = OPPONENT: {self.enemy_kills} vs PLAYER: {self.player_kills}'
  289.                 elif self.player_kills > self.enemy_kills:
  290.                     self.dbg_label['text'] = f'PLAYER WINS! SPIDERS KILLED = PLAYER: {self.player_kills} vs OPPONENT: {self.enemy_kills}'
  291.                 else:
  292.                     self.dbg_label['text'] = f'THIS IS A DRAW! = {self.enemy_kills}-{self.player_kills}'
  293.             self.canvas.unbind_all("<space>")
  294.         else:
  295.             if not self.spawn_cooldown:
  296.                 self.spawn_cooldown = self.respawn_value
  297.                 spider, symmetric_spider = spawn_spiders(entity_counter=self.entity_counter, spiders_max_level=spiders_max_level)
  298.                 self.entity_counter += 2
  299.                 self.spiders.append(spider)
  300.                 self.spiders.append(symmetric_spider)
  301.                 for s in [spider, symmetric_spider]:
  302.                     s.label = Label(self.root)
  303.                     s.update_label()
  304.                     # s.label.pack(pady=20)
  305.                     s.create_circle(canvas=self.canvas, color='red')
  306.             self.dbg_label['text'] = f'PLAYER_BASE_HEALTH = {self.base.health} - PLAYER_BASE_HEALTH = {self.base.health}\n'
  307.             self.dbg_label['text'] += f'SPIDERS KILLED = PLAYER: {self.player_kills} vs OPPONENT: {self.enemy_kills}'
  308.  
  309.         for hero in self.heroes + self.enemies:
  310.             hero.action = None
  311.  
  312.     def player_ia(self, heroes: List[Entity], player: EntityType):
  313.         base: Entity = self.enemy_base if player == EntityType.OPPONENT else self.base
  314.         if self.spiders:
  315.             spiders = sorted(self.spiders, key=lambda s: s.dist(base))
  316.             while heroes and spiders:
  317.                 spider: Entity = spiders.pop(0)
  318.                 hero: Entity = min(heroes, key=lambda h: h.dist(spider))
  319.                 heroes.remove(hero)
  320.                 hero.action = Action(type='MOVE', target=spider.location)
  321.         for hero in heroes:
  322.             if not hero.action:
  323.                 hero.action = Action(type='WAIT')
  324.  
  325.     def callback(self, event):
  326.         """
  327.            IA for Dummies (fonction tour par tour de Codingame)
  328.        :param event:
  329.        :return:
  330.        """
  331.         self.player_ia(heroes=self.heroes[:], player=EntityType.PLAYER)
  332.         self.player_ia(heroes=self.enemies[:], player=EntityType.OPPONENT)
  333.  
  334.         self.update()
  335.  
  336.  
  337. def read_int_value(message: str, max_value: int) -> int:
  338.     while True:
  339.         try:
  340.             print(f'{message} [1-{max_value}] ')
  341.             value = int(input())
  342.             if not value or not 1 <= int(value) <= max_value:
  343.                 raise ValueError
  344.         except ValueError:
  345.             continue
  346.         except UnboundLocalError:
  347.             continue
  348.         finally:
  349.             return int(value)
  350.  
  351.  
  352. if __name__ == "__main__":
  353.     MAP_WIDTH, MAP_HEIGHT = 17630, 9000
  354.     mac_display_resolution = 1920, 1080
  355.     ZOOM = 0.1
  356.     SPAWN_COOLDOWN = 2
  357.     reduce = lambda x_list: map(lambda x: x * ZOOM, x_list)
  358.  
  359.     max_turns = read_int_value(message="Enter max number of rounds:", max_value=300)
  360.     respawn_value = read_int_value(message="Frequency of respawn spiders:", max_value=10)
  361.     spiders_max_level = read_int_value(message="Maximum level of spiders:", max_value=10)
  362.     game: Game = Game(max_turns=int(max_turns), respawn_value=respawn_value, spiders_max_level=spiders_max_level)
  363.     game.init()
  364.     game.init_tk()
  365.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement