I'm writing a very simple dungeon adventure game in Ruby (practice for a newb). I want to address the player by name throughout, so naturally I want to be sure that if a player passes an empty name to my Player initialize method if tells them they can't do that and then prompts them to retry.
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
load 'game.rb'
end
end
end
The file name is 'game.rb' so I'm basically reloading the entire file here each time the player decides not to enter their name. Which is stupid...
It works, but in the worst way...I had to be 'clever' about where I exited the program so that the player would be insulated from the fact that I'm basically going all Inception on everyone and launching a game within a game every time the player starts a new game and neglects to enter their name. For instance, if they don't notice 3 times that they need to input their name because they're not paying attention, I effectively have 4 games running (the original, and the 3 the prompted by not entering their name) and they'd either need to end each of those games when they're tired of playing, or else I had to basically exit the whole thing hard at one keyword.
My question is this: is there a way to write my error "You did not enter..." exit the current game session, and relaunch the game? All I really want to do is ensure an empty string doesn't get passed to my initialize method, maybe by raising an exception, and then start over at the beginning of the script without having a game-within-a-game.
Here is a link to the full code for more insight: http://repl.it/8QY
It's hard to know without seeing the whole game structure, but your main file could look like
game_initialized = false
while ! game_initialized
begin
# here, initialize the game, including initialization
game_initialized = true
rescue NoNameError
# do nothing, but it will restart the game
end
end
# now play the game
And in your constructor, you add
class NoNameError < Exception
end
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
raise NoNameError
end
end
end
That should get you going.
I think Vincent's answer is good, it certainly is more OO than mine but a simple approach would be to do something like this at game start:
print "Welcome! "
in_name = ""
while true
puts "What is your name?"
in_name = gets.chomp
in_name.empty? ? (puts "You must enter a name before continuing") : break
end
Example:
Welcome! What is your name?
You must enter a name before continuing
What is your name?
Anthony
If you want your logic to be in Player, you can try something like this:
class Player
attr_accessor :name, :location
def initialize(name)
#name = keep_asking_for_name_if_not_already_entered(name)
end
def keep_asking_for_name_if_not_already_entered(name)
return name unless name.empty?
loop do
puts 'You did not enter your name! Enter a name, please:'
name = gets.chomp
break(name) unless name.empty?
end
end
end
Related
Sorry I'm a big beginner in Ruby and I don't know if my title and my question makes sense...
Here my code :
class Account
attr_reader :name, :balance
def initialize(name, balance=100)
#name = name
#balance = balance
end
def display_balance
pin_check
puts "Hello #{name} ! Your balance is $ #{balance}."
end
private
def pin
#pin = 1234
end
def pin_check(pin_number)
if #pin_number == #pin
puts "Access authorized: pending transaction"
else puts "Access denied: wrong PIN"
end
end
end
checking_account = Account.new("Saitama", 20_000)
checking_account.display_balance
What I’m trying to do is to automate the “pin check” in the other methods. My problem here is with the pin_check parameter : of course, the program is expecting an argument from this method but on my last line of code, I don’t know how to give it the 1234 argument expected… Is there any way to write correctly this last line of code to link display_balance and pin_check so as to giving the good argument to pin_check ?
I was thinking maybe write something like that but I know it doesn't work :
checking_account.display_balance.pin_check(1234)
How can I link both ? Thank you so much for your help !
I think the simplest thing to do would be to perform pin_check in the constructor, and then you don't have to worry about doing it for all methods. pin_number would be a constructor argument.
Another option would be to use something like ActiveModel::Validations, and add a validator that checks the PIN. Any client of your class would need to know to validate before performing any of the actions. That is the pattern used by Rails facilities such as ActiveRecord.
You can pass the pin into display_balance and pass it through to pin_check.
def display_balance(pin:)
pin_check(pin)
puts "Hello #{name} ! Your balance is $ #{balance}."
end
checking_account = Account.new("Saitama", 20_000)
checking_account.display_balance(pin: 1234)
Note that because pin_check only prints, the balance will always be displayed. You probably want it to raise an exception.
def pin_check(pin_number)
raise "Access denied: wrong PIN" unless pin_number == pin
end
Note that it's pin_number and not #pin_number. pin_number is the variable containing what was passed into pin_check. #pin_number is an "instance variable" stored on the object.
Update: Also note that it's pin and not #pin. #pin will not be set unless pin is called.
It's probably better to pass the pin in once during object initialization and raise an error if it does not match. This guarantees no operations can happen without a pin check.
attr_reader :name, :balance
def initialize(name:, balance: 100, pin:)
#name = name
#balance = balance
pin_check(pin)
end
private def pin_check(pin_try)
raise "Access denied: wrong PIN" unless pin_try == #pin
end
checking_account = Account.new(name: "Saitama", balance: 20_000, pin: 1234)
checking_account.display_balance
Note that I'm using named arguments. This lets you add arguments, even optional ones, without having to remember what argument 3 was.
You basically need to pass around pin wherever needs. You can pass pin to following method
def display_balance(pin)
pin_check(pin)
puts "Hello #{name} ! Your balance is $ #{balance}."
end
Now you can call checking_account.display_balance(1234)
As a beginner learning to program, it is extremely helpful to have such a supportive community out there!
I am having trouble getting this 'sample' game working. I am trying to develop a battle system where the player comes across opponents as they progress through a number of rooms. For some reason, when I run it on command prompt, it simply displays "you died" then exits. I am not sure where to go from here.
Any help would be greatly appreciated.
class Player
attr_accessor :hit_points, :attack_power
def initialize(hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= Opponent.attack_power
end
def print_status
puts "*" * 80
puts "HP: #{hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Death
puts "You died"
exit(1)
end
class Opponent
def initialize (hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
puts "you come across this awful opponent"
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= player.attack_power
end
def interact(player)
while player.alive?
hurt
break if #hit_points < 1
alive?
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt #{player_damage_done} damage, killing your opponent."
room
else
death
end
end
end
class Room
puts "you are now in the scary room, and you see an opponent!"
puts "You come accross a weaker opponent. It is a fish."
puts "Do you want to (F)ight or (L)eave?"
action = $stdin.gets.chomp
if action.downcase == "f"
fish = Opponent.new(2, 1)
fish.interact
else
death
end
end
Player.new(200, 1)
Room.new
class Engine
end
This is breaking because Death is a class and all the code within it is in the body of the class. That means this code will be executed when the class is defined, not at the time that death is called.
You haven't defined a method named death.
Because the Death class is tiny, and it would be awkward to name a method within it that stops the game (Death.death, Death.die, Death.run, Death.execute... Not great), and you don't need any of the advantages of a class (such as multiple instances or attributes stored in instance variables), I suggest you make the death action a part of the Player class.
class Player
# ...
def die
puts "You died"
exit(1)
end
end
Then when you've called death (the currently undefined method) Replace it with player.die.
As noted by #Kennycoc, you'll need to define a method for the death of an enemy, too.
So, it seems like you're under the impression that the code in the top level of the class is run when the class is instantiated (Class.new). This is not the case! Everything in the top level of the class is run as it is defined.
The simplest fix to get this running would be to add all the code in your top level of each class under a method named initialize this is what's run when the class is instantiated.
Also, you're using your Death class as if it were a method. You could either change it from class Death to def death or change your calls to Death.new after moving the code to the initialize method (this is not a normal pattern, but would work).
I've been trying to test a program that simulates an elevator for two days now with little success. Here's my elevator class, the program is still a work in progress and I've also commented out some methods that might not be essential to the test I'm having trouble with. I'll gladly show more code if you think it's needed
class Elevator
attr_accessor :current_floor
GROUND = 0
TOP = 15
def initialize
#floors = [] # list of floors to travel to
#pending = [] # store floors not in direction of travel
#current_floor = GROUND
#going_up = true # cannot travel downward from ground floor
#going_down = false
end
def get_input
gets.chomp
end
def run
enter_floors
sort_floors
move_to_floor
end
def enter_floors
# prompts the user for input and calls check_floor_numbers
end
def check_floor_numbers floors
# calls validate_floors to ensure user entered '2' instead of 'two'
# if proper floor numbers have been entered this method adds the number
# to #floors array other wise it calls floor_error_message
end
def floor_error_message
puts "Please enter numbers only."
enter_floors
end
def sort_floors
# if we are on the ground floor this method sorts #floors in ascending order
# if we are on the top floor it sorts #floors in descending order
# else it calls check_direction_of_travel
end
def move_to_floor
floor = #floors[0]
if #current_floor == floor
puts "You are already on floor #{floor}"
else
print_direction
(#current_floor..floor).each { |floor| puts "...#{floor}" }
#current_floor = floor # update current_floor
#floors.delete_at(0) # remove floor from list
end
check_for_more_passengers
end
def check_for_more_passengers
puts "Are there any more passengers? (Y/N)"
answer = (get_input).upcase
answer == 'Y' ? run : check_next_move
end
def check_next_move
if #floors.empty? && #pending.empty?
end_ride
else
move_to_floor
end
end
def check_direction_of_travel
# not implemented - add floor to appropriate array depending on dir
# of travel
end
def end_ride
puts "\n\nEND."
end
def print_direction
msg = " "
#going_up ? msg = "Going Up!" : msg = "Going Down!"
puts msg
end
end
I'm trying to test that the elevator can move to a specific floor. At first I was having trouble testing input from the console without running the program itself. I asked a question about this and was referred to this answer in a different question. The answer in question extract gets.chomp to a separate method then overrides the method in the tests. I ended up with something like this:
describe "it can move to a floor" do
before do
##moves = ["2", "N"]
def get_input; ##moves.next end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
Problem: get_input was not properly overidden and running the test suit prompted the user for input so it was suggested that I open the Elevator class in the test itself to ensure that the method was properly overridden. Attempting to do so eventually led to a test like this:
describe "it can move to a floor" do
before do
class Elevator
attr_accessor :current_floor
##moves = ["2", "N"]
def get_input; ##moves.next end
def run; end
end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
I had to override run and add an attr_accessor for current_floor because I was getting method missing errors.
Problem: This test gives the following error:
1) Failure: it can move to a floor#test_0001_should move to floor 2
[elevator_test.rb:24]: Expected: nil Actual: 2
I've tried to tidy up the Elevator class as much as possible and keep the methods as simple as I could given the parameters of the program.
Can anyone point me in the right direction towards getting this solved, with maybe pseudocode examples (if possible) to demonstrate how I should approach this problem if the answer is to refactor.
Please bear in mind that I'd also like to implement other tests like checking that the elevator class can maintain a list of floors, or that it can change direction, in the future when you answer.
Your test class ElevatorTest is redefining the Elevator to override method get_input, but it is not opening the class Elevator defined in elevator.rb, but instead it is sort of creating a new class Elevator which happens to be defined inside the class ElevatorTest. Remember every class is also a module, so now you have a new class ElevatorTest::Elevator.
To fix this issue, I have made some changes to elevator_test.rb which is shown below.
gem 'minitest', '>= 5.0.0'
require 'minitest/spec'
require 'minitest/autorun'
require_relative 'elevator'
class Elevator
##moves = ["2", "N"].each
def get_input; ##moves.next end
end
class ElevatorTest < MiniTest::Test
def test_working
assert_equal(1, 1)
end
describe "it can move to a floor" do
before do
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
end
Also, please remember to use .each while defining ##moves - it returns an enumerator. We can call .next only on an enumerator
I had a previous question that I asked here: I (think) I'm getting objects returned when I expect my array to have just 2 properties available
that may give some background. Having received a solution for that, I immediately moved to showing each player hand. The below module is included in both the Dealer and Player classes.
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
print "The dealer is showing: "
print self.hand[0].show_card
puts ''
elsif self.class == Player
print "You have: "
self.hand.each do |item|
item.show_card
end
puts ''
else
puts "A Random person is showing their hand."
end
end
end
I know this customization defeats the purpose of the module, just using it to reinforce the concept of modules. The above solution worked fine for the Dealer portion. But when the Player portion was called it printed a blank block. On a .inspect of each "item" in the Player block it confirmed that the items were in fact Card objects as expected. Here was the previous show_card method:
def show_card
"[#{#card_type} of #{#suit}]"
end
So it just returned a string with the card_type and suit.
I was able to fix the problem for the Player object portion just by changing the method to this:
def show_card
print "[#{#card_type} of #{#suit}]"
end
Why was this happening? I'm assuming it has something to do with the call the "each" on the Player Hand. Really just curious what the difference was and why these Card objects wouldn't print without the explicit "print" in there vice returning via String object.
Hope I was descriptive enough. This one just baffles me and I'm really trying to grasp these little things since I know it will prevent future errors like this. Thanks!
In Ruby you must print with puts or print. Just returning the string doesn't print it. The reason your Dealer class prints is because you did a print, but in your Player class, as you noted, you had no print. You only returned the string without printing.
As you noted, you were able to fix it by including the print:
def show_card
print "[#{#card_type} of #{#suit}]"
end
You could do this instead:
def show_card
"[#{#card_type} of #{#suit}]"
end
...
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
print "The dealer is showing: "
puts self.hand[0].show_card # PRINT THE CARD
elsif self.class == Player
print "You have: "
self.hand.each do |item|
print item.show_card # PRINT THE CARD
end
puts ''
else
puts "A Random person is showing their hand."
end
end
end
Which would be a little more "symmetrical" and prints what you want.
A slightly more compact would be:
def show_card
"[#{#card_type} of #{#suit}]"
end
...
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
puts "The dealer is showing: #{self.hand[0].show_card}"
elsif self.class == Player
print "You have: "
self.hand.each { |item| print item.show_card }
puts ''
else
puts "A Random person is showing their hand."
end
end
end
I ran into a github spec that was failing and as I am learning how to write specs, I fixed two of them that were failing apart for the last one in with a comment #THIS ONE IS STILL FAILING. How would one make it pass?
class Team
attr_reader :players
def initialize
#players = Players.new
end
end
class Players
def initialize
#players = ["","Some Player",""]
end
def size
#players.size
end
def include? player
raise "player must be a string" unless player.is_a?(String)
#players.include? player
end
end
describe "A new team" do
before(:each) do
#team = Team.new
end
it "should have 3 players (failing example)" do
#team.should have(3).players
end
it "should include some player (failing example)" do
#team.players.should include("Some Player")
end
#THIS ONE IS STILL FAILING
it "should include 5 (failing example)" do
#team.players.should include(5)
end
it "should have no players"
end
I'll assume that the aim is to modify the spec, not to modify the code to pass the spec.
In this case, we don't actually expect #team.players to include 5; rather, we expect it to raise an exception when asked whether it includes a non-string. This can be written as follows:
it "should raise an exception when asked whether it includes an integer" do
expect {
#team.players.should include(5)
}.to raise_exception
end
For a check on the exception type and message, use raise_exception(RuntimeError, "player must be a string").
You should probably modify the descriptions of the other two specs similarly, since they no longer fail.