Skip to content

Commit 4b4d9cb

Browse files
authored
Merge pull request #241 from ikostan/exercism-sync/ac010ff38964c0eb
Exercism sync/ac010ff38964c0eb
2 parents dd3caa1 + d1fa593 commit 4b4d9cb

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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+
directly adjacent (horizontally, vertically, diagonally) to each square.
10+
"""
11+
12+
13+
def annotate(garden: list) -> list:
14+
"""
15+
Annotate a garden with counts of adjacent flowers.
16+
17+
Expects a rectangular list of strings containing only spaces and ``*``.
18+
Validation errors raise a :class:`ValueError`.
19+
20+
:param list garden: A list of equal-length strings representing the garden.
21+
``*`` marks a flower; space marks empty.
22+
:returns: An annotated garden of the same shape. Empty squares are
23+
replaced by digits (``"1"``–``"8"``) when adjacent to flowers;
24+
squares with zero adjacent flowers remain spaces. Flowers
25+
(``*``) are preserved.
26+
:rtype: list
27+
:raises ValueError: If the garden is non-rectangular or contains
28+
invalid characters.
29+
"""
30+
# empty list
31+
if not garden:
32+
return []
33+
34+
# when the board receives malformed input
35+
if not _is_garden_valid(garden):
36+
raise ValueError("The board is invalid with current input.")
37+
38+
for i_row, row in enumerate(garden):
39+
for i_col, char in enumerate(row):
40+
if char == " ":
41+
flower_count: int = 0
42+
flower_count += _calc_flower_top(i_row, i_col, garden)
43+
flower_count += _calc_flower_bottom(i_row, i_col, garden)
44+
flower_count += _calc_flower_left(i_row, i_col, garden)
45+
flower_count += _calc_flower_right(i_row, i_col, garden)
46+
47+
if flower_count != 0:
48+
garden[i_row] = (
49+
garden[i_row][:i_col]
50+
+ str(flower_count)
51+
+ garden[i_row][i_col + 1:]
52+
)
53+
return garden
54+
55+
56+
def _calc_flower_left(i_row: int, i_col: int, garden: list) -> int:
57+
"""
58+
Count contiguous flowers to the left of the current position.
59+
60+
Scans leftward from ``(i_row, i_col - 1)`` until a non-flower character is
61+
found or the row boundary is reached.
62+
63+
:param int i_row: Current row index.
64+
:param int i_col: Current column index.
65+
:param list garden: The garden as a list of strings.
66+
:returns: Number of adjacent ``*`` cells to the left.
67+
:rtype: int
68+
"""
69+
flower_count: int = 0
70+
71+
if i_col - 1 >= 0:
72+
for char in garden[i_row][:i_col][::-1]:
73+
if char == "*":
74+
flower_count += 1
75+
else:
76+
break
77+
return flower_count
78+
79+
80+
def _calc_flower_right(i_row: int, i_col: int, garden: list) -> int:
81+
"""
82+
Count contiguous flowers to the right of the current position.
83+
84+
Scans rightward from ``(i_row, i_col + 1)`` until a non-flower character is
85+
found or the row boundary is reached.
86+
87+
:param int i_row: Current row index.
88+
:param int i_col: Current column index.
89+
:param list garden: The garden as a list of strings.
90+
:returns: Number of adjacent ``*`` cells to the right.
91+
:rtype: int
92+
"""
93+
flower_count: int = 0
94+
95+
if i_col + 1 < len(garden[i_row]):
96+
for char in garden[i_row][i_col + 1:]:
97+
if char == "*":
98+
flower_count += 1
99+
else:
100+
break
101+
return flower_count
102+
103+
104+
def _calc_flower_top(i_row: int, i_col: int, garden: list) -> int:
105+
"""
106+
Count flowers in the three cells directly above the current position.
107+
108+
Checks the top-left, top, and top-right neighbors when the row above
109+
exists and contains any flowers.
110+
111+
:param int i_row: Current row index.
112+
:param int i_col: Current column index.
113+
:param list garden: The garden as a list of strings.
114+
:returns: Number of ``*`` cells among the three upper neighbors.
115+
:rtype: int
116+
"""
117+
118+
flower_count: int = 0
119+
120+
if i_row - 1 >= 0 and "*" in garden[i_row - 1]:
121+
# top-left
122+
if i_col > 0 and garden[i_row - 1][i_col - 1] == "*":
123+
flower_count += 1
124+
# top
125+
if garden[i_row - 1][i_col] == "*":
126+
flower_count += 1
127+
# top-right
128+
if (
129+
i_col + 1 < len(garden[i_row])
130+
and garden[i_row - 1][i_col + 1] == "*"
131+
):
132+
flower_count += 1
133+
return flower_count
134+
135+
136+
def _calc_flower_bottom(i_row: int, i_col: int, garden: list) -> int:
137+
"""
138+
Count flowers in the three cells directly below the current position.
139+
140+
Checks the bottom-left, bottom, and bottom-right neighbors when the row
141+
below exists and contains any flowers.
142+
143+
:param int i_row: Current row index.
144+
:param int i_col: Current column index.
145+
:param list garden: The garden as a list of strings.
146+
:returns: Number of ``*`` cells among the three lower neighbors.
147+
:rtype: int
148+
"""
149+
150+
flower_count: int = 0
151+
152+
if i_row + 1 < len(garden) and "*" in garden[i_row + 1]:
153+
# bottom-left
154+
if i_col > 0 and garden[i_row + 1][i_col - 1] == "*":
155+
flower_count += 1
156+
# bottom
157+
if garden[i_row + 1][i_col] == "*":
158+
flower_count += 1
159+
# bottom-right
160+
if (
161+
i_col + 1 < len(garden[i_row])
162+
and garden[i_row + 1][i_col + 1] == "*"
163+
):
164+
flower_count += 1
165+
return flower_count
166+
167+
168+
def _is_garden_valid(garden: list) -> bool:
169+
"""
170+
Check whether the garden input is a valid rectangular board.
171+
172+
A garden is considered valid when all rows have the same length and only
173+
contain spaces or ``*`` characters.
174+
175+
:param list garden: Candidate garden as a list of strings.
176+
:returns: ``True`` if the input is rectangular and uses only valid
177+
characters; otherwise ``False``.
178+
:rtype: bool
179+
"""
180+
181+
garden_length: int = len(garden[0])
182+
# when the board receives malformed input
183+
for row in garden:
184+
# garden is not a rectangle due to inconsistent row length
185+
if len(row) != garden_length:
186+
return False
187+
# contains invalid chars inside row
188+
valid_chars: bool = all(char in " *" for char in row)
189+
if not valid_chars:
190+
return False
191+
return True

0 commit comments

Comments
 (0)