Add SRS shape implementations via build time code-gen
This commit is contained in:
parent
04484b38da
commit
c5ca155ad2
|
@ -2,6 +2,102 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mino"
|
name = "mino"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"mino-code-gen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mino-code-gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.105"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"mino"
|
"mino",
|
||||||
|
"mino-code-gen",
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "mino-code-gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
|
@ -0,0 +1,155 @@
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub fn compile_ruleset(in_path: impl AsRef<Path>, out_path: impl AsRef<Path>) -> Result<()> {
|
||||||
|
let mut in_file = std::fs::File::open(in_path.as_ref())
|
||||||
|
.with_context(|| format!("could not open input file {}", in_path.as_ref().display()))?;
|
||||||
|
let mut out_file = std::fs::File::create(out_path.as_ref())
|
||||||
|
.with_context(|| format!("could not open output file {}", out_path.as_ref().display()))?;
|
||||||
|
let rules_data: RulesData =
|
||||||
|
serde_json::from_reader(&mut in_file).context("error parsing input file")?;
|
||||||
|
compile(&rules_data)
|
||||||
|
.emit(&mut out_file)
|
||||||
|
.context("error writing to output file")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input data (JSON file)
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct RulesData {
|
||||||
|
shapes: BTreeMap<String, Vec<(i16, i16)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output data (Rust code)
|
||||||
|
struct RulesCode {
|
||||||
|
// establish consistent mapping from shape name to integer
|
||||||
|
shape_indices: HashMap<String, usize>,
|
||||||
|
extents: Extents<Vec<i16>>,
|
||||||
|
cells_bits: Vec<u16>,
|
||||||
|
cells_indices: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Extents<E> {
|
||||||
|
x0: E,
|
||||||
|
x1: E,
|
||||||
|
y0: E,
|
||||||
|
y1: E,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(rules: &RulesData) -> RulesCode {
|
||||||
|
let shape_indices: HashMap<String, usize> = rules
|
||||||
|
.shapes
|
||||||
|
.keys()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, sh)| (sh.clone(), i))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let len_per_shape_rot = shape_indices.len() * 4;
|
||||||
|
|
||||||
|
let mut extents = Extents {
|
||||||
|
x0: vec![i16::MAX; len_per_shape_rot],
|
||||||
|
x1: vec![i16::MIN; len_per_shape_rot],
|
||||||
|
y0: vec![i16::MAX; len_per_shape_rot],
|
||||||
|
y1: vec![i16::MIN; len_per_shape_rot],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cells_bits = vec![];
|
||||||
|
let mut cells_indices = vec![usize::MAX; len_per_shape_rot];
|
||||||
|
|
||||||
|
for (shape, &i0) in shape_indices.iter() {
|
||||||
|
let coords = rules.shapes.get(shape).unwrap();
|
||||||
|
for r in 0u8..4 {
|
||||||
|
let i = i0 * 4 + r as usize;
|
||||||
|
let (exts, data) = process_rotated_coords(coords, r);
|
||||||
|
extents.x0[i] = exts.x0;
|
||||||
|
extents.x1[i] = exts.x1;
|
||||||
|
extents.y0[i] = exts.y0;
|
||||||
|
extents.y1[i] = exts.y1;
|
||||||
|
cells_indices[i] = find_or_insert(&mut cells_bits, &data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RulesCode {
|
||||||
|
shape_indices,
|
||||||
|
extents,
|
||||||
|
cells_bits,
|
||||||
|
cells_indices,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_rotated_coords(coords: &[(i16, i16)], rot: u8) -> (Extents<i16>, Vec<u16>) {
|
||||||
|
let mut exts = Extents {
|
||||||
|
x0: i16::MAX,
|
||||||
|
x1: i16::MIN,
|
||||||
|
y0: i16::MAX,
|
||||||
|
y1: i16::MIN,
|
||||||
|
};
|
||||||
|
for &(x0, y0) in coords.iter() {
|
||||||
|
let (x, y) = rotate(x0, y0, rot);
|
||||||
|
exts.x0 = std::cmp::min(exts.x0, x);
|
||||||
|
exts.x1 = std::cmp::max(exts.x1, x + 1);
|
||||||
|
exts.y0 = std::cmp::min(exts.y0, y);
|
||||||
|
exts.y1 = std::cmp::max(exts.y1, y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = vec![0u16; (exts.y1 - exts.y0) as usize];
|
||||||
|
for &(x0, y0) in coords.iter() {
|
||||||
|
let (x, y) = rotate(x0, y0, rot);
|
||||||
|
data[(y - exts.y0) as usize] |= 1 << (x - exts.x0);
|
||||||
|
}
|
||||||
|
(exts, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(x: i16, y: i16, r: u8) -> (i16, i16) {
|
||||||
|
let mut xy = (x, y);
|
||||||
|
for _ in 0..r {
|
||||||
|
let (x, y) = xy;
|
||||||
|
xy = (y, -x);
|
||||||
|
}
|
||||||
|
xy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the first occurence of `needle` in `haystack`, or appends a suffix of it (up to
|
||||||
|
/// the entire length), returning the index within `haystack` that now contains `needle`.
|
||||||
|
fn find_or_insert<T: Eq + Copy>(haystack: &mut Vec<T>, needle: &[T]) -> usize {
|
||||||
|
for i in 0.. {
|
||||||
|
let len = std::cmp::min(haystack.len() - i, needle.len());
|
||||||
|
if haystack[i..i + len] == needle[..len] {
|
||||||
|
haystack.extend_from_slice(&needle[len..]);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RulesCode {
|
||||||
|
fn emit(&self, out: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||||
|
writeln!(out, "pub mod shape_indices {{")?;
|
||||||
|
for (name, &index) in self.shape_indices.iter() {
|
||||||
|
writeln!(out, "pub const {name}: usize = {index:?};")?;
|
||||||
|
}
|
||||||
|
writeln!(out, "}}")?;
|
||||||
|
|
||||||
|
writeln!(out, "pub mod extents {{")?;
|
||||||
|
for (name, arr) in [
|
||||||
|
("X0", &self.extents.x0),
|
||||||
|
("X1", &self.extents.x1),
|
||||||
|
("Y0", &self.extents.y0),
|
||||||
|
("Y1", &self.extents.y1),
|
||||||
|
] {
|
||||||
|
let len = arr.len();
|
||||||
|
writeln!(out, "pub const {name}: [i16; {len}] = {arr:?};")?;
|
||||||
|
}
|
||||||
|
writeln!(out, "}}")?;
|
||||||
|
|
||||||
|
let arr = &self.cells_bits;
|
||||||
|
let len = self.cells_bits.len();
|
||||||
|
writeln!(out, "pub const CELLS_BITS: [u16; {len}] = {arr:#x?};")?;
|
||||||
|
|
||||||
|
let arr = &self.cells_indices;
|
||||||
|
let len = self.cells_indices.len();
|
||||||
|
writeln!(out, "pub const CELLS_INDEX: [usize; {len}] = {arr:?};")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,12 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ascii"]
|
default = ["ascii", "srs"]
|
||||||
ascii = []
|
ascii = []
|
||||||
|
srs = []
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
mino-code-gen = { path = "../mino-code-gen" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# tracing = { version = "0.1", default-features = false}
|
# tracing = { version = "0.1", default-features = false}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let out_dir = std::env::var_os("OUT_DIR").unwrap();
|
||||||
|
let out_dir = Path::new(&out_dir);
|
||||||
|
|
||||||
|
let srs_in = "../assets/srs.json";
|
||||||
|
let srs_out = out_dir.join("srs.rs");
|
||||||
|
println!("cargo:rerun-if-changed={}", srs_in);
|
||||||
|
mino_code_gen::compile_ruleset(srs_in, srs_out).unwrap()
|
||||||
|
}
|
|
@ -7,6 +7,9 @@ pub mod piece;
|
||||||
pub use location::Loc;
|
pub use location::Loc;
|
||||||
pub use matrix::Mat;
|
pub use matrix::Mat;
|
||||||
|
|
||||||
|
#[cfg(feature = "srs")]
|
||||||
|
pub mod srs;
|
||||||
|
|
||||||
/// Allows constructing a `Mat` constant with string literals:
|
/// Allows constructing a `Mat` constant with string literals:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
//! Implementation of SRS shapes and movement quirks.
|
||||||
|
|
||||||
|
use crate::location::Rot;
|
||||||
|
use crate::piece::{Cells, Shape as GenericShape};
|
||||||
|
|
||||||
|
mod code_gen {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/srs.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Shape {
|
||||||
|
I = code_gen::shape_indices::I as u8,
|
||||||
|
J = code_gen::shape_indices::J as u8,
|
||||||
|
L = code_gen::shape_indices::L as u8,
|
||||||
|
O = code_gen::shape_indices::O as u8,
|
||||||
|
S = code_gen::shape_indices::S as u8,
|
||||||
|
T = code_gen::shape_indices::T as u8,
|
||||||
|
Z = code_gen::shape_indices::Z as u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericShape<'static> for Shape {
|
||||||
|
fn cells(&self, r: Rot) -> Cells<'static> {
|
||||||
|
let i = (*self as usize) * 4 + r as usize;
|
||||||
|
let xs = code_gen::extents::X0[i]..code_gen::extents::X1[i];
|
||||||
|
let ys = code_gen::extents::Y0[i]..code_gen::extents::Y1[i];
|
||||||
|
let idx = code_gen::CELLS_INDEX[i];
|
||||||
|
let data = &code_gen::CELLS_BITS[idx..idx + ys.len()];
|
||||||
|
Cells::new(xs, ys, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Piece = crate::piece::Piece<Shape>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::location::Loc;
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
|
fn get_cells(s: Shape, r: Rot) -> (Range<i16>, Range<i16>, Vec<(i16, i16)>) {
|
||||||
|
let piece = Piece {
|
||||||
|
shape: s,
|
||||||
|
loc: Loc { x: 0, y: 0, r },
|
||||||
|
};
|
||||||
|
let mut coords = piece.cells().coords().collect::<Vec<_>>();
|
||||||
|
coords.sort();
|
||||||
|
let (xs, ys) = piece.cells().extents();
|
||||||
|
(xs, ys, coords)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_cells(init: &[(i16, i16)], r: Rot) -> (Range<i16>, Range<i16>, Vec<(i16, i16)>) {
|
||||||
|
let mut coords = init.iter().map(|xy| rotate(*xy, r)).collect::<Vec<_>>();
|
||||||
|
coords.sort();
|
||||||
|
let min_x = coords.iter().map(|&(x, _)| x).min().unwrap();
|
||||||
|
let max_x = coords.iter().map(|&(x, _)| x).max().unwrap();
|
||||||
|
let min_y = coords.iter().map(|&(_, y)| y).min().unwrap();
|
||||||
|
let max_y = coords.iter().map(|&(_, y)| y).max().unwrap();
|
||||||
|
let xs = min_x..max_x + 1;
|
||||||
|
let ys = min_y..max_y + 1;
|
||||||
|
(xs, ys, coords)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(mut xy: (i16, i16), r: Rot) -> (i16, i16) {
|
||||||
|
for _ in 0..(r as u8) {
|
||||||
|
let (x, y) = xy;
|
||||||
|
xy = (y, -x);
|
||||||
|
}
|
||||||
|
xy
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_cells_all_rotations(s: Shape, expected_coords: &[(i16, i16)]) {
|
||||||
|
for r in (0..4).map(Rot::from) {
|
||||||
|
let (act_xs, act_ys, act_coords) = get_cells(s, r);
|
||||||
|
let (exp_xs, exp_ys, exp_coords) = compute_cells(expected_coords, r);
|
||||||
|
assert_eq!(act_xs, exp_xs, "{s:?},{r:?}");
|
||||||
|
assert_eq!(act_ys, exp_ys, "{s:?},{r:?}");
|
||||||
|
assert_eq!(act_coords, exp_coords, "{s:?},{r:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_srs_cells() {
|
||||||
|
test_cells_all_rotations(Shape::I, &[(-1, 0), (0, 0), (1, 0), (2, 0)]);
|
||||||
|
test_cells_all_rotations(Shape::J, &[(-1, 1), (-1, 0), (0, 0), (1, 0)]);
|
||||||
|
test_cells_all_rotations(Shape::L, &[(1, 1), (-1, 0), (0, 0), (1, 0)]);
|
||||||
|
test_cells_all_rotations(Shape::O, &[(0, 0), (1, 0), (0, 1), (1, 1)]);
|
||||||
|
test_cells_all_rotations(Shape::S, &[(-1, 0), (0, 0), (0, 1), (1, 1)]);
|
||||||
|
test_cells_all_rotations(Shape::T, &[(0, 1), (-1, 0), (0, 0), (1, 0)]);
|
||||||
|
test_cells_all_rotations(Shape::Z, &[(1, 0), (0, 0), (0, 1), (-1, 1)]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue