Skip to content

Commit

Permalink
Add connect exercise (#803)
Browse files Browse the repository at this point in the history
  • Loading branch information
tasxatzial authored Jan 29, 2025
1 parent 6ff3b6d commit 8918e4e
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 0 deletions.
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,18 @@
"conditionals"
]
},
{
"slug": "connect",
"name": "Connect",
"uuid": "7e3e449d-4c2f-4b06-89b4-a9d9efe9e68f",
"practices": [],
"prerequisites": [
"strings",
"conditionals",
"vectors"
],
"difficulty": 8
},
{
"slug": "zebra-puzzle",
"name": "Zebra Puzzle",
Expand Down
27 changes: 27 additions & 0 deletions exercises/practice/connect/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions exercises/practice/connect/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"tasxatzial"
],
"files": {
"solution": [
"src/connect.clj"
],
"test": [
"test/connect_test.clj"
],
"example": [
".meta/example.clj"
]
},
"blurb": "Compute the result for a game of Hex / Polygon."
}
139 changes: 139 additions & 0 deletions exercises/practice/connect/.meta/example.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
(ns connect)

(defn find-input-dimensions
[input-board]
(let [rows (count input-board)
columns (/ (inc (.length (first input-board))) 2)]
{:rows rows
:columns columns
:max-pos (* rows columns)}))

(defn parse-value
[value]
(if (= value ".")
{:value :E}
{:value (keyword value)}))

