Search code examples
scalafunctional-programmingmonadsscala-catsfor-comprehension

Passing State monad result form one step to another in a program + stop early


I have these steps:

trait BlackjackSteps {
    def gamerTakesTwoCards(gamerName:String): State[Deck, Gamer]
    def dealerTakesTwoCards: State[Deck, Dealer]
    def isBlackjack(gamer: Gamer, dealer: Dealer): Option[Player]
    def gamerDrawsCards(gamer: Gamer): State[Deck, Gamer]
    def dealerDrawsCards(dealer: Dealer, gamer: Gamer): State[Deck, Dealer]
    def determineWinner(gamer: Gamer, dealer: Dealer): Player

    def program(gamerName:String): State[Deck, Player] = for {
      gamer <- gamerTakesTwoCards(gamerName)
      dealer <- dealerTakesTwoCards
      //winner = isBlackjack(gamer, dealer)
      gamerFinal <- gamerDrawsCards(gamer)
      dealerFinal <- dealerDrawsCards(dealer, gamerFinal)
      winnerFinal = determineWinner(gamerFinal, dealerFinal)
    } yield  winnerFinal
  }

Two questions:

  • How do i get the Deck resulting from gamerTakesTwoCards and pass it to dealerTakesTwoCards?

  • isBlackjack may result in a winner in which case I need to stop and return winner. How can I change the above code to do that?

The Game:

  • a gamer and dealer play
  • they both draw two cards
  • if no 21 winner
  • players keep drawing cards until 17
  • highest player not over 21 points wins!

Complete code here: https://bitbucket.org/jameskingconsulting/blackjack-scala/src/master/

Edit:

I've de-sugared the for-comprehension just make clear what's happening:

def program(gamerName:String): State[Deck, Player] =
      gamerTakesTwoCards(gamerName).flatMap( gamer =>
        dealerTakesTwoCards.flatMap(dealer =>
          isBlackjack(gamer, dealer).fold(

            gamerDrawsCards(gamer).flatMap( gamerFinal =>
              dealerDrawsCards(dealer, gamerFinal).map( dealerFinal =>
                determineWinner(gamerFinal, dealerFinal)
              )
            )

          )(State.pure[Deck, Player])
        ))

Solution

    1. Nothing to do. That's the whole point.
    2. You have to return a winner in both cases, regardless of whether isBlackjack returns a None or a Some. Either way, you have to return a State[Deck, Player]. For example, you could achieve it with fold on the Option, mapping the success-case through pure:

      def program(gamerName:String): State[Deck, Player] = for {
        gamer <- gamerTakesTwoCards(gamerName)
        dealer <- dealerTakesTwoCards
        winner <- isBlackjack(gamer, dealer).fold(for {
          gamerFinal <- gamerDrawsCards(gamer)
          dealerFinal <- dealerDrawsCards(dealer, gamerFinal)
          winnerFinal = determineWinner(gamerFinal, dealerFinal)
        } yield winnerFinal)(State.pure[Deck, Player])
      } yield winner