Skip to content

Commit 3be57f4

Browse files
authored
Merge pull request #268 from ikostan/exercism-sync/82db5a915fb57615
Exercism sync/82db5a915fb57615
2 parents 6c58206 + 8d1ea19 commit 3be57f4

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""
2+
Flower Field is a compassionate reimagining of the popular game Minesweeper.
3+
4+
This module provides helpers to validate and annotate a rectangular garden
5+
representation, where each row is a string comprised of spaces and ``*``
6+
characters. A ``*`` denotes a flower; a space denotes an empty square.
7+
8+
The goal is to compute numeric hints indicating how many flowers are
9+
adjacent (horizontally, vertically, diagonally) to each square.
10+
"""
11+
12+
# Relative offsets to the eight neighboring cells around a given position
13+
COORDINATES: tuple[tuple[int, int]] = (
14+
(-1, -1),
15+
(-1, 0),
16+
(-1, 1),
17+
(0, -1),
18+
(0, 1),
19+
(1, -1),
20+
(1, 0),
21+
(1, 1),
22+
)
23+
24+
25+
def annotate(garden: list[str]) -> list[str]:
26+
"""
27+
Annotate a garden with counts of adjacent flowers.
28+
29+
Expects a rectangular list of strings containing only spaces and ``*``.
30+
Validation errors raise a :class:`ValueError`.
31+
32+
:param list garden: A list of equal-length strings representing the garden.
33+
``*`` marks a flower; space marks empty.
34+
:returns: An annotated garden of the same shape. Empty squares are
35+
replaced by digits (``"1"``–``"8"``) when adjacent to flowers;
36+
squares with zero adjacent flowers remain spaces. Flowers
37+
(``*``) are preserved.
38+
:rtype: list[str]
39+
:raises ValueError: If the garden is non-rectangular or contains
40+
invalid characters.
41+
"""
42+
# empty list
43+
if not garden:
44+
return []
45+
# raise an error when the board receives malformed input
46+
_validate(garden)
47+
return [
48+
"".join(
49+
str(count)
50+
if (count := _calc_surrounding_flowers(i_row, i_col, garden)) != 0
51+
else char
52+
for i_col, char in enumerate(row)
53+
)
54+
for i_row, row in enumerate(garden)
55+
]
56+
57+
58+
def _calc_surrounding_flowers(i_row: int, i_col: int, garden: list[str]) -> int:
59+
"""
60+
Count flowers adjacent to the given cell.
61+
62+
Counts the eight neighboring positions around ``(i_row, i_col)`` when the
63+
current cell is empty (space). If the cell itself is a flower (``*``), the
64+
count remains zero as the caller preserves flowers unchanged.
65+
66+
:param int i_row: Row index of the target cell.
67+
:param int i_col: Column index of the target cell.
68+
:param list garden: The rectangular garden representation.
69+
:returns: Number of adjacent flowers (0–8).
70+
:rtype: int
71+
"""
72+
return (
73+
sum(
74+
[
75+
_process_cell(i_row, offset_row, i_col, offset_col, garden)
76+
for offset_row, offset_col in COORDINATES
77+
]
78+
)
79+
if garden[i_row][i_col] == " "
80+
else 0
81+
)
82+
83+
84+
def _process_cell(i_row, offset_row, i_col, offset_col, garden) -> int:
85+
"""
86+
Return 1 if the neighbor at the given relative offset contains a flower.
87+
88+
Computes the absolute coordinates from ``(i_row, i_col)`` and the provided
89+
offsets, performs bounds checking to avoid ``IndexError``, and returns ``1``
90+
only when the cell is within the garden and equals ``"*"``.
91+
92+
:param int i_row: Row index of the reference cell.
93+
:param int offset_row: Row delta to apply to ``i_row``.
94+
:param int i_col: Column index of the reference cell.
95+
:param int offset_col: Column delta to apply to ``i_col``.
96+
:param list garden: The rectangular garden representation.
97+
:returns: ``1`` when the computed neighbor cell contains a flower, otherwise ``0``.
98+
:rtype: int
99+
"""
100+
row: int = i_row + offset_row
101+
col: int = i_col + offset_col
102+
103+
if (
104+
0 <= row < len(garden) # ROW: Avoid IndexError
105+
and 0 <= col < len(garden[0]) # COL: Avoid IndexError
106+
and garden[row][col] == "*" # Detect/count flower
107+
):
108+
return 1
109+
return 0
110+
111+
112+
def _validate(garden: list[str]) -> None:
113+
"""
114+
Validate the garden shape and contents.
115+
116+
Ensures the input is rectangular and contains only spaces and ``*``.
117+
Raise ValueError when the board receives malformed input garden is not
118+
a rectangle due to inconsistent row length or contains invalid chars
119+
inside the row.
120+
121+
:param list garden: A list of equal-length strings to validate.
122+
:raises ValueError: If rows have differing lengths or contain characters
123+
other than space or ``*``.
124+
"""
125+
garden_length = len(garden[0])
126+
if any(
127+
(len(row) != garden_length or not all(char in " *" for char in row))
128+
for row in garden
129+
):
130+
raise ValueError("The board is invalid with current input.")

0 commit comments

Comments
 (0)