PWC126 - Minesweeper Game

TL;DR

On with TASK #2 from The Weekly Challenge #126. Enjoy!

The challenge

You are given a rectangle with points marked with either x or *. Please consider the x as a land mine.

Write a script to print a rectangle with numbers and x as in the Minesweeper game.

A number in a square of the minesweeper game indicates the number of mines within the neighbouring squares (usually 8), also implies that there are no bombs on that square.

Example

Input:
    x * * * x * x x x x
    * * * * * * * * * x
    * * * * x * x * x *
    * * * x x * * * * *
    x * * * x * * * * x

Output:
    x 1 0 1 x 2 x x x x
    1 1 0 2 2 4 3 5 5 x
    0 0 1 3 x 3 x 2 x 2
    1 1 1 x x 4 1 2 2 2
    x 1 1 3 x 2 0 0 1 x

The questions

I think the puzzle is pretty clear, I would probably investigate a bit about the input and output formats and what to do with weird inputs, but nothing really insightful I guess.

The solution

The basic idea of the approach in this solution is that we scan the input field, looking for mines. As soon as we find one, we add 1 unit to all surrounding positions in the output rendering, making sure to preserve previously set mines.

I started with a full solution in Perl also for this challenge, again for technical reasons.

sub reveal_solution ($field) {
   # this will keep the "revealed" field
	my @retval;

   # we need address cells in the @retval grid, so we have to iterate
   # over indices of the input array instead of on the rows directly
	for my $ri (0 .. $field->$#*) {
      my $row = $field->[$ri];

      # same for columns, we need the index and we get it in $ci
      for my $ci (0 .. $row->$#*) {

         # make sure that the element is initialized.
         $retval[$ri][$ci] //= 0;

         # after this, the only cell that is meaningful for us is the mine,
         # as we will "propagate" its effects on the surrounding cells.
         # This is efficient as long as there are *few* mines.
         next if $row->[$ci] ne 'x';

         # if the input field has a mine, the output has one too
         $retval[$ri][$ci] = 'x';

         # now we iterate over the 3x3 grid centered as ($ri, $ci),
         # making sure to ignore the central position (which cannot
         # influence itself) and that we don't go beyond the limits
         # of the input field. $rd is a "delta" for rows.
         for my $rd (-1, 0, 1) {
            # This is a position in the output field that is influenced
            # by the mine we just found. Well, actually it's a row for
            # multiple positions.
            my $Ri = $ri + $rd;
            next if $Ri < 0 || $Ri > $field->$#*;

            # similarly we do for column indexes
            for my $cd (-1, 0, 1) {
               next unless $rd || $cd; # get rid of (0, 0)
               my $Ci = $ci + $cd;
               next if $Ci < 0 || $Ci > $row->$#*;
               $retval[$Ri][$Ci] //= 0; # initialize if necessary
               next if $retval[$Ri][$Ci] eq 'x'; # don't overwrite mines
               $retval[$Ri][$Ci]++; # increment close-by position
            }
         }
      }
	}
   return \@retval;
}

Translating to Raku was… feasible:


sub reveal-solution (@field) {
   # this will keep the "revealed" field
	my @retval;

   # we need address cells in the @retval grid, so we have to iterate
   # over indices of the input array instead of on the rows directly
	for 0 .. @field.end -> $ri {
      my @row := @field[$ri];

      # same for columns, we need the index and we get it in $ci
      for 0 .. @row.end -> $ci {

         # make sure that the element is initialized.
         @retval[$ri][$ci] //= 0;

         # after this, the only cell that is meaningful for us is the mine,
         # as we will "propagate" its effects on the surrounding cells.
         # This is efficient as long as there are *few* mines.
         next if @row[$ci] ne 'x';

         # if the input field has a mine, the output has one too
         @retval[$ri][$ci] = 'x';

         # now we iterate over the 3x3 grid centered as ($ri, $ci),
         # making sure to ignore the central position (which cannot
         # influence itself) and that we don't go beyond the limits
         # of the input field. $rd is a "delta" for rows.
         for -1, 0, 1 -> $rd {
            # This is a position in the output field that is influenced
            # by the mine we just found. Well, actually it's a row for
            # multiple positions.
            my $Ri = $ri + $rd;
            next if $Ri < 0 || $Ri > @field.end;

            # similarly we do for column indexes
            for -1, 0, 1 -> $cd {
               next unless $rd || $cd; # get rid of (0, 0)
               my $Ci = $ci + $cd;
               next if $Ci < 0 || $Ci > @row.end;
               @retval[$Ri][$Ci] //= 0; # initialize if necessary
               next if @retval[$Ri][$Ci] eq 'x'; # don't overwrite mines
               @retval[$Ri][$Ci]++; # increment close-by position
            }
         }
      }
	}
   return @retval;
}

It’s so much a translation that I also kept the comments.

The main differences is that we’re never using references (which don’t exist in Raku) or with their counterparts as scalars holding sequential stuff. What was $field here is @field.

There is an interesting twist in the intermediate variable that goes through the rows. While in Perl we take the reference:

...
my $row = $field->[$ri];
...

here in Raku we can use an array, provided that we bind it to the actual row. In other terms, not this:

my @row = @field[$ri];  # THIS DOES NOT WORK PROPERLY

but this:

my @row := @field[$ri]; # NOTE THE := INSTEAD OF =

I guess that something similar can be done with Perl too… but I’m happy like this!

Time’s up again, so thank you for reading and stay safe, folks!


Comments? Octodon, , GitHub, Reddit, or drop me a line!