Search code examples
functional-programmingfrpelm

Elm beginner: trying to write blackjack


I'm doing an independent study on Elm, and I feel like I'm learning to program all over again! As a learn-the-language project, I'm trying to get an easy blackjack up and running, but once I started I realized how much I still don't grasp. I have as far as drawing cards from a deck and adding them to a list:

import Random
import Mouse
import Array

--Build the deck
faces = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
suits = ['H', 'D', 'C', 'S']

allCards faces suits =
  case suits of
    x :: xs -> family faces x ++ allCards faces xs
    _ -> []

family faces suit =
  case faces of
    x :: xs -> (,) x suit :: family xs suit
    _ -> []

deck = allCards faces suits

rand : Signal Int
rand = Random.range 0 (length deck-1) Mouse.clicks 

pickCard n = head <| drop n deck
nextCard = lift pickCard rand

yourHand = foldp (::) [] nextCard

main = lift asText yourHand

My questions are mostly on how to continue. Looking at completed Elm projects helps a little, but many of them are hard for me to parse as a beginner. Any kind of direction helps!

  1. One of the first problems I had was trying to figure out how to remove cards from the deck once they're drawn, using something like dropCard deck card = filter (\card /= nextCard) deck to filter out the drawn card from the list. But my understanding of Elm is that every time a signal changes, the program re-evaluates, meaning that the deck is recreated in full every time a card is drawn. Would I need to foldp the original deck as well?

  2. What's the proper way to remove an element from one list and add it to another in functional programming? Function composition, like toHand . dropCard card?

  3. For adding card faces to determine winning/losing, I'm not sure how to get the integer value out of the list. I tried doing fst (head deck), but I got type errors, probably because deck is itself a signal of some kind. Is there something I'm not seeing?

That said, I've really enjoyed Elm thus far!


Solution

  • Questions

    1. For simple programs, the easiest way to think about signals is by thinking about what kinds of things in your program can change. In your case that would be the deck and the hand. Then you take these things and make a datastructure in which to store them. Then you do a foldp over that whole datastructure, so instead of just keeping track of the hand you also keep track of the deck.
    2. You can write a function that takes a list and an index, and returns the item at that index in the list and the list with that item removed. Then you add the item to the other list and you're done.
    3. fst (head deck) should work. Perhaps you forgot to remove lift in the main definition when you tried it?

    Example code

    -- This first part is your code:
    import Random
    import Mouse
    import Array
    
    --Build the deck
    faces = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
    suits = ['H', 'D', 'C', 'S']
    
    allCards faces suits =
      case suits of
        x :: xs -> family faces x ++ allCards faces xs
        _ -> []
    
    family faces suit =
      case faces of
        x :: xs -> (,) x suit :: family xs suit
        _ -> []
    
    -- Here come my additions/changes:
    -- Naming some types for clarity
    type Card = (Int,Char)
    type ProgramState = { deck : [Card], hand : [Card] }
    
    getFromList : Int -> [a] -> (a,[a])
    getFromList index list =
      let prefix = take index list
          (item :: postfix) = drop index list
      in (item, prefix ++ postfix)
    
    startState : ProgramState
    startState = { deck = allCards faces suits, hand = [] }
    
    rand : Signal Float
    rand = Random.float Mouse.clicks 
    
    rFloatToInt : Float -> Int -> Int -> Int
    rFloatToInt rnd lo hi = round ((rnd + toFloat lo) * toFloat hi)
    
    pickCard : Float -> ProgramState -> ProgramState
    pickCard rnd {deck,hand} = 
      let index = rFloatToInt rnd 0 (length deck - 1)
          (item, newDeck) = getFromList index deck
      in { deck = newDeck, hand = item :: hand }
    
    programState : Signal ProgramState
    programState = foldp pickCard startState rand
    
    main : Signal Element
    main = lift asText programState
    

    If anything is unclear, let me know.