Advertisement
musifter

AoC day 20 (pt2), Perl

Dec 22nd, 2020 (edited)
1,690
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 9.87 KB | None | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. use List::AllUtils  qw(none indexes);
  7.  
  8. $| = 1;
  9. $/ = '';
  10.  
  11. # Map for reversing bits of an edge
  12. my @Rev_Map = map { oct("0b" . reverse(sprintf( "%010b", $_ ))) } (0 .. 1023);
  13.  
  14. # Read in tiles, calculate edge array (D4 group) for manipulating
  15. my %Tiles;
  16. my $count = 0;
  17. while (<>) {
  18.     my @lines = split( "\n" );
  19.     chomp @lines;
  20.  
  21.     my ($num) = ((shift @lines) =~ m#Tile (\d+):#);
  22.  
  23.     my @tile = map { [ split( // ) ] } @lines;
  24.  
  25.     my ($ident, $rot1, $rot2, $rot3)   = (0,0,0,0);  # top, left, bottom, right
  26.     foreach my $i (0 .. 9) {
  27.         $ident  = ($ident << 1) | ($tile[0][$i] eq '#');
  28.         $rot1   = ($rot1 << 1)  | ($tile[9-$i][0] eq '#');
  29.         $rot2   = ($rot2 << 1)  | ($tile[9][9-$i] eq '#');
  30.         $rot3   = ($rot3 << 1)  | ($tile[$i][9] eq '#');
  31.     }
  32.  
  33.     my $flip  = $Rev_Map[ $rot1  ];
  34.     my $flip1 = $Rev_Map[ $ident ];
  35.     my $flip2 = $Rev_Map[ $rot3  ];
  36.     my $flip3 = $Rev_Map[ $rot2  ];
  37.  
  38.     foreach my $y (1 .. 8) {
  39.         foreach my $x (1 .. 8) {
  40.             $count++ if ($tile[$y][$x] eq '#');
  41.         }
  42.     }
  43.  
  44.     $Tiles{$num}{edge} = [ $ident, $rot1, $rot2, $rot3, $flip, $flip1, $flip2, $flip3 ];
  45.     $Tiles{$num}{tile} = \@tile;
  46. }
  47.  
  48. print "Hashes in image: $count\n";
  49.  
  50. # Match up tiles to sort out corners from rest
  51. my @Side_Map = ( 0, 1, 2, 3, 1, 0, 3, 2 );
  52. my @tlist = keys %Tiles;
  53. my $Dim = sqrt( scalar @tlist );
  54.  
  55. my %Match;
  56. foreach my $t1 (0 .. $#tlist) {
  57.     foreach my $t2 ($t1 + 1 .. $#tlist) {
  58.         foreach my $i (0 .. 7) {
  59.             my $s1 = $Tiles{$tlist[$t1]}{edge}[$i];
  60.  
  61.             foreach my $j (0 .. 7) {
  62.                 my $s2 = $Tiles{$tlist[$t2]}{edge}[$j];
  63.  
  64.                 if ($s1 == $s2) {
  65.                     $Match{$tlist[$t1]}{$tlist[$t2]} = $i;
  66.                     $Match{$tlist[$t2]}{$tlist[$t1]} = $j;
  67.                 }
  68.             }
  69.         }
  70.     }
  71. }
  72.  
  73.  
  74. # Put together unoriented tiles in Image
  75. my @Image;
  76. my %Info;
  77.  
  78. sub set_cell {
  79.     my ($y, $x, $tile) = @_;
  80.  
  81.     warn "Tile already used ($tile)\n"  if ($Info{$tile}{used});
  82.     $Image[$y][$x] = $tile;
  83.     $Info{$tile}{used} = 1;
  84. }
  85.  
  86. # Find a corner to start at while recording neighbours into info hash
  87. my $corner_start;
  88. foreach my $k (keys %Match) {
  89.     my $neigh = keys %{$Match{$k}};
  90.     $Info{$k}{neigh} = $neigh;
  91.     $corner_start = $k  if ($neigh == 2);
  92. }
  93.  
  94. &set_cell( 0, 0, $corner_start );
  95.  
  96. # Init the one on the next row so it is marked used
  97. my $cell = (keys %{$Match{$corner_start}})[0];
  98. &set_cell( 1, 0, $cell );
  99.  
  100. # Start by building upper edge
  101. foreach my $x (1 .. $Dim - 2) {
  102.     my $prev = $Image[0][$x - 1];
  103.     my @neigh = keys %{$Match{$prev}};
  104.  
  105.     foreach my $i (@neigh) {
  106.         next if ($Info{$i}{used});
  107.  
  108.         if ($Info{$i}{neigh} == 3) {
  109.             &set_cell( 0, $x, $i );
  110.         } elsif ($Info{$i}{neigh} == 4) {
  111.             &set_cell( 1, $x - 1, $i );
  112.         }
  113.     }
  114. }my $up_right;
  115. foreach my $i (keys %{$Match{$Image[0][$Dim - 2]}}) {
  116.     next if ($Info{$i}{used});
  117.     $up_right = $i  if ($Info{$i}{neigh} == 2);
  118. }
  119.  
  120. &set_cell( 0, $Dim - 1, $up_right );
  121.  
  122.  
  123. # Build right edge
  124. foreach my $y (1 .. $Dim - 2) {
  125.     my $prev = $Image[$y - 1][0];
  126.     my @neigh = keys %{$Match{$prev}};
  127.  
  128.     foreach my $i (@neigh) {
  129.         next if ($Info{$i}{used});
  130.  
  131.         if ($Info{$i}{neigh} == 3) {
  132.             &set_cell( $y, 0, $i );
  133.         } elsif ($Info{$i}{neigh} == 4) {
  134.             &set_cell( $y - 1, 1, $i );
  135.         }
  136.     }
  137. }
  138.  
  139. # Build the rest
  140. foreach my $y (1 .. $Dim - 1) {
  141.     foreach my $x (0 .. $Dim - 1) {
  142.         next if (defined $Image[$y][$x]);
  143.  
  144.         my $north = $Image[$y - 1][$x];
  145.  
  146.         my @unmatched = grep { !$Info{$_}{used} } keys %{$Match{$north}};
  147.         if (scalar @unmatched != 1) {
  148.             if (@unmatched) {
  149.                 warn "Warning: Unmatched $north: ", join( ', ', @unmatched ), "\n";
  150.             } else {
  151.                 warn "Warning: Unmatched $north: empty!\n";
  152.             }
  153.         } else {
  154.             &set_cell( $y, $x, $unmatched[0] );
  155.         }
  156.     }
  157. }
  158.  
  159. print "\nImage:\n";
  160. foreach my $y (0 .. $Dim - 1) {
  161.     foreach my $x (0 .. $Dim - 1) {
  162.         if (!defined $Image[$y][$x]) {
  163.             print "xxxx ";
  164.         } else {
  165.             print "$Image[$y][$x] ";
  166.         }
  167.     }
  168.     print "\n";
  169. }
  170.  
  171. #
  172. #  Helper functions for manipulating tiles:
  173. #
  174. sub print_tile {
  175.     my $cell = shift;
  176.  
  177.     my @tile = @{$cell->{tile}};
  178.     my @edge = @{$cell->{edge}};
  179.  
  180.     foreach my $y (0 .. 9) {
  181.         foreach my $x (0 .. 9) {
  182.             print $tile[$y][$x];
  183.         }
  184.         print "\n";
  185.     }
  186.  
  187.     print "Edges: [", join( ',', @edge ), "]\n";
  188.     foreach my $i (0 .. 3) {
  189.         printf( "$i [%3d]: %010b    %d [%3d]: %010b\n",
  190.                 $edge[$i], $edge[$i], $i+4, $edge[$i+4], $edge[$i+4] );
  191.     }
  192. }
  193.  
  194.  
  195. sub flip_tile {
  196.     my $cell = shift;
  197.  
  198.     my @tile = @{$cell->{tile}};
  199.     my @edge = @{$cell->{edge}};
  200.  
  201.     my @flip;
  202.     foreach my $y (0 .. 9) {
  203.         foreach my $x (0 .. 9) {
  204.             $flip[$x][$y] = $tile[$y][$x];
  205.         }
  206.     }
  207.  
  208.     my @e_flip;
  209.     foreach my $i (0 .. 7) {
  210.         $e_flip[$i] = $edge[($i + 4) % 8];
  211.     }
  212.  
  213.     return ('edge' => \@e_flip, 'tile' => \@flip);
  214. }
  215.  
  216. sub rotate_tile {
  217.     my $cell = shift;
  218.     my $num  = shift;
  219.  
  220.     return (%$cell)  if ($num == 0);
  221.  
  222.     my @tile = @{$cell->{tile}};
  223.     my @edge = @{$cell->{edge}};
  224.  
  225.     my @rot;
  226.     foreach my $y (0 .. 9) {
  227.         foreach my $x (0 .. 9) {
  228.             $rot[$y][$x] = $tile[9-$x][$y];
  229.         }
  230.     }
  231.  
  232.     my @e_rot;
  233.     foreach my $i (0 .. 3) {
  234.         $e_rot[$i] = $edge[($i + 1) % 4];
  235.         $e_rot[$i + 4] = $edge[($i - 1) % 4 + 4];
  236.     }
  237.  
  238.     $num--;
  239.     &rotate_tile( {'edge' => \@e_rot, 'tile' => \@rot}, $num );
  240. }
  241.  
  242. # detect which sides of first corner tile don't connect:
  243. my %corn_sides = ( 0 => 1, 1 => 1, 2 => 1, 3 => 1 );
  244. foreach my $i (keys %{$Match{$corner_start}}) {
  245.     delete $corn_sides{ $Side_Map[$Match{$corner_start}{$i}] };
  246. }
  247.  
  248. my @missing = sort { $a <=> $b } keys %corn_sides;
  249. if (scalar( keys %corn_sides ) != 2) {
  250.     die "Corner does not have two missing sides!\n";
  251. }
  252.  
  253. my $rots_needed = $missing[0];
  254. if ($rots_needed == 0 && $missing[1] == 3) {
  255.     $rots_needed = 3;
  256. }
  257.  
  258. my %rot_corner = &rotate_tile( $Tiles{$Image[0][0]}, $rots_needed );
  259.  
  260. # Rotating the blanks to the outside might not be enough,
  261. # we might need to flip it over too.
  262. if (none { $_ == $rot_corner{edge}[3] } @{$Tiles{$Image[0][1]}{edge}}) {
  263.     %rot_corner = &flip_tile( \%rot_corner );
  264. }
  265.  
  266. #
  267. # Putting together correctly rotated tiles:
  268. #
  269. my @Map;
  270. $Map[0][0] = \%rot_corner;
  271.  
  272. # build top edge by orienting to connect to left
  273. foreach my $i (1 .. $Dim - 1) {
  274.     my $need_left = $Rev_Map[ $Map[0][$i - 1]{edge}[3] ];
  275.  
  276.     my %next_tile = %{$Tiles{$Image[0][$i]}};
  277.     my ($targ_pos) = indexes { $_ == $need_left } @{$next_tile{edge}};
  278.     if ($targ_pos >= 4) {
  279.         %next_tile = &flip_tile( \%next_tile );
  280.         $targ_pos -= 4;
  281.     }
  282.  
  283.     %next_tile = &rotate_tile( \%next_tile, ($targ_pos + 3) % 4 );
  284.     $Map[0][$i] = \%next_tile;
  285. }
  286.  
  287. # build rest by orienting to connect to top
  288. foreach my $y (1 .. $Dim - 1) {
  289.     foreach my $x (0 .. $Dim - 1) {
  290.         my $need_top = $Rev_Map[ $Map[$y - 1][$x]{edge}[2] ];
  291.  
  292.         my %next_tile = %{$Tiles{$Image[$y][$x]}};
  293.  
  294.         my ($targ_pos) = indexes { $_ == $need_top } @{$next_tile{edge}};
  295.         if ($targ_pos >= 4) {
  296.             %next_tile = &flip_tile( \%next_tile );
  297.             $targ_pos -= 4;
  298.         }
  299.  
  300.         %next_tile = &rotate_tile( \%next_tile, $targ_pos );
  301.         $Map[$y][$x] = \%next_tile;
  302.     }
  303. }
  304.  
  305. #
  306. # Stitch together image as arrays of strings in four orientations:
  307. #   - as made + its reverse, by columns + its reverse
  308. #
  309. my @Stitch;
  310. foreach my $y (0 .. $Dim - 1) {
  311.     foreach my $ty (1 .. 8) {
  312.         my $line = $y * 8 + ($ty - 1);
  313.         $Stitch[0][$line] = '';
  314.         $Stitch[1][$line] = '';
  315.         foreach my $x (0 .. $Dim - 1) {
  316.             foreach my $tx (1 .. 8) {
  317.                 $Stitch[0][$line] .= $Map[$y][$x]{tile}[$ty][$tx];
  318.                 $Stitch[1][$line] .= $Map[$x][$y]{tile}[$tx][$ty];
  319.             }
  320.         }
  321.     }
  322. }
  323.  
  324. foreach my $m (0 .. 1) {
  325.     foreach my $line (@{$Stitch[$m]}) {
  326.         my $rev = reverse( $line );
  327.         push( @{$Stitch[$m + 2]}, $rev );
  328.     }
  329. }
  330.  
  331. # Using the middle line of a monster to get best map.
  332. # Assuming that the best match of these is correct.
  333. # For my input, the others have 0, 0, and 2 hits.
  334. my $best;
  335. my @hits;
  336. foreach my $i (0 .. 3) {
  337.     my @matches = indexes { m/#....##....##....###/ } @{$Stitch[$i]};
  338.     if (@matches > @hits) {
  339.         $best = $i;
  340.         @hits = @matches;
  341.     }
  342. }
  343.  
  344. print "\nBest orientation: $best\n";
  345. print "Lines with hits: ", join( ',', @hits ), "\n\n";
  346. foreach my $line (@{$Stitch[$best]}) {
  347.     print "$line\n";
  348. }
  349.  
  350. # Two remaining orientations to detect: up-side-up or up-side-down monsters
  351. my $up_side   = 0;
  352. my $down_side = 0;
  353.  
  354. foreach my $i (@hits) {
  355.     next if ($i == 0 || $i == scalar($Stitch[$best]) - 1);
  356.  
  357.     my $line = $Stitch[$best][$i];
  358.     while ($line =~ m/#....##....##....###/g) {
  359.         my $x_end = pos( $line );
  360.  
  361.         if (substr( $Stitch[$best][$i-1], $x_end - 2, 1 ) eq '#') {
  362.             my $lower = substr( $Stitch[$best][$i+1], $x_end - 19, 16 );
  363.             if ($lower =~ m/^#..#..#..#..#..#/) {
  364.                 $up_side++;
  365.             }
  366.         }
  367.  
  368.         if (substr( $Stitch[$best][$i+1], $x_end - 2, 1 ) eq '#') {
  369.             my $upper = substr( $Stitch[$best][$i-1], $x_end - 19, 16 );
  370.             if ($upper =~ m/^#..#..#..#..#..#/) {
  371.                 $down_side++;
  372.             }
  373.         }
  374.     }
  375. }
  376.  
  377. print "\nSeamonsters found Up-side-up: $up_side\n";
  378. print "Seamonsters fOund Up-side-dOwn: $down_side\n";
  379.  
  380. # Assuming no overlapping seamonsters
  381. print "Part 2: ", $count - 15 * ($up_side || $down_side), "\n";
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement