diff --git a/klient/client.py b/klient/client.py index d80002e..20dc3dc 100755 --- a/klient/client.py +++ b/klient/client.py @@ -14,13 +14,13 @@ from typing import List, Optional, Tuple parser = argparse.ArgumentParser() parser.add_argument("--token", type=str, required=True, help="API token.") -parser.add_argument("--server", type=str, default="http://localhost:5000", help="Server address.") -parser.add_argument("--game", type=str, default="main", help="'main' or 'test_#'.") -parser.add_argument("--command", type=str, nargs="+", default=["python3", "%"], help="Command to execute as strategy code. Use '%' for program.") -parser.add_argument("--program", type=str, default=None, help="Program to substitute to command.") -parser.add_argument("--copy", default=False, action="store_true", help="Save a copy of the program to ensure the same code is used all the time.") +parser.add_argument("--server", type=str, default="http://localhost:5000", help="Adresa severu.") +parser.add_argument("--game", type=str, default="main", help="'main' nebo 'test_#'.") +parser.add_argument("--command", type=str, nargs="+", default=["python3", "%"], help="Příkaz pro spuštění strategie. '%' bude nahrazeno programem.") +parser.add_argument("--program", type=str, default="play.py", help="Program, který má být použít v příkazu.") +parser.add_argument("--copy", default=False, action="store_true", help="Vyrob kopii souboru, aby se vždy použil stejný kód.") parser.add_argument("--log-level", type=str, default="info", choices=["error", "warning", "info"]) -parser.add_argument("--save-state", type=str, default=None, help="Save state to this file.") +parser.add_argument("--save-state", type=str, default=None, help="Pouze ulož herní info do souboru.") TIME_BEFORE_RETRY = 2.0 @@ -38,7 +38,7 @@ def main(args: argparse.Namespace) -> None: state = get_state(min_round, args) round = state["round"] min_round = round + 1 - # if requested, dump state to file instead + # ulož herní stav a ukonči program if args.save_state is not None: if args.save_state == "-": print(json.dumps(state)) @@ -55,7 +55,7 @@ def main(args: argparse.Namespace) -> None: try: turn = json.loads(turn_json) except json.JSONDecodeError: - logger.error(f"Invalid JSON returned by strategy code.") + logger.error(f"Strategie vypsala nevalidní JSON.") sys.exit(1) send_turn(turn, round, args) @@ -63,7 +63,7 @@ def main(args: argparse.Namespace) -> None: def get_command(args) -> List[str]: if args.program is None: - logger.error("Program not specified.") + logger.error("Specifikuj program.") sys.exit(1) program = os.path.realpath(args.program) @@ -80,7 +80,7 @@ def get_command(args) -> List[str]: def run_subprocess( command: List[str], state_json: str, timeout: Optional[int] ) -> Optional[str]: - """Run user program and return its output.""" + """Spusť strategii a vypiš její výstup.""" proc = Popen(command, encoding="utf-8", stdin=PIPE, stdout=PIPE, stderr=PIPE) @@ -88,20 +88,22 @@ def run_subprocess( stdout, stderr = proc.communicate(input=state_json, timeout=timeout) except TimeoutExpired: proc.kill() - logger.error("Strategy code took too long.") + logger.error("Strategie se počítala příliš dlouho.") return None if stderr: - logger.error(f"Strategy code stderr:\n{stderr}") + logger.error(f"Chybový výstup strategie:\n{stderr}") if proc.returncode != 0: - logger.error("Strategy code exited with non-zero exit code.") + logger.error("Strategie skončila s chybovým návratovým kódem.") sys.exit(1) return stdout def get_state(min_round: int, args) -> dict: - """Iteratively try to get game data from the server.""" + """Opakovaně se pokus získat herní data ze serveru.""" def get_state_once() -> Tuple[Optional[dict], float]: + """Vrací stav případě úspěchu, jinak None a dobu čekání.""" + try: res = requests.get(f"{args.server}/api/state", params={ "game": args.game, @@ -109,8 +111,8 @@ def get_state(min_round: int, args) -> dict: "min_round": min_round }) except requests.exceptions.RequestException as e: - # server might be down, retry later - logger.warning(f"Request error: {e}") + # server mohl vypadnout, zkus to znovu později + logger.warning(f"Chyba při dotazu na stav: {e}") return None, TIME_BEFORE_RETRY if not res.ok: log_server_error(res) @@ -118,14 +120,14 @@ def get_state(min_round: int, args) -> dict: state = res.json() if state["status"] == "ok": - logger.info("Received new state.") + logger.info("Nový stav obdržen.") return state, 0 - # retry after some time + # musíme chvíli počkat if state["status"] == "waiting": - logger.info("Server is busy.") + logger.info("Server počítá.") if state["status"] == "too_early": - logger.info("Round didn't start yet.") + logger.info("Kolo ještě nezačalo.") return None, state["wait"] state, wait_time = get_state_once() @@ -136,10 +138,10 @@ def get_state(min_round: int, args) -> dict: def send_turn(turn: dict, round: int, args) -> None: - """Iteratively try to send turn to the server.""" + """Opakovaně se pokus poslat tah na server.""" def send_turn_once() -> bool: - """Returns True if server answered.""" + """Vrací True pokud server odpověděl.""" try: res = requests.post( @@ -152,30 +154,30 @@ def send_turn(turn: dict, round: int, args) -> None: json=turn ) except requests.exceptions.RequestException as e: - # server might be down, retry later - logger.warning(f"Request error: {e}") + # server mohl vypadnout, zkus to znovu později + logger.warning(f"Chyba při posílání tahu: {e}") return False if not res.ok: log_server_error(res) sys.exit(1) - # print errors - # because such errors are caused by the submitted turn, - # retrying will not help, so return True + # vypiš chyby + # tyto chyby jsou způsobeny špatným tahem, + # opakování požadavku nepomůže, takže vracíme True res_json = res.json() if res_json["status"] == "ok": - logger.info("Turn accepted.") + logger.info("Tah úspěšně přijat.") elif res_json["status"] == "too_late": - logger.error("Turn submitted too late.") + logger.error("Tah poslán příliš pozdě.") elif res_json["status"] == "error": - logger.error(f"Turn error: {res_json['description']}") + logger.error(f"Chyba v tahu: {res_json['description']}") elif res_json["status"] == "warning": member_warns = "\n".join([ f" {member['id']}: {member['description']}" for member in res_json["members"] ]) - logger.info("Turn accepted with warnings.") - logger.warning(f"Member warnings:\n{member_warns}") + logger.info("Tah přijat s varováními.") + logger.warning(f"Varování pro vojáky:\n{member_warns}") return True while not send_turn_once(): @@ -185,7 +187,7 @@ def send_turn(turn: dict, round: int, args) -> None: def log_server_error(res: requests.Response) -> None: res_json = res.json() logger.error( - f"Server error: {res.status_code} {res.reason}: " + f"Chyba serveru: {res.status_code} {res.reason}: " f"{res_json['description']}" ) diff --git a/klient/play.py b/klient/play.py index 6b88bd3..319fa62 100755 --- a/klient/play.py +++ b/klient/play.py @@ -17,12 +17,12 @@ class Direction(enum.Enum): STAY = None def combine(self, other) -> Direction: - """Rotate by another direction.""" + """Otoč směr podle jiného otočení.""" return Direction((self.value + other.value) % 4) def invert(self) -> Direction: - """Get the opposite direction.""" + """Získej opačný směr.""" if self == Direction.STAY: return self @@ -82,7 +82,7 @@ class Field: return hash((self.i, self.j)) def get_neighbour_field(self, direction: Direction) -> Field: - """Get the next field in the given direction.""" + """Další políčko v daném směru.""" if direction == Direction.UP: neighbour_i = self.i - 1 @@ -99,7 +99,7 @@ class Field: else: neighbour_i = self.i neighbour_j = self.j - # ensure coords are in bounds + # zajisti, aby souřadnice byly v rozsahu herní plochy neighbour_i %= len(state.world) neighbour_j %= len(state.world[0]) return state.world[neighbour_i][neighbour_j] @@ -116,10 +116,7 @@ class Field: class State: - """The entire game state. - - Also contains methods for parsing and actions submission. - """ + """Celý herní stav včetně parsování a vypisování.""" def __init__(self, state: dict) -> None: self.teams = [Team(i, i == state["team_id"]) @@ -167,13 +164,13 @@ class State: return fields def build_turn(self) -> dict: - """Build a dictionary with member actions.""" + """Získej slovník s příkazy pro server.""" for team in self.teams: if not team.is_me: for member in team.members: if member.action is not Direction.STAY: - raise Exception("Akce přiřazena cizímu týmu") + raise Exception("Akce přiřazena cizímu týmu.") return { "members": [ {"id": member.id, "action": str(member.action)} @@ -188,7 +185,7 @@ state: State def find_fields(predicate: Callable[[Field], bool]) -> List[Field]: - """Find all fields that satisfy the given predicate.""" + """Najdi políčka splňující danou podmínku.""" result = [] for row in state.world: @@ -199,14 +196,15 @@ def find_fields(predicate: Callable[[Field], bool]) -> List[Field]: def pathfind(member: Member, goal: Field) -> Direction: - """Find the shortest path from member position to goal. + """Najdi nejkratší cestu od vojáka k cílovému políčku. - Returns the first direction to go in. - If there is no path, returns STAY. + Vrátí první krok, který má voják udělat. + Pokud žádná cesta neexistuje, vrátí STAY. """ - # BFS from goal to member - # this direction makes it easier to realize that no path exists + # BFS od cíle k vojákovi + # tento směr umožní rychle zjistit, že cesta neexistuje, + # a také jednoduše získat první krok explored: Set[Field] = set() queue = collections.deque([goal]) while queue: