make blokus in rust
This commit is contained in:
parent
98f161c620
commit
eca2b4acb8
11 changed files with 706 additions and 156 deletions
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue