ucbc-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.3
2
+ Name: ucbc-sdk
3
+ Version: 0.1.0
4
+ Summary: UCalgary Battlecode: the `ucbc` package a bot imports, one handle per game
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Typing :: Typed
7
+ Requires-Python: >=3.12
8
+ Project-URL: Homepage, https://ucbc.andrewheschl.ca
9
+ Description-Content-Type: text/markdown
10
+
11
+ # ucbc-sdk
12
+
13
+ UCalgary Battlecode: the pure-Python `ucbc` package a bot imports, one typed handle per
14
+ game. `pip install ucbc` brings it along with the engine and the `ucbc` command (Linux);
15
+ install `ucbc-sdk` alone for types and completion on any platform.
16
+
17
+ ```python
18
+ from ucbc.games.tictactoe import TicTacToeHandle
19
+
20
+
21
+ def step(handle: TicTacToeHandle) -> None:
22
+ handle.place(*handle.empty_cells()[0])
23
+ ```
24
+
25
+ https://ucbc.andrewheschl.ca
@@ -0,0 +1,15 @@
1
+ # ucbc-sdk
2
+
3
+ UCalgary Battlecode: the pure-Python `ucbc` package a bot imports, one typed handle per
4
+ game. `pip install ucbc` brings it along with the engine and the `ucbc` command (Linux);
5
+ install `ucbc-sdk` alone for types and completion on any platform.
6
+
7
+ ```python
8
+ from ucbc.games.tictactoe import TicTacToeHandle
9
+
10
+
11
+ def step(handle: TicTacToeHandle) -> None:
12
+ handle.place(*handle.empty_cells()[0])
13
+ ```
14
+
15
+ https://ucbc.andrewheschl.ca
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.8,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "ucbc-sdk"
7
+ version = "0.1.0"
8
+ description = "UCalgary Battlecode: the `ucbc` package a bot imports, one handle per game"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = []
12
+ classifiers = ["Programming Language :: Python :: 3", "Typing :: Typed"]
13
+
14
+ [project.urls]
15
+ Homepage = "https://ucbc.andrewheschl.ca"
16
+
17
+ [tool.uv.build-backend]
18
+ module-name = "ucbc"
19
+ module-root = ""
@@ -0,0 +1,3 @@
1
+ """UCalgary Battlecode: what a bot sees. ``ucbc.handle`` is the game-agnostic
2
+ handle, ``ucbc.games.<game>`` the typed one a bot's ``step`` receives. Running
3
+ matches is ``ucbc_engine``'s job."""
@@ -0,0 +1 @@
1
+ """One module per game. Each defines ``HANDLE``, the class a bot's ``step`` receives."""
@@ -0,0 +1,27 @@
1
+ """Tic-tac-toe as a bot sees it. A bot's ``step`` receives a :class:`TicTacToeHandle`:
2
+ the generated queries and actions plus a few conveniences."""
3
+
4
+ from ucbc.games.tictactoe._api import BoardView, Cell, Placed, TicTacToeApi
5
+
6
+ __all__ = ["HANDLE", "BoardView", "Cell", "Placed", "TicTacToeHandle"]
7
+
8
+
9
+ class TicTacToeHandle(TicTacToeApi):
10
+ def cell(self, row: int, col: int) -> Cell:
11
+ return self.board().cells[row * 3 + col]
12
+
13
+ def empty_cells(self) -> list[tuple[int, int]]:
14
+ return [(i // 3, i % 3) for i, c in enumerate(self.board().cells) if c == Cell.EMPTY]
15
+
16
+ @property
17
+ def me(self) -> Cell:
18
+ """The mark this bot plays."""
19
+ return self.board().you
20
+
21
+ @property
22
+ def turn(self) -> int:
23
+ """Marks placed so far this set."""
24
+ return self.board().turn
25
+
26
+
27
+ HANDLE = TicTacToeHandle
@@ -0,0 +1,66 @@
1
+ """Generated by `ucbc-dev gen-sdk` from the Rust types of the `tictactoe` game. Do not edit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Any, Self
8
+
9
+ from ucbc.handle import Handle
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class BoardView:
14
+ """What a bot sees when it asks for the board."""
15
+
16
+ cells: list[Cell]
17
+ you: Cell
18
+ """The mark this bot plays."""
19
+ to_move: Cell
20
+ turn: int
21
+ """Marks placed so far this set."""
22
+
23
+ @classmethod
24
+ def _from(cls, d: dict[str, Any]) -> Self:
25
+ return cls(
26
+ cells=[Cell(v) for v in d["cells"]],
27
+ you=Cell(d["you"]),
28
+ to_move=Cell(d["to_move"]),
29
+ turn=d["turn"],
30
+ )
31
+
32
+
33
+ class Cell(str, Enum):
34
+ EMPTY = "empty"
35
+ X = "x"
36
+ O = "o"
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Placed:
41
+ """The mark that was placed."""
42
+
43
+ row: int
44
+ col: int
45
+ mark: Cell
46
+
47
+ @classmethod
48
+ def _from(cls, d: dict[str, Any]) -> Self:
49
+ return cls(
50
+ row=d["row"],
51
+ col=d["col"],
52
+ mark=Cell(d["mark"]),
53
+ )
54
+
55
+
56
+ class TicTacToeApi(Handle):
57
+ """Queries and actions of the `tictactoe` game, one method each."""
58
+
59
+ def board(self) -> BoardView:
60
+ """The board and whose turn it is."""
61
+ return BoardView._from(self._query({"type": "board"}))
62
+
63
+ def place(self, row: int, col: int) -> Placed:
64
+ """Place this bot's mark. Refused if the cell is taken or a mark was already
65
+ placed this step."""
66
+ return Placed._from(self._act({"type": "place", "row": row, "col": col}))
@@ -0,0 +1,86 @@
1
+ """The game-agnostic side of what a bot's ``step(handle)`` receives: a handle to the
2
+ one game the engine runs."""
3
+
4
+ from collections.abc import Callable
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ __all__ = [
9
+ "ActionError",
10
+ "BridgeFn",
11
+ "Handle",
12
+ "Identity",
13
+ "QueryError",
14
+ "SetOver",
15
+ ]
16
+
17
+ BridgeFn = Callable[[str, dict[str, Any]], dict[str, Any]]
18
+ """Sends one ``"query"`` or ``"act"`` payload to the engine and returns its reply."""
19
+
20
+
21
+ class QueryError(Exception):
22
+ """The game refused a query."""
23
+
24
+
25
+ class ActionError(Exception):
26
+ """The game refused an action. Nothing is forfeited; try something else."""
27
+
28
+
29
+ class SetOver(ActionError):
30
+ """The set is already over."""
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class Identity:
35
+ bot_id: int
36
+ team: int
37
+ team_name: str
38
+ seed: int
39
+ game: str
40
+
41
+
42
+ class Handle:
43
+ """Wraps the engine's bridge. Each game subclasses this with typed methods."""
44
+
45
+ def __init__(self, identity: Identity, bridge: BridgeFn) -> None:
46
+ self._identity = identity
47
+ self._bridge = bridge
48
+ self.set_index = 0
49
+ self.tick = 0
50
+ self.memory: dict[str, Any] = {}
51
+ """Persists across this bot's steps within a set."""
52
+
53
+ @property
54
+ def bot_id(self) -> int:
55
+ return self._identity.bot_id
56
+
57
+ @property
58
+ def team(self) -> int:
59
+ return self._identity.team
60
+
61
+ @property
62
+ def team_name(self) -> str:
63
+ return self._identity.team_name
64
+
65
+ @property
66
+ def seed(self) -> int:
67
+ """Per bot, per set; stable for a given match seed."""
68
+ return self._identity.seed
69
+
70
+ def _call(self, name: str, payload: dict[str, Any]) -> dict[str, Any]:
71
+ reply = self._bridge(name, payload)
72
+ if "err" in reply:
73
+ kind, message = reply["err"]["kind"], reply["err"]["message"]
74
+ if kind == "query":
75
+ raise QueryError(message)
76
+ if kind == "set_over":
77
+ raise SetOver(message)
78
+ raise ActionError(message)
79
+ response: dict[str, Any] = reply["ok"]
80
+ return response
81
+
82
+ def _query(self, payload: dict[str, Any]) -> dict[str, Any]:
83
+ return self._call("query", payload)
84
+
85
+ def _act(self, payload: dict[str, Any]) -> dict[str, Any]:
86
+ return self._call("act", payload)
File without changes