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 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;
|
||||
|
|
|
@ -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 Queue<'a> = crate::queue::Queue<'a, PieceType>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
|
Loading…
Reference in New Issue