~subsetpark/whist

dee5eaf2ad556a7d43b66b252b24126dff213278 — Zach Smith 6 months ago 18a5446
Integrate lit up to begin-play
5 files changed, 178 insertions(+), 34 deletions(-)

M events.janet
M game/beginplay.janet
M lit/events.lit
M whist.janet
M whist.lit
M events.janet => events.janet +13 -5
@@ 1,12 1,10 @@
# events.janet
(import players)

# Events: Pick 1
(defn pick1 [name player choices] {:event "prompt_select" :name name :player player :count 1 :from choices})

# Hand side effects
# Events: Draw
(defn draw [player count] {:event "draw" :player player :count count})

# Decorations
# Events: Add Decoration
(defn add-decoration [player name value] {:event "add_decoration" :name name :player player :value value})



@@ 19,10 17,20 @@
	players2 (players/of-team players (string team2))]
    {:event "end_game" :scores (zipcoll (array/concat players1 players2)
					[score1 score1 score2 score2])}))
# Prompts
# Events: Pick 1
(defn pick1 [name player choices] {:event "prompt_select" :name name :player player :count 1 :from choices})

# Events: Prompt Play
(defn prompt-play
  [player &opt from]
  {:event "prompt_play" :player player :to "trick" :count 1 :from from})

(defn prompt-play [player &opt from] {:event "prompt_play" :player player :to "trick" :count 1 :from from})
# Events: Prompt Discard
(defn prompt-discard [player count] {:event "prompt_discard" :player player :count count})

# State labels
# Events: Add Info
(defn add-info [id label] {:event "add_info" :id id :label label})



M game/beginplay.janet => game/beginplay.janet +16 -7
@@ 1,8 1,19 @@
# game/beginplay.janet
(import events)

(defn- tricks-counter [player-name] (keyword player-name "_tricks"))
# Initial Stacks
(defn- init-stacks [] {:trick []})

# Initial Counters
(defn- tricks-counter [player-name] (keyword player-name "_tricks"))

(defn- init-counters [bidder other-players]
  (var counters (zipcoll (map |(tricks-counter ($0 :id)) other-players)
			 (map (fn [_] 0) other-players)))
  # It's the first trick; bidder gets one trick for the discard.
  (put counters (tricks-counter bidder) 1))


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


@@ 13,16 24,14 @@
  - `suit`: The led suit of the current trick.
  ```
  [{:meta meta} players {:player bidder :value to-discard}]
  (let [other-players (filter |(not= ($0 :id) bidder) players)
	counters (zipcoll (map |(tricks-counter ($0 :id)) other-players)
			  (map (fn [_] 0) other-players))]
    # It's the first trick; bidder gets one trick for the discard.
    (put counters (tricks-counter bidder) 1)
  (let [other-players (filter |(not= ($0 :id) bidder) players)]
    # State: Begin -> Play
    [{:phase "play"
      :meta (merge meta {:suit "undefined"})
      :info counters
      :info (init-counters bidder other-players)
      :stacks (init-stacks)}
     (array/concat
      # TODO: when players have names as well as IDs, this will map to names.
      (map |(events/add-info (tricks-counter ($0 :id)) ($0 :id)) players)
      (events/prompt-play bidder))]))


M lit/events.lit => lit/events.lit +7 -5
@@ 2,9 2,9 @@

--- events.janet
(import players)

@{Events: Pick 1}
# Hand side effects
@{Events: Draw}
# Decorations
@{Events: Add Decoration}
@{Events: Clear Decoration}
(defn end-game


@@ 13,8 13,10 @@
	players2 (players/of-team players (string team2))]
    {:event "end_game" :scores (zipcoll (array/concat players1 players2)
					[score1 score1 score2 score2])}))

(defn prompt-play [player &opt from] {:event "prompt_play" :player player :to "trick" :count 1 :from from})
# Prompts
@{Events: Pick 1}
@{Events: Prompt Play}
@{Events: Prompt Discard}
(defn add-info [id label] {:event "add_info" :id id :label label})
# State labels
@{Events: Add Info}
---
\ No newline at end of file

M whist.janet => whist.janet +6 -1
@@ 5,6 5,7 @@
(import game/beginplay)
(import game/play)

# `next` Function Signature
(defn next
  ```
  Rules engine for Bid Whist.


@@ 12,10 13,14 @@
  [{:state state
    :players players
    :action action}]
  # Return Next State
  (match (state :phase)
    "deal" (deal/deal-phase state players)
    "bid" (bid/bid-phase state players action)
    "discard" (discard/discard-phase state players action)
    "begin_play" (beginplay/begin-play-phase state players action)
    "play" (play/play-phase state players action)))
    "play" (play/play-phase state players action))

)



M whist.lit => whist.lit +136 -16
@@ 19,7 19,7 @@ accepts a POST request and passes the body to `next`. The input to our
function describes the state of the entire session, and the return
value of this function will be to describe *what happens next.*

