Browse Source

Strategická: Komentáře a chyby v češtině

master
David Klement 2 years ago
parent
commit
276c55531f
  1. 68
      klient/client.py
  2. 30
      klient/play.py

68
klient/client.py

@ -14,13 +14,13 @@ from typing import List, Optional, Tuple
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--token", type=str, required=True, help="API token.") 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("--server", type=str, default="http://localhost:5000", help="Adresa severu.")
parser.add_argument("--game", type=str, default="main", help="'main' or 'test_#'.") parser.add_argument("--game", type=str, default="main", help="'main' nebo 'test_#'.")
parser.add_argument("--command", type=str, nargs="+", default=["python3", "%"], help="Command to execute as strategy code. Use '%' for program.") 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=None, help="Program to substitute to command.") 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="Save a copy of the program to ensure the same code is used all the time.") 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("--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 TIME_BEFORE_RETRY = 2.0
@ -38,7 +38,7 @@ def main(args: argparse.Namespace) -> None:
state = get_state(min_round, args) state = get_state(min_round, args)
round = state["round"] round = state["round"]
min_round = round + 1 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 is not None:
if args.save_state == "-": if args.save_state == "-":
print(json.dumps(state)) print(json.dumps(state))
@ -55,7 +55,7 @@ def main(args: argparse.Namespace) -> None:
try: try:
turn = json.loads(turn_json) turn = json.loads(turn_json)
except json.JSONDecodeError: except json.JSONDecodeError:
logger.error(f"Invalid JSON returned by strategy code.") logger.error(f"Strategie vypsala nevalidní JSON.")
sys.exit(1) sys.exit(1)
send_turn(turn, round, args) send_turn(turn, round, args)
@ -63,7 +63,7 @@ def main(args: argparse.Namespace) -> None:
def get_command(args) -> List[str]: def get_command(args) -> List[str]:
if args.program is None: if args.program is None:
logger.error("Program not specified.") logger.error("Specifikuj program.")
sys.exit(1) sys.exit(1)
program = os.path.realpath(args.program) program = os.path.realpath(args.program)
@ -80,7 +80,7 @@ def get_command(args) -> List[str]:
def run_subprocess( def run_subprocess(
command: List[str], state_json: str, timeout: Optional[int] command: List[str], state_json: str, timeout: Optional[int]
) -> Optional[str]: ) -> Optional[str]:
"""Run user program and return its output.""" """Spusť strategii a vypiš její výstup."""
proc = Popen(command, encoding="utf-8", proc = Popen(command, encoding="utf-8",
stdin=PIPE, stdout=PIPE, stderr=PIPE) stdin=PIPE, stdout=PIPE, stderr=PIPE)
@ -88,20 +88,22 @@ def run_subprocess(
stdout, stderr = proc.communicate(input=state_json, timeout=timeout) stdout, stderr = proc.communicate(input=state_json, timeout=timeout)
except TimeoutExpired: except TimeoutExpired:
proc.kill() proc.kill()
logger.error("Strategy code took too long.") logger.error("Strategie se počítala příliš dlouho.")
return None return None
if stderr: if stderr:
logger.error(f"Strategy code stderr:\n{stderr}") logger.error(f"Chybový výstup strategie:\n{stderr}")
if proc.returncode != 0: 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) sys.exit(1)
return stdout return stdout
def get_state(min_round: int, args) -> dict: 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]: def get_state_once() -> Tuple[Optional[dict], float]:
"""Vrací stav případě úspěchu, jinak None a dobu čekání."""
try: try:
res = requests.get(f"{args.server}/api/state", params={ res = requests.get(f"{args.server}/api/state", params={
"game": args.game, "game": args.game,
@ -109,8 +111,8 @@ def get_state(min_round: int, args) -> dict:
"min_round": min_round "min_round": min_round
}) })
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# server might be down, retry later # server mohl vypadnout, zkus to znovu později
logger.warning(f"Request error: {e}") logger.warning(f"Chyba při dotazu na stav: {e}")
return None, TIME_BEFORE_RETRY return None, TIME_BEFORE_RETRY
if not res.ok: if not res.ok:
log_server_error(res) log_server_error(res)
@ -118,14 +120,14 @@ def get_state(min_round: int, args) -> dict:
state = res.json() state = res.json()
if state["status"] == "ok": if state["status"] == "ok":
logger.info("Received new state.") logger.info("Nový stav obdržen.")
return state, 0 return state, 0
# retry after some time # musíme chvíli počkat
if state["status"] == "waiting": if state["status"] == "waiting":
logger.info("Server is busy.") logger.info("Server počítá.")
if state["status"] == "too_early": if state["status"] == "too_early":
logger.info("Round didn't start yet.") logger.info("Kolo ještě nezačalo.")
return None, state["wait"] return None, state["wait"]
state, wait_time = get_state_once() 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: 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: def send_turn_once() -> bool:
"""Returns True if server answered.""" """Vrací True pokud server odpověděl."""
try: try:
res = requests.post( res = requests.post(
@ -152,30 +154,30 @@ def send_turn(turn: dict, round: int, args) -> None:
json=turn json=turn
) )
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
# server might be down, retry later # server mohl vypadnout, zkus to znovu později
logger.warning(f"Request error: {e}") logger.warning(f"Chyba při posílání tahu: {e}")
return False return False
if not res.ok: if not res.ok:
log_server_error(res) log_server_error(res)
sys.exit(1) sys.exit(1)
# print errors # vypiš chyby
# because such errors are caused by the submitted turn, # tyto chyby jsou způsobeny špatným tahem,
# retrying will not help, so return True # opakování požadavku nepomůže, takže vracíme True
res_json = res.json() res_json = res.json()
if res_json["status"] == "ok": if res_json["status"] == "ok":
logger.info("Turn accepted.") logger.info("Tah úspěšně přijat.")
elif res_json["status"] == "too_late": 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": 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": elif res_json["status"] == "warning":
member_warns = "\n".join([ member_warns = "\n".join([
f" {member['id']}: {member['description']}" f" {member['id']}: {member['description']}"
for member in res_json["members"] for member in res_json["members"]
]) ])
logger.info("Turn accepted with warnings.") logger.info("Tah přijat s varováními.")
logger.warning(f"Member warnings:\n{member_warns}") logger.warning(f"Varování pro vojáky:\n{member_warns}")
return True return True
while not send_turn_once(): 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: def log_server_error(res: requests.Response) -> None:
res_json = res.json() res_json = res.json()
logger.error( logger.error(
f"Server error: {res.status_code} {res.reason}: " f"Chyba serveru: {res.status_code} {res.reason}: "
f"{res_json['description']}" f"{res_json['description']}"
) )

