|
@ -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']}" |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|