0

I have the following grids (connect four)

grid1 = [
  [nil, nil, nil],
  [1, nil, nil],
  [1, nil, nil],
  [1, nil, nil]
]
grid2 = [
  [nil, nil, nil],
  [nil, nil, 1],
  [nil, nil, 1],
  [nil, nil, 1]
]

grid3 = [
  [nil, nil, nil],
  [nil, nil, nil],
  [nil, nil, nil],
  [1, 1, 1]
]

and this is the method I created to find three 1's in a vertical row and return the next available slot above

def searchArray(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      if array[yi][xi] != nil && array[yi][xi] == array[yi+1][xi] && array[yi][xi] == array[yi+2][xi]
        return v = [yi-1, xi]
      end
    end
  end
end


searchArray(grid2)

When I call the method on grid1, and grid 2 it works great but when I call it on Grid 3 the grid where the 1's are placed on the bottom row I get this error

undefined method `[]' for nil:NilClass
(repl):28:in `block (2 levels) in searchArray'
(repl):27:in `each'
(repl):27:in `each_with_index'
(repl):27:in `block in searchArray'
(repl):26:in `each'
(repl):26:in `each_with_index'
(repl):26:in `searchArray'
(repl):36:in `<main>'

Not sure what's going on Thanks

4
  • Indentation, please. That code is slammed left and it's barely readable as-is. Commented May 5, 2019 at 23:09
  • I think you're walking off the end of the array here. You're testing array[yi][xi] and then go ahead and presume array[yi+1] exists, which it may not. Commented May 5, 2019 at 23:11
  • Note: Ruby is a case-sensitive language and capital letters have specific meaning in terms of syntax. Variables and method names should be lower-case letters. Capitals indicate constants of the form ClassName or CONSTANT_NAME. Commented May 5, 2019 at 23:14
  • To format code within a paragraph surround it with backticks (`). To format a block of code, indent 4 spaces or select all the code and click on the icon {}. Commented May 6, 2019 at 0:38

3 Answers 3

1

You can solve a lot of problems here by simplifying this code using dig:

def search_array(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      stack = (0..2).map { |o| array.dig(yi + o, xi) }

      if (stack == [ 1, 1, 1 ])
        return [ yi - 1, xi ]
      end
    end
  end
end

Where dig can poke around and not cause exceptions if it misses the end of the array. Here map is used to quickly pull out an N high stack. You can do 1..2 or 0..4 or whatever is necessary.

Sign up to request clarification or add additional context in comments.

Comments

0

Let's take a look at your code, simplified slightly1:

def search_array(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      return [yi-1, xi] if x != nil && x == array[yi+1][xi] && x == array[yi+2][xi]
    end
  end
end

You go one row at a time, then for each element in that row, check if that element is not nil and if so, determine whether the two elements below it have the same non-nil value. If you reach the penultimate (next-to-last) row, yi = array.size - 2, you will compare x with array[yi+2][xi], which equals array[array.size][xi], which in turn equals nil[xi]. However, nil has no method [] so an undefined method exception is raised. Pay close attention to those error messages; often, as here, they guide you to the error.

Another problem is that if you found 1's in the first three rows of a column j you would return the index [-1, j], -1 being 0-1. You don't want that either.

I understand that you also wish to determine if dropping a coin in a column results in four-in-a-row horizontally. You could check both vertically and horizontally as follows.

def search_array(arr)
  arr.first.each_index do |j|
    r = arr.each_index.find { |i| arr[i][j] == 1 }
    next if r == 0
    r = r.nil? ? arr.size-1 : r-1
    return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)
  end
  nil
end

def below?(arr,r,j)
  r < arr.size-3 && (1..3).all? { |i| arr[r+i][j] == 1 }
end

def right?(arr,r,j)
  j < arr.first.size-3 && (1..3).all? { |i| arr[r][j+i] == 1 }
end

def left?(arr,r,j)
  j >= 3 && (1..3).all? { |i| arr[r][j-i] == 1 }
end

grid4 = [
  [nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil],
  [nil, nil,   1, nil, nil],
  [nil, nil,   1,   1,   1],
  [  1,   1,   1, nil,   1]
]

grid5 = [
  [nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil],
  [nil, nil,   1, nil, nil],
  [nil,   1,   1, nil, nil],
  [nil,   1,   1, nil,   1]
]

search_array grid1 #=> [0, 0] (vertical)
search_array grid2 #=> [0, 2] (vertical)
search_array grid3 #=> nil
search_array grid4 #=> [3, 1] (horizontal)
search_array grid5 #=> [1, 2] (vertical)

Note that if you wish to also check for four-in-a-row diagonnal you could change:

return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)

to

return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j) ||
  top_left_to_bottom_right?(arr,r,j) || bottom_left_to_top_right?(arr,r,j)

and add the additional methods top_left_to_bottom_right? and bottom_left_to_top_right?.

1. I changed the name of your method to search_array because Ruby has a convention to use snake case for the naming of variables and methods. You don't have to adopt that convention but 99%+ of Rubiests do.

10 Comments

Hi, great explanation. Really liking the first explanation. How would you make this work horizontally?(to get the next column that needs to be placed horizontally) I'm a beginner and this is helping me alot! thank you
return [i,j] if array[i][j].nil? && [1,2,3].all? { |k| array[i][j-k] == 1 } this is working for me
my problem is when using a bigger grid and checking 3 in a row horizontal to the right it works but when I wanna check them to the left its not working like so
grid4 = [ [nil,nil,nil,nil,1,1,1], [nil,nil,nil,nil,nil,nil,nil], [nil,nil,nil,nil,nil,nil,nil], [nil,nil,nil,nil,nil,nil,nil], [nil,nil,nil,nil,nil,nil,nil], [nil,nil,nil,nil,nil,nil,nil] ]
in this case I would need to use return [i,j] if array[i][j].nil? && [1,2,3].all? { |k| array[i][j+k] == 1 } on the last line to catch this way how can i combine this with this return [i,j] if array[i][j].nil? && [1,2,3].all? { |k| array[i][j-k] == 1 } to check for both ways?
|
0

I could suggest a slight different approach, this is not a complete solution, just a start. It should also help to catch the four.

First map the not nil indexes of the grid, let's consider grid3:

mapping = grid3.flat_map.with_index{ |y, yi| y.map.with_index { |x, xi| [xi, yi] if x }.compact }
#=> [[0, 3], [1, 3], [2, 3]]

Then group by first and second element to get the columns and rows:

cols = mapping.group_by(&:first) #=> {0=>[[0, 3]], 1=>[[1, 3]], 2=>[[2, 3]]}
rows = mapping.group_by(&:last) #=> {3=>[[0, 3], [1, 3], [2, 3]]}

Now, if you want to look for three elements in a row or in a column:

cols.keep_if { |_,v| v.size == 3 } #=> {}
rows.keep_if { |_,v| v.size == 3 } #=> {3=>[[0, 3], [1, 3], [2, 3]]}

The first line says there are no columns with three element aligned. The second line says that row with index 3 has three elements aligned and indexes are [[0, 3], [1, 3], [2, 3]].


Next step it to check that there are no gaps amongst elements. For example in a 4x4 grid you could get also [[0, 3], [1, 3], [3, 3]] which are three elements, but there is a gap in [2, 3],

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.