feat: add flying phase
This commit is contained in:
		
							parent
							
								
									91b1662302
								
							
						
					
					
						commit
						1250f9f057
					
				
							
								
								
									
										2
									
								
								doc/musings.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								doc/musings.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
# musings and learnings
 | 
			
		||||
2024-06-14T19:42:55-06:00	i sure do miss types sometimes. i'd like a little warning to pop up and warn me that i'm returning a string, e.g., when i ought to be returning a bool. i wonder if i could use some kind of a fake struct.. it would just a table, obviously. but maybe i could write a function that registers structs with a struct registry? and then another function to wrap functions that take a struct as a parameter to enforce the shape of the struct?
 | 
			
		||||
							
								
								
									
										201
									
								
								doc/tilde30.t
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								doc/tilde30.t
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,201 @@
 | 
			
		||||
---
 | 
			
		||||
title: TILDE30
 | 
			
		||||
subtitle: nine mens morris fennel game
 | 
			
		||||
author: dozens
 | 
			
		||||
created: 2024-06-01
 | 
			
		||||
updated: 2024-06-14
 | 
			
		||||
---
 | 
			
		||||
.pl 999i
 | 
			
		||||
.ce
 | 
			
		||||
{{title}}
 | 
			
		||||
 | 
			
		||||
.ce
 | 
			
		||||
{{subtitle}}
 | 
			
		||||
 
 | 
			
		||||
.IP 01
 | 
			
		||||
what  is up tilde30 fans it's ya boi dozens back with another up date on my
 | 
			
		||||
project! today i found a bug that was preventing mills from  being recognized
 | 
			
		||||
as mills. my algorithm for detecting mills is kind of (probably needlessly?)
 | 
			
		||||
complex. but luckily, i had already  written  a small module for writing unit
 | 
			
		||||
tests. so after a small refactor to isolate the individual steps as functions
 | 
			
		||||
that i  can  export,  i imported them into a test file and was able to more
 | 
			
		||||
carefully examine each step. turns out  the  culprit  was  a small  reducer in
 | 
			
		||||
which i was doing an 'and' when i ought to have been doing an 'or'. literally
 | 
			
		||||
just  changed  one  word  and  that fixed  it.  but i'm still pleased with the
 | 
			
		||||
process by which i arrived at that realization. i'm now confident that the
 | 
			
		||||
entirety of the  mill detection algorithm does what i want it to do. yay unit
 | 
			
		||||
tests!
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 02
 | 
			
		||||
today's goal was to implement capturing. but instead i discovered a bug in
 | 
			
		||||
rendering the board, and fixed  that.  i  didn't  bother taking  the  time to
 | 
			
		||||
really understand why the bug was happening. but after rewriting (and
 | 
			
		||||
simplifying) the render  function,  it's working correctly now. so yippee for
 | 
			
		||||
that. up next: capturing!
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 03
 | 
			
		||||
went to go do some remote smol computering.  posted up at the library and fixed
 | 
			
		||||
a small typo that was preventing the  game  state from  advancing  from phase 1
 | 
			
		||||
to phase 2.  and that's about all i could stand doing whilst coding on my
 | 
			
		||||
phone.  later, on  my  laptop,  successfully  implemented  capturing.   next
 | 
			
		||||
up: prevent a player from capturing a checker that is part of a mill.  i  think
 | 
			
		||||
this  will lead to a refactoring of the 'mill?' algorithm to generalize it a
 | 
			
		||||
little more.
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 04
 | 
			
		||||
i did not work on tidle30 today. not enough spoons.
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 05
 | 
			
		||||
today i did the refactor of the 'mill?' algorithm.
 | 
			
		||||
i ended up making it much more simple than how i had originally written it.
 | 
			
		||||
and it works pretty great!
 | 
			
		||||
the goal of this refactor was to be able check for a mill
 | 
			
		||||
after a move in order to change the game phase from placing to capture,
 | 
			
		||||
and also to check for a mill before a move
 | 
			
		||||
in order to check whether a capture is legal.
 | 
			
		||||
(a capture cannot break up a mill.)
 | 
			
		||||
as predicted,
 | 
			
		||||
my test-driven development workflow
 | 
			
		||||
