From 5041fc24c0faba1446886df630ef143e9c02d6bc Mon Sep 17 00:00:00 2001 From: habere-et-dispertire Date: Wed, 28 Aug 2024 06:00:11 +0100 Subject: [PATCH] [Implementation] State of Tic-Tac-Toe (#734) --- config.json | 8 + .../.docs/instructions.md | 101 +++++++++ .../state-of-tic-tac-toe/.meta/config.json | 19 ++ .../solutions/lib/StateOfTicTacToe.rakumod | 30 +++ .../solutions/t/state-of-tic-tac-toe.rakutest | 1 + .../.meta/template-data.yaml | 58 ++++++ .../state-of-tic-tac-toe/.meta/tests.toml | 101 +++++++++ .../lib/StateOfTicTacToe.rakumod | 5 + .../t/state-of-tic-tac-toe.rakutest | 195 ++++++++++++++++++ 9 files changed, 518 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/solutions/lib/StateOfTicTacToe.rakumod create mode 120000 exercises/practice/state-of-tic-tac-toe/.meta/solutions/t/state-of-tic-tac-toe.rakutest create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/template-data.yaml create mode 100644 exercises/practice/state-of-tic-tac-toe/.meta/tests.toml create mode 100644 exercises/practice/state-of-tic-tac-toe/lib/StateOfTicTacToe.rakumod create mode 100755 exercises/practice/state-of-tic-tac-toe/t/state-of-tic-tac-toe.rakutest diff --git a/config.json b/config.json index 47bfdd06..9887d651 100644 --- a/config.json +++ b/config.json @@ -711,6 +711,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "state-of-tic-tac-toe", + "name": "State of Tic-Tac-Toe", + "uuid": "072bb00c-e0e5-4f35-9e68-173111b6fd6d", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ] }, 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 00000000..f525d358 --- /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 games 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 00000000..e2f088d8 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "habere-et-dispertire" + ], + "files": { + "solution": [ + "lib/StateOfTicTacToe.rakumod" + ], + "test": [ + "t/state-of-tic-tac-toe.rakutest" + ], + "example": [ + ".meta/solutions/lib/StateOfTicTacToe.rakumod" + ] + }, + "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/solutions/lib/StateOfTicTacToe.rakumod b/exercises/practice/state-of-tic-tac-toe/.meta/solutions/lib/StateOfTicTacToe.rakumod new file mode 100644 index 00000000..805386a9 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/solutions/lib/StateOfTicTacToe.rakumod @@ -0,0 +1,30 @@ +unit module StateOfTicTacToe; + +enum Player < X O >; +enum State is export < win draw ongoing invalid >; + +multi can-win ( @board ) { + samewith @board, piece => X | O +} + +multi can-win ( @board, :$piece! ) { + $piece x 3 ∈ flat @board, + @board.map( *.comb ).reduce( &[Z~] ), + map *.join, .[0,4,8], .[2,4,6] with @board.join.comb +} + +sub is-invalid ( @board ) { + can-win @board, piece => X & O or + not 0|1 == [-] @board.join.comb.Bag< X O > +} + +sub has-drawn ( @board ) { + @board.join.contains: none ' ' and not can-win @board +} + +sub state-of-tic-tac-toe ( @board ) of State is export { + when is-invalid @board { invalid } + when has-drawn @board { draw } + when can-win @board { win } + default { ongoing } +} diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/solutions/t/state-of-tic-tac-toe.rakutest b/exercises/practice/state-of-tic-tac-toe/.meta/solutions/t/state-of-tic-tac-toe.rakutest new file mode 120000 index 00000000..06634865 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/solutions/t/state-of-tic-tac-toe.rakutest @@ -0,0 +1 @@ +../../../t/state-of-tic-tac-toe.rakutest \ No newline at end of file diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/template-data.yaml b/exercises/practice/state-of-tic-tac-toe/.meta/template-data.yaml new file mode 100644 index 00000000..7686cef8 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/template-data.yaml @@ -0,0 +1,58 @@ +properties: + gamestate: + test: |- + if %case:exists { + sprintf(q:to/END/, %case.Array.raku, %case.raku); + cmp-ok( + state-of-tic-tac-toe(%s), + "eqv", + State::invalid, + %s, + ); + END + } else { + sprintf(q:to/END/, %case.Array.raku, %case, %case.raku); + cmp-ok( + state-of-tic-tac-toe(%s), + "eqv", + State::%s, + %s, + ); + END + } + +unit: module +example: |- + enum Player < X O >; + enum State is export < win draw ongoing invalid >; + + multi can-win ( @board ) { + samewith @board, piece => X | O + } + + multi can-win ( @board, :$piece! ) { + $piece x 3 ∈ flat @board, + @board.map( *.comb ).reduce( &[Z~] ), + map *.join, .[0,4,8], .[2,4,6] with @board.join.comb + } + + sub is-invalid ( @board ) { + can-win @board, piece => X & O or + not 0|1 == [-] @board.join.comb.Bag< X O > + } + + sub has-drawn ( @board ) { + @board.join.contains: none ' ' and not can-win @board + } + + sub state-of-tic-tac-toe ( @board ) of State is export { + when is-invalid @board { invalid } + when has-drawn @board { draw } + when can-win @board { win } + default { ongoing } + } + +stub: |- + enum State is export ; + sub state-of-tic-tac-toe (@board) of State is export { + } 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 00000000..8fc25e21 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml @@ -0,0 +1,101 @@ +# 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" diff --git a/exercises/practice/state-of-tic-tac-toe/lib/StateOfTicTacToe.rakumod b/exercises/practice/state-of-tic-tac-toe/lib/StateOfTicTacToe.rakumod new file mode 100644 index 00000000..0145e99f --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/lib/StateOfTicTacToe.rakumod @@ -0,0 +1,5 @@ +unit module StateOfTicTacToe; + +enum State is export ; +sub state-of-tic-tac-toe (@board) of State is export { +} diff --git a/exercises/practice/state-of-tic-tac-toe/t/state-of-tic-tac-toe.rakutest b/exercises/practice/state-of-tic-tac-toe/t/state-of-tic-tac-toe.rakutest new file mode 100755 index 00000000..b3875026 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/t/state-of-tic-tac-toe.rakutest @@ -0,0 +1,195 @@ +#!/usr/bin/env raku +use Test; +use lib $?FILE.IO.parent(2).add('lib'); +use StateOfTicTacToe; + +cmp-ok( # begin: fe8e9fa9-37af-4d7e-aa24-2f4b8517161a + state-of-tic-tac-toe(["XOO", "X ", "X "]), + "eqv", + State::win, + "Won games: Finished game where X won via left column victory", +); # end: fe8e9fa9-37af-4d7e-aa24-2f4b8517161a + +cmp-ok( # begin: 96c30df5-ae23-4cf6-bf09-5ef056dddea1 + state-of-tic-tac-toe(["OXO", " X ", " X "]), + "eqv", + State::win, + "Won games: Finished game where X won via middle column victory", +); # end: 96c30df5-ae23-4cf6-bf09-5ef056dddea1 + +cmp-ok( # begin: 0d7a4b0a-2afd-4a75-8389-5fb88ab05eda + state-of-tic-tac-toe(["OOX", " X", " X"]), + "eqv", + State::win, + "Won games: Finished game where X won via right column victory", +); # end: 0d7a4b0a-2afd-4a75-8389-5fb88ab05eda + +cmp-ok( # begin: bd1007c0-ec5d-4c60-bb9f-1a4f22177d51 + state-of-tic-tac-toe(["OXX", "OX ", "O "]), + "eqv", + State::win, + "Won games: Finished game where O won via left column victory", +); # end: bd1007c0-ec5d-4c60-bb9f-1a4f22177d51 + +cmp-ok( # begin: c032f800-5735-4354-b1b9-46f14d4ee955 + state-of-tic-tac-toe(["XOX", " OX", " O "]), + "eqv", + State::win, + "Won games: Finished game where O won via middle column victory", +); # end: c032f800-5735-4354-b1b9-46f14d4ee955 + +cmp-ok( # begin: 662c8902-c94a-4c4c-9d9c-e8ca513db2b4 + state-of-tic-tac-toe(["XXO", " XO", " O"]), + "eqv", + State::win, + "Won games: Finished game where O won via right column victory", +); # end: 662c8902-c94a-4c4c-9d9c-e8ca513db2b4 + +cmp-ok( # begin: 2d62121f-7e3a-44a0-9032-0d73e3494941 + state-of-tic-tac-toe(["XXX", "XOO", "O "]), + "eqv", + State::win, + "Won games: Finished game where X won via top row victory", +); # end: 2d62121f-7e3a-44a0-9032-0d73e3494941 + +cmp-ok( # begin: 346527db-4db9-4a96-b262-d7023dc022b0 + state-of-tic-tac-toe(["O ", "XXX", " O "]), + "eqv", + State::win, + "Won games: Finished game where X won via middle row victory", +); # end: 346527db-4db9-4a96-b262-d7023dc022b0 + +cmp-ok( # begin: a013c583-75f8-4ab2-8d68-57688ff04574 + state-of-tic-tac-toe([" OO", "O X", "XXX"]), + "eqv", + State::win, + "Won games: Finished game where X won via bottom row victory", +); # end: a013c583-75f8-4ab2-8d68-57688ff04574 + +cmp-ok( # begin: 2c08e7d7-7d00-487f-9442-e7398c8f1727 + state-of-tic-tac-toe(["OOO", "XXO", "XX "]), + "eqv", + State::win, + "Won games: Finished game where O won via top row victory", +); # end: 2c08e7d7-7d00-487f-9442-e7398c8f1727 + +cmp-ok( # begin: bb1d6c62-3e3f-4d1a-9766-f8803c8ed70f + state-of-tic-tac-toe(["XX ", "OOO", "X "]), + "eqv", + State::win, + "Won games: Finished game where O won via middle row victory", +); # end: bb1d6c62-3e3f-4d1a-9766-f8803c8ed70f + +cmp-ok( # begin: 6ef641e9-12ec-44f5-a21c-660ea93907af + state-of-tic-tac-toe(["XOX", " XX", "OOO"]), + "eqv", + State::win, + "Won games: Finished game where O won via bottom row victory", +); # end: 6ef641e9-12ec-44f5-a21c-660ea93907af + +cmp-ok( # begin: ab145b7b-26a7-426c-ab71-bf418cd07f81 + state-of-tic-tac-toe(["XOO", " X ", " X"]), + "eqv", + State::win, + "Won games: Finished game where X won via falling diagonal victory", +); # end: ab145b7b-26a7-426c-ab71-bf418cd07f81 + +cmp-ok( # begin: 7450caab-08f5-4f03-a74b-99b98c4b7a4b + state-of-tic-tac-toe(["O X", "OX ", "X "]), + "eqv", + State::win, + "Won games: Finished game where X won via rising diagonal victory", +); # end: 7450caab-08f5-4f03-a74b-99b98c4b7a4b + +cmp-ok( # begin: c2a652ee-2f93-48aa-a710-a70cd2edce61 + state-of-tic-tac-toe(["OXX", "OOX", "X O"]), + "eqv", + State::win, + "Won games: Finished game where O won via falling diagonal victory", +); # end: c2a652ee-2f93-48aa-a710-a70cd2edce61 + +cmp-ok( # begin: 5b20ceea-494d-4f0c-a986-b99efc163bcf + state-of-tic-tac-toe([" O", " OX", "OXX"]), + "eqv", + State::win, + "Won games: Finished game where O won via rising diagonal victory", +); # end: 5b20ceea-494d-4f0c-a986-b99efc163bcf + +cmp-ok( # begin: 035a49b9-dc35-47d3-9d7c-de197161b9d4 + state-of-tic-tac-toe(["XXX", "XOO", "XOO"]), + "eqv", + State::win, + "Won games: Finished game where X won via a row and a column victory", +); # end: 035a49b9-dc35-47d3-9d7c-de197161b9d4 + +cmp-ok( # begin: e5dfdeb0-d2bf-4b5a-b307-e673f69d4a53 + state-of-tic-tac-toe(["XOX", "OXO", "XOX"]), + "eqv", + State::win, + "Won games: Finished game where X won via two diagonal victories", +); # end: e5dfdeb0-d2bf-4b5a-b307-e673f69d4a53 + +cmp-ok( # begin: b42ed767-194c-4364-b36e-efbfb3de8788 + state-of-tic-tac-toe(["XOX", "XXO", "OXO"]), + "eqv", + State::draw, + "Drawn games: Draw", +); # end: b42ed767-194c-4364-b36e-efbfb3de8788 + +cmp-ok( # begin: 227a76b2-0fef-4e16-a4bd-8f9d7e4c3b13 + state-of-tic-tac-toe(["XXO", "OXX", "XOO"]), + "eqv", + State::draw, + "Drawn games: Another draw", +); # end: 227a76b2-0fef-4e16-a4bd-8f9d7e4c3b13 + +cmp-ok( # begin: 4d93f15c-0c40-43d6-b966-418b040012a9 + state-of-tic-tac-toe([" ", "X ", " "]), + "eqv", + State::ongoing, + "Ongoing games: Ongoing game: one move in", +); # end: 4d93f15c-0c40-43d6-b966-418b040012a9 + +cmp-ok( # begin: c407ae32-4c44-4989-b124-2890cf531f19 + state-of-tic-tac-toe(["O ", " X ", " "]), + "eqv", + State::ongoing, + "Ongoing games: Ongoing game: two moves in", +); # end: c407ae32-4c44-4989-b124-2890cf531f19 + +cmp-ok( # begin: 199b7a8d-e2b6-4526-a85e-78b416e7a8a9 + state-of-tic-tac-toe(["X ", " XO", "OX "]), + "eqv", + State::ongoing, + "Ongoing games: Ongoing game: five moves in", +); # end: 199b7a8d-e2b6-4526-a85e-78b416e7a8a9 + +cmp-ok( # begin: 1670145b-1e3d-4269-a7eb-53cd327b302e + state-of-tic-tac-toe(["XX ", " ", " "]), + "eqv", + State::invalid, + "Invalid boards: Invalid board: X went twice", +); # end: 1670145b-1e3d-4269-a7eb-53cd327b302e + +cmp-ok( # begin: 47c048e8-b404-4bcf-9e51-8acbb3253f3b + state-of-tic-tac-toe(["OOX", " ", " "]), + "eqv", + State::invalid, + "Invalid boards: Invalid board: O started", +); # end: 47c048e8-b404-4bcf-9e51-8acbb3253f3b + +cmp-ok( # begin: 6c1920f2-ab5c-4648-a0c9-997414dda5eb + state-of-tic-tac-toe(["XXX", "OOO", " "]), + "eqv", + State::invalid, + "Invalid boards: Invalid board: X won and O kept playing", +); # end: 6c1920f2-ab5c-4648-a0c9-997414dda5eb + +cmp-ok( # begin: 4801cda2-f5b7-4c36-8317-3cdd167ac22c + state-of-tic-tac-toe(["XXX", "OOO", "XOX"]), + "eqv", + State::invalid, + "Invalid boards: Invalid board: players kept playing after a win", +); # end: 4801cda2-f5b7-4c36-8317-3cdd167ac22c + +done-testing;