diff --git a/config.json b/config.json index a36553efda2..f596be3f89c 100644 --- a/config.json +++ b/config.json @@ -284,6 +284,17 @@ "generic_over_type" ] }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "117d6a25-960e-4d53-8347-a20490f60f36", + "practices": [], + "prerequisites": [], + "difficulty": 7, + "topics": [ + "board_state" + ] + }, { "slug": "minesweeper", "name": "Minesweeper", @@ -291,6 +302,7 @@ "practices": [], "prerequisites": [], "difficulty": 7, + "status": "deprecated", "topics": [ "board_state" ] diff --git a/exercises/practice/flower-field/.docs/instructions.append.md b/exercises/practice/flower-field/.docs/instructions.append.md new file mode 100644 index 00000000000..51d0953a4b1 --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +## Performance Hint + +All the inputs and outputs are in ASCII. +Rust `String`s and `&str` are utf8, so while one might expect `"Hello".chars()` to be simple, it actually has to check each char to see if it's 1, 2, 3 or 4 `u8`s long. +If we know a `&str` is ASCII then we can call `.as_bytes()` and refer to the underlying data as a `&[u8]` (byte slice). +Iterating over a slice of ASCII bytes is much quicker as there are no codepoints involved - every ASCII byte is one `u8` long. + +Can you complete the challenge without cloning the input? diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 00000000000..bbdae0c2cb1 --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -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· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 00000000000..af9b6153617 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -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/ diff --git a/exercises/practice/flower-field/.gitignore b/exercises/practice/flower-field/.gitignore new file mode 100644 index 00000000000..96ef6c0b944 --- /dev/null +++ b/exercises/practice/flower-field/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 00000000000..aa8cb9c600c --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,39 @@ +{ + "authors": [ + "EduardoBautista" + ], + "contributors": [ + "ashleygwilliams", + "coriolinus", + "cwhakes", + "EduardoBautista", + "efx", + "ErikSchierboom", + "ffflorian", + "IanWhitney", + "keiravillekode", + "kytrinyx", + "lutostag", + "mkantor", + "nfiles", + "petertseng", + "rofrol", + "stringparser", + "workingjubilee", + "xakon", + "ZapAnton" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/flower_field.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.rs b/exercises/practice/flower-field/.meta/example.rs new file mode 100644 index 00000000000..830ecf5f987 --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.rs @@ -0,0 +1,66 @@ +struct Board { + pieces: Vec>, + num_rows: usize, + num_cols: usize, +} + +impl Board { + fn annotated(&self) -> Vec { + (0..self.num_rows).map(|y| self.annotated_row(y)).collect() + } + + fn annotated_row(&self, y: usize) -> String { + self.pieces[y] + .iter() + .enumerate() + .map(|(x, &c)| { + if c == ' ' { + self.count_neighbouring_flowers_char(x, y) + } else { + c + } + }) + .collect::() + } + + fn count_neighbouring_flowers_char(&self, x: usize, y: usize) -> char { + let mut count = 0; + for x1 in neighbouring_points(x, self.num_cols) { + for y1 in neighbouring_points(y, self.num_rows) { + let piece = self.pieces[y1][x1]; + if piece == '*' { + count += 1; + } + } + } + if count == 0 { + ' ' + } else { + (b'0' + count) as char + } + } +} + +pub fn annotate(pieces: &[&str]) -> Vec { + if pieces.is_empty() { + return Vec::new(); + } + let pieces_vec = pieces.iter().map(|&r| r.chars().collect()).collect(); + Board { + pieces: pieces_vec, + num_rows: pieces.len(), + num_cols: pieces[0].len(), + } + .annotated() +} + +fn neighbouring_points(x: usize, limit: usize) -> Vec { + let mut offsets = vec![x]; + if x >= 1 { + offsets.push(x - 1); + } + if x + 2 <= limit { + offsets.push(x + 1); + } + offsets +} diff --git a/exercises/practice/flower-field/.meta/test_template.tera b/exercises/practice/flower-field/.meta/test_template.tera new file mode 100644 index 00000000000..3d62988999c --- /dev/null +++ b/exercises/practice/flower-field/.meta/test_template.tera @@ -0,0 +1,33 @@ +use flower_field::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.garden | length < 2 -%} + let input = &[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + let expected{% if test.expected | length == 0 %}: &[&str]{% endif %} = &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + {% else -%} + #[rustfmt::skip] + let (input, expected) = (&[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ], &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]); + {% endif -%} + let actual = annotate(input); + assert_eq!(actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 00000000000..c2b24fdaf5c --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -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" diff --git a/exercises/practice/flower-field/Cargo.toml b/exercises/practice/flower-field/Cargo.toml new file mode 100644 index 00000000000..fc11c06f117 --- /dev/null +++ b/exercises/practice/flower-field/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "flower_field" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/flower-field/src/lib.rs b/exercises/practice/flower-field/src/lib.rs new file mode 100644 index 00000000000..965fb2b658e --- /dev/null +++ b/exercises/practice/flower-field/src/lib.rs @@ -0,0 +1,5 @@ +pub fn annotate(garden: &[&str]) -> Vec { + todo!( + "\nAnnotate each square of the given garden with the number of flowers that surround said square (blank if there are no surrounding flowers):\n{garden:#?}\n" + ); +} diff --git a/exercises/practice/flower-field/tests/flower_field.rs b/exercises/practice/flower-field/tests/flower_field.rs new file mode 100644 index 00000000000..05cd8e66a17 --- /dev/null +++ b/exercises/practice/flower-field/tests/flower_field.rs @@ -0,0 +1,190 @@ +use flower_field::*; + +#[test] +fn no_rows() { + let input = &[]; + let expected: &[&str] = &[]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_columns() { + let input = &[""]; + let expected = &[""]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " ", + " ", + ], &[ + " ", + " ", + " ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn garden_full_of_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "***", + "***", + ], &[ + "***", + "***", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn flower_surrounded_by_spaces() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " * ", + " ", + ], &[ + "111", + "1*1", + "111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn space_surrounded_by_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "* *", + "***", + ], &[ + "***", + "*8*", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line() { + let input = &[" * * "]; + let expected = &["1*2*1"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line_flowers_at_edges() { + let input = &["* *"]; + let expected = &["*1 1*"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + "*", + " ", + "*", + " ", + ], &[ + "1", + "*", + "2", + "*", + "1", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line_flowers_at_edges() { + #[rustfmt::skip] + let (input, expected) = (&[ + "*", + " ", + " ", + " ", + "*", + ], &[ + "*", + "1", + " ", + "1", + "*", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn cross() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * ", + " * ", + "*****", + " * ", + " * ", + ], &[ + " 2*2 ", + "25*52", + "*****", + "25*52", + " 2*2 ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn large_garden() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * * ", + " * ", + " * ", + " * *", + " * * ", + " ", + ], &[ + "1*22*1", + "12*322", + " 123*2", + "112*4*", + "1*22*2", + "111111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +}