move tidepool::{queue,garbage} all inside the sim module
This commit is contained in:
parent
e8351e73be
commit
d61e6588c7
|
@ -1,181 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod garbage;
|
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod queue;
|
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
use mino::srs::PieceType;
|
|
||||||
use rand::Rng;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
/// Manages the preview pieces and hold slot, and automatically refills the previews
|
|
||||||
/// whenever pieces are consumed from the front of the queue.
|
|
||||||
pub struct Queue<R: Rng> {
|
|
||||||
hold: Option<PieceType>,
|
|
||||||
next: VecDeque<PieceType>,
|
|
||||||
bag: Bag<R, PieceType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
static PIECES: [PieceType; 7] = [
|
|
||||||
// this order is arbitrary (alphabetical), but must be set in stone in order for bags
|
|
||||||
// to be reproducible by identical PRNG's.
|
|
||||||
PieceType::I,
|
|
||||||
PieceType::J,
|
|
||||||
PieceType::L,
|
|
||||||
PieceType::O,
|
|
||||||
PieceType::S,
|
|
||||||
PieceType::T,
|
|
||||||
PieceType::Z,
|
|
||||||
];
|
|
||||||
|
|
||||||
impl<R: Rng> Queue<R> {
|
|
||||||
/// Constructs a new queue.
|
|
||||||
///
|
|
||||||
/// - `rng`: random number source
|
|
||||||
/// - `count`: number of next pieces.
|
|
||||||
pub fn new(rng: R, count: usize) -> Self {
|
|
||||||
assert!(count > 0, "next pieces cannot be empty");
|
|
||||||
let mut next = VecDeque::with_capacity(count);
|
|
||||||
let mut bag = Bag::new(rng, PIECES);
|
|
||||||
while next.len() < count {
|
|
||||||
next.push_back(bag.pop());
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
hold: None,
|
|
||||||
next,
|
|
||||||
bag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current piece in the hold slot.
|
|
||||||
pub fn hold(&self) -> Option<PieceType> {
|
|
||||||
self.hold
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of the next previews.
|
|
||||||
pub fn next(&self) -> Vec<PieceType> {
|
|
||||||
self.next.iter().copied().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a piece from the front of the queue. The piece must either by the current
|
|
||||||
/// piece at the front of the next-previews, or it must be reachable by swapping with
|
|
||||||
/// the piece in hold; if the hold is empty then the second piece in the queue is
|
|
||||||
/// reachable by moving the first piece into the hold slot.
|
|
||||||
///
|
|
||||||
/// Panics if the given piece is not reachable from this queue.
|
|
||||||
pub fn remove(&mut self, ty: PieceType) {
|
|
||||||
// remove current piece from front of queue
|
|
||||||
let top = self.pop_front();
|
|
||||||
if top != ty {
|
|
||||||
// if not placing current piece, then must be placing either the hold piece or
|
|
||||||
// the piece reachable by hold (if hold was previously empty).
|
|
||||||
if let Some(held) = self.hold.replace(top) {
|
|
||||||
// hold piece
|
|
||||||
assert_eq!(ty, held);
|
|
||||||
} else {
|
|
||||||
// hold empty, so get next reachable piece
|
|
||||||
assert_eq!(ty, self.pop_front());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_front(&mut self) -> PieceType {
|
|
||||||
let ty = self.next.pop_front().expect("len > 0");
|
|
||||||
self.next.push_back(self.bag.pop());
|
|
||||||
ty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Bag<R: Rng, T: Copy> {
|
|
||||||
rng: R,
|
|
||||||
bag: Vec<T>,
|
|
||||||
count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Rng, T: Copy> Bag<R, T> {
|
|
||||||
fn new(rng: R, init: impl IntoIterator<Item = T>) -> Self {
|
|
||||||
let bag = init.into_iter().collect::<Vec<_>>();
|
|
||||||
let count = bag.len();
|
|
||||||
assert!(count > 0, "empty init bag");
|
|
||||||
Self { rng, bag, count }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(&mut self) -> T {
|
|
||||||
let i = self.count - 1;
|
|
||||||
self.bag.swap(self.rng.gen_range(0..self.count), i);
|
|
||||||
self.count = if i == 0 { self.bag.len() } else { i };
|
|
||||||
self.bag[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bag_order() {
|
|
||||||
let mut tys = std::collections::HashSet::new();
|
|
||||||
let mut bag = Bag::new(rand::thread_rng(), 'A'..='Z');
|
|
||||||
for _ in 0..50 {
|
|
||||||
tys.extend('A'..='Z');
|
|
||||||
for _ in 0..26 {
|
|
||||||
let ch = bag.pop();
|
|
||||||
assert!(tys.remove(&ch), "{ch:?}");
|
|
||||||
}
|
|
||||||
assert!(tys.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pop() {
|
|
||||||
let mut que = Queue::new(rand::thread_rng(), 5);
|
|
||||||
assert_eq!(que.hold(), None);
|
|
||||||
|
|
||||||
let next = que.next();
|
|
||||||
assert_eq!(next.len(), 5);
|
|
||||||
|
|
||||||
// [](a)bcde
|
|
||||||
let a = next[0];
|
|
||||||
let b = next[1];
|
|
||||||
let c = next[2];
|
|
||||||
let d = next[3];
|
|
||||||
let e = next[4];
|
|
||||||
assert_ne!(a, b);
|
|
||||||
assert_ne!(c, d);
|
|
||||||
assert_ne!(a, e);
|
|
||||||
|
|
||||||
// [](b)cdef -> a
|
|
||||||
que.remove(a);
|
|
||||||
let next = que.next();
|
|
||||||
assert_eq!(que.hold, None);
|
|
||||||
assert_eq!(next.len(), 5);
|
|
||||||
assert_eq!(next[0], b);
|
|
||||||
assert_eq!(next[1], c);
|
|
||||||
assert_eq!(next[2], d);
|
|
||||||
assert_eq!(next[3], e);
|
|
||||||
let f = next[4];
|
|
||||||
|
|
||||||
// [b](d)efgh -> c
|
|
||||||
que.remove(c);
|
|
||||||
assert_eq!(que.hold(), Some(b));
|
|
||||||
let next = que.next();
|
|
||||||
assert_eq!(next.len(), 5);
|
|
||||||
assert_eq!(next[0], d);
|
|
||||||
assert_eq!(next[1], e);
|
|
||||||
assert_eq!(next[2], f);
|
|
||||||
let g = next[3];
|
|
||||||
let h = next[4];
|
|
||||||
|
|
||||||
// [b](e)fghi -> d
|
|
||||||
que.remove(d);
|
|
||||||
assert_eq!(que.hold(), Some(b));
|
|
||||||
let next = que.next();
|
|
||||||
assert_eq!(next.len(), 5);
|
|
||||||
assert_eq!(next[0], e);
|
|
||||||
assert_eq!(next[1], f);
|
|
||||||
assert_eq!(next[2], g);
|
|
||||||
assert_eq!(next[3], h);
|
|
||||||
let i = next[4];
|
|
||||||
|
|
||||||
// [e](f)ghij -> b
|
|
||||||
que.remove(b);
|
|
||||||
assert_eq!(que.hold(), Some(e));
|
|
||||||
let next = que.next();
|
|
||||||
assert_eq!(next.len(), 5);
|
|
||||||
assert_eq!(next[0], f);
|
|
||||||
assert_eq!(next[1], g);
|
|
||||||
assert_eq!(next[2], h);
|
|
||||||
assert_eq!(next[3], i);
|
|
||||||
//let j = next[4];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
use crate::garbage::Garbage;
|
use mino::matrix::{Mat, MatBuf, COLUMNS};
|
||||||
use crate::queue::Queue;
|
|
||||||
|
|
||||||
use mino::matrix::{Mat, MatBuf};
|
|
||||||
use mino::srs::{Piece, PieceType};
|
use mino::srs::{Piece, PieceType};
|
||||||
use rand::SeedableRng;
|
use rand::{Rng as _, SeedableRng as _};
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
|
@ -18,8 +16,8 @@ pub struct Options {
|
||||||
type Rng = rand_xoshiro::Xoshiro256StarStar;
|
type Rng = rand_xoshiro::Xoshiro256StarStar;
|
||||||
|
|
||||||
pub struct Simul {
|
pub struct Simul {
|
||||||
queue: Queue<Rng>,
|
queue: Queue,
|
||||||
garbage: Garbage<Rng>,
|
garbage: Garbage,
|
||||||
matrix: MatBuf,
|
matrix: MatBuf,
|
||||||
lines_left: usize,
|
lines_left: usize,
|
||||||
}
|
}
|
||||||
|
@ -65,3 +63,363 @@ impl Simul {
|
||||||
self.lines_left
|
self.lines_left
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Manages the preview pieces and hold slot, and automatically refills the previews
|
||||||
|
/// whenever pieces are consumed from the front of the queue.
|
||||||
|
pub struct Queue {
|
||||||
|
hold: Option<PieceType>,
|
||||||
|
next: VecDeque<PieceType>,
|
||||||
|
bag: Bag,
|
||||||
|
}
|
||||||
|
|
||||||
|
static PIECES: [PieceType; 7] = [
|
||||||
|
// this order is arbitrary (alphabetical), but must be set in stone in order for bags
|
||||||
|
// to be reproducible by identical PRNG's.
|
||||||
|
PieceType::I,
|
||||||
|
PieceType::J,
|
||||||
|
PieceType::L,
|
||||||
|
PieceType::O,
|
||||||
|
PieceType::S,
|
||||||
|
PieceType::T,
|
||||||
|
PieceType::Z,
|
||||||
|
];
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
/// Constructs a new queue.
|
||||||
|
///
|
||||||
|
/// - `rng`: random number source
|
||||||
|
/// - `count`: number of next pieces.
|
||||||
|
pub fn new(rng: Rng, count: usize) -> Self {
|
||||||
|
assert!(count > 0, "number of pieces cannot be zero");
|
||||||
|
let mut next = VecDeque::with_capacity(count);
|
||||||
|
let mut bag = Bag::new(rng);
|
||||||
|
while next.len() < count {
|
||||||
|
next.push_back(bag.pop());
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
hold: None,
|
||||||
|
next,
|
||||||
|
bag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current piece in the hold slot.
|
||||||
|
pub fn hold(&self) -> Option<PieceType> {
|
||||||
|
self.hold
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of the next previews.
|
||||||
|
pub fn next(&self) -> Vec<PieceType> {
|
||||||
|
self.next.iter().copied().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a piece from the front of the queue. The piece must either by the current
|
||||||
|
/// piece at the front of the next-previews, or it must be reachable by swapping with
|
||||||
|
/// the piece in hold; if the hold is empty then the second piece in the queue is
|
||||||
|
/// reachable by moving the first piece into the hold slot.
|
||||||
|
///
|
||||||
|
/// Panics if the given piece is not reachable from this queue.
|
||||||
|
pub fn remove(&mut self, ty: PieceType) {
|
||||||
|
// remove current piece from front of queue
|
||||||
|
let top = self.pop_front();
|
||||||
|
if top != ty {
|
||||||
|
// if not placing current piece, then must be placing either the hold piece or
|
||||||
|
// the piece reachable by hold (if hold was previously empty).
|
||||||
|
if let Some(held) = self.hold.replace(top) {
|
||||||
|
// hold piece
|
||||||
|
assert_eq!(ty, held);
|
||||||
|
} else {
|
||||||
|
// hold empty, so get next reachable piece
|
||||||
|
assert_eq!(ty, self.pop_front());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_front(&mut self) -> PieceType {
|
||||||
|
let ty = self.next.pop_front().expect("len > 0");
|
||||||
|
self.next.push_back(self.bag.pop());
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bag {
|
||||||
|
rng: Rng,
|
||||||
|
bag: Vec<PieceType>,
|
||||||
|
pos: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bag {
|
||||||
|
fn new(rng: Rng) -> Self {
|
||||||
|
let bag = PIECES.to_vec();
|
||||||
|
Self { rng, bag, pos: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop(&mut self) -> PieceType {
|
||||||
|
static N: u32 = PIECES.len() as u32;
|
||||||
|
let i = self.pos;
|
||||||
|
let j = self.rng.gen_range(i..N); // gen u32 instead of usize so its deterministic
|
||||||
|
|
||||||
|
self.bag.swap(i as usize, j as usize);
|
||||||
|
self.pos += 1;
|
||||||
|
self.pos %= N;
|
||||||
|
|
||||||
|
self.bag[i as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages the current level of garbage and the remaining garbage lines.
|
||||||
|
pub struct Garbage {
|
||||||
|
cheese: std::iter::Take<Cheese>,
|
||||||
|
// current level of garbage on the matrix
|
||||||
|
level: i16,
|
||||||
|
// min/max garbage to insert on the matrix
|
||||||
|
range: RangeInclusive<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Garbage {
|
||||||
|
/// 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: Rng, 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 {
|
||||||
|
rng: Rng,
|
||||||
|
col: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cheese {
|
||||||
|
fn new(mut rng: Rng) -> Self {
|
||||||
|
let col = rng.gen_range(0..COLUMNS);
|
||||||
|
Self { rng, col }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Cheese {
|
||||||
|
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;
|
||||||
|
|
||||||
|
fn make_rng() -> Rng {
|
||||||
|
Rng::seed_from_u64(0x1234_5678_00ab_cdef)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bag_order() {
|
||||||
|
let mut tys = std::collections::HashSet::new();
|
||||||
|
let mut bag = Bag::new(make_rng());
|
||||||
|
for _ in 0..50 {
|
||||||
|
tys.extend(PIECES);
|
||||||
|
for _ in 0..7 {
|
||||||
|
let ch = bag.pop();
|
||||||
|
assert!(tys.remove(&ch), "{ch:?}");
|
||||||
|
}
|
||||||
|
assert!(tys.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pop() {
|
||||||
|
let mut que = Queue::new(make_rng(), 5);
|
||||||
|
assert_eq!(que.hold(), None);
|
||||||
|
|
||||||
|
let next = que.next();
|
||||||
|
assert_eq!(next.len(), 5);
|
||||||
|
|
||||||
|
// [](a)bcde
|
||||||
|
let a = next[0];
|
||||||
|
let b = next[1];
|
||||||
|
let c = next[2];
|
||||||
|
let d = next[3];
|
||||||
|
let e = next[4];
|
||||||
|
assert_ne!(a, b);
|
||||||
|
assert_ne!(c, d);
|
||||||
|
assert_ne!(a, e);
|
||||||
|
|
||||||
|
// [](b)cdef -> a
|
||||||
|
que.remove(a);
|
||||||
|
let next = que.next();
|
||||||
|
assert_eq!(que.hold, None);
|
||||||
|
assert_eq!(next.len(), 5);
|
||||||
|
assert_eq!(next[0], b);
|
||||||
|
assert_eq!(next[1], c);
|
||||||
|
assert_eq!(next[2], d);
|
||||||
|
assert_eq!(next[3], e);
|
||||||
|
let f = next[4];
|
||||||
|
|
||||||
|
// [b](d)efgh -> c
|
||||||
|
que.remove(c);
|
||||||
|
assert_eq!(que.hold(), Some(b));
|
||||||
|
let next = que.next();
|
||||||
|
assert_eq!(next.len(), 5);
|
||||||
|
assert_eq!(next[0], d);
|
||||||
|
assert_eq!(next[1], e);
|
||||||
|
assert_eq!(next[2], f);
|
||||||
|
let g = next[3];
|
||||||
|
let h = next[4];
|
||||||
|
|
||||||
|
// [b](e)fghi -> d
|
||||||
|
que.remove(d);
|
||||||
|
assert_eq!(que.hold(), Some(b));
|
||||||
|
let next = que.next();
|
||||||
|
assert_eq!(next.len(), 5);
|
||||||
|
assert_eq!(next[0], e);
|
||||||
|
assert_eq!(next[1], f);
|
||||||
|
assert_eq!(next[2], g);
|
||||||
|
assert_eq!(next[3], h);
|
||||||
|
let i = next[4];
|
||||||
|
|
||||||
|
// [e](f)ghij -> b
|
||||||
|
que.remove(b);
|
||||||
|
assert_eq!(que.hold(), Some(e));
|
||||||
|
let next = que.next();
|
||||||
|
assert_eq!(next.len(), 5);
|
||||||
|
assert_eq!(next[0], f);
|
||||||
|
assert_eq!(next[1], g);
|
||||||
|
assert_eq!(next[2], h);
|
||||||
|
assert_eq!(next[3], i);
|
||||||
|
//let j = next[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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() {
|
||||||
|
let cheese = Cheese::new(make_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(make_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue