182 lines
5.1 KiB
Rust
182 lines
5.1 KiB
Rust
use mino::matrix::{MatBuf, COLUMNS};
|
|
use rand::Rng;
|
|
use std::ops::RangeInclusive;
|
|
|
|
/// Manages the current level of garbage and the remaining garbage lines.
|
|
pub struct Garbage<R: Rng> {
|
|
cheese: std::iter::Take<Cheese<R>>,
|
|
// current level of garbage on the matrix
|
|
level: i16,
|
|
// min/max garbage to insert on the matrix
|
|
range: RangeInclusive<usize>,
|
|
}
|
|
|
|
impl<R: Rng> Garbage<R> {
|
|
/// Constructs a new garbage generator.
|
|
///
|
|
/// - `rng`: random number source
|
|
/// - `count`: total number of garbage rows to insert
|
|
/// - `range`: min/max amount of garbage on the matrix at a given time
|
|
pub fn new(rng: R, count: usize, range: RangeInclusive<usize>) -> Self {
|
|
Self {
|
|
cheese: Cheese::new(rng).take(count),
|
|
level: 0,
|
|
range,
|
|
}
|
|
}
|
|
|
|
/// Signals that a line clear happened at row `min_y`. This is necessary to determine
|
|
/// how much garbage needs to be inserted. Returns the number of garbage lines that
|
|
/// were removed from this line clear.
|
|
pub fn clear(&mut self, min_y: i16) -> usize {
|
|
if min_y < self.level {
|
|
let removed = (self.level - min_y) as usize;
|
|
self.level = min_y;
|
|
removed
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
/// Inserts new garbage lines to the bottom of `mat`. If `comboing` is `true` then
|
|
/// less garbage lines are inserted (like jstris's cheese race mechanics). Returns the
|
|
/// number of new garbage lines inserted.
|
|
pub fn insert(&mut self, mat: &mut MatBuf, comboing: bool) -> usize {
|
|
let target = if comboing {
|
|
*self.range.start()
|
|
} else {
|
|
*self.range.end()
|
|
};
|
|
let difference = target.saturating_sub(self.level as usize);
|
|
|
|
(&mut self.cheese)
|
|
.take(difference)
|
|
.map(|col| {
|
|
mat.shift_up();
|
|
mat.fill_row(0, garbage_row(col));
|
|
self.level += 1;
|
|
})
|
|
.count()
|
|
}
|
|
}
|
|
|
|
fn garbage_row(col: i16) -> u16 {
|
|
!(1 << col)
|
|
}
|
|
|
|
struct Cheese<R: Rng> {
|
|
rng: R,
|
|
col: i16,
|
|
}
|
|
|
|
impl<R: Rng> Cheese<R> {
|
|
fn new(mut rng: R) -> Self {
|
|
let col = rng.gen_range(0..COLUMNS);
|
|
Self { rng, col }
|
|
}
|
|
}
|
|
|
|
impl<R: Rng> Iterator for Cheese<R> {
|
|
type Item = i16;
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.col += self.rng.gen_range(1..COLUMNS);
|
|
self.col %= COLUMNS;
|
|
Some(self.col)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use mino::mat;
|
|
|
|
#[test]
|
|
fn test_garbage_row() {
|
|
let mat = mat! {
|
|
"xxxxxxxxx.";
|
|
"xxxxx.xxxx";
|
|
"xx.xxxxxxx";
|
|
".xxxxxxxxx";
|
|
};
|
|
assert_eq!(mat[0], garbage_row(0));
|
|
assert_eq!(mat[1], garbage_row(2));
|
|
assert_eq!(mat[2], garbage_row(5));
|
|
assert_eq!(mat[3], garbage_row(9));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cheese_no_duplicates() {
|
|
for _ in 0..50 {
|
|
let cheese = Cheese::new(rand::thread_rng());
|
|
let mut prev = None;
|
|
let mut history = std::collections::HashSet::new();
|
|
for x1 in cheese.take(500) {
|
|
if let Some(x0) = prev {
|
|
assert_ne!(x0, x1);
|
|
}
|
|
prev = Some(x1);
|
|
history.insert(x1);
|
|
}
|
|
assert_eq!(history.len(), COLUMNS as usize);
|
|
for x in 0..COLUMNS {
|
|
assert!(history.contains(&x));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_garbage_insert() {
|
|
let mut mat = MatBuf::new();
|
|
let mut garbage_left = 15;
|
|
let mut downstacked = 0;
|
|
let mut garbage = Garbage::new(rand::thread_rng(), garbage_left, 3..=9);
|
|
|
|
garbage_left -= garbage.insert(&mut mat, true);
|
|
assert_eq!(garbage_left, 12);
|
|
assert_eq!(mat.rows(), 3);
|
|
let m0 = mat[0];
|
|
let m1 = mat[1];
|
|
let m2 = mat[2];
|
|
|
|
garbage_left -= garbage.insert(&mut mat, false);
|
|
assert_eq!(garbage_left, 6);
|
|
assert_eq!(mat.rows(), 9);
|
|
assert_ne!(mat[5], m0);
|
|
assert_eq!(mat[6], m0);
|
|
assert_eq!(mat[7], m1);
|
|
assert_eq!(mat[8], m2);
|
|
|
|
mat.fill_row(9, mino::matrix::EMPTY_ROW | 0b1);
|
|
assert_eq!(garbage.insert(&mut mat, false), 0);
|
|
assert_eq!(mat.rows(), 10);
|
|
|
|
mat.fill_row(7, mino::matrix::FULL_ROW);
|
|
mat.fill_row(8, mino::matrix::FULL_ROW);
|
|
assert_eq!(mat.clear_lines(), 7..9);
|
|
assert_eq!(mat.rows(), 8);
|
|
|
|
downstacked += garbage.clear(7);
|
|
assert_eq!(downstacked, 2);
|
|
|
|
garbage_left -= garbage.insert(&mut mat, false);
|
|
assert_eq!(garbage_left, 4);
|
|
assert_eq!(mat.rows(), 10);
|
|
|
|
mat.clear();
|
|
downstacked += garbage.clear(0);
|
|
assert_eq!(downstacked, 11);
|
|
|
|
garbage_left -= garbage.insert(&mut mat, false);
|
|
assert_eq!(garbage_left, 0);
|
|
assert_eq!(mat.rows(), 4);
|
|
|
|
mat.clear();
|
|
downstacked += garbage.clear(0);
|
|
assert_eq!(downstacked, 15);
|
|
|
|
assert_eq!(garbage.insert(&mut mat, false), 0);
|
|
assert_eq!(mat.rows(), 0);
|
|
}
|
|
}
|