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
Related
My following class is like so:
attr_reader :player, :player_choice, :cpu_choice, :choices, :game, :result
def initialize(player)
#player = player
#player_choice = ""
#cpu_choice = ""
#choices = Choices.new
#result = ""
end
def get_result
#result
end
def show_cpu_choice
#cpu_choice
end
def set_player_choice(choice)
#player_choice = choice
set_cpu_choice
decide_winner
end
def set_cpu_choice
#cpu_choice = #choices.get_choices.sample
end
I have omitted any irrelevant methods but basically I want to hardcode my #cpu_choice to "Scissors" for example so this following test can work,since my cpu choice is randomly generated but it is not working whichever method i am trying in rspec.
My set_cpu_choice randomonises from an array from an instance variable in my Choices class btw.
let(:game) {Game.new("Johnny")}
describe 'Player wins' do
it 'Player selects Rock and CPU has picked Scissors' do
game.set_player_choice("Rock")
allow(game).to receive(:show_cpu_choice).and_return("Scissors")
expect(game.get_result).to eq("Johnny")
end
end
I have tried the above in my rspec and I have also tried to do it using instance_variable_set but my test still keeps randomising the cpu choice.
I have also looked at something called srand but that looks too complicated for me since I dont understand any of it.
So I'll assume that decide_winner calls show_cpu_choice and sets #result. The problem is that when set_player_choice is called game hasn't yet been set up to return "Scissors". The allow should be moved before the call to set_player_choice
I have an issue I have been whacking my head against for hours now, and neither I nor anyone I have asked has been able to come up with a suitable answer.
Essentially, I am writing a method that allows me to edit an instance variable of another method. I have multiple ways of doing this, however my issue is with writing the test for this method. I have tried many different double types, however as they are immutable and do not store states, I did not manage to find a way to make it work.
Here is the class whose working variable is changed:
class MyClass
attr_writer :working
def working?
#working
end
end
Here is the class and method that change it:
class OtherClass
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
(The actual class is much larger, but I have only included a generalised version of the method in question. I can put all of the specific code up in a gist if it would help)
So I need a way to test that makes_work does in fact accept the array of objects to be changed, changes them and appends them to array_of_fixed_objects. What would be the best way of testing this in a containerised way, without requiring MyClass?
My last attempt was using spies to see what methods were called on my dummy instance, however a range of failures, depending on what I did. Here is the most recent test I wrote:
describe '#make_work' do
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(working = true)
end
end
This currently throws the error:
undefined method to_sym for true:TrueClass
Many thanks for any help! I apologise if some formatting/ info is a little bit messed up, I am still pretty new to this whole stackoverflow thing!
I think the problem is have_received(working = true), it should be have_received(:working=).with(true)
Edit:
Examples of using have_received
https://github.com/rspec/rspec-mocks#test-spies
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/setting-constraints/matching-arguments
This works for me
class MyClass
attr_writer :working
def working?
#working
end
end
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_fixed_objects = []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
describe '#make_work' do
subject { OtherClass.new }
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(:working=).with(true)
end
end
If you'd rather just avoid stubbing, you could use an instance of OpenStruct instead of a double:
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_instances_of_MyClass_to_fix, #ary_of_fixed_objects = [], []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
#ary_of_fixed_objects
end
end
require 'ostruct'
RSpec.describe "#makes_work" do
describe "given an array" do
let(:array) { [OpenStruct.new(working: nil)] }
subject { OtherClass.new }
before do
subject.ary_of_instances_of_MyClass_to_fix = array
end
it "sets the 'working' attribute for each element" do
expect(array.map(&:working)).to eq [nil]
subject.makes_work
expect(array.map(&:working)).to eq [true]
end
end
end
EDIT: For those criticizing my intentions with replacing self, you are free to click the back button, continue developing with your own opinions, and leave me to develop with mine :)
I was wondering if there is a way to completely remove the object that self references and replace it with a new instance.
Example:
def refresh_from_server!
self = newly_fetched_object_from_server
end
I don't want to return the new object.
It seems like I would have to build my own copying interface and call self.copy_from(other_object) but maybe someone has a cool ruby bit to share that works better!
--EDIT
Since some people seem unclear on the question, I want instance.my_method! to completely replace instance with a new instance of that class
For example lets imagine we have a class
class Counter
attr_accessor :count
def initialize
count = 0
end
def reset!
# This is what I want to achieve.
# Obviously in this case it would be trivial to write `self.count = 0`
# Or return a new value
# But I have a much more complex object in real life
# which copying would not be trivial
# All I'm looking for is a bit of stylistic sugar to make my code look cooler
# If it doesn't exist, I would love to know why
self = Counter.new
end
def up
count += 1
end
end
No, you can't replace self. You can only change some/all of its state, but the object reference will remain the same.
Why would you want to do this, anyway? If you just want to piggyback on your initialization logic (as it seems to me to be the case), some refactoring will help: just call a shared method from both places.
class Counter
attr_accessor :count
def initialize
init_state
end
def reset!
init_state
end
def up
self.count += 1
end
private
def init_state
self.count = 0
end
end
As already noted by others, self can't be replaced from enclosed instance. If replacement of instance with a new instance is required, it need to be done from outside, like in a class factory which registers its class instances.
Bellow is a simplest example using a delegator, demonstrating what I mean. SimpleDelegator represents a simple wrapper around Counter instance:
require 'delegate'
class Counter
attr_accessor :count
def initialize
#count = 0
end
end
class CounterDecorator < SimpleDelegator
def reset!
__setobj__(__getobj__.class.new)
end
end
c = CounterDecorator.new(Counter.new)
p c.__getobj__.object_id
c.count = 123
p c.count
c.reset!
p c.__getobj__.object_id
p c.count
# produces following output
20131160
123
20130900
0
Though the question is old, it is still visited. I will attempt to elaborate more on the "why" in "Why can't self be replaced in Ruby?".
usage of self in which context
https://web.archive.org/web/20191217060940/https://www.honeybadger.io/blog/ruby-self-cheat-sheet/
There are various contexts in which self can be used. You question uses it in the context of an instance method, so I will focus on that.
E.g. this context:
class SomeClass
def some_method
puts "#{self.class} - #{self.object_id} - #{self.inspect}"
end
end
a = SomeClass.new
a.some_method
# prints : SomeClass - 47013616336320 - #<SomeClass:0x000055846bcd7b80>
Note that there are other usages of self: e.g. where it reference the Class object in scope of a class definition. E.g.
class SomeClass
puts "#{self.class} - #{self.object_id} - #{self.inspect}"
end
# prints : Class - 47102719314940 - SomeClass
the intended effect of replacing self
Below code a demonstration of what you expected / wished (as I understand it):
class Counter
def some_method
puts "#{self.class} - #{self.object_id} - #{self.inspect}"
end
def refresh!
self = Counter.new # not possible
# results in error : "Can't change the value of self"
end
end
a = Counter.new
a.some_method
# prints : Counter - 47013616336320 - #<Counter:0x000055846bcd7b80>
a.refresh!
# now you wish a to point to a different object
But what about other references? E.g. assuming you wanted:
a = Counter.new
b = a
a.some_method
b.some_method
# both print same : Counter - 47013616336320 - #<Counter:0x000055846bcd7b80>
a.refresh!
# now you wish both a and b to point to the same (new) object
If stated as such it gives a hint on the why not.
why we can't replace self
The short answer is that it is simply not something that the language / interpreter offers. As to the reasoning: in a way #matthewd answers that in this answer:
All ruby variable references are essentially pointers (but not
pointers-to-pointers), in C parlance.
You can mutate an object (assuming it's not immutable), and all
variables that reference it will thus be pointing at the same (now
mutated) object. But the only way to change which object a variable is
referring to is with direct assignment to that variable -- and each
variable is a separate reference; you can't alias a single reference
with two names.
In short: there may be other references to that object in variables that are not in the scope of the instance method. These cannot be manipulated by that instance method.
a way to achieve the intended effect
If you want this effect and only want to touch the code of Counter you might move all methods and state to an inner class Counter::Inner and make Counter behave like a decoupled reference. The only 'state' of Counter would be the reference to the Counter::Inner object and Counter can delegate all calls it receives to that reference in a method_missing method. In case of your refresh! you can replace the reference in Counter same as you now intent to replace self. All outside code will now use indirectly the new Counter:Inner instance.
class Counter
class Inner
def some_method
puts "#{self.class} - #{self.object_id} - #{self.inspect}"
end
end
def initialize(*args)
#reference = Inner.new(*args)
end
def method_missing(method_id, *args)
#reference.send(method_id, *args)
end
def refresh!
#reference = Inner.new
end
end
a = Counter.new
b = a
a.some_method
b.some_method
# both print same : Counter::Inner - 46991238242100 - #<Counter::Inner:0x0000557a00203e68>
a.refresh!
a.some_method
b.some_method
# both print same : Counter::Inner - 46991238240000 - #<Counter::Inner:0x0000557a00202e00>
Just one more answer for the archives :-) I hope this gives useful insights to future visitors.
I have an empty object that accepts calculated values for each factor. The method is readable but long and ugly. What is a DRY way of doing this?
class ReadingScore
def initialize(reading, score)
#reading = reading
#score = score
end
def assign_scoring_factors
#score.heart_rate_factor = heart_rate_factor
#score.systolic_pressure_factor = systolic_pressure_factor
#score.diastolic_pressure_factor = diastolic_pressure_factor
#score.mean_pressure_factor = mean_pressure_factor
#score.signal_minimum_factor = signal_minimum_factor
#score.signal_average_factor = signal_average_factor
…
end
def heart_rate_factor
#reading.heart_rate && (1..10).include?(#reading.heart_rate) ? 0 : 10
end
…
end
Update
The overall purpose of this class is to calculate a score of a reading. I can’t provide all the code because it is a proprietary algorithm for a medical device.
But basically there are n factors of a #reading that are calculated and then saved to a #score object associated with the #reading. The sum of the factors is also calculated as a total on the #score object. The #score object looks like the following:
#score=
#<Score:0x007faa0b33ec50
#attributes=
{"id"=>42,
"reading_id"=>42,
"sum_of_factors"=>10,
"heart_rate_factor"=>10,
"another_factor"=>0,
"another_factor"=>0}
This seems to be the best option so far. The first answer to the question started me on this route, but the poster seems to have removed it…
def assign_factors_to_score
factors.each do |factor|
#score.public_send("#{factor}=", self.public_send(factor))
end
end
def factors
%i{factor_a factor_b factor_c factor_d}
end
You can automatically populate an array of factors using method_added. This combines nicely with dynamic assignment of factors as in your answer.
class ReadingScore
#factors = []
def self.method_added meth
#factors << meth if meth =~ /_factor\Z/
end
def self.factors
#factors
end
end
Note that these are class methods, so you would need to use self.class.factors when using this in an instance method.
Here is a full implementation in case you do not see how to integrate this code.
You could use delegate
class ReadingScore
extend Forwardable
delegate [:heart_rate_factor=, :systolic_pressure_factor=,:diastolic_pressure_factor=,
:mean_pressure_factor=,:signal_minimum_factor=,:signal_average_factor=] => :#score
def initialize
#score = Score.new
end
def assign_scoring_factors
%w(heart_rate_factor systolic_pressure_factor diastolic_pressure_factor mean_pressure_factor signal_minimum_factor signal_average_factor).each do |meth|
self.send("#{meth}=",self.send(meth))
end
self
end
end
but I agree with others that rethinking the whole design might be better here.
You could also use tap but the code will look fairly similar to what you have now.
Also I have no idea what a Score looks like internally because it seems like it would be better to place this logic inside the Score or Reading and pass all of this to a method or initializer of Score. e.g.
class ReadingScore
def intialize(reading)
#reading = Reading.new(reading)
#score = Score.new(#reading)
end
end
class Reading
#...
def heart_rate_score
heart_rate && (1..10).include?(#reading.heart_rate) ? 0 : 10
end
def systolic_pressure_score
#logic
end
def diastolic_pressure_score
#logic
end
def mean_pressure_score
#logic
end
def signal_minimum_score
#logic
end
def signal_average_score
#logic
end
end
class Score
attr_accessor :heart_rate_factor,:systolic_pressure_factor,:diastolic_pressure_factor,
:mean_pressure_factor,:signal_minimum_factor,:signal_average_factor
def initialize(reading)
factorialize(reading)
self
end
private
def factorialize(reading)
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average) do |meth|
self.send("#{meth}_factor=",reading.send("#{meth}_score")
end
end
end
This way your logic is centralized in Score and Reading and can be avoided in ReadingScore. This will make the code easier to trace and will clean up the original class.
you can do it like this, if you insist:
def assign_scoring_factors
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average).each |f| do
eval("#score.#{f}.factor = #{f}.factor")
end
end
but this isn't what I'd do. I'd either leave it moist, or just use a map.
I am creating a code for a small game of 4 horses running a set distance across my terminal. I have it to where it is outputting my horses that I have added and my users horse, but when I go to my next class to build the race it self, I keep getting method undefined errors. I searched for something similar but couldn't find anything. learningruby.com has some roundabout answers to it, but not showing me what im missing.
class Horses
##list_of_horses = []
attr_accessor :name
attr_accessor :position
def initialize
self.name = nil
self.position = 0
end
def self.add_horse(*horse_variables)
horse = Horses.new
horse.name = horse_variables[0]
##list_of_horses.push horse
end
def self.add_user(*user_variables)
add_user = Horses.new
add_user.name = user_variables[0]
##list_of_horses.push add_user
end
def self.display_data
puts "*" * 60
##list_of_horses.each do |racer|
print "-" * racer.position
puts racer.name
end
end
def move_forward
self.position += rand(1..5)
end
def self.display_horses
##list_of_horses
end
end
horse1 = Horses.add_horse ("Jackrabbit")
horse2 = Horses.add_horse ("Pokey")
horse3 = Horses.add_horse ("Snips")
user1 = Horses.add_user ("Jim")
Horses.display_data
Now when I run just this file, It will give me the printout in my terminal of
Jackrabbit
Pokey
Snips
Jim
But when I start trying to call the methods I have created in my Horses class in my next class of Race even outside of the Race class itself, Im returning method undefined.
require_relative 'Horses_class.rb'
no_winner = true
class Race
def begin_race
puts "And the Race has begun!"
end
end
while no_winner == true
puts begin_race
racing = Race.new
racing.Horses.display_data
end
So why am I not allowed to call my other methods? should I be using a splat or is there something more simplistic that im missing? Thank you in advanced.
Jim
Your begin_race method seems to be out of scope when you're calling it. You need to use either the . or the :: (scope) operator to access it.
class Race
def self.begin_race
puts "And the race has begun!"
end
end
Race::begin_race
# or
Race.begin_race
Also, when you call racing.Horses.display_data you must make sure that your Horses class is a sub-class of you racing class. You can not call a sub-class via an object, you must call it through the class constant.
class Race
class Horses
def self.display_data
puts "The Data"
end
end
end
# Access 'display_data'
Race::Horses.display_data
So in this case your require_relative should be within your Race class and your while block should look like
while no_winner == true
Race.begin_race
Race::Horses.display_data
end