made the refactor pretty painless.
 | 
			
		||||
i just got tripped up for a while
 | 
			
		||||
because i didn't realize i was passing the wrong type of move to the function.
 | 
			
		||||
you see, i have two different representations of a move:
 | 
			
		||||
one is a number 1 - 24 referring to the index of an array of player moves
 | 
			
		||||
(that is, the game state is a single array with values 0 = vacant, 1 = occupied by player 1, 2 = player 2);
 | 
			
		||||
and the other is an alphanumeric value (e.g. A1, b4, 7G)
 | 
			
		||||
referring to a place on the game board.
 | 
			
		||||
i frequently have to convert e.g. B2 into 4.
 | 
			
		||||
and what i don't really have right now
 | 
			
		||||
is a good type system that can tell me if i passed the wrong type to a function.
 | 
			
		||||
oh well!
 | 
			
		||||
i guess i'll have to do some manual type checking
 | 
			
		||||
in each function if i really want that kind of type safety.
 | 
			
		||||
this completes milestone 3: capture a checker.
 | 
			
		||||
next up:
 | 
			
		||||
implement some kind of a play counter
 | 
			
		||||
so the game can transition from placing to sliding.
 | 
			
		||||
.nf
 | 
			
		||||
http://cgit.tilde.town/~dozens/9mm/commit/?id=7776b2011a2585723078b275c838fd7332488d76
 | 
			
		||||
.fi
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 06
 | 
			
		||||
added transitioning from Placing to Moving,
 | 
			
		||||
and also implemented Moving logic.
 | 
			
		||||
this completes milestones 4 and 5.
 | 
			
		||||
starting to feel like a real game!
 | 
			
		||||
after getting my ass beat yesterday
 | 
			
		||||
by unknown function parameter types,
 | 
			
		||||
i added some type assertions to my new function.
 | 
			
		||||
and sure enough,
 | 
			
		||||
later on i passed the wrong value to the function.
 | 
			
		||||
but this time the assertion failed
 | 
			
		||||
and gave me a useful error message.
 | 
			
		||||
wahoooooo!
 | 
			
		||||
no time wasted tonight haha.
 | 
			
		||||
up next:
 | 
			
		||||
fix a bug that prevents captures from happening
 | 
			
		||||
during the moving phase?
 | 
			
		||||
watching:
 | 
			
		||||
maniac (2018) on netflix,
 | 
			
		||||
starring emma stone and jonah hill.
 | 
			
		||||
.nf
 | 
			
		||||
http://cgit.tilde.town/~dozens/9mm/commit/?id=f985dc4e5c9fdec06436c21440c3dc7245369847
 | 
			
		||||
.fi
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 07
 | 
			
		||||
as i said earlier,
 | 
			
		||||
there is currently a bug
 | 
			
		||||
that is preventing the board
 | 
			
		||||
from updating when a capture happens.
 | 
			
		||||
instead of working on the bug,
 | 
			
		||||
i instead focused on how sad it makes me
 | 
			
		||||
to have to enter 18 - 20 moves every time
 | 
			
		||||
just to test the capturing ui in the moving phase of the game.
 | 
			
		||||
this sadness inspired me to write an expect(1) script
 | 
			
		||||
that will interact with the game ui
 | 
			
		||||
and make all the moves for me.
 | 
			
		||||
much faster!
 | 
			
		||||
then i abstracted and isolated the moves
 | 
			
		||||
into a data file
 | 
			
		||||
so that for future ui testing,
 | 
			
		||||
i can just write down a list of moves
 | 
			
		||||
and not write a whole expect script.
 | 
			
		||||
and then i wrote a small awk script
 | 
			
		||||
that will convert data files into expect files.
 | 
			
		||||
so now i have some basic ui scripting,
 | 
			
		||||
which is maybe the first step toward actual ui testing?
 | 
			
		||||
and i have already used it to confirm the behavior
 | 
			
		||||
in both the placing phase
 | 
			
		||||
and the moving phase.
 | 
			
		||||
so it's not anything unique to phase 2
 | 
			
		||||
despite my original suspicions.
 | 
			
		||||
thanks, tests!
 | 
			
		||||
up next:
 | 
			
		||||
