I am doing a command line of game of Rock Paper Scissors Lizard Spock in Ruby. I have the matchup method that takes the variables #shape (the hand shape randomly selected by the game) and #player_shape (the hand shape chosen by the player).
My matchup method compares the two shapes and sets the game result to #result:
class Game
SHAPES = [:rock, :paper, :scissors, :lizard, :spock]
attr_reader :shape, :player_shape, :status
# ...
def matchup
if #shape == #player_shape
#result = :draw
else
case #player_shape
when :rock
#result = (#shape == :scissors || #shape == :lizard) ? :player_wins : :game_wins
when :paper
#result = (#shape == :rock || #shape == :spock) ? :player_wins : :game_wins
when :scissors
#result = (#shape == :paper || #shape == :lizard) ? :player_wins : :game_wins
when :lizard
#result = (#shape == :paper || #shape == :spock) ? :player_wins : :game_wins
when :spock
#result = (#shape == :rock || #shape == :scissors) ? :player_wins : :game_wins
end
end
end
# ...
end
I ran the code many times and it works as expected, but in my spec file, the result I'm getting is not matching the code behavior. Here is the spec:
describe Game do
subject(:game) { Game.new }
# ...
describe "#matchup" do
context "game chooses rock" do
before do
allow(game).to receive(:shape).and_return(:rock)
end
it "sets result as :player_wins if the game chooses paper" do
allow(game).to receive(:player_shape).and_return(:paper)
game.matchup
expect(game.result).to eq(:player_wins)
end
end
end
end
and here is the result:
Failure/Error: expect(game.result).to eq(:player_wins)
expected: :player_wins
got: :draw
(compared using ==)
Diff:
## -1,2 +1,2 ##
-:player_wins
+:draw
What I am doing wrong here? I've tried and tried and still can't figure out how to solve this problem.
Here
allow(game).to receive(:shape).and_return(:rock)
and there
allow(game).to receive(:player_shape).and_return(:paper)
You're stubbing methods Game#shape and Game#player_shape for game object.
But you're using #shape == #player_shape as condition in if statement.
Ruby treats undefined instance variables (those which starts from #) as nil by default.
→ irb
irb(main):001:0> #shape
=> nil
Thus #shape == #player_shape is the same as nil == nil in your case. Which is actually a true. What's why program flow reach #result = :draw line.
To make it work you need to define shape and player_shape methods by using attr_reader, for example, and use them instead of instance variables.
upd
Now replace #shape with shape and #player_shape with player_shape in your code. Be sure you initialise values for them.
Take a look at that Ruby Monk tutorial (or any other articles) about instance vairables to understand how they work
Related
I am stuck on implementing the minimax algorithm for a tic tac toe game. I keep getting the same error every time (undefined method `<' for nil:NilClass (NoMethodError)) but can't seem to fix it.
My implementation is as follows:
def minimax(current_board, current_player)
if user_won?(current_board)
return -10
elsif computer_won?(current_board)
return 10
elsif tie?(current_board)
return 0
end
available_squares = empty_squares(current_board)
moves = []
available_squares.each do |available_square|
move = {}
current_board[available_square] = COMPUTER_MARKER if current_player == 'computer'
current_board[available_square] = PLAYER_MARKER if current_player == 'player'
score = minimax(current_board, alternate_player(current_player))
current_board[available_square] = INITIAL_MARKER
move[available_square] = score
moves.push(move)
end
best_move = nil
best_score = current_player == 'computer' ? -Float::INFINITY : Float:: INFINITY
if current_player == 'computer'
for hsh in moves
hsh.each do |move, score|
if score > best_score
best_move = move
best_score = score
end
end
end
else
for hsh in moves
hsh.each do |move, score|
if score < best_score
best_move = move
best_score = score
end
end
end
end
best_move
end
I have been stuck on this for days so any help would be appreciated.
Here is the rest of my code: https://github.com/YBirader/launch_school/blob/master/RB101/lesson_6/ttt.rb
The variable score has to be defined outside of the loop. Then the line
if score > best_score
will work.
I've just been through and debugged this.
It turns out that the minimax method is occasionally returning nil, which is then pushed into the hash of moves on line 94, as it can't compare nil to any number with > or < this throws an error, and we can't proceed.
You just need to add a fallback to 0 when the result is nil, change line 91 to this:
minimax(current_board, alternate_player(current_player)) || 0
You also don't need to define score outside of the loop, as that variable is only used to add it to the hash on line 94. What I'd missed earlier is that score is defined again in the each loop.
I've been working on this for a few days, at least. Testing seems to show the correct value is being returned. My problem is being able to grab the best_move value and have it print out. I set up the suggested_move method and try to use return suggested_move(best_move) but it triggers the method for every level back up the tree. Also it returns the wrong value, which I'm guessing is because it's stopping before depth is back to 0.
In my minimax I had a similar setup the difference being the depth was incremented (not decremented) on successive calls. Point being I was able to say if depth == 0 p best_move. So I'm scratching my head because using that same conditional I get a nil class error in this code.
#board = ["X","O","O","X","X","","","O",""]
def available_spaces
#board.map.with_index {|a, i| a == "" ? i+1 : nil}.compact
end
def suggested_move(move)
p "Suggested move: #{move}"
end
def full?
#board.all?{|token| token == "X" || token == "O"}
end
def suggested_move(move)
p "Move: #{move}"
end
def bestmove(board, depth=0, best_score = {})
return 0 if full?
return 1 if won?
best = -Float::INFINITY
available_spaces.each do |space|
#board[space-1] = current_player
best_score[space] = -bestmove(#board, depth-1, {})
#board[space-1] = ""
end
p best_score
if best_score.max_by {|key,value| value }[1] > best
best = best_score.max_by {|key,value| value }[1]
best_move = best_score.max_by {|key,value| value }[0]
end
return best_move
end
bestmove(#board)
I have the following tic-tac-toe game: (I'm a noob, please disregard the design of the class, the game works, that's all I care about for now.)
#a tic tac toe game
class TicTacToe
require "yaml"
attr_accessor :player1, :player2
#crates playes and a game board to play tic tac toe
def initialize()
#player1 = Player.new("Player One", "x")
#player2 = Player.new("Player Two", "o")
#game_board = Board.new
end
#prints the board
def print_board
#game_board.board.each_with_index do |row, index|
puts "#{row.join(" | ")}"
puts "---------" unless index == 2
end
puts
end
#determines whose move it is
def move
if #turn % 2 == 1
player_one_turn
else
player_two_turn
end
#turn += 1
end
def valid_move?(row, col)
if #game_board.board[row][col] == " "
return true
else
return false
end
end
#player ones turn
def player_one_turn
print_board
puts "#{#player1.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player1.shape
else
puts "There's already a shape at that position."
player_one_turn
end
if win?(#player1.shape)
winner(#player1.name)
#winner = true
end
end
#player two's turn
def player_two_turn
print_board
puts "#{#player2.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player2.shape
else
puts "There's already a shape at that position."
player_two_turn
end
if win?(#player2.shape)
winner(#player2.name)
#winner = true
end
end
def win?(shape)
if (#game_board.board[0][0] == shape) && (#game_board.board[0][1] == shape) && (#game_board.board[0][2] == shape)
return true
elsif (#game_board.board[1][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[1][2] == shape)
return true
elsif (#game_board.board[2][0] == shape) && (#game_board.board[2][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][0] == shape) && (#game_board.board[2][0] == shape)
return true
elsif (#game_board.board[0][1] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][1] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][2] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][0] == shape)
return true
else
return false
end
end
def draw?
if #turn > 9
print_board
puts "The game ended in a draw. :)"
#winner = true
return true
end
false
end
def winner(winner_name)
puts "#{winner_name}, YOU WIN!!!"
end
def play
#turn = 1
#winner = false
until #winner
move unless draw?
save
end
end
#a class that generates an empty board
class Board
attr_accessor :board
def initialize
#board = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
end
end
#a class that assigns creates plaers and assigns them a shape "x" or "o"
class Player
attr_accessor :name, :shape
def initialize(name, shape)
#name = name
#shape = shape
end
end
def save
yaml = YAML::dump(self)
File.open("save.txt", "w"){|file| file.write(yaml)}
end
def self.load
file = File.read("save.txt")
YAML::load_file(file)
end
end
game = TicTacToe.new
game.play
I want to start playing the game, quit the program in the middle of the game and then come back and finish it later after I call TicTacToe.load. However, when I do this now, the YAML file is loaded, but program does not resume where it's supposed to.
Can someone tell me if there is a way to do what I'm trying to do?
I regionally thought that doing something like YAML::load(self) would automatically load the save state of the file I was referring to via some kind of magic. However, I have come to learn that that the way that I designed my class and the dependencies in my "play" function would not allow me to load the previous state if my file.
When loading a YAML file, one has to load the file to a variable and then manually assign the object values to values of the class. That way, the current state of the variables are pretty much being manually assigned to the instance variables of the class. For example, I could have done something like this: file = YAML.load("file_name"), then assign variables values like: #board = file.board.
Had I known this before, I would have designed my class with less dependencies so that it would be loadable in a much cleaner and more convenient way.
Don't understand why #nums.pop won't work in the value method. It seems to tell me that it can't do that for nil, but if I just say #nums, it shows that there is indeed something in the array. So then why can't I pop it out?
class RPNCalculator
def initialize
#value = value
nums ||= []
#nums = nums
end
def push(num)
#nums << num
end
def plus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop + #nums.pop
#nums.push(#value)
end
end
def minus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2] - #nums[-1]
#nums.pop(2)
#nums.push(#value)
end
end
def divide
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2].to_f / #nums[-1].to_f
#nums.pop(2)
#nums.push(#value)
end
end
def times
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop.to_f * #nums.pop.to_f
#nums.push(#value)
end
end
def value
#nums #Don't understand why #nums.pop won't work here
end
def tokens(str)
str.split(" ").map { |char| (char.match(/\d/) ? char.to_i : char.to_sym)}
end
def evaluate(str)
tokens(str).each do |x|
if x == ":-"
minus
elsif x == ":+"
plus
elsif x == ":/"
divide
elsif x ==":*"
times
else
push(x)
end
end
value
end
end
Error relates to the following part of a spec:
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
Error says either:
Failure/Error: calculator.value.should == 5
expected: 5
got: [5] <using ==>
OR if .pop is used
Failure/Error: #calculator = RPNCalculator.new
NoMethodError:
undefined method 'pop' for nil:NilClass
Your initialize method assigning #value = value calls the function at def value which returns #nums which has not yet been created in initialize since #nums is created afterwards with nums ||= []; #nums = nums therefore it's nil. This is why .pop won't work.
You've created #nums as an array with nums ||= [] and you're using it with push and pop so why are you checking for the value with value.should == 5 (Integer) when calling value returns an (Array). You would need to write it like value.first.should == 5 or value[0].should == 5 ... otherwise you should change value to return just the element you want
def value
#nums.pop # or #nums[0], or #nums.first or #nums.last however you plan on using it
end
The problem is #value = value in your initialize method. Fix that then you can add the .pop in value.
EDIT
Also your evaluation is calling methods before you've populated #nums with the values. Then the methods "raise" errors. You can't call minus after only one value has been pushed to #nums.
Here's how I would do the flow for splitting the string
# Multiplication and Division need to happen before addition and subtraction
mylist = "1+3*7".split(/([+|-])/)
=> ["1", "+", "3*7"]
# Compute multiplication and division
mylist = mylist.map {|x| !!(x =~ /[*|\/]/) ? eval(x) : x }
=> ["1", "+", 21]
# Do the rest of the addition
eval mylist.join
=> 22
I realize this isn't exactly how you're going about solving this... but I think splitting by order of mathematical sequence will be the right way to go. So first evaluate everything between (), then only multiplication and division, then all addition and subtraction.
EDIT I just looked into what a RPN Calculator is. So don't mind my splitting recommendation as it doesn't apply.
On this page:
https://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/45-more-classes/lessons/105-equality_of_objects
I am trying to correct this code, so that it passes its tests.
My attempt is quite bad as I am only just beginning to learn how software logic works.
class Item
attr_reader :item_name, :qty
def initialize(item_name, qty)
#item_name = item_name
#qty = qty
end
def to_s
"Item (#{#item_name}, #{#qty})"
end
def ==(other_item)
end
end
p Item.new("abcd",1) == Item.new("abcd",1)
p Item.new("abcd",2) == Item.new("abcd",1)
This is my solution, but it is not correct:
class Item
attr_reader :item_name, :qty
def initialize(item_name, qty)
#item_name = item_name
#qty = qty
end
def to_s
"Item (#{#item_name}, #{#qty})"
end
def ==(other_item)
if self == other_item
return true
else
return false
end
end
end
p Item.new("abcd",1) == Item.new("abcd",1)
p Item.new("abcd",2) == Item.new("abcd",1)
I was hoping a Rubyist out there could help me solve this exercise. I am unsure of how to solve it.
Thanks for your help
here is the output from the test:
STDOUT:
nil
nil
Items with same item name and quantity should be equal
RSpec::Expectations::ExpectationNotMetError
expected Item (abcd, 1) got Item (abcd, 1) (compared using ==) Diff:
Items with same item name but different quantity should not be equal ✔
Items with same same quantity but different item name should not be equal ✔
When you override the == method, you should give meaning to your comparison. The default == behavior checks that the other item is identical to the compared item (they have the same object_id). Try this:
def ==(other_item)
other_item.is_a?(Item) &&
self.item_name == other_item.item_name &&
self.qty == other_item.qty
end
Can point you in right direction instead of telling the answer.
You are comparing the references of objects for equality, whereas, you are asked to compare only those attributes for equality. That is, compare both objects parameters in such a way that if they are equal, it must return true; else false
When you scroll down past the question you will see that the next example provides a clear solution.
def ==(other_item)
self.item_name == other_item.item_name &&
self.qty == other_item.qty
end
end