How to find first winning combination in Tic-Tac-Toe board? - ruby

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.

Related

Using a method that accepts blocks and can return truthy or falsey values

So, I am back with another question! I have learned how to accept arguments with blocks but I need now to place a block in my method (I think).
Here is what I have to do, it's a generalized reduce method with the following tests going through it:
describe 'my own reduce' do
it "returns a running total when not given a starting point" do
source_array = [1,2,3]
expect(reduce(source_array){|memo, n| memo + n}).to eq(6)
end
it "returns a running total when given a starting point" do
source_array = [1,2,3]
starting_point = 100
expect(reduce(source_array, starting_point){|memo, n| memo + n}).to eq(106)
end
it "returns true when all values are truthy" do
source_array = [1, 2, true, "razmatazz"]
expect(reduce(source_array){|memo, n| memo && n}).to be_truthy
end
it "returns false when any value is false" do
source_array = [1, 2, true, "razmatazz", false]
expect(reduce(source_array){|memo, n| memo && n}).to be_falsy
end
it "returns true when a truthy value is present" do
source_array = [ false, nil, nil, nil, true]
expect(reduce(source_array){|memo, n| memo || n}).to eq(true)
end
it "returns false when no truthy value is present" do
source_array = [ false, nil, nil, nil]
expect(reduce(source_array){|memo, n| memo && n}).to eq(false)
end
end
Here is my code:
def reduce(element1, starting_point = 0, &block)
element1.reduce(starting_point, &block)
end
Which passes 4 out of the 6 tests. But the last part requires checking the values in the source_array and if any are truthy return true or if any are falsey, return false. I tried putting in the follow block along with the reduce method:
def reduce(element1, starting_point = 0, &block)
element1.reduce(starting_point, &block){ |x, y| if x || y = true; p true; else p false; end}
end
If you look at the tests, you can see it will pass one array with 'true' and one with 'false' and I need it to work for all the 6 tests.
Please, any explanation has been helping me greatly.
If your job is to write your own reduce, don't use Enumerable#reduce inside. You can use Enumerable#each or for/while loop
You can pass a block to another method just as you do it with method(arg1, arg2, &block).
You can call your block with #call, e.g. block.call(arg1, arg2)
You cannot specify a default value for starting_point which will work for every use case, since you want to use numbers & booleans.
If you don't specify a starting_point, reduce will simply use the first element as starting_point:
def reduce(elements, starting_point = nil, &block)
if starting_point.nil?
elements.reduce(&block)
else
elements.reduce(starting_point, &block)
end
end
I think the failing test should specify a starting_point.
it "returns true when a truthy value is present" do
source_array = [ false, nil, nil, nil, true]
- expect(reduce(source_array){|memo, n| memo || n}).to eq(true)
+ expect(reduce(source_array, false){|memo, n| memo || n}).to eq(true)
end
The meaning of || depends on the left-hand side. Integer#|| is different from eg. FalseClass#||. They are different methods.

Nil when calling a method on the variable, but not nil when calling just the variable

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.

Refactoring feedback for Reverse Polish Notation (RPN) or Postfix Notation

One of the pre-work exercises for Dev Bootcamp is an RPN calculator. I made it work but would like refactoring feedback. Any and all help to make this code cleaner is greatly appreciated.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
case
when i == "+" then array << b[0] + b[1]
when i == '-' then array << b[0] - b[1]
when i == '*' then array << b[0] * b[1]
when i == '/' then array << b[0] / b[1]
end
end
end
p array.pop
end
end
calc = RPNCalculator.new
calc.evaluate('1 2 +') # => 3
calc.evaluate('2 5 *') # => 10
calc.evaluate('50 20 -') # => 30
calc.evaluate('70 10 4 + 5 * -') # => 0
class RPNCalculator
def evaluate rpn
array = rpn.split(" ").inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
b = array.pop(2)
array << b[0].send(i, b[1])
end
end
p array.pop
end
end
I tend to prefer avoiding case..when in favor of lookup tables. So I'd change your code to:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
array << i.to_i
else
array << array.pop(2).reduce(op(i))
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I also don't believe you should only be popping off 2 operands. "1 2 3 +" would be valid RPN, evaluating to 6. The entire stack should be reduced. This also avoids the mutation, which is a good thing, as it follows a more functional style.
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
array = a.inject([]) do |array, i|
if i =~ /\d+/
[*array, i.to_i]
else
[array.reduce(op(i))]
end
end
p array.pop
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I removed the other mutation here too, by using [*arr, value] instead of actually modifying the array.
Finally, I'd avoid printing directly from your #evaluate method and just return the number. I'd also (again) avoid the mutation:
class RPNCalculator
def evaluate(rpn)
a = rpn.split(' ')
stack = a.inject([]) do |stack, i|
if i =~ /\d+/
[*stack, i.to_i]
else
[stack.reduce(op(i))]
end
end
stack.last
end
private
def op(char)
{'+'=>:+, '-'=>:-, '/'=>:/, '*'=>:*}[char]
end
end
I renamed 'array' to 'stack', since it is a parser stack and is less generic than just array.

