Implement some basic downstacking heuristics
This commit is contained in:
parent
b6e6a00164
commit
5313580b2e
|
@ -0,0 +1,223 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use core::ops::Range;
|
||||
use mino::matrix::EMPTY_ROW;
|
||||
use mino::{Mat, MatBuf};
|
||||
|
||||
/// This is the algorithm that mystery and I developed. It computes the "minimum downstack
|
||||
/// estimate", which approximates the minimum pieces to downstack a given board state. It
|
||||
/// works by repeatedly finding garbage rows that are covered, computing the minimum
|
||||
/// number of pieces needed to uncover the garbage, simulating rows being removed to
|
||||
/// uncover garbage, and repeating until there are no much garbage rows on the board.
|
||||
pub fn mystery_mdse(init_mat: &Mat) -> i32 {
|
||||
let mut mat: MatBuf = MatBuf::new();
|
||||
mat.copy_from(init_mat);
|
||||
|
||||
let mut count = 0;
|
||||
let mut iters = 0;
|
||||
|
||||
// find garbage rows covered by some rows of "residue"
|
||||
while let Some(res_ys) = residue(&mat) {
|
||||
debug_assert!(res_ys.len() != 0, "{res_ys:?}");
|
||||
|
||||
let res = &mut mat[res_ys];
|
||||
if let Some((_x0, _y0, area)) = flood_fill(res) {
|
||||
// estimate pieces needed to fill residue, and then fill them
|
||||
let res_pc = (area + 3) / 4; // = ceil(area/4)
|
||||
|
||||
// add to total count, but dampen by iteration count; residue cleared later on
|
||||
// most likely has blocks contributed to it by attempts to clear in earlier
|
||||
// iterations.
|
||||
iters += 1;
|
||||
count += 1 + res_pc.saturating_sub(iters) as i32;
|
||||
|
||||
// FIXME: only clear lines above y0
|
||||
mat.clear_lines();
|
||||
} else {
|
||||
debug_assert!(false, "flood_fill() didn't find a region");
|
||||
mat.clear_lines();
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/// Finds a hole that is covered up by blocks above it. Returns the total range of rows
|
||||
/// covering the hole, or `None` if no holes found in the stack.
|
||||
///
|
||||
/// Consider the following stack:
|
||||
///
|
||||
/// + 0123456789
|
||||
/// 4 .x.......x
|
||||
/// 3 .x......xx
|
||||
/// 2 xxx...xxxx
|
||||
/// 1 xxxxxxxx.x
|
||||
/// 0 xx..xxxxxx
|
||||
///
|
||||
/// Given this stack, `residue` returns the range `2..=3`. The hole found is in row 1
|
||||
/// column 8, and there are two rows of residue above it. Note that the hole in row 0
|
||||
/// columns 2-3 is also a valid hole, but row 1 is found first because it is on a higher
|
||||
/// row.
|
||||
fn residue(mat: &Mat) -> Option<Range<i16>> {
|
||||
let mut y = mat.rows();
|
||||
let mut prev_row = EMPTY_ROW; // = mat[y]
|
||||
|
||||
// find garbage row
|
||||
let mask = loop {
|
||||
if y == 0 {
|
||||
// hit the bottom
|
||||
return None;
|
||||
}
|
||||
let curr_row = mat[y - 1];
|
||||
let mask = prev_row & !curr_row;
|
||||
// for some bit i, prev_row[i] == 1 && curr_row[i] == 0
|
||||
if mask != 0 {
|
||||
break mask;
|
||||
}
|
||||
y -= 1;
|
||||
prev_row = curr_row;
|
||||
};
|
||||
|
||||
// find top of residue
|
||||
let y0 = y;
|
||||
let y1 = loop {
|
||||
let curr_row = mat[y];
|
||||
if curr_row & mask == 0 {
|
||||
break y;
|
||||
}
|
||||
y += 1;
|
||||
};
|
||||
|
||||
Some(y0..y1)
|
||||
}
|
||||
|
||||
/// Runs the flood fill algorithm on the given set of matrix row data, filling at most one
|
||||
/// region of contiguous empty space. If an empty region was found in the slice, returns
|
||||
/// `(x,y,area)` where `(x,y)` is first point in the region filled in, and `area` is the
|
||||
/// number of empty cells filled.
|
||||
fn flood_fill(rows: &mut [u16]) -> Option<(i16, i16, u32)> {
|
||||
fn init(rows: &[u16]) -> Option<(i16, i16)> {
|
||||
for (y, row) in rows.iter().enumerate() {
|
||||
let x = row.trailing_ones();
|
||||
if x < 16 {
|
||||
return Some((x as i16, y as i16));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
let (x0, y0) = init(&rows)?;
|
||||
|
||||
fn flood(rows: &mut [u16], x: i16, y: i16) -> u32 {
|
||||
if x < 0 || y < 0 || (y as usize) >= rows.len() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let idx = y as usize;
|
||||
let mask = 1 << x;
|
||||
if rows[idx] & mask != 0 {
|
||||
return 0;
|
||||
}
|
||||
rows[idx] |= mask;
|
||||
|
||||
// FIXME: improve this:
|
||||
// - the max recursion depth is small (~400?) so we should use an explicit stack
|
||||
// - scan filling could be more efficient than plain recursion
|
||||
1 + flood(rows, x - 1, y)
|
||||
+ flood(rows, x + 1, y)
|
||||
+ flood(rows, x, y - 1)
|
||||
+ flood(rows, x, y + 1)
|
||||
}
|
||||
|
||||
Some((x0, y0, flood(rows, x0, y0)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use mino::{mat, matrix::EMPTY_ROW};
|
||||
|
||||
#[test]
|
||||
fn test_flood_fill() {
|
||||
// + 0123456789
|
||||
// 2 ....xxxxx.
|
||||
// 1 xx..xxxx..
|
||||
// 0 xxx...xxxx
|
||||
let mut rows = [
|
||||
0b1111000111 | EMPTY_ROW,
|
||||
0b0011110011 | EMPTY_ROW,
|
||||
0b0111110000 | EMPTY_ROW,
|
||||
];
|
||||
// + 0123456789
|
||||
// 2 FFFFxxxxx.
|
||||
// 1 xxFFxxxx..
|
||||
// 0 xxxFFFxxxx
|
||||
let (fx, fy, area) = flood_fill(&mut rows).unwrap();
|
||||
assert_eq!((fx, fy), (3, 0));
|
||||
assert_eq!(area, 3 + 2 + 4);
|
||||
assert_eq!(rows[0], 0b1111111111 | EMPTY_ROW, "{:b}", rows[0]);
|
||||
assert_eq!(rows[1], 0b0011111111 | EMPTY_ROW, "{:b}", rows[1]);
|
||||
assert_eq!(rows[2], 0b0111111111 | EMPTY_ROW, "{:b}", rows[2]);
|
||||
// + 0123456789
|
||||
// 2 ffffxxxxxF
|
||||
// 1 xxffxxxxFF
|
||||
// 0 xxxfffxxxx
|
||||
let (fx, fy, area) = flood_fill(&mut rows).unwrap();
|
||||
assert_eq!((fx, fy), (8, 1));
|
||||
assert_eq!(area, 2 + 1);
|
||||
assert_eq!(rows[0], 0b1111111111 | EMPTY_ROW, "{:b}", rows[0]);
|
||||
assert_eq!(rows[1], 0b1111111111 | EMPTY_ROW, "{:b}", rows[1]);
|
||||
assert_eq!(rows[2], 0b1111111111 | EMPTY_ROW, "{:b}", rows[2]);
|
||||
// ---
|
||||
assert_eq!(flood_fill(&mut rows), None);
|
||||
assert_eq!(flood_fill(&mut []), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_residue() {
|
||||
assert_eq!(
|
||||
residue(mat! {
|
||||
".x.......x"; // 4
|
||||
".x......Rx"; // 3
|
||||
"xxx...xxRx"; // 2
|
||||
"xxxxxxxx.x"; // 1
|
||||
"xx..xxxxxx"; // 0
|
||||
}),
|
||||
Some(2..4)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
residue(mat! {
|
||||
".x.......x"; // 4
|
||||
".x......Rx"; // 3
|
||||
"xxx...xRRx"; // 2
|
||||
"xxxxxxx..x"; // 1
|
||||
"xx..xxxxxx"; // 0
|
||||
}),
|
||||
Some(2..4)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
residue(mat! {
|
||||
".x........"; // 5
|
||||
".xR......."; // 4
|
||||
".xRR......"; // 3
|
||||
"xx.....xx."; // 2
|
||||
"xx...xxxxx"; // 1
|
||||
"xxxxxxxxx."; // 0
|
||||
}),
|
||||
Some(3..5)
|
||||
);
|
||||
|
||||
assert_eq!(residue(Mat::EMPTY), None);
|
||||
assert_eq!(
|
||||
residue(mat! {
|
||||
".x........"; // 3
|
||||
"xx.....x.."; // 2
|
||||
"xx...xxxx."; // 1
|
||||
"xxxxxxxxx."; // 0
|
||||
}),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
use mino::matrix::{COLUMNS, EMPTY_ROW, FULL_ROW};
|
||||
use mino::Mat;
|
||||
|
||||
mod downstacking;
|
||||
|
||||
pub use downstacking::mystery_mdse;
|
||||
|
||||
pub fn max_height(matrix: &Mat) -> i32 {
|
||||
matrix.rows() as i32
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue