1#![no_std]
14
15use alloc::vec;
16use alloc::vec::Vec;
17use core::error::Error;
18use core::fmt::{Debug, Display, Formatter};
19use core::hash::{Hash, Hasher};
20use core::marker::PhantomData;
21use core::num::{IntErrorKind, NonZeroUsize};
22use core::ops::{Add, AddAssign, Deref, DerefMut, Neg, Sub, SubAssign};
23use num_traits::{CheckedAdd, CheckedMul, CheckedSub, One, Unsigned, Zero};
24use serde::de::DeserializeOwned;
25use serde::{Deserialize, Serialize};
26
27extern crate alloc;
28
29#[derive(Copy, Clone, Debug, Hash, Deserialize, Serialize)]
31pub enum Orientation {
32 UpDown,
34 LeftRight,
36}
37
38#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]
40pub enum Direction {
41 Up,
43 Down,
45 Left,
47 Right,
49}
50
51impl Display for Direction {
52 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
53 f.write_str(match self {
54 Direction::Up => "up",
55 Direction::Down => "down",
56 Direction::Left => "left",
57 Direction::Right => "right",
58 })
59 }
60}
61
62impl Neg for Direction {
63 type Output = Self;
64
65 fn neg(self) -> Self::Output {
66 match self {
67 Direction::Up => Direction::Down,
68 Direction::Down => Direction::Up,
69 Direction::Left => Direction::Right,
70 Direction::Right => Direction::Left,
71 }
72 }
73}
74
75pub trait BoardValue:
77 One
78 + Ord
79 + Add<Output = Self>
80 + CheckedAdd
81 + Sub<Output = Self>
82 + CheckedSub
83 + AddAssign
84 + SubAssign
85 + Copy
86 + Into<usize>
87 + TryFrom<usize>
88 + Zero
89 + CheckedMul
90 + Debug
91 + Display
92 + Unsigned
93 + DeserializeOwned
94 + Serialize
95 + 'static
96{
97}
98
99impl<V> BoardValue for V where
100 V: One
101 + Ord
102 + Add<Output = Self>
103 + CheckedAdd
104 + Sub<Output = Self>
105 + CheckedSub
106 + AddAssign
107 + SubAssign
108 + Copy
109 + Into<usize>
110 + TryFrom<usize>
111 + Zero
112 + CheckedMul
113 + Debug
114 + Display
115 + Unsigned
116 + DeserializeOwned
117 + Serialize
118 + 'static
119{
120}
121
122#[derive(Copy, Clone, Debug, Hash, Deserialize, Serialize)]
125pub struct Car<V> {
126 length: V,
127 orientation: Orientation,
128}
129
130impl<V> Car<V> {
131 pub fn length(&self) -> &V {
133 &self.length
134 }
135
136 pub fn orientation(&self) -> Orientation {
138 self.orientation
139 }
140}
141
142impl<V> Car<V>
143where
144 V: BoardValue,
145{
146 pub fn new(length: V, orientation: Orientation) -> Option<Self> {
148 if length < V::one() {
149 None
150 } else {
151 Some(Self {
152 length,
153 orientation,
154 })
155 }
156 }
157}
158
159#[derive(Copy, Clone, Debug, Hash, Deserialize, Serialize)]
161pub struct Position<V> {
162 row: V,
163 column: V,
164}
165
166impl<V> Position<V> {
167 pub fn row(&self) -> &V {
169 &self.row
170 }
171
172 pub fn column(&self) -> &V {
174 &self.column
175 }
176}
177
178impl<V> Add for Position<V>
179where
180 V: BoardValue,
181{
182 type Output = Self;
183
184 fn add(self, rhs: Self) -> Self::Output {
185 Self {
186 row: self.row + rhs.row,
187 column: self.column + rhs.column,
188 }
189 }
190}
191
192impl<V> CheckedAdd for Position<V>
193where
194 V: BoardValue,
195{
196 fn checked_add(&self, rhs: &Self) -> Option<Self> {
197 Some(Self {
198 row: self.row.checked_add(&rhs.row)?,
199 column: self.column.checked_add(&rhs.column)?,
200 })
201 }
202}
203
204impl<V> Sub for Position<V>
205where
206 V: BoardValue,
207{
208 type Output = Self;
209
210 fn sub(self, rhs: Self) -> Self::Output {
211 Self {
212 row: self.row - rhs.row,
213 column: self.column - rhs.column,
214 }
215 }
216}
217
218impl<V> CheckedSub for Position<V>
219where
220 V: BoardValue,
221{
222 fn checked_sub(&self, rhs: &Self) -> Option<Self> {
223 Some(Self {
224 row: self.row.checked_sub(&rhs.row)?,
225 column: self.column.checked_sub(&rhs.column)?,
226 })
227 }
228}
229
230impl<V> AddAssign for Position<V>
231where
232 V: BoardValue,
233{
234 fn add_assign(&mut self, rhs: Self) {
235 self.row += rhs.row;
236 self.column += rhs.column;
237 }
238}
239
240impl<V> Position<V>
241where
242 V: BoardValue,
243{
244 pub fn as_index(&self, dim: &Dimensions<V>) -> Option<usize> {
246 if self.row >= dim.rows || self.column >= dim.columns {
247 return None;
248 }
249 let row = self.row.into();
250 let column = self.column.into();
251 Some(row * dim.columns.into() + column)
252 }
253}
254
255impl<V> Position<V>
256where
257 Self: CheckedAdd<Output = Self> + CheckedSub<Output = Self>,
258 V: BoardValue,
259{
260 pub fn shift(&self, dir: Direction, by: V) -> Option<Self> {
263 match dir {
264 Direction::Up => self.checked_sub(&Self::from((by, V::zero()))),
265 Direction::Down => self.checked_add(&Self::from((by, V::zero()))),
266 Direction::Left => self.checked_sub(&Self::from((V::zero(), by))),
267 Direction::Right => self.checked_add(&Self::from((V::zero(), by))),
268 }
269 }
270}
271
272impl<V> From<(V, V)> for Position<V> {
273 fn from((row, column): (V, V)) -> Self {
274 Self { row, column }
275 }
276}
277
278#[derive(Copy, Clone, Debug, Hash, Deserialize, Serialize)]
280pub struct Dimensions<V> {
281 rows: V,
282 columns: V,
283}
284
285impl<V> Dimensions<V> {
286 pub fn rows(&self) -> &V {
288 &self.rows
289 }
290
291 pub fn columns(&self) -> &V {
293 &self.columns
294 }
295}
296
297#[derive(Debug)]
299pub struct DimensionError(IntErrorKind);
300
301impl Display for DimensionError {
302 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
303 let reason = match self.0 {
304 IntErrorKind::PosOverflow => "the dimensions were too large",
305 IntErrorKind::Zero => "the dimensions have zero area",
306 _ => unreachable!(),
307 };
308 f.write_fmt(format_args!("dimensions could not be used: {reason}"))
309 }
310}
311
312impl Error for DimensionError {}
313
314impl<V> TryFrom<(V, V)> for Dimensions<V>
315where
316 V: BoardValue,
317{
318 type Error = DimensionError;
319
320 fn try_from((rows, columns): (V, V)) -> Result<Self, Self::Error> {
321 if let Some(size) = rows.checked_mul(&columns) {
322 if size.is_zero() {
323 Err(DimensionError(IntErrorKind::Zero))
324 } else {
325 Ok(Self { rows, columns })
326 }
327 } else {
328 Err(DimensionError(IntErrorKind::PosOverflow))
329 }
330 }
331}
332
333#[derive(Clone, Debug, Hash, Deserialize, Serialize)]
336pub struct State<V> {
337 dim: Dimensions<V>,
338 cars: Vec<(Position<V>, Car<V>)>,
339}
340
341impl<V> State<V> {
342 pub fn dimensions(&self) -> &Dimensions<V> {
344 &self.dim
345 }
346
347 pub fn cars(&self) -> &[(Position<V>, Car<V>)] {
349 &self.cars
350 }
351}
352
353impl<V> State<V> {
354 pub fn empty<D: TryInto<Dimensions<V>>>(dim: D) -> Result<Self, D::Error> {
356 let dim = dim.try_into()?;
357 Ok(Self {
358 dim,
359 cars: Vec::new(),
360 })
361 }
362}
363
364#[derive(Debug)]
366pub enum InvalidStateType {
367 InvalidPosition(NonZeroUsize),
369 Overlap(NonZeroUsize, NonZeroUsize),
371}
372
373#[derive(Debug)]
375pub struct InvalidStateError<V> {
376 position: Position<V>,
377 variant: InvalidStateType,
378}
379
380impl<V> Display for InvalidStateError<V>
381where
382 V: BoardValue,
383{
384 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
385 match self.variant {
386 InvalidStateType::InvalidPosition(idx) => f.write_fmt(format_args!(
387 "car {idx} was located at an invalid position ({}, {})",
388 self.position.row, self.position.column
389 )),
390 InvalidStateType::Overlap(idx1, idx2) => f.write_fmt(format_args!(
391 "car {idx1} and car {idx2} overlapped at position ({}, {})",
392 self.position.row, self.position.column
393 )),
394 }
395 }
396}
397
398impl<V> Error for InvalidStateError<V> where V: BoardValue {}
399
400fn add_car_concrete<V>(
401 board: &mut [Option<NonZeroUsize>],
402 idx: NonZeroUsize,
403 dim: &Dimensions<V>,
404 position: &Position<V>,
405 car: &Car<V>,
406) -> Result<(), InvalidStateError<V>>
407where
408 V: BoardValue,
409{
410 let mut base = *position;
411 let offset = match car.orientation {
412 Orientation::UpDown => Position {
413 row: V::one(),
414 column: V::zero(),
415 },
416 Orientation::LeftRight => Position {
417 row: V::zero(),
418 column: V::one(),
419 },
420 };
421 for _ in 0..car.length.into() {
422 match base.as_index(dim).and_then(|p| board.get_mut(p)) {
423 None => {
424 return Err(InvalidStateError {
425 position: base,
426 variant: InvalidStateType::InvalidPosition(idx),
427 });
428 }
429 Some(entry) => {
430 if let Some(existing) = entry {
431 return Err(InvalidStateError {
432 position: base,
433 variant: InvalidStateType::Overlap(*existing, idx),
434 });
435 }
436 }
437 }
438 base += offset;
439 }
440 let mut base = *position;
441 for _ in 0..car.length.into() {
442 board[base.as_index(dim).unwrap()] = Some(idx);
443 base += offset;
444 }
445 Ok(())
446}
447
448impl<V> State<V>
449where
450 V: BoardValue,
451{
452 fn concrete(&self) -> Result<Vec<Option<NonZeroUsize>>, InvalidStateError<V>> {
453 let mut board = vec![None; self.dim.columns.into() * self.dim.rows.into()];
454 for (idx, (position, car)) in self.cars.iter().enumerate() {
455 add_car_concrete(
456 &mut board,
457 NonZeroUsize::new(idx + 1).unwrap(),
458 &self.dim,
459 position,
460 car,
461 )?;
462 }
463 Ok(board)
464 }
465
466 pub fn board(&self) -> Result<Board<&Self, V>, InvalidStateError<V>> {
468 Ok(Board {
469 concrete: self.concrete()?,
470 state: self,
471 phantom: PhantomData,
472 })
473 }
474
475 pub fn board_mut(&mut self) -> Result<Board<&mut Self, V>, InvalidStateError<V>> {
477 Ok(Board {
478 concrete: self.concrete()?,
479 state: self,
480 phantom: PhantomData,
481 })
482 }
483}
484
485#[derive(Debug)]
487pub struct Board<R, V> {
488 state: R,
489 concrete: Vec<Option<NonZeroUsize>>,
490 phantom: PhantomData<V>,
491}
492
493impl<R, V> Hash for Board<R, V>
494where
495 R: Hash,
496{
497 fn hash<H: Hasher>(&self, state: &mut H) {
498 self.state.hash(state);
499 }
500}
501
502impl<R, V> Board<R, V> {
503 pub fn concrete(&self) -> &Vec<Option<NonZeroUsize>> {
505 &self.concrete
506 }
507}
508
509impl<R, V> Board<R, V>
510where
511 R: Deref<Target = State<V>>,
512 V: BoardValue,
513{
514 pub fn get<P: Into<Position<V>>>(&self, position: P) -> Option<Option<NonZeroUsize>> {
518 position
519 .into()
520 .as_index(&self.state.dim)
521 .and_then(|p| self.concrete.get(p).copied())
522 }
523}
524
525impl<R, V> Board<R, V>
526where
527 R: Deref<Target = State<V>>,
528{
529 pub fn state(&self) -> &State<V> {
531 self.state.deref()
532 }
533}
534
535impl<R, V> Board<R, V>
536where
537 R: DerefMut<Target = State<V>>,
538{
539 pub fn state_mut(&mut self) -> &mut State<V> {
541 self.state.deref_mut()
542 }
543}
544
545#[derive(Debug)]
547pub enum InvalidMoveType<V> {
548 InvalidCar,
550 InvalidDirection,
552 InvalidFinalPosition,
554 Intersects(Position<V>, NonZeroUsize),
557}
558
559#[derive(Debug)]
561pub struct InvalidMoveError<V> {
562 car: NonZeroUsize,
563 dir: Direction,
564 variant: InvalidMoveType<V>,
565}
566
567impl<V> Display for InvalidMoveError<V>
568where
569 V: BoardValue,
570{
571 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
572 match &self.variant {
573 InvalidMoveType::InvalidCar => f.write_fmt(format_args!("cannot move car {} {} because it doesn't exist", self.car, self.dir)),
574 InvalidMoveType::InvalidDirection => f.write_fmt(format_args!("cannot move car {} {} because its orientation does not allow for movement in that direction", self.car, self.dir)),
575 InvalidMoveType::InvalidFinalPosition => f.write_fmt(format_args!("cannot move car {} {} because it enters an invalid position", self.car, self.dir)),
576 InvalidMoveType::Intersects(pos, other) => f.write_fmt(format_args!("cannot move car {} {} because it would intersect with car {} at ({}, {})", self.car, self.dir, other, pos.row, pos.column)),
577 }
578 }
579}
580
581impl<V> Error for InvalidMoveError<V> where V: BoardValue {}
582
583impl<R, V> Board<R, V>
584where
585 R: DerefMut<Target = State<V>>,
586 V: BoardValue,
587{
588 pub fn add_car<P: Into<Position<V>>>(
590 &mut self,
591 position: P,
592 car: Car<V>,
593 ) -> Result<NonZeroUsize, InvalidStateError<V>> {
594 let position = position.into();
595 let idx = NonZeroUsize::new(self.state.cars.len() + 1).unwrap();
596 add_car_concrete(&mut self.concrete, idx, &self.state.dim, &position, &car)?;
597 self.state.cars.push((position, car));
598 Ok(idx)
599 }
600}
601
602impl<R, V> Board<R, V>
603where
604 R: DerefMut<Target = State<V>>,
605 V: BoardValue,
606{
607 pub fn shift_car(
609 &mut self,
610 car: NonZeroUsize,
611 dir: Direction,
612 ) -> Result<Position<V>, InvalidMoveError<V>> {
613 let idx = car.get() - 1;
614 if let Some((pos, actual)) = self.state.cars.get(idx).copied() {
615 let (deleted, inserted) = match (dir, actual.orientation) {
616 (Direction::Up, Orientation::UpDown)
617 | (Direction::Left, Orientation::LeftRight) => (
618 pos.shift(-dir, actual.length - V::one()),
619 pos.shift(dir, V::one()),
620 ),
621 (Direction::Down, Orientation::UpDown)
622 | (Direction::Right, Orientation::LeftRight) => {
623 (Some(pos), pos.shift(dir, actual.length))
624 }
625 _ => {
626 return Err(InvalidMoveError {
627 car,
628 dir,
629 variant: InvalidMoveType::InvalidDirection,
630 });
631 }
632 };
633 if let (Some(deleted_pos), Some(inserted_pos)) = (deleted, inserted) {
634 let deleted =
635 deleted_pos
636 .as_index(&self.state.dim)
637 .ok_or_else(|| InvalidMoveError {
638 car,
639 dir,
640 variant: InvalidMoveType::InvalidFinalPosition,
641 })?;
642 let inserted =
643 inserted_pos
644 .as_index(&self.state.dim)
645 .ok_or_else(|| InvalidMoveError {
646 car,
647 dir,
648 variant: InvalidMoveType::InvalidFinalPosition,
649 })?;
650 if let Ok([deleted, inserted]) = self.concrete.get_disjoint_mut([deleted, inserted])
651 {
652 return if let Some(idx) = inserted {
653 Err(InvalidMoveError {
654 car,
655 dir,
656 variant: InvalidMoveType::Intersects(inserted_pos, *idx),
657 })
658 } else {
659 *inserted = deleted.take();
660 let new = pos.shift(dir, V::one()).unwrap();
661 self.state.cars[idx].0 = new;
662 Ok(new)
663 };
664 }
665 }
666 Err(InvalidMoveError {
667 car,
668 dir,
669 variant: InvalidMoveType::InvalidFinalPosition,
670 })
671 } else {
672 Err(InvalidMoveError {
673 car,
674 dir,
675 variant: InvalidMoveType::InvalidCar,
676 })
677 }
678 }
679}
680
681impl<R, V> Display for Board<R, V>
682where
683 R: Deref<Target = State<V>>,
684 V: BoardValue,
685{
686 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
687 let num_width = (self.state.cars.len().saturating_sub(1).max(1).ilog10() + 2)
688 .next_multiple_of(2) as usize
689 - 1;
690 let side = num_width + 2;
691 let mut indices = self.concrete.iter().copied();
692 for _row in 0..self.state.dim.rows.into() {
693 for _padding in 0..(side / 2) {
694 writeln!(f, "{:#>1$}", "", self.state.dim.columns.into() * side)?;
695 }
696 for _column in 0..self.state.dim.columns.into() {
697 write!(f, "#")?;
698 if let Some(idx) = indices.next().unwrap() {
699 write!(f, "{idx:0num_width$}")?;
700 } else {
701 write!(f, "{:#>num_width$}", "")?;
702 }
703 write!(f, "#")?;
704 }
705 writeln!(f)?;
706 for _padding in 0..(side / 2) {
707 writeln!(f, "{:#>1$}", "", self.state.dim.columns.into() * side)?;
708 }
709 }
710 Ok(())
711 }
712}
713
714#[cfg(test)]
715mod test {
716 extern crate std;
717
718 use crate::{
719 Car, Direction, InvalidMoveError, InvalidMoveType, InvalidStateError, InvalidStateType,
720 Orientation, State,
721 };
722 use alloc::boxed::Box;
723 use core::error::Error;
724 use core::num::NonZeroUsize;
725 use std::println;
726
727 #[test]
728 fn simple_board() -> Result<(), Box<dyn Error>> {
729 let mut state = State::empty((2u8, 2))?;
730 let mut board = state.board_mut()?;
731 println!("{board}");
732 let idx = board.add_car((0, 0), Car::new(1, Orientation::LeftRight).unwrap())?;
733 assert_eq!(1, idx.get());
734 println!("{board}");
735 assert_eq!(1, board.get((0, 0)).unwrap().unwrap().get());
736
737 match board.add_car((2, 0), Car::new(1, Orientation::UpDown).unwrap()) {
738 Err(InvalidStateError {
739 position,
740 variant: InvalidStateType::InvalidPosition(idx),
741 }) => {
742 assert_eq!((2, 0), (position.row, position.column));
743 assert_eq!(2, idx.get());
744 }
745 _ => unreachable!("Should error here"),
746 }
747
748 match board.add_car((0, 0), Car::new(1, Orientation::UpDown).unwrap()) {
749 Err(InvalidStateError {
750 position,
751 variant: InvalidStateType::Overlap(idx1, idx2),
752 }) => {
753 assert_eq!((0, 0), (position.row, position.column));
754 assert_eq!(1, idx1.get());
755 assert_eq!(2, idx2.get());
756 }
757 _ => unreachable!("Should error here"),
758 }
759
760 board.shift_car(idx, Direction::Right)?;
761 assert_eq!(1, board.get((0, 1)).unwrap().unwrap().get());
762 assert_eq!(None, board.get((0, 0)).unwrap());
763 println!("{board}");
764
765 board.shift_car(idx, Direction::Left)?;
766 assert_eq!(1, board.get((0, 0)).unwrap().unwrap().get());
767 assert_eq!(None, board.get((0, 1)).unwrap());
768 println!("{board}");
769
770 let idx = board.add_car((0, 1), Car::new(1, Orientation::UpDown).unwrap())?;
771 assert_eq!(2, idx.get());
772 println!("{board}");
773 assert_eq!(2, board.get((0, 1)).unwrap().unwrap().get());
774
775 board.shift_car(idx, Direction::Down)?;
776 assert_eq!(2, board.get((1, 1)).unwrap().unwrap().get());
777 assert_eq!(None, board.get((0, 1)).unwrap());
778 println!("{board}");
779
780 board.shift_car(idx, Direction::Up)?;
781 assert_eq!(2, board.get((0, 1)).unwrap().unwrap().get());
782 assert_eq!(None, board.get((1, 1)).unwrap());
783 println!("{board}");
784
785 match board.shift_car(NonZeroUsize::new(3).unwrap(), Direction::Right) {
786 Err(InvalidMoveError {
787 variant: InvalidMoveType::InvalidCar,
788 car,
789 dir,
790 }) => {
791 assert_eq!(3, car.get());
792 assert_eq!(dir, Direction::Right)
793 }
794 s => unreachable!("Expected another error, got {s:?}"),
795 }
796
797 match board.shift_car(idx, Direction::Up) {
798 Err(InvalidMoveError {
799 variant: InvalidMoveType::InvalidFinalPosition,
800 car,
801 dir,
802 }) => {
803 assert_eq!(2, car.get());
804 assert_eq!(dir, Direction::Up)
805 }
806 s => unreachable!("Expected another error, got {s:?}"),
807 }
808
809 match board.shift_car(idx, Direction::Right) {
810 Err(InvalidMoveError {
811 variant: InvalidMoveType::InvalidDirection,
812 car,
813 dir,
814 }) => {
815 assert_eq!(2, car.get());
816 assert_eq!(dir, Direction::Right)
817 }
818 s => unreachable!("Expected another error, got {s:?}"),
819 }
820
821 match board.shift_car(NonZeroUsize::new(1).unwrap(), Direction::Right) {
822 Err(InvalidMoveError {
823 variant: InvalidMoveType::Intersects(at, with),
824 car,
825 dir,
826 }) => {
827 assert_eq!(1, car.get());
828 assert_eq!(2, with.get());
829 assert_eq!((0, 1), (at.row, at.column));
830 assert_eq!(dir, Direction::Right);
831 }
832 s => unreachable!("Expected another error, got {s:?}"),
833 }
834
835 let concrete = board.concrete().clone();
836 drop(board);
837
838 let board = state.board()?;
839
840 assert_eq!(&concrete, board.concrete());
841
842 Ok(())
843 }
844
845 #[test]
846 fn multi_board() -> Result<(), Box<dyn Error>> {
847 let mut state = State::empty((5u8, 5))?;
848 let mut board = state.board_mut()?;
849 println!("{board}");
850 let idx = board.add_car((0, 0), Car::new(3, Orientation::UpDown).unwrap())?;
851 assert_eq!(1, idx.get());
852 println!("{board}");
853
854 board.shift_car(idx, Direction::Down)?;
855 println!("{board}");
856
857 Ok(())
858 }
859}