From f985dc4e5c9fdec06436c21440c3dc7245369847 Mon Sep 17 00:00:00 2001 From: dozens Date: Thu, 6 Jun 2024 22:49:52 -0600 Subject: feat: add moving phase also moves `moves` into the game object and moves updating `moves` to game:update --- lib/flip.fnl | 6 -- lib/flip.test.fnl | 13 ----- lib/index.fnl | 6 +- lib/kvflip.fnl | 6 ++ lib/kvflip.test.fnl | 13 +++++ lib/space-is-neighbor.fnl | 18 ++++++ lib/space-is-neighbor.test.fnl | 22 +++++++ main.fnl | 127 ++++++++++++++++++++++++++--------------- 8 files changed, 145 insertions(+), 66 deletions(-) delete mode 100644 lib/flip.fnl delete mode 100644 lib/flip.test.fnl create mode 100644 lib/kvflip.fnl create mode 100644 lib/kvflip.test.fnl create mode 100644 lib/space-is-neighbor.fnl create mode 100644 lib/space-is-neighbor.test.fnl diff --git a/lib/flip.fnl b/lib/flip.fnl deleted file mode 100644 index 9d21b63..0000000 --- a/lib/flip.fnl +++ /dev/null @@ -1,6 +0,0 @@ -(fn flip [t] - "takes a table of {key value} and returns a table of {value key}" - (collect [k v (pairs t)] (values v k))) - -{: flip} - diff --git a/lib/flip.test.fnl b/lib/flip.test.fnl deleted file mode 100644 index 32fa005..0000000 --- a/lib/flip.test.fnl +++ /dev/null @@ -1,13 +0,0 @@ -(let [{: flip} (require :lib.flip) - {: describe :end test-end} (require :lib.test)] - (describe "flip()" (fn [t] - (let [input {:apple "red" :banana "yellow"} - expected {:red "apple" :yellow "banana"} - ] - (t {:given "a table" - :should "flip that table!" - : expected - :actual (flip input)}) - (test-end))))) - - 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/kvflip.fnl b/lib/kvflip.fnl new file mode 100644 index 0000000..25fc222 --- /dev/null +++ b/lib/kvflip.fnl @@ -0,0 +1,6 @@ +(fn kvflip [t] + "takes a table of {key value} and returns a table of {value key}" + (collect [k v (pairs t)] (values v k))) + +{: kvflip} + diff --git a/lib/kvflip.test.fnl b/lib/kvflip.test.fnl new file mode 100644 index 0000000..162650d --- /dev/null +++ b/lib/kvflip.test.fnl @@ -0,0 +1,13 @@ +(let [{: kvflip} (require :lib.kvflip) + {: describe :end test-end} (require :lib.test)] + (describe "kvflip()" (fn [t] + (let [input {:apple "red" :banana "yellow"} + expected {:red "apple" :yellow "banana"} + ] + (t {:given "a table" + :should "kvflip that table!" + : expected + :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,9 +171,20 @@ (= 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) ) ) ) -- cgit 1.4.1-2-gfad0