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: String, x: usize, y: usize, w: usize, h: usize) -> Node {
210 Node::new(id, 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 nodes 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().cloned().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: String,
311 x: usize,
312 y: usize,
313 w: usize,
314 h: usize,
315 ) -> Result<&Node, GridEngineError> {
316 if self.items.contains_key(&id) {
317 return Err(GridEngineError::Item(ItemError::ItemAlreadyExists {
318 id: id.clone(),
319 }));
320 };
321
322 let node = self.new_node(id, x, y, w, h);
323 let node_id = node.id.to_string();
324
325 self.handle_collision(&node, x, y, &mut self.grid.clone())?;
326
327 self.create_add_change(node);
328
329 self.apply_changes(&self.pending_changes.clone())?;
330 self.pending_changes.clear();
331
332 let node = self
333 .items
334 .get(&node_id)
335 .ok_or(InnerGridError::MismatchedGridItem { id: node_id })?;
336 Ok(node)
337 }
338
339 fn create_remove_change(&mut self, node: &Node) {
340 self.pending_changes.push(Change::Remove(RemoveChangeData {
341 value: node.clone(),
342 }));
343 }
344
345 /// Removes an item from the grid by its ID.
346 ///
347 /// # Arguments
348 ///
349 /// * `id` - ID of the item to remove
350 ///
351 /// # Returns
352 ///
353 /// * `Ok(Node)` - The removed node
354 /// * `Err(GridEngineError)` - If item doesn't exist
355 ///
356 /// # Example
357 ///
358 /// ```
359 /// use grid_engine::grid_engine::GridEngine;
360 /// use std::error::Error;
361 ///
362 /// # fn main() -> Result<(), Box<dyn Error>> {
363 ///
364 /// let mut grid = GridEngine::new(10, 10);
365 /// grid.add_item("box1".to_string(), 0, 0, 2, 2)?;
366 /// grid.remove_item("box1")?; // Removes the item
367 ///
368 /// # Ok(())
369 /// # }
370 /// ```
371 pub fn remove_item(&mut self, id: &str) -> Result<Node, GridEngineError> {
372 let node = match self.items.get(id) {
373 Some(node) => node,
374 None => Err(GridEngineError::Item(ItemError::ItemNotFound {
375 id: id.to_string(),
376 }))?,
377 }
378 .clone();
379
380 self.create_remove_change(&node);
381
382 self.apply_changes(&self.pending_changes.clone())?;
383 self.pending_changes.clear();
384 Ok(node)
385 }
386
387 /// Checks if a node would collide with any existing items at the specified position.
388 ///
389 /// This is used internally to detect potential collisions before making grid changes.
390 /// It considers the node's dimensions and any existing items in the target area.
391 ///
392 /// # Returns
393 ///
394 /// * `Ok(Vec<&Node>)` - List of nodes that would collide with the given node
395 /// * `Err(InnerGridError)` - If position check fails (e.g., out of bounds)
396 fn will_collides_with(
397 &self,
398 node: &Node,
399 x: usize,
400 y: usize,
401 grid: &mut InnerGrid,
402 ) -> Result<Vec<&Node>, InnerGridError> {
403 let mut collides_with: Vec<&Node> = Vec::new();
404
405 for_cell(
406 ForCellArgs {
407 x,
408 y,
409 w: node.w,
410 h: node.h,
411 },
412 &mut |x, y| {
413 let cell = grid
414 .get(x, y)
415 .ok_or(InnerGridError::OutOfBoundsAccess { x, y })?;
416
417 match cell {
418 Some(cell_ref) => {
419 if cell_ref != &node.id {
420 let node = self.items.get(cell_ref).ok_or(
421 InnerGridError::MismatchedGridItem {
422 id: cell_ref.to_string(),
423 },
424 )?;
425
426 if !collides_with.contains(&node) {
427 collides_with.push(node);
428 }
429 }
430 }
431 None => {
432 // Nothing to collide with
433 }
434 }
435 Ok(())
436 },
437 )?;
438
439 Ok(collides_with)
440 }
441
442 /// Handles collision resolution when adding or moving items.
443 ///
444 /// When a collision is detected, this method:
445 /// 1. Identifies all affected items
446 /// 2. Calculates new positions for colliding items
447 /// 3. Creates appropriate move changes to relocate affected items
448 ///
449 /// The default collision resolution strategy moves affected items downward,
450 /// which may trigger dynamic grid expansion in the y-axis.
451 fn handle_collision(
452 &mut self,
453 node: &Node,
454 x: usize,
455 y: usize,
456 grid: &mut InnerGrid,
457 ) -> Result<(), InnerGridError> {
458 let collides_with = self
459 .will_collides_with(node, x, y, grid)?
460 .iter()
461 .map(|n| (*n).clone())
462 .collect::<Vec<Node>>();
463
464 for collided in collides_with {
465 let mut new_grid = grid.clone();
466
467 node.update_grid(&mut new_grid, UpdateGridOperation::Remove)?;
468 let new_x = collided.x;
469 let new_y = y + node.h;
470 self.create_move_change(collided, new_x, new_y, &mut new_grid)?;
471 }
472
473 Ok(())
474 }
475
476 /// Creates a change operation to move a node to a new position.
477 ///
478 /// This method:
479 /// 1. Handles any collisions at the new position
480 /// 2. Checks if the node was already scheduled to move
481 /// 3. Creates a Move change operation if needed
482 ///
483 /// # Arguments
484 ///
485 /// * `node` - The node to move
486 /// * `new_x` - Target x coordinate
487 /// * `new_y` - Target y coordinate
488 /// * `grid` - The grid to check for collisions
489 fn create_move_change(
490 &mut self,
491 node: Node,
492 new_x: usize,
493 new_y: usize,
494 grid: &mut InnerGrid,
495 ) -> Result<(), InnerGridError> {
496 let old_node = node.clone();
497 self.handle_collision(&node, new_x, new_y, grid)?;
498
499 let already_moved = self.pending_changes.iter().any(|change| match change {
500 Change::Move(data) => data.new_value.id == node.id,
501 _ => false,
502 });
503
504 if already_moved {
505 return Ok(());
506 }
507
508 self.pending_changes.push(Change::Move(MoveChangeData {
509 old_value: old_node,
510 new_value: Node::new(node.id.to_string(), new_x, new_y, node.w, node.h),
511 }));
512
513 Ok(())
514 }
515
516 /// Moves an existing item to a new position in the grid.
517 ///
518 /// If the move would cause collisions, affected items are automatically
519 /// repositioned to prevent overlap.
520 ///
521 /// # Arguments
522 ///
523 /// * `id` - ID of the item to move
524 /// * `new_x` - New X coordinate
525 /// * `new_y` - New Y coordinate
526 ///
527 /// # Returns
528 ///
529 /// * `Ok(())` - If move successful
530 /// * `Err(GridEngineError)` - If item doesn't exist or move invalid
531 ///
532 /// # Example
533 ///
534 /// ```
535 /// use grid_engine::grid_engine::GridEngine;
536 /// # use std::error::Error;
537 ///
538 /// # fn main() -> Result<(), Box<dyn Error>> {
539 ///
540 /// let mut grid = GridEngine::new(10, 10);
541 /// grid.add_item("box1".to_string(), 0, 0, 2, 2)?;
542 /// grid.move_item("box1", 2, 2)?; // Moves box to position 2,2
543 ///
544 /// // Check if the item was moved correctly
545 /// let item = grid.get_nodes();
546 /// assert_eq!(item.len(), 1);
547 /// assert_eq!(item[0].x(), &2);
548 /// assert_eq!(item[0].y(), &2);
549 ///
550 /// # Ok(())
551 /// # }
552 ///
553 /// ```
554 pub fn move_item(
555 &mut self,
556 id: &str,
557 new_x: usize,
558 new_y: usize,
559 ) -> Result<(), GridEngineError> {
560 let node = match self.items.get(id) {
561 Some(node) => node,
562 None => Err(GridEngineError::Item(ItemError::ItemNotFound {
563 id: id.to_string(),
564 }))?,
565 };
566
567 self.create_move_change(node.clone(), new_x, new_y, &mut self.grid.clone())?;
568
569 self.apply_changes(&self.pending_changes.clone())?;
570 self.pending_changes.clear();
571
572 Ok(())
573 }
574
575 /// Applies a batch of changes to the grid.
576 ///
577 /// This method handles the actual application of all pending changes to both
578 /// the grid structure and the item tracking system. Changes are applied in order,
579 /// and all operations are executed atomically - if any change fails, none of
580 /// the changes will be applied.
581 ///
582 /// After successful application, triggers change events to notify any registered listeners.
583 ///
584 /// # Arguments
585 ///
586 /// * `changes` - Vector of changes to apply (Add, Remove, or Move operations)
587 ///
588 /// # Returns
589 ///
590 /// * `Ok(())` - If all changes were applied successfully
591 /// * `Err(GridEngineError)` - If any change application fails
592 /// ```
593 fn apply_changes(&mut self, changes: &[Change]) -> Result<(), GridEngineError> {
594 for change in changes.iter() {
595 match &change {
596 Change::Add(data) => {
597 let node = &data.value;
598
599 node.update_grid(&mut self.grid, UpdateGridOperation::Add)?;
600
601 self.items.insert(node.id.to_string(), node.clone());
602 }
603 Change::Remove(data) => {
604 let node = &data.value;
605
606 node.update_grid(&mut self.grid, UpdateGridOperation::Remove)?;
607
608 self.items.remove(&node.id);
609 }
610 Change::Move(data) => {
611 let node = &data.new_value;
612 let old_node = &data.old_value;
613
614 old_node.update_grid(&mut self.grid, UpdateGridOperation::Remove)?;
615
616 self.items.insert(node.id.to_string(), node.clone());
617
618 node.update_grid(&mut self.grid, UpdateGridOperation::Add)?;
619 }
620 }
621 }
622
623 self.events
624 .trigger_changes_event(&ChangesEventValue::new(changes.to_vec()));
625 Ok(())
626 }
627
628 /// Returns a reference to the grid events system.
629 pub fn events(&self) -> &GridEvents {
630 &self.events
631 }
632
633 /// Returns a mutable reference to the grid events system.
634 pub fn events_mut(&mut self) -> &mut GridEvents {
635 &mut self.events
636 }
637}
638
639mod tests {
640 #[allow(unused_imports)]
641 use super::*;
642
643 #[test]
644 fn test_for_cell() {
645 let mut results = Vec::new();
646 let mut callback = |x: usize, y: usize| {
647 results.push((x, y));
648 Ok(())
649 };
650
651 for_cell(
652 ForCellArgs {
653 x: 1,
654 y: 2,
655 w: 2,
656 h: 2,
657 },
658 &mut callback,
659 )
660 .unwrap();
661
662 assert_eq!(results, vec![(1, 2), (1, 3), (2, 2), (2, 3)]);
663 }
664
665 #[test]
666 fn test_add_item() {
667 let mut engine = GridEngine::new(10, 10);
668 let item_0_id = engine
669 .add_item("0".to_string(), 0, 0, 2, 2)
670 .unwrap()
671 .id
672 .clone();
673
674 assert!(engine.items.len() == 1);
675 for_cell(
676 ForCellArgs {
677 x: 0,
678 y: 0,
679 w: 2,
680 h: 2,
681 },
682 &mut |x, y| {
683 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
684 Ok(())
685 },
686 )
687 .unwrap();
688 }
689
690 #[test]
691 fn test_add_item_handle_duplicated_id() {
692 let mut engine = GridEngine::new(10, 10);
693 engine.add_item("0".to_string(), 0, 0, 2, 2).unwrap();
694
695 assert!(engine.add_item("0".to_string(), 0, 0, 2, 2).is_err())
696 }
697
698 #[test]
699 fn test_add_item_handle_collision() {
700 let mut engine = GridEngine::new(10, 10);
701 let item_0_id = engine
702 .add_item("0".to_string(), 0, 0, 2, 2)
703 .unwrap()
704 .id
705 .clone();
706 let item_1_id = engine
707 .add_item("1".to_string(), 0, 0, 2, 2)
708 .unwrap()
709 .id
710 .clone();
711
712 // Item 0 should stay in position 0, 0
713 let item_0 = engine.items.get(&item_0_id).unwrap();
714 assert_eq!(item_0.x, 0);
715 assert_eq!(item_0.y, 2);
716 item_0
717 .for_cell(&mut |x, y| {
718 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
719 Ok(())
720 })
721 .unwrap();
722
723 // Item 1 should go to position 0, 2
724 let item_1 = engine.items.get(&item_1_id).unwrap();
725 assert_eq!(item_1.x, 0);
726 assert_eq!(item_1.y, 0);
727 item_1
728 .for_cell(&mut |x, y| {
729 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_1_id);
730 Ok(())
731 })
732 .unwrap();
733 }
734
735 #[test]
736 fn test_remove_item() {
737 let mut engine = GridEngine::new(10, 10);
738 let item_0_id = engine
739 .add_item("0".to_string(), 0, 0, 2, 3)
740 .unwrap()
741 .id
742 .clone();
743 engine.remove_item(&item_0_id).unwrap();
744 for_cell(
745 ForCellArgs {
746 x: 0,
747 y: 0,
748 w: 2,
749 h: 3,
750 },
751 &mut |x, y| {
752 let value = engine.grid.get(x, y).unwrap();
753 assert_eq!(value, &None);
754 Ok(())
755 },
756 )
757 .unwrap();
758 }
759
760 #[test]
761 fn test_move_item() {
762 let mut engine = GridEngine::new(10, 10);
763 let item_0_id = engine
764 .add_item("0".to_string(), 0, 0, 2, 2)
765 .unwrap()
766 .id
767 .clone();
768 engine.move_item(&item_0_id, 1, 1).unwrap();
769
770 // Asserts that its present on the new position
771 for_cell(
772 ForCellArgs {
773 x: 1,
774 y: 1,
775 w: 2,
776 h: 2,
777 },
778 &mut |x, y| {
779 let item_on_expected_position = engine.grid.get(x, y).unwrap().as_ref().unwrap();
780 assert_eq!(item_on_expected_position, &item_0_id);
781 Ok(())
782 },
783 )
784 .unwrap();
785
786 // Asserts that its not present on the old position
787 for_cell(
788 ForCellArgs {
789 x: 0,
790 y: 0,
791 w: 1,
792 h: 1,
793 },
794 &mut |x, y| {
795 assert_eq!(engine.grid.get(x, y).unwrap(), &None);
796 Ok(())
797 },
798 )
799 .unwrap();
800 }
801
802 #[test]
803 fn test_move_item_handle_collision() {
804 let mut engine = GridEngine::new(10, 10);
805 let item_0_id = engine
806 .add_item("0".to_string(), 0, 0, 2, 2)
807 .unwrap()
808 .id
809 .clone();
810 let item_1_id = engine
811 .add_item("1".to_string(), 0, 2, 2, 2)
812 .unwrap()
813 .id
814 .clone();
815 engine.move_item("0", 0, 1).unwrap();
816
817 // Item 0 should go to position 0, 1
818 let item_0 = engine.items.get(&item_0_id).unwrap();
819 assert_eq!(item_0.x, 0);
820 assert_eq!(item_0.y, 1);
821 item_0
822 .for_cell(&mut |x, y| {
823 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_0_id);
824 Ok(())
825 })
826 .unwrap();
827
828 // Item 1 should go to position 0, 3
829 let item_1 = engine.items.get(&item_1_id).unwrap();
830 assert_eq!(item_1.x, 0);
831 assert_eq!(item_1.y, 3);
832 item_1
833 .for_cell(&mut |x, y| {
834 assert_eq!(engine.grid.get(x, y).unwrap().as_ref().unwrap(), &item_1_id);
835 Ok(())
836 })
837 .unwrap();
838 }
839
840 #[test]
841 fn test_will_collides_with() {
842 let mut engine = GridEngine::new(10, 10);
843 let item_0_id = engine
844 .add_item("0".to_string(), 0, 0, 1, 2)
845 .unwrap()
846 .id
847 .clone();
848
849 // Asserts that does not collide with self
850 assert!(
851 engine
852 .will_collides_with(
853 engine.items.get(&item_0_id).unwrap(),
854 0,
855 0,
856 &mut engine.grid.clone()
857 )
858 .unwrap()
859 .is_empty()
860 );
861
862 // Asserts that does not collide with empty position
863 assert!(
864 engine
865 .will_collides_with(
866 engine.items.get(&item_0_id).unwrap(),
867 2,
868 2,
869 &mut engine.grid.clone()
870 )
871 .unwrap()
872 .is_empty()
873 );
874
875 // Asserts that collide with occupied position
876 engine.add_item("1".to_string(), 1, 2, 1, 2).unwrap();
877
878 // Full collision
879 assert!(
880 engine
881 .will_collides_with(
882 engine.items.get(&item_0_id).unwrap(),
883 1,
884 2,
885 &mut engine.grid.clone()
886 )
887 .unwrap()
888 .len()
889 == 1
890 );
891
892 // Partial collision
893 assert!(
894 engine
895 .will_collides_with(
896 engine.items.get(&item_0_id).unwrap(),
897 1,
898 1,
899 &mut engine.grid.clone()
900 )
901 .unwrap()
902 .len()
903 == 1
904 );
905 }
906
907 #[test]
908 fn test_get_nodes() {
909 let mut engine = GridEngine::new(10, 10);
910 let item_0_id = engine
911 .add_item("0".to_string(), 0, 0, 2, 2)
912 .unwrap()
913 .id
914 .clone();
915 let item_1_id = engine
916 .add_item("1".to_string(), 0, 2, 2, 2)
917 .unwrap()
918 .id
919 .clone();
920
921 let nodes = engine.get_nodes();
922 assert_eq!(nodes.len(), 2);
923 assert_eq!(nodes[0].id, item_0_id);
924 assert_eq!(nodes[1].id, item_1_id);
925 }
926
927 #[test]
928 fn test_move_result_will_not_collides_with_moving_item() {
929 let mut engine = GridEngine::new(10, 10);
930 engine.add_item("0".to_string(), 0, 0, 2, 3).unwrap();
931 engine.add_item("1".to_string(), 0, 6, 2, 2).unwrap();
932 engine.move_item("1", 0, 2).unwrap();
933
934 for_cell(
935 ForCellArgs {
936 x: 0,
937 y: 7,
938 w: 2,
939 h: 2,
940 },
941 &mut |x, y| {
942 let value = engine.grid.get(x, y).unwrap();
943 println!("value: {:?}", value);
944 assert_ne!(value, &Some("1".to_string()));
945 Ok(())
946 },
947 )
948 .unwrap();
949 }
950
951 #[test]
952 fn test_node_movements_that_collides_twice_works() {
953 let mut engine = GridEngine::new(14, 10);
954 engine.add_item("0".to_string(), 1, 1, 2, 3).unwrap();
955 engine.add_item("1".to_string(), 2, 4, 2, 4).unwrap();
956 engine.add_item("2".to_string(), 0, 6, 2, 4).unwrap();
957 engine.move_item("2", 1, 2).unwrap();
958
959 println!("Items: {:#?}", engine.items);
960
961 engine.items.iter().for_each(|(_, node)| {
962 node.for_cell(&mut |x, y| {
963 let value = engine.grid.get(x, y).unwrap();
964 println!("Validating x: {}, y: {}", x, y);
965 assert_eq!(&Some(node.clone().id), value);
966 Ok(())
967 })
968 .unwrap();
969 });
970 }
971}