diff --git a/config.json b/config.json index 20260fb..779dfd0 100644 --- a/config.json +++ b/config.json @@ -392,6 +392,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "6c162b52-01dc-4845-af09-1af65f112946", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "matching-brackets", "name": "Matching Brackets", diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 0000000..bbdae0c --- /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 0000000..af9b615 --- /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/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 0000000..024aa81 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "flower-field.red" + ], + "test": [ + "flower-field-test.red" + ], + "example": [ + ".meta/example.red" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.red b/exercises/practice/flower-field/.meta/example.red new file mode 100644 index 0000000..e21a48b --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.red @@ -0,0 +1,60 @@ +Red [ + description: {"Flower Field" exercise solution for exercism platform} + author: "BNAndras" +] + +annotate: function [ + garden +][ + rows: length? garden + cols: length? first garden + result: [] + + repeat i rows [ + marked-row: copy "" + current-row: pick garden i + + repeat j cols [ + cell: pick current-row j + + either cell = #"*" [ + append marked-row "*" + ][ + count: 0 + min-row: max 1 (i - 1) + max-row: min rows (i + 1) + min-col: max 1 (j - 1) + max-col: min cols (j + 1) + + row-indices: collect [ + repeat k (max-row - min-row + 1) [keep min-row + k - 1] + ] + col-indices: collect [ + repeat k (max-col - min-col + 1) [keep min-col + k - 1] + ] + + foreach ni row-indices [ + foreach nj col-indices [ + if not and~ ni = i nj = j [ + neighbor-row: pick garden ni + neighbor: pick neighbor-row nj + if neighbor = #"*" [ + count: count + 1 + ] + ] + ] + ] + + either count = 0 [ + append marked-row " " + ][ + append marked-row to string! count + ] + ] + ] + + append result marked-row + ] + + result +] \ No newline at end of file diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 0000000..c2b24fd --- /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/flower-field-test.red b/exercises/practice/flower-field/flower-field-test.red new file mode 100644 index 0000000..240e707 --- /dev/null +++ b/exercises/practice/flower-field/flower-field-test.red @@ -0,0 +1,120 @@ +Red [ + description: {Tests for "Flower Field" Exercism exercise} + author: "loziniak" +] + +#include %testlib.red + +test-init/limit %flower-field.red 1 +; test-init/limit %.meta/example.red 1 ; test example solution + +canonical-cases: [#[ + description: "no rows" + input: #[ + garden: [] + ] + expected: [] + function: "annotate" + uuid: "237ff487-467a-47e1-9b01-8a891844f86c" +] #[ + description: "no columns" + input: #[ + garden: [""] + ] + expected: [""] + function: "annotate" + uuid: "4b4134ec-e20f-439c-a295-664c38950ba1" +] #[ + description: "no flowers" + input: #[ + garden: [" " " " " "] + ] + expected: [" " " " " "] + function: "annotate" + uuid: "d774d054-bbad-4867-88ae-069cbd1c4f92" +] #[ + description: "garden full of flowers" + input: #[ + garden: ["***" "***" "***"] + ] + expected: ["***" "***" "***"] + function: "annotate" + uuid: "225176a0-725e-43cd-aa13-9dced501f16e" +] #[ + description: "flower surrounded by spaces" + input: #[ + garden: [" " " * " " "] + ] + expected: ["111" "1*1" "111"] + function: "annotate" + uuid: "3f345495-f1a5-4132-8411-74bd7ca08c49" +] #[ + description: "space surrounded by flowers" + input: #[ + garden: ["***" "* *" "***"] + ] + expected: ["***" "*8*" "***"] + function: "annotate" + uuid: "6cb04070-4199-4ef7-a6fa-92f68c660fca" +] #[ + description: "horizontal line" + input: #[ + garden: [" * * "] + ] + expected: ["1*2*1"] + function: "annotate" + uuid: "272d2306-9f62-44fe-8ab5-6b0f43a26338" +] #[ + description: "horizontal line, flowers at edges" + input: #[ + garden: ["* *"] + ] + expected: ["*1 1*"] + function: "annotate" + uuid: "c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e" +] #[ + description: "vertical line" + input: #[ + garden: [" " "*" " " "*" " "] + ] + expected: ["1" "*" "2" "*" "1"] + function: "annotate" + uuid: "a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5" +] #[ + description: "vertical line, flowers at edges" + input: #[ + garden: ["*" " " " " " " "*"] + ] + expected: ["*" "1" " " "1" "*"] + function: "annotate" + uuid: "b40f42f5-dec5-4abc-b167-3f08195189c1" +] #[ + description: "cross" + input: #[ + garden: [" * " " * " "*****" " * " " * "] + ] + expected: [" 2*2 " "25*52" "*****" "25*52" " 2*2 "] + function: "annotate" + uuid: "58674965-7b42-4818-b930-0215062d543c" +] #[ + description: "large garden" + input: #[ + garden: [" * * " " * " " * " " * *" " * * " " "] + ] + expected: ["1*22*1" "12*322" " 123*2" "112*4*" "1*22*2" "111111"] + function: "annotate" + uuid: "dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8" +]] + + +foreach c-case canonical-cases [ + case-code: reduce [ + 'expect c-case/expected compose [ + (to word! c-case/function) (values-of c-case/input) + ] + ] + + test c-case/description case-code +] + +test-results/print diff --git a/exercises/practice/flower-field/flower-field.red b/exercises/practice/flower-field/flower-field.red new file mode 100644 index 0000000..dfd808b --- /dev/null +++ b/exercises/practice/flower-field/flower-field.red @@ -0,0 +1,11 @@ +Red [ + description: {"Flower Field" exercise solution for exercism platform} + author: "" ; you can write your name here, in quotes +] + +annotate: function [ + garden +] [ + cause-error 'user 'message "You need to implement annotate function." +] + diff --git a/exercises/practice/flower-field/testlib.red b/exercises/practice/flower-field/testlib.red new file mode 100644 index 0000000..9b0b79e --- /dev/null +++ b/exercises/practice/flower-field/testlib.red @@ -0,0 +1,217 @@ +Red [ + description: {Unit testing library} + author: "loziniak" +] + + +context [ + tested: ignore-after: test-file: results: output: none + + set 'test-init function [ + file [file!] + /limit + ia [integer!] + ] [ + self/tested: 0 + self/ignore-after: either limit [ia] [none] + self/test-file: file + self/results: copy [] + self/output: copy "" + ] + + sandbox!: context [ + + assert: function [ + code [block!] + /local result + ] [ + res: last results + + set/any 'result do code + either :result = true [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result + ] + + expect: function [ + expectation [any-type!] + code [block!] + /local result + ] [ + res: last results + res/expected: :expectation + + set/any 'result do code + res/actual: :result + + either :result = :expectation [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result + ] + + expect-error: function [ + type [word!] + code [block!] + /message + msg [string!] + /local result result-or-error + ] [ + returned-error?: no + set/any 'result-or-error try [ + set/any 'result do code + returned-error?: yes + :result + ] + + res: last results + res/actual: :result-or-error + res/expected: compose [type: (type)] + if message [append res/expected compose [id: 'message arg1: (msg)]] + + either all [ + error? :result-or-error + not returned-error? + result-or-error/type = type + any [ + not message + all [ + result-or-error/id = 'message + result-or-error/arg1 = msg + ] + ] + ] [ + res/status: 'pass + ] [ + res/status: 'fail + throw/name none 'expect-fail + ] + + :result-or-error + ] + ] + + set 'test function [ + summary [string!] + code [block!] + /extern + tested + ] [ + append results result: make map! compose/only [ + summary: (summary) ;@@ [string!] + test-code: (copy code) ;@@ [block!] + status: none ;@@ [word!] : 'pass | 'fail | 'error | 'ignored + ;-- expected (optional field) + ;-- actual (optional field) + ;-- output (optional field) + ] + + either any [ + none? ignore-after + tested < ignore-after + ] [ + clear output + old-functions: override-console + + exercise: make sandbox! load test-file + code: bind code exercise + uncaught?: yes + outcome: catch [ + outcome: try [ + catch/name [ + do code + ] 'expect-fail + none + ] + uncaught?: no + outcome + ] + + case [ + error? outcome [ + result/status: 'error + result/actual: outcome + ] + uncaught? [ + result/status: 'error + result/actual: make error! [type: 'throw id: 'throw arg1: outcome] + ] + ] + + restore-console old-functions + result/output: copy output + ] [ + result/status: 'ignored + ] + + tested: tested + 1 + ] + + set 'test-results function [ + /print + ] [ + either print [ + foreach result self/results [ + system/words/print rejoin [ + pad/with result/summary 40 #"." + "... " + switch result/status [ + pass ["✓"] + fail [rejoin [ + {FAILED.} + either find result 'expected [rejoin [ + { Expected: } result/expected + either find result 'actual [rejoin [ + {, but got } result/actual + ]] [] + ]] [] + newline + result/output + ]] + error [rejoin [ + newline + result/output + form result/actual + ]] + ignored ["(ignored)"] + ] + ] + ] + ] [ + self/results + ] + ] + + + override-console: function [] [ + old-functions: reduce [:prin :print :probe] + + system/words/prin: function [value [any-type!]] [ + append self/output form :value + return () + ] + system/words/print: function [value [any-type!]] [ + append self/output reduce [form :value #"^/"] + return () + ] + system/words/probe: function [value [any-type!]] [ + append self/output reduce [mold :value #"^/"] + return :value + ] + return old-functions + ] + + restore-console: function [old-functions [block!]] [ + set [prin print probe] old-functions + ] + +]