I am solving the " rock, paper, scissor" game. Input an array and recursively output the winner. Here is my code:
class RockPaperScissors
# Exceptions this class can raise:
class NoSuchStrategyError < StandardError; end
def self.winner(player1, player2)
strategy = player1[1]+player2[1]
raise NoSuchStrategyError.new("Strategy must be one of R,P,S") if strategy !~ /(R|P|S){2}/
strategy =~ /rs|sp|pr|rr|ss|pp/i ? player1 : player2
end
def self.tournament_winner(tournament)
if tournament.length==2 && tournament.first[0].is_a?(String)
winner(tournament[0],tournament[1])
else
#keep slice the array in half
***winner(tournament_winner(tournament[0,tournament.length/2]), tournament_winner(tournament[tournament.length/2]))***
end
end
end
I got stack level too deep because that code in bold. Is it because the tournament.length is changing so I shouldn't put it inside the recursion? Could someone gives a detailed explanation about how that happened?
I searched the answer and someone used the code below and worked. I wonder why that reference to tournament won't cause the same recursion issue.
winner(tournament_winner(tournament[0]), tournament_winner(tournament[1]))
Thank you for any help!
sample input:
[
[
[ ["Armando", "P"], ["Dave", "S"] ],
[ ["Richard", "R"], ["Michael", "S"] ],
],
[
[ ["Allen", "S"], ["Omer", "P"] ],
[ ["David E.", "R"], ["Richard X.", "P"] ]
]
]
Your code does not slice the tournament in half - tournament[tournament.length/2] does not return the second half of the array - it only returns the item in position tournament.length/2, instead you should do:
winner(tournament_winner(tournament[0...tournament.length/2]), tournament_winner(tournament[tournament.length/2..-1]))
Also, you do not consider an array of length 1, which results in an endless recursion.
Here is a working version of your code:
def tournament_winner(tournament)
if tournament.length == 1
tournament_winner(tournament[0])
elsif tournament.length==2 && tournament.first[0].is_a?(String)
winner(tournament[0],tournament[1])
else
winner(tournament_winner(tournament[0...tournament.length/2]),
tournament_winner(tournament[tournament.length/2..-1]))
end
end
There are so many better ways of handling this than messy regexs, confusing abbreviations, and strings all over the place.
For example, you could go with a more object oriented approach:
module RockPaperScissors
class NoSuchStrategyError < StandardError; end
class Move
include Comparable
attr_reader :strategy
def initialize(strategy)
raise NoSuchStrategyError unless strategies.include?(strategy)
#strategy = strategy
end
def <=>(opposing_move)
if strengths[strategy] == opposing_move.strategy
1
elsif strengths[opposing_move.strategy] == strategy
-1
else
0
end
end
protected
def strengths
{
rock: :scissors,
scissors: :paper,
paper: :rock
}
end
def strategies
strengths.keys
end
end
class Player
attr_reader :name, :move
def initialize(name, move)
#name, #move = name, move
end
end
class Tournament
def initialize(*players)
#player_1, #player_2, _ = *players
end
def results
p1move = #player_1.move
p2move = #player_2.move
if p1move > p2move
"#{#player_1.name} wins."
elsif p2move > p1move
"#{#player_2.name} wins."
else
"Tie."
end
end
end
end
Example usage:
rock = RockPaperScissors::Move.new(:rock)
paper = RockPaperScissors::Move.new(:paper)
player_1 = RockPaperScissors::Player.new('John Smith', rock)
player_2 = RockPaperScissors::Player.new('Corey', paper)
tournament = RockPaperScissors::Tournament.new(player_1, player_2)
tournament.results #=> "Corey wins."
Related
I can play the game. Switch the player all working fine but not getting result who won the game.
def initialize_board
#count = 9
#player = PLAYER_ONE #current_player
#board = Array.new(3){ Array.new(3, " ") }
end
def play
inputs = get_inputs
return false if !inputs
update_board(inputs)
print_board
end
def switch_player
if(#player == PLAYER_ONE)
#player = PLAYER_TWO
else
#player = PLAYER_ONE
end
end
def game_over?
# #count = #count - 1
# #count <= 0
if check_winner
puts "#{#player} won "
end
end
def check_winner
WIN_COMBINATIONS.find do |indices|
binding.pry
values = #board.values_at(*indices)
values.all?('X') || values.all?('O')
end
end
Here I am getting indices [0,1,2] in all cases while debugging.
The main reason why you're not getting the winner is because your 'values = #board.values_at(*indices)' statement returns an array of arrays. And values.all?('X') || values.all?('O') checks not an 'X' or 'O' pattern but an array object. So you need to flatten an array first.
values.flatten!
Stefan already answered similar question , but his board was one-dimensional because of %w expression, you can read about it here
New at Ruby so excuse the poor code. I would like to iterate through the multidimensional array WIN_COMBINATIONS and check whether at least one array has all of its elements equal to 'X' or all equal to 'O'. If so, return the matched array. This is done through the won? function but it seems to only be returning the entire multidimensional array. Any assistance would be appreciated.
class TicTacToe
WIN_COMBINATIONS = [
[0,1,2], # top_row
[3,4,5], # middle_row
[6,7,8], # bottom_row
[0,3,6], # left_column
[1,4,7], # center_column
[2,5,8], # right_column
[0,4,8], # left_diagonal
[6,4,2] # right_diagonal
]
def initialize
#board = Array.new(9, " ")
end
def display_board
puts " #{#board[0]} | #{#board[1]} | #{#board[2]} "
puts "-----------"
puts " #{#board[3]} | #{#board[4]} | #{#board[5]} "
puts "-----------"
puts " #{#board[6]} | #{#board[7]} | #{#board[8]} "
end
def input_to_index(board_position)
user_input = board_position.to_i
user_input - 1
end
def move(board_index, player_token = 'X')
#board[board_index] = player_token
end
def position_taken?(board_position)
if #board[board_position] == ' '
false
else
true
end
end
def valid_move?(board_position)
if board_position >= 0 and board_position <= 8
if #board[board_position] == ' '
true
end
else
false
end
end
def current_player
turn_count % 2 == 0 ? "X" : "O"
end
def turn_count
#board.count{|token| token == "X" || token == "O"}
end
def turn
puts "Select your move (1-9)\n"
move = gets.chomp
move_index = input_to_index(move)
if valid_move?(move_index)
token = current_player
move(move_index, token)
display_board
else
puts "Select your move (1-9)\n"
move = gets.chomp
end
end
def won?
WIN_COMBINATIONS.each do |combinations|
if combinations.all? {|combination| combination == 'X' or combination == 'O'}
combinations
else
false
end
end
end
def draw?
if full? and !won?
true
elsif won?
false
else
false
end
end
def over?
end
def winner
end
def play
end
end
[...] it seems to only be returning the entire multidimensional array.
There are several issues with your attempted solution:
WIN_COMBINATIONS is an array of indices. These indices are numeric, so they will never be 'X' or 'O'. You have to check whether their corresponding values are 'X' or 'O'.
or is a control-flow operator intended for do_this or fail scenarios. The boolean "or" operator is ||. Using or instead of || might work but may have unexpected results due to its lower precedence. You almost always want ||.
The expression array.all? { |element| element == 'X' || element == 'O' } checks whether all elements are either 'X' or 'O'. It would be true for ['X','O','O'] and false for ['X',' ','O']. That's because you put the conditional inside the block. What you want is to check whether the elements are all 'X', or all 'O':
array.all?('X') || array.all?('O')
Your method's return value is the result of WIN_COMBINATIONS.each { ... } and Array#each always returns the array itself (i.e. WIN_COMBINATIONS) regardless of the blocks' result. To get the first element matching a condition, use find.
Let's apply all this to your code. Given this board:
#board = %w[
X - O
O X -
- - X
]
You could get the first matching combination via:
WIN_COMBINATIONS.find do |indices|
values = #board.values_at(*indices)
values.all?('X') || values.all?('O')
end
#=> [0, 4, 8]
values_at returns the values for the corresponding indices (* transforms the indices array to a list of arguments, so values_at(*[0,1,2]) becomes values_at(0,1,2)). The block's 2nd line then checks whether these values are all 'X', or all 'O'. Once this evaluates to true, the loop breaks and find returns the matching element. (or nil if there was no match)
Here is how I would approach the problem:
class TicTacToe
class OccupiedError < StandardError; end
attr_reader :rows
def initialize
#rows = 3.times.map{ Array(3, nil) }
end
def place!(player, x:, y:)
raise ArgumentError, "player must be :x or :o" unless [:x, :o].include?(player)
raise OccupiedError, "slot is already occupied" unless #rows[y][x].nil?
#rows[y][x] = player
end
# gets an array of columns instead of rows.
def columns
(0..2).map { |n| #rows.map {|row| row[n] } }
end
def diagonals
[
[#rows[0][0], #rows[1][1], #rows[2][2]], # lrt
[#rows[0][2], #rows[1][1], #rows[2][0]] # rtl
]
end
def all_combos
rows + columns + diagonals
end
# checks all the horizontal, vertical and diagonal combinations
def check_for_winner
# checks all combos for three in a row
(all_combos.find{ |a| a.all?(:x) || a.all?(:o) })&.first
end
end
In the initialize method we create a 3*3 array which represents all the positions on the board. This makes it a lot easier since its already grouped in rows. Intead of an empty string use nil to represent an empty square as nil is falsy.
When we want to check for a winner we gather up the rows, columns and the two diagonals into an array of arrays:
[1] pry(main)> game.rows
=> [[:o, :o, :o], [nil, :x, :x], [:x, nil, nil]]
[2] pry(main)> game.all_combos
=> [[:o, :o, :o],
[nil, :x, :x],
[:x, nil, nil],
[:o, nil, :x],
[:o, :x, nil],
[:o, :x, nil],
[:o, :x, nil],
[:o, :x, :x]]
From there we just have to check if any of them are all :x or :o. We don't actually have to list the winning combinations. In this case game.check_for_winner will return :o.
I'm trying to program the AI for a Mastermind game in ruby using Donal Knuth's 5 guess algorithm. The game consists of a codemaker, who uses 8 different colored pegs to create a set of 4, and a codebreaker, who guesses at the code and receives feedback (a red square for a peg which is both the right color and in the right spot, and a white square for a peg which is the right color but in the wrong spot).
I've created a set for all possible codes. My goal is to compare feedback from the guess to feedback from all codes in the set, then delete the ones that don't match. It seems to delete the entire set though.
class ComputerPlayer < Player
def initialize(game)
super(game)
#all_possible_codes = create_codes
#turn = 1
end
def get_code
Array.new(4){rand(1..6)}
end
def get_guess
puts #all_possible_codes.length
if #turn == 0
#turn += 1
cull_set([1, 1, 2, 2])
#all_possible_codes.delete("1122")
return [1, 1, 2, 2]
else
random_sample = #all_possible_codes.to_a.sample.split('').map{|str| str.to_i}
#all_possible_codes.delete(random_sample.join(''))
cull_set(random_sample)
random_sample
end
end
def cull_set(guess)
feedback = #game.feedback_on_guess(guess)
puts feedback
#all_possible_codes.delete_if { |str| #game.feedback_on_guess(str.split.map{|num| num.to_i}) != feedback }
end
def create_codes
set = Set.new
(1..8).each do |i|
(1..8).each do |j|
(1..8).each do |k|
(1..8).each do |l|
set << [i, j, k, l].join('')
end
end
end
end
set
end
end
#this is the feedback_on_guess method used by the above class
def feedback_on_guess(code_guess)
code_duplicate = #code
feedback = []
code_duplicate.map.with_index do |entry, i|
if entry == code_guess[i]
feedback.push('r')
code_guess[i] = -1
-2
else
entry
end
end.each do |entry|
found_index = code_guess.find_index(entry)
if found_index
feedback.push('g')
code_guess[found_index] = -1
end
end
puts feedback
feedback
end
Try
copy = something.dup
because after just
copy = something
copy and something are pointing to the same object. You can confirm this by checking the object_id of the object referenced by the variable. If it is the same, then it is the same object.
When you dup an object, you will cretae a copy. Depending on what you want to dup you might need to implement/override the logic to create a copy. For built in Classes like String, Hash and so on it will work out of the box.
Be aware that nested constructs (eq. Hash containing other Hashes) are not duplicated.
h1 = {"a" => {"b" => 2}}
h2 = h1.dup
puts h1.object_id # 70199597610060
puts h2.object_id # 70199597627020
puts h1["a"].object_id # 70199597610080
puts h2["a"].object_id # 70199597610080
I am banging my head against the error message I keep getting, reading that "syntax error, unexpected keyword_end, expecting end-of-input." I cannot find my mistake for the life of me. It's probably sloppy-looking, I am a newbie. And any tips on preventing this specific issue in the future would also be greatly appreciated!
$move_direction_hash = {"N" => [0,1], "E" => [1,0], "S" => [0,-1], "W" => [-1,0]}
$cardinal_directions = ["N", "E", "S", "W"]
class Martian
attr_accessor :coordinate_x, :coordinate_y, :bearing, :direction_string
def initialize (coordinate_x, coordinate_y, bearing, direction_string)
#coordinate_x = coordinate_x
#coordinate_y = coordinate_y
#bearing = bearing
#direction_string = direction_string
end
def check_valid
#coordinate_x.between?(0, $boundaries[0]) && coordinate_y.between?(0, $boundaries[1])
return true
end
end
#will be second and last called in source code
def get_final
return "#{coordinate_x} #{coordinate_y} #{bearing}"
end
def move_forward
# find where in the hash the bearing is
position_array = $move_direction_hash[#bearing]
# returns a temporary variable
# that is the
test_x = #coordinate_x + position_array[0]
test_y = #coordinate_y + position_array[1]
if $rovers_on_grid.include?([test_x.to_s, test_y.to_s])
puts "Stopping Martian. You're about to crash!"
get_final
break
else
#coordinate_x = test_x
#coordinate_y = test_y
if check_valid == false
puts "Stopping Martian. About to run off the plateau."
get_final
break
else
return #coordinate_x, #coordinate_y
end
end
end
def add_to_grid
$rovers_on_grid << [#x_coordinate, #y_coordinate]
end
def read_instructions
#direction_string.each_char do |direction|
if direction == "L" || direction == "R"
position = $cardinal_directions.index(#bearing)
if direction == "L"
position = (position - 1)%4
$cardinal_directions[position]
elsif direction == "R"
position = (position + 1)%4
$cardinal_directions[position]
else
puts "Error!"
end
#bearing = $cardinal_directions[position]
elsif direction == "M"
move_forward
end
end
end
end
This error is located in the check_valid method. You missed the if.
def check_valid
if #coordinate_x.between?(0, $boundaries[0]) && coordinate_y.between?(0, $boundaries[1])
return true
end
end
Like steenslag mentioned the if statement is not required. You can write:
def check_valid
return #coordinate_x.between?(0, $boundaries[0]) && coordinate_y.between?(0, $boundaries[1])
end
I am trying to split the sub-arrays if there are more than 8. I have tried calling the rps_tournament_winner function on players if it has a flattened length longer than 16 but I get a "stack too deep error".
Do I have to work on the players variable or tournament? I'm looking for a nudge in the right direction; not a complete solution.
def rps_tournament_winner(tournament)
return rps_game_winner(tournament) if tournament.flatten.length == 4
players = tournament.flatten(2)
while players.length > 1
players = players.each_slice(2).map { |x| rps_game_winner(x) }
end
players[0]
end
This is the shortest I was able to come up with (with recursion)
def rps_tournament_winner(games)
if games.flatten.length > 4
rps_game_winner([rps_tournament_winner(games[0]), rps_tournament_winner(games[1])])
else
rps_game_winner(games)
end
end
This is also an elegant solution w/o flattening the array:
def rps_tournament_winner(tournament)
if tournament[0][0].is_a?(Array)
tournament =[rps_tournament_winner(tournament[0]), rps_tournament_winner(tournament[1])]
end
rps_game_winner(tournament)
end
I solved it using recursion
class WrongNumberOfPlayersError < StandardError ; end
class NoSuchStrategyError < StandardError ; end
def rps_game_winner(game)
raise WrongNumberOfPlayersError unless game.length == 2
if game[0][0].is_a?(Array) then
winner1 = rps_game_winner(game[0])
winner2 = rps_game_winner(game[1])
game = [winner1, winner2]
end
raise NoSuchStrategyError unless /^(P|R|S){2}$/ =~ game[0][1] + game[1][1]
case game[0][1]
when "R"
if game[1][1] == "P" then
game[1]
else
game[0]
end
when "P"
if game[1][1] == "S" then
game[1]
else
game[0]
end
when "S"
if game[1][1] == "R" then
game[1]
else
game[0]
end
end
end
def rps_tournament_winner(tournament)
rps_game_winner(tournament)
end
Only answering this to close the question, hopefully my answer will help some one else =D
After staring at it for about three hours, my mind actually stopped napping and i came up with this:
def rps_tournament_winner(tournament)
return rps_game_winner(tournament) if tournament.flatten.length == 4
if tournament.flatten.length == 16
players = tournament.flatten(2)
else
players = tournament.flatten(4)
end
while players.length > 1
players = players.each_slice(2).map { |x| rps_game_winner(x) }
end
players[0]
end
The if statement allows me to check that if a tournament of 8 or more players (the testing was only for 8, 16 and 32 players so I doubt this would work for larger sets) My problem before was that I would only flatten(2) which for the larger tournament would not work.