how to test STDIN with RSpec - ruby

Ok, need help working out a test. I want to test that this class receives a letter "O" and
that when called the "move_computer" method returns WHATEVER the person enters on the cli. my mental subprocessor tells me this is a simple assign a variable to something to hold the random human input at STDIN. Just not getting it right now...anyone point me in the right direction?
here is my class...
class Player
def move_computer(leter)
puts "computer move"
#move = gets.chomp
return #move
end
end
my test look like...
describe "tic tac toe game" do
context "the player class" do
it "must have a computer player O" do
player = Player.new()
player.stub!(:gets) {"\n"} #FIXME - what should this be?
STDOUT.should_receive(:puts).with("computer move")
STDOUT.should_receive(:puts).with("\n") #FIXME - what should this be?
player.move_computer("O")
end
end
end

Because move_computer returns the input, I think you meant to say:
player.move_computer("O").should == "\n"
I would write the full spec like this:
describe Player do
describe "#move_computer" do
it "returns a line from stdin" do
subject.stub!(:gets) {"penguin banana limousine"}
STDOUT.should_receive(:puts).with("computer move")
subject.move_computer("O").should == "penguin banana limousine"
end
end
end

Here is the answer I came up with...
require_relative '../spec_helper'
# the universe is vast and infinite...it contains a game.... but no players
describe "tic tac toe game" do
context "the player class" do
it "must have a human player X"do
player = Player.new()
STDOUT.should_receive(:puts).with("human move")
player.stub(:gets).and_return("")
player.move_human("X")
end
it "must have a computer player O" do
player = Player.new()
STDOUT.should_receive(:puts).with("computer move")
player.stub(:gets).and_return("")
player.move_computer("O")
end
end
end
[NOTE TO THE ADMINS...it would be cool if I could just select all my code text and right indent in one button push. (hmmm...I thought that was a feature in the past...?)]

Related

Undefined Method "attack" for Player::Class

