ruby dynamic class instance creation - ruby

I'm programming a board game as part of an Ruby OOP learning exercise. I'm attempting to create a game board consisting of nine spaces arranged in three rows of three. Below is a snippet of the currently non-functioning code:
class Board
def initialize()
#dynamically creates instances of the Space class named #s1 through #s9 with #s 1-9 as the location parameter
9.times do |x|
x += 1
y = "#s" + x.to_s
instance_variable_set( y , x.to_s) = Space.new
end
end
def print_board
#prints a 3X3 grid containing each instance's location parameter
puts "\n\n#{#s1.location}|#{#s2.location}|#{#s3.location}\n-----\n#{#s4.location}|#{#s5.location}|#{#s6.location}\n-----\n#{#s7.location}|#{#s8.location}|#{#s9.location}\n\n\n"
end
end
class Space
attr_accessor :location
def initialize(location)
#location = location
end
end
board = Board.new
board.print_board
The desired output is:
X|X|X
-----
X|X|X
-----
X|X|X
...but I keep getting errors related to the creation of instances of the Space class. I've tried so many different things that I can't remember them all. I know there has to be a way to do it like this, but I'm new to Ruby, and it has me stumped. I know could just create 9 individual instances of the Space class, but that seems cheap and dirty.

Here's a slightly re-worked, more Ruby-style version of same:
class Board
def initialize
#board = Array.new(9) do
Space.new
end
end
def move(x,y,set)
#board[(x - 1) + (y - 1) * 3].location = set
end
def to_s
#board.each_slice(3).collect do |row|
row.join('|')
end.join("\n------\n") + "\n"
end
end
class Space
attr_accessor :location
def initialize(location = nil)
#location = location || ' '
end
def to_s
#location
end
end
board = Board.new
puts board.to_s
board.move(1,2,'x')
puts board.to_s
It's worth noting a few tricks:
Array.new takes an argument as to how many entries to pre-populate with.
An Array can be given a block to evaluate that supplies the value to be used for default entries.
each_slice pulls out groups of N entries to process.
collect transforms each row into a simple string.
join combines the rows with horizontal lines.
to_s is the default name for "show as string" and should be used unless it's otherwise confusing.
Space needed a default of nil for the initializer
Methods that do not take arguments do not need brackets, so initializer is preferable to initializer().
The approach you're pursuing with instance_variable_set is a lot harder to maintain and violates the Zero, One or Infinity Rule by having nine variables that represent the cells. What you need is one variable of theoretically infinite size, bounded only by constraints you impose if necessary. Having multiple variables makes things much more difficult to iterate, makes it inconvenient to pass through as an argument, and generally makes a mess of what should be simple.

Related

Adding to Ruby Objects

