Вот как я бы подошел к проблеме:
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
В методе initialize мы создаем массив 3 * 3, который представляет все позиции на доске. Это делает его намного проще, поскольку он уже сгруппирован по строкам. Вместо пустой строки используйте nil для представления пустого квадрата, так как nil - ложь.
Когда мы хотим проверить победителя, мы собираем строки, столбцы и две диагонали в массив массивов:
[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]]
Оттуда мы просто должны проверить, все ли из них :x
или :o
. На самом деле нам не нужно перечислять выигрышные комбинации. В этом случае game.check_for_winner
вернет :o
.