Introduction
Flower Field is a compassionate reimagining of the popular game Minesweeper.
The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square.
“Flower Field” shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan.
Instructions
Instructions
Your task is to add flower counts to empty squares in a completed Flower Field garden.
The garden itself is a rectangle board composed of squares that are either empty (' ') or a flower ('*').
For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally).
If the empty square has no adjacent flowers, leave it empty.
Otherwise replace it with the count of adjacent flowers.
For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the ’·’ character for display on screen):
·*·*·
··*··
··*··
·····
Which your code should transform into this:
1*3*1
13*31
·2*2·
·111·
Dig Deeper
Data Driven
Data Driven
class FlowerField
OFFSETS = [
{-1, -1}, {0, -1}, {1, -1},
{-1, 0}, {1, 0},
{-1, 1}, {0, 1}, {1, 1},
]
def initialize(@board : Array(String) )
end
def annotate()
nrows = @board.size()
@board.map_with_index do |row, y|
ncols = row.size()
row.chars.map_with_index do |cell, x|
if cell == '*'
next '*'
end
sum = OFFSETS.count do |(dx, dy)|
new_x = x + dx
new_y = y + dy
new_x >= 0 && new_x < ncols && new_y >= 0 && new_y < nrows && @board[new_y][new_x] == '*'
end
sum == 0 ? ' ' : sum.to_s
end.join
end
end
end
Using a data-driven approach to create logic to solve Flower Field makes the solution concise and flexible.
If the rules change, it is as easy as to change them.
The solution starts by creating an Array with the rules which are tuples with the x and y offset.
Defining an initialize method takes an Array(String) as an argument and assigns it to an instance variable @board.
The annotate method starts by defining the number of rows on the board.
Then it maps over the @board array with the index y for each row.
Then, it converts each row into an array of Char and maps over it with the index x for each cell.
If a cell is a flower, it will be skipped and the flower will be returned.
Otherwise, it will count the number of flowers around the cell.
It will do this by having the rules defined beforehand and then calculating the new x and y positions for each rule.
It will then check if the new x and y positions are within the board’s bounds and if the cell is a flower.
If it is, the count will be increased.
Finally, it will check if the sum is 0.
If it is, it will return a space; otherwise, it will return the sum as a string.
Then, it will join the row and return to the board.
Translate
Iteration and tr method
class FlowerField
def initialize(@board : Array(String))
end
def annotate
@board.map_with_index do |row, y|
row.chars.map_with_index do |cell, x|
cell.to_s.tr(" ", annotated_space(x, y))
end.join
end
end
private def annotated_space(x, y)
([y - 1, 0].max..[y + 1, @board.size - 1].min).sum do |y|
([x - 1, 0].max..[x + 1, @board[0].size - 1].min).count do |x|
@board[y][x] == '*'
end
end.to_s.tr("0", " ")
end
end
What makes this approach interesting is the use of the tr method, which allows us to have a concise solution that doesn’t use conditionals.
This approach starts with defining a initialize method that takes an Array(String) as an argument and assigns it to an instance variable @board.
The annotate method starts with mapping over the @board array with the index y for each row.
Then, it converts each row into an array of characters and maps over it with the index x for each cell.
It converts each cell from a Char to a String and then uses the method tr.
The tr method allows you to replace a set of characters with another set of characters in a string.
This is useful since it allows us to replace only the space character with the result of the annotated_space method.
The annotated_space method takes the x and y coordinates of the cell and calculates the number of flowers around it.
It then creates a range representing the area’s height to check for flowers.
Since the index is 0, taking minus 1 would make the value -1, which would be valid in Crystal but would represent the last element.
We don’t want this behavior, so we use the max method to ensure that the value is at least 0.
The same is done for the min value, but here we want to ensure that the value is, at most, the last index.
Then, we use the sum method to sum the total number of flowers returned by the inner loop.
The inner loop creates a range representing the area’s width to check for flowers.
As before, we use the max and min methods to ensure that the values are within the board’s bounds.
Then, we use the count method to count how many times the inner condition is true, which indicates whether the cell is a flower or not.
Finally, we convert the sum to a string and use the tr method to replace all the zeros with spaces.
Source: Exercism crystal/flower-field