[Flower-Field]: Reimplement Minesweeper as Flower Field (#3934)

* Reimplemented Minesweeper as Flower Field and regenerated test cases.
* Removed accidental solution from stub file.
This commit is contained in:
BethanyG
2025-06-25 12:17:48 -07:00
committed by GitHub
parent b7a33e7bf7
commit b3cf79fc02
11 changed files with 330 additions and 0 deletions

View File

@@ -1412,6 +1412,22 @@
], ],
"difficulty": 4 "difficulty": 4
}, },
{
"slug": "flower-field",
"name": "Flower Field",
"uuid": "0c2751c1-5d2f-499a-81b8-226e5092ea88",
"practices": ["lists"],
"prerequisites": [
"conditionals",
"lists",
"list-methods",
"loops",
"numbers",
"strings",
"string-methods"
],
"difficulty": 4
},
{ {
"slug": "rail-fence-cipher", "slug": "rail-fence-cipher",
"name": "Rail Fence Cipher", "name": "Rail Fence Cipher",

View File

@@ -0,0 +1,14 @@
# Instructions append
## Exception messages
Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.
This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the `board()` function receives malformed input. The tests will only pass if you both `raise` the `exception` and include a message with it.
To raise a `ValueError` with a message, write the message as an argument to the `exception` type:
```python
# when the board receives malformed input
raise ValueError("The board is invalid with current input.")
```

View File

@@ -0,0 +1,26 @@
# 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.
For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen):
```text
·*·*·
··*··
··*··
·····
```
Which your code should transform into this:
```text
1*3*1
13*31
·2*2·
·111·
```

View File

@@ -0,0 +1,7 @@
# Introduction
[Flower Field][history] 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.
[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/

View File

@@ -0,0 +1,53 @@
{
"exercise": "flower-field",
"version": "2.0",
"comments": [
" The expected outputs are represented as arrays of strings to ",
" improve readability in this JSON file. ",
" Your track may choose whether to present the input as a single ",
" string (concatenating all the lines) or as the list. "
],
"cases": [
{
"description": "annotate 9",
"property": "annotate",
"input": {
"garden": [
" ",
" * ",
" ",
" ",
" * "
]
},
"expected": [
" 111",
" 1*1",
" 111",
"111 ",
"1*1 "
]
},
{
"description": "different len",
"property": "annotate",
"input": {
"garden": [
" ",
"* ",
" "
]
},
"expected": {"error": "The board is invalid with current input."}
},
{
"description": "invalid char",
"property": "annotate",
"input": {
"garden": ["X * "]
},
"expected": {"error": "The board is invalid with current input."}
}
]
}

View File

@@ -0,0 +1,22 @@
{
"authors": [
"habere-et-dispertire",
"bethanyg"
],
"contributors": [
"isaacg",
"kotp"
],
"files": {
"solution": [
"flower_field.py"
],
"test": [
"flower_field_test.py"
],
"example": [
".meta/example.py"
]
},
"blurb": "Mark all the flowers in a garden."
}

View File

@@ -0,0 +1,40 @@
def annotate(garden):
if not garden:
return []
verify_board(garden)
row_len = len(garden[0])
col_len = len(garden)
board = [list(row) for row in garden]
for index1 in range(col_len):
for index2 in range(row_len):
if board[index1][index2] != ' ':
continue
low = max(index2 - 1, 0)
high = min(index2 + 2, row_len + 2)
counts = garden[index1][low:high].count('*')
if index1 > 0:
counts += garden[index1 - 1][low:high].count('*')
if index1 < col_len - 1:
counts += garden[index1 + 1][low:high].count('*')
if counts == 0:
continue
board[index1][index2] = str(counts)
return [''.join(row) for row in board]
def verify_board(garden):
# Rows with different lengths
row_len = len(garden[0])
if not all(len(row) == row_len for row in garden):
raise ValueError('The board is invalid with current input.')
# Unknown character in board
character_set = set()
for row in garden:
character_set.update(row)
if character_set - set(' *'):
raise ValueError('The board is invalid with current input.')

View File

@@ -0,0 +1,27 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header()}}
{%- macro test_call(case) -%}
{{ case["property"] | to_snake }}({{ case["input"]["garden"] }})
{%- endmacro %}
class {{ exercise | camel_case }}Test(unittest.TestCase):
{% for case in cases -%}
def test_{{ case["description"] | to_snake }}(self):
self.assertEqual({{ test_call(case) }}, {{ case["expected"] }})
{% endfor %}
# Additional tests for this track
{% for case in additional_cases -%}
def test_{{ case["description"] | to_snake }}(self):
{%- if case is error_case %}
with self.assertRaises(ValueError) as err:
{{ test_call(case) }}
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{%- else %}
self.assertEqual({{- test_call(case) }}, {{ case["expected"] }})
{%- endif %}
{% endfor %}