(defn create-values
[input-board]
(let [dimensions (find-input-dimensions input-board)]
(->> input-board
(apply str)
(re-seq #"[XO.]")
(map parse-value)
(zipmap (range 1 (inc (:max-pos dimensions)))))))

(defn connect-positions
[board pos1 pos2]
(-> board
(update-in [pos1 :neighbors] #(conj % pos2))
(update-in [pos2 :neighbors] #(conj % pos1))))

(defn on-right-edge?
[pos dimensions]
(zero? (rem pos (:columns dimensions))))

(defn on-bottom-edge?
[pos dimensions]
(> pos (- (:max-pos dimensions) (:columns dimensions))))

(defn on-left-edge?
[pos dimensions]
(zero? (rem (dec pos) (:columns dimensions))))

(defn connect-right
[board pos dimensions]
(if (on-right-edge? pos dimensions)
board
(connect-positions board pos (inc pos))))

(defn connect-down-right
[board pos dimensions]
(if (on-bottom-edge? pos dimensions)
board
(connect-positions board pos (+ pos (:columns dimensions)))))

(defn connect-down-left
[board pos dimensions]
(if (or (on-left-edge? pos dimensions)
(on-bottom-edge? pos dimensions))
board
(connect-positions board pos (+ pos (dec (:columns dimensions))))))

(defn create-position-connections
[board pos dimensions]
(reduce #(%2 %1 pos dimensions)
board [connect-right connect-down-left connect-down-right]))

(defn create-connections
[input-board]
(let [dimensions (find-input-dimensions input-board)]
(reduce #(create-position-connections %1 %2 dimensions)
{} (range 1 (inc (:max-pos dimensions))))))

(defn create-board
[input-board]
(let [connections (create-connections input-board)
values (create-values input-board)]
(merge-with into values connections)))

(defn path?
[board start-pos final-positions]
(let [start-value (:value (get board start-pos))]
(if (= start-value :E)
false
(loop [positions-to-visit [start-pos]
visited #{}]
(if (empty? positions-to-visit)
false
(let [current-position (peek positions-to-visit)
remaining-positions (pop positions-to-visit)
new-visited (conj visited current-position)
neighbors (:neighbors (get board current-position))
valid-neighbors (filter #(and (not (visited %))
(= start-value (:value (get board %))))
neighbors)]
(cond
(contains? final-positions current-position) true
(visited current-position) (recur remaining-positions visited)
:else (recur (into remaining-positions valid-neighbors) new-visited))))))))

(defn any-path?
[board start-positions final-positions]
(boolean (some #(path? board % final-positions) start-positions)))

(defn X-edge-positions
[dimensions]
(let [{:keys [columns max-pos]} dimensions]
{:start-positions (range 1 (inc max-pos) columns)
:end-positions (range columns (inc max-pos) columns)}))

(defn O-edge-positions
[dimensions]
(let [{:keys [columns max-pos]} dimensions]
{:start-positions (range 1 (inc columns))
:end-positions (range (inc (- max-pos columns)) (inc max-pos))}))

(def type->edge-positions-fn
{:X X-edge-positions
:O O-edge-positions})

(defn filter-positions-by-type
[board positions type]
(filter #(= type (:value (get board %))) positions))

(defn wins?
[type board dimensions]
(let [edge-positions-fn (type->edge-positions-fn type)
{:keys [start-positions end-positions]} (edge-positions-fn dimensions)
starting-type-positions (filter-positions-by-type board start-positions type)]
(any-path? board starting-type-positions (set end-positions))))

(defn connect-winner
[input-board]
(let [board (create-board input-board)
dimensions (find-input-dimensions input-board)]
(cond
(wins? :X board dimensions) :X
(wins? :O board dimensions) :O
:else :no-winner)))
10 changes: 10 additions & 0 deletions exercises/practice/connect/.meta/generator.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns connect-generator
(:require [hbs.helper :refer [safe-str]]))

(defn- update-expected [expected]
(if (= "" expected)
(safe-str :no-winner)
(safe-str (keyword expected))))

(defn update-test-case [test-case]
(update test-case :expected update-expected))
13 changes: 13 additions & 0 deletions exercises/practice/connect/.meta/generator.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(ns connect-test
(:require [clojure.test :refer [deftest testing is]]
connect))

{{#test_cases.winner}}
(deftest connect-winner_test_{{idx}}
(testing {{description}}
(is (= {{expected}}
(connect/connect-winner
[{{#input.board~}}
{{.}}
{{/input.board}}])))))
{{/test_cases.winner}}
40 changes: 40 additions & 0 deletions exercises/practice/connect/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 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"

[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"
6 changes: 6 additions & 0 deletions exercises/practice/connect/deps.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{:aliases {:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner.git"
:sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}}}
4 changes: 4 additions & 0 deletions exercises/practice/connect/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(defproject connect "0.1.0-SNAPSHOT"
:description "connect exercise."
:url "https://github.com/exercism/clojure/tree/main/exercises/practice/connect"
:dependencies [[org.clojure/clojure "1.12.0"]])
6 changes: 6 additions & 0 deletions exercises/practice/connect/src/connect.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(ns connect)

(defn connect-winner
"Returns the winner of the given connect board."
[board]
)
98 changes: 98 additions & 0 deletions exercises/practice/connect/test/connect_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
(ns connect-test
(:require [clojure.test :refer [deftest testing is]]
connect))

(deftest connect-winner_test_1
(testing "an empty board has no winner"
(is (= :no-winner
(connect/connect-winner
[". . . . ."
" . . . . ."
" . . . . ."
" . . . . ."
" . . . . ."])))))

(deftest connect-winner_test_2
(testing "X can win on a 1x1 board"
(is (= :X
(connect/connect-winner
["X"])))))

(deftest connect-winner_test_3
(testing "O can win on a 1x1 board"
(is (= :O
(connect/connect-winner
["O"])))))

(deftest connect-winner_test_4
(testing "only edges does not make a winner"
(is (= :no-winner
(connect/connect-winner
["O O O X"
" X . . X"
" X . . X"
" X O O O"])))))

(deftest connect-winner_test_5
(testing "illegal diagonal does not make a winner"
(is (= :no-winner
(connect/connect-winner
["X O . ."
" O X X X"
" O X O ."
" . O X ."
" X X O O"])))))

(deftest connect-winner_test_6
(testing "nobody wins crossing adjacent angles"
(is (= :no-winner
(connect/connect-winner
["X . . ."
" . X O ."
" O . X O"
" . O . X"
" . . O ."])))))

(deftest connect-winner_test_7
(testing "X wins crossing from left to right"
(is (= :X
(connect/connect-winner
[". O . ."
" O X X X"
" O X O ."
" X X O X"
" . O X ."])))))

(deftest connect-winner_test_8
(testing "O wins crossing from top to bottom"
(is (= :O
(connect/connect-winner
[". O . ."
" O X X X"
" O O O ."
" X X O X"
" . O X ."])))))

(deftest connect-winner_test_9
(testing "X wins using a convoluted path"
(is (= :X
(connect/connect-winner
[". X X . ."
" X . X . X"
" . X . X ."
" . X X . ."
" O O O O O"])))))

(deftest connect-winner_test_10
(testing "X wins using a spiral path"
(is (= :X
(connect/connect-winner
["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"])))))

0 comments on commit 8918e4e

Please sign in to comment.