# 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 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 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 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