1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
require 'minitest/autorun'

module PuzzleHunt
  class WordSearch
    # Breaks a block of text into a matrix of single characters.
    def self.from_chars(input)
      data = input.chomp.split("\n").map {|row| row.split(//) }
      self.new(data)
    end

    include Enumerable

    DIRS = [-1, 0, 1].flat_map {|x| [-1, 0, 1].map {|y| [x,y] }} - [[0,0]]

    attr_reader :data

    def initialize(data)
      @data = data
    end

    def [](x, y)
      data[x][y]
    end

    def each_word
      return enum_for(__method__) unless block_given?

      data.each.with_index do |row, i|
        row.each.with_index do |elem, j|
          yield elem

          DIRS.map do |dx, dy|
            x,y = i,j
            word = [ elem ]

            loop do
              x += dx
              y += dy
              break unless (0...data.size).cover?(x) && (0...row.size).cover?(y)
              word << self[x,y]
              yield word.join
            end
          end
        end
      end
    end
  end

  class TestWordSearch < Minitest::Test
    def test_from_chars
      ws = WordSearch.from_chars("ab\ncd")
      assert_equal 'a', ws[0,0]
      assert_equal 'b', ws[0,1]
      assert_equal 'c', ws[1,0]
      assert_equal 'd', ws[1,1]
    end

    def test_each
      ws = WordSearch.from_chars("abc\ndef\nghi")
      words = ws.each_word.map.to_a
      %w[ a b c d e f g h i
          ab ad ae
          ea eb ec ed ef eg eh ei
          abc def ghi aei adg cfi ].each do |word|
        assert_includes words, word
      end
    end
  end
end