Search code examples
rubymatrixgomoku

Ruby count duplicates in diagonal rows of matrix


I'm implementing gomoku game in Ruby, this is a variation of tic-tac-toe played on 15x15 board, and the first player who places 5 O's or X's in horizontal, vertical or diagonal row wins.

First, I assigning Matrix to a variable and fill it with numbers from 0 to 224, so there are no repetitions and I could count them later

gomoku = Matrix.zero(15)
num = 0
15.times do |i|
  15.times do |j|
    gomoku[i, j] = num
    num += 1
  end
end

then players take turns, and after every turn I check a win with the method win?

def win? matrix
  15.times do |i|
    return true if matrix.row_vectors[i].chunk{|e| e}.map{|_, v| v.length}.max > 4 # thanks to sawa for this way of counting adjacent duplicates
    return true if matrix.column_vectors[i].chunk{|e| e}.map{|_, v| v.length}.max > 4
  end
  return false
end

I know, that I'm probably doing it wrong, but my problem isn't that, though suggestions are welcome. The problem is with diagonal rows. I don't know how to count duplicates in diagonal rows


Solution

  • diagonal_vectors = (-10 .. 10).flat_map do |x|
      i = x < 0 ? 0 : x
      j = x < 0 ? -x : 0
      d = 15 - x.abs
      [
        d.times.map { |k|
          gomoku[i + k, j + k]
        },
        d.times.map { |k|
          gomoku[i + k, 14 - j - k]
        }
      ]
    end
    

    With this, you can apply the same test sawa gave you.

    EDIT: What this does

    When looking at diagonals, there's two kinds: going down-left, and going down-right. Let's focus on down-right ones for now. In a 15x15 matrix, there are 29 down-right diagonals: one starting at each element of the first row, one starting at each element of the first column, but taking care not to count the one starting at [0, 0] twice. But some diagonals are too short, so we want to only take those that start on the first eleven rows and columns (because others will be shorter than 5 elements). This is what the first three lines do: [i, j] will be [10, 0], [9, 0] ... [0, 0], [0, 1], ... [0, 10]. d is the length of a diagonal starting at that position. Then, d.times.map { |k| gomoku[i + k, j + k] } collects all the elements in that diagonal. Say we're working on [10, 0]: d is 5, so we have [10, 0], [11, 1], [12, 2], [13, 3], [14, 4]; and we collect values at those coordinates in a list. Simultaneously, we'll also work on a down-left diagonal; that's the other map's job, which flips one coordinate. Thus, the inner block will return a two-element array, which is two diagonals, one down-left, one down-right. flat_map will take care of iterating while squishing the two-element arrays so that we get one big array of diagonals, not array of two-element arrays of diagonals.