View File

@@ -0,0 +1,46 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.
[237ff487-467a-47e1-9b01-8a891844f86c]
description = "no rows"
[4b4134ec-e20f-439c-a295-664c38950ba1]
description = "no columns"
[d774d054-bbad-4867-88ae-069cbd1c4f92]
description = "no flowers"
[225176a0-725e-43cd-aa13-9dced501f16e]
description = "garden full of flowers"
[3f345495-f1a5-4132-8411-74bd7ca08c49]
description = "flower surrounded by spaces"
[6cb04070-4199-4ef7-a6fa-92f68c660fca]
description = "space surrounded by flowers"
[272d2306-9f62-44fe-8ab5-6b0f43a26338]
description = "horizontal line"
[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e]
description = "horizontal line, flowers at edges"
[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5]
description = "vertical line"
[b40f42f5-dec5-4abc-b167-3f08195189c1]
description = "vertical line, flowers at edges"
[58674965-7b42-4818-b930-0215062d543c]
description = "cross"
[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8]
description = "large garden"

View File

@@ -0,0 +1,3 @@
def annotate(garden):
# Function body starts here
pass

View File

@@ -0,0 +1,76 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json
# File last updated on 2025-06-25
import unittest
from flower_field import (
annotate,
)
class FlowerFieldTest(unittest.TestCase):
def test_no_rows(self):
self.assertEqual(annotate([]), [])
def test_no_columns(self):
self.assertEqual(annotate([""]), [""])
def test_no_flowers(self):
self.assertEqual(annotate([" ", " ", " "]), [" ", " ", " "])
def test_garden_full_of_flowers(self):
self.assertEqual(annotate(["***", "***", "***"]), ["***", "***", "***"])
def test_flower_surrounded_by_spaces(self):
self.assertEqual(annotate([" ", " * ", " "]), ["111", "1*1", "111"])
def test_space_surrounded_by_flowers(self):
self.assertEqual(annotate(["***", "* *", "***"]), ["***", "*8*", "***"])
def test_horizontal_line(self):
self.assertEqual(annotate([" * * "]), ["1*2*1"])
def test_horizontal_line_flowers_at_edges(self):
self.assertEqual(annotate(["* *"]), ["*1 1*"])
def test_vertical_line(self):
self.assertEqual(annotate([" ", "*", " ", "*", " "]), ["1", "*", "2", "*", "1"])
def test_vertical_line_flowers_at_edges(self):
self.assertEqual(annotate(["*", " ", " ", " ", "*"]), ["*", "1", " ", "1", "*"])
def test_cross(self):
self.assertEqual(
annotate([" * ", " * ", "*****", " * ", " * "]),
[" 2*2 ", "25*52", "*****", "25*52", " 2*2 "],
)
def test_large_garden(self):
self.assertEqual(
annotate([" * * ", " * ", " * ", " * *", " * * ", " "]),
["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"],
)
# Additional tests for this track
def test_annotate_9(self):
self.assertEqual(
annotate([" ", " * ", " ", " ", " * "]),
[" 111", " 1*1", " 111", "111 ", "1*1 "],
)
def test_different_len(self):
with self.assertRaises(ValueError) as err:
annotate([" ", "* ", " "])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(
err.exception.args[0], "The board is invalid with current input."
)
def test_invalid_char(self):
with self.assertRaises(ValueError) as err:
annotate(["X * "])
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(
err.exception.args[0], "The board is invalid with current input."
)