fix the dang bug
 | 
			
		||||
.nf
 | 
			
		||||
http://cgit.tilde.town/~dozens/9mm/commit/?id=91b1662302c14cf84ca8b90c1f3ec20a585f67a5
 | 
			
		||||
.fi
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 08
 | 
			
		||||
with fresh eyes,
 | 
			
		||||
i was able to see the bug that was preventing capturing.
 | 
			
		||||
it was a single line in the update function.
 | 
			
		||||
and i deleted it!
 | 
			
		||||
then i set about trying to allow transitioning
 | 
			
		||||
moving to flying.
 | 
			
		||||
but i introduced another bug
 | 
			
		||||
that i can't find right now..
 | 
			
		||||
it prevents capturing in the moving phase.
 | 
			
		||||
i'll have to look at it more later.
 | 
			
		||||
right now,
 | 
			
		||||
i have to finish packing!
 | 
			
		||||
i'm going on vacation!
 | 
			
		||||
we'll see whether or not i'm able to continue
 | 
			
		||||
working on 9mm / tilde30
 | 
			
		||||
while away from home.
 | 
			
		||||
i'm not bringing my laptop.
 | 
			
		||||
so i'll be limited to coding on my phone
 | 
			
		||||
with my little folding bluetooth keyboard.
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP "WEEK ONE REVIEW"
 | 
			
		||||
when i look back on week one,
 | 
			
		||||
i feel like i made more progress
 | 
			
		||||
than i had expected to.
 | 
			
		||||
even while spending time on writing unit tests
 | 
			
		||||
and "ui tests."
 | 
			
		||||
if i keep up this pace
 | 
			
		||||
then i expect i'll be done with the game in another week.
 | 
			
		||||
but i'm on vacation next week,
 | 
			
		||||
so i'm not confident that i will keep up the pace.
 | 
			
		||||
whether or not i get around to it as part of tilde30,
 | 
			
		||||
i do want to build a gui frontend for the game.
 | 
			
		||||
because i think that would be fun.
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP "09-13"
 | 
			
		||||
i did not do any computering
 | 
			
		||||
during this five day low-tech beach vacation.
 | 
			
		||||
.
 | 
			
		||||
.
 | 
			
		||||
.IP 14
 | 
			
		||||
fixed the bug that prevented capturing during the moving phase.
 | 
			
		||||
implemented flying!
 | 
			
		||||
and also handled an edge case
 | 
			
		||||
where you cannot break up a mill
 | 
			
		||||
when capturing
 | 
			
		||||
unless there are no other non-milled checkers.
 | 
			
		||||
in which case
 | 
			
		||||
you can break up a mill
 | 
			
		||||
when capturing.
 | 
			
		||||
up next:
 | 
			
		||||
ending the game.
 | 
			
		||||
 | 
			
		||||
.pl \n[nl]u
 | 
			
		||||
							
								
								
									
										4
									
								
								justfile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								justfile
									
									
									
									
									
								
							@ -9,3 +9,7 @@ test:
 | 
			
		||||
# build expect scripts
 | 
			
		||||