So I am trying to make a text based game with classes for an assignment. I have worked for 2 hours and could not find what the problem is.
class Rankuun
attr_accessor :rankuun_damage, :rankuun_health
def initialize
rankuun_health = 200
rankuun_damage = 100
end
def monolouge
puts 'Rankuun: "So, I see that you have lived this long. I am suprised.'
puts "Not a single libing creature has lived for this long inside my dungeon."
puts "But it's time that your endless slaughter of my brethren are halted."
puts "Now face what true fear really is!"
puts "Hoc vanitas est, et non est fere ut serves!"
puts "You see a mystical aura rise around Rankuun, and hear the shouts of agony"
puts "Rankuun has grown twice in size, and has taken the form of some kind of lich"
puts 'Rankuun: WELCOME TO DIE!"'
end
end
class Player
attr_accessor :health, :gold
def initialize
health = 100
money = 200
puts "Health: #{health}"
puts "Gold: #{money}"
end
def attack
puts "You attack the monster!"
hitmiss = 1
if hitmiss == 1
dmg = rand(5..10)
puts "You hit the monster, and do #{dmg} damage!"
monster_health = monster_health - dmg
elsif hitmiss == 2
puts "You missed!"
end
end
def guard
puts "You attempt to defend yourself"
guard = rand(1..2)
if guard == 1
counter = rand(5..10)
puts "You block the damage, and counterstrike for #{counter} damage"
monster_health = monster_health - counter
elsif guard == 2
monster_counter = rand(1..5)
puts "You try to guard, but the enemy hits harder than you expected, and you get dealt #{monster_counter}"
health = health = monster_counter
end
end
def loot
puts "You search the room and find:"
loot_item = rand (2..3)
if loot_item == 2
puts "You find some gold!"
money = money + 50
puts "Health: #{health}"
puts "Gold: #{money}"
elsif loot_item == 3
puts "You find a curious potion that seems to heal you"
health = health + 50
puts "Health: #{health}"
puts "Gold: #{money}"
end
end
def encounter
encounter = rand(1..2)
if encounter == 1
puts "A monster confronts you!"
monster = Monster.new
elsif encounter == 2
puts "There appears to be no monsters in this room"
end
end
end
class Monster
attr_accessor :monster_health, :monster_damage
def initialize
monster_health = 50
monster_damage = 10
end
def monster_attack
puts "The monster attacks you!"
end
end
puts "There has been a saying in your town for as long as you can remember:"
puts "Ne pas entrer dans le Donjon De Rankuun"
puts 'It means: "Do not enter The Dungeon of Rankuun"'
puts "Many adventurers died inside, and the only living creature in there is the man named Rankuun"
puts "He has great power over the Dungeon, reviving the dead and casting black magic"
puts "You have been selected by the village to go into the Dungeon and exterminate Rankuun"
puts "You have been given a sword, a shield, and some gold. Now you must enter:"
puts "T H E D U N G E O N O F R A N K U U N!"
puts ""
puts ""
player = Player.new
player.encounter
room1 = gets.chomp
if room1 == "attack"
player.attack
elsif room1 == "loot"
player.loot
end
It would be great if this problem were solved. Thanks for responding and aiding me in my assignment.
Welcome to the exciting world of object-oriented design. Many adventurers died inside.
I think you may have a small misunderstanding about the difference between classes and instances. If so, I strongly advise you to read about it before continuing.
You created a new instance of Player when you called Player.new. Your first mistake was not putting it in a variable.
Try something like this:
my_player = Player.new
Secondly, you are trying to call encounter on the Player class, while you should call it on the new instance.
my_player.encounter
You do the same thing inside the Monster class with Player.attack.
I could tell you how to solve each of these problems individually, but I think you would benefit more from redesigning some parts of the project to be easier to change in the future. Hopefully, most of the problems will resolve themselves along the way.
Generally speaking, the shorter a method is, the better. When you tell the Player to attack, that is all it should do. Instead, it does all sorts of things, including getting the monster to attack!
It suddenly becomes apparent that the two classes have quite a lot in common: they both attack; they both take damage, and they both die. It's time to make a superclass. (If you are not familiar with how classical inheritance works, you should learn - this truly is the perfect use case for it.)
class Character
attr_accessor :health
def attack damageable, damage
damageable.take_damage damage
end
def take_damage damage
health -= damage # Equivenent to health = health - damage
potential_death
end
def potential_death
if dead?
die
end
end
def dead?
health <= 0 # With random damage, it could be less than 0.
end
def die # overruled by subclass
end
end
The greatest advantage to doing it like this is you only have to write the code in one place, and it will work for everything. If you change your mind about a design decision, you can change it in one place and know that everything will be adjusted.
You can make a subclass similar to this:
class Monster < Character
def die
super # Call the copy of die in Character, in case it contains something important
reward killer
puts "You kill the monster..."
end
def reward rewardable
rewardable.gain_money 30
end
end
class Player < Character
def die
super # Call the copy of die in Character, in case it contains something important
puts "You died..."
game.over
end
end
(These are only examples; they are not as complete as the code you already have.)
Do you see how each method only does one thing? If you apply that principle to everything you write, it will become much easier to reuse bits and pieces.
I hope this has been useful. If you decide to stick with what you have and just fix the errors, just say so in the comments, and I'll help you with that.
Good luck!

rspec testing a single part of a method

I have this method for a tic tac tow game:
def start(token)
if token == "X"
puts "#{#player1}: Please enter a number for your X to go"
elsif token == "O"
puts "#{#player2}: Please enter a number for your O to go"
end
player_input = STDIN.gets.chomp
locate_player_input(token, player_input)
end
I'm only trying to test to see if the correct thing is puts'd to the terminal. I have this test:
describe "#start" do
context "X player's turns" do
it "prints proper messege" do
expect(STDOUT).to receive(:puts).with("Harry: Please enter a number for your X to go")
game.start("X")
end
end
end
But I have a feeling the game.start("X") line is what is not making this work. How can I write the test to just check if the puts statement is correctly outputted?
I think I figured it out. Since my function was first puts-ing something and then calling the next method to be run, I needed another expect statement. My passing test is as such:
describe "#start" do
context "X player's turns" do
it "prints proper messege" do
expect(STDOUT).to receive(:puts).with("Harry: Please enter a number for your X to go")
expect(game).to receive(:get_input)
game.start("X")
end
end
end
I'm not sure if this is correct, but the test did pass.

Ruby: How to exit a function and then return to it

