#!/usr/bin/env raku unit sub MAIN ( Int $part where * == 1|2 = 1 #= part to run (1 or 2) ); my @seats = "input".IO.lines.map(*.comb.cache.Array); my Int ($x-max, $y-max) = (@seats[0].end, @seats.end); my Num $visibility = 1e0; my Int $tolerance = 4; # Infinite visibility & increased tolerance for part 2. ($visibility, $tolerance) = (Inf, 5) if $part == 2; my List @directions[8] = ( # $y, $x ( +1, +0 ), # bottom ( +1, +1 ), # bottom-right ( +1, -1 ), # bottom-left ( -1, +0 ), # top ( -1, +1 ), # top-right ( -1, -1 ), # top-left ( +0, +1 ), # right ( +0, -1 ), # left ); my Int $round = 0; loop { $round++; print "Round $round.\r"; my Int ($x, $y) = (-1, 0); my @changed; INNER: loop { if $x == $x-max { $x = 0; # goto next row if not in the last row. last INNER if $y == $y-max; $y += 1; } else { $x += 1; } @changed[$y][$x] = @seats[$y][$x]; given @seats[$y][$x] { when '.' { next INNER; } when 'L' { unless adjacent-occupied(@seats, $x, $y, $visibility, True) { @changed[$y][$x] = '#'; } } when '#' { if adjacent-occupied(@seats, $x, $y, $visibility, False) >= $tolerance { @changed[$y][$x] = 'L'; } } } } # If seats didn't change then exit the loop. last if @seats eqv @changed; for 0 .. @changed.end -> $y { for 0.. @changed[0].end -> $x { @seats[$y][$x] = @changed[$y][$x]; } } } say "Part $part: ", @seats.join.comb('#').elems; # adjacent-occupied returns the number of adjacent cells that have # been occupied by others. # # $visibility should be 1 if only directly adjacent seats are to be # counted. Make it Inf for infinite visibility. It ignores floors # ('.'). # # If $only-bool is set then a Bool will be returned which will # indicate whether any adjacent seat it occupied or not. subset Occupied where Int|Bool; sub adjacent-occupied ( @seats, Int $x, Int $y, Num $visibility, Bool $only-bool = False --> Occupied ) { my Int $occupied = 0; for neighbors(@seats, $x, $y, $visibility).List -> $neighbor { if @seats[$neighbor[0]][$neighbor[1]] eq '#' { return True if $only-bool; $occupied++ ; } } return $occupied; } # neighbors returns the neighbors of given index. It doesn't account # for $visibility when caching the results. So, if $visibility changes # & it has a cached result then the return value might be wrong. So, # you can't solve both part 1 & 2 at once because $visibility changes # between the two. This can be solved easily by just accounting for # $visibility when caching the neighbors. sub neighbors ( @seats, Int $x, Int $y, Num $visibility --> List ) { state Array @neighbors; unless @neighbors[$y][$x] { my Int $pos-x; my Int $pos-y; DIRECTION: for @directions -> $direction { $pos-x = $x; $pos-y = $y; SEAT: for 1 .. $visibility { $pos-y += $direction[0]; $pos-x += $direction[1]; next DIRECTION unless @seats[$pos-y][$pos-x]; given @seats[$pos-y][$pos-x] { # Don't care about floors, no need to check those. when '.' { next SEAT; } when 'L'|'#' { push @neighbors[$y][$x], [$pos-y, $pos-x]; next DIRECTION; } } } } } return @neighbors[$y][$x]; }