From 93fbfc85e1296668aadfd3b8b877fa21ad9a88ca Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Fri, 17 Jul 2026 19:26:36 +1000 Subject: [PATCH] add `bowling` exercise We also reduce the difficulty of nth-prime to 7. --- config.json | 59 +++-- .../bowling/.docs/instructions.append.md | 22 ++ .../practice/bowling/.docs/instructions.md | 56 ++++ exercises/practice/bowling/.meta/config.json | 19 ++ .../practice/bowling/.meta/example.factor | 61 +++++ exercises/practice/bowling/.meta/generator.jl | 36 +++ exercises/practice/bowling/.meta/tests.toml | 103 ++++++++ .../bowling/bowling/bowling-tests.factor | 250 ++++++++++++++++++ .../practice/bowling/bowling/bowling.factor | 20 ++ .../exercism-tools/exercism-tools.factor | 35 +++ .../ocr-numbers/.docs/instructions.append.md | 2 +- 11 files changed, 642 insertions(+), 21 deletions(-) create mode 100644 exercises/practice/bowling/.docs/instructions.append.md create mode 100644 exercises/practice/bowling/.docs/instructions.md create mode 100644 exercises/practice/bowling/.meta/config.json create mode 100644 exercises/practice/bowling/.meta/example.factor create mode 100644 exercises/practice/bowling/.meta/generator.jl create mode 100644 exercises/practice/bowling/.meta/tests.toml create mode 100644 exercises/practice/bowling/bowling/bowling-tests.factor create mode 100644 exercises/practice/bowling/bowling/bowling.factor create mode 100644 exercises/practice/bowling/exercism-tools/exercism-tools.factor diff --git a/config.json b/config.json index 57b1d955..4d82c0e9 100644 --- a/config.json +++ b/config.json @@ -2260,6 +2260,26 @@ ], "difficulty": 7 }, + { + "slug": "nth-prime", + "name": "Nth Prime", + "uuid": "cbd8dbf2-67da-49fe-a89b-db82e4a9f440", + "practices": [ + "numbers", + "curry-compose-fry" + ], + "prerequisites": [ + "numbers", + "conditionals", + "sequences", + "higher-order-sequences", + "curry-compose-fry", + "errors", + "indexed-iteration", + "generics" + ], + "difficulty": 7 + }, { "slug": "paasio", "name": "PaaS I/O", @@ -2381,26 +2401,6 @@ ], "difficulty": 7 }, - { - "slug": "nth-prime", - "name": "Nth Prime", - "uuid": "cbd8dbf2-67da-49fe-a89b-db82e4a9f440", - "practices": [ - "numbers", - "curry-compose-fry" - ], - "prerequisites": [ - "numbers", - "conditionals", - "sequences", - "higher-order-sequences", - "curry-compose-fry", - "errors", - "indexed-iteration", - "generics" - ], - "difficulty": 8 - }, { "slug": "palindrome-products", "name": "Palindrome Products", @@ -2440,6 +2440,25 @@ ], "difficulty": 8 }, + { + "slug": "bowling", + "name": "Bowling", + "uuid": "c61784d6-6183-4d30-9046-21e85ed583f1", + "practices": [ + "tuples" + ], + "prerequisites": [ + "numbers", + "conditionals", + "sequences", + "tuples", + "mutation", + "errors", + "indexed-iteration", + "locals" + ], + "difficulty": 9 + }, { "slug": "connect", "name": "Connect", diff --git a/exercises/practice/bowling/.docs/instructions.append.md b/exercises/practice/bowling/.docs/instructions.append.md new file mode 100644 index 00000000..ccfd4654 --- /dev/null +++ b/exercises/practice/bowling/.docs/instructions.append.md @@ -0,0 +1,22 @@ +# Instructions append + +## Words + +Define a `game` tuple and a constructor ` ( -- game )` that returns a new +game with no rolls. + +Provide these words operating on a game: + +- `roll ( pins game -- )` — record that `pins` pins were knocked down. Throw an + error when the roll is invalid: + - `"Negative roll is invalid"` for a negative number of pins. + - `"Cannot roll after game is over"` once all ten frames (and any fill balls) + have been rolled. + - `"Pin count exceeds pins on the lane"` when more pins are knocked down than + are standing. +- `score ( game -- n )` — the total score for the game. Throw + `"Score cannot be taken until the end of the game"` when the game is not yet + complete. + +Note that `roll` shadows the `roll` shuffle word from the `kernel` vocabulary. +The test suite disambiguates with `FROM: bowling => roll ;`. diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md new file mode 100644 index 00000000..60ccad1b --- /dev/null +++ b/exercises/practice/bowling/.docs/instructions.md @@ -0,0 +1,56 @@ +# Instructions + +Score a bowling game. + +Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle. +Write code to keep track of the score of a game of bowling. + +## Scoring Bowling + +The game consists of 10 frames. +A frame is composed of one or two ball throws with 10 pins standing at frame initialization. +There are three cases for the tabulation of a frame. + +- An open frame is where a score of less than 10 is recorded for the frame. + In this case the score for the frame is the number of pins knocked down. + +- A spare is where all ten pins are knocked down by the second throw. + The total value of a spare is 10 plus the number of pins knocked down in their next throw. + +- A strike is where all ten pins are knocked down by the first throw. + The total value of a strike is 10 plus the number of pins knocked down in the next two throws. + If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. + +Here is a three frame example: + +| Frame 1 | Frame 2 | Frame 3 | +| :--------: | :--------: | :--------------: | +| X (strike) | 5/ (spare) | 9 0 (open frame) | + +Frame 1 is (10 + 5 + 5) = 20 + +Frame 2 is (5 + 5 + 9) = 19 + +Frame 3 is (9 + 0) = 9 + +This means the current running total is 48. + +The tenth frame in the game is a special case. +If someone throws a spare or a strike then they get one or two fill balls respectively. +Fill balls exist to calculate the total of the 10th frame. +Scoring a strike or spare on the fill ball does not give the player more fill balls. +The total value of the 10th frame is the total number of pins knocked down. + +For a tenth frame of X1/ (strike and a spare), the total value is 20. + +For a tenth frame of XXX (three strikes), the total value is 30. + +## Requirements + +Write code to keep track of the score of a game of bowling. +It should support two operations: + +- `roll(pins : int)` is called each time the player rolls a ball. + The argument is the number of pins knocked down. +- `score() : int` is called only at the very end of the game. + It returns the total score for that game. diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json new file mode 100644 index 00000000..088e084b --- /dev/null +++ b/exercises/practice/bowling/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "bowling/bowling.factor" + ], + "test": [ + "bowling/bowling-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Score a bowling game.", + "source": "The Bowling Game Kata from UncleBob", + "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" +} diff --git a/exercises/practice/bowling/.meta/example.factor b/exercises/practice/bowling/.meta/example.factor new file mode 100644 index 00000000..1a928b2f --- /dev/null +++ b/exercises/practice/bowling/.meta/example.factor @@ -0,0 +1,61 @@ +USING: accessors combinators kernel locals math sequences ; +IN: bowling + +TUPLE: game rolls current frame ; + +: ( -- game ) + V{ } clone V{ } clone 1 game boa ; + +! Pins standing for the next roll, or f when the game is over. +:: tenth-standing ( cur -- pins ) + { + { [ cur length 0 = ] [ 10 ] } + { [ cur length 1 = ] [ cur first 10 = [ 10 ] [ 10 cur first - ] if ] } + { [ cur length 2 = ] [ + cur first 10 = + [ cur second 10 = [ 10 ] [ 10 cur second - ] if ] + [ cur sum 10 = [ 10 ] [ f ] if ] if + ] } + [ f ] + } cond ; + +:: standing ( game -- pins ) + game current>> :> cur + game frame>> 10 < + [ cur empty? [ 10 ] [ 10 cur first - ] if ] + [ cur tenth-standing ] if ; + +: frame-done? ( game -- ? ) + current>> [ first 10 = ] [ length 2 = ] bi or ; + +:: roll ( pins game -- ) + pins 0 < [ "Negative roll is invalid" throw ] when + game standing :> available + available [ "Cannot roll after game is over" throw ] unless + pins available > [ "Pin count exceeds pins on the lane" throw ] when + pins game rolls>> push + pins game current>> push + game frame>> 10 < game frame-done? and + [ game [ 1 + ] change-frame V{ } clone >>current drop ] when ; + +:: score ( game -- n ) + game standing [ "Score cannot be taken until the end of the game" throw ] when + game rolls>> :> rolls + 0 :> total! + 0 :> i! + 10 [ + i rolls nth :> first-roll + first-roll 10 = + [ + total 10 + i 1 + rolls nth + i 2 + rolls nth + total! + i 1 + i! + ] + [ + i 1 + rolls nth :> second-roll + first-roll second-roll + 10 = + [ total 10 + i 2 + rolls nth + total! ] + [ total first-roll + second-roll + total! ] if + i 2 + i! + ] if + ] times + total ; diff --git a/exercises/practice/bowling/.meta/generator.jl b/exercises/practice/bowling/.meta/generator.jl new file mode 100644 index 00000000..49dede1f --- /dev/null +++ b/exercises/practice/bowling/.meta/generator.jl @@ -0,0 +1,36 @@ +module Bowling + +const EXTRA_VOCABS = ["locals", "sequences"] +const EXTRA_HEADER = "FROM: bowling => roll ;" + +function gen_test_case(case) + input = case["input"] + expected = case["expected"] + + body = String[" :> g"] + previous = input["previousRolls"] + if !isempty(previous) + push!(body, "$(format_int_array(previous)) [ g roll ] each") + end + if haskey(input, "roll") + push!(body, "$(to_int_str(input["roll"])) g roll") + else + push!(body, "g score") + end + body_str = join(body, "\n ") + + if expected isa Dict + msg = escape_factor(expected["error"]) + return """[ + [let $(body_str) + ] +] [ "$(msg)" = ] must-fail-with""" + else + return """{ $(to_int_str(expected)) } [ + [let $(body_str) + ] +] unit-test""" + end +end + +end diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml new file mode 100644 index 00000000..19042607 --- /dev/null +++ b/exercises/practice/bowling/.meta/tests.toml @@ -0,0 +1,103 @@ +# 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. + +[656ae006-25c2-438c-a549-f338e7ec7441] +description = "should be able to score a game with all zeros" + +[f85dcc56-cd6b-4875-81b3-e50921e3597b] +description = "should be able to score a game with no strikes or spares" + +[d1f56305-3ac2-4fe0-8645-0b37e3073e20] +description = "a spare followed by zeros is worth ten points" + +[0b8c8bb7-764a-4287-801a-f9e9012f8be4] +description = "points scored in the roll after a spare are counted twice" + +[4d54d502-1565-4691-84cd-f29a09c65bea] +description = "consecutive spares each get a one roll bonus" + +[e5c9cf3d-abbe-4b74-ad48-34051b2b08c0] +description = "a spare in the last frame gets a one roll bonus that is counted once" + +[75269642-2b34-4b72-95a4-9be28ab16902] +description = "a strike earns ten points in a frame with a single roll" + +[037f978c-5d01-4e49-bdeb-9e20a2e6f9a6] +description = "points scored in the two rolls after a strike are counted twice as a bonus" + +[1635e82b-14ec-4cd1-bce4-4ea14bd13a49] +description = "consecutive strikes each get the two roll bonus" + +[e483e8b6-cb4b-4959-b310-e3982030d766] +description = "a strike in the last frame gets a two roll bonus that is counted once" + +[9d5c87db-84bc-4e01-8e95-53350c8af1f8] +description = "rolling a spare with the two roll bonus does not get a bonus roll" + +[576faac1-7cff-4029-ad72-c16bcada79b5] +description = "strikes with the two roll bonus do not get bonus rolls" + +[efb426ec-7e15-42e6-9b96-b4fca3ec2359] +description = "last two strikes followed by only last bonus with non strike points" + +[72e24404-b6c6-46af-b188-875514c0377b] +description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus" + +[62ee4c72-8ee8-4250-b794-234f1fec17b1] +description = "all strikes is a perfect game" + +[1245216b-19c6-422c-b34b-6e4012d7459f] +description = "rolls cannot score negative points" + +[5fcbd206-782c-4faa-8f3a-be5c538ba841] +description = "a roll cannot score more than 10 points" + +[fb023c31-d842-422d-ad7e-79ce1db23c21] +description = "two rolls in a frame cannot score more than 10 points" + +[6082d689-d677-4214-80d7-99940189381b] +description = "bonus roll after a strike in the last frame cannot score more than 10 points" + +[e9565fe6-510a-4675-ba6b-733a56767a45] +description = "two bonus rolls after a strike in the last frame cannot score more than 10 points" + +[2f6acf99-448e-4282-8103-0b9c7df99c3d] +description = "two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike" + +[6380495a-8bc4-4cdb-a59f-5f0212dbed01] +description = "the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike" + +[2b2976ea-446c-47a3-9817-42777f09fe7e] +description = "second bonus roll after a strike in the last frame cannot score more than 10 points" + +[29220245-ac8d-463d-bc19-98a94cfada8a] +description = "an unstarted game cannot be scored" + +[4473dc5d-1f86-486f-bf79-426a52ddc955] +description = "an incomplete game cannot be scored" + +[2ccb8980-1b37-4988-b7d1-e5701c317df3] +description = "cannot roll if game already has ten frames" + +[4864f09b-9df3-4b65-9924-c595ed236f1b] +description = "bonus rolls for a strike in the last frame must be rolled before score can be calculated" + +[537f4e37-4b51-4d1c-97e2-986eb37b2ac1] +description = "both bonus rolls for a strike in the last frame must be rolled before score can be calculated" + +[8134e8c1-4201-4197-bf9f-1431afcde4b9] +description = "bonus roll for a spare in the last frame must be rolled before score can be calculated" + +[9d4a9a55-134a-4bad-bae8-3babf84bd570] +description = "cannot roll after bonus roll for spare" + +[d3e02652-a799-4ae3-b53b-68582cc604be] +description = "cannot roll after bonus rolls for strike" diff --git a/exercises/practice/bowling/bowling/bowling-tests.factor b/exercises/practice/bowling/bowling/bowling-tests.factor new file mode 100644 index 00000000..f0aeffd9 --- /dev/null +++ b/exercises/practice/bowling/bowling/bowling-tests.factor @@ -0,0 +1,250 @@ +USING: bowling exercism-tools io kernel locals sequences tools.test unicode ; +IN: bowling.tests +FROM: bowling => roll ; + +"should be able to score a game with all zeros" description +{ 0 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +STOP-HERE + +"should be able to score a game with no strikes or spares" description +{ 90 } [ + [let :> g + { 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 } [ g roll ] each + g score + ] +] unit-test + +"a spare followed by zeros is worth ten points" description +{ 10 } [ + [let :> g + { 6 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"points scored in the roll after a spare are counted twice" description +{ 16 } [ + [let :> g + { 6 4 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"consecutive spares each get a one roll bonus" description +{ 31 } [ + [let :> g + { 5 5 3 7 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"a spare in the last frame gets a one roll bonus that is counted once" description +{ 17 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 3 7 } [ g roll ] each + g score + ] +] unit-test + +"a strike earns ten points in a frame with a single roll" description +{ 10 } [ + [let :> g + { 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"points scored in the two rolls after a strike are counted twice as a bonus" description +{ 26 } [ + [let :> g + { 10 5 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"consecutive strikes each get the two roll bonus" description +{ 81 } [ + [let :> g + { 10 10 10 5 3 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + g score + ] +] unit-test + +"a strike in the last frame gets a two roll bonus that is counted once" description +{ 18 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 7 1 } [ g roll ] each + g score + ] +] unit-test + +"rolling a spare with the two roll bonus does not get a bonus roll" description +{ 20 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 7 3 } [ g roll ] each + g score + ] +] unit-test + +"strikes with the two roll bonus do not get bonus rolls" description +{ 30 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 10 10 } [ g roll ] each + g score + ] +] unit-test + +"last two strikes followed by only last bonus with non strike points" description +{ 31 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 10 0 1 } [ g roll ] each + g score + ] +] unit-test + +"a strike with the one roll bonus after a spare in the last frame does not get a bonus" description +{ 20 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 3 10 } [ g roll ] each + g score + ] +] unit-test + +"all strikes is a perfect game" description +{ 300 } [ + [let :> g + { 10 10 10 10 10 10 10 10 10 10 10 10 } [ g roll ] each + g score + ] +] unit-test + +"rolls cannot score negative points" description +[ + [let :> g + -1 g roll + ] +] [ "Negative roll is invalid" = ] must-fail-with + +"a roll cannot score more than 10 points" description +[ + [let :> g + 11 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"two rolls in a frame cannot score more than 10 points" description +[ + [let :> g + { 5 } [ g roll ] each + 6 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"bonus roll after a strike in the last frame cannot score more than 10 points" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 } [ g roll ] each + 11 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"two bonus rolls after a strike in the last frame cannot score more than 10 points" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 5 } [ g roll ] each + 6 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike" description +{ 26 } [ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 10 6 } [ g roll ] each + g score + ] +] unit-test + +"the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 6 } [ g roll ] each + 10 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"second bonus roll after a strike in the last frame cannot score more than 10 points" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 10 } [ g roll ] each + 11 g roll + ] +] [ "Pin count exceeds pins on the lane" = ] must-fail-with + +"an unstarted game cannot be scored" description +[ + [let :> g + g score + ] +] [ "Score cannot be taken until the end of the game" = ] must-fail-with + +"an incomplete game cannot be scored" description +[ + [let :> g + { 0 0 } [ g roll ] each + g score + ] +] [ "Score cannot be taken until the end of the game" = ] must-fail-with + +"cannot roll if game already has ten frames" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 } [ g roll ] each + 0 g roll + ] +] [ "Cannot roll after game is over" = ] must-fail-with + +"bonus rolls for a strike in the last frame must be rolled before score can be calculated" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 } [ g roll ] each + g score + ] +] [ "Score cannot be taken until the end of the game" = ] must-fail-with + +"both bonus rolls for a strike in the last frame must be rolled before score can be calculated" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 10 } [ g roll ] each + g score + ] +] [ "Score cannot be taken until the end of the game" = ] must-fail-with + +"bonus roll for a spare in the last frame must be rolled before score can be calculated" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 3 } [ g roll ] each + g score + ] +] [ "Score cannot be taken until the end of the game" = ] must-fail-with + +"cannot roll after bonus roll for spare" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 3 2 } [ g roll ] each + 2 g roll + ] +] [ "Cannot roll after game is over" = ] must-fail-with + +"cannot roll after bonus rolls for strike" description +[ + [let :> g + { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 3 2 } [ g roll ] each + 2 g roll + ] +] [ "Cannot roll after game is over" = ] must-fail-with diff --git a/exercises/practice/bowling/bowling/bowling.factor b/exercises/practice/bowling/bowling/bowling.factor new file mode 100644 index 00000000..fac0ecff --- /dev/null +++ b/exercises/practice/bowling/bowling/bowling.factor @@ -0,0 +1,20 @@ +USING: kernel ; +IN: bowling + +! Define a `game` tuple to hold the rolls made so far, and any +! other state you need. `` should return a new game with +! no rolls. + +! `roll` records the number of pins knocked down, throwing when +! the roll is invalid or the game is already over. +! `score` returns the final score, throwing when the game is +! not yet complete. + +: ( -- game ) + "unimplemented" throw ; + +: roll ( pins game -- ) + "unimplemented" throw ; + +: score ( game -- n ) + "unimplemented" throw ; diff --git a/exercises/practice/bowling/exercism-tools/exercism-tools.factor b/exercises/practice/bowling/exercism-tools/exercism-tools.factor new file mode 100644 index 00000000..df5f6920 --- /dev/null +++ b/exercises/practice/bowling/exercism-tools/exercism-tools.factor @@ -0,0 +1,35 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces prettyprint.config sequences + source-files.errors.debugger system tools.test vocabs + vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + [ failure error>> [ error. ] [ 2drop ] recover ] without-limits + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + vocab-roots [ "." prefix ] change-global + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/exercises/practice/ocr-numbers/.docs/instructions.append.md b/exercises/practice/ocr-numbers/.docs/instructions.append.md index 964d41a7..215457d8 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.append.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.append.md @@ -5,7 +5,7 @@ `convert ( rows -- str )` takes the grid as an array of strings, one string per row, and returns the recognised digits as a single string. Multiple rows of cells are joined in the output with -commas (`,`) per the canonical instructions. +commas (`,`). ## Errors