expects:
 | 
			
		||||
  for f in test/*.dat; do awk -f test/test.awk $f > ${f/dat/expect}; done
 | 
			
		||||
 | 
			
		||||
# make the project
 | 
			
		||||
project:
 | 
			
		||||
  awk '$0 ~ /^---$/ && times++ < 2 { a=!a;next; } a' doc/tilde30.t | recfmt -f doc/tilde30.t | awk '$0 ~ /^---$/ { times++;next } times > 1' | nroff -ms -Tascii
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								lib/all-mills.fnl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								lib/all-mills.fnl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
(local {: mill-at? } (require :lib.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? [moves player]
 | 
			
		||||
  (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]
 | 
			
		||||
  (let [next-player (toggle-player current-player)
 | 
			
		||||
        player-moves (only-player-moves all-moves next-player)
 | 
			
		||||
        all-mills (all-moves-are-mills? player-moves current-player)]
 | 
			
		||||
    all-mills))
 | 
			
		||||
 | 
			
		||||
{: all-mills?
 | 
			
		||||
 ;; do not use; just for testing:
 | 
			
		||||
 : toggle-player
 | 
			
		||||
 : only-player-moves
 | 
			
		||||
 : all-moves-are-mills?
 | 
			
		||||
 }
 | 
			
		||||
							
								
								
									
										41
									
								
								lib/all-mills.test.fnl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/all-mills.test.fnl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
(let [{: describe
 | 
			
		||||
       :end test-end} (require :lib.test)
 | 
			
		||||
      {: all-mills?
 | 
			
		||||
       : toggle-player
 | 
			
		||||
       : only-player-moves
 | 
			
		||||
       : all-moves-are-mills?
 | 
			
		||||
       } (require :lib.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? moves 1)
 | 
			
		||||
            }))
 | 
			
		||||
      (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? moves 1)
 | 
			
		||||
            }))))
 | 
			
		||||
    (test-end))))
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
(local {: all-mills?} (require :lib.all-mills))
 | 
			
		||||
(local {: contains} (require :lib.contains))
 | 
			
		||||
(local {: head} (require :lib.head))
 | 
			
		||||
(local {: keys} (require :lib.keys))
 | 
			
		||||
@ -9,6 +10,7 @@
 | 
			
		||||
(local {: tail} (require :lib.tail))
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
 : all-mills?
 | 
			
		||||
 : contains
 | 
			
		||||
 : head
 | 
			
		||||
 : keys
 | 
			
		||||
 | 
			
		||||
@ -28,8 +28,7 @@
 | 
			
		||||
  (let [candidates (get-candidates all-mills move)
 | 
			
		||||
        my-moves (candidate-moves candidates current-moves)
 | 
			
		||||
        my-mills (move-mills my-moves)
 | 
			
		||||
        result (any my-mills)
 | 
			
		||||
        ]
 | 
			
		||||
        result (any my-mills)]
 | 
			
		||||
    result))
 | 
			
		||||
 | 
			
		||||
{: mill-at?
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										114
									
								
								main.fnl
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								main.fnl
									
									
									
									
									
								
							@ -5,6 +5,7 @@
 | 
			
		||||
  : kvflip
 | 
			
		||||
  : pprint
 | 
			
		||||
  : slice
 | 
			
		||||
  : all-mills?
 | 
			
		||||
  :mill-at? mill-at-maker
 | 
			
		||||
  :space-is-neighbor? space-is-neighbor-maker
 | 
			
		||||
  } (require :lib.index))
 | 
			
		||||
@ -28,21 +29,29 @@
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;; story mode:
 | 
			
		||||
;; there are two players
 | 
			
		||||
;; their names are LUIGI and MARIO
 | 
			
		||||
;; their names are WIGI and MALO
 | 
			
		||||
(local player {
 | 
			
		||||
  :one 1 ;; luigi has light cows
 | 
			
		||||
  :two 2 ;; mario has DARK cows >:)
 | 
			
		||||
  :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]
 | 
			
		||||
    (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))
 | 
			
		||||
  (assert (= "string" (type m)) "index-of-move needs a string argument")
 | 
			
		||||
  (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))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(fn player-count [moves player]
 | 
			
		||||
  (accumulate [count 0
 | 
			
		||||
               _ x (ipairs moves)]
 | 
			
		||||
              (if (= x player) (+ count 1) count)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
;; game state object
 | 
			
		||||
@ -51,34 +60,57 @@
 | 
			
		||||
  :stage stages.placing
 | 
			
		||||
  :update (fn [self move]
 | 
			
		||||
    (case self.stage
 | 
			
		||||
      4 ;; capture
 | 
			
		||||
      4 ;; CAPTURE
 | 
			
		||||
        (do
 | 
			
		||||
             ;; TODO: capturing during moving is not working?
 | 
			
		||||
          (tset self.moves (index-of-move move) 0)
 | 
			
		||||
          (tset self :player (self:next-player))
 | 
			
		||||
          (tset self :stage (if (> self.pieces-placed 17) stages.moving stages.placing))
 | 
			
		||||
          (tset self.moves (index-of-move move) self.player)
 | 
			
		||||
          )
 | 
			
		||||
      1 ;; placing
 | 
			
		||||
          (let [flytime (and (> self.pieces-placed 17) (= 3 (player-count self.moves self.player)))
 | 
			
		||||
                movetime (and (> self.pieces-placed 17) (> (player-count self.moves self.player) 3))]
 | 
			
		||||
            (tset self :stage (if flytime stages.flying
 | 
			
		||||
                                movetime stages.moving
 | 
			
		||||
                                stages.placing))))
 | 
			
		||||
      1 ;; PLACING
 | 
			
		||||
        (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 [flytime (and (> self.pieces-placed 17) (= 3 (player-count self.moves self.player)))
 | 
			
		||||
                movetime (and (> self.pieces-placed 17) (> (player-count self.moves self.player) 3))
 | 
			
		||||
                capturetime (mill-at? self.moves (index-of-move move))]
 | 
			
		||||
            (tset self :stage (if
 | 
			
		||||
                                capturetime stages.capture
 | 
			
		||||
                                flytime stages.flying
 | 
			
		||||
                                movetime stages.moving
 | 
			
		||||
                                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.pieces-placed 17) (= 3 (player-count self.moves (self:next-player))))
 | 
			
		||||
                movetime (and (> self.pieces-placed 17) (> (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 stages.capture
 | 
			
		||||
                                flytime stages.flying
 | 
			
		||||
                                movetime stages.moving
 | 
			
		||||
                                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)
 | 
			
		||||
            (if (mill-at? self.moves to)
 | 
			
		||||
                (tset self :stage stages.capture)
 | 
			
		||||
                (tset self :player (self:next-player))
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            (let [flytime (and (> self.pieces-placed 17) (= 3 (player-count self.moves (self:next-player))))
 | 
			
		||||
                  movetime (and (> self.pieces-placed 17) (> (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 stages.capture
 | 
			
		||||
                                  flytime stages.flying
 | 
			
		||||
                                  movetime stages.moving
 | 
			
		||||
                                  stages.placing))
 | 
			
		||||
              (if (not capturetime) (tset self :player (self:next-player)))))
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
  :next-player (fn [self] (if (= player.one self.player) player.two player.one))
 | 
			
		||||
@ -98,6 +130,7 @@
 | 
			
		||||
(game:init)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
; TODO: move to lib utility
 | 
			
		||||
(fn string-upper [s]
 | 
			
		||||
  (.. (string.upper (string.sub s 1 1)) (string.sub s 2)))
 | 
			
		||||
 | 
			
		||||
@ -136,27 +169,23 @@
 | 
			
		||||
  (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 (space-exists? from) (space-exists? to))))
 | 
			
		||||
    (and (>= (length m) 4) (space-exists? from) (space-exists? to))))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
; is this a legal move?
 | 
			
		||||
; 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..
 | 
			
		||||
;   https://fennel-lang.org/reference#case-try-for-matching-multiple-steps
 | 
			
		||||
;   update: i didn't really like that
 | 
			
		||||
; i think maybe i do want the monad after all..
 | 
			
		||||
; i'll come back to it later
 | 
			
		||||
(fn valid-move? [move]
 | 
			
		||||
  (or
 | 
			
		||||
    (and
 | 
			
		||||
@ -169,8 +198,10 @@
 | 
			
		||||
      (= stages.capture game.stage)
 | 
			
		||||
      (or (space-is-occupied-by-opponent? move)
 | 
			
		||||
          (print "Choose an opponent's piece to remove."))
 | 
			
		||||
      (or (not (mill-at? game.moves (index-of-move move)))
 | 
			
		||||
          (print "Ma'am, it is ILLEGAL to break up a mill."))
 | 
			
		||||
      (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
 | 
			
		||||
      (= stages.moving game.stage)
 | 
			
		||||
@ -184,8 +215,13 @@
 | 
			
		||||
          (print "That ain't your neighbor, Johnny")) 
 | 
			
		||||
      )
 | 
			
		||||
    (and
 | 
			
		||||
      ;; TODO: add flying phase
 | 
			
		||||
      (= 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!")) 
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								test/capture-oops-all-mills.dat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								test/capture-oops-all-mills.dat
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
# PLACING PHASE (18 moves)
 | 
			
		||||
A1
 | 
			
		||||
A4
 | 
			
		||||
A7
 | 
			
		||||
b2
 | 
			
		||||
b4
 | 
			
		||||
b6
 | 
			
		||||
c3
 | 
			
		||||
c4
 | 
			
		||||
c5
 | 
			
		||||
d1
 | 
			
		||||
d2
 | 
			
		||||
d3
 | 
			
		||||
d5
 | 
			
		||||
d6
 | 
			
		||||
d7
 | 
			
		||||
e3
 | 
			
		||||
e4
 | 
			
		||||
e5
 | 
			
		||||
# MOVING PHASE (6 captures)
 | 
			
		||||
e4f4
 | 
			
		||||
e3e4
 | 
			
		||||
f4g4
 | 
			
		||||
d3e3
 | 
			
		||||
b4
 | 
			
		||||
g4g1
 | 
			
		||||
c4b4
 | 
			
		||||
d2
 | 
			
		||||
d7g7
 | 
			
		||||
e3d3
 | 
			
		||||
c5c4
 | 
			
		||||
b2d2
 | 
			
		||||
a1
 | 
			
		||||
c4c5
 | 
			
		||||
d2b2
 | 
			
		||||
d5
 | 
			
		||||
g7g4
 | 
			
		||||
b2d2
 | 
			
		||||
a7
 | 
			
		||||
c3c4
 | 
			
		||||
d2b2
 | 
			
		||||
c4
 | 
			
		||||
# FLYING PHASE!
 | 
			
		||||
c5g7
 | 
			
		||||
d3
 | 
			
		||||
e4f4
 | 
			
		||||
g7f6
 | 
			
		||||
b2d2
 | 
			
		||||
f6g7
 | 
			
		||||
d1
 | 
			
		||||
d2b2
 | 
			
		||||
							
								
								
									
										43
									
								
								test/flying.dat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								test/flying.dat
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
# PLACING PHASE (18 moves)
 | 
			
		||||
A1
 | 
			
		||||
A4
 | 
			
		||||
A7
 | 
			
		||||
b2
 | 
			
		||||
b4
 | 
			
		||||
b6
 | 
			
		||||
c3
 | 
			
		||||
c4
 | 
			
		||||
c5
 | 
			
		||||
d1
 | 
			
		||||
d2
 | 
			
		||||
d3
 | 
			
		||||
d5
 | 
			
		||||
d6
 | 
			
		||||
d7
 | 
			
		||||
e3
 | 
			
		||||
e4
 | 
			
		||||
e5
 | 
			
		||||
# MOVING PHASE (6 captures)
 | 
			
		||||
e4f4
 | 
			
		||||
e3e4
 | 
			
		||||
f4g4
 | 
			
		||||
d3e3
 | 
			
		||||
b4
 | 
			
		||||
g4g1
 | 
			
		||||
c4b4
 | 
			
		||||
d2
 | 
			
		||||
d7g7
 | 
			
		||||
e3d3
 | 
			
		||||
c5c4
 | 
			
		||||
b2d2
 | 
			
		||||
a1
 | 
			
		||||
c4c5
 | 
			
		||||
d2b2
 | 
			
		||||
d5
 | 
			
		||||
g7g4
 | 
			
		||||
b2d2
 | 
			
		||||
a7
 | 
			
		||||
c3c4
 | 
			
		||||
d2b2
 | 
			
		||||
c4
 | 
			
		||||
# FLYING PHASE!
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
# placing time
 | 
			
		||||
A1
 | 
			
		||||
A4
 | 
			
		||||
A7
 | 
			
		||||
@ -16,7 +17,9 @@ d7
 | 
			
		||||
e3
 | 
			
		||||
e4
 | 
			
		||||
e5
 | 
			
		||||
# moving time
 | 
			
		||||
e4f4
 | 
			
		||||
e3e4
 | 
			
		||||
f4g4
 | 
			
		||||
# player 2 to capture:
 | 
			
		||||
d3e3
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ BEGIN {
 | 
			
		||||
  print "spawn fennel main.fnl"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/^#/ { next }
 | 
			
		||||
 | 
			
		||||
{ print "expect -re \"Player .'s turn:\""
 | 
			
		||||
  print "send -- \"" $0 "\\r\""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user