add pretty simple Queue type to mino

This commit is contained in:
tali 2023-04-11 14:53:09 -04:00
parent 5e73d7627e
commit 3fb3743cd1
3 changed files with 142 additions and 0 deletions

View File

@ -3,10 +3,12 @@
pub mod input;
pub mod matrix;
pub mod piece;
pub mod queue;
pub use input::Movement;
pub use matrix::{Mat, MatBuf};
pub use piece::{Loc, Piece, Rot};
pub use queue::Queue;
#[cfg(feature = "srs")]
pub mod srs;

139
mino/src/queue.rs Normal file
View File

@ -0,0 +1,139 @@
//! Data structure to represent the "queue", which encompasses the current piece, the next
//! previews, and the hold slot. The data structure attempts to remove ambiguity between
//! queues that have the same two pieces immediately available, by automatically moving
//! the front of the queue into the hold slot if the hold slot would be empty.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Queue<'a, T: Copy> {
pub hold: Option<T>,
pub next: &'a [T],
}
impl<'a, T: Copy> Queue<'a, T> {
/// Constructs a new queue.
pub fn new(mut hold: Option<T>, mut next: &'a [T]) -> Self {
if hold.is_none() && !next.is_empty() {
hold = Some(next[0]);
next = &next[1..];
}
Self { hold, next }
}
/// Returns `true` if the queue is completely empty (i.e., `reachable()` would yield
/// no values).
pub fn is_empty(&self) -> bool {
// HACK: its impossible for `next` to be non-empty and `hold` to be None, because
// if `next` was non-empty then the `new` constructor would automatically move
// some value into the hold slot.
debug_assert!(self.hold.is_some() || self.next.is_empty());
self.hold.is_none() /* && self.next.is_empty() */
}
/// Returns the number of total pieces in the queue, including the hold slot. This
/// equals the number of times `remove` may be called before the queue becomes empty.
pub fn len(&self) -> usize {
self.hold.map_or(0, |_| 1) + self.next.len()
}
/// Yields the next pieces available to use from the queue. The returned iterator may
/// be empty, and may yield at most 2 elements.
pub fn reachable(&self) -> impl Iterator<Item = T> {
let front = self.next.get(0).copied();
[front, self.hold].into_iter().flatten()
}
}
impl<'a, T: Copy + Eq> Queue<'a, T> {
/// Removes the given piece from the front of the queue, returning a new queue with
/// that piece removed.
///
/// Panics if the piece is not actually reachable from this queue.
pub fn remove(&self, val: T) -> Self {
assert!(!self.is_empty(), "trying to remove from empty queue");
if self.hold == Some(val) {
Queue::new(None, self.next)
} else {
assert!(self.next[0] == val, "trying to remove unreachable item");
Queue::new(self.hold, &self.next[1..])
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::vec::Vec;
use core::fmt::Debug;
#[test]
fn test_queue_new_eq() {
assert_eq!(
Queue::new(None, &["A", "B", "C"]),
Queue::new(Some("A"), &["B", "C"])
);
assert_eq!(Queue::new(None, &["A"]), Queue::new(Some("A"), &[]));
}
#[test]
fn test_queue_empty() {
assert!(!Queue::new(None, &["A", "B", "C"]).is_empty());
assert!(!Queue::new(None, &["A"]).is_empty());
assert!(!Queue::new(Some("A"), &[]).is_empty());
assert!(Queue::new(None, &[] as &[&str]).is_empty());
}
fn assert_reachable<T: Copy + Eq + Debug>(q: &Queue<T>, ts: impl IntoIterator<Item = T>) {
let mut ts = ts.into_iter().collect::<Vec<_>>();
for t in q.reachable() {
let idx = ts
.iter()
.enumerate()
.find_map(|(i, &u)| (t == u).then_some(i));
if let Some(idx) = idx {
ts.remove(idx);
} else {
panic!("{t:?} found but not expected");
}
}
for t in ts {
panic!("{t:?} expected but not found");
}
}
#[test]
fn test_queue_reachable() {
assert_reachable(&Queue::new(None, &[] as &[&str]), []);
assert_reachable(&Queue::new(None, &["A", "B", "C"]), ["A", "B"]);
assert_reachable(&Queue::new(Some("D"), &[]), ["D"]);
assert_reachable(&Queue::new(Some("D"), &["A"]), ["A", "D"]);
assert_reachable(&Queue::new(Some("D"), &["A", "B", "C"]), ["A", "D"]);
}
#[test]
fn test_queue_remove() {
assert_eq!(
Queue::new(None, &["A", "B", "C"]).remove(&"A"),
Queue::new(Some("B"), &["C"])
);
assert_eq!(
Queue::new(Some("D"), &["A", "B", "C"]).remove(&"A"),
Queue::new(Some("D"), &["B", "C"])
);
assert_eq!(
Queue::new(None, &["A", "B", "C"]).remove(&"B"),
Queue::new(None, &["A", "C"])
);
}
#[test]
#[should_panic]
fn test_queue_bad_remove_1() {
Queue::new(None, &["A", "B", "C"]).remove(&"C");
}
#[test]
#[should_panic]
fn test_queue_bad_remove_2() {
Queue::new(Some("D"), &["A", "B", "C"]).remove(&"B");
}
}

View File

@ -116,6 +116,7 @@ impl core::fmt::Display for InvalidPieceName {
}
pub type Piece = crate::piece::Piece<PieceType>;
pub type Queue<'a> = crate::queue::Queue<'a, PieceType>;
#[cfg(test)]
mod test {