Ruby beginner: class, instance variable, accessor, rspec - ruby

So I'm in the process of trying to recreate a board game called Ciao Ciao. I'm not anywhere near done but I keep getting stuck and would really appreciate some help. So far I've made the following 3 classes and rspec file:
The Player class
require_relative 'die'
class Player
attr_reader :name
attr_accessor :token, :position, :point
def initialize(name, token, position, point)
#name = name
#token = token
#position = position
#point = point
end
def advance
#position += #number #basically I want the player to advance when he rolls between 1-4 but not sure how to connect it to the Die class here.
end
def lie
#token -= 1 #here I want the player to lose a token if he rolls between 5-6
#position == 0 #and have to start again from position 0
end
def score
#token -= 1
#position == 0
#point += 1
end
end
The Game Class
require_relative 'player'
require_relative 'die'
class Game
def initialize(title)
#title = title
#players = []
end
def join(player)
#players << player
end
def play
puts "There are #{#players.size} players in the current round of #{#title}."
#players.each do |player|
die = Die.new
case die.roll
when 1..4
puts "#{player.name} just rolled #{die.roll}!"
player.advance
puts "#{player.name} advances to #{player.position}!"
when 5..6
puts "#{player.name} just rolled #{die.roll}!"
player.lie
puts "#{player.name} is down to #{player.token} and starts at #{player.name}!"
end
puts "#{player.name} has #{player.point} points and is at #{player.position}. He has #{player.token} token(s) left."
if player.position >= 10
player.score
puts "#{player.name} scores a point for reaching the endzone!"
end
if player.token == 0
#players.delete(player)
puts "#{player.name} has been eliminated."
end
end
end
end
The Die Class
class Die
attr_reader :number
def initialize
end
def roll
#number = rand(1..6)
end
end
The rspec file
require_relative 'game'
describe Game do
before do
#game = Game.new("chaochao")
#initial_token == 4
#initial_position == 0
#initial_point == 0
#player = Player.new("iswg", #initial_token, #initial_position, #initial_point)
#game.join(#player)
end
it "advances the player if a number between 1 and 4 is rolled" do
#game.stub(:roll).and_return(3)
#game.play
#player.position.should == #initial_position + 3
end
it "makes the player lie if a number between 5 and 6 is rolled" do
#game.stub(:roll).and_return(5)
#game.play
#player.token.should == #initial_token - 1
end
end
I keep getting the following error message when I run the rspec file:
Failures:
1) Game advances the player if a number between 1 and 4 is rolled
Failure/Error: #game.play
NoMethodError:
undefined method -' for nil:NilClass
# ./player.rb:19:inlie'
# ./game.rb:24:in block in play'
# ./game.rb:16:ineach'
# ./game.rb:16:in play'
# ./game_spec.rb:17:inblock (2 levels) in '
2) Game makes the player lie if a number between 5 and 6 is rolled
Failure/Error: #game.play
NoMethodError:
undefined method +' for nil:NilClass
# ./player.rb:15:inadvance'
# ./game.rb:21:in block in play'
# ./game.rb:16:ineach'
# ./game.rb:16:in play'
# ./game_spec.rb:23:inblock (2 levels) in '
So the error message points to the advance/lie methods under Player class but I have no idea what I've done wrong. Also please feel free to point out other blunders. Thanks so much in advance.

This isn't the whole problem, but it's a problem.
Your first error stood out to me a bit. player.lie is only supposed to be called when the roll is a 5 or 6, but you stubbed the roll method to return a 3. Or did you?
roll is a method of the Die class, but you stubbed #game, which is an instance of the Game class.

Related

How i can solve this issue ? undefined method for class

