Search code examples
rubycomparison-operators

String || comparison operator in Ruby


I am new to Ruby and am trying to practice by coding up a simple TicTacToe game.

I have just run into a small issue with the evaluation of if a player has won the game.

Essentially the game works with a board that is equal to:

board = ["#",' ',' ',' ',' ',' ',' ',' ',' ',' ']

Every time a player moves their "marker" (X or O) is added to the board and there are functions to check if the spot is taken, if it is a draw or if a player has won.

So an interim board might look something like this:

board = ["#",' ',' ','O',' ','X',' ','O',' ',' ']

My issue is coming when evaluating if someone won. I have written a function for this: (the first 3 expressions check for a horizontal win, the next 3 a vertical win and the following 2 look for diagonal wins.

def check_for_win(board)
  if [board[1],board[2],board[3]].uniq == [("X" || "O")] || [board[4],board[5],board[6]].uniq == [("X" || "O")] ||
    [board[7],board[8],board[9]].uniq == [("X" || "O")] || [board[1],board[4],board[7]].uniq == [("X" || "O")] ||
    [board[5],board[2],board[8]].uniq == [("X" || "O")] || [board[6],board[9],board[3]].uniq == [("X" || "O")] ||
    [board[1],board[5],board[9]].uniq == [("X" || "O")] || [board[7],board[5],board[3]].uniq == [("X" || "O")] 
    true
  else
    false
  end 
end 

This function seems to evaluate if there is a winner with the "X" marker, but for the "O" marker if cannot evaluate to true. It should be noted that the left side of the expression still evaluates to what I want it to, for example:

board = ["#",' ',' ','O',' ','O',' ','O',' ',' ']
p [board[7],board[5],board[3]].uniq 
>> ["O"]

I can not seem to understand why and any direction on this would be greatly appreciated.


Solution

  • 'X' || 'O' just says X or O. And since any string is truthy it always returns X. So any spot where you’ve said [('X' || 'O')], you’ve really just said ['X'].

    Because of this, you’re only ever checking if a whole line of 3 is all X.

    I don’t really understand how you’re trying to test this, but I feel like you’d be better off running the function twice first handing in X and then handing in O if it didn’t find X as a winner, as opposed to trying to check both as once.

    Alternatively you could instead have the function return 'X', 'O', or nil and then you could have it only run the function once. The string returned would be who won and if it’s nil then no one won. I would also recommend making a loop for this. I find it easier to read.

    Here's how I would solve the problem.

    ROWS = [
      [1,2,3],
      [4,5,6],
      [7,8,9],
    
      [1,4,7],
      [2,5,8],
      [3,6,9],
    
      [1,5,9],
      [7,5,3],
    ]
    
    def find_winner(board)
      ROWS.each do |row|
        # subtract 1 because the ROWS is 1-indexed (like a phone dial) but arrays are 0-indexed
        row_values = row.map { |v| board[v - 1] }
        return('X') if row_values.all?('X')
        return('O') if row_values.all?('O')
      end
    
      return(nil)
    end
    
    
    test1 = [
      'X', 'X', 'X',
      'O', 'X', 'O',
      'O', 'O', '',
    ]
    puts "Winner of test1: #{find_winner(test1).inspect}"
    "X"
    
    test2 = [
      'X', '',  'X',
      'X', 'O', 'O',
      'X', 'O', 'X',
    ]
    puts "Winner of test2: #{find_winner(test2).inspect}"
    "X"
    
    test3 = [
      'O', 'X', 'X',
      'O', 'X', 'O',
      'O', 'O', '',
    ]
    puts "Winner of test3: #{find_winner(test3).inspect}"
    "O"
    
    test4 = [
      'O', 'X', 'O',
      'X', 'O', 'X',
      'O', 'O', 'X',
    ]
    puts "Winner of test4: #{find_winner(test4).inspect}"
    "O"
    
    test5 = [
      'O', 'X', 'O',
      'O', 'X', 'O',
      'X', 'O', 'X',
    ]
    puts "Winner of test5: #{find_winner(test5).inspect}"
    nil