~subsetpark/whist

e6f6b42f09d1a337aa177977bcfef34e5da5c3ec — Zach Smith 10 months ago c454910
Add init.janet to lit
M game/beginplay.janet => game/beginplay.janet +1 -1
@@ 14,7 14,7 @@
  (put counters (tricks-counter bidder) 1))


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


M game/bid.janet => game/bid.janet +1 -1
@@ 36,7 36,7 @@
    not-passed)

## Main Bid Function
(defn bid-phase
(defn evaluate-phase
  ```
  The players bid in an auction to name trump. 


M game/deal.janet => game/deal.janet +1 -1
@@ 10,7 10,7 @@
                           :not_passed (zipcoll (map |($0 :id) players) [true true true true])})

## Main Deal Function
(defn deal-phase
(defn evaluate-phase
  ```
  Each player starts with 12 cards. 


M game/discard.janet => game/discard.janet +1 -1
@@ 5,7 5,7 @@
(defn- make-full-bid [high-bid second-bid bidder]
  (merge high-bid second-bid {:player bidder}))

(defn discard-phase
(defn evaluate-phase
  ```
  The bidder has named their full contract. 


M game/play.janet => game/play.janet +1 -1
@@ 162,7 162,7 @@
## Main Play Function
(defn- add-to-stack [stack card] (tuple ;stack card))

(defn play-phase
(defn evaluate-phase
  ```
  Players play to tricks.
  

M init.janet => init.janet +6 -0
@@ 1,7 1,10 @@
## init.janet
## Config
(defn
  config
  []
  {:deck "52JJ"
   :player_count 4
   :stacks [{:id "trick"
	     :label "trick"
	     :orientation :up


@@ 10,6 13,7 @@
   :info [{:id "north_south" :label "North/South" :value 0}
	  {:id "east_west" :label "East/West" :value 0}]})

## Init
(defn-
  make-player
  [id team]


@@ 23,3 27,5 @@
	     (make-player thd "north_south")
	     (make-player fth "east_west")]
   :state {:phase "deal" :info {:north_south 0 :east_west 0}}})



M lit/events.lit => lit/events.lit +1 -1
@@ 95,7 95,7 @@ Finally, if three out of four players pass, then the fourth player
  (array ;bids))
---

@s 
@s bids.janet

--- bids.janet
@{All Bids}

M whist.do => whist.do +1 -1
@@ 1,2 1,2 @@
redo-ifchange whist.lit
lit --md-compiler pandoc whist.lit
\ No newline at end of file
lit whist.lit
\ No newline at end of file

M whist.janet => whist.janet +5 -5
@@ 15,11 15,11 @@
    :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))
    "deal" (deal/evaluate-phase state players)
    "bid" (bid/evaluate-phase state players action)
    "discard" (discard/evaluate-phase state players action)
    "begin_play" (beginplay/evaluate-phase state players action)
    "play" (play/evaluate-phase state players action))

)


M whist.lit => whist.lit +96 -19
@@ 104,7 104,7 @@ Every *next state* describes a change after some single action.

The `state` consists of the state of the game. This includes things
like the cards on the table and the scores for the players, but it
also will include arbitrary metadata that you read and write.
also will include arbitrary metadata that we read and write.

The `players` are a list of the players, their metadata, and their
current hands.


@@ 116,18 116,30 @@ The most basic and important attribute of the game state is the
present.

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.
modules. Each one has the same return signature.

As we shall see, the return value of `next` is always a list with two
elements:

1. The new game state;
2. A list of *events* to be evaluated by the server within the game.

--- 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))
  "deal" (deal/evaluate-phase state players)
  "bid" (bid/evaluate-phase state players action)
  "discard" (discard/evaluate-phase state players action)
  "begin_play" (beginplay/evaluate-phase state players action)
  "play" (play/evaluate-phase state players action))
---

@s

We've broken up our Bid Whist game by those phases. Each phase has a
single module which exposes a single `evaluate-phase` function. Our main
`whist` module simply looks at the phase of the incoming game state
and delegates to the appropriate module.

