David Klement
2 years ago
3 changed files with 153 additions and 0 deletions
@ -0,0 +1,78 @@ |
|||||
|
import logging |
||||
|
import json |
||||
|
import requests |
||||
|
from typing import Optional, Tuple |
||||
|
|
||||
|
|
||||
|
TIME_BEFORE_RETRY = 2.0 |
||||
|
logger = logging.Logger("client") |
||||
|
|
||||
|
|
||||
|
def get_state(min_round: int, args) -> Tuple[Optional[dict], float]: |
||||
|
"""Returns None and wait time if there was an error.""" |
||||
|
|
||||
|
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("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 |
||||
|
|
||||
|
# 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.") |
||||
|
return None, state["wait"] |
||||
|
|
||||
|
state = json.loads(r.json()) |
||||
|
logger.info("Received new state.") |
||||
|
return state, 0 |
||||
|
|
||||
|
|
||||
|
def send_turn(turn: dict, round: int, args) -> bool: |
||||
|
"""Returns True if the server received the request.""" |
||||
|
|
||||
|
try: |
||||
|
r = requests.post( |
||||
|
f"{args.server}/api/state", |
||||
|
params={ |
||||
|
"game": args.game, |
||||
|
"token": args.token, |
||||
|
"round": round |
||||
|
}, |
||||
|
json=json.dumps(turn) |
||||
|
) |
||||
|
except requests.exceptions.RequestException as e: |
||||
|
logger.warning("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 = json.loads(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_errors = [ |
||||
|
f" {member}: {error}" |
||||
|
for member, error in response["errors"].items() |
||||
|
] |
||||
|
logger.error(f"Member errors:\n{'\n'.join(member_errors)}") |
||||
|
|
||||
|
return True |
@ -0,0 +1,74 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
import argparse |
||||
|
import time |
||||
|
from client import get_state, send_turn, logger, TIME_BEFORE_RETRY |
||||
|
|
||||
|
|
||||
|
parser = argparse.ArgumentParser() |
||||
|
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("--token", type=str, default=None, help="API token.") |
||||
|
parser.add_argument("--log_level", type=str, default="WARNING", choices=["ERROR", "WARNING", "INFO"]) |
||||
|
# you can add your own arguments, the strategy function will get them |
||||
|
|
||||
|
|
||||
|
def strategy(team_id: int, state: dict, args: argparse.Namespace) -> dict: |
||||
|
"""Finds the best move for the given state. |
||||
|
|
||||
|
This function is called every round. |
||||
|
It should return a dict with actions for each of team's soldiers. |
||||
|
|
||||
|
State format: |
||||
|
```yaml |
||||
|
{ |
||||
|
map: [[{ |
||||
|
home_for_team: Optional[int], |
||||
|
occupied_by_team: Optional[int], |
||||
|
hill: bool, |
||||
|
members: [{ |
||||
|
type: "soldier", |
||||
|
team: int, |
||||
|
id: int, |
||||
|
remaining_rounds: int |
||||
|
}] |
||||
|
}]] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Turn format: |
||||
|
```yaml |
||||
|
{ |
||||
|
members: [{ |
||||
|
id: int, |
||||
|
action: Optional[str] |
||||
|
# None, "left", "right", "up", "down" |
||||
|
}] |
||||
|
} |
||||
|
``` |
||||
|
""" |
||||
|
return {} |
||||
|
|
||||
|
|
||||
|
def main(args: argparse.Namespace): |
||||
|
min_round = 0 |
||||
|
logger.setLevel(args.log_level) |
||||
|
|
||||
|
while True: |
||||
|
state, wait_time = get_state(min_round) |
||||
|
if state is None: |
||||
|
# retry later |
||||
|
time.sleep(wait_time) |
||||
|
continue |
||||
|
|
||||
|
turn = strategy(state["team_id"], state["state"], args) |
||||
|
|
||||
|
round = state["round"] |
||||
|
while not send_turn(turn, round): |
||||
|
# if there was a connection error, retry later |
||||
|
time.sleep(TIME_BEFORE_RETRY) |
||||
|
min_round = round + 1 |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
args = parser.parse_args() |
||||
|
main(args) |
@ -0,0 +1 @@ |
|||||
|
requests |
Loading…
Reference in new issue