Repository: exercism/clojure
Pull request #255 (2018-10-01 20:38:32)

add go-counting


Patch:
diff --git a/config.json b/config.json
index 192e09e..813def4 100644
--- a/config.json
+++ b/config.json
@@ -640,6 +640,14 @@
       "unlocked_by": null,
       "difficulty": 1,
       "topics": null
+    },
+    {
+      "slug": "go-counting",
+      "uuid": "987438e8-1db3-447a-a243-e67c7591709d",
+      "core": false,
+      "unlocked_by": null,
+      "difficulty": 1,
+      "topics": null
     }
   ]
 }
diff --git a/exercises/go-counting/README.md b/exercises/go-counting/README.md
new file mode 100644
index 0000000..f7d9cd4
--- /dev/null
+++ b/exercises/go-counting/README.md
@@ -0,0 +1,40 @@
+# Go Counting
+
+Count the scored points on a Go board.
+
+In the game of go (also known as baduk, igo, cờ vây and wéiqí) points
+are gained by completely encircling empty intersections with your
+stones. The encircled intersections of a player are known as its
+territory.
+
+Write a function that determines the territory of each player. You may
+assume that any stones that have been stranded in enemy territory have
+already been taken off the board.
+
+Write a function that determines the territory which includes a specified coordinate.
+
+Multiple empty intersections may be encircled at once and for encircling
+only horizontal and vertical neighbours count. In the following diagram
+the stones which matter are marked "O" and the stones that don't are
+marked "I" (ignored).  Empty spaces represent empty intersections.
+
+```text
++----+
+|IOOI|
+|O  O|
+|O OI|
+|IOI |
++----+
+```
+
+To be more precise an empty intersection is part of a player's territory
+if all of its neighbours are either stones of that player or empty
+intersections that are part of that player's territory.
+
+## Source
+
+[wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29)
+[Sensei's Library](http://senseis.xmp.net/).
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
diff --git a/exercises/go-counting/project.clj b/exercises/go-counting/project.clj
new file mode 100644
index 0000000..c7399d3
--- /dev/null
+++ b/exercises/go-counting/project.clj
@@ -0,0 +1,4 @@
+(defproject go-counting "0.1.0-SNAPSHOT"
+  :description "go-counting exercise."
+  :url "https://github.com/exercism/clojure/tree/master/exercises/go-counting"
+  :dependencies [[org.clojure/clojure "1.9.0"]])
diff --git a/exercises/go-counting/src/example.clj b/exercises/go-counting/src/example.clj
new file mode 100644
index 0000000..a3c9dea
--- /dev/null
+++ b/exercises/go-counting/src/example.clj
@@ -0,0 +1,50 @@
+(ns go-counting)
+
+(defn grid->graph [xs]
+  (->> (for [x (-> xs count range), y (-> xs first count range)]
+         (case (get-in xs [x y])
+           \W     [[y x] :white]
+           \B     [[y x] :black]
+           \space [[y x] :free]))
+       (into {})))
+
+(defn neighbors [pred stones [x y]]
+  (->> [[0 1] [0 -1] [1 0] [-1 0]]
+       (map (fn [[j k]] [(+ x j) (+ y k)]))
+       (filter (comp pred stones))))
+
+(defn territory-of [stones [x y]]
+  (if (= :free (stones [x y]))
+    (letfn [(f [[seen frontier]]
+              (let [nseen (reduce conj seen frontier)]
+                [nseen (->> frontier
+                            (mapcat #(neighbors #{:free} stones %))
+                            (filter (complement nseen)))]))]
+      (->> [#{} [[x y]]]
+           (iterate f)
+           (drop-while (comp seq second))
+           (ffirst)))
+    #{}))  
+
+(defn territory-owner [stones territory]
+  (->> territory
+       (mapcat (partial neighbors  #{:black :white} stones))
+       (map stones)
+       (#(cond (empty? %)                    nil
+               (every? (partial = :black) %) :black
+               (every? (partial = :white) %) :white
+               :else                         nil))))
+
+(defn territory [grid [x y]]
+  (let [stones    (grid->graph grid)
+        territory (territory-of stones [x y])]
+    (if (nil? (stones [x y]))
+      (throw (Throwable. "Invalid coordinate!"))
+      {:stones territory :owner (territory-owner stones territory)})))
+
+(defn territories [grid]
+  (let [territories   (->> grid grid->graph keys (map (partial territory grid)))
+        territory-for #(->> territories (filter (comp % :owner)) (map :stones) (reduce concat) set)]
+    {:black-territory (territory-for (partial = :black)) 
+     :white-territory (territory-for (partial = :white))
+     :null-territory  (territory-for nil?)}))
diff --git a/exercises/go-counting/src/go_counting.clj b/exercises/go-counting/src/go_counting.clj
new file mode 100644
index 0000000..26e8c2a
--- /dev/null
+++ b/exercises/go-counting/src/go_counting.clj
@@ -0,0 +1,5 @@
+(ns go-counting)
+
+(defn territory [grid [x y]])
+
+(defn territories [grid])


Test patch:
diff --git a/exercises/go-counting/test/go_counting_test.clj b/exercises/go-counting/test/go_counting_test.clj
new file mode 100644
index 0000000..04f8f7a
--- /dev/null
+++ b/exercises/go-counting/test/go_counting_test.clj
@@ -0,0 +1,38 @@
+(ns go-counting-test
+  (:require [clojure.test :refer [deftest is]]
+            [go-counting :as g]))
+
+(def example
+  ["  B  "
+   " B B "
+   "B W B"
+   " W W "
+   "  W  "])
+
+(deftest territory
+  (is (= (g/territory example [0 1])
+         {:stones #{[0 0] [0 1] [1 0]} :owner :black}))
+  (is (= (g/territory example [2 3])
+         {:stones #{[2 3]} :owner :white}))
+  (is (= (g/territory example [1 4])
+         {:stones #{[0 3] [0 4] [1 4]} :owner nil}))
+  (is (= (g/territory example [1 1])
+         {:stones #{} :owner nil}))
+  (is (thrown? Throwable (g/territory example [-1 1])))
+  (is (thrown? Throwable (g/territory example [5 1])))
+  (is (thrown? Throwable (g/territory example [1 -1])))
+  (is (thrown? Throwable (g/territory example [1 5]))))
+
+(deftest territories
+  (is (= (g/territories [" "])
+         {:black-territory #{}
+          :white-territory #{}
+          :null-territory  #{[0 0]}}))
+  (is (= (g/territories [" BW " " BW "])
+         {:black-territory #{[0 0] [0 1]}
+          :white-territory #{[3 0] [3 1]}
+          :null-territory  #{}}))
+  (is (= (g/territories [" B "])
+         {:black-territory #{[0 0] [2 0]}
+          :white-territory #{}
+          :null-territory  #{}})))