Can one instance variable access another in the same class? - ruby

I am writing a chess engine in Ruby.
I have a Game class, which consists of two attributes:
:board, an instance of my Board class.
:log, an array of moves, for saving & loading games.
The Board class consists of two attributes:
:white, an instance of my Player class.
:black, an instance of my Player class.
The Player class consists of piece locations, represented as bitstrings:
:pawns, :knights, ..., :king
I would like the Player class to have methods like in_check? to indicate if that player is in check. However, that requires, #white to know the values of #black, which is an instance back in the Board class.
Is there a way I can access the variable #black from #white without explicitly passing the locations of the black pieces as a parameter to the in_check method?

The first player can pass message "you're in check" to another player after or before making a step.
The accessing variables is not a good idea, it leads to the future problems, pass messages instead.

Let #white and #black respectively ask the board about the check.
Update:
Well, I guess there are a lot of assumptions here on my side, so lets write these down and see whether we have some common ground:
class Board
def initialize
...
#black = Player.new(self, other_args*)
#white = Player.new(self, other_args*)
...
end
def am_i_in_check?(player)
case player
when #black
return does_white_check_black?
when #white
return does_black_check_white?
end
end
...
end
class Player
...
def initialize(board,...)
...
#board=board
...
end
...
def wants_to_know_whether_it_is_checked
#board.am_i_in_check?(self)
...
end
...
end
I guess there are typos hidden above, but it should describe my idea now.

Related

Class Variables Overriding

