286 lines
9.7 KiB
Python
286 lines
9.7 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
import sys, json, math
|
||
|
|
||
|
from dataclasses import dataclass
|
||
|
from datetime import datetime
|
||
|
from typing import Optional
|
||
|
|
||
|
|
||
|
"""
|
||
|
============================================
|
||
|
______ _ _______ _____
|
||
|
|___ / | | | | ___ \_ _|
|
||
|
/ / ___ _ _ ___ | | | | |_/ / | |
|
||
|
/ / / _ \ | | / __| | |/\| | __/ | |
|
||
|
./ /__| __/ |_| \__ \ \ /\ / | _| |_
|
||
|
\_____/\___|\__,_|___/ \/ \/\_| \___/
|
||
|
|
||
|
============================================
|
||
|
|
||
|
|
||
|
Deze file bevatten een aantal klassen die het makkelijker maken om te werken met de input
|
||
|
die je krijgt van de game Planetwars.
|
||
|
|
||
|
De belangrijkste klasse hierin is PlanetWars.
|
||
|
Deze laat je toe om makkelijk de staat van het spel bij te houden.
|
||
|
|
||
|
|
||
|
De bedoeling van de klasse is dat je hem eenmaal aanmaakt en dan
|
||
|
bij elke beurt de update() functie aanroept met de data die je krijgt van het spel.
|
||
|
|
||
|
Daarna heb je alle data in de variabelen
|
||
|
- my_planets
|
||
|
- neutral_planets
|
||
|
- ...
|
||
|
- enemy_expeditions
|
||
|
|
||
|
Die bestaan uit lijsten de klassen Planet en Expedition.
|
||
|
De bedoeling van die klassen is om makkelijk met de data overweg te kunnen
|
||
|
en NIET om zelf een instance van aan te maken (zoals aangegeven door @dataclass)
|
||
|
|
||
|
Je kan dan de functie add_move() gebruiken om een bepaalde zet die je wilt doen toe te voegen.
|
||
|
Voeg er zoveel toe als je wilt en gebruik dan de functie move() om ze effectief door te geven.
|
||
|
Daarna kan je weer wachten op nieuwe data van het spel.
|
||
|
|
||
|
De klasse heeft nog twee andere variabelen.
|
||
|
- _queued_moves
|
||
|
- current_turn
|
||
|
|
||
|
Het is niet de bedoeling om van buiten de klasse aan _queued_moves te komen (aangegeven door de leidende _).
|
||
|
Wil je toch extra functionaliteit toevoegen om bijvoorbeeld een bepaalde move te verwijderen
|
||
|
voeg dat dan toe als een functie binnenin de klasse.
|
||
|
|
||
|
Tenslotte heb je nog de variable current_turn.
|
||
|
Die wordt momenteel niet gebruikt en houd alleen bij aan welke turn we zitten.
|
||
|
|
||
|
|
||
|
Je kan de file example.py bekijken met een voorbeeld implementatie.
|
||
|
"""
|
||
|
|
||
|
|
||
|
def log(message: str):
|
||
|
"""Logs een gegeven bericht met een tijdstip ervoor."""
|
||
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
print(f"[{timestamp}] {message}", file=sys.stderr)
|
||
|
|
||
|
@dataclass
|
||
|
class Planet():
|
||
|
"""
|
||
|
Stelt een planeet voor.
|
||
|
|
||
|
Niet de bedoeling dat je deze klasse zelf aanmaakt.
|
||
|
"""
|
||
|
owner: Optional[int]
|
||
|
ship_count: int
|
||
|
name: str
|
||
|
x: float
|
||
|
y: float
|
||
|
|
||
|
@staticmethod
|
||
|
def _from_input(planet: dict):
|
||
|
"""
|
||
|
Maakt een Planet object gegeven de input die je krijgt van de game.
|
||
|
|
||
|
Moet je niet zelf gebruiken.
|
||
|
"""
|
||
|
return Planet(planet["owner"], planet["ship_count"], planet["name"], planet["x"], planet["y"])
|
||
|
|
||
|
@staticmethod
|
||
|
def distance_between(p1: Planet, p2: Planet):
|
||
|
"""Geeft het aantal beurten terug die nodig zijn om een bepaalde planeet te bereiken."""
|
||
|
return math.ceil(math.dist([p1.x, p1.y], [p2.x, p2.y]))
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Expedition():
|
||
|
"""
|
||
|
Stelt een expeditie voor.
|
||
|
|
||
|
Niet de bedoeling dat je deze klasse zelf aanmaakt.
|
||
|
"""
|
||
|
id: int
|
||
|
ship_count: int
|
||
|
origin: Planet
|
||
|
destination: Planet
|
||
|
owner: int
|
||
|
turns_remaining: int
|
||
|
|
||
|
@staticmethod
|
||
|
def _from_input(exp: dict, planets: list[Planet]):
|
||
|
"""
|
||
|
Maakt een Expedition object gegeven de input die je krijgt van de game.
|
||
|
|
||
|
Moet je niet zelf gebruiken.
|
||
|
"""
|
||
|
origin = next((planet for planet in planets if planet.name == exp["origin"]), None)
|
||
|
destination = next((planet for planet in planets if planet.name == exp["destination"]), None)
|
||
|
if None in (origin, destination):
|
||
|
log("Error: No origin or destination planet found")
|
||
|
log(planets)
|
||
|
raise KeyError("Origin or destination planet not found")
|
||
|
return Expedition(exp["id"], exp["ship_count"], origin, destination, exp["owner"], exp["turns_remaining"])
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Move():
|
||
|
"""
|
||
|
Stelt een zet voor.
|
||
|
|
||
|
Niet de bedoeling dat je deze klasse zelf aanmaakt.
|
||
|
"""
|
||
|
origin: Planet
|
||
|
destination: Planet
|
||
|
ship_count: int
|
||
|
|
||
|
def get_dict(self):
|
||
|
"""Maak een dictionary van de gegevens zodat het kan gezien worden als een zet."""
|
||
|
return {
|
||
|
"origin": self.origin.name,
|
||
|
"destination": self.destination.name,
|
||
|
"ship_count": self.ship_count
|
||
|
}
|
||
|
|
||
|
|
||
|
class PlanetWars():
|
||
|
"""Houdt de staat van het spel bij."""
|
||
|
my_planets: list[Planet] = [] # Lijst van de planeten onder jouw controle.
|
||
|
neutral_planets: list[Planet] = [] # Lijst van de planeten die niemand heeft.
|
||
|
enemy_planets: list[Planet] = [] # Lijst van de planeten onder de controle van de tegenstander.
|
||
|
|
||
|
my_expeditions: list[Expedition] = [] # Alle huidige expedities die van jouw zijn.
|
||
|
enemy_expeditions: list[Expedition] = [] # Alle huidige expedities die van de tegenstander zijn.
|
||
|
|
||
|
_queued_moves: list[Move] = [] # Alle moves die je klaar hebt staan om uit te voeren. Kom hier niet rechtstreeks aan.
|
||
|
_moved = False # Houd bij of je al een move hebt gespeeld deze beurt.
|
||
|
|
||
|
current_turn = 0 # Aantal beurten die al gepasseerd zijn.
|
||
|
|
||
|
def update(self, input: str):
|
||
|
"""
|
||
|
Roep dit iedere beurt op met de input die je krijgt van het spel.
|
||
|
Gaat over de data en maakt er de juiste objecten van.
|
||
|
|
||
|
Args:
|
||
|
- input (str): De data die je krijgt van het spel. Zie example.py hoe je dit het best krijgt
|
||
|
|
||
|
Returns:
|
||
|
- bool: Geeft aan of opgegeven data klopt en of je iets kan doen.
|
||
|
"""
|
||
|
try:
|
||
|
state = json.loads(input)
|
||
|
except:
|
||
|
log("Error: Converting input to json")
|
||
|
log(input)
|
||
|
return False
|
||
|
|
||
|
try:
|
||
|
planets = [Planet._from_input(planet) for planet in state["planets"]]
|
||
|
except KeyError:
|
||
|
log("Error: Parsing planet input")
|
||
|
log(input)
|
||
|
return False
|
||
|
|
||
|
if len(planets) == 0:
|
||
|
log("Error: No planets")
|
||
|
log(input)
|
||
|
return False
|
||
|
|
||
|
self.my_planets = [planet for planet in planets if planet.owner == 1]
|
||
|
self.neutral_planets = [planet for planet in planets if planet.owner is None]
|
||
|
self.enemy_planets = [planet for planet in planets if planet.owner not in [None, 1]]
|
||
|
|
||
|
|
||
|
try:
|
||
|
self.my_expeditions = [Expedition._from_input(expedition, planets) for expedition in state["expeditions"] if expedition["owner"] == 1]
|
||
|
self.enemy_expeditions = [Expedition._from_input(expedition, planets) for expedition in state["expeditions"] if expedition["owner"] != 1]
|
||
|
except KeyError:
|
||
|
log("Error: Parsing expedition input")
|
||
|
log(input)
|
||
|
return False
|
||
|
|
||
|
self._queued_moves.clear()
|
||
|
self._moved = False
|
||
|
self.current_turn += 1
|
||
|
|
||
|
return True
|
||
|
|
||
|
def add_move(self, origin: Planet, destination: Planet, ships: int):
|
||
|
"""
|
||
|
Voeg een zet toe.
|
||
|
Je kan zoveel zetten toevoegen als je wilt en ze worden pas doorgegeven wanneer je move() aanroept.
|
||
|
|
||
|
Args:
|
||
|
- origin (Planet): De planeet van waar de schepen moeten komen.
|
||
|
- destination (Planet): De planeet waar de schepen moeten toekomen.
|
||
|
- ships (int): De hoeveelheid schepen die je wilt versturen
|
||
|
|
||
|
Returns:
|
||
|
- bool: Geeft weer indien het een geldige zet is. Let op er worden maar een paar basis checks gedaan hiervoor
|
||
|
"""
|
||
|
# Je kan maar 1 keer al je moves versturen per buurt.
|
||
|
# Je kan daarin wel meerdere moves zetten.
|
||
|
# Je kan dit doen door meerdere keren add_move op te roepen voor je move() oproept.
|
||
|
if self._moved:
|
||
|
return False
|
||
|
|
||
|
# Je kan alleen schepen sturen die komen van je eigen planeet
|
||
|
if origin.owner != 1:
|
||
|
return False
|
||
|
|
||
|
# Je kan geen schepen sturen naar dezelfde planeet als van waar ze komen
|
||
|
if origin.name == destination.name:
|
||
|
return False
|
||
|
|
||
|
# Je kan niet meer schepen versturen dan je er zijn op de oorsprong planeet
|
||
|
total_ships = ships + sum(move.ship_count for move in self._queued_moves if move.origin.name == origin.name)
|
||
|
if total_ships > origin.ship_count:
|
||
|
return False
|
||
|
|
||
|
self._queued_moves.append(Move(origin, destination, ships))
|
||
|
|
||
|
return True
|
||
|
|
||
|
def clear_moves(self):
|
||
|
"""Verwijder de moves die je al had toegevoegd"""
|
||
|
self._queued_moves.clear()
|
||
|
|
||
|
def move(self):
|
||
|
"""Voer alle zetten uit en ga naar de volgende beurt"""
|
||
|
# Je kan maar 1 keer al je moves versturen per buurt.
|
||
|
# Je kan daarin wel meerdere moves zetten.
|
||
|
# Je kan dit doen door meerdere keren add_move op te roepen voor je move() oproept.
|
||
|
if self._moved:
|
||
|
return
|
||
|
|
||
|
moves = [move.get_dict() for move in self._queued_moves]
|
||
|
|
||
|
self._moved = True
|
||
|
|
||
|
print(json.dumps({ "moves": moves }))
|
||
|
sys.stdout.flush()
|
||
|
|
||
|
|
||
|
"""
|
||
|
Weet je niet hoe je nu verder moet?
|
||
|
|
||
|
Kijk naar example.py hoe de klasse te implementeren.
|
||
|
|
||
|
Nog enkele tips hoe je je bot beter kan maken:
|
||
|
|
||
|
- Maak een nieuwe functie aan in de klasse PlanetWars `predict`.
|
||
|
Deze probeert te voorspellen hoeveel schepen van je tegenstander er op een bepaalde
|
||
|
planeet toekomen na x aantal beurten.
|
||
|
|
||
|
- Maak een onderscheid tussen een gevallen waarbij jouw tegenstander geen extra schepen meer
|
||
|
verstuurd naar een bepaalde planeet en al zijn schepen er naar verstuurd.
|
||
|
Welke planeten kan je met de minste effort makkelijk pakken?
|
||
|
|
||
|
- Indien je al meerdere planeten hebt probeer schepen van de achterliggende planeten naar
|
||
|
de frontline te sturen.
|
||
|
|
||
|
- ...
|
||
|
|
||
|
Wees creatief, de mogelijkheden zijn eindeloos :D
|
||
|
"""
|