~subsetpark/whist

eddf3ab89665236aa3aa5a0de452d4c6a81eb34b — Zach Smith 11 months ago 9e87f34
Manage info and stacks consistently
M events.janet => events.janet +2 -0
@@ 11,3 11,5 @@
(defn prompt-play [player from] {:event "prompt_play" :player player :to "trick" :count 1 :from from})

(defn prompt-discard [player count] {:event "prompt_discard" :player player :count count})

(defn add-info [id label] {:event "add_info" :id id :label label})

M game/beginplay.janet => game/beginplay.janet +18 -14
@@ 1,3 1,5 @@
(import events)

(defn begin-play-phase
  ```
  The bidder has discarded six cards. 


@@ 8,17 10,19 @@
  - `suit`: The led suit of the current trick.
  ```
  [{:meta meta} players {:player bidder :value to-discard}]
    (let [bidder-record (find |(= ($0 :id) bidder) players)
	  current-hand (in bidder-record :hand)
	  other-players (filter |(not= ($0 :id) bidder) players)
	  infos @{} ]
      (each player other-players
	(put infos (keyword (player :id) "_tricks") {:value 0}))
      # It's the first trick; bidder gets one trick for the discard.
      (put infos (keyword bidder "_tricks") {:value 1})
      [{:phase "play"
	:meta (merge meta {:suit :null})
	:info infos
	:stacks {:trick []}}
       [{:event "discard" :player bidder :value to-discard}
	{:event "prompt_play" :player bidder :to "trick" :count 1}]]))
  (let [bidder-record (find |(= ($0 :id) bidder) players)
	current-hand (in bidder-record :hand)
	other-players (filter |(not= ($0 :id) bidder) players)
	infos @{} ]
    (each player other-players
      (put infos (keyword (player :id) "_tricks") 0))
    # It's the first trick; bidder gets one trick for the discard.
    (put infos (keyword bidder "_tricks") 1)
    [{:phase "play"
      :meta (merge meta {:suit :null})
      :info infos
      :stacks {:trick []}}
     (array/concat
      (map |(events/add-info (string ($0 :id) "_tricks") ($0 :id)) players)
      [{:event "discard" :player bidder :value to-discard}
       {:event "prompt_play" :player bidder :to "trick" :count 1}])]))

M game/bid.janet => game/bid.janet +9 -8
@@ 15,9 15,10 @@
  - `high_bid`: The current high bid.
  - `not_passed`: The players that haven't yet passed.
  ```
  [{:meta meta} players action]
  [state players action]
  (if (= action :null) (error {:error "action required"}))
  (let [{:player last-bidder :value last-bid} action
  (let [meta (state :meta)
	{:player last-bidder :value last-bid} action
	{:player previous-high-bidder :bid high-bid} (meta :high_bid)
	# Record the high bid (whether it's a new bid or the existing high bid).
	[high-bid high-bidder] (case last-bid


@@ 41,16 42,16 @@
      (array/push events {:event "clear_decoration" :name "bid_action" :player previous-high-bidder}))
    (if (and (not (nil? high-bidder)) (= 1 (length not-passed)))
      # If all but one have passed, bidding is over.
      [{:meta meta
	# State: Bid -> Discard
	:phase "discard"}
      [(merge state {:meta meta
		     # State: Bid -> Discard
		     :phase "discard"})
       # 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, move to the next bidder.
      [{:meta (put meta :not_passed not-passed)
	#State: Bid -> Bid
	:phase "bid"}
      [(merge state {:meta (put meta :not_passed not-passed)
		     #State: Bid -> Bid
		     :phase "bid"})
       (array/concat
	events
	(events/add-decoration next-bidder "bid_action" "bidding")

M game/deal.janet => game/deal.janet +3 -3
@@ 11,11 11,11 @@

  The first player is prompted to begin bidding.
  ```
  [_state players]
  [state players]
  (let [bidder ((in players 0) :id)]
    # State: Deal -> Bid 
    [{:phase "bid"
      :meta (new-meta players)}
    [(merge state {:phase "bid"
		   :meta (new-meta players)})
     (array/concat
      (all-draw players)
      (events/add-decoration bidder "bid_action" "bidding")

M game/play.janet => game/play.janet +4 -5
@@ 40,7 40,6 @@
			  (map | ($0 :id))
			  (map | (keyword $0 "_tricks"))
			  (map | (info $0))
			  (map | ($0 :value))
			  (sum))]
    (>= total-tricks (+ 6 (bid :count))))) 