Have a problem, when run a code allways have error.
Expect: for the user add win or reduce his balance.
undefined method `balance=' for #<Dice:0x0000563d4d4dfd88 #name="foo", #balance=600, #bet=300>
Did you mean? balance
(repl):22:in `increase_decrease_cash'
(repl):62:in `<class:Game>'
(repl):29:in `<main>'
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
class Dice
attr_accessor :name, :bet
attr_reader :balance
def initialize(name, balance, bet)
#name = name
#balance = balance
#bet = bet
end
def self.roll
#roll_dice = rand(1..2)
end
def self.check_bet
if #player.bet > #player.balance
puts "Enter number from 1 to #{#player.balance}"
end
end
def self.increase_decrease_cash
if #roll == #my_number
#player.balance += #player.bet
else
#player.balance -= #player.bet
end
end
end
class Game < Dice
#player = Dice.new("foo", 600, 0)
puts "Hello #{#player.name} your balance is: #{#player.balance}"
puts "Bones throwing count times"
a = 2 #gets.chomp.to_i
while a > 0 do
puts ""
puts "Enter your bet !!!"
# PLAYER BET
#player.bet = 300 #gets.chomp.to_i
check_bet
puts "Respected #{#player.name} your bet is: #{#player.bet}"
puts "Now select number 1-2"
# BONES ROLL
#my_number = roll # gets.chomp.to_i
puts "###################"
puts "Now we throw bones"
#roll = roll
puts "Nuber is #{roll}"
if #roll == #my_number
puts "Your win, you get #{#player.bet}"
else
puts "You lose #{#player.bet}"
end
p "$$$$"
p #player.balance
p "$$$$"
a -= 1
increase_decrease_cash
end
end
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
attr_reader creates only the get method for balance. You need both get and set method for balance. Because you set balance in the initialize method. So, you should use attr_accessor instead of attr_reader.
attr_accessor :balance

I'm trying to incorporate scenes into my mini space shooter game but Private Method Error keeps occuring

