~subsetpark/whist

18418665df266c09b3ef1e8cb00aee6cfb61bfce — Zach Smith 6 months ago c7bddc8
Break out bid phase
3 files changed, 62 insertions(+), 36 deletions(-)

M game/bid.janet
M players.janet
M test/bid.janet
M game/bid.janet => game/bid.janet +42 -25
@@ 2,6 2,42 @@
(import bids)
(import events)

(defn- new-meta
  [state action]
  (let [meta (state :meta)
	{:bid previous-high-bid :player previous-high-bidder} (meta :high_bid)
	{:value last-bid :player last-bidder} action
	# Record the high bid (whether it's a new bid or the existing high bid).
	[high-bid high-bidder] (case last-bid
				 "pass" [previous-high-bid previous-high-bidder]
				 [last-bid last-bidder])]
    @{:high_bid @{:player high-bidder :bid high-bid}}))

(defn- initial-events
  [state action high-bid]
  (let [meta (state :meta)
	{:bid previous-high-bid :player previous-high-bidder} (meta :high_bid)
	{:value last-bid :player last-bidder} action]
    (case last-bid
      "pass" @[(events/add-decoration last-bidder "bid_action" "passed")]
      # Handle a bid. 
      (array/concat 
       (case previous-high-bidder
	 nil @[]
	 @[(events/clear-decoration previous-high-bidder "bid_action")])
       (events/add-decoration last-bidder "bid_action" "declarer")
       (events/add-decoration last-bidder "bid" (bids/to-text high-bid))))))

(defn- update-not-passed
  [state action]
  (let [meta (state :meta)
	{:value last-bid :player last-bidder} action
	not-passed (meta :not_passed)]
    (case last-bid
      # Handle a new pass. Mark the player as passed by removing them from the set.
      "pass" (put not-passed (keyword last-bidder) nil))
    not-passed))

(defn bid-phase
  ```
  The players bid in an auction to name trump. 


@@ 15,30 51,10 @@
  ```
  [state players action]
  (if (= action :null) (error {:error "action required"}))
  (let [meta (state :meta)
	{:bid previous-high-bid :player previous-high-bidder} (meta :high_bid)
	{:value last-bid :player last-bidder} action
	# Record the high bid (whether it's a new bid or the existing high bid).
	[high-bid high-bidder] (case last-bid
				 "pass" [previous-high-bid previous-high-bidder]
				 [last-bid last-bidder])
	new-meta @{:high_bid @{:player high-bidder :bid high-bid}}
	# Track which players have passed.
	not-passed (meta :not_passed)
	remaining-players (filter |(in not-passed (keyword ($0 :id))) players)
	next-bidder (players/next-player last-bidder remaining-players)
	events (case last-bid
		 # Handle a new pass. Mark the player as passed by removing them from the set.
		 "pass" (do
			  (put not-passed (keyword last-bidder) nil)
			  @[(events/add-decoration last-bidder "bid_action" "passed")])
		 # Handle a bid. 
		 (array/concat 
		  (case previous-high-bidder
		    nil @[]
		    @[(events/clear-decoration previous-high-bidder "bid_action")])
		  (events/add-decoration last-bidder "bid_action" "declarer")
		  (events/add-decoration last-bidder "bid" (bids/to-text high-bid))))]
  (let  [new-meta (new-meta state action)
	 {:high_bid {:player high-bidder :bid high-bid}} new-meta
	 events (initial-events state action high-bid)
	 not-passed (update-not-passed state action)]
    # The set of players still in the auction is now up to date. We'll
    # use it to determine whether the action is over.
    (if (and (not (nil? high-bidder)) (= 1 (length not-passed)))


@@ 49,7 65,8 @@
       # Bidder selects suit in a trumps bid or direction in a no-trumps bid.
       (array/push events (events/pick1 "bid" high-bidder (bids/second-bid high-bid)))]
      # Otherwise, continue the bidding.
      (do 
      (let [{:player last-bidder} action
	    next-bidder (players/next-player last-bidder players not-passed)]
	(array/push events (events/add-decoration next-bidder "bid_action" "bidding"))
	# The auction isn't over; include the set of players still bidding in the metadata.
	[(merge state {:meta (put new-meta :not_passed not-passed)

M players.janet => players.janet +15 -6
@@ 1,10 1,19 @@
(defn next-player
  [id players]
  (let [ind (find-index
	     |(= ($0 :id) id)
	     players)
	new-ind (mod (inc ind) (length players))]
    ((players new-ind) :id)))
  [id players &opt out-of]
  
  (var ind (find-index |(and (= ($0 :id) id)) players))
  (let [f (fiber/new (fn []
		       (while true
			 (do
			   (set ind (mod (inc ind) (length players)))
			   (yield ind)))))]
    (var found nil)
    (while (not found)
      (let [new-ind (resume f)
	    new-id ((players new-ind) :id)]
	(if (or (nil? out-of) (in out-of (keyword new-id)))
	  (set found new-id))))
    found))

(defn of-team [players team] (->> players
				  (filter |(= ($0 :team) team))

M test/bid.janet => test/bid.janet +5 -5
@@ 50,14 50,14 @@
		      :state {:phase "bid"
			      :meta {:high_bid {}
				     :not_passed (all-not-passed)}}
		      :action {:name "bid" :player "North" :value "pass"}})
		      :action {:name "bid" :player "West" :value "pass"}})
  (def [state events] (whist/next player-passed))
  (def [north-decoration east-decoration east-prompt] events)
  (is (= {:value "passed" :event "add_decoration" :player "North" :name "bid_action"} north-decoration))
  (is (= {:value "bidding" :event "add_decoration" :player "East" :name "bid_action"} east-decoration))
  (is (deep= {:name "bid" :count 1 :event "prompt_select" :player "East" :from (bids/available-bids)} east-prompt))
  (is (= {:value "passed" :event "add_decoration" :player "West" :name "bid_action"} north-decoration))
  (is (= {:value "bidding" :event "add_decoration" :player "North" :name "bid_action"} east-decoration))
  (is (deep= {:name "bid" :count 1 :event "prompt_select" :player "North" :from (bids/available-bids)} east-prompt))
  (is (deep= @{} (get-in state [:meta :high_bid])))
  (is (deep= @{:East true :South true :West true} (get-in state [:meta :not_passed]))))
  (is (deep= @{:East true :South true :North true} (get-in state [:meta :not_passed]))))


(deftest last-pass