make blokus in rust
This commit is contained in:
parent
98f161c620
commit
eca2b4acb8
11 changed files with 706 additions and 156 deletions
1
.envrc
1
.envrc
|
|
@ -1 +1,2 @@
|
||||||
use flake
|
use flake
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
|
||||||
177
blokus.py
177
blokus.py
|
|
@ -1,159 +1,30 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from typing import Any
|
|
||||||
import numpy as np
|
|
||||||
import random
|
import random
|
||||||
|
import game
|
||||||
|
|
||||||
BOARD_SIZE = 14
|
BOARD_SIZE = 14
|
||||||
|
|
||||||
|
|
||||||
def make_board():
|
tiles = game.game_tiles()
|
||||||
a = np.array([[0 for i in range(BOARD_SIZE)] for j in range(BOARD_SIZE)])
|
|
||||||
a[4, 4] = -1
|
|
||||||
a[9, 9] = -1
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
tiles = [
|
|
||||||
np.array([[1]]),
|
|
||||||
np.array([[1], [1]]),
|
|
||||||
np.array([[1], [1], [1]]),
|
|
||||||
np.array([[1, 0], [1, 1]]),
|
|
||||||
np.array([[1], [1], [1], [1]]),
|
|
||||||
np.array([[1, 0], [1, 0], [1, 1]]),
|
|
||||||
np.array([[1, 0], [1, 1], [1, 0]]),
|
|
||||||
np.array([[1, 1], [1, 1]]),
|
|
||||||
np.array([[1, 1, 0], [0, 1, 1]]),
|
|
||||||
np.array([[1], [1], [1], [1], [1]]),
|
|
||||||
np.array([[1, 0], [1, 0], [1, 0], [1, 1]]),
|
|
||||||
np.array([[1, 0], [1, 0], [1, 1], [0, 1]]),
|
|
||||||
np.array([[1, 0], [1, 1], [1, 1]]),
|
|
||||||
np.array([[1, 1], [1, 0], [1, 1]]),
|
|
||||||
np.array([[1, 0], [1, 1], [1, 0], [1, 0]]),
|
|
||||||
np.array([[0, 1, 0], [0, 1, 0], [1, 1, 1]]),
|
|
||||||
np.array([[1, 0, 0], [1, 0, 0], [1, 1, 1]]),
|
|
||||||
np.array([[1, 1, 0], [0, 1, 1], [0, 0, 1]]),
|
|
||||||
np.array([[1, 0, 0], [1, 1, 1], [0, 0, 1]]),
|
|
||||||
np.array([[1, 0, 0], [1, 1, 1], [0, 1, 0]]),
|
|
||||||
np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_permutations(which_tiles: list[int]) -> list[tuple[int, np.ndarray]]:
|
|
||||||
permutations: list[tuple[int, np.ndarray]] = []
|
|
||||||
|
|
||||||
for i, tile in enumerate(tiles):
|
|
||||||
if i not in which_tiles:
|
|
||||||
continue
|
|
||||||
|
|
||||||
rots = [np.rot90(tile, k) for k in range(4)]
|
|
||||||
flips = [np.flip(r, axis=1) for r in rots] # flip horizontally
|
|
||||||
all_orients = rots + flips # 8 orientations
|
|
||||||
|
|
||||||
seen: set[tuple[Any, bytes]] = set()
|
|
||||||
for t in all_orients:
|
|
||||||
key = (t.shape, t.tobytes())
|
|
||||||
if key not in seen:
|
|
||||||
seen.add(key)
|
|
||||||
permutations.append((i, t))
|
|
||||||
|
|
||||||
return permutations
|
|
||||||
|
|
||||||
|
|
||||||
def can_place(
|
def can_place(
|
||||||
board: np.ndarray, tile: np.ndarray, player: int
|
board: game.Board, tile: game.Tile, player: game.Player
|
||||||
) -> list[tuple[int, int]]:
|
) -> list[tuple[int, int]]:
|
||||||
placements: list[tuple[int, int]] = []
|
placements = []
|
||||||
has_minus_one = False
|
placements.extend
|
||||||
for x in range(BOARD_SIZE):
|
|
||||||
for y in range(BOARD_SIZE):
|
|
||||||
if board[x, y] == -1:
|
|
||||||
has_minus_one = True
|
|
||||||
with np.nditer(tile, flags=["multi_index"]) as it:
|
|
||||||
for v in it:
|
|
||||||
if v == 1:
|
|
||||||
(i, j) = it.multi_index
|
|
||||||
if x + i >= BOARD_SIZE:
|
|
||||||
break
|
|
||||||
if y + j >= BOARD_SIZE:
|
|
||||||
break
|
|
||||||
if board[x + i][y + j] > 0:
|
|
||||||
break
|
|
||||||
if x + i - 1 >= 0 and board[x + i - 1][y + j] == player:
|
|
||||||
break
|
|
||||||
if y + j - 1 >= 0 and board[x + i][y + j - 1] == player:
|
|
||||||
break
|
|
||||||
if x + i + 1 < BOARD_SIZE and board[x + i + 1][y + j] == player:
|
|
||||||
break
|
|
||||||
if y + j + 1 < BOARD_SIZE and board[x + i][y + j + 1] == player:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
placements.append((x, y))
|
|
||||||
final: list[tuple[int, int]] = []
|
|
||||||
if has_minus_one:
|
|
||||||
for x, y in placements:
|
|
||||||
with np.nditer(tile, flags=["multi_index"]) as it:
|
|
||||||
for v in it:
|
|
||||||
(i, j) = it.multi_index
|
|
||||||
if v == 1 and board[x + i, y + j] == -1:
|
|
||||||
final.append((x, y))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
for x, y in placements:
|
|
||||||
with np.nditer(tile, flags=["multi_index"]) as it:
|
|
||||||
for v in it:
|
|
||||||
(i, j) = it.multi_index
|
|
||||||
if (
|
|
||||||
x + i + 1 < BOARD_SIZE
|
|
||||||
and y + j + 1 < BOARD_SIZE
|
|
||||||
and board[x + i + 1][y + j + 1] == player
|
|
||||||
):
|
|
||||||
final.append((x, y))
|
|
||||||
break
|
|
||||||
if (
|
|
||||||
x + i + 1 < BOARD_SIZE
|
|
||||||
and y + j - 1 >= 0
|
|
||||||
and board[x + i + 1][y + j - 1] == player
|
|
||||||
):
|
|
||||||
final.append((x, y))
|
|
||||||
break
|
|
||||||
if (
|
|
||||||
x + i - 1 >= 0
|
|
||||||
and y + j + 1 < BOARD_SIZE
|
|
||||||
and board[x + i - 1][y + j + 1] == player
|
|
||||||
):
|
|
||||||
final.append((x, y))
|
|
||||||
break
|
|
||||||
if (
|
|
||||||
x + i - 1 >= 0
|
|
||||||
and y + j - 1 >= 0
|
|
||||||
and board[x + i - 1][y + j - 1] == player
|
|
||||||
):
|
|
||||||
final.append((x, y))
|
|
||||||
break
|
|
||||||
return final
|
|
||||||
|
|
||||||
|
|
||||||
def do_placement(
|
def print_game_state(game_state: tuple[game.Board, list[int], list[int]]):
|
||||||
tidx: int,
|
|
||||||
tile: np.ndarray,
|
|
||||||
placement: tuple[int, int],
|
|
||||||
game_state: tuple[np.ndarray, list[int], list[int]],
|
|
||||||
player: int,
|
|
||||||
):
|
|
||||||
assert player > 0
|
|
||||||
(x, y) = placement
|
|
||||||
with np.nditer(tile, flags=["multi_index"]) as it:
|
|
||||||
for v in it:
|
|
||||||
(i, j) = it.multi_index
|
|
||||||
if v == 1:
|
|
||||||
game_state[0][x + i, y + j] = player
|
|
||||||
game_state[player].remove(tidx)
|
|
||||||
|
|
||||||
|
|
||||||
def print_game_state(game_state: tuple[np.ndarray, list[int], list[int]]):
|
|
||||||
(board, p1tiles, p2tiles) = game_state
|
(board, p1tiles, p2tiles) = game_state
|
||||||
|
|
||||||
for row in board:
|
barr = []
|
||||||
|
for i in range(BOARD_SIZE):
|
||||||
|
barr.append([])
|
||||||
|
for j in range(BOARD_SIZE):
|
||||||
|
barr[i].append(board[(j, i)])
|
||||||
|
|
||||||
|
for row in barr:
|
||||||
print(
|
print(
|
||||||
"".join(
|
"".join(
|
||||||
[
|
[
|
||||||
|
|
@ -169,7 +40,7 @@ def print_game_state(game_state: tuple[np.ndarray, list[int], list[int]]):
|
||||||
|
|
||||||
|
|
||||||
game_state = (
|
game_state = (
|
||||||
make_board(),
|
game.Board(),
|
||||||
[i for i in range(21)],
|
[i for i in range(21)],
|
||||||
[i for i in range(21)],
|
[i for i in range(21)],
|
||||||
)
|
)
|
||||||
|
|
@ -179,12 +50,15 @@ playing = True
|
||||||
player = 1
|
player = 1
|
||||||
while playing:
|
while playing:
|
||||||
moves = []
|
moves = []
|
||||||
assert player > 0
|
assert player == 1 or player == 2
|
||||||
for tidx, tile in get_permutations(game_state[player]):
|
gp = game.Player.P1 if player == 1 else game.Player.P2
|
||||||
for placement in can_place(game_state[0], tile, player):
|
for tile_idx in game_state[player]:
|
||||||
moves.append((tidx, tile, placement))
|
tile = tiles[tile_idx]
|
||||||
|
perms = tile.permutations()
|
||||||
|
for perm in perms:
|
||||||
|
plcs = game_state[0].tile_placements(perm, gp)
|
||||||
|
moves.extend((tile_idx, perm, plc) for plc in plcs)
|
||||||
|
|
||||||
print_game_state(game_state)
|
|
||||||
print(f"player {player} has {len(moves)} options")
|
print(f"player {player} has {len(moves)} options")
|
||||||
|
|
||||||
if len(moves) == 0:
|
if len(moves) == 0:
|
||||||
|
|
@ -193,7 +67,12 @@ while playing:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
(tidx, tile, placement) = random.choice(moves)
|
(tidx, tile, placement) = random.choice(moves)
|
||||||
do_placement(tidx, tile, placement, game_state, player)
|
print(
|
||||||
|
f"player {player} is placing the following tile with index {tidx} at {placement}\n{tile}"
|
||||||
|
)
|
||||||
|
game_state[0].place(tile, placement, gp)
|
||||||
|
game_state[player].remove(tidx)
|
||||||
|
print_game_state(game_state)
|
||||||
|
|
||||||
if player == 1:
|
if player == 1:
|
||||||
player = 2
|
player = 2
|
||||||
|
|
|
||||||
70
flake.lock
generated
70
flake.lock
generated
|
|
@ -1,6 +1,56 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"fix-py": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1752329544,
|
||||||
|
"narHash": "sha256-LtZyywexRDB5FFmEU6A1e5tMIb0MX8/xxpjCHonUzYE=",
|
||||||
|
"owner": "GuillaumeDesforges",
|
||||||
|
"repo": "fix-python",
|
||||||
|
"rev": "248e2ea9620faee9b8a2ae10e12320de2a819fe9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "GuillaumeDesforges",
|
||||||
|
"repo": "fix-python",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689068808,
|
||||||
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "flake-utils",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1682786779,
|
||||||
|
"narHash": "sha256-m7QFzPS/CE8hbkbIVK4UStihAQMtczr0vSpOgETOM1g=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "08e4dc3a907a6dfec8bb3bbf1540d8abbffea22b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1764167966,
|
"lastModified": 1764167966,
|
||||||
"narHash": "sha256-nXv6xb7cq+XpjBYIjWEGTLCqQetxJu6zvVlrqHMsCOA=",
|
"narHash": "sha256-nXv6xb7cq+XpjBYIjWEGTLCqQetxJu6zvVlrqHMsCOA=",
|
||||||
|
|
@ -18,8 +68,9 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs",
|
"fix-py": "fix-py",
|
||||||
"systems": "systems"
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"systems": "systems_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
|
@ -36,6 +87,21 @@
|
||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
11
flake.nix
11
flake.nix
|
|
@ -2,10 +2,11 @@
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
systems.url = "github:nix-systems/default";
|
systems.url = "github:nix-systems/default";
|
||||||
|
fix-py.url = "github:GuillaumeDesforges/fix-python";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{ nixpkgs, ... }:
|
{ nixpkgs, fix-py, ... }:
|
||||||
let
|
let
|
||||||
eachSystem =
|
eachSystem =
|
||||||
f:
|
f:
|
||||||
|
|
@ -15,10 +16,10 @@
|
||||||
devShells = eachSystem (pkgs: {
|
devShells = eachSystem (pkgs: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
(pkgs.python3.withPackages (ppkgs: [
|
pkgs.cargo
|
||||||
ppkgs.numpy
|
pkgs.rustc
|
||||||
ppkgs.torch
|
pkgs.stdenv.cc.cc.lib
|
||||||
]))
|
fix-py.packages.${pkgs.system}.default
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
72
game/.gitignore
vendored
Normal file
72
game/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
/target
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
.pytest_cache/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
include/
|
||||||
|
man/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
pip-selfcheck.json
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
|
# Rope
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Pyenv
|
||||||
|
.python-version
|
||||||
173
game/Cargo.lock
generated
Normal file
173
game/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "game"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pyo3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.177"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyo3"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
|
||||||
|
dependencies = [
|
||||||
|
"indoc",
|
||||||
|
"libc",
|
||||||
|
"memoffset",
|
||||||
|
"once_cell",
|
||||||
|
"portable-atomic",
|
||||||
|
"pyo3-build-config",
|
||||||
|
"pyo3-ffi",
|
||||||
|
"pyo3-macros",
|
||||||
|
"unindent",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyo3-build-config"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyo3-ffi"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pyo3-build-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyo3-macros"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"pyo3-macros-backend",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyo3-macros-backend"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"pyo3-build-config",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.111"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unindent"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
||||||
12
game/Cargo.toml
Normal file
12
game/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "game"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[lib]
|
||||||
|
name = "game"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = "0.25.0"
|
||||||
15
game/pyproject.toml
Normal file
15
game/pyproject.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1.9,<2.0"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "game"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
]
|
||||||
|
dynamic = ["version"]
|
||||||
|
[tool.maturin]
|
||||||
|
features = ["pyo3/extension-module"]
|
||||||
323
game/src/lib.rs
Normal file
323
game/src/lib.rs
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
mod game {
|
||||||
|
use pyo3::{exceptions::PyIndexError, prelude::*, types::PySequence};
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
};
|
||||||
|
const BOARD_SIZE: usize = 14;
|
||||||
|
const START_SQUARES: [(usize, usize); 2] = [(4, 4), (9, 9)];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Square {
|
||||||
|
Start,
|
||||||
|
Empty,
|
||||||
|
Player(Player),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Square {
|
||||||
|
fn is_player(&self, player: Player) -> bool {
|
||||||
|
match self {
|
||||||
|
Square::Player(p) if *p == player => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[pyclass]
|
||||||
|
enum Player {
|
||||||
|
P1,
|
||||||
|
P2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[pyclass(str)]
|
||||||
|
struct Tile {
|
||||||
|
parts: HashSet<(usize, usize)>,
|
||||||
|
size: (usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Tile {
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
let (xs, ys) = self.size;
|
||||||
|
let mut vvec = Vec::new();
|
||||||
|
for _ in 0..=ys {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for _ in 0..=xs {
|
||||||
|
vec.push(" ")
|
||||||
|
}
|
||||||
|
vvec.push(vec)
|
||||||
|
}
|
||||||
|
for &(x, y) in self.parts.iter() {
|
||||||
|
vvec[y][x] = "██"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the grid
|
||||||
|
for row in vvec {
|
||||||
|
for cell in row {
|
||||||
|
write!(fmt, "{}", cell)?;
|
||||||
|
}
|
||||||
|
writeln!(fmt)?; // Newline per row
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
fn game_tiles() -> Vec<Tile> {
|
||||||
|
vec![
|
||||||
|
Tile::new(vec![vec![1]]),
|
||||||
|
Tile::new(vec![vec![1], vec![1]]),
|
||||||
|
Tile::new(vec![vec![1], vec![1], vec![1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1], vec![1], vec![1], vec![1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 0], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 1], vec![1, 0]]),
|
||||||
|
Tile::new(vec![vec![1, 1], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 1, 0], vec![0, 1, 1]]),
|
||||||
|
Tile::new(vec![vec![1], vec![1], vec![1], vec![1], vec![1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 0], vec![1, 0], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 0], vec![1, 1], vec![0, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 1], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 1], vec![1, 0], vec![1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0], vec![1, 1], vec![1, 0], vec![1, 0]]),
|
||||||
|
Tile::new(vec![vec![0, 1, 0], vec![0, 1, 0], vec![1, 1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0, 0], vec![1, 0, 0], vec![1, 1, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 1, 0], vec![0, 1, 1], vec![0, 0, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0, 0], vec![1, 1, 1], vec![0, 0, 1]]),
|
||||||
|
Tile::new(vec![vec![1, 0, 0], vec![1, 1, 1], vec![0, 1, 0]]),
|
||||||
|
Tile::new(vec![vec![0, 1, 0], vec![1, 1, 1], vec![0, 1, 0]]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Tile {
|
||||||
|
#[new]
|
||||||
|
fn new(arr: Vec<Vec<usize>>) -> Self {
|
||||||
|
let mut parts = HashSet::new();
|
||||||
|
for (y, row) in arr.iter().enumerate() {
|
||||||
|
for (x, val) in row.iter().enumerate() {
|
||||||
|
if *val != 0 {
|
||||||
|
parts.insert((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let xs = parts
|
||||||
|
.iter()
|
||||||
|
.max_by(|x, y| x.0.cmp(&y.0))
|
||||||
|
.unwrap_or(&(0, 0))
|
||||||
|
.0;
|
||||||
|
let ys = parts
|
||||||
|
.iter()
|
||||||
|
.max_by(|x, y| x.1.cmp(&y.1))
|
||||||
|
.unwrap_or(&(0, 0))
|
||||||
|
.1;
|
||||||
|
Tile {
|
||||||
|
parts,
|
||||||
|
size: (xs, ys),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permutations(&self) -> Vec<Tile> {
|
||||||
|
let mut vfinal = Vec::new();
|
||||||
|
for val in vec![
|
||||||
|
self.clone(),
|
||||||
|
self.rotate(),
|
||||||
|
self.rotate().rotate(),
|
||||||
|
self.rotate().rotate().rotate(),
|
||||||
|
self.flip(),
|
||||||
|
self.rotate().flip(),
|
||||||
|
self.rotate().rotate().flip(),
|
||||||
|
self.rotate().rotate().rotate().flip(),
|
||||||
|
] {
|
||||||
|
if !vfinal.contains(&val) {
|
||||||
|
vfinal.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vfinal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flip(&self) -> Self {
|
||||||
|
let (xs, ys) = self.size;
|
||||||
|
let v = self.parts.iter().map(|&(i, j)| (xs - i, j)).collect();
|
||||||
|
Tile {
|
||||||
|
parts: v,
|
||||||
|
size: (xs, ys),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(&self) -> Self {
|
||||||
|
let (xs, ys) = self.size;
|
||||||
|
let v = self
|
||||||
|
.parts
|
||||||
|
.iter()
|
||||||
|
.map(|&(i, j)| (j, self.size.0 - i))
|
||||||
|
.collect();
|
||||||
|
Tile {
|
||||||
|
parts: v,
|
||||||
|
size: (ys, xs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Board {
|
||||||
|
tiles: [[Square; BOARD_SIZE]; BOARD_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<(usize, usize)> for Board {
|
||||||
|
type Output = Square;
|
||||||
|
|
||||||
|
fn index(&self, index: (usize, usize)) -> &Self::Output {
|
||||||
|
&self.tiles[index.1][index.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<(usize, usize)> for Board {
|
||||||
|
fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
|
||||||
|
&mut self.tiles[index.1][index.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Board {
|
||||||
|
#[new]
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut board = Board {
|
||||||
|
tiles: [[Square::Empty; BOARD_SIZE]; BOARD_SIZE],
|
||||||
|
};
|
||||||
|
for sq in START_SQUARES {
|
||||||
|
board[sq] = Square::Start
|
||||||
|
}
|
||||||
|
board
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __len__(&self) -> PyResult<usize> {
|
||||||
|
Ok(BOARD_SIZE * BOARD_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getitem__(&self, idx: (isize, isize)) -> PyResult<usize> {
|
||||||
|
let (x, y) = idx;
|
||||||
|
if x >= BOARD_SIZE as isize {
|
||||||
|
return Err(PyIndexError::new_err("x is larger than BOARD_SIZE"));
|
||||||
|
}
|
||||||
|
if y >= BOARD_SIZE as isize {
|
||||||
|
return Err(PyIndexError::new_err("y is larger than BOARD_SIZE"));
|
||||||
|
}
|
||||||
|
if x < -(BOARD_SIZE as isize) {
|
||||||
|
return Err(PyIndexError::new_err("x is smaller than -BOARD_SIZE"));
|
||||||
|
}
|
||||||
|
if y < -(BOARD_SIZE as isize) {
|
||||||
|
return Err(PyIndexError::new_err("y is smaller than -BOARD_SIZE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sq = self[(
|
||||||
|
((x + BOARD_SIZE as isize) % BOARD_SIZE as isize) as usize,
|
||||||
|
((y + BOARD_SIZE as isize) % BOARD_SIZE as isize) as usize,
|
||||||
|
)];
|
||||||
|
Ok(match sq {
|
||||||
|
Square::Start => 3,
|
||||||
|
Square::Empty => 0,
|
||||||
|
Square::Player(Player::P1) => 1,
|
||||||
|
Square::Player(Player::P2) => 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn place(&mut self, tile: Tile, pos: (usize, usize), player: Player) {
|
||||||
|
let (x, y) = pos;
|
||||||
|
for &(i, j) in tile.parts.iter() {
|
||||||
|
assert!(
|
||||||
|
self[(x + i, y + j)] == Square::Empty || self[(x + i, y + j)] == Square::Start,
|
||||||
|
"Can't put a tile in a place where it collides with another"
|
||||||
|
);
|
||||||
|
self[(x + i, y + j)] = Square::Player(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile_placements(&self, tile: Tile, player: Player) -> Vec<(usize, usize)> {
|
||||||
|
let mut starting = false;
|
||||||
|
for sq in START_SQUARES {
|
||||||
|
if self[sq] == Square::Start {
|
||||||
|
starting = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut possible_placements = Vec::new();
|
||||||
|
for y in 0..BOARD_SIZE {
|
||||||
|
for x in 0..BOARD_SIZE {
|
||||||
|
let mut can_place = true;
|
||||||
|
let mut on_start = false;
|
||||||
|
'inner: for &(i, j) in tile.parts.iter() {
|
||||||
|
if x + i >= BOARD_SIZE {
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if y + j >= BOARD_SIZE {
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if self[(x + i, y + j)] == Square::Start {
|
||||||
|
on_start = true;
|
||||||
|
}
|
||||||
|
if let Square::Player(_p) = self[(x + i, y + j)] {
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if x + i >= 1 && self[(x + i - 1, y + j)] == Square::Player(player) {
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if y + j >= 1 && self[(x + i, y + j - 1)] == Square::Player(player) {
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if x + i + 1 < BOARD_SIZE
|
||||||
|
&& self[(x + i + 1, y + j)] == Square::Player(player)
|
||||||
|
{
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
if y + j + 1 < BOARD_SIZE
|
||||||
|
&& self[(x + i, y + j + 1)] == Square::Player(player)
|
||||||
|
{
|
||||||
|
can_place = false;
|
||||||
|
break 'inner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if can_place {
|
||||||
|
if starting {
|
||||||
|
if on_start {
|
||||||
|
possible_placements.push((x, y));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for &(i, j) in tile.parts.iter() {
|
||||||
|
if (x + i + 1 < BOARD_SIZE
|
||||||
|
&& y + j + 1 < BOARD_SIZE
|
||||||
|
&& self[(x + i + 1, y + j + 1)].is_player(player))
|
||||||
|
|| (x + i + 1 < BOARD_SIZE
|
||||||
|
&& y + j >= 1
|
||||||
|
&& self[(x + i + 1, y + j - 1)].is_player(player))
|
||||||
|
|| (x + i >= 1
|
||||||
|
&& y + j + 1 < BOARD_SIZE
|
||||||
|
&& self[(x + i - 1, y + j + 1)].is_player(player))
|
||||||
|
|| (x + i >= 1
|
||||||
|
&& y + j >= 1
|
||||||
|
&& self[(x + i - 1, y + j - 1)].is_player(player))
|
||||||
|
{
|
||||||
|
possible_placements.push((x, y));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
possible_placements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
pyproject.toml
Normal file
4
pyproject.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[project]
|
||||||
|
name = "blokus"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
4
pyrightconfig.json
Normal file
4
pyrightconfig.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"venv": ".venv",
|
||||||
|
"venvPath": "./"
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue