summary refs log tree commit diff
diff options
context:
space:
mode:
authordozens2024-06-06 22:49:52 -0600
committerdozens2024-06-06 22:49:52 -0600
commitf985dc4e5c9fdec06436c21440c3dc7245369847 (patch)
tree336d7287757c54a66824ed03c7531f23122a33f2
parent7776b2011a2585723078b275c838fd7332488d76 (diff)
feat: add moving phase
also moves `moves` into the game object and moves updating `moves` to
game:update
-rw-r--r--lib/index.fnl6
-rw-r--r--lib/kvflip.fnl (renamed from lib/flip.fnl)4
-rw-r--r--lib/kvflip.test.fnl (renamed from lib/flip.test.fnl)8
-rw-r--r--lib/space-is-neighbor.fnl18
-rw-r--r--lib/space-is-neighbor.test.fnl22
-rw-r--r--main.fnl127
6 files changed, 132 insertions, 53 deletions
diff --git a/lib/index.fnl b/lib/index.fnl
index 6323160..1fb686c 100644
--- a/lib/index.fnl
+++ b/lib/index.fnl
@@ -1,19 +1,21 @@
 (local {: contains} (require :lib.contains))
-(local {: flip} (require :lib.flip))
 (local {: head} (require :lib.head))
 (local {: keys} (require :lib.keys))
+(local {: kvflip} (require :lib.kvflip))
 (local {: mill-at?} (require :lib.mill))
 (local {: pprint} (require :lib.tableprint))
 (local {: slice} (require :lib.slice))
+(local {: space-is-neighbor?} (require :lib.space-is-neighbor))
 (local {: tail} (require :lib.tail))
 
 {
  : contains
- : flip
  : head
  : keys
+ : kvflip
  : mill-at?
  : pprint
  : slice
+ : space-is-neighbor?
  : tail
  }
