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
- # HERO = 1
- # BASE = 2
- #
- 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
- action: Action = None
- 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)
- def dist(self, other):
- return norm(self.location - other.location) if isinstance(other, Entity) else norm(self.location - other)
- 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)
- # debug(self.circle)
- 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)
- self.label['text'] = f"{self.health}" if self.type in [EntityType.SPIDER, EntityType.PLAYER_BASE, EntityType.ENEMY_BASE] else f"{self.id}"
- 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(e: Entity) -> bool:
- x, y = e.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
- dbg_label: Label = None
- base: array = None
- detect_range: array = None
- enemy_base: array = None
- enemy_detect_range: array = None
- map_width: int = None
- map_height: int = None
- reduce = None
- player_kills = 0
- enemy_kills = 0
- turns = 1
- entity_counter = 0
- spawn_cooldown: int = 0
- heroes: List[Entity] = field(default_factory=list)
- enemies: List[Entity] = field(default_factory=list)
- spiders: List[Entity] = field(default_factory=list)
- def init(self):
- """
- Initialise les entités de départ dans le jeu
- :return:
- """
- self.base = Entity(id=None, health=3, type=EntityType.PLAYER_BASE, x=0, y=0, vx=0, vy=0, radius=300, speed=0)
- 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)
- 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)
- 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)
- hero_1 = Entity(id=0, health=math.inf, type=EntityType.PLAYER, x=1414, y=849, vx=0, vy=0, radius=800, speed=800)
- hero_2 = Entity(id=1, health=math.inf, type=EntityType.PLAYER, x=1131, y=1131, vx=0, vy=0, radius=800, speed=800)
- hero_3 = Entity(id=2, health=math.inf, type=EntityType.PLAYER, x=849, y=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)
- 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)
- 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)
- 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)
- self.enemies.append(hero_1)
- self.enemies.append(hero_2)
- self.enemies.append(hero_3)
- self.entity_counter += len(self.enemies)
- spider, symmetric_spider = spawn_spiders(entity_counter=self.entity_counter, spiders_max_level=self.spiders_max_level)
- 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.dbg_label = Label(self.root, text="Hello World!")
- self.dbg_label.pack(pady=20)
- # Entities
- self.base.create_circle(canvas=self.canvas, color='green')
- self.base.label = Label(self.root)
- self.base.update_label()
- self.detect_range.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_detect_range.create_circle(canvas=self.canvas, color='orange')
- for e in self.heroes + self.enemies + 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(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:
- """
- self.turns += 1
- self.spawn_cooldown -= 1 if self.spawn_cooldown else 0
- for hero in self.heroes + self.enemies:
- if hero.action.type != 'WAIT':
- hero.move(canvas=self.canvas, target=hero.action.target)
- for spider in self.spiders:
- try:
- if hero.dist(spider) <= hero.radius:
- spider.health -= 2
- if spider.health <= 0:
- self.canvas.delete(spider.circle)
- # spider.circle.destroy()
- spider.label.destroy()
- self.spiders.remove(spider)
- if hero.type == EntityType.PLAYER:
- self.player_kills += 1
- else:
- self.enemy_kills += 1
- except TypeError:
- debug(f'{spider.type} {spider.location} - {hero.type} {hero.location}')
- for spider in self.spiders:
- if spider.dist(self.base) <= 300:
- self.base.health -= 1
- self.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()
- self.canvas.delete(spider.circle)
- spider.label.destroy()
- self.spiders.remove(spider)
- elif spider.dist(self.base) <= 5000:
- spider.move(canvas=self.canvas, target=self.base)
- elif spider.dist(self.enemy_base) <= 5000:
- spider.move(canvas=self.canvas, target=self.enemy_base)
- elif not inside_map(spider):
- spider.label.destroy()
- self.spiders.remove(spider)
- else:
- spider.move(canvas=self.canvas)
- if self.base.health <= 0:
- self.dbg_label['text'] = f'OPPONENT WINS after {self.turns} turns!'
- self.canvas.unbind_all("<space>")
- elif self.enemy_base.health <= 0:
- self.dbg_label['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.base.health:
- self.dbg_label['text'] = f'OPPONENT WINS!'
- elif self.enemy_base.health < self.base.health:
- self.dbg_label['text'] = f'PLAYER WINS!'
- else:
- if self.player_kills < self.enemy_kills:
- self.dbg_label['text'] = f'OPPONENT WINS! SPIDERS KILLED = OPPONENT: {self.enemy_kills} vs PLAYER: {self.player_kills}'
- elif self.player_kills > self.enemy_kills:
- self.dbg_label['text'] = f'PLAYER WINS! SPIDERS KILLED = PLAYER: {self.player_kills} vs OPPONENT: {self.enemy_kills}'
- else:
- self.dbg_label['text'] = f'THIS IS A DRAW! = {self.enemy_kills}-{self.player_kills}'
- 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=spiders_max_level)
- 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.label.pack(pady=20)
- s.create_circle(canvas=self.canvas, color='red')
- self.dbg_label['text'] = f'PLAYER_BASE_HEALTH = {self.base.health} - PLAYER_BASE_HEALTH = {self.base.health}\n'
- self.dbg_label['text'] += f'SPIDERS KILLED = PLAYER: {self.player_kills} vs OPPONENT: {self.enemy_kills}'
- for hero in self.heroes + self.enemies:
- hero.action = None
- def player_ia(self, heroes: List[Entity], player: EntityType):
- base: Entity = self.enemy_base if player == EntityType.OPPONENT else self.base
- if self.spiders:
- spiders = sorted(self.spiders, key=lambda s: s.dist(base))
- while heroes and spiders:
- spider: Entity = spiders.pop(0)
- hero: Entity = min(heroes, key=lambda h: h.dist(spider))
- heroes.remove(hero)
- hero.action = Action(type='MOVE', target=spider.location)
- for hero in heroes:
- if not hero.action:
- hero.action = Action(type='WAIT')
- def callback(self, event):
- """
- IA for Dummies (fonction tour par tour de Codingame)
- :param event:
- :return:
- """
- self.player_ia(heroes=self.heroes[:], player=EntityType.PLAYER)
- self.player_ia(heroes=self.enemies[:], player=EntityType.OPPONENT)
- self.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