[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:
16
config.json
16
config.json
@@ -1412,6 +1412,22 @@
|
||||
],
|
||||
"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",
|
||||
"name": "Rail Fence Cipher",
|
||||
|
||||
14
exercises/practice/flower-field/.docs/instructions.append.md
Normal file
14
exercises/practice/flower-field/.docs/instructions.append.md
Normal 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.")
|
||||
```
|
||||
26
exercises/practice/flower-field/.docs/instructions.md
Normal file
26
exercises/practice/flower-field/.docs/instructions.md
Normal 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·
|
||||
```
|
||||
7
exercises/practice/flower-field/.docs/introduction.md
Normal file
7
exercises/practice/flower-field/.docs/introduction.md
Normal 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/
|
||||
53
exercises/practice/flower-field/.meta/additional_tests.json
Normal file
53
exercises/practice/flower-field/.meta/additional_tests.json
Normal 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."}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
22
exercises/practice/flower-field/.meta/config.json
Normal file
22
exercises/practice/flower-field/.meta/config.json
Normal 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."
|
||||
}
|
||||
40
exercises/practice/flower-field/.meta/example.py
Normal file
40
exercises/practice/flower-field/.meta/example.py
Normal 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.')
|
||||
27
exercises/practice/flower-field/.meta/template.j2
Normal file
27
exercises/practice/flower-field/.meta/template.j2
Normal 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 %}
|
||||
46
exercises/practice/flower-field/.meta/tests.toml
Normal file
46
exercises/practice/flower-field/.meta/tests.toml
Normal 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"
|
||||
3
exercises/practice/flower-field/flower_field.py
Normal file
3
exercises/practice/flower-field/flower_field.py
Normal file
@@ -0,0 +1,3 @@
|
||||
def annotate(garden):
|
||||
# Function body starts here
|
||||
pass
|
||||
76
exercises/practice/flower-field/flower_field_test.py
Normal file
76
exercises/practice/flower-field/flower_field_test.py
Normal 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."
|
||||
)
|
||||
Reference in New Issue
Block a user