From 9202f28f827be32ef8a2024f0ee337f77f72c67a Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Fri, 17 Jul 2026 18:44:00 +1000 Subject: [PATCH] add `state-of-tic-tac-toe` exercise --- config.json | 18 ++ .../.docs/instructions.md | 101 ++++++++ .../state-of-tic-tac-toe/.meta/config.json | 19 ++ .../state-of-tic-tac-toe/.meta/example.factor | 27 ++ .../state-of-tic-tac-toe/.meta/generator.jl | 15 ++ .../state-of-tic-tac-toe/.meta/tests.toml | 107 ++++++++ .../exercism-tools/exercism-tools.factor | 35 +++ .../state-of-tic-tac-toe-tests.factor | 236 ++++++++++++++++++ .../state-of-tic-tac-toe.factor | 5 + 9 files changed, 563 insertions(+) create mode 100644 exercises/practice/state-of-tic-tac-toe/.docs/instructions.md create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/config.json create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/example.factor create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/generator.jl create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/tests.toml create mode 100644 exercises/practice/state-of-tic-tac-toe/exercism-tools/exercism-tools.factor create mode 100644 exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe-tests.factor create mode 100644 exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe.factor diff --git a/config.json b/config.json index 57b1d95..a89d6e6 100644 --- a/config.json +++ b/config.json @@ -1921,6 +1921,24 @@ ], "difficulty": 5 }, + { + "slug": "state-of-tic-tac-toe", + "name": "State of Tic-Tac-Toe", + "uuid": "3185b66f-fedf-44b1-b447-e4adbc025e9d", + "practices": [ + "errors" + ], + "prerequisites": [ + "strings", + "sequences", + "arrays", + "higher-order-sequences", + "booleans", + "errors", + "locals" + ], + "difficulty": 5 + }, { "slug": "tournament", "name": "Tournament", diff --git a/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md b/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md new file mode 100644 index 0000000..1a03ebb --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md @@ -0,0 +1,101 @@ +# Instructions + +In this exercise, you're going to implement a program that determines the state of a [tic-tac-toe][] game. +(_You may also know the game as "noughts and crosses" or "Xs and Os"._) + +The game is played on a 3×3 grid. +Players take turns to place `X`s and `O`s on the grid. +The game ends when one player has won by placing three of marks in a row, column, or along a diagonal of the grid, or when the entire grid is filled up. + +In this exercise, we will assume that `X` starts. + +It's your job to determine which state a given game is in. + +There are 3 potential game states: + +- The game is **ongoing**. +- The game ended in a **draw**. +- The game ended in a **win**. + +If the given board is invalid, throw an appropriate error. + +If a board meets the following conditions, it is invalid: + +- The given board cannot be reached when turns are taken in the correct order (remember that `X` starts). +- The game was played after it already ended. + +## Examples + +### Ongoing game + +```text + | | + X | | +___|___|___ + | | + | X | O +___|___|___ + | | + O | X | + | | +``` + +### Draw + +```text + | | + X | O | X +___|___|___ + | | + X | X | O +___|___|___ + | | + O | X | O + | | +``` + +### Win + +```text + | | + X | X | X +___|___|___ + | | + | O | O +___|___|___ + | | + | | + | | +``` + +### Invalid + +#### Wrong turn order + +```text + | | + O | O | X +___|___|___ + | | + | | +___|___|___ + | | + | | + | | +``` + +#### Continued playing after win + +```text + | | + X | X | X +___|___|___ + | | + O | O | O +___|___|___ + | | + | | + | | +``` + +[tic-tac-toe]: https://en.wikipedia.org/wiki/Tic-tac-toe diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/config.json b/exercises/practice/state-of-tic-tac-toe/.meta/config.json new file mode 100644 index 0000000..f8be8b2 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "state-of-tic-tac-toe/state-of-tic-tac-toe.factor" + ], + "test": [ + "state-of-tic-tac-toe/state-of-tic-tac-toe-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Determine the game state of a match of Tic-Tac-Toe.", + "source": "Created by Sascha Mann for the Julia track of the Exercism Research Experiment.", + "source_url": "https://github.com/exercism/research_experiment_1/tree/julia-dev/exercises/julia-1-a" +} diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/example.factor b/exercises/practice/state-of-tic-tac-toe/.meta/example.factor new file mode 100644 index 0000000..5666b5a --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/example.factor @@ -0,0 +1,27 @@ +USING: kernel locals math sequences ; +IN: state-of-tic-tac-toe + +CONSTANT: winning-lines { + { 0 1 2 } { 3 4 5 } { 6 7 8 } + { 0 3 6 } { 1 4 7 } { 2 5 8 } + { 0 4 8 } { 2 4 6 } +} + +:: won? ( cells ch -- ? ) + winning-lines [ [ cells nth ch = ] all? ] any? ; + +:: gamestate ( board -- state ) + board concat :> cells + cells [ CHAR: X = ] count :> xs + cells [ CHAR: O = ] count :> os + cells CHAR: X won? :> x-won + cells CHAR: O won? :> o-won + + xs os 1 + > [ "Wrong turn order: X went twice" throw ] when + os xs > [ "Wrong turn order: O started" throw ] when + x-won xs os = and o-won xs os > and or + [ "Impossible board: game should have ended after the game was won" throw ] when + + x-won o-won or + [ "win" ] + [ cells [ CHAR: \s = ] any? [ "ongoing" ] [ "draw" ] if ] if ; diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/generator.jl b/exercises/practice/state-of-tic-tac-toe/.meta/generator.jl new file mode 100644 index 0000000..019f2b4 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/generator.jl @@ -0,0 +1,15 @@ +module StateOfTicTacToe + +function gen_test_case(case) + lines = map(s -> " \"$(escape_factor(s))\"", case["input"]["board"]) + board = "{\n" * join(lines, "\n") * "\n}" + expected = case["expected"] + if expected isa Dict + msg = escape_factor(expected["error"]) + return """[ $(board) gamestate ]\n[ "$(msg)" = ] must-fail-with""" + else + return """{ "$(expected)" }\n[ $(board) gamestate ] unit-test""" + end +end + +end diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml new file mode 100644 index 0000000..5f574b2 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml @@ -0,0 +1,107 @@ +# 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. + +[fe8e9fa9-37af-4d7e-aa24-2f4b8517161a] +description = "Won games -> Finished game where X won via left column victory" + +[96c30df5-ae23-4cf6-bf09-5ef056dddea1] +description = "Won games -> Finished game where X won via middle column victory" + +[0d7a4b0a-2afd-4a75-8389-5fb88ab05eda] +description = "Won games -> Finished game where X won via right column victory" + +[bd1007c0-ec5d-4c60-bb9f-1a4f22177d51] +description = "Won games -> Finished game where O won via left column victory" + +[c032f800-5735-4354-b1b9-46f14d4ee955] +description = "Won games -> Finished game where O won via middle column victory" + +[662c8902-c94a-4c4c-9d9c-e8ca513db2b4] +description = "Won games -> Finished game where O won via right column victory" + +[2d62121f-7e3a-44a0-9032-0d73e3494941] +description = "Won games -> Finished game where X won via top row victory" + +[108a5e82-cc61-409f-aece-d7a18c1beceb] +description = "Won games -> Finished game where X won via middle row victory" +include = false + +[346527db-4db9-4a96-b262-d7023dc022b0] +description = "Won games -> Finished game where X won via middle row victory" +reimplements = "108a5e82-cc61-409f-aece-d7a18c1beceb" + +[a013c583-75f8-4ab2-8d68-57688ff04574] +description = "Won games -> Finished game where X won via bottom row victory" + +[2c08e7d7-7d00-487f-9442-e7398c8f1727] +description = "Won games -> Finished game where O won via top row victory" + +[bb1d6c62-3e3f-4d1a-9766-f8803c8ed70f] +description = "Won games -> Finished game where O won via middle row victory" + +[6ef641e9-12ec-44f5-a21c-660ea93907af] +description = "Won games -> Finished game where O won via bottom row victory" + +[ab145b7b-26a7-426c-ab71-bf418cd07f81] +description = "Won games -> Finished game where X won via falling diagonal victory" + +[7450caab-08f5-4f03-a74b-99b98c4b7a4b] +description = "Won games -> Finished game where X won via rising diagonal victory" + +[c2a652ee-2f93-48aa-a710-a70cd2edce61] +description = "Won games -> Finished game where O won via falling diagonal victory" + +[5b20ceea-494d-4f0c-a986-b99efc163bcf] +description = "Won games -> Finished game where O won via rising diagonal victory" + +[035a49b9-dc35-47d3-9d7c-de197161b9d4] +description = "Won games -> Finished game where X won via a row and a column victory" + +[e5dfdeb0-d2bf-4b5a-b307-e673f69d4a53] +description = "Won games -> Finished game where X won via two diagonal victories" + +[b42ed767-194c-4364-b36e-efbfb3de8788] +description = "Drawn games -> Draw" + +[227a76b2-0fef-4e16-a4bd-8f9d7e4c3b13] +description = "Drawn games -> Another draw" + +[4d93f15c-0c40-43d6-b966-418b040012a9] +description = "Ongoing games -> Ongoing game: one move in" + +[c407ae32-4c44-4989-b124-2890cf531f19] +description = "Ongoing games -> Ongoing game: two moves in" + +[199b7a8d-e2b6-4526-a85e-78b416e7a8a9] +description = "Ongoing games -> Ongoing game: five moves in" + +[1670145b-1e3d-4269-a7eb-53cd327b302e] +description = "Invalid boards -> Invalid board: X went twice" + +[47c048e8-b404-4bcf-9e51-8acbb3253f3b] +description = "Invalid boards -> Invalid board: O started" + +[b1dc8b13-46c4-47db-a96d-aa90eedc4e8d] +description = "Invalid boards -> Invalid board" +include = false + +[6c1920f2-ab5c-4648-a0c9-997414dda5eb] +description = "Invalid boards -> Invalid board: X won and O kept playing" +reimplements = "b1dc8b13-46c4-47db-a96d-aa90eedc4e8d" + +[4801cda2-f5b7-4c36-8317-3cdd167ac22c] +description = "Invalid boards -> Invalid board: players kept playing after a win" + +[5a84757a-fc86-4328-aec9-a5759e6ed35d] +description = "Invalid boards -> Invalid board: O kept playing after X wins" + +[cf25543d-583a-4656-b9ab-f82dc00a4a02] +description = "Invalid boards -> Invalid board: X kept playing after O wins" diff --git a/exercises/practice/state-of-tic-tac-toe/exercism-tools/exercism-tools.factor b/exercises/practice/state-of-tic-tac-toe/exercism-tools/exercism-tools.factor new file mode 100644 index 0000000..df5f692 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/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/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe-tests.factor b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe-tests.factor new file mode 100644 index 0000000..dc7212a --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe-tests.factor @@ -0,0 +1,236 @@ +USING: exercism-tools io kernel state-of-tic-tac-toe tools.test unicode ; +IN: state-of-tic-tac-toe.tests + +"Finished game where X won via left column victory" description +{ "win" } +[ { + "XOO" + "X " + "X " +} gamestate ] unit-test + +STOP-HERE + +"Finished game where X won via middle column victory" description +{ "win" } +[ { + "OXO" + " X " + " X " +} gamestate ] unit-test + +"Finished game where X won via right column victory" description +{ "win" } +[ { + "OOX" + " X" + " X" +} gamestate ] unit-test + +"Finished game where O won via left column victory" description +{ "win" } +[ { + "OXX" + "OX " + "O " +} gamestate ] unit-test + +"Finished game where O won via middle column victory" description +{ "win" } +[ { + "XOX" + " OX" + " O " +} gamestate ] unit-test + +"Finished game where O won via right column victory" description +{ "win" } +[ { + "XXO" + " XO" + " O" +} gamestate ] unit-test + +"Finished game where X won via top row victory" description +{ "win" } +[ { + "XXX" + "XOO" + "O " +} gamestate ] unit-test + +"Finished game where X won via middle row victory" description +{ "win" } +[ { + "O " + "XXX" + " O " +} gamestate ] unit-test + +"Finished game where X won via bottom row victory" description +{ "win" } +[ { + " OO" + "O X" + "XXX" +} gamestate ] unit-test + +"Finished game where O won via top row victory" description +{ "win" } +[ { + "OOO" + "XXO" + "XX " +} gamestate ] unit-test + +"Finished game where O won via middle row victory" description +{ "win" } +[ { + "XX " + "OOO" + "X " +} gamestate ] unit-test + +"Finished game where O won via bottom row victory" description +{ "win" } +[ { + "XOX" + " XX" + "OOO" +} gamestate ] unit-test + +"Finished game where X won via falling diagonal victory" description +{ "win" } +[ { + "XOO" + " X " + " X" +} gamestate ] unit-test + +"Finished game where X won via rising diagonal victory" description +{ "win" } +[ { + "O X" + "OX " + "X " +} gamestate ] unit-test + +"Finished game where O won via falling diagonal victory" description +{ "win" } +[ { + "OXX" + "OOX" + "X O" +} gamestate ] unit-test + +"Finished game where O won via rising diagonal victory" description +{ "win" } +[ { + " O" + " OX" + "OXX" +} gamestate ] unit-test + +"Finished game where X won via a row and a column victory" description +{ "win" } +[ { + "XXX" + "XOO" + "XOO" +} gamestate ] unit-test + +"Finished game where X won via two diagonal victories" description +{ "win" } +[ { + "XOX" + "OXO" + "XOX" +} gamestate ] unit-test + +"Draw" description +{ "draw" } +[ { + "XOX" + "XXO" + "OXO" +} gamestate ] unit-test + +"Another draw" description +{ "draw" } +[ { + "XXO" + "OXX" + "XOO" +} gamestate ] unit-test + +"Ongoing game: one move in" description +{ "ongoing" } +[ { + " " + "X " + " " +} gamestate ] unit-test + +"Ongoing game: two moves in" description +{ "ongoing" } +[ { + "O " + " X " + " " +} gamestate ] unit-test + +"Ongoing game: five moves in" description +{ "ongoing" } +[ { + "X " + " XO" + "OX " +} gamestate ] unit-test + +"Invalid board: X went twice" description +[ { + "XX " + " " + " " +} gamestate ] +[ "Wrong turn order: X went twice" = ] must-fail-with + +"Invalid board: O started" description +[ { + "OOX" + " " + " " +} gamestate ] +[ "Wrong turn order: O started" = ] must-fail-with + +"Invalid board: X won and O kept playing" description +[ { + "XXX" + "OOO" + " " +} gamestate ] +[ "Impossible board: game should have ended after the game was won" = ] must-fail-with + +"Invalid board: players kept playing after a win" description +[ { + "XXX" + "OOO" + "XOX" +} gamestate ] +[ "Impossible board: game should have ended after the game was won" = ] must-fail-with + +"Invalid board: O kept playing after X wins" description +[ { + "OO " + "XXX" + " O " +} gamestate ] +[ "Impossible board: game should have ended after the game was won" = ] must-fail-with + +"Invalid board: X kept playing after O wins" description +[ { + "XX " + "OOO" + " XX" +} gamestate ] +[ "Impossible board: game should have ended after the game was won" = ] must-fail-with diff --git a/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe.factor b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe.factor new file mode 100644 index 0000000..bfa5bcc --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe/state-of-tic-tac-toe.factor @@ -0,0 +1,5 @@ +USING: kernel ; +IN: state-of-tic-tac-toe + +: gamestate ( board -- state ) + "unimplemented" throw ;