@@ 96,7 95,7 @@
    (var new-trick-state @{:info (state :info)
			   :meta @{:bid current-bid}
			   :stacks @{:trick []}})
    (update-in new-trick-state [:info (keyword highest-player "_tricks") :value] inc)
    (update-in new-trick-state [:info (keyword highest-player "_tricks")] inc)

    (case (length ((players 0) :hand)) 
      # There are no more cards in the players' hands; resolve the


@@ 105,13 104,13 @@
      # `play` prompt.)
      0 (let [score-value (contract-value current-bid)
	      opponents (-> players (non-bidding-team current-bid) (keyword))
	      opponents-score (get-in state [:info opponents :value])
	      opponents-score (get-in state [:info opponents])
	      bidders (-> players (bidding-team current-bid) (keyword))
	      bidders-score (get-in state [:info bidders :value])
	      bidders-score (get-in state [:info bidders])
	      bidders-score (if (made-bid? players current-bid (new-trick-state :info))
			      (+ bidders-score score-value)
			      (- bidders-score score-value))]
	  (set new-trick-state {:info {opponents {:value opponents-score} bidders {:value bidders-score}}
	  (set new-trick-state {:info {opponents opponents-score bidders bidders-score}
				:meta {}
				:stacks {}
				:phase "deal"})

M init.janet => init.janet +5 -4
@@ 2,12 2,13 @@
  config
  []
  {:deck "52JJ"
   :stacks [{:name "trick"
   :stacks [{:id "trick"
	     :label "trick"
	     :orientation :up
	     :max-size 4
	     :alignment :stagger}]
   :info [{:name "north_south" :label "North/South" :value 0}
	  {:name "east_west" :label "East/West" :value 0}]})
   :info [{:id "north_south" :label "North/South" :value 0}
	  {:id "east_west" :label "East/West" :value 0}]})

(defn-
  make-player


@@ 21,4 22,4 @@
	     (make-player snd "east_west")
	     (make-player thd "north_south")
	     (make-player fth "east_west")]
   :state {:phase "deal"}})
   :state {:phase "deal" :info {:north_south 0 :east_west 0}}})

M test/bid.janet => test/bid.janet +1 -1
@@ 55,7 55,7 @@
  (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 (= {:name "bid" :count 1 :event "prompt_select" :player "East" :from (bids/available-bids)} east-prompt))
  (is (deep= {:name "bid" :count 1 :event "prompt_select" :player "East" :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]))))


M test/last-card.janet => test/last-card.janet +11 -11
@@ 18,8 18,8 @@
		     :state @{:phase "play"
			      :meta @{:suit "clubs"
				      :bid @{:count 3 :suit "notrumps" :direction "up" :player "North"}}
			      :info @{:north_south @{:value 0}
				      :North_tricks @{:value 0}}
			      :info @{:north_south 0
				      :North_tricks 0}
			      :stacks @{:trick [(merge-into @{:player "East"} CK)
						(merge-into @{:player "South"} D2)
						(merge-into @{:player "West"} S3)]}}


@@ 29,7 29,7 @@
  (let [played-card (merge-into @{:player "North"} CA)]
    (is (deep= @{:bid @{:count 3 :suit "notrumps" :direction "up" :player "North"}} (state :meta)))
    (is (deep= @{:trick []} (state :stacks)))
    (is (deep= @{:North_tricks @{:value 1} :north_south @{:value 0}} (state :info))))
    (is (deep= @{:North_tricks 1 :north_south 0} (state :info))))
  (is (= {:event "clear_decoration" :player "North" :name "play_action"} deco1))
  (is (= {:event "clear_decoration" :player "East" :name "play_action"} deco2))
  (is (= {:event "clear_decoration" :player "South" :name "play_action"} deco3))


