ETOOBUSY 🚀 minimal blogging for the impatient
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 thex
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!