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.
- your
@board
is an array of 3 rows, but your possible wins is just an array of points.flatten
ing your @board means you can use the index as is. - 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.