From 99015bf9e3a1df111780126a4fb197216e5622aa Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Thu, 26 Mar 2026 18:21:38 +1100 Subject: [PATCH] Add `connect` exercise --- config.json | 8 + .../practice/connect/.docs/instructions.md | 27 ++++ exercises/practice/connect/.meta/config.json | 17 ++ exercises/practice/connect/.meta/tests.toml | 46 ++++++ exercises/practice/connect/dub.sdl | 2 + exercises/practice/connect/example/connect.d | 78 +++++++++ exercises/practice/connect/source/connect.d | 151 ++++++++++++++++++ 7 files changed, 329 insertions(+) create mode 100644 exercises/practice/connect/.docs/instructions.md create mode 100644 exercises/practice/connect/.meta/config.json create mode 100644 exercises/practice/connect/.meta/tests.toml create mode 100644 exercises/practice/connect/dub.sdl create mode 100644 exercises/practice/connect/example/connect.d create mode 100644 exercises/practice/connect/source/connect.d diff --git a/config.json b/config.json index 685bb9e..822ec9a 100644 --- a/config.json +++ b/config.json @@ -859,6 +859,14 @@ "prerequisites": [], "difficulty": 7 }, + { + "slug": "connect", + "name": "Connect", + "uuid": "087ca9a0-0ef4-4406-8a9a-bcd338ebf782", + "practices": [], + "prerequisites": [], + "difficulty": 8 + }, { "slug": "zebra-puzzle", "name": "Zebra Puzzle", diff --git a/exercises/practice/connect/.docs/instructions.md b/exercises/practice/connect/.docs/instructions.md new file mode 100644 index 0000000..7f34bfa --- /dev/null +++ b/exercises/practice/connect/.docs/instructions.md @@ -0,0 +1,27 @@ +# Instructions + +Compute the result for a game of Hex / Polygon. + +The abstract boardgame known as [Hex][hex] / Polygon / CON-TAC-TIX is quite simple in rules, though complex in practice. +Two players place stones on a parallelogram with hexagonal fields. +The player to connect his/her stones to the opposite side first wins. +The four sides of the parallelogram are divided between the two players (i.e. one player gets assigned a side and the side directly opposite it and the other player gets assigned the two other sides). + +Your goal is to build a program that given a simple representation of a board computes the winner (or lack thereof). +Note that all games need not be "fair". +(For example, players may have mismatched piece counts or the game's board might have a different width and height.) + +The boards look like this: + +```text +. O . X . + . X X O . + O O O X . + . X O X O + X O O O X +``` + +"Player `O`" plays from top to bottom, "Player `X`" plays from left to right. +In the above example `O` has made a connection from left to right but nobody has won since `O` didn't connect top and bottom. + +[hex]: https://en.wikipedia.org/wiki/Hex_%28board_game%29 diff --git a/exercises/practice/connect/.meta/config.json b/exercises/practice/connect/.meta/config.json new file mode 100644 index 0000000..4173e18 --- /dev/null +++ b/exercises/practice/connect/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "source/connect.d" + ], + "test": [ + "source/connect.d" + ], + "example": [ + "example/connect.d" + ] + }, + "blurb": "Compute the result for a game of Hex / Polygon." +} diff --git a/exercises/practice/connect/.meta/tests.toml b/exercises/practice/connect/.meta/tests.toml new file mode 100644 index 0000000..951b87e --- /dev/null +++ b/exercises/practice/connect/.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. + +[6eff0df4-3e92-478d-9b54-d3e8b354db56] +description = "an empty board has no winner" + +[298b94c0-b46d-45d8-b34b-0fa2ea71f0a4] +description = "X can win on a 1x1 board" + +[763bbae0-cb8f-4f28-bc21-5be16a5722dc] +description = "O can win on a 1x1 board" + +[819fde60-9ae2-485e-a024-cbb8ea68751b] +description = "only edges does not make a winner" + +[2c56a0d5-9528-41e5-b92b-499dfe08506c] +description = "illegal diagonal does not make a winner" + +[41cce3ef-43ca-4963-970a-c05d39aa1cc1] +description = "nobody wins crossing adjacent angles" + +[cd61c143-92f6-4a8d-84d9-cb2b359e226b] +description = "X wins crossing from left to right" + +[495e33ed-30a9-4012-b46e-d7c4d5fe13c3] +description = "X wins with left-hand dead end fork" + +[ab167ab0-4a98-4d0f-a1c0-e1cddddc3d58] +description = "X wins with right-hand dead end fork" + +[73d1eda6-16ab-4460-9904-b5f5dd401d0b] +description = "O wins crossing from top to bottom" + +[c3a2a550-944a-4637-8b3f-1e1bf1340a3d] +description = "X wins using a convoluted path" + +[17e76fa8-f731-4db7-92ad-ed2a285d31f3] +description = "X wins using a spiral path" diff --git a/exercises/practice/connect/dub.sdl b/exercises/practice/connect/dub.sdl new file mode 100644 index 0000000..4ed0433 --- /dev/null +++ b/exercises/practice/connect/dub.sdl @@ -0,0 +1,2 @@ +name "connect" +buildRequirements "disallowDeprecations" diff --git a/exercises/practice/connect/example/connect.d b/exercises/practice/connect/example/connect.d new file mode 100644 index 0000000..8a85aff --- /dev/null +++ b/exercises/practice/connect/example/connect.d @@ -0,0 +1,78 @@ +module connect; + +pure string winner(immutable string[] board) +{ + immutable size_t rows = board.length; + immutable size_t columns = (board[0].length + 1) / 2; + immutable size_t n = rows * columns; + + size_t[] parents; + parents.length = n + 4; + foreach (i; 0 .. n + 4) + parents[i] = i; + + size_t root(size_t node) + { + size_t current = node; + while (parents[current] != current) + { + parents[current] = parents[parents[current]]; + current = parents[current]; + } + return current; + } + + char occupant(size_t row, size_t column) + { + if (row < rows) + return board[row][row + 2 * column]; + else if (column < 2) + return 'O'; // 0 = top, 1 = bottom + else + return 'X'; // 2 = left, 3 = right + } + + void markAdjacent(size_t row1, size_t col1, size_t row2, size_t col2) + { + if (occupant(row1, col1) != occupant(row2, col2)) + return; + + immutable size_t root1 = root(row1 * columns + col1); + immutable size_t root2 = root(row2 * columns + col2); + parents[root2] = root1; + } + + // Connect edge virtual nodes + foreach (j; 0 .. columns) + { + markAdjacent(0, j, rows, 0); // top edge + markAdjacent(rows - 1, j, rows, 1); // bottom edge + } + foreach (i; 0 .. rows) + { + markAdjacent(i, 0, rows, 2); // left edge + markAdjacent(i, columns - 1, rows, 3); // right edge + } + + // Connect horizontal neighbors + foreach (i; 0 .. rows) + foreach (j; 0 .. columns - 1) + markAdjacent(i, j, i, j + 1); + + // Connect diagonal \ neighbors + foreach (i; 0 .. rows - 1) + foreach (j; 0 .. columns) + markAdjacent(i, j, i + 1, j); + + // Connect diagonal / neighbors + foreach (i; 0 .. rows - 1) + foreach (j; 0 .. columns - 1) + markAdjacent(i, j + 1, i + 1, j); + + if (root(n + 0) == root(n + 1)) + return "O"; + else if (root(n + 2) == root(n + 3)) + return "X"; + else + return ""; +} diff --git a/exercises/practice/connect/source/connect.d b/exercises/practice/connect/source/connect.d new file mode 100644 index 0000000..8e7c9d7 --- /dev/null +++ b/exercises/practice/connect/source/connect.d @@ -0,0 +1,151 @@ +module connect; + +pure string winner(immutable string[] board) +{ + // implement this function +} + +unittest +{ + immutable int allTestsEnabled = 0; + + // An empty board has no winner + { + immutable string[] board = [ + ". . . . .", + " . . . . .", + " . . . . .", + " . . . . .", + " . . . . .", + ]; + assert(winner(board) == ""); + } + + static if (allTestsEnabled) + { + // X can win on a 1x1 board + { + immutable string[] board = [ + "X", + ]; + assert(winner(board) == "X"); + } + + // O can win on a 1x1 board + { + immutable string[] board = [ + "O", + ]; + assert(winner(board) == "O"); + } + + // Only edges does not make a winner + { + immutable string[] board = [ + "O O O X", + " X . . X", + " X . . X", + " X O O O", + ]; + assert(winner(board) == ""); + } + + // Illegal diagonal does not make a winner + { + immutable string[] board = [ + "X O . .", + " O X X X", + " O X O .", + " . O X .", + " X X O O", + ]; + assert(winner(board) == ""); + } + + // Nobody wins crossing adjacent angles + { + immutable string[] board = [ + "X . . .", + " . X O .", + " O . X O", + " . O . X", + " . . O .", + ]; + assert(winner(board) == ""); + } + + // X wins crossing from left to right + { + immutable string[] board = [ + ". O . .", + " O X X X", + " O X O .", + " X X O X", + " . O X .", + ]; + assert(winner(board) == "X"); + } + + // X wins with left-hand dead end fork + { + immutable string[] board = [ + ". . X .", + " X X . .", + " . X X X", + " O O O O", + ]; + assert(winner(board) == "X"); + } + + // X wins with right-hand dead end fork + { + immutable string[] board = [ + ". . X X", + " X X . .", + " . X X .", + " O O O O", + ]; + assert(winner(board) == "X"); + } + + // O wins crossing from top to bottom + { + immutable string[] board = [ + ". O . .", + " O X X X", + " O O O .", + " X X O X", + " . O X .", + ]; + assert(winner(board) == "O"); + } + + // X wins using a convoluted path + { + immutable string[] board = [ + ". X X . .", + " X . X . X", + " . X . X .", + " . X X . .", + " O O O O O", + ]; + assert(winner(board) == "X"); + } + + // X wins using a spiral path + { + immutable string[] board = [ + "O X X X X X X X X", + " O X O O O O O O O", + " O X O X X X X X O", + " O X O X O O O X O", + " O X O X X X O X O", + " O X O O O X O X O", + " O X X X X X O X O", + " O O O O O O O X O", + " X X X X X X X X O", + ]; + assert(winner(board) == "X"); + } + } +}