summaryrefslogtreecommitdiffhomepage
path: root/samples/13_path_finding_algorithms/05_dijkstra/app
diff options
context:
space:
mode:
authorAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
committerAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
commita4b9c048a1d751f5226833bb0c527ba1a8ac5d09 (patch)
tree3f2535e7a6272e796d50e7f07c906d4c9eb1b14a /samples/13_path_finding_algorithms/05_dijkstra/app
parenta24a71805b1924ae7f80776c736f94575c171d2c (diff)
downloaddragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.tar.gz
dragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.zip
Synced with 2.3.
Diffstat (limited to 'samples/13_path_finding_algorithms/05_dijkstra/app')
-rw-r--r--samples/13_path_finding_algorithms/05_dijkstra/app/main.rb844
1 files changed, 844 insertions, 0 deletions
diff --git a/samples/13_path_finding_algorithms/05_dijkstra/app/main.rb b/samples/13_path_finding_algorithms/05_dijkstra/app/main.rb
new file mode 100644
index 0000000..b335447
--- /dev/null
+++ b/samples/13_path_finding_algorithms/05_dijkstra/app/main.rb
@@ -0,0 +1,844 @@
+# Demonstrates how Dijkstra's Algorithm allows movement costs to be considered
+
+# Inspired by https://www.redblobgames.com/pathfinding/a-star/introduction.html
+
+# The first grid is a breadth first search with an early exit.
+# It shows a heat map of all the cells that were visited by the search and their relative distance.
+
+# The second grid is an implementation of Dijkstra's algorithm.
+# Light green cells have 5 times the movement cost of regular cells.
+# The heat map will darken based on movement cost.
+
+# Dark green cells are walls, and the search cannot go through them.
+class Movement_Costs
+ attr_gtk
+
+ # This method is called every frame/tick
+ # Every tick, the current state of the search is rendered on the screen,
+ # User input is processed, and
+ # The next step in the search is calculated
+ def tick
+ defaults
+ render
+ input
+ calc
+ end
+
+ def defaults
+ # Variables to edit the size and appearance of the grid
+ # Freely customizable to user's liking
+ grid.width ||= 10
+ grid.height ||= 10
+ grid.cell_size ||= 60
+ grid.rect ||= [0, 0, grid.width, grid.height]
+
+ # The location of the star and walls of the grid
+ # They can be modified to have a different initial grid
+ # Walls are stored in a hash for quick look up when doing the search
+ state.star ||= [1, 5]
+ state.target ||= [8, 4]
+ state.walls ||= {[1, 1] => true, [2, 1] => true, [3, 1] => true, [1, 2] => true, [2, 2] => true, [3, 2] => true}
+ state.hills ||= {
+ [4, 1] => true,
+ [5, 1] => true,
+ [4, 2] => true,
+ [5, 2] => true,
+ [6, 2] => true,
+ [4, 3] => true,
+ [5, 3] => true,
+ [6, 3] => true,
+ [3, 4] => true,
+ [4, 4] => true,
+ [5, 4] => true,
+ [6, 4] => true,
+ [7, 4] => true,
+ [3, 5] => true,
+ [4, 5] => true,
+ [5, 5] => true,
+ [6, 5] => true,
+ [7, 5] => true,
+ [4, 6] => true,
+ [5, 6] => true,
+ [6, 6] => true,
+ [7, 6] => true,
+ [4, 7] => true,
+ [5, 7] => true,
+ [6, 7] => true,
+ [4, 8] => true,
+ [5, 8] => true,
+ }
+
+ # What the user is currently editing on the grid
+ # We store this value, because we want to remember the value even when
+ # the user's cursor is no longer over what they're interacting with, but
+ # they are still clicking down on the mouse.
+ state.user_input ||= :none
+
+ # Values that are used for the breadth first search
+ # Keeping track of what cells were visited prevents counting cells multiple times
+ breadth_first_search.visited ||= {}
+ # The cells from which the breadth first search will expand
+ breadth_first_search.frontier ||= []
+ # Keeps track of which cell all cells were searched from
+ # Used to recreate the path from the target to the star
+ breadth_first_search.came_from ||= {}
+
+ # Keeps track of the movement cost so far to be at a cell
+ # Allows the costs of new cells to be quickly calculated
+ # Also doubles as a way to check if cells have already been visited
+ dijkstra_search.cost_so_far ||= {}
+ # The cells from which the Dijkstra search will expand
+ dijkstra_search.frontier ||= []
+ # Keeps track of which cell all cells were searched from
+ # Used to recreate the path from the target to the star
+ dijkstra_search.came_from ||= {}
+ end
+
+ # Draws everything onto the screen
+ def render
+ render_background
+
+ render_heat_maps
+
+ render_star
+ render_target
+ render_hills
+ render_walls
+
+ render_paths
+ end
+ # The methods below subdivide the task of drawing everything to the screen
+
+ # Draws what the grid looks like with nothing on it
+ def render_background
+ render_unvisited
+ render_grid_lines
+ render_labels
+ end
+
+ # Draws two rectangles the size of the grid in the default cell color
+ # Used as part of the background
+ def render_unvisited
+ outputs.solids << [scale_up(grid.rect), unvisited_color]
+ outputs.solids << [move_and_scale_up(grid.rect), unvisited_color]
+ end
+
+ # Draws grid lines to show the division of the grid into cells
+ def render_grid_lines
+ for x in 0..grid.width
+ outputs.lines << vertical_line(x)
+ outputs.lines << shifted_vertical_line(x)
+ end
+
+ for y in 0..grid.height
+ outputs.lines << horizontal_line(y)
+ outputs.lines << shifted_horizontal_line(y)
+ end
+ end
+
+ # Easy way to draw vertical lines given an index for the first grid
+ def vertical_line column
+ scale_up([column, 0, column, grid.height])
+ end
+
+ # Easy way to draw horizontal lines given an index for the second grid
+ def horizontal_line row
+ scale_up([0, row, grid.width, row])
+ end
+
+ # Easy way to draw vertical lines given an index for the first grid
+ def shifted_vertical_line column
+ scale_up([column + grid.width + 1, 0, column + grid.width + 1, grid.height])
+ end
+
+ # Easy way to draw horizontal lines given an index for the second grid
+ def shifted_horizontal_line row
+ scale_up([grid.width + 1, row, grid.width + grid.width + 1, row])
+ end
+
+ # Labels the grids
+ def render_labels
+ outputs.labels << [175, 650, "Number of steps", 3]
+ outputs.labels << [925, 650, "Distance", 3]
+ end
+
+ def render_paths
+ render_breadth_first_search_path
+ render_dijkstra_path
+ end
+
+ def render_heat_maps
+ render_breadth_first_search_heat_map
+ render_dijkstra_heat_map
+ end
+
+ # Renders the breadth first search on the first grid
+ def render_breadth_first_search
+ end
+
+ # This heat map shows the cells explored by the breadth first search and how far they are from the star.
+ def render_breadth_first_search_heat_map
+ # For each cell explored
+ breadth_first_search.visited.each_key do | visited_cell |
+ # Find its distance from the star
+ distance = (state.star.x - visited_cell.x).abs + (state.star.y - visited_cell.y).abs
+ max_distance = grid.width + grid.height
+ # Get it as a percent of the maximum distance and scale to 255 for use as an alpha value
+ alpha = 255.to_i * distance.to_i / max_distance.to_i
+ outputs.solids << [scale_up(visited_cell), red, alpha]
+ end
+ end
+
+ def render_breadth_first_search_path
+ # If the search found the target
+ if breadth_first_search.visited.has_key?(state.target)
+ # Start from the target
+ endpoint = state.target
+ # And the cell it came from
+ next_endpoint = breadth_first_search.came_from[endpoint]
+ while endpoint and next_endpoint
+ # Draw a path between these two cells
+ path = get_path_between(endpoint, next_endpoint)
+ outputs.solids << [scale_up(path), path_color]
+ # And get the next pair of cells
+ endpoint = next_endpoint
+ next_endpoint = breadth_first_search.came_from[endpoint]
+ # Continue till there are no more cells
+ end
+ end
+ end
+
+ # Renders the Dijkstra search on the second grid
+ def render_dijkstra
+ end
+
+ def render_dijkstra_heat_map
+ dijkstra_search.cost_so_far.each do |visited_cell, cost|
+ max_cost = (grid.width + grid.height) #* 5
+ alpha = 255.to_i * cost.to_i / max_cost.to_i
+ outputs.solids << [move_and_scale_up(visited_cell), red, alpha]
+ end
+ end
+
+ def render_dijkstra_path
+ # If the search found the target
+ if dijkstra_search.came_from.has_key?(state.target)
+ # Get the target and the cell it came from
+ endpoint = state.target
+ next_endpoint = dijkstra_search.came_from[endpoint]
+ while endpoint and next_endpoint
+ # Draw a path between them
+ path = get_path_between(endpoint, next_endpoint)
+ outputs.solids << [move_and_scale_up(path), path_color]
+
+ # Shift one cell down the path
+ endpoint = next_endpoint
+ next_endpoint = dijkstra_search.came_from[endpoint]
+
+ # Repeat till the end of the path
+ end
+ end
+ end
+
+ # Renders the star on both grids
+ def render_star
+ outputs.sprites << [scale_up(state.star), 'star.png']
+ outputs.sprites << [move_and_scale_up(state.star), 'star.png']
+ end
+
+ # Renders the target on both grids
+ def render_target
+ outputs.sprites << [scale_up(state.target), 'target.png']
+ outputs.sprites << [move_and_scale_up(state.target), 'target.png']
+ end
+
+ def render_hills
+ state.hills.each_key do |hill|
+ outputs.solids << [scale_up(hill), hill_color]
+ outputs.solids << [move_and_scale_up(hill), hill_color]
+ end
+ end
+
+ # Draws the walls on both grids
+ def render_walls
+ state.walls.each_key do |wall|
+ outputs.solids << [scale_up(wall), wall_color]
+ outputs.solids << [move_and_scale_up(wall), wall_color]
+ end
+ end
+
+ def get_path_between(cell_one, cell_two)
+ path = nil
+ if cell_one.x == cell_two.x
+ if cell_one.y < cell_two.y
+ path = [cell_one.x + 0.3, cell_one.y + 0.3, 0.4, 1.4]
+ else
+ path = [cell_two.x + 0.3, cell_two.y + 0.3, 0.4, 1.4]
+ end
+ else
+ if cell_one.x < cell_two.x
+ path = [cell_one.x + 0.3, cell_one.y + 0.3, 1.4, 0.4]
+ else
+ path = [cell_two.x + 0.3, cell_two.y + 0.3, 1.4, 0.4]
+ end
+ end
+ path
+ end
+
+ # Representation of how far away visited cells are from the star
+ # Replaces the render_visited method
+ # Visually demonstrates the effectiveness of early exit for pathfinding
+ def render_breadth_first_search_heat_map
+ breadth_first_search.visited.each_key do | visited_cell |
+ distance = (state.star.x - visited_cell.x).abs + (state.star.y - visited_cell.y).abs
+ max_distance = grid.width + grid.height
+ alpha = 255.to_i * distance.to_i / max_distance.to_i
+ outputs.solids << [scale_up(visited_cell), red, alpha]
+ end
+ end
+
+ # Translates the given cell grid.width + 1 to the right and then scales up
+ # Used to draw cells for the second grid
+ # This method does not work for lines,
+ # so separate methods exist for the grid lines
+ def move_and_scale_up(cell)
+ cell_clone = cell.clone
+ cell_clone.x += grid.width + 1
+ scale_up(cell_clone)
+ end
+
+ # In code, the cells are represented as 1x1 rectangles
+ # When drawn, the cells are larger than 1x1 rectangles
+ # This method is used to scale up cells, and lines
+ # Objects are scaled up according to the grid.cell_size variable
+ # This allows for easy customization of the visual scale of the grid
+ def scale_up(cell)
+ # Prevents the original value of cell from being edited
+ cell = cell.clone
+
+ # If cell is just an x and y coordinate
+ if cell.size == 2
+ # Add a width and height of 1
+ cell << 1
+ cell << 1
+ end
+
+ # Scale all the values up
+ cell.map! { |value| value * grid.cell_size }
+
+ # Returns the scaled up cell
+ cell
+ end
+
+ # Handles user input every tick so the grid can be edited
+ # Separate input detection and processing is needed
+ # For example: Adding walls is started by clicking down on a hill,
+ # but the mouse doesn't need to remain over hills to add walls
+ def input
+ # If the mouse was lifted this tick
+ if inputs.mouse.up
+ # Set current input to none
+ state.user_input = :none
+ end
+
+ # If the mouse was clicked this tick
+ if inputs.mouse.down
+ # Determine what the user is editing and edit the state.user_input variable
+ determine_input
+ end
+
+ # Process user input based on user_input variable and current mouse position
+ process_input
+ end
+
+ # Determines what the user is editing and stores the value
+ # This method is called the tick the mouse is clicked
+ # Storing the value allows the user to continue the same edit as long as the
+ # mouse left click is held
+ def determine_input
+ # If the mouse is over the star in the first grid
+ if mouse_over_star?
+ # The user is editing the star from the first grid
+ state.user_input = :star
+ # If the mouse is over the star in the second grid
+ elsif mouse_over_star2?
+ # The user is editing the star from the second grid
+ state.user_input = :star2
+ # If the mouse is over the target in the first grid
+ elsif mouse_over_target?
+ # The user is editing the target from the first grid
+ state.user_input = :target
+ # If the mouse is over the target in the second grid
+ elsif mouse_over_target2?
+ # The user is editing the target from the second grid
+ state.user_input = :target2
+ # If the mouse is over a wall in the first grid
+ elsif mouse_over_wall?
+ # The user is removing a wall from the first grid
+ state.user_input = :remove_wall
+ # If the mouse is over a wall in the second grid
+ elsif mouse_over_wall2?
+ # The user is removing a wall from the second grid
+ state.user_input = :remove_wall2
+ # If the mouse is over a hill in the first grid
+ elsif mouse_over_hill?
+ # The user is adding a wall from the first grid
+ state.user_input = :add_wall
+ # If the mouse is over a hill in the second grid
+ elsif mouse_over_hill2?
+ # The user is adding a wall from the second grid
+ state.user_input = :add_wall2
+ # If the mouse is over the first grid
+ elsif mouse_over_grid?
+ # The user is adding a hill from the first grid
+ state.user_input = :add_hill
+ # If the mouse is over the second grid
+ elsif mouse_over_grid2?
+ # The user is adding a hill from the second grid
+ state.user_input = :add_hill2
+ end
+ end
+
+ # Processes click and drag based on what the user is currently dragging
+ def process_input
+ if state.user_input == :star
+ input_star
+ elsif state.user_input == :star2
+ input_star2
+ elsif state.user_input == :target
+ input_target
+ elsif state.user_input == :target2
+ input_target2
+ elsif state.user_input == :remove_wall
+ input_remove_wall
+ elsif state.user_input == :remove_wall2
+ input_remove_wall2
+ elsif state.user_input == :add_hill
+ input_add_hill
+ elsif state.user_input == :add_hill2
+ input_add_hill2
+ elsif state.user_input == :add_wall
+ input_add_wall
+ elsif state.user_input == :add_wall2
+ input_add_wall2
+ end
+ end
+
+ # Calculates the two searches
+ def calc
+ # If the searches have not started
+ if breadth_first_search.visited.empty?
+ # Calculate the two searches
+ calc_breadth_first
+ calc_dijkstra
+ end
+ end
+
+
+ def calc_breadth_first
+ # Sets up the Breadth First Search
+ breadth_first_search.visited[state.star] = true
+ breadth_first_search.frontier << state.star
+ breadth_first_search.came_from[state.star] = nil
+
+ until breadth_first_search.frontier.empty?
+ return if breadth_first_search.visited.has_key?(state.target)
+ # A step in the search
+ # Takes the next frontier cell
+ new_frontier = breadth_first_search.frontier.shift
+ # For each of its neighbors
+ adjacent_neighbors(new_frontier).each do | neighbor |
+ # That have not been visited and are not walls
+ unless breadth_first_search.visited.has_key?(neighbor) || state.walls.has_key?(neighbor)
+ # Add them to the frontier and mark them as visited in the first grid
+ breadth_first_search.visited[neighbor] = true
+ breadth_first_search.frontier << neighbor
+ # Remember which cell the neighbor came from
+ breadth_first_search.came_from[neighbor] = new_frontier
+ end
+ end
+ end
+ end
+
+ # Calculates the Dijkstra Search from the beginning to the end
+
+ def calc_dijkstra
+ # The initial values for the Dijkstra search
+ dijkstra_search.frontier << [state.star, 0]
+ dijkstra_search.came_from[state.star] = nil
+ dijkstra_search.cost_so_far[state.star] = 0
+
+ # Until their are no more cells to be explored
+ until dijkstra_search.frontier.empty?
+ # Get the next cell to be explored from
+ # We get the first element of the array which is the cell. The second element is the priority.
+ current = dijkstra_search.frontier.shift[0]
+
+ # Stop the search if we found the target
+ return if current == state.target
+
+ # For each of the neighbors
+ adjacent_neighbors(current).each do | neighbor |
+ # Unless this cell is a wall or has already been explored.
+ unless dijkstra_search.came_from.has_key?(neighbor) or state.walls.has_key?(neighbor)
+ # Calculate the movement cost of getting to this cell and memo
+ new_cost = dijkstra_search.cost_so_far[current] + cost(neighbor)
+ dijkstra_search.cost_so_far[neighbor] = new_cost
+
+ # Add this neighbor to the cells too be explored
+ dijkstra_search.frontier << [neighbor, new_cost]
+ dijkstra_search.came_from[neighbor] = current
+ end
+ end
+
+ # Sort the frontier so exploration occurs that have a low cost so far.
+ # My implementation of a priority queue
+ dijkstra_search.frontier = dijkstra_search.frontier.sort_by {|cell, priority| priority}
+ end
+ end
+
+ def cost(cell)
+ if state.hills.has_key?(cell)
+ return 5
+ else
+ return 1
+ end
+ end
+
+
+
+
+ # Moves the star to the cell closest to the mouse in the first grid
+ # Only resets the search if the star changes position
+ # Called whenever the user is editing the star (puts mouse down on star)
+ def input_star
+ old_star = state.star.clone
+ unless cell_closest_to_mouse == state.target
+ state.star = cell_closest_to_mouse
+ end
+ unless old_star == state.star
+ reset_search
+ end
+ end
+
+ # Moves the star to the cell closest to the mouse in the second grid
+ # Only resets the search if the star changes position
+ # Called whenever the user is editing the star (puts mouse down on star)
+ def input_star2
+ old_star = state.star.clone
+ unless cell_closest_to_mouse2 == state.target
+ state.star = cell_closest_to_mouse2
+ end
+ unless old_star == state.star
+ reset_search
+ end
+ end
+
+ # Moves the target to the grid closest to the mouse in the first grid
+ # Only reset_searchs the search if the target changes position
+ # Called whenever the user is editing the target (puts mouse down on target)
+ def input_target
+ old_target = state.target.clone
+ unless cell_closest_to_mouse == state.star
+ state.target = cell_closest_to_mouse
+ end
+ unless old_target == state.target
+ reset_search
+ end
+ end
+
+ # Moves the target to the cell closest to the mouse in the second grid
+ # Only reset_searchs the search if the target changes position
+ # Called whenever the user is editing the target (puts mouse down on target)
+ def input_target2
+ old_target = state.target.clone
+ unless cell_closest_to_mouse2 == state.star
+ state.target = cell_closest_to_mouse2
+ end
+ unless old_target == state.target
+ reset_search
+ end
+ end
+
+ # Removes walls in the first grid that are under the cursor
+ def input_remove_wall
+ # The mouse needs to be inside the grid, because we only want to remove walls
+ # the cursor is directly over
+ # Recalculations should only occur when a wall is actually deleted
+ if mouse_over_grid?
+ if state.walls.has_key?(cell_closest_to_mouse) or state.hills.has_key?(cell_closest_to_mouse)
+ state.walls.delete(cell_closest_to_mouse)
+ state.hills.delete(cell_closest_to_mouse)
+ reset_search
+ end
+ end
+ end
+
+ # Removes walls in the second grid that are under the cursor
+ def input_remove_wall2
+ # The mouse needs to be inside the grid, because we only want to remove walls
+ # the cursor is directly over
+ # Recalculations should only occur when a wall is actually deleted
+ if mouse_over_grid2?
+ if state.walls.has_key?(cell_closest_to_mouse2) or state.hills.has_key?(cell_closest_to_mouse2)
+ state.walls.delete(cell_closest_to_mouse2)
+ state.hills.delete(cell_closest_to_mouse2)
+ reset_search
+ end
+ end
+ end
+
+ # Adds a hill in the first grid in the cell the mouse is over
+ def input_add_hill
+ if mouse_over_grid?
+ unless state.hills.has_key?(cell_closest_to_mouse)
+ state.hills[cell_closest_to_mouse] = true
+ reset_search
+ end
+ end
+ end
+
+
+ # Adds a hill in the second grid in the cell the mouse is over
+ def input_add_hill2
+ if mouse_over_grid2?
+ unless state.hills.has_key?(cell_closest_to_mouse2)
+ state.hills[cell_closest_to_mouse2] = true
+ reset_search
+ end
+ end
+ end
+
+ # Adds a wall in the first grid in the cell the mouse is over
+ def input_add_wall
+ if mouse_over_grid?
+ unless state.walls.has_key?(cell_closest_to_mouse)
+ state.hills.delete(cell_closest_to_mouse)
+ state.walls[cell_closest_to_mouse] = true
+ reset_search
+ end
+ end
+ end
+
+ # Adds a wall in the second grid in the cell the mouse is over
+ def input_add_wall2
+ if mouse_over_grid2?
+ unless state.walls.has_key?(cell_closest_to_mouse2)
+ state.hills.delete(cell_closest_to_mouse2)
+ state.walls[cell_closest_to_mouse2] = true
+ reset_search
+ end
+ end
+ end
+
+ # Whenever the user edits the grid,
+ # The search has to be reset_searchd upto the current step
+ # with the current grid as the initial state of the grid
+ def reset_search
+ breadth_first_search.visited = {}
+ breadth_first_search.frontier = []
+ breadth_first_search.came_from = {}
+
+ dijkstra_search.frontier = []
+ dijkstra_search.came_from = {}
+ dijkstra_search.cost_so_far = {}
+ end
+
+
+
+ # Returns a list of adjacent cells
+ # Used to determine what the next cells to be added to the frontier are
+ def adjacent_neighbors(cell)
+ neighbors = []
+
+ # Gets all the valid neighbors into the array
+ # From southern neighbor, clockwise
+ neighbors << [cell.x , cell.y - 1] unless cell.y == 0
+ neighbors << [cell.x - 1, cell.y ] unless cell.x == 0
+ neighbors << [cell.x , cell.y + 1] unless cell.y == grid.height - 1
+ neighbors << [cell.x + 1, cell.y ] unless cell.x == grid.width - 1
+
+ # Sorts the neighbors so the rendered path is a zigzag path
+ # Cells in a diagonal direction are given priority
+ # Comment this line to see the difference
+ neighbors = neighbors.sort_by { |neighbor_x, neighbor_y| proximity_to_star(neighbor_x, neighbor_y) }
+
+ neighbors
+ end
+
+ # Finds the vertical and horizontal distance of a cell from the star
+ # and returns the larger value
+ # This method is used to have a zigzag pattern in the rendered path
+ # A cell that is [5, 5] from the star,
+ # is explored before over a cell that is [0, 7] away.
+ # So, if possible, the search tries to go diagonal (zigzag) first
+ def proximity_to_star(x, y)
+ distance_x = (state.star.x - x).abs
+ distance_y = (state.star.y - y).abs
+
+ if distance_x > distance_y
+ return distance_x
+ else
+ return distance_y
+ end
+ end
+
+ # When the user grabs the star and puts their cursor to the far right
+ # and moves up and down, the star is supposed to move along the grid as well
+ # Finding the cell closest to the mouse helps with this
+ def cell_closest_to_mouse
+ # Closest cell to the mouse in the first grid
+ x = (inputs.mouse.point.x / grid.cell_size).to_i
+ y = (inputs.mouse.point.y / grid.cell_size).to_i
+ # Bound x and y to the grid
+ x = grid.width - 1 if x > grid.width - 1
+ y = grid.height - 1 if y > grid.height - 1
+ # Return closest cell
+ [x, y]
+ end
+
+ # When the user grabs the star and puts their cursor to the far right
+ # and moves up and down, the star is supposed to move along the grid as well
+ # Finding the cell closest to the mouse in the second grid helps with this
+ def cell_closest_to_mouse2
+ # Closest cell grid to the mouse in the second
+ x = (inputs.mouse.point.x / grid.cell_size).to_i
+ y = (inputs.mouse.point.y / grid.cell_size).to_i
+ # Translate the cell to the first grid
+ x -= grid.width + 1
+ # Bound x and y to the first grid
+ x = 0 if x < 0
+ y = 0 if y < 0
+ x = grid.width - 1 if x > grid.width - 1
+ y = grid.height - 1 if y > grid.height - 1
+ # Return closest cell
+ [x, y]
+ end
+
+ # Signal that the user is going to be moving the star from the first grid
+ def mouse_over_star?
+ inputs.mouse.point.inside_rect?(scale_up(state.star))
+ end
+
+ # Signal that the user is going to be moving the star from the second grid
+ def mouse_over_star2?
+ inputs.mouse.point.inside_rect?(move_and_scale_up(state.star))
+ end
+
+ # Signal that the user is going to be moving the target from the first grid
+ def mouse_over_target?
+ inputs.mouse.point.inside_rect?(scale_up(state.target))
+ end
+
+ # Signal that the user is going to be moving the target from the second grid
+ def mouse_over_target2?
+ inputs.mouse.point.inside_rect?(move_and_scale_up(state.target))
+ end
+
+ # Signal that the user is going to be removing walls from the first grid
+ def mouse_over_wall?
+ state.walls.each_key do | wall |
+ return true if inputs.mouse.point.inside_rect?(scale_up(wall))
+ end
+
+ false
+ end
+
+ # Signal that the user is going to be removing walls from the second grid
+ def mouse_over_wall2?
+ state.walls.each_key do | wall |
+ return true if inputs.mouse.point.inside_rect?(move_and_scale_up(wall))
+ end
+
+ false
+ end
+
+ # Signal that the user is going to be removing hills from the first grid
+ def mouse_over_hill?
+ state.hills.each_key do | hill |
+ return true if inputs.mouse.point.inside_rect?(scale_up(hill))
+ end
+
+ false
+ end
+
+ # Signal that the user is going to be removing hills from the second grid
+ def mouse_over_hill2?
+ state.hills.each_key do | hill |
+ return true if inputs.mouse.point.inside_rect?(move_and_scale_up(hill))
+ end
+
+ false
+ end
+
+ # Signal that the user is going to be adding walls from the first grid
+ def mouse_over_grid?
+ inputs.mouse.point.inside_rect?(scale_up(grid.rect))
+ end
+
+ # Signal that the user is going to be adding walls from the second grid
+ def mouse_over_grid2?
+ inputs.mouse.point.inside_rect?(move_and_scale_up(grid.rect))
+ end
+
+ # These methods provide handy aliases to colors
+
+ # Light brown
+ def unvisited_color
+ [221, 212, 213]
+ end
+
+ # Camo Green
+ def wall_color
+ [134, 134, 120]
+ end
+
+ # Pastel White
+ def path_color
+ [231, 230, 228]
+ end
+
+ def red
+ [255, 0, 0]
+ end
+
+ # A Green
+ def hill_color
+ [139, 173, 132]
+ end
+
+ # Makes code more concise
+ def grid
+ state.grid
+ end
+
+ def breadth_first_search
+ state.breadth_first_search
+ end
+
+ def dijkstra_search
+ state.dijkstra_search
+ end
+end
+
+# Method that is called by DragonRuby periodically
+# Used for updating animations and calculations
+def tick args
+
+ # Pressing r will reset the application
+ if args.inputs.keyboard.key_down.r
+ args.gtk.reset
+ reset
+ return
+ end
+
+ # Every tick, new args are passed, and the Dijkstra tick method is called
+ $movement_costs ||= Movement_Costs.new(args)
+ $movement_costs.args = args
+ $movement_costs.tick
+end
+
+
+def reset
+ $movement_costs = nil
+end