Skip to content

Commit 94ea5ae

Browse files
authored
Merge pull request #269 from ikostan/exercism-sync/80e487be7475263f
Exercism sync/80e487be7475263f
2 parents 3be57f4 + 8c7a4dc commit 94ea5ae

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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+
_process_cell(i_row, offset_row, i_col, offset_col, garden)
75+
for offset_row, offset_col in COORDINATES
76+
)
77+
if garden[i_row][i_col] == " "
78+
else 0
79+
)
80+
81+
82+
def _process_cell(i_row, offset_row, i_col, offset_col, garden) -> int:
83+
"""
84+
Return 1 if the neighbor at the given relative offset contains a flower.
85+
86+
Computes the absolute coordinates from ``(i_row, i_col)`` and the provided
87+
offsets, performs bounds checking to avoid ``IndexError``, and returns ``1``
88+
only when the cell is within the garden and equals ``"*"``.
89+
90+
:param int i_row: Row index of the reference cell.
91+
:param int offset_row: Row delta to apply to ``i_row``.
92+
:param int i_col: Column index of the reference cell.
93+
:param int offset_col: Column delta to apply to ``i_col``.
94+
:param list garden: The rectangular garden representation.
95+
:returns: ``1`` when the computed neighbor cell contains a flower, otherwise ``0``.
96+
:rtype: int
97+
"""
98+
row: int = i_row + offset_row
99+
col: int = i_col + offset_col
100+
101+
if (
102+
0 <= row < len(garden) # ROW: Avoid IndexError
103+
and 0 <= col < len(garden[0]) # COL: Avoid IndexError
104+
and garden[row][col] == "*" # Detect/count flower
105+
):
106+
return 1
107+
return 0
108+
109+
110+
def _validate(garden: list[str]) -> None:
111+
"""
112+
Validate the garden shape and contents.
113+
114+
Ensures the input is rectangular and contains only spaces and ``*``.
115+
Raise ValueError when the board receives malformed input garden is not
116+
a rectangle due to inconsistent row length or contains invalid chars
117+
inside the row.
118+
119+
:param list garden: A list of equal-length strings to validate.
120+
:raises ValueError: If rows have differing lengths or contain characters
121+
other than space or ``*``.
122+
"""
123+
garden_length = len(garden[0])
124+
if any(
125+
(len(row) != garden_length or not all(char in " *" for char in row))
126+
for row in garden
127+
):
128+
raise ValueError("The board is invalid with current input.")

0 commit comments

Comments
 (0)