From c7b2c982004e350f5e3032321baadfc9021b6bad Mon Sep 17 00:00:00 2001 From: dozens Date: Thu, 20 Jun 2024 09:17:06 -0600 Subject: 🗄️ 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/ --- src/game.fnl | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/game.fnl (limited to 'src/game.fnl') diff --git a/src/game.fnl b/src/game.fnl new file mode 100644 index 0000000..b48a6ac --- /dev/null +++ b/src/game.fnl @@ -0,0 +1,204 @@ +;; helper and utility functions +(local { + : tbl + } (require :lib.index)) +(local { + : all-mills? + :mill-at? mill-at-maker + :no-moves? no-moves-maker + :space-is-neighbor? space-is-neighbor-maker + } (require :lib.game.index)) +(local const (require :lib.constants)) +;; 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)) +(local no-moves? (partial no-moves-maker const.neighbors)) + + +;; story mode: +;; there are two players +;; their names are WIGI and MALO +(local player { + :one 1 ;; wigi has light cows + :two 2 ;; malo has DARK cows >:) +}) + + +; return the numerical index (1-24) of a [A-Za-z0-9] formatted move +(fn index-of-move [m] + (assert (= "string" (type m)) "index-of-move needs a string argument") + (let [upper (string.upper m) + rev (string.reverse upper) + idx (tbl.head (icollect [i v (ipairs const.spaces)] + (if (or (= v upper) (= v rev)) i)))] + idx)) + + +(fn player-count [moves player] + (accumulate [count 0 + _ x (ipairs moves)] + (if (= x player) (+ count 1) count))) + + +;; game state object +(local game { + :player player.one + :stage const.stages.placing + :update (fn [self move] + (case self.stage + 4 ;; CAPTURE + (do + (tset self.moves (index-of-move move) 0) + (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player)))) + movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3)) + endtime (and (self:phase-two?) + (or (< (length (icollect [_ m (ipairs self.moves)] (if (= m 1) 1))) 3) + (< (length (icollect [_ m (ipairs self.moves)] (if (= m 2) 2))) 3)))] + (tset self :stage (if endtime const.stages.complete + flytime const.stages.flying + movetime const.stages.moving + const.stages.placing)) + (if (not endtime) (tset self :player (self:next-player))) + )) + 1 ;; PLACING + (do + (set self.pieces-placed (+ 1 self.pieces-placed)) + (tset self :stage (if (self:phase-two?) const.stages.moving const.stages.placing)) + (tset self.moves (index-of-move move) self.player) + (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves self.player))) + movetime (and (self:phase-two?) (> (player-count self.moves self.player) 3)) + capturetime (mill-at? self.moves (index-of-move move))] + (tset self :stage (if + capturetime const.stages.capture + flytime const.stages.flying + movetime const.stages.moving + const.stages.placing)) + (if (not capturetime) (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))] + (tset self.moves from 0) + (tset self.moves to self.player) + (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player)))) + movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3)) + capturetime (mill-at? self.moves (index-of-move (string.sub move -2 -1))) + endtime (no-moves? self.moves (self:next-player))] + (tset self :stage (if + capturetime const.stages.capture + flytime const.stages.flying + movetime const.stages.moving + endtime const.stages.complete + const.stages.placing)) + (if (not capturetime) (tset self :player (self:next-player))))) + 3 ;; FLYING + (let [from (index-of-move (string.sub move 1 2)) + to (index-of-move (string.sub move -2 -1))] + (tset self.moves from 0) + (tset self.moves to self.player) + (let [flytime (and (self:phase-two?) (= 3 (player-count self.moves (self:next-player)))) + movetime (and (self:phase-two?) (> (player-count self.moves (self:next-player)) 3)) + capturetime (mill-at? self.moves (index-of-move (string.sub move -2 -1)))] + (tset self :stage (if + capturetime const.stages.capture + flytime const.stages.flying + movetime const.stages.moving + const.stages.placing)) + (if (not capturetime) (tset self :player (self:next-player))))) + 5 ;; COMPLETE + (print "Unreachable!") + ) + (tset self :turns (+ self.turns 1)) + ) + :next-player (fn [self] (if (= player.one self.player) player.two player.one)) + :pieces-placed 0 + :turns 1 + ; so basically there's phase 1 where you place your checkers + ; and then phase 2 when you move and fly around trying to capture pieces + :phase-two? (fn [self] (> self.pieces-placed 17)) + :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)) + ) +}) + + +; add the inverse of each valid move +; e.g. 1A = A1 +(fn add-reverse-moves [] + (let [reversed (icollect [_ v (ipairs const.spaces)] (string.reverse v))] + (each [_ v (ipairs reversed)] + (table.insert const.spaces v)))) ;; oh nooooo i'm mutating a const???? +(add-reverse-moves) + + +; does the move exist within the domain of valid spaces +(fn space-exists? [m] (tbl.contains const.spaces (string.upper m))) + + +; 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? (. game.moves (index-of-move m))))) + +; is the space m occupied by the player's opponent? +(fn space-is-occupied-by-opponent? [m] + "is the space m occupied by the player's opponent?" + (let [opponent (if (= game.player 1) 2 1) + result (= opponent (. game.moves (index-of-move m))) ] + result)) + +; checks that the first 2 charcters and the last 2 characters +; of a string are legal spaces +; moving-format is the same as flying-format +(fn moving-format? [m] + (let [from (string.sub m 1 2) + to (string.sub m -2 -1)] + (and (>= (length m) 4) (space-exists? from) (space-exists? to)))) + + +; is this a legal move? +(fn valid-move? [move] + (or + (and + (= const.stages.placing game.stage) + (or (space-exists? move) + (print "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move.")) + (or (space-is-unoccupied? move) + (print "That space is occupied!"))) + (and + (= const.stages.capture game.stage) + (or (space-is-occupied-by-opponent? move) + (print "Choose an opponent's piece to remove.")) + (or (or (all-mills? game.moves game.player) + (not (mill-at? game.moves (index-of-move move)))) + (print "Ma'am, it is ILLEGAL to break up a mill.") + )) + (and + (= const.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 + (= const.stages.flying 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!"))))) + +(tset game :validate-move valid-move?) + +{: game} -- cgit 1.4.1-2-gfad0