Home > OS >  TicTacToe Heavy Win Condition
TicTacToe Heavy Win Condition

Time:02-12

I made a TicTacToe game in Ruby. I am pretty new to Ruby OOP. My win conditions for checking row, column and diagonals are really heavy and they take too much code line and look pretty horrible. My code also always repeating.

So my question is how to implement the below win conditions into my code?

 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 
]

Here is my win conditions:

module CheckWinner
  def check_horizontal(player1, player2)
    player1_win = false
    player2_win = false
    @board.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_vertical(player1, player2)
    player1_win = false
    player2_win = false
    @board.transpose.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_diagonal(player1, player2)
    if @board[0][0] == player1 && board[1][1] == player1 && board[2][2] == player1 ||
       @board[0][2] == player1 && board[1][1] == player1 && board[2][0] == player1
      puts "#{@player1} won!"
      true

    elsif @board[0][0] == player2 && board[1][1] == player2 && board[2][2] == player2 ||
          @board[0][2] == player2 && board[1][1] == player2 && board[2][0] == player2
      puts "#{@player2} won!"
      true
    end
  end
end

I invoke these conditions from this line of code:

def game
    choosing_player
    loop do
      player1(@player1)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?

      player2(@player2)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?
    end
  end
end

Here is my full of project that it is correctly working.

module CheckWinner
  def check_horizontal(player1, player2)
    player1_win = false
    player2_win = false
    @board.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_vertical(player1, player2)
    player1_win = false
    player2_win = false
    @board.transpose.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_diagonal(player1, player2)
    if @board[0][0] == player1 && board[1][1] == player1 && board[2][2] == player1 ||
       @board[0][2] == player1 && board[1][1] == player1 && board[2][0] == player1
      puts "#{@player1} won!"
      true

    elsif @board[0][0] == player2 && board[1][1] == player2 && board[2][2] == player2 ||
          @board[0][2] == player2 && board[1][1] == player2 && board[2][0] == player2
      puts "#{@player2} won!"
      true
    end
  end
end

# TicTacToe Board
class Board
  include CheckWinner
  attr_accessor :board

  def initialize
    @board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  end

  def print_board
    puts '-------------'
    @board.each do |row|
      print '|'
      row.each do |col|
        print " #{col}"
        print ' | '
      end
      puts
    end
    puts '-------------'
  end

  def twodimentional_board
    @board = @board.each_slice(3).map { |el| el }
  end

  def occupied_error(value)
    puts 'There is a value or wrong place! Try Again!'
    twodimentional_board
    print_board
    value == @player1 ? player2(@player1) : player1(@player2) # Stay same player
  end

  def move_if_possible(place, value)
    @board.flatten!
    if @board[place - 1] == 'X' || @board[place - 1] == 'O' || !place.between?(1, 9)
      occupied_error(value)
    else
      @board[place - 1] = value
      twodimentional_board
      @board
    end
  end

  def full?
    if @board.flatten.all?(String)
      puts 'Draw!'
      true
    end
  end

  def choosing_player
    puts 'Choose for Player1(X or O)'
    loop do
      @player1 = gets.chomp!
      break if @player1 == 'X' || @player1 == 'O'

      puts 'Try Again!(X or O)'
    end
    puts "Player 1 is: #{@player1}"

    @player1 == 'X' ? @player2 = 'O' : @player2 = 'X'
    puts "Player 2 is: #{@player2}"
    print_board
  end

  def player1(player1)
    puts "Choice #{player1} Place on a board(1 to 10)"
    @place = gets.chomp!.to_i
    move_if_possible(@place, player1)
    print_board
  end

  def player2(player2)
    puts "Choice #{player2} Place on a board(1 to 10)"
    @place = gets.chomp!.to_i
    move_if_possible(@place, player2)
    print_board
  end

  def game
    choosing_player
    loop do
      player1(@player1)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?

      player2(@player2)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?
    end
  end
end

board = Board.new
board.game

If you want to live preview here is repl link

CodePudding user response:

I'm not sure if this is exactly what you're after, but there are a few things here.

  1. your @board is an array of 3 rows, but your possible wins is just an array of points. flattening your @board means you can use the index as is.
  2. you already have an array of win conditions, you then ignore this array and work through manually all options

definitions

# example winning board for 'O'
@board = [['O','X','O'], ['X','O','X'], ['X','O','O']]
player1 = 'X'
player2 = 'O'

First, loop through the possible combinations in WIN_COMBINATIONS, and for each of those combinations get those cells from @board as individual arrays, for later checking. store the array of arrays as another array (using #map). the * character is the ruby splat operator, which converts an array into arguments for the called procedure.

shorthand:

winmap = WIN_COMBINATIONS.map {|w| @board.flatten.values_at(*w)}

longhand:

flat_board = @board.flatten
winmap = WIN_COMBINATIONS.map do |win|
  flat_board.values_at(*win)
end

which sets up an array like follows:

winmap = [ 
  ["O", "X", "O"], # top_row 
  ["X", "O", "X"], # middle_row 
  ["X", "O", "O"], # bottom_row 
  ["O", "X", "X"], # left_column 
  ["X", "O", "O"], # center_column 
  ["O", "X", "O"], # right_column 
  ["O", "O", "O"], # left_diagonal 
  ["X", "O", "O"] # right_diagonal 
]

once you have the list of all possible winning cells from the board, you can use your existing row searching code, except rather than looping through each array, we use the array operation #any? to identify matches

player1_win = winmap.any?{|m| m.all?(player1)}
player2_win = winmap.any?{|m| m.all?(player2)}

this should eliminate all the individual subroutines, and give you a true/false answer for if anyone has won.

CodePudding user response:

Here's a different approach to determining if the board indicates a win, and if it does, who wins.

Let board be an array of three elements, each an array of three elements, each element being 'X', 'O' or '_', the last indicating the cell has not yet been marked.

Let's create some methods.

def row_win?(board)
  board.each do |row|
    u = row.uniq
    return u.first if u.size == 1
  end
  nil
end

This method returns 'X' if 'X' wins in a row, 'O' if 'O' wins in a row and nil if neither player wins in a row.

def column_win?(board)
  row_win?(board.transpose)
end

This method similarly returns 'X', 'O' or nil, indicating if there is a win in a column, and if so, who wins.

def diagonal_win?(board)
  row_win? [[board[0][0], board[1][1], board[1][1]],
            [board[0][2], board[1][1], board[2][0]]]
end

This method does the same for the two diagonals.

Lastly,

def win?(board)
  row_win?(board) || column_win?(board) || diagonal_win?(board)
end

Let's try it.

win? [['X', '_', 'X'],
      ['_', 'X', 'O'],
      ['X', 'O', 'O']]
  #=> 'X'
win? [['X', 'O', 'X'],
      ['_', 'O', 'X'],
      ['X', 'O', 'O']]
  #=> 'O'
win? [['X', 'O', 'X'],
      ['O', 'O', 'X'],
      ['X', 'X', 'O']]
  #=> nil          
 

CodePudding user response:

Why don't you track state helping you determine winning condition as you mark new moves? That way you don't have to recompute all what you already know.

For instance, if I tell you my board size is 3, that there's already 2 Xs in the first row and now X played a new move in that row leading to 3 Xs, you know X won immediately right? You don't even need to know anything else about the state of the board.

You can apply the same logic here and simply keep track of X & O counts per row, cols & diags as marks are recorded and you won't even have to loop to check who's won.

The above-described approach is easy to implement and takes O(1) time complexity to check the winning condition. You trade a bit of memory (O(n)) for computation speed.

  • Related