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.
|
||||
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"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[workspace]
|
||||
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"
|
||||
|
||||
[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}
|
||||
|
|
|
@ -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 matrix::Mat;
|
||||
|
||||
#[cfg(feature = "srs")]
|
||||
pub mod srs;
|
||||
|
||||
/// 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