make blokus in rust

This commit is contained in:
Noa Aarts 2025-12-05 15:10:02 +01:00
parent 98f161c620
commit eca2b4acb8
Signed by: noa
GPG key ID: 1850932741EFF672
11 changed files with 706 additions and 156 deletions

72
game/.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
}
}
}