I'm writing a text-adventure game for exercise 36 of Learn Ruby the hard Way: http://ruby.learncodethehardway.org/book/ex36.html
I want to include 'instructions' as an option players can use anytime, but once the player exits the Room() function and enters the intructions() function, I am unsure how to return them to the appropriate room. I can have the instructions() always return player to the start after, but is there a way to get them back to the same location?
Here is a quick example, sorry it's incomplete...I'm still in the middle of building it:
puts
puts <<INTRO
"Welcome to the Cave of Indifference.
It doesn't much care for you. Beware!
There are deadly areas of this cave, but if you seek
it you may find the secret treasure and escape with your life."
INTRO
puts
puts "Type \'Instructions\' at any time for direction"
puts
sword = false
monster = true
treasure = false
def Command()
puts ">>> "
end
def dead(how)
puts how.to_s
puts "PLAYER DEAD!"
Process.exit(0)
end
def instruction()
puts "Rooms will have individual instructions"
puts "but here are some general items."
puts "west, east, north, south: goes that direction"
puts "look: look around the room"
puts "take: to take item or object"
end
def Room1()
puts "You are now at the cave entrance."
puts "You may go west or east. OR exit with your life!"
Command(); choice = gets.chomp()
if choice.downcase == "exit" && treasure = true
puts "Congratulations! You win!"
Process.break
elsif choice.downcase == "exit" && treasure = false
puts "Seriously?! Giving up already?"
puts "Fine. Here is what happens:"
dead("You stumble on your exit from the cave and trip
on a rock. The fall cracks your skull and you bleed
to death. Bye bye!")
elsif choice.downcase.include? "right"
#INPUT
elsif choice.downcase.include? "left"
#INPUT
elsif choice.downcase.include? "instructions"
instructions()
else
"That command makes no sense, try again."
end
end
Room1()
I also assume there are many issues with the code and would be very appreciative of your help, but no worries I am going to keep working on this and make it really fun to play :)
You can give the instruction method the last location.
def instruction(last_room)
#do stuff
last_room.call()
end
You would call that function like so:
instructions(method(:Room1)), where Room1 is the name of the method you want to return to.
The problem you have isn't having instructions return to Room1 (or RoomX). It'll do that without you doing anything special. What you need is something like:
#room = :Room1
while true
send(#room)
end
and then set the variable #room to control which room you're in.
It's not the greatest way in the world to do that, but it'll get you started.

Rspec user input while loop tests

I am using RSpec to test my rock paper scissors game. Included in my begin_game function I have the following code:
user_input = gets.chomp.downcase.to_sym
while !choices.include? user_input
puts "Please choose a valid selection : rock, paper, or scissors"
user_input = gets.chomp.downcase.to_sym
end
I am trying to test for different possible user_inputs. I have tried this:
let(:new_game) {RockPaperScissors.new}
.......
context 'validate that the user input is one of the given choices' do
it 'should prompt the user for a new input if the original one is invalid' do
new_game.stub(:gets) {"r"}
expect(new_game.begin_game).to eq("Please choose a valid selection : rock, paper, or scissors")
end
end
but this results in an infinite loop of "Please choose a valid selection ..." being outputted to Terminal. I read the RSpec mocking documentation but it was difficult for me to understand.
The reason why it's looping is because new_game.stub(:gets) { "r" } will always return r no matter how many times you call it. Thus user_input will never contain valid input and your test will run forever.
To fix this, you should make new_game#gets return a valid selection after a certain number of tries.
For example,
new_game.stub(:gets) do
#counter ||= 0
response = if #counter > 3 # an arbitrary threshold
"rock"
else
"r"
end
#counter += 1
response
end
This would cause your test to print Please choose a valid selection... 4 times and then terminate.
Depending on how you implemented RockPaperScissors#begin_game, the test you wrote would still not pass. This is because puts("a string") will always return nil. Moreover, a while loop will also return nil. So at no point would the above snippet of code return the string "Please choose a valid selection : rock, paper, or scissors".
An implementation of begin_game that would pass is:
def begin_game
user_input = gets.chomp.downcase.to_sym
if choices.include? user_input
# return something here
else
"Please choose a valid selection : rock, paper, or scissors"
end
end
but at that point, I would probably rename it to handle_move, and have it accept an argument as a parameter to avoid stubbing gets in the first place.
def handle_move(input)
if choices.include? input
"Great move!"
else
"Please choose a valid selection : rock, paper, or scissors"
end
end

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

Resources