@@ 48,16 48,16 @@
		     :state @{:phase "play"
			      :meta @{:suit "clubs"
				      :bid @{:count 3 :suit "notrumps" :direction "up" :player "North"}}
			      :info @{:north_south @{:value 0}
				      :east_west @{:value 1}
				      :North_tricks @{:value 8} :South_tricks @{:value 0}}
			      :info @{:north_south 0
				      :east_west 1
				      :North_tricks 8 :South_tricks 0}
			      :stacks @{:trick [(merge-into @{:player "East"} CK)
						(merge-into @{:player "South"} D2)
						(merge-into @{:player "West"} S3)]}}
		     :action @{:player "North" :name "play" :value CA}})
  (def [state events] (whist/next player-lead))
  (def [deco1 deco2 deco3 deco4] events)
  (is (= {:north_south {:value 6} :east_west {:value 1}} (state :info)))
  (is (= {:north_south 6 :east_west 1} (state :info)))
  (is (= {} (state :meta)))
  (is (= {} (state :stacks)))
  (is (= {:player "North" :name "play_action" :event "clear_decoration"} deco1))


@@ 70,16 70,16 @@
		     :state @{:phase "play"
			      :meta @{:suit "clubs"
				      :bid @{:count 3 :suit "notrumps" :direction "up" :player "North"}}
			      :info @{:north_south @{:value 0}
				      :east_west @{:value 1}
				      :North_tricks @{:value 0} :South_tricks @{:value 0}}
			      :info @{:north_south 0
				      :east_west 1
				      :North_tricks 0 :South_tricks 0}
			      :stacks @{:trick [(merge-into @{:player "East"} CK)
						(merge-into @{:player "South"} D2)
						(merge-into @{:player "West"} S3)]}}
		     :action @{:player "North" :name "play" :value CA}})
  (def [state events] (whist/next player-lead))
  (def [deco1 deco2 deco3 deco4] events)
  (is (= {:north_south {:value -6} :east_west {:value 1}} (state :info)))
  (is (= {:north_south -6 :east_west 1} (state :info)))
  (is (= {} (state :meta)))
  (is (= {} (state :stacks)))
  (is (= {:player "North" :name "play_action" :event "clear_decoration"} deco1))

M test/whist.janet => test/whist.janet +9 -7
@@ 1,3 1,4 @@

(import testament :prefix "")

(import whist)


@@ 16,7 17,8 @@

(deftest init
  (is (= {:players player-state
	  :state {:phase "deal"}}
	  :state {:phase "deal"
		  :info {:north_south 0 :east_west 0}}}
	 (init/init ;players))))

(deftest deal


@@ 24,7 26,7 @@
  (def [north-draw _east-draw _south-draw _west_draw north-decoration north-prompt] events)
  (is (= {:event "draw" :count 12 :player "North"} north-draw))
  (is (= {:value "bidding" :event "add_decoration" :player "North" :name "bid_action"} north-decoration))
  (is (= {:name "bid" :count 1 :event "prompt_select" :player "North" :from (bids/available-bids)} north-prompt)))
  (is (deep= {:name "bid" :count 1 :event "prompt_select" :player "North" :from (bids/available-bids)} north-prompt)))

(deftest discard-prompt
  (def player-completed-bid {:players player-state


@@ 52,13 54,13 @@
				  :meta {:suit :null}}
			  :action {:player "North" :name "discard" :value [C2]}}]
    (def [state events] (whist/next player-discarded))
    (def [north-discard north-prompt] events)
    (def [info1 info2 info3 info4 north-discard north-prompt] events)
    (is (deep= {:event "discard" :value [@{:rank 2 :suit "clubs"}] :player "North"} north-discard))
    (is (= {:count 1 :to "trick" :event "prompt_play" :player "North"} north-prompt))
    (is (deep= {:info @{:West_tricks {:value 0}
			:East_tricks {:value 0}
			:North_tricks {:value 1}
			:South_tricks {:value 0}}
    (is (deep= {:info @{:West_tricks 0
			:East_tricks 0
			:North_tricks 1
			:South_tricks 0}
		:phase "play"
		:meta @{:suit :null}
		:stacks {:trick ()}}