grid_engine/grid_engine.rs
1// Copyright (c) 2025 Thiago Ramos
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Grid Engine manages a 2D grid system with support for adding, removing, and moving items.
22//!
23//! # Key Features
24//!
25//! - Automatic collision detection and handling
26//! - Event system for tracking changes
27//! - Expanding the grid on the y axis dynamically
28//!
29//! # Example
30//!
31//! ```
32//! use grid_engine::grid_engine::GridEngine;
33//! # use std::error::Error;
34//!
35//! # fn main() -> Result<(), Box<dyn Error>> {
36//! let mut grid = GridEngine::new(10, 12);
37//!
38//! // Add items to the grid
39//! grid.add_item("item1".to_string(), 2, 2, 2, 4)?;
40//!
41//! // Move items (handles collisions automatically)
42//! grid.move_item("item1", 4, 4)?;
43//!
44//! // Remove items
45//! grid.remove_item("item1")?;
46//! #
47//! # Ok(())
48//! # }
49//! ```
50
51use crate::error::{GridEngineError, InnerGridError, ItemError};
52use crate::grid_events::{ChangesEventValue, GridEvents};
53use crate::inner_grid::{InnerGrid, UpdateGridOperation};
54use crate::node::Node;
55use crate::utils::{ForCellArgs, for_cell};
56use std::{collections::BTreeMap, fmt::Debug};
57
58/// Represents data for an item addition change
59#[derive(Clone, Debug, Eq, PartialEq, Hash)]
60pub struct AddChangeData {
61 /// The node being added to the grid
62 value: Node,
63}
64
65impl AddChangeData {
66 /// Creates a new AddChangeData instance
67 ///
68 /// # Arguments
69 ///
70 /// * `value` - The node being added to the grid
71 ///
72 /// # Returns
73 ///
74 /// A new instance of AddChangeData
75 pub fn new(value: Node) -> Self {
76 Self { value }
77 }
78
79 /// Returns the node being added
80 pub fn value(&self) -> &Node {
81 &self.value
82 }
83}
84
85/// Represents data for an item removal change
86#[derive(Clone, Debug, Eq, PartialEq, Hash)]
87pub struct RemoveChangeData {
88 /// The node being removed from the grid
89 value: Node,
90}
91
92impl RemoveChangeData {
93 /// Creates a new RemoveChangeData instance
94 ///
95 /// # Arguments
96 ///
97 /// * `value` - The node being removed from the grid
98 ///
99 /// # Returns
100 ///
101 /// A new instance of RemoveChangeData
102 pub fn new(value: Node) -> Self {
103 Self { value }
104 }
105
106 /// Returns the node being removed
107 pub fn value(&self) -> &Node {
108 &self.value
109 }
110}
111
112/// Represents data for an item movement change
113#[derive(Clone, Debug, Eq, PartialEq, Hash)]
114pub struct MoveChangeData {
115 /// The original state of the node
116 old_value: Node,
117 /// The new state of the node after movement
118 new_value: Node,
119}
120
121impl MoveChangeData {
122 /// Creates a new MoveChangeData instance
123 ///
124 /// # Arguments
125 ///
126 /// * `old_value` - The original state of the node
127 /// * `new_value` - The new state of the node after movement
128 ///
129 /// # Returns
130 ///
131 /// A new instance of MoveChangeData
132 pub fn new(old_value: Node, new_value: Node) -> Self {
133 Self {
134 old_value,
135 new_value,
136 }
137 }
138
139 /// Returns the original state of the node
140 pub fn old_value(&self) -> &Node {
141 &self.old_value
142 }
143
144 /// Returns the new state of the node after movement
145 pub fn new_value(&self) -> &Node {
146 &self.new_value
147 }
148}
149
150/// Represents different types of changes that can occur in the grid
151#[derive(Clone, Debug, Eq, PartialEq, Hash)]
152pub enum Change {
153 /// Adding a new item to the grid
154 Add(AddChangeData),
155 /// Removing an existing item from the grid
156 Remove(RemoveChangeData),
157 /// Moving an item to a new position
158 Move(MoveChangeData),
159}
160
161/// The main engine for managing a 2D grid system.
162///
163/// `GridEngine` provides functionality for:
164/// - Adding items to specific grid positions
165/// - Moving items while handling collisions
166/// - Removing items from the grid
167/// - Tracking changes through an event system
168/// - Expand the grid dynamically on the y axis
169///
170/// When items collide during placement or movement, the engine automatically
171/// repositions affected items to prevent overlapping, the default is to move the collided items down, increasing their y axis.
172#[derive(Debug)]
173pub struct GridEngine {
174 /// The underlying grid structure
175 grid: InnerGrid,
176 /// Map of item IDs to their Node representations
177 items: BTreeMap<String, Node>,
178 /// Changes waiting to be applied
179 pending_changes: Vec<Change>,
180 /// Event system for tracking grid changes
181 events: GridEvents,
182}
183
184impl GridEngine {
185 /// Creates a new GridEngine with specified dimensions.
186 ///
187 /// # Arguments
188 ///
189 /// * `rows` - Initial number of rows in the grid
190 /// * `cols` - Initial number of columns in the grid
191 ///
192 /// # Example
193 ///
194 /// ```
195 /// use grid_engine::grid_engine::GridEngine;
196 ///
197 /// let grid = GridEngine::new(10, 10); // Creates a 10x10 grid
198 /// ```
199 pub fn new(rows: usize, cols: usize) -> GridEngine {
200 GridEngine {
201 grid: InnerGrid::new(rows, cols),
202 items: BTreeMap::new(),
203 pending_changes: Vec::new(),
204 events: GridEvents::default(),
205 }
206 }
207
208 /// Creates a new node with the specified parameters.
209 fn new_node(&mut self, id: impl Into<String>, x: usize, y: usize, w: usize, h: usize) -> Node {
210 Node::new(id.into(), x, y, w, h)
211 }
212
213 /// Creates a change operation to add a new node to the grid.
214 fn create_add_change(&mut self, node: Node) {
215 self.pending_changes
216 .push(Change::Add(AddChangeData { value: node }));
217 }
218
219 /// Get the node sorted by id
220 ///
221 /// # Example
222 ///
223 /// ```
224 /// use grid_engine::grid_engine::GridEngine;
225 ///
226 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
227 /// let mut grid = GridEngine::new(10, 10);
228 /// grid.add_item("b".to_string(), 0, 0, 2, 2).unwrap();
229 /// grid.add_item("a".to_string(), 0, 2, 2, 2).unwrap();
230 ///
231 /// let nodes = grid.get_nodes();
232 /// assert_eq!(nodes.len(), 2);
233 /// assert_eq!(nodes[0].id(), "a");
234 /// assert_eq!(nodes[1].id(), "b");
235 /// # Ok(())
236 /// # }
237 /// ```
238 pub fn get_nodes(&self) -> Vec<&Node> {
239 let mut cloned: Vec<&Node> = self.items.values().collect();
240 // Would be better to sort by some created_at
241 cloned.sort_by_key(|n| n.id.clone());
242 cloned
243 }
244
245 /// Gets a reference to the underlying grid structure.
246 ///
247 /// This provides access to the raw grid data for inspection purposes.
248 /// Note that modifications should be made through GridEngine's public methods
249 /// rather than directly manipulating the inner grid.
250 ///
251 /// # Returns
252 ///
253 /// A reference to the InnerGrid instance
254 ///
255 /// # Example
256 ///
257 /// ```
258 /// use grid_engine::grid_engine::GridEngine;
259 /// use std::error::Error;
260 ///
261 /// # fn main() -> Result<(), Box<dyn Error>> {
262 /// let grid = GridEngine::new(10, 10);
263 /// let inner_grid = grid.get_inner_grid();
264 /// assert_eq!(inner_grid.rows(), 10);
265 /// assert_eq!(inner_grid.cols(), 10);
266 /// # Ok(())
267 /// # }
268 pub fn get_inner_grid(&self) -> &InnerGrid {
269 &self.grid
270 }
271
272 /// Adds an item to the grid at the specified position.
273 ///
274 /// If the new item would collide with existing items, those items are
275 /// automatically repositioned to avoid overlap.
276 ///
277 /// # Arguments
278 ///
279 /// * `id` - Unique identifier for the item
280 /// * `x` - X coordinate (column) for item placement
281 /// * `y` - Y coordinate (row) for item placement
282 /// * `w` - Width of the item in grid cells
283 /// * `h` - Height of the item in grid cells
284 ///
285 /// # Returns
286 ///
287 /// * `Ok(&Node)` - Reference to the newly added node
288 /// * `Err(GridEngineError)` - If item already exists or placement fails
289 ///
290 /// # Example
291 ///
292 /// ```
293 /// use grid_engine::grid_engine::GridEngine;
294 /// use std::error::Error;
295 ///
296 /// # fn main() -> Result<(), Box<dyn Error>> {
297 /// let mut grid = GridEngine::new(10, 10);
298 /// grid.add_item("box1".to_string(), 0, 0, 2, 2)?; // 2x2 item at top-left
299 ///
300 /// // Check if the item was added correctly
301 /// let item = grid.get_nodes();
302 /// assert_eq!(item.len(), 1);
303 /// assert_eq!(item[0].id(), "box1");
304 ///
305 /// # Ok(())
306 /// # }
307 /// ```
308 pub fn add_item(
309 &mut self,
310 id: impl Into<String>,
311 x: usize,
312 y: usize,
313 w: usize,
314 h: usize,
315 ) -> Result<&Node, GridEngineError> {
316 let id = id.into();
317 if self.items.contains_key(&id) {
318 return Err(GridEngineError::Item(ItemError::ItemAlreadyExists { id }));
319 };
320
321 let node = self.new_node(id, x, y, w, h);
322 let node_id = node.id.to_string();
323
324 self.handle_collision(&node, x, y, &mut self.grid.clone())?;
325
326 self.create_add_change(node);
327
328 self.apply_changes(&self.pending_changes.clone())?;
329 self.pending_changes.clear();
330
331 let node = self
332 .items
333 .get(&node_id)
334 .ok_or(InnerGridError::MismatchedGridItem { id: node_id })?;
335 Ok(node)
336 }
337
338 fn create_remove_change(&mut self, node: &Node) {
339 self.pending_changes.push(Change::Remove(RemoveChangeData {
340 value: node.clone(),
341 }));
342 }
343
344 /// Removes an item from the grid by its ID.
345 ///
346 /// # Arguments
347 ///
348 /// * `id` - ID of the item to remove
349 ///
350 /// # Returns
351 ///
352 /// * `Ok(Node)` - The removed node
353 /// * `Err(GridEngineError)` - If item doesn't exist
354 ///
355 /// # Example
356 ///
357 /// ```
358 /// use grid_engine::grid_engine::GridEngine;
359 /// use std::error::Error;
360 ///
361 /// # fn main() -> Result<(), Box<dyn Error>> {
362 ///
363 /// let mut grid = GridEngine::new(10, 10);
364 /// grid.add_item("box1".to_string(), 0, 0, 2, 2)?;
365 /// grid.remove_item("box1")?; // Removes the item
366 ///
367 /// # Ok(())
368 /// # }
369 /// ```
370 pub fn remove_item(&mut self, id: &str) -> Result<Node, GridEngineError> {
371 let node = match self.items.get(id) {
372 Some(node) => node,
373 None => Err(GridEngineError::Item(ItemError::ItemNotFound {
374 id: id.to_string(),
375 }))?,
376 }
377 .clone();
378
379 self.create_remove_change(&node);
380
381 self.apply_changes(&self.pending_changes.clone())?;
382 self.pending_changes.clear();
383 Ok(node)
384 }
385
386 /// Checks if a node would collide with any existing items at the specified position.
387 ///
388 /// This is used internally to detect potential collisions before making grid changes.
389 /// It considers the node's dimensions and any existing items in the target area.
390 ///
391 /// # Returns
392 ///
393 /// * `Ok(Vec<&Node>)` - List of nodes that would collide with the given node
394 /// * `Err(InnerGridError)` - If position check fails (e.g., out of bounds)
395 fn will_collides_with(
396 &self,
397 node: &Node,
398 x: usize,
399 y: usize,
400 grid: &mut InnerGrid,
401 ) -> Result<Vec<&Node>, InnerGridError> {
402 let mut collides_with: Vec<&Node> = Vec::new();
403
404 for_cell(
405 ForCellArgs {
406 x,
407 y,
408 w: node.w,
409 h: node.h,
410 },
411 &mut |x, y| {
412 let cell = grid
413 .get(x, y)
414 .ok_or(InnerGridError::OutOfBoundsAccess { x, y })?;
415
416 match cell {
417 Some(cell_ref) => {
418 if cell_ref != &node.id {
419 let node = self.items.get(cell_ref).ok_or(
420 InnerGridError::MismatchedGridItem {
421 id: cell_ref.to_string(),
422 },
423 )?;
424
425 if !collides_with.contains(&node) {
426 collides_with.push(node);
427 }
428 }
429 }
430 None => {
431 // Nothing to collide with
432 }
433 }
434 Ok(())
435 },
436 )?;
437
438 Ok(collides_with)
439 }
440
441 /// Handles collision resolution when adding or moving items.
442 ///
443 /// When a collision is detected, this method:
444 /// 1. Identifies all affected items
445 /// 2. Calculates new positions for colliding items
446 /// 3. Creates appropriate move changes to relocate affected items
447 ///
448 /// The default collision resolution strategy moves affected items downward,
449 /// which may trigger dynamic grid expansion in the y-axis.
450 fn handle_collision(
451 &mut self,
452 node: &Node,
453 x: usize,
454 y: usize,
455 grid: &mut InnerGrid,
456 ) -> Result<(), InnerGridError> {
457 let collides_with = self
458 .will_collides_with(node, x, y, grid)?
459 .iter()
460 .map(|n| (*n).clone())
461 .collect::<Vec<Node>>();
462
463 for collided in collides_with {
464 let mut new_grid = grid.clone();
465
466 node.update_grid(&mut new_grid, UpdateGridOperation::Remove)?;
467 let new_x = collided.x;
468 let new_y = y + node.h;
469 self.create_move_change(collided, new_x, new_y, &mut new_grid)?;
470 }
471
472 Ok(())
473 }
474
475 /// Creates a change operation to move a node to a new position.
476 ///
477 /// This method:
478 /// 1. Handles any collisions at the new position
479 /// 2. Checks if the node was already scheduled to move
480 /// 3. Creates a Move change operation if needed
481 ///
482 /// # Arguments
483 ///
484 /// * `node` - The node to move
485 /// * `new_x` - Target x coordinate
486 /// * `new_y` - Target y coordinate
487 /// * `grid` - The grid to check for collisions
488 fn create_move_change(
489 &mut self,
490 node: Node,
491 new_x: usize,
492 new_y: usize,
493 grid: &mut InnerGrid,
494 ) -> Result<(), InnerGridError> {
495 let old_node = node.clone();
496 self.handle_collision(&node, new_x, new_y, grid)?;
497
498 let already_moved = self.pending_changes.iter().any(|change| match change {
499 Change::Move(data) => data.new_value.id == node.id,
500 _ => false,
501 });
502
503 if already_moved {
504 return Ok(());
505 }
506
507 self.pending_changes.push(Change::Move(MoveChangeData {
508 old_value: old_node,
509 new_value: Node::new(node.id.to_string(), new_x, new_y, node.w, node.h),
510 }));
511
512 Ok(())
513 }
514
515 /// Moves an existing item to a new position in the grid.
516 ///
517 /// If the move would cause collisions, affected items are automatically
518 /// repositioned to prevent overlap.
519 ///
520 /// # Arguments
521 ///
522 /// * `id` - ID of the item to move
523 /// * `new_x` - New X coordinate
524 /// * `new_y` - New Y coordinate
525 ///
526 /// # Returns
527 ///
528 /// * `Ok(())` - If move successful
529 /// * `Err(GridEngineError)` - If item doesn't exist or move invalid
530 ///
531 /// # Example
532 ///
533 /// ```
534 /// use grid_engine::grid_engine::GridEngine;
535 /// # use std::error::Error;
536 ///
537 /// # fn main() -> Result<(), Box<dyn Error>> {
538 ///
539 /// let mut grid = GridEngine::new(10, 10);
540 /// grid.add_item("box1".to_string(), 0, 0, 2, 2)?;
541 /// grid.move_item("box1", 2, 2)?; // Moves box to position 2,2
542 ///
543 /// // Check if the item was moved correctly
544 /// let item = grid.get_nodes();
545 /// assert_eq!(item.len(), 1);
546 /// assert_eq!(item[0].x(), &2);
547 /// assert_eq!(item[0].y(), &2);
548 ///
549 /// # Ok(())
550 /// # }
551 ///
552 /// ```
553 pub fn move_item(
554 &mut self,
555 id: &str,
556 new_x: usize,
557 new_y: usize,
558 ) -> Result<(), GridEngineError> {
559 let node = match self.items.get(id) {
560 Some(node) => node,
561 None => Err(GridEngineError::Item(ItemError::ItemNotFound {
562 id: id.to_string(),
563 }))?,
564 };
565
566 self.create_move_change(node.clone(), new_x, new_y, &mut self.grid.clone())?;
567
568 self.apply_changes(&self.pending_changes.clone())?;
569 self.pending_changes.clear();
570
571 Ok(())
572 }
573
574 /// Applies a batch of changes to the grid.
575 ///
576 /// This method handles the actual application of all pending changes to both
577 /// the grid structure and the item tracking system. Changes are applied in order,
578 /// and all operations are executed atomically - if any change fails, none of
579 /// the changes will be applied.
580 ///
581 /// After successful application, triggers change events to notify any registered listeners.
582 ///
583 /// # Arguments
584 ///
585 /// * `changes` - Vector of changes to apply (Add, Remove, or Move operations)
586 ///
587 /// # Returns
588 ///
589 /// * `Ok(())` - If all changes were applied successfully
590 /// * `Err(GridEngineError)` - If any change application fails
591 /// ```
592 fn apply_changes(&mut self, changes: &[Change]) -> Result<(), GridEngineError> {
593 for change in changes.iter() {
594 match &change {
595 Change::Add(data) => {
596 let node = &data.value;
597
598 node.update_grid(&mut self.grid, UpdateGridOperation::Add)?;
599
600 self.items.insert(node.id.to_string(), node.clone());
601 }
602 Change::Remove(data) => {
603 let node = &data.value;
604
605 node.update_grid(&mut self.grid, UpdateGridOperation::Remove)?;
606
607 self.items.remove(&node.id);
608 }
609 Change::Move(data) => {
610 let node = &data.new_value;
611 let old_node = &data.old_value;
612
613 old_node.update_grid(&mut self.grid, UpdateGridOperation::Remove)?;
614
615 self.items.insert(node.id.to_string(), node.clone());
616
617 node.update_grid(&mut self.grid, UpdateGridOperation::Add)?;
618 }
619 }
620 }
621
622 self.events
623 .trigger_changes_event(&ChangesEventValue::new(changes.to_vec()));
624 Ok(())
625 }
626
627 /// Returns a reference to the grid events system.
628 pub fn events(&self) -> &GridEvents {
629 &self.events
630 }
631
632 /// Returns a mutable reference to the grid events system.
633 pub fn events_mut(&mut self) -> &mut GridEvents {
634 &mut self.events
635 }
636}
637
638mod tests {
639 #[allow(unused_imports)]
640 use super::*;
641
642 #[test]
643 fn test_for_cell() {
644 let mut results = Vec::new();
645 let mut callback = |x: usize, y: usize| {
646 results.push((x, y));
647 Ok(())
648 };
649
650 for_cell(
651 ForCellArgs {
652 x: 1,
653 y: 2,
654 w: 2,
655 h: 2,
656 },
657 &mut callback,
658 )
659 .unwrap();
660
661 assert_eq!(results, vec![(1, 2), (1, 3), (2, 2), (2, 3)]);
662 }
663
664 #[test]
665 fn test_add_item() {
666 let mut engine = GridEngine::new(10, 10);
667 let item_0_id = engine
668 .add_item("0".to_string(), 0, 0, 2, 2)
669 .unwrap()
670 .id
671 .clone();
672
673 assert!(engine.items.len() == 1);
674 for_cell(
675 ForCellArgs {
676 x: 0,
677 y: 0,
678 w: 2,
679 h: 2,
680 },
681 &mut |x, y| {
682 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
683 Ok(())
684 },
685 )
686 .unwrap();
687 }
688
689 #[test]
690 fn test_add_item_handle_duplicated_id() {
691 let mut engine = GridEngine::new(10, 10);
692 engine.add_item("0".to_string(), 0, 0, 2, 2).unwrap();
693
694 assert!(engine.add_item("0".to_string(), 0, 0, 2, 2).is_err())
695 }
696
697 #[test]
698 fn test_add_item_handle_collision() {
699 let mut engine = GridEngine::new(10, 10);
700 let item_0_id = engine
701 .add_item("0".to_string(), 0, 0, 2, 2)
702 .unwrap()
703 .id
704 .clone();
705 let item_1_id = engine
706 .add_item("1".to_string(), 0, 0, 2, 2)
707 .unwrap()
708 .id
709 .clone();
710
711 // Item 0 should stay in position 0, 0
712 let item_0 = engine.items.get(&item_0_id).unwrap();
713 assert_eq!(item_0.x, 0);
714 assert_eq!(item_0.y, 2);
715 item_0
716 .for_cell(&mut |x, y| {
717 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
718 Ok(())
719 })
720 .unwrap();
721
722 // Item 1 should go to position 0, 2
723 let item_1 = engine.items.get(&item_1_id).unwrap();
724 assert_eq!(item_1.x, 0);
725 assert_eq!(item_1.y, 0);
726 item_1
727 .for_cell(&mut |x, y| {
728 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_1_id);
729 Ok(())
730 })
731 .unwrap();
732 }
733
734 #[test]
735 fn test_remove_item() {
736 let mut engine = GridEngine::new(10, 10);
737 let item_0_id = engine
738 .add_item("0".to_string(), 0, 0, 2, 3)
739 .unwrap()
740 .id
741 .clone();
742 engine.remove_item(&item_0_id).unwrap();
743 for_cell(
744 ForCellArgs {
745 x: 0,
746 y: 0,
747 w: 2,
748 h: 3,
749 },
750 &mut |x, y| {
751 let value = engine.grid.get(x, y).unwrap();
752 assert_eq!(value, &None);
753 Ok(())
754 },
755 )
756 .unwrap();
757 }
758
759 #[test]
760 fn test_move_item() {
761 let mut engine = GridEngine::new(10, 10);
762 let item_0_id = engine
763 .add_item("0".to_string(), 0, 0, 2, 2)
764 .unwrap()
765 .id
766 .clone();
767 engine.move_item(&item_0_id, 1, 1).unwrap();
768
769 // Asserts that its present on the new position
770 for_cell(
771 ForCellArgs {
772 x: 1,
773 y: 1,
774 w: 2,
775 h: 2,
776 },
777 &mut |x, y| {
778 let item_on_expected_position = engine.grid.get(x, y).unwrap().as_ref().unwrap();
779 assert_eq!(item_on_expected_position, &item_0_id);
780 Ok(())
781 },
782 )
783 .unwrap();
784
785 // Asserts that its not present on the old position
786 for_cell(
787 ForCellArgs {
788 x: 0,
789 y: 0,
790 w: 1,
791 h: 1,
792 },
793 &mut |x, y| {
794 assert_eq!(engine.grid.get(x, y).unwrap(), &None);
795 Ok(())
796 },
797 )
798 .unwrap();
799 }
800
801 #[test]
802 fn test_move_item_handle_collision() {
803 let mut engine = GridEngine::new(10, 10);
804 let item_0_id = engine
805 .add_item("0".to_string(), 0, 0, 2, 2)
806 .unwrap()
807 .id
808 .clone();
809 let item_1_id = engine
810 .add_item("1".to_string(), 0, 2, 2, 2)
811 .unwrap()
812 .id
813 .clone();
814 engine.move_item("0", 0, 1).unwrap();
815
816 // Item 0 should go to position 0, 1
817 let item_0 = engine.items.get(&item_0_id).unwrap();
818 assert_eq!(item_0.x, 0);
819 assert_eq!(item_0.y, 1);
820 item_0
821 .for_cell(&mut |x, y| {
822 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
823 Ok(())
824 })
825 .unwrap();
826
827 // Item 1 should go to position 0, 3
828 let item_1 = engine.items.get(&item_1_id).unwrap();
829 assert_eq!(item_1.x, 0);
830 assert_eq!(item_1.y, 3);
831 item_1
832 .for_cell(&mut |x, y| {
833 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_1_id);
834 Ok(())
835 })
836 .unwrap();
837 }
838
839 #[test]
840 fn test_will_collides_with() {
841 let mut engine = GridEngine::new(10, 10);
842 let item_0_id = engine
843 .add_item("0".to_string(), 0, 0, 1, 2)
844 .unwrap()
845 .id
846 .clone();
847
848 // Asserts that does not collide with self
849 assert!(
850 engine
851 .will_collides_with(
852 engine.items.get(&item_0_id).unwrap(),
853 0,
854 0,
855 &mut engine.grid.clone()
856 )
857 .unwrap()
858 .is_empty()
859 );
860
861 // Asserts that does not collide with empty position
862 assert!(
863 engine
864 .will_collides_with(
865 engine.items.get(&item_0_id).unwrap(),
866 2,
867 2,
868 &mut engine.grid.clone()
869 )
870 .unwrap()
871 .is_empty()
872 );
873
874 // Asserts that collide with occupied position
875 engine.add_item("1".to_string(), 1, 2, 1, 2).unwrap();
876
877 // Full collision
878 assert!(
879 engine
880 .will_collides_with(
881 engine.items.get(&item_0_id).unwrap(),
882 1,
883 2,
884 &mut engine.grid.clone()
885 )
886 .unwrap()
887 .len()
888 == 1
889 );
890
891 // Partial collision
892 assert!(
893 engine
894 .will_collides_with(
895 engine.items.get(&item_0_id).unwrap(),
896 1,
897 1,
898 &mut engine.grid.clone()
899 )
900 .unwrap()
901 .len()
902 == 1
903 );
904 }
905
906 #[test]
907 fn test_get_nodes() {
908 let mut engine = GridEngine::new(10, 10);
909 let item_0_id = engine
910 .add_item("0".to_string(), 0, 0, 2, 2)
911 .unwrap()
912 .id
913 .clone();
914 let item_1_id = engine
915 .add_item("1".to_string(), 0, 2, 2, 2)
916 .unwrap()
917 .id
918 .clone();
919
920 let nodes = engine.get_nodes();
921 assert_eq!(nodes.len(), 2);
922 assert_eq!(nodes[0].id, item_0_id);
923 assert_eq!(nodes[1].id, item_1_id);
924 }
925
926 #[test]
927 fn test_move_result_will_not_collides_with_moving_item() {
928 let mut engine = GridEngine::new(10, 10);
929 engine.add_item("0".to_string(), 0, 0, 2, 3).unwrap();
930 engine.add_item("1".to_string(), 0, 6, 2, 2).unwrap();
931 engine.move_item("1", 0, 2).unwrap();
932
933 for_cell(
934 ForCellArgs {
935 x: 0,
936 y: 7,
937 w: 2,
938 h: 2,
939 },
940 &mut |x, y| {
941 let value = engine.grid.get(x, y).unwrap();
942 println!("value: {:?}", value);
943 assert_ne!(value, &Some("1".to_string()));
944 Ok(())
945 },
946 )
947 .unwrap();
948 }
949
950 #[test]
951 fn test_node_movements_that_collides_twice_works() {
952 let mut engine = GridEngine::new(14, 10);
953 engine.add_item("0".to_string(), 1, 1, 2, 3).unwrap();
954 engine.add_item("1".to_string(), 2, 4, 2, 4).unwrap();
955 engine.add_item("2".to_string(), 0, 6, 2, 4).unwrap();
956 engine.move_item("2", 1, 2).unwrap();
957
958 println!("Items: {:#?}", engine.items);
959
960 engine.items.iter().for_each(|(_, node)| {
961 node.for_cell(&mut |x, y| {
962 let value = engine.grid.get(x, y).unwrap();
963 println!("Validating x: {}, y: {}", x, y);
964 assert_eq!(&Some(node.clone().id), value);
965 Ok(())
966 })
967 .unwrap();
968 });
969 }
970}