#![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.is_empty(), "{res_ys:?}"); let res = &mut mat[res_ys]; // determine pieces needed to fill residue by filling them if let Some((_x0, _y0, area)) = flood_fill(res) { // min number of pieces = ceil(area/4) let res_pc = (area + 3) / 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..4`. 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> { // find hole that is covered let mut mask; let mut y0 = mat.rows(); let mut prev_row = EMPTY_ROW; // = mat[y] loop { if y0 == 0 { // hit the bottom return None; } let curr_row = mat[y0 - 1]; // for some bit i, prev_row[i] == 1 && curr_row[i] == 0 mask = prev_row & !curr_row; if mask != 0 { break; } y0 -= 1; prev_row = curr_row; } // find top of residue let mut y1 = y0; loop { let curr_row = mat[y1]; if curr_row & mask == 0 { break; } y1 += 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() { // trailing_ones() finds some unoccipied cell in `row`. if the row is full // then this return 16 (since there are 16 bits in the row representation // type). let x = row.trailing_ones(); if x < 16 { return Some((x as i16, y as i16)); } } None } fn flood(rows: &mut [u16], x: i16, y: i16) -> u32 { // test if (x,y) is OOB if x < 0 || y < 0 || (y as usize) >= rows.len() { return 0; } // try to fill in (x,y) 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) } let (x0, y0) = init(rows)?; 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 ); } }