I have been doing a little reading up on Ruby. Like how simplistic the language is. I've been trying to look it up, and figure it out on my own. I'm looking for some help with Objects and how I add data to it. I want to make an Object called Athlete where I read it in from a .txt or .csv file their Jersey Number and Name.
class Athlete
def setNumber (jNum)
#mynum = jNum
end
def getNumber
return #mynum
end
def setName (jName)
#myname = jName
end
def getName
return #myname
end
end
Is that how I would set up the class?
Then I read in the file:
myAthlete = Athlete.new
fileObj = File.new(uInput, "r")
while (line = fileObj.gets)
jData = line.split(" ")
myAthlete.setNumber(jData.at(0))
myAthlete.setName(jData.at(1))
end
fileObj.close
this is where I start to get a bit lost. I know it splits the data perfectly, because I've already tried this with just Array.new -- That being said, I'm trying to make the array inside of the Athlete class. Can someone assist me with this?
So if my input file is:
52 Sabathia
19 Tanaka
17 Holliday
24 Sanchez
I would like for it to split and then if I call lets say uhhh myAthlete(1) it'd print out Tanaka's stuff
The thing about Ruby to embrace out of the gate is how clean the syntax is and how a lot of Ruby style conventions are driven by that. For example, Ruby advises against methods with get and set in them, instead preferring things like name and name= for accessor and mutator methods.
Yes, you can have = at the end of a method name, just like you can have ? or !, each of which have taken to mean particular things.
It's also a given that the last operation you perform in a method is implicitly the return value, so there's no need for return.
Here's a simple refactoring of your code with that in mind:
class Athlete
def number
#number
end
def number=(value)
#number = value
end
def name
#name
end
def name=(value)
#name = value
end
end
You can reduce this even further since Ruby has a method that will generate these for you automatically called attr_accessor. You can also make your life a little easier by making an initialize method to populate these:
class Athlete
attr_accessor :number
attr_accessor :name
def initialize(number, name)
#number = number
#name = name
end
end
So to put this all together:
athletes = [ ]
File.readlines(input) do |line|
number, name = line.chomp.split(/\s+/)
athletes << Athlete.new(number, name)
end
A lot of Ruby code uses blocks to define operations that should happen. In this case readlines calls that block for each line read from the file. These lines include the \n newline at the end which chomp removes. << is a quick way to append something to an array.
Try to keep your variable and method names all lower-case as well. Capitals have significant meaning in Ruby, so jData should be jdata or j_data.
Update: To make this more debugger-friendly:
class Athlete
def inspect
"Athlete [%s] %s" % [ #number, #name ]
end
end
First off, you don't need to define explicit getters/setters. Something like this will do
class Athlete
attr_accessor :name, :number
def initialize(name, number)
self.name = name
self.number = number
end
end
Or even shorter:
Athlete = Struct.new(:name, :number)
Then to create athletes:
athletes = File.foreach(file_path).map do |line|
number, name = line.chomp.split(' ')
Athlete.new(name, number)
end
You will now have an array full of Athletes.

Using a method to generate multiple instances of another class

I am trying to let a method initiate multiple instances of another class.
class Players
def initialize(players)
#players = players
end
def generate_boards
#players.each do |player|
board = BingoBoardGenerator.new
player = BingoBoard.new(player, board.generate)
p player
end
end
end
players = ["Nick","Jiyoon","Mae","Lawson","Matthew"]
plays = Players.new(players)
plays.generate_boards
p player shows that five instances of BingoBoard were created properly, but I am not sure how to access them (or where they are). Any help on how to call these instances? Normally I would do:
nick = BingoBoard.new("Nick", board.generate)
nick.board
but when I instantiate them all together, I don't know how to set/access their instance name.
As indicated by user2864740, you can use :map instead of :each to return an array of BingoBoard instances. If you wanted to store those instances to use later, you can use memoization as shown below. The first time :bingo_board_instances is called, the boards will be generated and the instance variable #bingo_board_instances will be set so that future invocations of :bingo_board_instances will not result in the generation of new boards.
class Players
def initialize(players)
#players = players
end
def generate_boards
#players.map do |player|
board = BingoBoardGenerator.new
BingoBoard.new(player, board.generate)
end
end
def bingo_board_instances
#bingo_board_instances ||= generate_boards
end
end
While the above code works just fine, I think the more intuitive solution would be to have a Player class (instead of Players) and then pass in an array of Player instances when initializing a BingoBoardGenerator. With this approach, you can set an instance variable for each individual player and create a unique board that belongs to the player:
class BingoBoardGenerator
def initialize(args)
#dynamically set instance vars to handle n number of players
args.fetch(:players).each_with_index do |player,index|
instance_variable_set("#player_#{index+1}",player)
end
end
def generate_boards
instance_variables.map do |player|
player = instance_variable_get(instance_var)
#you will need to implement :generate_bingo_board on player...I would suggest using memoization so you can store the player's board for later retrieval
player.generate_bingo_board
end
end
end
#I have no idea what your implementation looks like...
b = BingoBoardGenerator.new(players: [Player.new("Jen"),Player.new("Phil"),Player.new("Mary"),Player.new("Bert")])
b.generate_boards
This would allow you to better encapsulate data that may belong to individual players, including the ability to ask each Player instance for its :board.

Making a Yhatzee game, array won't show up on screen

Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result

How to count instances of class without counting reassignment?

I am working on class methods.
I am trying to count the number of created instances of a class. I am able to do this by creating a counter variable in the initialize method.
The problem arises when I reassign the variable originally assigned to one class instance. Because the initialize method is called twice, it does not recognize that the variable is simply being reassigned to another class instance.
class Ticket
attr_accessor :price
attr_reader :event, :venue
##count = 0
##tickets = {}
def initialize(event, venue)
#event = event
#venue = venue
##count += 1
end
def self.count
##count
end
end
a = Ticket.new("Michael Buble", "Staples")
a = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket count of #{Ticket.count}"
When I run the above code in IRB, it gives me a Ticket count of 2 (as expected). How do I change my code so that it recognizes the overwrite?
NOTE: I know that this question has been asked before for Objective C, but the reassignment aspect of the question adds a different element to the problem. Let me know otherwise.
ObjectSpace.each_object(Ticket).count
Will give you the count of object currently in memory. On testing in IRB I find it runs into the problem you describe, objects persist in memory even though you have assigned a new one to the variable. Technically the object still exists, even though you assign a new instance to the variable "a".
See this article: Deleting an object in Ruby The answers have plenty of info about what you are trying to do.
In the real world you wouldn't be counting instances in memory, you'd be asking a database how many exist. You need to think in terms of a database.
Your use of a to repeatedly contain the Ticket instance is wrong. You should be using an Array, Hash or Set to maintain the list, then ask the container how many exist:
require 'set'
class Ticket
attr_accessor :price
attr_reader :event, :venue
##tickets = Set.new
def initialize(event, venue)
#event = event
#venue = venue
##tickets << self
end
def delete
##tickets.delete(self)
end
def self.count
##tickets.size
end
end
a = Ticket.new("Michael Buble", "Staples")
b = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket count of #{Ticket::count}"
b.delete
puts "Ticket count of #{Ticket::count}"
You can build this out by adding ways to retrieve a particular instance from ##tickets, add a to_s so you can list them, but, in the end, you'll want to use a real database. If your code were to crash for any reason, your entire list of tickets would disappear, which would be unacceptable in real life.
If you really want to count live instances of the Ticket class (for reasons I cannot fathom), #Beartech has the right idea:
class Ticket
attr_reader :event, :venue
def initialize(event, venue)
#event = event
#venue = venue
end
def self.count_live_instances
ObjectSpace.garbage_collect
ObjectSpace.each_object(self).to_a.size
end
end
a = Ticket.new("Michael Buble", "Staples")
b = Ticket.new("Cher", "Canadian Tire Center")
a = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket instances count = #{Ticket.count_live_instances}" # => 2
It is essential to garbage-collect before invoking ObjectSpace#each_object. If you are skeptical, insert p ObjectSpace.each_object(self).to_a.size as the first line of self.count_live_instances. It will print 3.
(There is also a method ObjectSpace#count_objects. This method returns a hash like this one: {:TOTAL=>56139,..., :T_ARRAY=>3139,..., :T_ICLASS=>32}. Unfortunately, the keys are "object types"; you won't find :TICKET among them.)
class Gs
def self.method1
code...
end
def self.method2
code...
end
def self.method3
code...
end
end
Gs.new
p Gs.singleton_methods.count
The Gs.singleton_methods.count will print 3
it will count the singleton methods,if we use the self keyword or
classname.method name..

Ruby, assert_equal of two arrays of objects

I have the following situation. I am trying to write a unit test for an array of objects. The object is defined something like this:
class Element
attr_reader :title, :season, :episode
def initialize ( name, number )
#name = name
#number = number
end
def to_s
number = "%02d" % #number
result = "Number " << number << " " << #name
result
end
end
During the test I assert two arrays which both contain three elements, the elements are identical and even the order is identical still I get an error that the assert isn't equal. I guess I am missing something really basic here, whats the catch?
If I compare each element by the to_s method, the assert is correct.. Is that the way it should be done in the first place?
Try declaring a method == for your class, with the following code.
def ==(other)
self.to_s == other.to_s
end
Sidenote, you might want to refactor your to_s method too, for some concise code.
def to_s
"Number %02d #{#name}" % #number
end
Edit:
Numbers already have an == method defined (https://github.com/evanphx/rubinius/blob/master/kernel/bootstrap/fixnum.rb#L117).
Ruby compares arrays by running an == compare on each element of an Array. Here's the implementation of == on Arrays, as done in Rubinius (a Ruby implementation written almost completely in Ruby itself) https://github.com/evanphx/rubinius/blob/master/kernel/common/array.rb#L474.
If you leave out various error detections, it basically runs an == on all the elements of the array, recursively, and returns true if all of them match.

Resources