"""python3 sudoku.py http://localhost:3000 — standard library only. newGame() -> Game; game.board[y][x]; game.move(x, y, n) -> Game. x = column, y = row (0–8); n = value (1–9). """ import json import sys from urllib.error import HTTPError from urllib.parse import quote from urllib.request import Request, urlopen class ApiError(Exception): def __init__(self, status, details): self.status, self.details = status, details super().__init__(f"HTTP {status}: {details}") def request(base_url, path, method="GET", body=None): req = Request( base_url.rstrip("/") + path, data=None if body is None else json.dumps(body).encode(), headers={"Content-Type": "application/json"}, method=method, ) try: with urlopen(req, timeout=10) as response: return json.load(response) except HTTPError as error: details = error.read().decode() try: details = json.loads(details) except ValueError: pass raise ApiError(error.code, details) from error class Game: def __init__(self, game_id, board, base_url): self.id, self.board, self.base_url = game_id, board, base_url def move(self, x, y, n): if (any(type(v) is not int for v in (x, y, n)) or not 0 <= x <= 8 or not 0 <= y <= 8 or not 1 <= n <= 9): raise ValueError("x and y must be 0–8; n must be 1–9") data = request(self.base_url, f"/board/{quote(self.id, safe='')}/turn", "PATCH", {"row": y, "col": x, "value": n}) self.board = data["board"] return self def newGame(base_url="http://localhost:3000"): game_id = request(base_url, "/board", "POST")["id"] data = request(base_url, f"/board/{quote(game_id, safe='')}") return Game(game_id, data["board"], base_url) if __name__ == "__main__": game = newGame(sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000") game = game.move(2, 1, 5) print(json.dumps(game.board))