diff --git a/lib/flip.fnl b/lib/kvflip.fnl
index 9d21b63..25fc222 100644
--- a/lib/flip.fnl
+++ b/lib/kvflip.fnl
@@ -1,6 +1,6 @@
-(fn flip [t]
+(fn kvflip [t]
   "takes a table of {key value} and returns a table of {value key}"
   (collect [k v (pairs t)] (values v k)))
 
-{: flip}
+{: kvflip}
 
diff --git a/lib/flip.test.fnl b/lib/kvflip.test.fnl
index 32fa005..162650d 100644
--- a/lib/flip.test.fnl
+++ b/lib/kvflip.test.fnl
@@ -1,13 +1,13 @@
-(let [{: flip} (require :lib.flip)
+(let [{: kvflip} (require :lib.kvflip)
       {: describe :end test-end} (require :lib.test)]
-  (describe "flip()" (fn [t]
+  (describe "kvflip()" (fn [t]
                        (let [input {:apple "red" :banana "yellow"}
                              expected {:red "apple" :yellow "banana"}
                              ]
                          (t {:given "a table"
-                             :should "flip that table!"
+                             :should "kvflip that table!"
                              : expected
-                             :actual (flip input)})
+                             :actual (kvflip input)})
                          (test-end)))))
 
 
diff --git a/lib/space-is-neighbor.fnl b/lib/space-is-neighbor.fnl
new file mode 100644
index 0000000..380607c
--- /dev/null
+++ b/lib/space-is-neighbor.fnl
@@ -0,0 +1,18 @@
+(local {: contains} (require :lib.contains))
+(local {: head} (require :lib.head))
+(local {: tail} (require :lib.tail))
+
+(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/space-is-neighbor.test.fnl b/lib/space-is-neighbor.test.fnl
new file mode 100644
index 0000000..7b0c0af
--- /dev/null
+++ b/lib/space-is-neighbor.test.fnl
@@ -0,0 +1,22 @@
+(let [{: space-is-neighbor?} (require :lib.space-is-neighbor)
+      {: neighbors} (require :lib.constants)
+      {: describe :end 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))))
+
diff --git a/main.fnl b/main.fnl
index 9954984..af23d90 100644
--- a/main.fnl
+++ b/main.fnl
@@ -2,49 +2,50 @@
 (local {
   : contains
   : head
-  : flip
+  : kvflip
   : pprint
   : slice
   :mill-at? mill-at-maker
+  :space-is-neighbor? space-is-neighbor-maker
   } (require :lib.index))
 ;; constants...more like just strings
 (local const (require :lib.constants))
-;; front-loading mill with a partial
+;; front-loading with some partials
 (local mill-at? (partial mill-at-maker const.mills))
+(local space-is-neighbor? (partial space-is-neighbor-maker const.neighbors))
 
 
-; there are three phases of play:
-; placing, moving, and flying.
-; (plus one for capturing)
-; (plus one for complete)
+;; there are three phases of play:
+;; placing, moving, and flying.
+;; (plus one for capturing)
+;; (plus one for game-over)
 (local stages {
   :placing  1 ;; placing the cows
   :moving   2 ;; moving the cows
   :flying   3 ;; flying the cows
   :capture  4 ;; capture a cow (we do not shoot cows)
-  :complete 5 ;; no more cows!
+  :complete 5 ;; no more cows! jk the cows are fine. the game's just over okay
 })
 
 
-; there are two players
-; their names are LUIGI and MARIO
+;; there are two players
+;; their names are LUIGI and MARIO
 (local player {
   :one 1 ;; luigi has light cows
   :two 2 ;; mario has DARK cows >:)
 })
 
 
-; initialize moves[] to 0.
-; this is the game state.
-; shows which spaces are occupied by which players.
-; 0 = unoccupied
-; 1 = Player 1
-; 2 = Player 2
-;; TODO: move this to game.moves?
-(local moves (fcollect [i 1 24] 0))
+; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
+(fn index-of-move [m]
+    (let [upper (string.upper m)
+          rev   (string.reverse upper)
+          idx   (head (icollect [i v (ipairs const.spaces)]
+                        (if (or (= v upper) (= v rev)) i)))]
+         idx))
 
 
-; game state object
+;; game state object
 (local game {
   :player player.one
   :stage stages.placing
@@ -52,19 +53,51 @@
     (case self.stage
       4 ;; capture
         (do
-          (tset moves move 0)
+             ;; TODO: capturing during moving is not working?
+          (tset self.moves (index-of-move move) 0)
           (tset self :player (self:next-player))
-          (tset self :stage stages.placing)
+          (tset self :stage (if (> self.pieces-placed 17) stages.moving stages.placing))
+          (tset self.moves (index-of-move move) self.player)
           )
       1 ;; placing
-        (if (mill-at? moves move)
-          (tset self :stage stages.capture)
-          (tset self :player (self:next-player))
+        (do
+          (set self.pieces-placed (+ 1 self.pieces-placed))
+          (tset self :stage (if (> self.pieces-placed 17) stages.moving stages.placing))
+          (tset self.moves (index-of-move move) self.player)
+          (if (mill-at? self.moves (index-of-move move))
+              (tset self :stage stages.capture)
+              (tset self :player (self:next-player))
+              )
           )
+      2 ;; moving
+        (let [from (index-of-move (string.sub move 1 2))
+              to  (index-of-move (string.sub move -2 -1))]
+            (print "From" from)
+            (print "To" to)
+            (tset self.moves from 0)
+            (tset self.moves to self.player)
+            (if (mill-at? self.moves to)
+                (tset self :stage stages.capture)
+                (tset self :player (self:next-player))
+                )
+            )
     )
   )
   :next-player (fn [self] (if (= player.one self.player) player.two player.one))
+  :pieces-placed 0
+  :init (fn [self]
+    ; initialize moves[] to 0.
+    ; this is the game state.
+    ; shows which spaces are occupied by which players.
+    ; 0 = unoccupied
+    ; 1 = Player 1
+    ; 2 = Player 2
+    ; NOTE: I think it might be a good idea to make moves
+    ;       a list of moves. so that there can be undo and history
+    (set self.moves (fcollect [i 1 24] 0))
+  )
 })
+(game:init)
 
 
 (fn string-upper [s]
@@ -83,7 +116,7 @@
             (print (string.format row-template (table.unpack myslice)))
             (set index offset)))
         (print row))))
