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 """