Boolean comparison of array of strings in Ruby

I've got an array in Ruby that essentially represents a square boolean matrix. Dots represent zeroes, while any other character represents ones. Example:
irb(main):044:0> g
=> [".b", "m."] # This grid has two '1' values and two '0' values.
I'd like to perform a specified logical operation (say, OR) on this array with another similar array to get a third result. For example, if h is ["q.", "r."], then something akin to g.perform_or(h) should yield a new array ["qb", "r."]. (The choice of r to represent the result of 'm' || 'r' is arbitrary and not relevant; any other non-'.' character can be there.)
How might I do this?
Edit: I made an error in my example. Apologies!
For OR:
g.zip(h).map {|gx,hx| (0...gx.size).map {|i| [gx[i..i],hx[i..i]].any? {|cell| cell != "."} ? "x" : "."}.join}
For AND just change the "any?" to "all?".
Man, this one has been gathering dust on my disk for a loong time:
class String
def to_bool; return chars.map {|c| if c == '.' then false else c end } end
def from_bool; return self end
end
class TrueClass; def from_bool; return 't' end end
class FalseClass; def from_bool; return '.' end end
class Array
def to_bool; map(&:to_bool) end
def from_bool; map {|row| row.map(&:from_bool).join} end
def |(other)
to_bool.zip(other.to_bool).inject([]) {|row, (l, r)|
row << l.zip(r).inject([]) {|col, (l, r)|
col << (l || r).from_bool
}
}
end
def &(other)
to_bool.zip(other.to_bool).inject([]) {|row, (l, r)|
row << l.zip(r).inject([]) {|col, (l, r)|
col << (l && r).from_bool
}
}
end
end
Here's a (rather incomplete) testsuite:
require 'test/unit'
class TestLogicMatrix < Test::Unit::TestCase
def test_to_bool
assert_equal [['a', false], [false, 'z']], ['a.', '.z'].to_bool
end
def test_from_bool
assert_equal ['a.', 'tz'], [['a', false], [true, 'z']].from_bool
end
def test_that_OR_works
assert_equal ['qb', 'm.'], (['.b', 'm.'] | ['q.', 'r.']).from_bool
end
def test_that_AND_works
assert_equal ['..', 'r.'], (['.b', 'm.'] & ['q.', 'r.']).from_bool
end
end

Generate a different range in Ruby i.e. all possible /[0-9A-Za-z]{3}/

I feel like I'm using Ruby the wrong way here: I want to generate all possible matches for the regular expression /[0-9A-Za-z]{3}/
I can't use succ because "999".succ => "1000" and "zZz".succ => "aaAa".
I'm having trouble using ranges because I can't seem to union (0..9), ('A'..'Z'), ('a'..'z')
So I wrote:
def alphaNumeric
#range and succ don't cut it for [0-9a-zA-Z]
(0..9).each{|x|yield x.to_s}
('a'..'z').each{|x|yield x}
('A'..'Z').each{|x|yield x}
end
def alphaNumericX3
alphaNumeric{ |a|
alphaNumeric{ |b|
alphaNumeric{ |c|
yield a+b+c
}
}
}
end
alphaNumericX3.each{|x|p x}
My question is 2 fold:
Is there a less ugly way, and is there a way where alphaNumericX3 could be defined from the parameters (alphaNumeric, 3)?
PS I'm aware that I could define a new class for range. But thats definitly not shorter. If you can make this next block shorter and clearer than the above block, please do:
class AlphaNum
include Comparable
attr :length
def initialize(s)
#a=s.chars.to_a
#length=#a.length
end
def to_s
#a.to_s
end
def <=>(other)
#a.to_s <=> other.to_s
end
def succ
def inc(x,n)
return AlphaNum.new('0'*(#length+1)) if x<0
case n[x]
when '9'
n[x]='A'
when 'Z'
n[x]='a'
when 'z'
n[x]='0'
return inc(x-1,n)
else
n[x]=n[x].succ
end
return AlphaNum.new(n.to_s)
end
inc(#length-1,#a.clone)
end
end
# (AlphaNum.new('000')..AlphaNum.new('zzz')).each{|x|p x}
# === alphaNumericX3.each{|x|p x}
Use Array#product:
alpha_numerics = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
alpha_numerics
.product(alpha_numerics, alpha_numerics)
.map { |triplet| triplet.join('') }
class String
def nextify
case self
when '9' then 'A'
when 'Z' then 'a'
when 'z' then '0'
else self.succ
end
end
end
class AlphaNum
def initialize(string)
#string = string
end
def succ
#string.split(//).inject("") { |s,n| s << n.nextify }
end
def method_missing(*args, &block)
#string.send(*args, &block)
end
end
a = AlphaNum.new("999")
puts a.succ #=> 'AAA'

Resources