summary refs log tree commit diff
path: root/lib/game
diff options
context:
space:
mode:
authordozens2024-06-20 09:17:06 -0600
committerdozens2024-06-20 09:17:06 -0600
commitc7b2c982004e350f5e3032321baadfc9021b6bad (patch)
tree3f8883c94bed5a1f847cf60cb11207ca53946c67 /lib/game
parentce09973e7cacccdc779f91b8e6e48a520b9f9f4d (diff)
🗄️ big tidy up
- isolate core game logic and move it to src/game.fnl
- main.fnl should be just the ui now
- move all table funcs into lib/table
- move all (1) string funcs into lib/string
- move all game funcs into lib/game/
Diffstat (limited to 'lib/game')
-rw-r--r--lib/game/README5
-rw-r--r--lib/game/all-mills.fnl25
-rw-r--r--lib/game/all-mills.test.fnl41
-rw-r--r--lib/game/index.fnl11
-rw-r--r--lib/game/mill.fnl41
-rw-r--r--lib/game/mill.test.fnl123
-rw-r--r--lib/game/no-moves.fnl26
-rw-r--r--lib/game/no-moves.test.fnl49
-rw-r--r--lib/game/space-is-neighbor.fnl19
-rw-r--r--lib/game/space-is-neighbor.test.fnl20
10 files changed, 360 insertions, 0 deletions
diff --git a/lib/game/README b/lib/game/README
new file mode 100644
index 0000000..6dcd00f
--- /dev/null
+++ b/lib/game/README
@@ -0,0 +1,5 @@
+These are all game specific functions that are big and or complex enough that i
+wanted to break them out into their own modules so i could test them.
+
+when you add a function here, add a test file, and be sure to import/export it
+to/from index.fnl
diff --git a/lib/game/all-mills.fnl b/lib/game/all-mills.fnl
new file mode 100644
index 0000000..e5b3d2b
--- /dev/null
+++ b/lib/game/all-mills.fnl
@@ -0,0 +1,25 @@
+(local {: mill-at? } (require :lib.game.mill))
+(local {: mills } (require :lib.constants))
+
+(fn toggle-player [p] (if (= p 1) 2 1))
+
+(fn only-player-moves [moves player]
+  (icollect [_ move (ipairs moves)] (if (= move player) player 0)))
+
+(fn all-moves-are-mills? [player moves]
+  (accumulate [result true
+               i m (ipairs moves) ]
+              (and result (if (= m 0) true (mill-at? mills moves i)))))
+
+(fn all-mills? [all-moves current-player]
+  (->> current-player
+       (toggle-player)
+       (only-player-moves all-moves)
+       (all-moves-are-mills? current-player)))
+
+{: all-mills?
+ ;; do not use; just for testing:
+ : toggle-player
+ : only-player-moves
+ : all-moves-are-mills?
+ }
diff --git a/lib/game/all-mills.test.fnl b/lib/game/all-mills.test.fnl
new file mode 100644
index 0000000..055f6a5
--- /dev/null
+++ b/lib/game/all-mills.test.fnl
@@ -0,0 +1,41 @@
+(let [{: describe
+       : test-end} (require :lib.test)
+      {: all-mills?
+       : toggle-player
+       : only-player-moves
+       : all-moves-are-mills?
+       } (require :lib.game.all-mills)]
+
+  (describe "# ALL-MILLS" (fn []
+    (describe "toggle-player()" (fn [t]
+      (t {:given "a player"
+          :should "return the next"
+          :expected 2
+          :actual (toggle-player 1)
+          })))
+    (describe "only-player-moves()" (fn [t]
+      (let [moves    [ 0 2 0 2 2 2 0 0 0 0 0 0 0 2 0 0 0 2 0 2 0 1 1 1 ]
+            expected [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
+            ]
+        (t {:given "a bunch of moves and a player"
+            :should "filter out all the moves not belonging to the player"
+            : expected
+            :actual (only-player-moves moves 1)
+            }))))
+    (describe "all-moves-are-mills?()" (fn [t]
+      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 ]
+            ]
+        (t {:given "a bunch of moves and a player"
+            :should "return true if all the player moves are mills"
+            :expected true
+            :actual (all-moves-are-mills? 1 moves)
+            }))
+      (let [moves [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 ]
+            ]
+        (t {:given "a bunch of moves and no mill and a player"
+            :should "return false"
+            :expected false
+            :actual (all-moves-are-mills? 1 moves)
+            }))))
+    (test-end))))
+
diff --git a/lib/game/index.fnl b/lib/game/index.fnl
new file mode 100644
index 0000000..f542f76
--- /dev/null
+++ b/lib/game/index.fnl
@@ -0,0 +1,11 @@
+(local {: all-mills?} (require :lib.game.all-mills))
+(local {: mill-at?} (require :lib.game.mill))
+(local {: space-is-neighbor?} (require :lib.game.space-is-neighbor))
+(local {: no-moves?} (require :lib.game.no-moves))
+
+{
+ : all-mills?
+ : mill-at?
+ : no-moves?
+ : space-is-neighbor?
+ }
diff --git a/lib/game/mill.fnl b/lib/game/mill.fnl
new file mode 100644
index 0000000..d15b53e
--- /dev/null
+++ b/lib/game/mill.fnl
@@ -0,0 +1,41 @@
+(local {: contains} (require :lib.table))
+
+(fn get-candidates [all-mills next-move]
+  "a list of mills that contain next-move"
+  (icollect [_ mill (ipairs all-mills)]
+    (if (contains mill next-move) mill)))
+
+(fn any [t]
+  "take a list of booleans, returns true if any of them are true"
+  (accumulate [acc false
+               i x (ipairs t)]
+               (or acc x)))
+
+(fn move-mills [moves-list]
+  (icollect [_ moves (ipairs moves-list)]
+    (let [player (. moves 1)]
+      (accumulate [acc true
+                   _ m (ipairs moves)]
+                  (and acc (not= m 0) (= player m))))))
+
+(fn candidate-moves [moves candidates]
+  "Just turning board spaces into player moves"
+  (icollect [_ spaces (ipairs candidates)]
+            (icollect [_ space (ipairs spaces)]
+                      (. moves space))))
+
+(fn mill-at? [all-mills current-moves move]
+  "Is there a mill at this move?"
+  (->> (get-candidates all-mills move)
+       (candidate-moves current-moves) 
+       (move-mills)
+       (any)))
+
+{: mill-at?
+ ;; not for consumption,
+ ;; just for testing:
+ : get-candidates
+ : candidate-moves
+ : move-mills
+ : any
+ }
diff --git a/lib/game/mill.test.fnl b/lib/game/mill.test.fnl
new file mode 100644
index 0000000..604c759
--- /dev/null
+++ b/lib/game/mill.test.fnl
@@ -0,0 +1,123 @@
+(let [{: describe
+       : test-end} (require :lib.test)
+      {: mill-at?
+       : get-candidates
+       : move-mills
+       : candidate-moves
+       : any
+       } (require :lib.game.mill)
+      {: mills } (require :lib.constants)
+      with-mills (partial mill-at? mills)]
+
+  (describe "# MILL" (fn []
+    (describe "get-candidates()" (fn [t]
+        (t
+          (let [move 3
+                expected [[1 2 3] [3 15 24]]
+                moves [ 1 1 0 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] ]
+            {:given (string.format "a move of %d" move)
+            :should "return [[1 2 3] [3 15 24]]" 
+            : expected 
+            :actual (get-candidates mills move)
+            }))
+        (t
+          (let [move 1
+                expected [[1 2 3] [1 10 22]]
+                moves [ 0 0 0 ] ]
+            {:given (string.format "a move of %d" move)
+            :should "return [[1 2 3] [1 10 22]]" 
+            : expected 
+            :actual (get-candidates mills move) }))
+        (t
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                expected [[1 2 3] [1 10 22]]
+               ]
+            {:given (string.format "a move of %d" move)
+            :should "still return [[1 2 3] [1 10 22]]" 
+            : expected 
+            :actual (get-candidates mills move) }))))
+        
+    (describe "any()" (fn [t]
+        (t {:given "a table of false false true"
+            :should "return true"
+            :expected true
+            :actual (any [false false true]) })
+        (t {:given "a table of true false"
+            :should "return true"
+            :expected true
+            :actual (any [true false]) })
+        (t {:given "a single false"
+            :should "return false"
+            :expected false
+            :actual (any [false]) })
+        (t {:given "a single true"
+            :should "return true"
+            :expected true
+            :actual (any [true]) })))
+
+    (describe "move-mills()" (fn [t]
+      (t
+        (let [moves [[1 1 1] [0 2 2]] ]
+          {:given "a list of moves"
+           :should "turn them into true/false if they are mills"
+           :expected [true false]
+           :actual (move-mills moves) }))
+      (t
+        (let [moves [[0 1 1] [0 2 2]] ]
+          {:given "no mills"
+           :should "should return false"
+           :expected [false false]
+           :actual (move-mills moves) }))
+      (t
+        (let [moves [[2 2 2] [2 0 0]] ]
+          {:given "mill, no mill"
+           :should "should return true false"
+           :expected [true false]
+           :actual (move-mills moves) }))))
+
+    (describe "candidate-moves()" (fn [t]
+      (t (let [spaces [[1 2 3] [1 10 22]]
+               moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ]
+         {:given "spaces [[1 2 3] [1 10 22]]"
+          :should "map to moves"
+          :expected [[2 2 2] [2 0 0]]
+          :actual (candidate-moves moves spaces)}))))
+
+    (describe "mill-at?()" (fn [t]
+        (t 
+          (let [move 1
+                moves [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] ]
+            {:given "no mills"
+             :should "return false"
+             :expected false
+             :actual (mill-at? mills moves move)}))
+        (t 
+          (let [move 4
+                moves [1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill but not at Move"
+             :should "return false"
+             :expected false
+             :actual (with-moves move)}))
+        (t 
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill"
+             :should "return true"
+             :expected true
+             :actual (with-moves move) }))
+        (t 
+          (let [move 1
+                moves [2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
+                with-mills (partial mill-at? mills) 
+                with-moves (partial with-mills moves)]
+            {:given "a mill"
+             :should "return the opposite of false"
+             :expected false
+             :actual (not (with-moves move)) }))))
+
+    (test-end))))
diff --git a/lib/game/no-moves.fnl b/lib/game/no-moves.fnl
new file mode 100644
index 0000000..02482cc
--- /dev/null
+++ b/lib/game/no-moves.fnl
@@ -0,0 +1,26 @@
+(local {: tail} (require :lib.table))
+
+(fn get-player-idxs [player moves]
+  (icollect [i p (ipairs moves)] (when (= p player) i)))
+
+(fn idx-to-neighbors [idxs all-neighbors]
+  (icollect [_ i (ipairs idxs)] (tail (. all-neighbors i))))
+
+(fn neighbor-is-occupied? [neighbors moves]
+  (icollect [_ move (ipairs neighbors)]
+    (icollect [_ neighbor (ipairs move)]
+      (not= (. moves neighbor) 0))))
+
+(fn reduce-to-bool [xs]
+  (accumulate [acc true
+               _ x (ipairs xs)]
+               (and x)))
+
+(fn no-moves? [neighbors all-moves player]
+  (-> (get-player-idxs player all-moves)
+    (idx-to-neighbors neighbors)
+    (neighbor-is-occupied? all-moves)
+    (reduce-to-bool)
+    (reduce-to-bool)))
+
+{: no-moves? }
diff --git a/lib/game/no-moves.test.fnl b/lib/game/no-moves.test.fnl
new file mode 100644
index 0000000..a94d60a
--- /dev/null
+++ b/lib/game/no-moves.test.fnl
@@ -0,0 +1,49 @@
+(let [{: no-moves?} (require :lib.game.no-moves)
+      {: neighbors} (require :lib.constants)
+      {: describe : test-end} (require :lib.test)
+      with-neighbors (partial no-moves? neighbors)
+      ]
+
+  (describe "# NOMOVES" (fn [t]
+    (let [moves [ 1 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "one move with no moves"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "one move with one move"
+            :should "return false"
+            :expected false
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 1 1 0 2 0 0 0 0 2 0 0 0 0 2 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "several moves with no moves"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 0 2 0 2 1 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]
+          player 1
+          ]
+        (t {:given "four occupied neighbors"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+    (let [moves [ 1 2 1 2 0 2 1 2 1 2 1 0 1 2 1 2 2 2 0 1 0 0 0 0 0 ]
+          player 2
+          ]
+        (t {:given "this turn that is giving me trouble"
+            :should "return true"
+            :expected true
+            :actual (with-neighbors moves player)
+            }))
+
+    (test-end))))
diff --git a/lib/game/space-is-neighbor.fnl b/lib/game/space-is-neighbor.fnl
new file mode 100644
index 0000000..373feaf
--- /dev/null
+++ b/lib/game/space-is-neighbor.fnl
@@ -0,0 +1,19 @@
+(local {: contains
+        : head
+        : tail
+        } (require :lib.table))
+
+(lambda space-is-neighbor? [all-neighbors from to]
+  ;; i have learned to check that i'm passing the correct type of move
+  ;; i.e. a number and not a string
+  (assert (= "number" (type from)) "from must be a number")
+  (assert (= "number" (type to)) "to must be a number")
+  (assert (= "table" (type all-neighbors)) "all-neighbors must be a table")
+
+  (let [neighborhood-list (icollect [_ n (ipairs all-neighbors)] (if (= from (head n)) n))
+        neighborhood (head neighborhood-list)
+        neighbors (tail neighborhood)
+        is-neighbor (contains neighbors to)]
+    is-neighbor))
+
+{: space-is-neighbor?}
diff --git a/lib/game/space-is-neighbor.test.fnl b/lib/game/space-is-neighbor.test.fnl
new file mode 100644
index 0000000..0ae7d4e
--- /dev/null
+++ b/lib/game/space-is-neighbor.test.fnl
@@ -0,0 +1,20 @@
+(let [{: space-is-neighbor?} (require :lib.game.space-is-neighbor)
+      {: neighbors} (require :lib.constants)
+      {: describe : test-end} (require :lib.test)
+      with-neighbors (partial space-is-neighbor? neighbors) ]
+
+  (describe "# SPACE-IS-NEIGHBOR" (fn [t]
+    (t {:given "space of 3"
+        :should "know 2 is a neighbor"
+        :expected true
+        :actual  (with-neighbors 3 2)})
+    (t {:given "space of 3"
+        :should "know 15 is a neighbor"
+        :expected true
+        :actual  (with-neighbors 3 15)})
+    (t {:given "space of 3"
+        :should "know 1 is not a neighbor"
+        :expected false
+        :actual  (with-neighbors 3 1)})
+
+    (test-end))))