I am implementing Game of Life in Ruby and here is what I have so far:
class Cell
attr_accessor :world, :x, :y
def initialize(world=World.new, x=0, y=0)
#world = world
#world.cells << self
#x = x
#y = y
end
def neighbours
#neighbours = []
world.cells.each do |cell|
# Detects neighbour to the north
if self.x == cell.x && self.y == cell.y - 1
#neighbours << cell
end
# Detects neighbour to the north-east
if self.x == cell.x - 1 && self.y == cell.y - 1
#neighbours << cell
end
# Detects neighbour to the east
if self.x == cell.x - 1 && self.y == cell.y
#neighbours << cell
end
# Detects neighbour to the south-east
if self.x == cell.x - 1 && self.y == cell.y + 1
#neighbours << cell
end
# Detects neighbour to the south
if self.x == cell.x && self.y == cell.y + 1
#neighbours << cell
end
# Detects neighbour to the south-west
if self.x == cell.x + 1 && self.y == cell.y + 1
#neighbours << cell
end
# Detects neighbour to the west
if self.x == cell.x + 1 && self.y == cell.y
#neighbours << cell
end
# Detects neighbour to the north-west
if self.x == cell.x + 1 && self.y == cell.y - 1
#neighbours << cell
end
end
#neighbours
end
def alive?
self.world.cells.include?(self)
end
def dead?
!self.world.cells.include?(self)
end
def die!
self.world.cells.delete(self)
end
def revive!
self.world.cells << self
end
end
class World
attr_accessor :cells
def initialize
#cells = []
end
def tick!
self.cells.each do |cell|
# Rule 1
if cell.neighbours.count < 2
cell.die!
end
end
end
end
I have been coding in Rails for a while but I am confused as to how to do the following things:
validate and make sure only one Cell object can exist on one field in the World?
How to save things to DB (postgresql for example) ? And do I have to in this case or can I just leave it as it and run it in memory?
How to make the graphical output of my Game of life so it looks something like this?
Reason for my confusion is because Rails does this right out of the box and now I just need the help of understanding how Ruby alone does this.
EDIT:
I've updated the Cell class with validation method, but I can only run it after the object is being initialized. Is there a way to run it while initializing? Here's the code:
5 def initialize(world=World.new, x=0, y=0) | 53 neighbour_cell = Cell.new(subject.world, -1, 0)
6 #world = world | 54 subject.neighbours.count.should == 1
7 #world.cells << self # if self.valid? < this after if doesn't work | 55 end
8 #x = x | 56
9 #y = y | 57 it 'Detects cell to the north-west' do
10 end | 58 neighbour_cell = Cell.new(subject.world, -1, 1)
11 | 59 subject.neighbours.count.should == 1
12 def valid? | 60 end
13 #valid = true | 61
14 self.world.cells.each do |cell| | 62 it 'Creates a live cell' do
15 if self.x == cell.x && self.y == cell.y | 63 cell.should be_alive
16 #valid = false | 64 end
17 self.world.cells.delete(self) | 65
18 end | 66 it 'Kills a cell' do
19 end | 67 cell.die!
20 #valid | 68 cell.should be_dead
21 end
It's good that you're breaking out of Rails and trying something different, especially a classic challenge like the Game of Life.
As for your questions, they all depend on the system design that you choose. But for a simple implementation here's a few pointers.
How about using the Set class? This is an array of objects that allows no duplicates.
You only need a DB if you want to maintain the state between separate runs of the application. If you don't need that, just keep it all in memory.
This is too big a question to answer here, but you have many options. The extremes are:
managing screen updates pixel rendering yourself, printing characters to the terminal as the cells change
using something like rubygame, which has a concept of sprites, GUIs and drawing
You are asking some big, general questions here, and each of them have many answers. I'll give my initial take on each:
1: Validation - It seems like (coming from rails) you want to have some kind of method like valid? that you can call on your objects. In pure ruby, though, you have to define explicitly what a "valid" object really is. You also have to decide where your validation should take place, i.e. which object is doing the validation and which object is being validated.
From the code presented above, I'd suggest that you need at least one more class, call it Game for example, which controls the world and all of the cells in it. Currently, you've loaded a lot of logic into the Cell class, and you might want to review the single responsibility principle to see if this is a valid (ha ha) design choice.
2. Persistence - There's nothing stopping you from using ActiveRecord in pure ruby as your ORM. You could check out this SO question for an example implementation: how-to-use-active-record-without-rails.
3. Graphics - There are a number of game libraries for ruby which have built-in graphics APIs for whichever platform you are on. I've not worked with them much personally, but gosu seems like a good option.
I have done it! But not with the approach taken here. Here is my code below, the only thing left to do is implement some graphic output. Also, here is the whole code on Github, check it out and comment and help me improve it and stuff. Thank you both for helping me :)
I could refactor this 'live_neighbours_around_cell(cell)' method, but don't know how. Can you help with that? Also, it would be useful to add 'Cell.new' immediately to 'cells' array and not like I do now where I have separate function for that.
class Game
attr_accessor :world
def initialize(world=World.new, seeds=[[1, 1]])
#world = world
seeds.each do |seed|
world.cell_board[seed[0]][seed[1]].alive = true
end
end
def tick!
next_round_live_cells = []
next_round_dead_cells = []
world.cells.each do |cell|
# Rule 1:
# Any live cell with fewer than two live neighbours dies
if world.live_neighbours_around_cell(cell).count < 2
next_round_dead_cells << cell
end
# Rule 2:
# Any live cell with two or three live neighbours lives on to the next generation
if cell.alive? && world.live_neighbours_around_cell(cell).count == (2 || 3)
next_round_live_cells << cell
end
# Rule 3:
# Any live cell with more than three live neighbours dies
if cell.alive? && world.live_neighbours_around_cell(cell).count > 3
next_round_dead_cells << cell
end
# Rule 4:
# Any dead cell with exactly three live neighbours becomes a live cell
if cell.dead? && world.live_neighbours_around_cell(cell).count == 3
next_round_live_cells << cell
end
end
next_round_live_cells.each do |cell|
cell.revive!
end
next_round_dead_cells.each do |cell|
cell.die!
end
end
end
class World
attr_accessor :rows, :cols, :cell_board, :cells
# Scheme of default initialized world matrix
#------------------------
# 0 1 2
# 0 [ dead, dead, dead ]
# 1 [ dead, alive, dead ]
# 2 [ dead, dead, dead ]
#-----------------------
def initialize(rows=3, cols=3)
#rows = rows
#cols = cols
#cells = []
#cell_board = Array.new(rows) do |row|
Array.new(cols) do |col|
Cell.new(row, col)
end
end
cell_board.each do |row|
row.each do |element|
if element.is_a?(Cell)
cells << element
end
end
end
end
def live_cells
cells.select { |cell| cell.alive }
end
def live_neighbours_around_cell(cell)
live_neighbours = []
live_cells.each do |live_cell|
# Neighbour to the North
if live_cell.x == cell.x - 1 && live_cell.y == cell.y
live_neighbours << live_cell
end
# Neighbour to the North-East
if live_cell.x == cell.x - 1 && live_cell.y == cell.y + 1
live_neighbours << live_cell
end
# Neighbour to the East
if live_cell.x == cell.x && live_cell.y == cell.y + 1
live_neighbours << live_cell
end
# Neighbour to the South-East
if live_cell.x == cell.x + 1 && live_cell.y == cell.y + 1
live_neighbours << live_cell
end
# Neighbour to the South
if live_cell.x == cell.x + 1 && live_cell.y == cell.y
live_neighbours << live_cell
end
# Neighbour to the South-West
if live_cell.x == cell.x + 1 && live_cell.y == cell.y - 1
live_neighbours << live_cell
end
# Neighbour to the West
if live_cell.x == cell.x && live_cell.y == cell.y - 1
live_neighbours << live_cell
end
# Neighbour to the North-West
if live_cell.x == cell.x - 1 && live_cell.y == cell.y - 1
live_neighbours << live_cell
end
end
live_neighbours
end
end
class Cell
attr_accessor :x, :y, :alive
def initialize(x=0, y=0)
#x = x
#y = y
#alive = false
end
def alive?
alive
end
def dead?
!alive
end
def die!
#alive = false
end
def revive!
#alive = true # same as > self.alive = true
end
end
Related
Example:
class CustomObject
.....
def ==(other)
self.x == other.x && self.y =! other.y
end
.....
end
array_of_custom_objects = CustomObject.load_for(company_id: company_id)
new_custom_object = CustomObject.new(....)
array_of_custom_objects.include? new_custom_object
My question is does the array include? method compare two objects bases on the defination of == method?
Bascially, will the above code determine whether my new_custom_object is included in the array of CustomObject by evaluating the overridden == method for each insance of CustomObject in the array with new_custom_object?
My question is does the array include? method compare two objects bases on the defination of == method?
Yes. As said in: https://ruby-doc.org/3.2.0/Array.html#method-i-include-3F
include?(obj) → true or false click to toggle source
Returns true if for some index i in self, obj == self[i]; otherwise false:
Seems to be working, (though I'm not sure if this is the most optimal way of doing things as we don't know the context of your code):
class CustomObject
attr_reader :x, :y, :z
def initialize(x, y, z)
#x = x
#y = y
#z = z
end
def ==(other)
self.x == other.x && self.y != other.y
end
end
custom_objects = []
new_custom_object_1 = CustomObject.new(1, 2, 3)
custom_objects << new_custom_object_1
new_custom_object_2 = CustomObject.new(2, 3, 4)
custom_objects << new_custom_object_2
search_object = CustomObject.new(2, 7, 4) # 2 == 2 && 3 != 7
puts custom_objects.include?(search_object)
# => true
search_object = CustomObject.new(2, 3, 4) # 2 == 2 && 3 != 3
puts custom_objects.include?(search_object)
# => false
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.
I apologize in advance if this is a re-post, but I have read other posts on the topic and I am still can't figure out what to do. I am trying to implement Negamax in a ruby tic tac toe game and I am getting a stack overflow error on line 55. I have read so many posts and articles on Negamax and I still can't get it to work.
This file isn't part of a larger program yet, I just wanted to pass in a board and see if it would make a move.
class Negamax
attr_accessor :board, :mark, :depth, :winning_routes
def initialize
#board = ["X","2","3","4","5","6","7","8","9"]
#mark = "O"
#depth = 1
#winning_routes = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
end
def negamax(board, mark, depth)
if winner?(mark) || isboardfull?(board)
return game_result(mark)
else
max = -1.0/0
mark == "O" ? opponent = "X" : "O"
available_moves.each do |space|
board[space] = mark
score = -negamax(board, opponent, depth + 1)
board[space] = "#{space + 1}"
if score > max
max = score
best_move = space if depth == 1
board[best_move] = mark
end
end
return max
end
end
def available_moves()
board.each_index.select { |s| board[s] != "X" && board[s] != "O"}
end
def isboardfull?(board)
boardtos = board.join(",")
boardtos =~ /\d/ ? false : true
end
def game_result(mark)
if winner?(mark)
return 1
elsif winner?(mark == "O" ? "X" : "O")
return -1
else
return 0
end
end
def winner?(mark)
result = false
marker = mark
winning_routes.each do |group|
if board[group[0]] == marker && board[group[1]] == marker && board[group[2]] == marker
result = true
end
end
result
end
end
game = Negamax.new()
game.negamax(game.board, game.mark, game.depth)
print game.board
I try to solve this problem http://www.nattee.net/~dae/algo/prob/hw03b_tiling/problem.pdf
So I using divide and conquer method to solve it but when I execute my program I get
tile.rb:7: stack level too deep (SystemStackError)
And this is my code
def tile (x, y, bx, by, ex, ey)
mx = (bx+ex)/2
my = (by+ey)/2
if (by<=y && y<=my)
if (bx<=x && x<=mx) # top-left
puts "0 #{mx} #{my}"
elsif (mx+1<=x && x<=ex) # top-right
puts "1 #{mx} #{my}"
end
elsif (my+1<=y && y<=ey)
if (bx<=x && x<=mx) # bottom-left
puts "2 #{mx} #{my}"
elsif (mx+1<=x && x<=ex) # bottom-right
puts "3 #{mx} #{my}"
end
end
tile(x,y,bx,by,mx,my) #top-left
tile(x,y,mx+1,by,ey,my) #top-right
tile(x,y,bx,my+1,mx+1,ey) #bottom-left
tile(x,y,mx+1,my+1,ex,ey) #bottom-right
if ex-bx == 2 && ey-by == 2 then return end
end
temp = []
gets.chomp.strip.split(" ").each do |item|
temp << item.to_i
end
L = temp[0]
x = temp[1]
y = temp[2]
tile(x,y,0,0,L-1,L-1)
I can't find the cause.
There is no way out of your recursion - for every call to tile, it will make 4 more calls to tile. Recursion always needs a "relief valve" way out before the recursion call. Try moving your return up before the tile calls.
The return statement could stand to be written in more idiomatic ruby.
Try:
return if (ex-bx == 2 && ey-by == 2)
I am triggering endless recursion when trying to make a method that pulls up tiles when they are a zero. I have been testing by entering the following in irb:
class Board
attr_accessor :size, :board
def initialize(size = gets.chomp.to_i)
#size = size
#board = (1..#size).map { |x| ["L"] * #size }
end
def print_board
#board.map { |row| puts row.join }
end
end
class Mine
attr_accessor :proxi, :row, :col
def initialize(proxi)
#proxi = proxi
#row = 0
#col = 0
#random = Random.new
check_position
end
def check_position
if #proxi.board[#row - 1][#col - 1] != "L"
#row = #random.rand(1..#proxi.board.length)
#col = #random.rand(1..#proxi.board[0].length)
check_position
else
map_position
end
end
def map_position
#proxi.board[#row - 1][#col - 1] = "*"
end
end
b = Board.new(20)
m = (1..b.size * 2).map { |i| i = Mine.new(b) }
class Detector
attr_accessor :board, :proxi, :row, :col, :value
def initialize(board, proxi)
#board = board
#proxi = proxi
#row = 0
#col = 0
#value = 0
end
def mine?
if #proxi.board[#row - 1][#col - 1] == "*"
true
else
false
end
end
def detect
(#row - 1..#row + 1).each do |r|
(#col - 1..#col + 1).each do |c|
unless (r - 1 < 0 || r - 1 > #proxi.size - 1) || (c - 1 < 0 || c - 1 > #proxi.size - 1)
#value += 1 if #proxi.board[r - 1][c - 1] == "*"
end
end
end
end
def map_position
#proxi.board[#row - 1][#col - 1] = #value
#board.board[#row - 1][#col - 1] = #value
end
def recursion
if #proxi.board[#row - 1][#col - 1] == 0
(#row - 1..#row + 1).each do |r|
(#col - 1..#col + 1).each do |c|
unless (r - 1 < 0 || r - 1 > #proxi.size - 1) || (c - 1 < 0 || c - 1 > #proxi.size - 1)
#row, #col = r, c
detect
map_position
recursion
end
end
end
end
end
def reset
#row, #col, #value = 0, 0, 0
end
end
d = Detector.new(b, b)
b.print_board
If the output has plenty of free space in the upper right corner proceed to pasting the next part, else repaste.
d.row = 1
d.col = 1
d.mine?
d.detect
d.map_position
d.recursion
b.print_board
It will error out with a stack level too deep error at the recursion method. I know this is because it is having issues ending the recursive pattern. I thought my two unless statements deterring it from searching off the board would limit it to the area in the board. Plus the mines would force it to be limited in zeros it can expose. Maybe it is somehow writing spaces off the board or overwriting things on the board?
You don’t need a recursion here. Simply check each position for mines around:
Please always use 0-based arrays to eliminate lots of #blah - 1.
In detect you need to return immediately if there is a mine and set the #value otherwise:
def detect
return if #proxi.board[#row][#col] == '*'
value = 0 # no need to be global anymore
(#row - 1..#row + 1).each do |r|
(#col - 1..#col + 1).each do |c|
unless r < 0 || r >= #proxi.size || c < 0 || c >= #proxi.size
value += 1 if #proxi.board[r][c] == "*"
end
end
end
#proxi.board[#row][#col] = value
end
Now you don’t need map_position method at all. Simply check all the cells:
def check
(0..#proxi.size - 1).each do |r|
(0..#proxi.size - 1).each do |c|
#row, #col = r, c
detect
end
end
end
Hope it helps.
Exceeding the stack size is usually an indication that your recursion does not have the correct terminating condition. In your case, what mechanism is in place to prevent recursion from being called multiple times with the same #row #col pair? Note that of the 9 pairs that (#row - 1..#row + 1) (#col - 1..#col + 1) produce, one of those pairs is #row #col itself. The function will call itself infinitely many times!
A simple way to solve this would be to have something like a revealed array that keeps track of visited cells. Then recursion would mark each cell it visits as visited and return immediately if it is called on an already visited cell.
Additionally, your use of instance variables is extremely fragile here. Recursion relies on the fact that each function call has its own scope, but every call of recursion shares the same instance variables - which you're using to pass arguments! You should be using method arguments to keep the scopes distinct.