Whenever the game reaches the draw_end method it crashes. The draw_start and draw_game methods work great but the game crashes when it reaches the draw_end and therefore the ending credits cannot be displayed. The error is are these :
Traceback (most recent call last):
9: from sector_five_scenes.rb:275:in `<main>'
8: from /Users/skynet/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/gosu-0.14.5/lib/gosu/patches.rb:72:in `tick'
7: from /Users/skynet/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/gosu-0.14.5/lib/gosu/patches.rb:72:in `tick'
6: from sector_five_scenes.rb:29:in `draw'
5: from sector_five_scenes.rb:237:in `draw_end'
4: from /Users/skynet/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/gosu-0.14.5/lib/gosu/compat.rb:165:in `block (2 levels) in <class:Window>'
3: from /Users/skynet/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/gosu-0.14.5/lib/gosu/compat.rb:165:in `clip_to'
2: from sector_five_scenes.rb:238:in `block in draw_end'
1: from sector_five_scenes.rb:238:in `each'
sector_five_scenes.rb:239:in `block (2 levels) in draw_end': private method `draw' called for #<Credit:0x00007ff1b48ad448> (NoMethodError)
I have tried renaming the methods in the credits file. I have tried adding and removing the require_relative 'credit'. I removed the #credits array from the initialize_end method and placed it into the initialize method
Aside from that I'm really out of ideas of where to go from here and would really appreciate your help.
This portion connects all the classes to the main file
require 'gosu'
require_relative 'credit'
require_relative 'player'
require_relative 'enemy'
require_relative 'bullet'
require_relative 'explosion'
This part opens the window and sets the first scene.
class SectorFive < Gosu::Window
WIDTH = 800
HEIGHT = 600
ENEMY_FREQUENCY = 0.05
MAX_ENEMIES = 100
def initialize
super(WIDTH, HEIGHT)
self.caption = "Sector Five"
#background_image = Gosu::Image.new('ima/start.png')
#scene = :start
end
#The draw method below is the guide for this program.
def draw
case #scene
when:start
draw_start
when:game
draw_game
when:end
draw_end
end
end
This sets the background image
def draw_start
#background_image.draw(0,0,0)
end
This draws all the enemy, player, bullets, and explosions
def draw_game
#player.draw
#background_image.draw(0,0,0)
#enemies.each do |enemy|
enemy.draw
end
#enemybullets.each do |bullet|
bullet.draw
end
#bullets.each do |bullet|
bullet.draw_two
end
#explosions.each do |explosion|
explosion.draw
end
#playerexplosions.each do |explosion|
explosion.draw_two
end
end
This allows the game to update every frame
def update
case #scene
when :game
update_game
when :end
update_end
end
end
This executes the start of the game after the first scene
def button_down(id)
case #scene
when :start
button_down_start(id)
when :game
button_down_game(id)
when :end
button_down_end(id)
end
end
This method calls the initialize_game method
def button_down_start(id)
initialize_game
end
This method defines the initialize_game method by defining the variables
def initialize_game
#background_image = Gosu::Image.new('ima/space.png')
#player = Player.new(self)
#enemies = []
#bullets = []
#enemybullets = []
#random_enemy_bullets = []
#explosions = []
#playerexplosions = []
#framecounter = 0
#scene = :game
#enemies_appeared = 0
#enemies_destroyed = 0
#credits = []
end
This is what allows the game to function. It tells the game what to do!
def update_game
#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)
#enemies_appeared += 1
end
#enemies.each do |enemy|
enemy.move
end
#Fires a bullet
#enemies.select do |enemy|
if #framecounter % 600 == 0
#enemybullets.push Bullet.new(self, #enemies[-1].x, #enemies[-1].y, #enemies[-1].angle)
if #enemies.length > 3
#enemybullets.push Bullet.new(self, enemy.x, enemy.y, enemy.angle)
end
end
end
# Detects whether there is a collision between a player bullet and an enemy.
#enemies.dup.each do |enemy|
#bullets.dup.each do |bullet|
distance = Gosu.distance(enemy.x, enemy.y, bullet.x, bullet.y)
if distance < enemy.radius + bullet.radius
#enemies.delete enemy
#bullets.delete bullet
#explosions.push Explosion.new(self, enemy.x, enemy.y)
#enemies_destroyed += 1
end
end
end
#explosions.dup.each do |explosion|
if #explosions.length > 15
#explosions.delete explosion
#explosions.delete explosion
end
end
#explosions.dup.each do |explosion|
#enemies.dup.each do |enemy|
distance = Gosu.distance(explosion.x, explosion.y, enemy.x, enemy.y)
if distance < explosion.radius + enemy.radius
#enemies.delete enemy
#explosions.push Explosion.new(self, enemy.x, enemy.y)
#enemies_destroyed += 1
end
end
end
#Explosions on the player
#enemybullets.each do |bullet|
distance = Gosu.distance(bullet.x, bullet.y, #player.x, #player.y)
if distance < bullet.radius + #player.radius
#enemybullets.delete bullet
#playerexplosions.push Explosion.new(self, #player.x, #player.y)
end
end
#playerexplosions.dup.each do |explosion|
#explosions.delete explosion unless explosion.finishedtwo
end
#playerexplosions.dup.each do |explosion|
#enemies.dup.each do |enemy|
distance = Gosu.distance(explosion.x, explosion.y, enemy.x, enemy.y)
if distance < explosion.radius + enemy.radius
#enemies.delete enemy
#playerexplosions.delete explosion
#explosions.push Explosion.new(self, enemy.x, enemy.y)
end
end
end
#enemies.dup.each do |enemy|
if enemy.y > HEIGHT + enemy.radius
#enemies.delete enemy
end
end
#bullets.dup.each do |bullet|
#bullets.delete bullet unless bullet.onscreen?
end
#enemybullets.each do |bullet|
bullet.enemy_move
end
#bullets.each do |bullet|
bullet.move
end
#explosions.each do |explosion|
explosion.move
end
#playerexplosions.each do |explosion|
explosion.move
end
This part of the update method defines when the game ends.
initialize_end(:count_reached) if #enemies_appeared > MAX_ENEMIES
#enemies.each do |enemy|
distance = Gosu::distance(enemy.x, enemy.y, #player.x, #player.y)
initialize_end(:hit_by_enemy) if distance < #player.radius + enemy.radius
end
initialize_end(:off_top) if #player.y < -#player.radius
end
This method allows the player to fire bullets
def button_down_game(id)
if id == Gosu::KbSpace
#bullets.push Bullet.new(self, #player.x, #player.y, #player.angle)
end
end
This method sets the end scene("The scene that is not working") and displays messages on the bottom of the screen and top of the screen depending on the players fate and also is suppose to display the ending credits in the center of the screen produced by the credits.txt file. The credits should slowly move -y out of the window.
def initialize_end(fate)
case fate
when :count_reached
#message = "You Made it! You destroyed #{#enemies_destroyed} ships"
#message2 = "and #{100 - #enemies_destroyed} reached the base."
when :hit_by_enemy
#message = "You were struck by an enemy ship"
#message2 = "Before your ship was destroyed, "
#message2 += "you took out #{#enemies_destroyed} enemy ships!"
when :off_top
#message = "You got too close to the enemy mothership."
#message2 = "Before your ship was destroyed, "
#message2 += "you took out #{#enemies_destroyed} enemy ships!"
end
#bottom_message = "Press P to play again, or Q to quit."
#message_font = Gosu::Font.new(28)
y = 700
File.open('credits.txt').each do |line|
#credits.push(Credit.new(self,line.chomp,100,y))
y+=30
end
#scene = :end
end
This is the method that is producing the Private Method error. I can't figure out why.
def draw_end
clip_to(50,140,700,360) do
#credits.each do |credit|
credit.draw
end
end
draw_line(0,140,Gosu::Color::RED,WIDTH,140,Gosu::Color::RED)
#message_font.draw(#message,40,40,1,1,1,Gosu::Color::FUCHSIA)
#message_font.draw(#message2,40,75,1,1,1,Gosu::Color::FUCHSIA)
draw_line(0,500,Gosu::Color::RED,WIDTH,500,Gosu::Color::RED)
#message_font.draw(#bottom_message,180,540,1,1,1,Gosu::Color::AQUA)
end
The methods below do not work either. Produces the same Private method error. I don't understand why this doesn't work because I have used require_relative on the credit ruby file.
def update_end
#credits.each do |credit|
credit.move
end
if #credits.last.y < 150
#credits.each do |credit|
credit.reset
end
end
end
# This method gives the player the option to play again or quit the game and the player will select the button based on his or her preference.
def button_down_end(id)
if id == Gosu::KbP
initialize_game
elsif id == Gosu::KbQ
close
end
end
end
window = SectorFive.new
window.show
----------------------------------------------------------------------
"This is the Credit class"
class Credit
SPEED = 1
attr_reader :y, :x, :text
def initialize(window,text, x, y)
#x = x
#y = #initial_y = y
#text = text
#font = Gosu::Font.new(24)
end
end
def move
#y -= SPEED
end
def draw
#font.draw(#text, #x, #y, 1)
end
def reset
#y = #initial_y
end
-----------------------------------------------------------------------
"""The credits.txt file"""
"This is a part of the File.open method used to push the line into the credits array."
SectorFive
By: John Jordan Shelley
Based on a Tutorial from
Learn Game Programming with Ruby
By Mark Sobkowicz
—Images——
Game art from OpenGameArt.org
Licensed under Creative Commons: Domain CC0
The game should smoothly execute the initialize end method and give the player options depending on the player's fate and play the ending credits.

undefined method `[]' for nil:NilClass (NoMethodError) in tic-tac-toe game

I am building a Tic-Tac-Toe game to be played on the command line.
module TicTacToe
class Player
attr_accessor :symbol
def initialize(symbol)
#symbol = symbol
end
end
class Board
attr_reader :spaces
def initialize
#spaces = Array.new(9)
end
def to_s
output = ""
0.upto(8) do |position|
output << "#{#spaces[position] || position}"
case position % 3
when 0, 1 then output << " | "
when 2 then output << "\n-----------\n" unless position == 8
end
end
output
end
def space_available(cell, sym)
if spaces[cell].nil?
spaces[cell] = sym
else
puts "Space unavailable"
end
end
end
class Game < Board
attr_reader :player1, :player2
def initialize
play_game
end
def play_game
#player1 = Player.new("X")
#player2 = Player.new("O")
puts Board.new
#current_turn = 1
turn
end
def move(player)
while victory != true
puts "Where would you like to move?"
choice = gets.chomp.to_i
space_available(choice, player.symbol)
puts Board
#current_turn += 1
turn
end
end
def turn
#current_turn.even? ? move(#player2) : move(#player1)
end
def victory
#still working on this
end
end
end
puts TicTacToe::Game.new
The method that is to take a user's cell choice (space_available) and alter the array with their piece ('X' or 'O') is giving me an error. I can't find why my code is throwing this particular error.
The problem is that you don't call the parent constructor in your Game class, therefore #spaces is not initialized.
Your hierarchy decision is questionable, but to make it work, you can simply change the Game constructor to:
def initialize
super
play_game
end
You are calling spaces[cell]. The error is telling you that you are calling [] on nil, which means that spaces must be nil.
Perhaps you mean #spaces? Otherwise - you need to tell the program how spaces is defined and how it is initialized. A simple spaces = {} unless spaces would work
Another way of initialising your spaces variable would be to call super when you initialize the Game:
class Game < Board
attr_reader :player1, :player2
def initialize
super
play_game
end
...

Calling a method from a method in another class

It's a fairly well know one. Determining the rank of a poker hand. I created the following classes: Card:
class Card
attr_accessor :suite, :rank, :value
def initialize(suite, rank, value)
#suite = suite
#rank = rank
#value = value
end
def to_s
puts "#{#value}, #{#suite}, #{#rank}"
end
end
Deck:
class Deck
def initialize()
#cardsInDeck = 52
#deck = Array.new()
end
def add_card(card)
#deck.push(card)
end
def deck_size
#deck.length
end
def to_s
#deck.each do |card|
"#{card.rank}, #{card.suite}"
end
end
def shuffle_cards
#deck.shuffle!
end
def deal_cards
#Here I create a new hand object, and when popping cards from the deck
# stack I insert the card into the hand. However, when I want to print
# the cards added to the hand I get the following error:
# : undefined method `each' for #<Hand:0x007fa51c02fd50> (NoMethodError)from
# driver.rb:36:in `<main>'
#hand = Hand.new
for i in 0..51 do
card = #deck.pop
#cardsInDeck -= 1
puts "#{card.value}, #{card.rank}, #{card.suite}"
#hand.add_cards(card)
end
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
end
end
Hand
require_relative 'deck'
require_relative 'card'
class Hand
def initialize()
#hand = Array.new()
end
def to_s
count = 0
#hand.each do |card|
"#{card.value}, #{card.rank}, #{card.suite}"
count += 1
end
end
def add_cards(card)
#hand.push(card)
end
def hand_size()
#hand.length
end
end
and Driver File:
require 'logger'
require_relative 'card'
require_relative 'deck'
require_relative 'hand'
suite = ["Hearts", "Diamonds", "Clubs", "Spades"]
rank = ["Ace", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King"]
deck = Deck.new()
suite.each do |i|
v = 1
rank.each do |j|
deck.add_card(Card.new(i, j, v))
v += 1
end
end
In the Deck class, the deal_card method, I do not understand why looping for an array causes a method error
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
#hand is an instance of Hand, and there is no instance method each defined for Hand, so that is why #hand.each is generating an undefined method error.
My answer is not very direct on the error but could hopefully help you in this case.
You method deal_cards is where Dependency Injection can play its role. By the original design Deck has a hard dependency on Hand which is not good and harder to test. You need to change it like
def deal_cards(hand=nil)
#hand = hand || Hand.new
# Others
end
By this you can accept instance outside of Hand, say Foot as long as somebody can play cards by feet!
You can also unit testing this method without writing Hand class at all.
Better to unit test the class instead of manual checking, then you can inject any instance you like into this method during testing.

Ruby: Using variables between classes

I am making a short, text-based game as an extra credit exercise based on the Ruby I have learned so far and I'm having trouble getting classes to read and write variables between each other. I have read extensively and searched for clarification on how to do this but I haven't had much luck. I have tried using # instance variables and attr_accessible but I can't figure it out. Here is my code so far:
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def play
while true
puts "\n--------------------------------------------------"
if #room_count == 0
go_to = Entrance.new()
go_to.start
elsif #room_count == 1
go_to = FirstRoom.new()
go_to.start
elsif #room_count == 2
go_to = SecondRoom.new()
go_to.start
elsif #room_count == 3
go_to = ThirdRoom.new()
go_to.start
end
end
end
end
class Entrance
def start
puts "You are at the entrance."
#room_count += 1
end
end
class FirstRoom
def start
puts "You are at the first room."
#room_count += 1
end
end
class SecondRoom
def start
puts "You are at the second room."
#room_count += 1
end
end
class ThirdRoom
def start
puts "You are at the third room. You have reached the end of the game."
Process.exit()
end
end
game = Game.new()
game.play
I want to have the different Room classes change the #room_count variable so that Game class knows which room to go to next. I am also trying to do this without implementing class inheritance. Thanks!
class Room
def initialize(game)
#game = game
#game.room_count += 1
end
def close
#game.room_count -= 1
end
end
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def new_room
Room.new self
end
end
game = Game.new
game.room_count # => 0
room = game.new_room
game.room_count # => 1
room.close
game.room_count # => 0

Resources