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
require "set"
require "strscan"

def parse(line)
  deltas = []

  ss = StringScanner.new(line)
  until ss.eos?
    deltas << case
              when ss.scan(/e/)  then [ 1,  0]
              when ss.scan(/se/) then [ 0,  1]
              when ss.scan(/sw/) then [-1,  1]
              when ss.scan(/w/)  then [-1,  0]
              when ss.scan(/nw/) then [ 0, -1]
              when ss.scan(/ne/) then [ 1, -1]
              else fail
              end
  end

  deltas.inject {|c,d| c.zip(d).map {|c,d| c+d }}
end

NEIGHBORS = %w[ e se sw w nw ne ].map {|dir| parse(dir) }
def tick(tiles)
  coords = Set.new(tiles.flat_map {|coord|
    [
      coord,
      *NEIGHBORS.map {|n| coord.zip(n).map {|c,d| c+d }},
    ]
  })
  Set.new(coords.select {|coord|
    live_neighbors = NEIGHBORS.map {|n| coord.zip(n).map {|c,d| c+d }}.count {|c| tiles.include?(c) }
    if tiles.include?(coord)
      (1..2).cover?(live_neighbors)
    else
      live_neighbors == 2
    end
  })
end

tiles = Set.new
ARGF.read.split("\n").each do |line|
  tile = parse(line)
  if tiles.include?(tile)
    tiles.delete(tile)
  else
    tiles << tile
  end
end

100.times do |i|
  tiles = tick(tiles)
end

p tiles.size