Browse Source

Strategická: Lepší chybové chování

master
David Klement 1 year ago
parent
commit
642f4389af
  1. 166
      klient/client.py

166
klient/client.py

@ -4,6 +4,7 @@ import json
import logging
import requests
import shutil
import sys
import tempfile
import time
from subprocess import Popen, PIPE, TimeoutExpired
@ -36,12 +37,7 @@ def main(args: argparse.Namespace) -> None:
min_round = 0
while True:
state, wait_time = get_state(min_round, args)
if state is None:
# retry later
time.sleep(wait_time)
continue
state = get_state(min_round, args)
round = state["round"]
min_round = round + 1
# if requested, dump state to file instead
@ -59,16 +55,14 @@ def main(args: argparse.Namespace) -> None:
turn = json.loads(turn_json)
except json.JSONDecodeError:
logger.error(f"Invalid JSON returned by strategy code.")
continue
sys.exit(1)
while not send_turn(turn, round, args):
# if there was a connection error, retry later
time.sleep(TIME_BEFORE_RETRY)
send_turn(turn, round, args)
def run_subprocess(
program: str, state_json: str, timeout: Optional[float]
) -> Optional[str]:
program: str, state_json: str, timeout: Optional[float]
) -> Optional[str]:
"""Run user program and return its output."""
proc = Popen([program], encoding="utf-8",
@ -83,82 +77,104 @@ def run_subprocess(
logger.error(f"Strategy code stderr:\n{stderr}")
if proc.returncode != 0:
logger.error("Strategy code exited with non-zero exit code.")
return None
sys.exit(1)
return stdout
def get_state(min_round: int, args) -> Tuple[Optional[dict], float]:
"""Get game data from the server.
def get_state(min_round: int, args) -> dict:
"""Iteratively try to get game data from the server."""
Returns None and wait time if there was an error."""
state, wait_time = get_state_once()
while state is None:
time.sleep(wait_time)
state, wait_time = get_state_once()
try:
r = requests.get(f"{args.server}/api/state", params={
"game": args.game,
"token": args.token,
"min_round": min_round
})
# retry later if there was an error
except requests.exceptions.RequestException as e:
logger.warning(f"Request error: {e}")
return None, TIME_BEFORE_RETRY
if not r.ok:
logger.warning(f"Server error: {r.status_code} {r.reason}")
return None, TIME_BEFORE_RETRY
state = r.json()
# also retry if the server is not willing to give us the state yet
if state["status"] == "waiting":
logger.info("Server is busy.")
return None, state["wait"]
if state["status"] == "too_early":
logger.info("Round didn't start yet.")
def get_state_once() -> Tuple[Optional[dict], float]:
try:
res = requests.get(f"{args.server}/api/state", params={
"game": args.game,
"token": args.token,
"min_round": min_round
})
except requests.exceptions.RequestException as e:
# server might be down, retry later
logger.warning(f"Request error: {e}")
return None, TIME_BEFORE_RETRY
if not res.ok:
logger.error(f"Server error: {res.status_code} {res.reason}")
log_server_error(res)
sys.exit(1)
state = res.json()
if state["status"] == "ok":
return state, 0
# retry after some time
if state["status"] == "waiting":
logger.info("Server is busy.")
if state["status"] == "too_early":
logger.info("Round didn't start yet.")
return None, state["wait"]
logger.info("Received new state.")
return state, 0
def send_turn(turn: dict, round: int, args) -> bool:
"""Send turn to the server.
def send_turn(turn: dict, round: int, args) -> None:
"""Iteratively try to send turn to the server."""
Returns True if the server received the request."""
while not send_turn_once():
time.sleep(TIME_BEFORE_RETRY)
try:
r = requests.post(
f"{args.server}/api/action",
params={
"game": args.game,
"token": args.token,
"round": round
},
json=turn
)
except requests.exceptions.RequestException as e:
logger.warning(f"Request error: {e}")
return False
if not r.ok:
logger.warning(f"Server error: {r.status_code} {r.reason}")
return False
# print errors
# because such errors are caused by the submitted turn,
# retrying will not help, so return True
response = r.json()
if response["status"] == "ok":
logger.info("Turn accepted.")
elif response["status"] == "too_late":
logger.error("Turn submitted too late.")
elif response["status"] == "error":
logger.error(f"Turn error: {response['description']}")
elif response["status"] == "warning":
member_warns = [
f" {member['id']}: {member['description']}"
for member in response["members"]
]
logger.warn("Member warnings:\n" + "\n".join(member_warns))
return True
def send_turn_once() -> bool:
"""Returns True if server answered."""
try:
res = requests.post(
f"{args.server}/api/action",
params={
"game": args.game,
"token": args.token,
"round": round
},
json=turn
)
except requests.exceptions.RequestException as e:
# server might be down, retry later
logger.warning(f"Request error: {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
response = res.json()
if response["status"] == "ok":
logger.info("Turn accepted.")
elif response["status"] == "too_late":
logger.error("Turn submitted too late.")
elif response["status"] == "error":
logger.error(f"Turn error: {response['description']}")
elif response["status"] == "warning":
member_warns = [
f" {member['id']}: {member['description']}"
for member in response["members"]
]
logger.warning(
"Member warnings:\n"
"\n".join(member_warns)
)
return True
def log_server_error(res: requests.Response) -> None:
logger.error(
f"Server error: {res.status_code} {res.reason}\n"
f"{res['description']}"
)
if __name__ == "__main__":

Loading…
Cancel
Save