From 9bf31e86cef62bed76a35353e791768960b14d70 Mon Sep 17 00:00:00 2001 From: dozens Date: Sun, 23 Jun 2024 15:41:31 -0600 Subject: Move error handling to front-end game.validate-move will now return an error code is the move is invalid. default error messages are found in lib/constants.fnl. error handling is now handled on the front end. --- doc/tilde30.t | 39 +++++++++++++++++++++++++++++++++++++++ lib/constants.fnl | 30 +++++++++++++++++++++++++++--- main.fnl | 20 +++++++++++++------- src/game.fnl | 53 ++++++++++++++++++++--------------------------------- 4 files changed, 99 insertions(+), 43 deletions(-) diff --git a/doc/tilde30.t b/doc/tilde30.t index c931c07..8a6911a 100644 --- a/doc/tilde30.t +++ b/doc/tilde30.t @@ -272,5 +272,44 @@ and will be spending a lot of time on my back. So I'm either going to get a lot on 9mm, or nothing at all. We'll see! +. +. +.IP "20 - 21" +I was having back surgery! +. +. +.IP "WEEK THREE REVIEW" +Finished the game, and then had a major surgery. +Doing some tidying up and housekeeping now. +Organzing tests, +breaking stuff out into modules. +Rewriting and refactoring a couple of functions. +Generally getting ready to start thinking +about Stretch Goal 1: Story Mode. +I have a vague inkling of an idea about +doing some kind of generative story / narration +based on each move of the game. +Assign symbolic meaning to the outer, middle, and inner rings of the board. +Add a little twist based on whether the move is a capture, a slide, a placement. +That kind of thing. +Not sure how I want to do it exactly yet. +I'd like to be able to show the story progress as you play. +But updating the story every move seems like kind of a lot. +Maybe just at every game phase. +So three installments. +One at the end of Placement, +the second at the end of Movement, +and the third at the end of the game? +Anyway. + +Up next: big refactor of the validation logic. +I don't want it to just print to console +if I'm going to eventually support multiple frontends. +I need to return an error code or something +for the client to interpret. +I'm still interested in the Result / Either monad option +but I think I'm doing to first try the conventional lua way +and throw an error, and then 'pcall' the function and handle the error politely. + .pl \n[nl]u diff --git a/lib/constants.fnl b/lib/constants.fnl index be9a6be..996c11c 100644 --- a/lib/constants.fnl +++ b/lib/constants.fnl @@ -97,9 +97,33 @@ :complete 5 ;; no more cows! jk the cows are fine. the game's just over okay }) -{: board + +;; errror codes +(local errors { + :not-a-space "That space does not exist!\nHint: 1a 1A A1 a1 are all the same move." + :occupied "That space is occupied!" + :not-an-opponent "Choose an opponent's piece to remove." + :is-mill "Ma'am, it is ILLEGAL to break up a mill." + :bad-move-format "Try a move like B2B4 or A7 D7" + :not-yours "That's not yours, don't touch it." + :not-neighbor "That ain't your neighbor, Johnny" +}) + + +;; if you like it then you shoulda put a ... +(local rings { + :outer [ 1 2 3 15 24 23 22 10 ] + :middle [ 4 5 6 14 21 20 19 11 ] + :inner [ 7 8 9 13 18 17 16 12 ] +}) + + +{ + : board + : errors : mills : neighbors + : rings + : spaces : stages - : spaces} - + } diff --git a/main.fnl b/main.fnl index c205454..8144b11 100644 --- a/main.fnl +++ b/main.fnl @@ -34,17 +34,23 @@ ;; game loop (while (not (= game.stage const.stages.complete)) (with-board game.moves) - ;; validation loop (var is-valid false) (var move "") + ;; validation loop (while (not is-valid) (set move (get-move)) - (set is-valid (game.validate-move move)) - (if (not is-valid) - (print "Try again.") - (do - (print (string.format "Turn %d: You chose %s" game.turns move)) - (game:update move))))) + (case (pcall game.validate-move move) + (false msg) + (do + (let [(i j) (string.find msg ": ") + key (string.sub msg (+ 1 j))] + (print (. const.errors key))) + (print "Try again.")) + ok + (do + (set is-valid true) + (print (string.format "Turn %d: You chose %s" game.turns move)) + (game:update move))))) ;; game is complete (print "Congratulations!") (print (string.format "Player %d is the winner!" game.player))) diff --git a/src/game.fnl b/src/game.fnl index b48a6ac..4a24ce2 100644 --- a/src/game.fnl +++ b/src/game.fnl @@ -4,15 +4,15 @@ } (require :lib.index)) (local { : all-mills? - :mill-at? mill-at-maker - :no-moves? no-moves-maker - :space-is-neighbor? space-is-neighbor-maker + :mill-at? full-mill-at ;; stay with me... + :no-moves? full-no-moves + :space-is-neighbor? full-space-is-neighbor } (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)) +;; front-loading the "fulls" with some partials +(local mill-at? (partial full-mill-at const.mills)) +(local space-is-neighbor? (partial full-space-is-neighbor const.neighbors)) +(local no-moves? (partial full-no-moves const.neighbors)) ;; story mode: @@ -124,8 +124,7 @@ ; 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)) - ) + (set self.moves (fcollect [i 1 24] 0))) }) @@ -164,40 +163,28 @@ ; is this a legal move? +; note: string argument to assert funs are defined in /lib/constants (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!"))) + (assert (space-exists? move) :not-a-space) + (assert (space-is-unoccupied? move) :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.") - )) + (assert (space-is-occupied-by-opponent? move) :not-an-opponent) + (assert (or (all-mills? game.moves game.player) (not (mill-at? game.moves (index-of-move move)))) :is-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")) ) + (assert (moving-format? move) :bad-move-format) + (assert (not (space-is-occupied-by-opponent? (string.sub move 1 2))) :not-yours) + (assert (space-is-unoccupied? (string.sub move -2 -1)) :occupied) + (assert (space-is-neighbor? (index-of-move (string.sub move 1 2)) (index-of-move (string.sub move -2 -1))) :not-neighbor) ) (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!"))))) + (assert (moving-format? move) :bad-move-format) + (assert (not (space-is-occupied-by-opponent? (string.sub move 1 2))) :not-yours) + (assert (space-is-unoccupied? (string.sub move -2 -1)) :occupied)))) (tset game :validate-move valid-move?) -- cgit 1.4.1-2-gfad0