30
klient/play.py

@ -17,12 +17,12 @@ class Direction(enum.Enum):
STAY = None STAY = None
def combine(self, other) -> Direction: def combine(self, other) -> Direction:
"""Rotate by another direction.""" """Otoč směr podle jiného otočení."""
return Direction((self.value + other.value) % 4) return Direction((self.value + other.value) % 4)
def invert(self) -> Direction: def invert(self) -> Direction:
"""Get the opposite direction.""" """Získej opačný směr."""
if self == Direction.STAY: if self == Direction.STAY:
return self return self
@ -82,7 +82,7 @@ class Field:
return hash((self.i, self.j)) return hash((self.i, self.j))
def get_neighbour_field(self, direction: Direction) -> Field: 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: if direction == Direction.UP:
neighbour_i = self.i - 1 neighbour_i = self.i - 1
@ -99,7 +99,7 @@ class Field:
else: else:
neighbour_i = self.i neighbour_i = self.i
neighbour_j = self.j neighbour_j = self.j
# ensure coords are in bounds # zajisti, aby souřadnice byly v rozsahu herní plochy
neighbour_i %= len(state.world) neighbour_i %= len(state.world)
neighbour_j %= len(state.world[0]) neighbour_j %= len(state.world[0])
return state.world[neighbour_i][neighbour_j] return state.world[neighbour_i][neighbour_j]
@ -116,10 +116,7 @@ class Field:
class State: class State:
"""The entire game state. """Celý herní stav včetně parsování a vypisování."""
Also contains methods for parsing and actions submission.
"""
def __init__(self, state: dict) -> None: def __init__(self, state: dict) -> None:
self.teams = [Team(i, i == state["team_id"]) self.teams = [Team(i, i == state["team_id"])
@ -167,13 +164,13 @@ class State:
return fields return fields
def build_turn(self) -> dict: 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: for team in self.teams:
if not team.is_me: if not team.is_me:
for member in team.members: for member in team.members:
if member.action is not Direction.STAY: 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 { return {
"members": [ "members": [
{"id": member.id, "action": str(member.action)} {"id": member.id, "action": str(member.action)}
@ -188,7 +185,7 @@ state: State
def find_fields(predicate: Callable[[Field], bool]) -> List[Field]: 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 = [] result = []
for row in state.world: 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: 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. Vrátí první krok, který voják udělat.
If there is no path, returns STAY. Pokud žádná cesta neexistuje, vrátí STAY.
""" """
# BFS from goal to member # BFS od cíle k vojákovi
# this direction makes it easier to realize that no path exists # tento směr umožní rychle zjistit, že cesta neexistuje,
# a také jednoduše získat první krok
explored: Set[Field] = set() explored: Set[Field] = set()
queue = collections.deque([goal]) queue = collections.deque([goal])
while queue: while queue:

Loading…
Cancel
Save