Pitch

Summary

In Pitch, a partnership trick-taking game, players are dealt 6 cards from a shuffled French deck. Players bid on the number of points they think their team can win. The player who bids the highest plays first and sets trump with their first card played. 6 tricks are played, with the winner of each trick leading the next trick. Points are awarded for the high, low, and jack of trump, along with a “game” point for the highest sum of cards collected. The team with the highest score wins.

Rules of Play

These rules are summarized from https://www.pagat.com/allfours/pitch.html.

Pitch is an All Fours point trick-taking game. Other games in this genre are All Fours and Pedro.

Information Flow

Card Movement Graph

The diagram above visualizes information flow in Pitch using a single random rollout in CardStock. Visibility is attached to card locations, so all cards in a location share the same visibility; information is revealed or concealed as cards move between locations and ownership changes.

Public locations are shown in green, hidden locations in yellow, private locations in blue, and memory locations in light gray. Locations containing cards with distinct backs are indicated with bold borders. Rectangles denote ownership (players or table), and directed edges show card movement between locations and resulting visibility changes. Taken information is shown with red dotted edges (public to private/hidden), and shared information with blue dashed edges (private cards transferred to another player).

Branching Factor

Branching Factor Graph

The figure above illustrates the branching factor (y-axis) for each decision point (x-axis) of Pitch, summarized over 100 random games. We define a decision point as a specific moment at which a player must choose from a set of possible actions. Following this definition, a player can be part of multiple distinct decision points during one of their turns, resulting in successive actions.

Decision Spread

Decision Spread Graph

The figure above illustrates the normalized spread (y-axis) for each decision point (x-axis) of Pitch, summarized over 100 games with all MCTS players. At each decision point, a player evaluates the potential moves in terms of their potential final score. The spread is the difference between the best estimated move and the worst estimated move. This value is then divided by the highest spread found during the game to calculate the normalized spread.

Lead History

Lead History Graph

The figure above illustrates the normalized average lead history (y-axis) for each decision point (x-axis) of Pitch, summarized over 100 games with all MCTS players. At each decision point, a player evaluates the potential moves in terms of their potential final score. For the best estimated move, they record the estimated score for all players, assigning them normalized rank estimates, where 1 is first place, and 0 is last place. Across 100 games, these histories are grouped according to final game rank, and then averaged.

RECYCLE Code

The rules for Pitch are coded in RECYCLE, a card game description language, to encourage standardized implementations across different systems.

You can also Download the code.

;; Pitch
;; https://www.pagat.com/allfours/pitch.html

