Add SRS shape implementations via build time code-gen

This commit is contained in:
tali 2022-12-14 09:58:38 -05:00
parent 04484b38da
commit c5ca155ad2
8 changed files with 375 additions and 2 deletions

96
Cargo.lock generated
View File

@ -2,6 +2,102 @@
# It is not intended for manual editing.
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]]
name = "mino"
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"

View File

@ -1,4 +1,5 @@
[workspace]
members = [
"mino"
"mino",
"mino-code-gen",
]

9
mino-code-gen/Cargo.toml Normal file
View File

@ -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"

155
mino-code-gen/src/lib.rs Normal file
View File

@ -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(())
}
}

View File

@ -5,8 +5,12 @@ version = "0.1.0"
edition = "2021"
[features]
default = ["ascii"]
default = ["ascii", "srs"]
ascii = []
srs = []
[build-dependencies]
mino-code-gen = { path = "../mino-code-gen" }
[dependencies]
# tracing = { version = "0.1", default-features = false}

11
mino/build.rs Normal file
View File

@ -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()
}

View File

@ -7,6 +7,9 @@ pub mod piece;
pub use location::Loc;
pub use matrix::Mat;
#[cfg(feature = "srs")]
pub mod srs;
/// Allows constructing a `Mat` constant with string literals:
///
/// ```

94
mino/src/srs.rs Normal file
View File

@ -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)]);
}
}