I was playing around with class variables, and I knew that I could override a class variable in a subclass but I didn't realize where I call a class method matters. For example;
class Vehicle
##wheels = 4
def self.wheels
##wheels
end
end
puts Vehicle.wheels #4
puts Vehicle.wheels #4
puts Vehicle.wheels #4
class Motorcycle < Vehicle
##wheels = 2
end
The puts statement outputs 4, that all makes sense to me because I figured that you ##wheels is not overridden until an object is instantiated from the Motorcycle class. So then I did this:
class Vehicle
##wheels = 4
def self.wheels
##wheels
end
end
class Motorcycle < Vehicle
##wheels = 2
end
puts Vehicle.wheels #2
puts Vehicle.wheels #2
puts Vehicle.wheels #2
Now once I move those three lines to after the class Motorcycle they output 2... even though a Motorcycle object hasn't been instantiated. This confuses me. In detail what is happening here? Is it because class variables are loaded when we try to access it? How is this possible?
First of all, instantiation has nothing to do with it; this is a class variable, not an instance variable. Your code never instantiates anything; nor, as written, does it need to. We're just talking about some classes. But classes are first-class objects in Ruby, so that's fine.
Second, order does matter, because in Ruby all statements are executable. When you say
class Motorcycle < Vehicle
##wheels = 2
end
...all of that code executes right now, at the moment you say it — the moment when it is encountered as Ruby walks down the page obeying instructions. That code is not a mere template to be obeyed at some future time; it says: "create a Motorcycle class and set its class variable wheels to 2, now."
Third, you're not "overriding" anything. In the class Motorcycle, ##wheels is the very same class variable ##wheels that you defined for Vehicle earlier. It is not some other variable that "overrides" it. This is a single value that works like a kind of namespaced global, reachable from the class, any subclasses, and any instances thereof.
For more information, see (e.g.):
How do I override a variable in a Ruby subclass without affecting the superclass?
Ruby class instance variable vs. class variable
https://www.ruby-lang.org/en/documentation/faq/8/
If you want something you can override, what you're looking for is probably more like an instance variable in a class context, often called a "class instance variable" — e.g. like this:
class Vehicle
#wheels = 4
class << self
attr_reader :wheels
end
end
class Motorcycle < Vehicle
#wheels = 2
end
puts Vehicle.wheels # 4
puts Motorcycle.wheels # 2
All occurances of ##wheels refer to the exactly same variable. First you set it to 4, then you set it to 2. Why does it surprise you, that it has the value 2? Use #wheels instead (as matt) suggested, if you want to have a separate variable for both.
Having said this (and this is the only reason I'm writing this answer, because matt has already covered everything else in his answer), your usage of this "variable" suggests that is is supposed to be an invariable property of the respective class. That is, every motorcycle is supposed to have 2 wheels. In this case, it would perhaps make more sense to make it a constant of its class:
class Motorcycle < Vehicle
WHEELS = 2
end
class Unicycle < Vehicle
WHEELS = 1
end
and use it as
puts Motorcycle::WHEELS
If you have a variable v, where you only know that it is some subclasss of Vehicle, you can write
puts v.class::WHEELS
Whether you also define WHEELS in the class Vehicle itself, its a design question. If you intend to treat Vehicle as abstract class, it does not make sense to define a fixed number of wheels in it. For orthogonality, you could catch this case by doing a
class Vehicle
WHEELS = nil
end
if you want to, or just don't define it and rely on the fact that your code will raise an exception, if you try to access the wheels of something which is "just a plain vehicle".
If you do want to instantiate a Vehicle, of course it may make sense to set the constants there to, i.e., 4.
However, in this case I would consider allowing individual instances having a different number of wheels. The Reliant Robin was a car with 3 wheels, and trucks have sometimes 6 or 8 wheels. For maximum generality, I would therefore make the number of wheels an instance variable, and of course you can provide a default value:
class Vehicle
attr_reader :wheels
def initialize(wheels=4)
#wheels=wheels
end
end
class Motorcycle < Vehicle
attr_reader :wheels
def initialize(wheels=2)
super(wheels)
end
end
m = Motorcycle.new
puts m.wheels

Ruby variable not accessible in classes

I'm trying to pass a variable to numerous classes but get an 'undefined local variable or method' error.
I've created a 'Player' class which has a method to initialise an object with two parameters, and have a 'Board' class where I want to use one of these parameters (I've just included a 'puts' statement for simplicity below) but this is where the error occurs. Outside of the 'Board' class, the same statement works (currently commented out).
How can I use the player1.name value inside of the 'Board' class please? Thanks
class Player
attr_accessor :name, :symbol
def initialize(name, symbol)
#name = name
#symbol = symbol
end
end
class Board
puts player1.name
end
player1 = Player.new("Player1","X")
#puts player1.name
When you open a class with the class keyword you open a new scope and the current bindings are thrown out of the window. Furthermore your code would never work since you reference player1 before defining the variable.
To solve these two issues we have to move the player1 definition above the class. Then instead of using the class keyword to define Board class we can use the Class constructor which keeps the current bindings.
This results in:
player1 = Player.new("Player1","X")
Board = Class.new do
puts player1.name
end
Although this might solve your problem, I doubt that this is the functionality you'll actually need. The question reeks of a XY-problem. The above will bind the class as a whole to what the player1 value is at the moment of class definition.
In turn this means that every instance of Board is in some way bound to player1. I assume that every instance of Board can have its own player(s), in which case the following would be a better fit:
class Board
def initialize(player1)
#player1 = player1
end
end
It seems that you're trying to call the instance of player1 inside the Board class before it is created.
This means player1 does not exist in that context.
I guess you could either instantiate it inside the Board class or pass it as an option. Here's an example:
class Board
def initialize(options={})
# Uses the player passed to it.
#player = options[:player]
end
# Not often seen since you can get it from the player class
# but can be done here inside the Board class too if you want to.
def player_name
puts #player.name
end
end
# Instantiate your player as usual.
player1 = Player.new("Player1","X")
# Adding it to an options hash
# this step is optional, you can add it directly to board
# like you did for adding the name and symbol to the player class.
options = {player: player1}
# Instantiate a board with the player you've created.
board = Board.new(options)
# To puts the player name run the method.
board.player_name # "Player1"
See Ruby Classes and Modules if you would like more examples and learn more.

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.

Ruby find method callers class