(game
 
 (setup 
  ;; Set up the players, 4 players, in teams of 2
  (create players 4)
  (create teams (1, 3) (2, 4))
  
  ;; Create the deck source
  (create deck (game iloc STOCK) 
   (deck (RANK (ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, 
                EIGHT, NINE, TEN, JACK, QUEEN, KING))
         (COLOR (RED (SUIT (HEARTS, DIAMONDS)))
                (BLACK (SUIT (CLUBS, SPADES)))))))        

 ;; Get ready to play
 (do (
  (shuffle (game iloc STOCK))
  (all player 'P
   (repeat 6 (move (top (game iloc STOCK))
                   (top ('P iloc HAND)))))
  (set (game str LEAD) NONE)
  (set (game str TRUMP) NONE)
  (set (game points GAME)
   (((RANK : ACE)  4) ((RANK : KING) 3) ((RANK : QUEEN) 2)
    ((RANK : JACK) 1) ((RANK : TEN) 10)))))
 
 ;; bidding time!
 (stage player 
  (end (all player 'P (== (game sto BIDS) 4)))

  (choice (
   ;; not the last, you can pass, or you are last and someone already bid
   ((or (!= (game sto BIDS) 3)
        (any player 'P (== ('P str BID) MAX)))
    (inc (game sto BIDS)))

   ;; bid between 2 and 5, must be greater than previous
   (any (range 2..5) 'B
    ((or (all player 'P (> 'B ('P sto BID)))
         (and (== (game sto BIDS) 3) ;; the last player can steal earlier high bid
              (all player 'P (> (+ 'B 1) ('P sto BID)))))
     (do (
      (all player 'P
       ((== ('P str BID) MAX)
        (set ('P str BID) NONE)))
      (set ((current player) str BID) MAX)
      (set ((current player) sto BID) 'B)
      (set ((team (current player)) sto BID) 'B)
      (inc (game sto BIDS)))))))))

 ;; the highest bidder goes first
 (do (
  (all player 'P 
   ((== ('P str BID) MAX)
    (cycle current 'P)))))

 ;; players play a round until their cards are gone    
 (stage player
  (end (all player 'P (== (size ('P iloc HAND)) 0)))
                
  ;; players play a hand once
  (stage player
   (end (all player 'P (> (size ('P vloc TRICK)) 0)))
               
   (choice (
    ;; if first player, play any card, remember it in the lead spot
    ((== (game str LEAD) NONE)                       
     (any ((current player) iloc HAND) 'C
      (do (
       (move 'C (top ((current player) vloc TRICK)))
       (set (game str LEAD) (cardatt SUIT (top ((current player) vloc TRICK))))

       ;; if first player, then you also establish trump with your first card
       ((== (game str TRUMP) NONE)
        (set (game str TRUMP) 
             (cardatt SUIT (top ((current player) vloc TRICK)))))))))

    ;; if following player and can follow SUIT
    ;;   play any card that follows SUIT or TRUMP
    ((and (!= (game str LEAD) NONE)
          (>  (size (filter ((current player) iloc HAND) 'L 
                     (== (cardatt SUIT 'L) (game str LEAD)))) 0))
     (any (filter ((current player) iloc HAND) 'L 
           (or (== (cardatt SUIT 'L) (game str LEAD))
               (== (cardatt SUIT 'L) (game str TRUMP)))) 'C
      (move 'C (top ((current player) vloc TRICK)))))
                                                         
    ;; if following player and cannot follow SUIT
    ;;   play any card
    ((and (!= (game str LEAD) NONE)
          (== (size (filter ((current player) iloc HAND) 'L 
                     (== (cardatt SUIT 'L) (game str LEAD)))) 0))
     (any ((current player) iloc HAND) 'C
      (move 'C (top ((current player) vloc TRICK))))))))
        
  ;; after players play hand, wrap up trick
  (do (
   ;; solidfy card recedence
   (set (game points PRECEDENCE)
    (((SUIT : (game str TRUMP)) 200) ((SUIT : (game str LEAD)) 100)
     ((RANK : ACE)  14) ((RANK : KING) 13) ((RANK : QUEEN) 12)
     ((RANK : JACK) 11) ((RANK : TEN)  10) ((RANK : NINE)   9)
     ((RANK : EIGHT) 8) ((RANK : SEVEN) 7) ((RANK : SIX)    6)
     ((RANK : FIVE)  5) ((RANK : FOUR)  4) ((RANK : THREE)  3)
     ((RANK : TWO)   2)))
             
   ;; determine who won the hand, set them first next time
   (set (game str LEAD) NONE)
   (cycle next (owner (max (union (all player 'P ('P vloc TRICK))) 
                       using (game points PRECEDENCE))))
 
   ;; count the number of tricks won per team
   (inc ((team (next player)) sto SMUDGE))

   ;; discard all the played cards
   (all player 'P (move (top ('P vloc TRICK)) 
                        (top ((next player) vloc TRICKSWON)))))))

 ;; Start points for score
 (do (
  (all player 'P
   (do (
    ;; calculate GAME point sum
    (inc ((team 'P) sto GAMESCORE) 
         (sum ('P vloc TRICKSWON) using (game points GAME)))

    ;; add in HIGH, LOW, JACK from TRUMP
    (let (filter (union (all player 'MP ('MP vloc TRICKSWON))) 'UC 
          (== (cardatt SUIT 'UC) (game str TRUMP))) 'ONLYTRUMP
     (inc ((team 'P) sto TEMPSCORE) 
          (size (filter ('P vloc TRICKSWON) 'C 
                 (and (== (cardatt SUIT 'C)
                          (game str TRUMP))
                      (or (== (cardatt RANK 'C) ;; HIGH
                              (cardatt RANK (max 'ONLYTRUMP 
                                             using (game points PRECEDENCE))))
                          (== (cardatt RANK 'C) ;; LOW
                              (cardatt RANK (min 'ONLYTRUMP 
                                             using (game points PRECEDENCE))))
                          (== (cardatt RANK 'C) JACK))))))))))))

 ;; Calculate team scores         
 (stage team
  (end (all player 'P (== (size ('P vloc TRICKSWON)) 0)))
  (do (
   ((> ((current team) sto GAMESCORE) ((next team) sto GAMESCORE))
    (inc ((current team) sto TEMPSCORE)))

   ;; nonbidding team, get any points scored
   ((all (current team) 'TP (!= ('TP str BID) MAX))
    (inc ((current team) sto SCORE) ((current team) sto TEMPSCORE)))

   ;; determine if bid was made, otherwise set back
   ((any (current team) 'TP (== ('TP str BID) MAX))
    (do (
     ((and (<= ((current team) sto BID) 4)
           (>= ((current team) sto TEMPSCORE) ((current team) sto BID)))
      (inc ((current team) sto SCORE) ((current team) sto TEMPSCORE)))
     ((and (<= ((current team) sto BID) 4)
           (< ((current team) sto TEMPSCORE) ((current team) sto BID)))
      (dec ((current team) sto SCORE) ((current team) sto BID)))

     ;; calculate smudge
     ((and (== ((current team) sto BID) 5)
           (== ((current team) sto SMUDGE) 6)
           (== ((current team) sto TEMPSCORE) 4))
      (inc ((current team) sto SCORE) 5))
     ((and (== ((current team) sto BID) 5)
           (or (!= ((current team) sto SMUDGE) 6)
               (!= ((current team) sto TEMPSCORE) 4)))
      (dec ((current team) sto SCORE) 5)))))

   ;; discard cards
   (all (current team) 'TP
    (repeat all
     (move (top ('TP vloc TRICKSWON))
           (top (game vloc DISCARD))))))))
                                   
 (scoring max ((team (current player)) sto SCORE)))