Strategická: Komentáře a chyby v češtině
This commit is contained in:
parent
de72cb8499
commit
276c55531f
2 changed files with 49 additions and 49 deletions
|
@ -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']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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ý má 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…
Reference in a new issue