I have classes A and B, each with methods a and b respectively, like below.
class A
def a
#I want to get the object which calls this method
#caller = B
end
end
class B
def initialize
#to_call = A.new
end
def b
#to_call.a
end
end
B.new.b
How can I return B that calls a so that I can use methods of B inside A?
I have a class Board, which Game classes use to play. The board class has a method interact that gets user input either with gets.chomp or STDIN.getc to simulate guessing games or games which use arrow keys. The game class calls the interact method to begin playing the game, and sends it a block that handles the way the game is played. Each game has its own set of rules, therefore each game class has a method that displays its rule book to the user. Within interact, when the user enters "-rules", I want the board class to return the class that called its interact method and store it in a variable caller. With the caller variable defined, I want to use caller.rule_book to display the rules of the game class that called the board's interact method.
I don't know a way you you could do it, besides using the source's location (caller_locations), but I'd advise not to follow this path. Too much indirection often leads to code extremely hard to debug. It's much simpler and readable to simply pass the context (in this case, self, or the properties needed for the computation) as an argument:
class A
def initialize(context)
#context = context
end
def a
# Do something with #context
end
end
class B
def initialize
#to_call = A.new(self)
end
def b
#to_call.a
end
end
B.new.b

Testing methods that depend on an essentially random instance variable

I'm working on a Blackjack game. My Game object contains a deck object that gets shuffled after the deck reaches a certain level of penetration. Many of my methods depend on this deck object. I don't see any reason for the deck object to be accessible through setter methods. I'm having trouble testing the methods on my Game class, since they depend on the order of the deck, which is random.
For instance, I have the deal_hand method.
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
How should I test a method like this? I was thinking I could just manually create a Deck object that gets used in the #deck instance variable. Unfortunately, I can't set the instance variable, and I don't really want to add a setter, since there's no reason for that to be "settable" except to test. Should I monkey patch the class from my test-file and add a setter?
As an aside--I mostly write scripts--I decided I needed to start writing tests after this project got out of hand. Is there any canonical resource for "testing patterns?"
edit:
I am using MiniTest, which supports stubbing/mocking. Though as far as I can tell, it only lets you set expected return values for method calls on the mock object. If I made a Mock deck, the actual deck object also depends on an internal array. None of the code that calls the deck accesses the array directly.
Use a mock library. RSpec comes built with one, but I dislike it, so I'll show you what it might look like with Surrogate, the one I wrote:
class Deck
def pop(n) end
def reset() end
def size() end
end
class Game
def initialize(deck)
#deck = deck
end
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
def reset_deck
#deck.reset
end
end
Hand = Struct.new :card1, :card2
class Player
def hands
#hands ||= []
end
end
require 'surrogate/rspec'
class MockDeck
Surrogate.endow self
define(:reset)
define(:pop) { |n| n.times.to_a }
define(:size) { 1 }
end
describe Game, 'deal_hand' do
let(:deck) { MockDeck.new }
let(:player) { Player.new }
let(:game) { Game.new deck }
it 'resets the deck if there are less than 2 cards' do
deck.will_have_size 2 # set the return value of deck.size
game.deal_hand player
deck.was_not told_to :reset # assert what happened to the deck
deck.will_have_size 1
game.deal_hand player
deck.was told_to :reset
end
it 'deals the top 2 cards to the player' do
deck.will_pop [:card1, :card2]
game.deal_hand player
deck.was told_to(:pop).with(2)
player.hands.last.should == Hand.new(:card1, :card2)
end
end
describe Deck do
it 'is substitutable for the mock' do
# because we use the mock in tests
# we want to make sure its interface matches the real deck
Deck.should substitute_for MockDeck
end
end
Have you considered using mocha?
That would allow you to stub or mock the Deck to ensure that it has the expected cards for your test runs.
In your test use the method instance_variable_set, which is a ruby method on object.
So I am assuming your method is in the Game class, so you if you're setting up something like
#test_deck = something_that_sets_up_state_of_test_deck
#game = Game.new
#game.instance_variable_set(:deck, #test_deck
That will set your instance variable within Game, without the need for attr_accessible or getters and setters being explicitly built.

Resources