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.
- ucbc_sdk-0.1.0/PKG-INFO +25 -0
- ucbc_sdk-0.1.0/README.md +15 -0
- ucbc_sdk-0.1.0/pyproject.toml +19 -0
- ucbc_sdk-0.1.0/ucbc/__init__.py +3 -0
- ucbc_sdk-0.1.0/ucbc/games/__init__.py +1 -0
- ucbc_sdk-0.1.0/ucbc/games/tictactoe/__init__.py +27 -0
- ucbc_sdk-0.1.0/ucbc/games/tictactoe/_api.py +66 -0
- ucbc_sdk-0.1.0/ucbc/handle.py +86 -0
- ucbc_sdk-0.1.0/ucbc/py.typed +0 -0
ucbc_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
ucbc_sdk-0.1.0/README.md
ADDED
|
@@ -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 @@
|
|
|
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
|