Keeping track of the path, Knights travel - ruby

So far my shortest path method will stop when it reaches the goal position, printing out everything it did along the way. I would like to know how I could go about implementing parent positions so I can print the path along with the goal. This is an assignment.
class Knight
attr_accessor :x, :y, :prev_position, :moves
def initialize(position)
#x = position[0]
#y = position[1]
#prev_position = nil
#moves = [
[-1,-2],
[-2,-1],
[-2,+1],
[-1,+2],
[+1,-2],
[+2,-1],
[+2,+1],
[+1,+2]]
end
def possible
move_list = Array.new
#moves.each do |moves|
x = #x + moves[0]
y = #y + moves[1]
if x.between?(0,7)
if y.between?(0,7)
move_list << [x,y]
end
end
end
move_list
end
end
def shortest_path(position,goal)
paths = Array.new
#start_knight = Knight.new(position)
until #start_knight.x == goal[0] and
#start_knight.y == goal[1]
#start_knight.possible.each do |p| paths << p end
shifted = paths.shift
#start_knight.x = shifted[0]
#start_knight.y = shifted[1]
puts "[#{#start_knight.x},#{#start_knight.y}]"
end
end
shortest_path([0,0],[7,7])

Related

ruby packing variables and monkey patching

Okay so this is the code:
class Board
attr_reader :size
def initialize n
#grid = Array.new(n) {Array.new(n,:N)}
#size = n * n
end
def [] position
row, col = position
#grid[row][col]
end
def []= position, val
row, col = position
#grid[row][col] = val
end
def num_ships
#grid.flatten.count(:S)
end
def attack position
if self[position] == :S
self[position] = :H
puts "you sunk my battleship"
return true
else
self[position] = :X
return false
end
end
def place_random_ships
max_ships = #size * 0.25
while self.num_ships < max_ships
row = rand(0...#grid.length)
col = rand(0...#grid.length)
position = [row,col]
self[position] = :S
end
end
end
But,
def place_random_ships
max_ships = #size * 0.25
while self.num_ships < max_ships
row = rand(0...#grid.length)
col = rand(0...#grid.length)
position = [row,col]
self[position] = :S
end
end
this works, and does what it's suppose to, but when I avoid packing [row,col] and add it directly it does not work.
def place_random_ships
max_ships = #size * 0.25
while self.num_ships < max_ships
row = rand(0...#grid.length)
col = rand(0...#grid.length)
self[row,col] = :S
end
end
I'm still new to programming, so please try to explain the issue to where I can understand it, or tell me the problem, so I can google it to get a better understanding please.
The issue is that you defined []= to take 2 argument, and array and a value.
def []= position, val
row, col = position
#grid[row][col] = val
end
With your current implementation you would need to call it like this
foo[[row,col]] = :S
What you might want to to is define []= like this:
def []= row, col, val
#grid[row][col] = val
end
then when you want to pass the array position you can use the array spread operator. With this implementation both of these calls will work.
position = [1,2]
foo[1,2] = :S
foo[*position] = :S
if you do that you probably would want to define [] the same way.
def [] row,col
#grid[row][col]
end

Trouble making random spawning enemies shoot bullets

I have figured out how to randomly spawn enemies in different locations, but I cant figure out to make some of the enemies randomly fire bullets.
I created a Bullet class and a Enemy class. I use attr_reader to locate the enemies and call the x and y locations in the new Bullet method, but it fails to find where the enemy is located.
require 'gosu'
require_relative 'player'
require_relative 'enemy'
require_relative 'bullet'
class Proto < Gosu::Window
WIDTH = 1000
HEIGHT = 800
ENEMY_FREQUENCY = 0.03
attr_reader :x, :y, :radius, :angle
def initialize
super(WIDTH,HEIGHT)
self.caption = "Proto"
#player = Player.new(self)
#enemies = []
#bullets = []
#framecounter = 0
end
def update
#framecounter += 1
#player.turn_left if button_down?(Gosu::KbLeft)
#player.turn_right if button_down?(Gosu::KbRight)
#player.accelerate if button_down?(Gosu::KbUp)
#player.backward if button_down?(Gosu::KbDown)
#player.move
if rand < ENEMY_FREQUENCY
#enemies.push Enemy.new(self)
end
#enemies.each do |enemy|
enemy.move
if #framecounter % 60 == 0 && #enemies[3]
#bullets.push Bullet.new(self, #enemy.x, #enemy.y, #enemy.angle)
end
end
#bullets.each do |bullet|
bullet.move
end
end
def draw
#player.draw
#enemies.each do |enemy|
enemy.draw
end
#bullets.each do |bullet|
bullet.draw
end
end
end
window = Proto.new
window.show
class Enemy
SPEED = 1
attr_reader :x, :y, :radius, :angle
def initialize(window)
#radius = 20
#x = rand(window.width - 2 * #radius) + #radius
#y = 0
#image = Gosu::Image.new('ima/tile000.png')
end
def move
#y += SPEED
end
def draw
#image.draw(#x - #radius, #y - #radius, 2)
end
end
require_relative 'enemy'
class Bullet
SPEED = 5
def initialize(window, x, y, angle)
#x = x
#y = y
#direction = angle
#image = Gosu::Image.new('ima/tile000.png')
#imaget = Gosu::Image.new('ima/tile000.png')
#radius = 3
#window = window
end
def move
#y += SPEED
end
def draw
#image.draw(#x - #radius, #y - #radius, 1)
#imaget.draw(#enemy.x - radius, #enemy.y - #radius, 1)
end
end
I expect random enemies to fire bullets.
As discussed in the comments, the issue was that you had
#enemies.each do |enemy|
#enemy.draw
end
instead of
#enemies.each do |enemy|
enemy.draw
end

Adding value from parent node

I am trying to implement the A* Algorithm to find the shortest path from A to B. I am using a linked list with nodes to find the best path.
One of the values I need to calculate is g, which is the cost of movement from the starting point to the current node. Moving horizontal or vertical cost 10, and moving diagonal costs 14.
For example,
11 22 33
44 55 66
Moving from 11 to 22 cost 10. From 22 to 66 cost 14, so moving from 11 to 66 cost 24 total according to that particular path.
In my node class, I have a method that calculate g from the previous node to the current node, in the previous example, 22 to 66. In order to find the total g cost, I added the g value from the parent's node. However, this is extremely expensive and takes forever to run, especially when the number of possible paths is huge.
What can I do to speed up this algorithm? Is there a better way to calculate the g value?
Here is the relevant code from my Node class.
class Node
attr_accessor :position, :g, :h, :parent
def initialize(position, parent = nil)
#position = position
#parent = parent
if parent.nil?
#g = 0
else
#g = calc_g + #parent.g #<= this line costs the issue.
end
end
def calc_h(end_pos)
(#position.first - end_pos.first).abs + (#position.last - end_pos.last).abs
end
def calc_g
row_diff = (#position.first - self.parent.position.first).abs
col_diff = (#position.last - self.parent.position.last).abs
if [row_diff, col_diff] == [1,1]
g = 14
else
g = 10
end
end
end
Here is my MazeSolver class.
require_relative 'node'
class MazeSolver
attr_accessor :open_list, :closed_list, :maze, :start_node, :end_node, :current_node
def initialize(file_name)
#open_list = []
#closed_list = {}
#maze = create_maze_array(file_name)
#start_node = Node.new(find_point("S"))
#end_node = Node.new(find_point("E"))
#current_node = #start_node
end
def create_maze_array(file_name)
maze = File.readlines(file_name).map(&:chomp)
maze.map! do |line|
line.split('')
end
maze
end
def find_point(sym)
#maze.each_with_index do |line, row|
line.each_with_index do |el, col|
return [row, col] if el == sym
end
end
end
def find_neighbors(position)
row, col = position
(row-1..row+1).each do |r|
(col-1..col+1).each do |c|
next if #closed_list.values.include?([r, c])
unless #maze[r][c] == '*'
#open_list << Node.new([r, c], #current_node)
end
end
end
#open_list
end
def solved?
#current_node.position == #end_node.position
end
def solve
until solved?
#closed_list[#current_node] = #current_node.position
find_neighbors(#current_node.position)
#current_node = lowest_f(#open_list)
#open_list.delete(#current_node)
end
path = get_path(#current_node)
display_route(path)
end
# determine the lowest f score node
def lowest_f(nodes)
f_score = {}
nodes.each do |node|
f_score[node] = node.g + node.calc_h(#end_node.position)
end
f_score.sort_by { |node, score| score }.first.first
end
# create path array
def get_path(node)
path = []
until node.position == #start_node.position
path << node.position
node = node.parent
end
path
end
# display maze without route and maze with route
def display_route(path)
display_maze
path.each do |move|
unless #maze[move.first][move.last] == 'E'
#maze[move.first][move.last] = 'X'
end
end
puts
display_maze
end
# display the maze as it is.
def display_maze
#maze.each do |line|
puts line.join
end
end
end
if __FILE__ == $PROGRAM_NAME
puts "What is the name of the maze file?"
begin
MazeSolver.new(gets.chomp).solve
rescue
puts "File does not exist. Try Again"
retry
end
end
Here is the link to repl with example mazes that I worked with. maze1.txt is the one that takes a long time to run.

Ruby instance variable changes unexpectedly

In the code below, a cluster has many points.
class Cluster
attr_accessor :centroid, :points
def initialize(centroid, *points)
#centroid = centroid
#points = points
end
end
class Point
attr_accessor :x, :y
def initialize(x = 0, y = 0)
#x = x
#y = y
end
end
An example of a Cluster object (lets us call this c):
#<Cluster:0x007ff5c123c210
#centroid=#<Point:0x007ff5c123c288 #x=25, #y=125>,
#points=
[#<Point:0x007ff5c123c238 #x=25, #y=125>,
#<Point:0x007ff5c1020120 #x=28, #y=145>]>
I am trying to calculate the mean of the points, and update #centroid without changing #points.
Let's say I have:
class Point
def +(point)
#x = #x + point.x
#y = #y + point.y
self
end
def /(num)
#x = #x/num
#y = #y/num
self
end
end
and to calculate the mean of all the points, I run:
c.centroid = c.points.reduce(&:+)/c.points.length
Then, c changes to:
#<Cluster:0x007ff5c123c210
#centroid=#<Point:0x007ff5c1515ec8 #x=26, #y=135>,
#points=
[#<Point:0x007ff5c1515ec8 #x=26, #y=135>,
#<Point:0x007ff5c1020120 #x=28, #y=145>]>
Note that first element of the #points is changed. Any suggestions?
Your + method in Point modifies the point's members #x and #y. You need to return a new point with the calculated values instead:
def +(point)
Point.new(#x + point.x, #y + point.y)
end
def /(num)
Point.new(#x/num, #y/num)
end
Since you did not pass an initial value to reduce, the first point became modified. You can pass a new point as the initial value to reduce, which will be modified and returned.
c.centroid = c.points.reduce(Point.new, &:+)/c.points.length
I think the problem is caused by the + method modifying the point #x and #y values.
Try changing the + method to:
def +(point)
x = #x + point.x
y = #y + point.y
self.class.new(x, y)
end

Setting method local variables from a proc

If I have a class with two instance variables #x and #y, I can change them from a proc using self.instance_exec:
class Location
attr_accessor :x, :y
def initialize
#x = 0
#y = 0
end
def set &block
self.instance_exec(&block)
end
end
location = Location.new
location.set do
#x = rand(100)
#y = rand(100)
end
puts location.x
puts location.y
If I have a class with a method set with two local variables x and y, I can use proc return values to set them:
class Location
def set &block
x = 0;
y = 0;
x, y = block.call()
# do something with x and y
puts x
puts y
end
end
location = Location.new
location.set do
x = rand(100)
y = rand(100)
[x, y]
end
Is there a way to access the set method local variables x and y from the proc without using return values?
You can do it, sort of, but it isn't pretty
There is a way for block to set a variable in a calling method, but it isn't pretty. You can pass in a binding, then eval some code using the binding:
def foo(binding)
binding.eval "x = 2"
end
x = 1
foo(binding)
p x # => 2
Blocks also carry with them the binding in which they were defined, so if a block is being passed, then:
def foo(&block)
block.binding.eval "x = 2"
end
x = 1
foo {}
p x # => 2
What's in the block doesn't matter, in this case. It's just being used as a carrier for the binding.
Other ways to achieve the same goal
Yield an object
A more pedestrian way for a block to interact with it's caller is to pass an object to the block:
class Point
attr_accessor :x
attr_accessor :y
end
class Location
def set
point = Point.new
yield point
p point.x # => 10
p point.y # => 20
end
end
location = Location.new
location.set do |point|
point.x = 10
point.y = 20
end
This is often preferred to fancier techniques: It's easy to understand both its implementation and its use.
instance_eval an object
If you want to (but you probably shouldn't want to), the block's caller can use instance_eval/instance_exec to call the block. This sets self to the object, for that block.
class Location
def set(&block)
point = Point.new
point.instance_eval(&block)
p point.x # => 10
p point.y # => 20
end
end
location = Location.new
location.set do
self.x = 10
self.y = 20
end
You see that the block had to use use self. when calling the writers, otherwise new, local variables would have been declared, which is not what is wanted here.
Yield or instance_eval an object
Even though you still probably shouldn't use instance_eval, sometimes it's useful. You don't always know when it's good, though, so let's let the method's caller decide which technique to use. All the method has to do is to check that the block has parameters:
class Location
def set(&block)
point = Point.new
if block.arity == 1
block.call point
else
point.instance_eval(&block)
end
p point.x
p point.y
end
end
Now the user can have the block executed in the scope of the point:
location = Location.new
location.set do
self.x = 10
self.y = 20
end
# => 10
# => 20
or it can have the point passed to it:
location.set do |point|
point.x = 30
point.y = 40
end
# => 30
# => 40

Resources