Skip to content

Commit 948bedf

Browse files
authored
Merge pull request #250 from ikostan/exercism-sync/8e85932bb96c7ce7
[Sync Iteration] python/flower-field/8
2 parents ba49413 + 6260392 commit 948bedf

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
13+
def annotate(garden: list[str]) -> list[str]:
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[str]
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+
# raise an error when the board receives malformed input
35+
_validate(garden)
36+
37+
new_garden: list[str] = garden.copy()
38+
for i_row, row in enumerate(new_garden):
39+
for i_col, char in enumerate(row):
40+
if char == " ":
41+
flower_count = 0
42+
flower_count += _calc_surrounding_flowers(i_row, i_col, new_garden)
43+
44+
if flower_count != 0:
45+
new_garden[i_row] = (
46+
new_garden[i_row][:i_col]
47+
+ str(flower_count)
48+
+ new_garden[i_row][i_col + 1 :]
49+
)
50+
51+
return new_garden
52+
53+
def _calc_surrounding_flowers(i_row: int, i_col: int, garden: list[str]) -> int:
54+
total: int = 0
55+
56+
if i_col - 1 >= 0 and garden[i_row][i_col - 1] == "*":
57+
total += 1
58+
59+
if i_col + 1 < len(garden[i_row]) and garden[i_row][i_col + 1] == "*":
60+
total += 1
61+
62+
if i_row - 1 >= 0 and "*" in garden[i_row - 1]:
63+
if i_col > 0 and garden[i_row - 1][i_col - 1] == "*":
64+
total += 1
65+
if garden[i_row - 1][i_col] == "*":
66+
total += 1
67+
if i_col + 1 < len(garden[i_row]) and garden[i_row - 1][i_col + 1] == "*":
68+
total += 1
69+
70+
if i_row + 1 < len(garden) and "*" in garden[i_row + 1]:
71+
if i_col > 0 and garden[i_row + 1][i_col - 1] == "*":
72+
total += 1
73+
if garden[i_row + 1][i_col] == "*":
74+
total += 1
75+
if i_col + 1 < len(garden[i_row]) and garden[i_row + 1][i_col + 1] == "*":
76+
total += 1
77+
78+
return total
79+
80+
81+
def _calc_flower_bottom(i_row: int, i_col: int, garden: list[str]) -> int:
82+
"""
83+
Count flowers in the three cells directly below the current position.
84+
85+
Checks the bottom-left, bottom, and bottom-right neighbors when the row
86+
below exists and contains any flowers.
87+
88+
:param int i_row: Current row index.
89+
:param int i_col: Current column index.
90+
:param list garden: The garden as a list of strings.
91+
:returns: Number of ``*`` cells among the three lower neighbors.
92+
:rtype: int
93+
"""
94+
flower_count = 0
95+
96+
if i_row + 1 < len(garden) and "*" in garden[i_row + 1]:
97+
# bottom-left
98+
if i_col > 0 and garden[i_row + 1][i_col - 1] == "*":
99+
flower_count += 1
100+
# bottom
101+
if garden[i_row + 1][i_col] == "*":
102+
flower_count += 1
103+
# bottom-right
104+
if i_col + 1 < len(garden[i_row]) and garden[i_row + 1][i_col + 1] == "*":
105+
flower_count += 1
106+
return flower_count
107+
108+
109+
def _validate(garden: list[str]):
110+
"""
111+
Validate the garden shape and contents.
112+
113+
Ensures the input is rectangular and contains only spaces and ``*``.
114+
115+
:param list garden: A list of equal-length strings to validate.
116+
:raises ValueError: If rows have differing lengths or contain characters
117+
other than space or ``*``.
118+
"""
119+
garden_length = len(garden[0])
120+
for row in garden:
121+
# when the board receives malformed input
122+
# garden is not a rectangle due to inconsistent row length
123+
# or contains invalid chars inside the row
124+
if len(row) != garden_length or not all(char in " *" for char in row):
125+
raise ValueError("The board is invalid with current input.")

0 commit comments

Comments
 (0)