--- whist.janet 
(import game/deal)
(import game/bid)


@@ 147,7 159,7 @@ There's very little logic in the deal, so it's a good place to
start. We simply pull out the first player and then return.

--- Main Deal Function
(defn deal-phase
(defn evaluate-phase
  ```
  Each player starts with 12 cards. 



@@ 318,16 330,16 @@ thing that happens and there's no player input necessary to evaluate it.
The auction, and the rest of the game to boot, are different. In
@{Deal Events} we prompted the first player for their opening bid;
therefore, we expect that this incoming bid action is the next input
that the server will receive.[^stateless]
that the server will receive.

[^stateless]: Of course, in production, we should expect that we will
**[NOTE]**: Of course, in production, we should expect that we will
be receiving many requests from different ongoing games at
once. That doesn't pose any difficulty as our game rules server is
stateless. Every request will contain everything needed to evaluate
the next state of the game. 

--- Main Bid Function
(defn bid-phase
(defn evaluate-phase
  ```
  The players bid in an auction to name trump. 



@@ 361,9 373,9 @@ In this case, we can reason that two things need to be true for the
auction to be over:

1. There is some high bidder;
2. Everyone else has passed.[^end-criteria]
2. Everyone else has passed.

[^end-criteria]: In a system where we were responsible for the error
**[NOTE]**: In a system where we were responsible for the error
handling and validation, we might want to validate that the high
bidder and the one person left in the set of not-passed players are
*the same player*. In this system, that constraint would only be


@@ 545,7 557,7 @@ kitty and then discard 6 cards.
(defn- make-full-bid [high-bid second-bid bidder]
  (merge high-bid second-bid {:player bidder}))

(defn discard-phase
(defn evaluate-phase
  ```
  The bidder has named their full contract. 



@@ 653,7 665,7 @@ for every other player, and 1 for the bidder.
@{Initial Stacks}
@{Initial Counters}

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



@@ 713,9 725,9 @@ prompting the player.
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].
, we'll be associating an info box like `North_tricks` to a label like `North`

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

@s Play



@@ 732,7 744,7 @@ handle a call in this phase, there are two main cases:
--- Main Play Function
(defn- add-to-stack [stack card] (tuple ;stack card))

(defn play-phase
(defn evaluate-phase
  ```
  Players play to tricks.
  


@@ 1088,4 1100,69 @@ we can wrap up all the components into a single module.
@{Main Play Function}
---

@s Other Endpoints

We have implemented the entirety of our `/next` API, which contains
all of rules for our game. 

In order to have a working rules engine, we need to expose two more
endpoints: one to return some basic configuration values that will be
the case for all Bid Whist sessions, and one to return the initial
game state for a single session.

@s /config

Our `config` function is called by the game server to get the
configuration values which are independent from session to session. In
particular, that includes:

- what deck to use;
- what the table configuration is;
- what the info box configuration is.

--- Config
(defn
  config
  []
  {:deck "52JJ"
   :player_count 4
   :stacks [{:id "trick"
	     :label "trick"
	     :orientation :up
	     :max-size 4
	     :alignment :stagger}]
   :info [{:id "north_south" :label "North/South" :value 0}
	  {:id "east_west" :label "East/West" :value 0}]})
---

@s /init

Finally, we need to expose an `init` function that returns the
starting state for a specific session. This function takes the list of
player IDs in the game and returns an object with `players` and
`state` in it. 

--- Init
(defn-
  make-player
  [id team]
  {:id id :team team})

(defn init
  "Create an initial game state."
  [fst snd thd fth]
  {:players [(make-player fst "north_south")
	     (make-player snd "east_west")
	     (make-player thd "north_south")
	     (make-player fth "east_west")]
   :state {:phase "deal" :info {:north_south 0 :east_west 0}}})
---

@s init.janet

--- init.janet
@{Config}
@{Init}
---

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