card_game_engine 0.0.0

An extensible card game engine
Documentation
# A simple game of Uno

data Color = Red | Green | Blue | Yellow

data Number = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine

data Card = 
    | Simple { color: Color, number: Number } 
    | Plus2 { color: Color } 
    | Plus4 
    | Reverse { color: Color } 
    | PickColor

zone Deck { } unique

zone Discard { color: Red, number: Some Zero } unique

zone Hand { } per player

data ActivePlayer = ActivePlayer { player: Player }

allColors = [Red, Green, Blue, Yellow]
allNumbers = [Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine]

allCards :: [Card]
allCards = [Simple { color , number } | color <- allColors, number <- allNumbers]
        ++ [Plus2 { color } | color <- allColors]
        ++ [Reverse { color } | color <- allColors]
        ++ [Plus4, Plus4, Plus4, Plus4]
        ++ [PickColor, PickColor, PickColor, PickColor]

# When the game starts, each player gets 7 cards

startGame = do
    let deck = game.getZone Deck
    game.insertObjects deck (shuffle (map (card = game.createObject [ card ]) allCards))
    for player in game.getPlayers do
        let hand = player.getZone Hand
        let cards = takeObjects deck 7
        game.insertObjects hand cards
    game.setValue ActivePlayer { player: (takeRandom game.getPlayers) }

on GameStart run startGame

action PlayCard = PlayCard { object: Object }
action PlayCardWithColor = PlayCardWithColor { object: Object, color: Color }
action DrawCard = DrawCard

# The active player is always allowed to play cards from their hand

activePlayerCanPlayFromHand = do
    let ActivePlayer { player } = game.getValue
    let hand = player.getZone Hand
    for card in hand.getObjects do
        match card
            when Simple { .. } | Reverse { .. } | Plus2 { .. } do
                player.addAction PlayCard
            when Plus4 | PickColor do
                player.addAction PlayCardWithColor

while GameRunning run activePlayerCanPlayFromHand

# Playing a normal card is fine, as long as color and/or numbers match!

playedCard player PlayCard { object } = do
    let discard = game.getZone Discard
    let currentColor = discard.color
    let currentNumber = discard.number
    let card: Card = object.getValue
    match card
        when Simple { color, number } do
            if !(color == currentColor || (Some number) = currentNumber) then
                raise InvalidAction
        when Plus2 { color } do
            if color != currentColor then
                raise InvalidAction
        when Reverse { color } do
            if color != currentColor then
                raise InvalidAction
            game.reversePlayerOrder
        otherwise do
            raise BuggedState
    let hand = player.getZone hand
    game.insertObjects discard [(game.removeObject hand object)]
    game.moveToNextPlayer    

on Action<PlayCard> run playedCard

# Players can choose to play a special card, but have to give their choice of
# color

playedChoiceCard player PlayCardWithColor { object, color } = do
    let discard = game.getZone Discard
    let card: Card = object.getValue
    match card
        when Plus4 | PickColor do
            discard.color = color
        otherwise do
            raise BuggedState
    let hand = player.getZone hand
    game.insertObjects discard [(game.removeObject hand object)]
    game.moveToNextPlayer    

on Action<PlayCardWithColor> run playedCard

# Players can choose to draw cards

playerDrawCard player DrawCard = do
    let deck = game.getZone Deck
    let hand = player.getZone hand
    game.insertObjects hand (game.takeObjects deck 1)
    game.moveToNextPlayer

on Action<DrawCard> run playerDrawCard

# When a card is played, let's update the allowed colors!

setColorFromPlayedCard object = do
    let discard = game.getZone Discard    
    let card: Card = object.getValue
    match card
        when Simple { color, number } do
            discard.color = color
            discard.number = Some number
        when Plus2 { color } | Reverse { color } do
            discard.color = color
            discard.number = None
        when Plus4 | PickColor do
            # Value is set when card is played

on ObjectMoved { to: Discard { .. }, .. } run setColorFromPlayedCard

# Playing special cards causes that player to take two cards and skips their
# turn

handlePlayedSpecialCards object = do
    let deck = game.getZone Deck
    let card: Card = object.getValue
    let ActivePlayer { player } = game.getValue
    let hand = player.getZone Hand
    match card
        when Plus2 { .. } do
            game.insertObjects hand (game.takeObjects deck 2)
            game.moveToNextPlayer
        when Plus4 do
            game.insertObjects hand (game.takeObjects deck 4)
            game.moveToNextPlayer
        otherwise do
            # Nothing!

on ObjectMoved { to: Discard { .. }, from: Hand { .. } } run handlePlayedSpecialCards

# If after playing a card, a players hand is empty, then they win!

playerWinsGame player = do
    let hand = player.getZone Hand
    if hand.isEmpty then
        game.playerWinGame player

on PlayerTurnEnd run playerWinsGame


# Whenever a card is taken from the deck, if it is empty afterwards, we shuffle
# the discard pile into the deck

shuffleDiscardIntoDeck _ = do
    let deck = game.getZone Deck
    let discard = game.getZone Discard
    if deck.isEmpty then
        game.insertObjects deck (shuffle (game.takeAllObjects discard))

on ObjectMoved { from: Deck, .. } run shuffleDiscardIntoDeck