add pretty simple Queue type to mino
This commit is contained in:
parent
5e73d7627e
commit
3fb3743cd1
|
@ -3,10 +3,12 @@
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
pub mod piece;
|
pub mod piece;
|
||||||
|
pub mod queue;
|
||||||
|
|
||||||
pub use input::Movement;
|
pub use input::Movement;
|
||||||
pub use matrix::{Mat, MatBuf};
|
pub use matrix::{Mat, MatBuf};
|
||||||
pub use piece::{Loc, Piece, Rot};
|
pub use piece::{Loc, Piece, Rot};
|
||||||
|
pub use queue::Queue;
|
||||||
|
|
||||||
#[cfg(feature = "srs")]
|
#[cfg(feature = "srs")]
|
||||||
pub mod srs;
|
pub mod srs;
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -116,6 +116,7 @@ impl core::fmt::Display for InvalidPieceName {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Piece = crate::piece::Piece<PieceType>;
|
pub type Piece = crate::piece::Piece<PieceType>;
|
||||||
|
pub type Queue<'a> = crate::queue::Queue<'a, PieceType>;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
Loading…
Reference in New Issue