-  (print (.. "Stage: " (string-upper (. (flip stages) game.stage))))
+  (print (.. "Stage: " (string-upper (. (kvflip stages) game.stage))))
   (print (.. "Player " game.player "'s turn:")))
 
 
@@ -100,29 +133,25 @@
 (fn space-exists? [m] (contains const.spaces (string.upper m)))
 
 
-; return the numerical index (1-24) of a [A-Za-z0-9] formatted move
-(fn index-of-move [m]
-    (let [upper (string.upper m)
-          rev   (string.reverse upper)
-          idx   (head (icollect [i v (ipairs const.spaces)]
-                        (if (or (= v upper) (= v rev)) i)))]
-         idx))
-
 ; is the space represented by a [A-Za-z0-9] move unoccupied?
 (fn space-is-unoccupied? [m]
   (let [unoccupied? 0] ; i.e. is move equal to 0
-    (= unoccupied? (. moves (index-of-move m)))))
+    (= unoccupied? (. game.moves (index-of-move m)))))
 
 
 (fn space-is-occupied-by-opponent? [m]
   (let [opponent (if (= game.player 1) 2 1)
-        result (= opponent (. moves (index-of-move m))) ]
+        result (= opponent (. game.moves (index-of-move m))) ]
     result))
 
 
+(fn moving-format? [m]
+  (let [from (string.sub m 1 2)
+        to (string.sub m -2 -1)]
+    (and (space-exists? from) (space-exists? to))))
 
 ; is this a legal move?
-; TODO: maybe some functional error handling here?
+; maybe some functional error handling here?
 ;   https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch08#pure-error-handling
 ;   https://mostly-adequate.gitbook.io/mostly-adequate-guide/appendix_b#either
 ; or maybe all i need is a case-try statement..
@@ -142,10 +171,21 @@
       (= stages.capture game.stage)
       (or (space-is-occupied-by-opponent? move)
           (print "Choose an opponent's piece to remove."))
-      (or (not (mill-at? moves (index-of-move move)))
+      (or (not (mill-at? game.moves (index-of-move move)))
           (print "Ma'am, it is ILLEGAL to break up a mill."))
       )
     (and
+      (= stages.moving game.stage)
+      (or (moving-format? move)
+          (print "Try a move like A1A2 or A7 D7")) 
+      (or (not (space-is-occupied-by-opponent? (string.sub move 1 2)))
+          (print "That's not yours, don't touch it."))
+      (or (space-is-unoccupied? (string.sub move -2 -1))
+          (print "That space is occupied!")) 
+      (or (space-is-neighbor? (index-of-move (string.sub move 1 2)) (index-of-move (string.sub move -2 -1)))
+          (print "That ain't your neighbor, Johnny")) 
+      )
+    (and
       ;; TODO: add flying phase
       (= stages.flying game.stage)
       )
@@ -161,7 +201,7 @@
 (fn main []
   ;; game loop
   (while (not (= game.stage stages.complete))
-    (print-board const.board moves)
+    (print-board const.board game.moves)
 
     ;; validation loop
     (var is-valid false)
@@ -169,14 +209,11 @@
     (while (not is-valid)
       (set move (get-move))
       (set is-valid (valid-move? move))
-      (let [idx (index-of-move move)]
-        (if (not is-valid)
-          (print "Try again.")
-          (do
-            (print (.. "You chose " move))
-            (tset moves idx game.player)
-            (game:update idx)
-            )
+      (if (not is-valid)
+        (print "Try again.")
+        (do
+          (print (.. "You chose " move))
+          (game:update move)
           )
         )
     )