--- whist.janet
--- `next` Function Signature
(defn next
  ```
  Rules engine for Bid Whist.


@@ 27,7 27,8 @@ value of this function will be to describe *what happens next.*
  [{:state state
    :players players
    :action action}]
  @{Return Next State})
  @{Return Next State}
)
---

The three components of the input are the `state`, the `players`, and


@@ 54,26 55,23 @@ Here we'll match on the game phase to call one of the five main game
modules. Each one has the same return signature and implements the
`next` function.

--- whist.janet :=
--- Return Next State
(match (state :phase)
  "deal" (deal/deal-phase state players)
  "bid" (bid/bid-phase state players action)
  "discard" (discard/discard-phase state players action)
  "begin_play" (beginplay/begin-play-phase state players action)
  "play" (play/play-phase state players action))
---

--- whist.janet 
(import game/deal)
(import game/bid)
(import game/discard)
(import game/beginplay)
(import game/play)

(defn next
  ```
  Rules engine for Bid Whist.
  ```
  [{:state state
    :players players
    :action action}]
  (match (state :phase)
    "deal" (deal/deal-phase state players)
    "bid" (bid/bid-phase state players action)
    "discard" (discard/discard-phase state players action)
    "begin_play" (beginplay/begin-play-phase state players action)
    "play" (play/play-phase state players action)))
@{`next` Function Signature}
---

We'll start with the deal. We see that we only need the game state and


@@ 533,4 531,126 @@ selected will have already been removed from their hand.
Once the declarer has discarded their six cards, they score one trick
and lead to the first trick.

With the beginning of the play phase, we need to keep track of two new
types of data: **stacks** and **info**.

@s Stacks

A stack is any collection of one or more cards besides the deck and
the players' hands. For any game, the stacks make up the table; any
card or pile of cards on the table is part of a stack.

@s Initializing the Stacks

In bid whist, there's only ever one pile of cards on the table: the
cards played to the current trick. In the tamerlane system, the stacks
*state* is quite simple: it's an object mapping the name of a stack to
a list of card values. 

--- Initial Stacks
(defn- init-stacks [] {:trick []})
---

This is part of the game state, and so whatever we return in the state
response will be the content of the stacks in the game. The play phase
begins with a single stack, the `trick`, which is empty.

@s Info

Info is the generic way of recording data that's important to keep
track of the game. Unlike the metadata we've been using above, which
was specific to our purposes and not designed to be exposed directly
to players, there will be some game elements during the play phase
that we do expect to be displayed directly (and which we will want to
manipulate as state, rather than as side effects): namely, each player
will have a count of current tricks and each team will have a score.

--- Initial Counters
(defn- tricks-counter [player-name] (keyword player-name "_tricks"))

(defn- init-counters [bidder other-players]
  (var counters (zipcoll (map |(tricks-counter ($0 :id)) other-players)
			 (map (fn [_] 0) other-players)))
  # It's the first trick; bidder gets one trick for the discard.
  (put counters (tricks-counter bidder) 1))
---

We structure these counters very similarly to the stacks: as an object
mapping the name of the info box to the value it contains.

In this case the names are `<player name>_tricks` and the values are 0
for every other player, and 1 for the bidder.

@s beginplay.janet

--- game/beginplay.janet
(import events)

@{Initial Stacks}
@{Initial Counters}

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

  They may lead any card from their hand.

  Provides:
  - `suit`: The led suit of the current trick.
  ```
  [{:meta meta} players {:player bidder :value to-discard}]
  (let [other-players (filter |(not= ($0 :id) bidder) players)]
    # State: Begin -> Play
    [{:phase "play"
      :meta (merge meta {:suit "undefined"})
      :info (init-counters bidder other-players)
      :stacks (init-stacks)}
     (array/concat
      # TODO: when players have names as well as IDs, this will map to names.
      (map |(events/add-info (tricks-counter ($0 :id)) ($0 :id)) players)
      (events/prompt-play bidder))]))
---

Here we return the state--with two new top-level attributes, `info`
and `stacks`---and the events.

@s Prompt Play

The first new event type we can cover is `prompt_play`.

--- Events: Prompt Play
(defn prompt-play
  [player &opt from]
  {:event "prompt_play" :player player :to "trick" :count 1 :from from})
---

This is the last prompt type that we need. It's structured similarly
to `prompt_discard`, but it has a little more information in it. In
addition to `count`, there's also `to` and `from`.

`to` is required and must be the name of a stack in the game. Unlike
in the case of discards, when a player plays a card it has to *go*
somewhere; given a `to` value, the server will be able to remove the
card from the player's hand and put it on the top of the specified
stack.

Finally, we sometimes need to specify `from`. This will be a list of
cards in the player's hand that they can select from. In bid whist,
players must follow suit if they can; therefore, we will sometimes
have to specify which cards in the hand are of the correct suit when
prompting the player.

@s Add Info

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

We would like to be able to display useful labels on our info
boxes. For that reason we can make an `add_info` event, which simply
associates an info box ID with a more friendly label (in this case
, we'll be associating an info box like `North_tricks` to a label like `North`[^player-names].

[^player-names]: Update this with Player Names.

@include lit/events.lit
\ No newline at end of file