Exercism - Flower Field
This post shows you how to get Flower Field exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Classes and Constructors
Classes define blueprints for creating objects. Constructors initialize objects with data.
class FlowerField {
final List<String> garden;
FlowerField(this.garden);
}
void main() {
List<String> board = [
' * * ',
' * ',
];
FlowerField field = FlowerField(board);
print(field.garden); // [' * * ', ' * ']
}
Lists of Strings
Lists of strings can represent a 2D grid where each string is a row. You can access individual characters using string indexing.
void main() {
List<String> garden = [
' * * ',
' * ',
' ',
];
// Access a row
String row = garden[0];
print(row); // ' * * '
// Access a character in a row
char firstChar = garden[0][0];
print(firstChar); // ' '
// Check if character is a flower
bool isFlower = garden[0][1] == '*';
print(isFlower); // true
}
Records (Tuples) for Directions
Records allow you to group multiple values together. They’re perfect for representing direction offsets (row, column).
void main() {
// Direction offsets: (row change, column change)
var north = (-1, 0); // Move up one row
var east = (0, 1); // Move right one column
var south = (1, 0); // Move down one row
var west = (0, -1); // Move left one column
// List of all 8 directions
List<(int, int)> directions = [
(-1, -1), (-1, 0), (-1, 1), // NW, N, NE
(0, -1), (0, 1), // W, E
(1, -1), (1, 0), (1, 1), // SW, S, SE
];
// Use in loop
int row = 2, col = 3;
for (final (dr, dc) in directions) {
int newRow = row + dr;
int newCol = col + dc;
print('($newRow, $newCol)');
}
}
Static Const Lists
Static const lists are class-level constants that can be accessed without creating an instance. They’re perfect for direction definitions.
class FlowerField {
// Static const - shared by all instances
static const _directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1),
];
// Can use in instance methods
void countFlowers() {
for (final (dr, dc) in _directions) {
// Check each direction...
}
}
}
Nested For Loops
Nested for loops allow you to iterate through rows and columns of a 2D structure.
void main() {
List<String> garden = [
' * * ',
' * ',
' ',
];
// Outer loop: iterate through rows
for (var row = 0; row < garden.length; row++) {
// Inner loop: iterate through columns
for (var col = 0; col < garden[row].length; col++) {
print('Row $row, Col $col: ${garden[row][col]}');
}
}
}
String Indexing and Character Access
Strings can be indexed like arrays to access individual characters. You can check if a character matches a specific value.
void main() {
String row = " * * ";
// Access characters by index
print(row[0]); // ' '
print(row[1]); // '*'
print(row[2]); // ' '
// Check if character is a flower
if (row[1] == '*') {
print("Found a flower!");
}
// Get row length
print(row.length); // 5
}
String Concatenation
Strings can be built by concatenating characters or substrings. You can use the += operator to append to a string.
void main() {
String line = '';
// Build string character by character
line += '1';
line += '*';
line += '3';
line += '*';
line += '1';
print(line); // "1*3*1"
// Build conditionally
String result = '';
for (int i = 0; i < 5; i++) {
if (i == 2) {
result += '*';
} else {
result += i.toString();
}
}
print(result); // "01*34"
}
Helper Methods
Helper methods break down complex logic into smaller, reusable functions. They improve code readability and maintainability.
class FlowerField {
// Main method
List<String> get annotated {
// Uses helper methods
for (var row = 0; row < rows; row++) {
for (var col = 0; col < cols; col++) {
int count = _countAdjacentFlowers(row, col);
// ...
}
}
}
// Helper method: count flowers around a position
int _countAdjacentFlowers(int row, int col) {
// Implementation...
}
// Helper method: check if position is valid
bool _isValidPosition(int row, int col) {
// Implementation...
}
}
Conditional Logic
Conditional statements allow you to execute different code based on conditions. This is essential for handling different cell types.
void main() {
char cell = '*';
// Check cell type
if (cell == '*') {
// It's a flower - keep it
print("Flower found");
} else {
// It's empty - count adjacent flowers
int count = countAdjacent();
if (count == 0) {
print("Leave empty");
} else {
print("Replace with count: $count");
}
}
}
Comparison Operators
Comparison operators (==, >=, <) compare values and return boolean results. They’re used for bounds checking and character comparison.
void main() {
int row = 2;
int col = 3;
List<String> garden = [' * * ', ' * '];
// Check if position is valid
bool isValid = row >= 0 &&
row < garden.length &&
col >= 0 &&
col < garden[row].length;
print(isValid); // true
// Check if character is flower
bool isFlower = garden[row][col] == '*';
print(isFlower); // false
}
Bounds Checking
Bounds checking ensures you don’t access invalid array or string indices. It’s critical when checking adjacent cells.
void main() {
List<String> garden = [' * * ', ' * '];
int row = 0;
int col = 0;
// Check if position is within bounds
bool isValid(int r, int c) {
return r >= 0 &&
r < garden.length &&
c >= 0 &&
c < garden[r].length;
}
// Check adjacent positions
if (isValid(row - 1, col)) {
// Safe to access garden[row - 1][col]
print(garden[row - 1][col]);
}
}
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
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.
Example
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·
What is Flower Field?
Flower Field is a compassionate reimagining of Minesweeper, where instead of mines, players search for flowers in a garden. The game uses numeric hints to indicate how many flowers are adjacent to each square, helping players identify all flowers without accidentally stepping on them.
— Game Design
How can we annotate the garden?
To annotate the garden with flower counts:
- Iterate through each cell: For each row and column position
- Check cell type:
- If it’s a flower (’*’), keep it as is
- If it’s empty (’ ’), count adjacent flowers
- Count adjacent flowers: Check all 8 directions (N, NE, E, SE, S, SW, W, NW)
- Update cell:
- If count is 0, leave it empty
- If count > 0, replace with the count as a string
- Build result: Construct new rows with updated cells
The key insight is using direction offsets to check all 8 adjacent positions, with bounds checking to ensure we don’t access invalid indices.
For example, with the input:
·*·*·
··*··
··*··
·····
Processing the first row, first column (·):
- Check all 8 directions: NW(-1,-1), N(-1,0), NE(-1,1), W(0,-1), E(0,1), SW(1,-1), S(1,0), SE(1,1)
- Valid positions: E(0,1) has '', SE(1,1) has ''
- Count: 2 flowers → but wait, let’s verify…
- Actually, checking position (0,0): adjacent flowers at (0,1) and (1,1) → count = 2? No, let me recount…
- Position (0,0): Check (0,1) = '', (1,1) = '', (1,0) = ’ ’ → Actually, only (0,1) is adjacent? No, (1,1) is diagonal.
- All 8 directions from (0,0): (-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)
- Valid: (0,1)='', (1,1)='' → count = 2? But the example shows 1…
Wait, let me re-read the example output:
1*3*1
13*31
·2*2·
·111·
Position (0,0) shows ‘1’, so there’s 1 adjacent flower. Let me check: (0,1)=’*’ is adjacent, so count=1. That matches!
Solution
class FlowerField {
final List<String> garden;
FlowerField(this.garden);
// All 8 directions: N, NE, E, SE, S, SW, W, NW
static const _directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1),
];
List<String> get annotated {
if (garden.isEmpty) return [];
final rows = garden.length;
final result = <String>[];
for (var row = 0; row < rows; row++) {
final cols = garden[row].length;
var line = '';
for (var col = 0; col < cols; col++) {
if (garden[row][col] == '*') {
line += '*';
} else {
final count = _countAdjacentFlowers(row, col);
line += count == 0 ? ' ' : count.toString();
}
}
result.add(line);
}
return result;
}
int _countAdjacentFlowers(int row, int col) {
var count = 0;
for (final (dr, dc) in _directions) {
final newRow = row + dr;
final newCol = col + dc;
if (_isValidPosition(newRow, newCol) &&
garden[newRow][newCol] == '*') {
count++;
}
}
return count;
}
bool _isValidPosition(int row, int col) {
return row >= 0 &&
row < garden.length &&
col >= 0 &&
col < garden[row].length;
}
}
Let’s break down the solution:
-
class FlowerField- Main class representing the garden:final List<String> garden: The input garden as a list of strings (each string is a row)FlowerField(this.garden): Constructor that initializes the garden
-
static const _directions- All 8 adjacent directions:- Defines direction offsets as records: (row change, column change)
(-1, -1): Northwest,(-1, 0): North,(-1, 1): Northeast(0, -1): West,(0, 1): East(1, -1): Southwest,(1, 0): South,(1, 1): Southeast- Static const means it’s shared by all instances
-
List<String> get annotated- Main method that annotates the garden:- Returns a new list of strings with flower counts added
if (garden.isEmpty) return []: Handle empty garden edge casefinal rows = garden.length: Get number of rowsfinal result = <String>[]: Initialize result list
-
Outer loop:
for (var row = 0; row < rows; row++)- Iterate through rows:final cols = garden[row].length: Get number of columns in this rowvar line = '': Initialize empty string for building the new row
-
Inner loop:
for (var col = 0; col < cols; col++)- Iterate through columns:if (garden[row][col] == '*'): Check if current cell is a flowerline += '*': If flower, keep it as iselse: If empty, count adjacent flowersfinal count = _countAdjacentFlowers(row, col): Get count of adjacent flowersline += count == 0 ? ' ' : count.toString(): Add space if 0, or count as string
-
result.add(line)- Add completed row to result:- After processing all columns in a row, add the built line to result
-
int _countAdjacentFlowers(int row, int col)- Helper method to count adjacent flowers:var count = 0: Initialize counterfor (final (dr, dc) in _directions): Iterate through all 8 directionsfinal newRow = row + dr: Calculate new row positionfinal newCol = col + dc: Calculate new column positionif (_isValidPosition(newRow, newCol) && garden[newRow][newCol] == '*'): Check if position is valid and contains a flowercount++: Increment counter if flower found- Returns total count of adjacent flowers
-
bool _isValidPosition(int row, int col)- Helper method for bounds checking:row >= 0: Row must be non-negativerow < garden.length: Row must be within garden heightcol >= 0: Column must be non-negativecol < garden[row].length: Column must be within row width- Returns true if position is valid, false otherwise
The solution efficiently annotates the garden by checking all 8 adjacent positions for each empty cell, using helper methods to keep the code clean and maintainable.
A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉
Visit My YouTube Channel