What is Ligretto? A case study in compositional and functional programming Peter Achten, Jurriën Stutterheim, László Domoszlai, Rinus Plasmeijer P.Achten@cs.ru.nl, j.stutterheim@cs.ru.nl, dlacko@gmail.com, rinus@cs.ru.nl Radboud University Nijmegen, Netherlands, ICIS, MBSD What is Ligretto? • Card game for 2-12 players • Simultaneous, not turn-based • Player cards get moved to middle • Player cards get moved per player • It's hectic and fun! Features: • Application logic • Interaction • Coordination FP TOP iTask FP day january 9 2015 / TU Twente 2 iTask = FP + Interaction + Coordination • TOP paradigm for developing distributed multi-user applications • Abstraction of work performed by computers and humans • Tasks are observable and first-class citizens FP TOP FP day january 9 2015 / TU Twente 3 iTask = FP + Interaction + Coordination • Typed abstraction: (Task m) • Coordination via combinator functions (step (>>*), parallel, shared data sources, ...) • Interaction via pure functions and editors FP viewSharedInformation viewInformation :: Title [ViewOption :: Title [ViewOption r r ] (RWShared r w) -> Task r | iTask r ] r -> Task r | iTask r updateInformation :: Title [UpdateOption r r] r -> Task r | iTask r updateSharedInformation :: Title [UpdateOption r w] (RWShared r w) -> Task w | iTask r & iTask w TOP • Compositional • Implementation tool chain: • editlets, JavaScript, HTML, ... • Works in any browser FP day january 9 2015 / TU Twente 4 Separation of concerns, part u :: Card = { back :: Color , front :: Color , no :: Int } :: Color = Red | Green | Blue | Yellow FP C model ... updateSharedInformation name [] game >>* [OnValue player_wins] ... D FP day january 9 2015 / TU Twente C TOP coordination 5 zed i m o cust iTask = FP + Interaction + Coordination imageView :: (m -> Image m) -> ViewOption m | iTask m FP viewSharedInformation viewInformation :: Title [ViewOption :: Title [ViewOption r r ] (RWShared r w) -> Task r | iTask r ] r -> Task r | iTask r updateInformation :: Title [UpdateOption r r] r -> Task r | iTask r updateSharedInformation :: Title [UpdateOption r w] (RWShared r w) -> Task w | iTask r & iTask w TOP imageUpdate :: (r -> m) (m -> Image m) (r m -> w) -> UpdateOption r w | iTask m FP day january 9 2015 / TU Twente 6 zed i m o cust iTask = FP + Interaction + Coordination • Typed abstraction: (Image m) • Interaction via pure functions and editors FP viewSharedInformation viewInformation :: Title [ViewOption :: Title [ViewOption r r ] (RWShared r w) -> Task r | iTask r ] r -> Task r | iTask r updateInformation :: Title [UpdateOption r r] r -> Task r | iTask r updateSharedInformation :: Title [UpdateOption r w] (RWShared r w) -> Task w | iTask r & iTask w • Compositional • Implementation tool chain: • editlets, JavaScript, HTML, SVG • Works in any browser FP day january 9 2015 / TU Twente • • • • • TOP Scalable by design XML-based standard by W3C Supported by major browsers Declarative by nature (mostly) Excellent hit detection 7 Separation of concerns, part v :: Card = { back , front , no :: Color = Red | | Blue | FP model :: Color :: Color :: Int } Green Yellow C ... updateSharedInformation name [imageUpdate ...] game >>* [OnValue player_wins] ... Graphics.Scalable TOP custom coordination interaction FP day january 9 2015 / TU Twente 8 A compositional Ligretto specification Model • entities, relations • rules of the game Custom Interaction • cards, layout, interaction FP day january 9 2015 / TU Twente Coordination • overall game structure 9 Model • entities, relations • rules of the game :: Card :: :: :: :: :: :: = { back :: , front :: , no :: Color = Red | Green | Pile :== [Card] Player = { color :: , row :: , ligretto :: , hand :: , seed :: Hand = { conceal :: , discard :: GameSt = { middle :: , players :: NoOfPlayers :== Int play_concealed_pile play_hand_card play_row_card get_player colors initial_player FP day january 9 2015 / TU Twente :: :: :: :: :: :: Color Color Int } Blue | Yellow Color [Card] Pile Hand Int } Pile Pile } [Pile] [Player] } Color GameSt Color GameSt Color Int GameSt Color GameSt NoOfPlayers NoOfPlayers Color Int -> -> -> -> -> -> GameSt GameSt GameSt Player [Color] Player 10 Coordination • overall game structure play_Ligretto play_Ligretto = >>= \me -> >>= \you -> >>= \rs :: Task (Color,User) get currentUser invite_friends let us = zip2 (colors (1+length you)) [me:you] in allTasks (repeatn (length us) (get randomInt)) -> let game = { middle = repeatn 16 [] , players = [ initial_player (length us) c (abs r) \\ (c,_) <- us & r <- rs] } in withShared game (play_game us) invite_friends :: Task [User] invite_friends = enterSharedMultipleChoice "Select friends to play with" [] users >>= \you -> if (not (isMember (length you) [1..3])) ( viewInformation "Oops" [] "number of friends must be 1, 2, or 3" >>| invite_friends ) (return you) FP day january 9 2015 / TU Twente 11 Coordination • overall game structure play_game :: [(Color,User)] (Shared GameSt) -> Task (Color,User) play_game us game_st = anyTask [u @: play (c,u) game_st \\ (c,u) <- us] >>= \w -> allTasks [u @: accolades w (c,u) game_st \\ (c,u) <- us] >>| return w play :: (Color,User) (Shared GameSt) -> Task (Color,User) play (c,u) game_st = updateSharedInformation (toString u) [imageViewUpdate id (player_perspective c) (\_ st -> st)] game_st >>* [OnValue player_wins] where player_wins (Value game _) | isEmpty (get_player c game).ligretto = Just (return (c,u)) player_wins _ = Nothing accolades :: (Color,User) (Color,User) (Shared GameSt) -> Task GameSt accolades (_,winner) (c,_) game_st = viewSharedInformation ("The winner is " <+++ winner) [imageView (player_perspective c)] game_st FP day january 9 2015 / TU Twente 12 Custom Interaction • cards, layout, interaction • Compositional images: • which are the image patterns? • which are the layout patterns? • which are the image layers? • Interactive images: • which (composite) images are interactive? FP day january 9 2015 / TU Twente 13 Graphics.Scalable concepts, part u • Every value of type (Image m): • • • • is infinitely wide and perfectly transparent has a local coordinate system defined by the span box measures are Span values (px :: Real -> Span) can be subject to transformation: scaling, skewing, rotating, flipping, and masking • Basic images: the usual suspects • empty, text, circle, ellipse, rect, polygon, polyline, xline, yline, line • Image attributes • attributes alter appearance but not span box same as SVG name-attribute pairs class <@< attr :: (Image m) (attr m) -> Image m FP day january 9 2015 / TU Twente 14 Custom Interaction • cards, layout, interaction card_width card_height no_stroke_color no_stroke_color no_stroke_color no_stroke_color = px 58.5 = px 90.0 Red Green Blue Yellow = = = = Blue Red Green Green instance toSVGColor Color where toSVGColor Red = toSVGColor toSVGColor Green = toSVGColor toSVGColor Blue = toSVGColor toSVGColor Yellow = toSVGColor "darkred" "darkgreen" "midnightblue" "gold" card_shape = rect card_width card_height <@< {xradius = card_height /. 18} <@< {yradius = card_height /. 18} cardfont size = normalFontDef "Verdana" size big_no no color = text (cardfont 20.0) (toString no) <@< {fill = toSVGColor "white"} <@< {stroke = toSVGColor color } ligretto color = text (cardfont 12.0) "Ligretto" <@< {fill = toSVGColor "none" } <@< {stroke = toSVGColor color } FP day january 9 2015 / TU Twente 15 Graphics.Scalable concepts, part v • Image composition is just stacking (z-axis) and aligning (x-, y-axis) :: Layout m :== [ImageOffset] [Image m] (Host m) -> Image m :: Host m :== Maybe (Image m) :: ImageOffset :== (Span, Span) collage :: Layout m • Derived image composition functions: overlay, beside, above, grid, margin FP day january 9 2015 / TU Twente 16 Custom Interaction • cards, layout, interaction card_image :: SideUp Card -> Image m card_image side card | side === Front = let no = margin (px 5.0) (big_no card.no (no_stroke_color card.front)) in overlay [(AtMiddleX,AtTop),(AtMiddleX,AtBottom)] [] [no, rotate (deg 180.0) no] host | otherwise = overlay [(AtLeft,AtBottom)] [] [skewy (deg -20.0) (ligretto card.back)] host where host = Just (card_shape <@< {fill = if (side === Front) (toSVGColor card.front) (toSVGColor "white")}) card = {back = Red, front = Green, no = 7} FP day january 9 2015 / TU Twente 17 Custom Interaction • cards, layout, interaction no_card_image :: Image m no_card_image = overlay [(AtMiddleX,AtMiddleY)] [] [text (cardfont 12.0) "empty"] (Just (card_shape <@< {fill = toSVGColor "lightgrey"})) pile_of_cards :: SideUp Pile -> Image m pile_of_cards side pile = overlay [] [(zero,card_height /. 18 *. dy) \\ dy <- [0..]] (map (card_image side) (reverse pile)) host where host = Just no_card_image pile_image :: SideUp Pile -> Image m pile_image side pile | no_of_cards > 10 = above [AtMiddleX] [] [text (cardfont 10.0) (toString no_of_cards) ,top_cards_image] Nothing | otherwise = top_cards_image where no_of_cards = length pile top_cards_image = pile_of_cards side (take 10 pile) FP day january 9 2015 / TU Twente 18 Custom Interaction • cards, layout, interaction middle_image :: [Pile] -> Image m middle_image middle = circular (card_height *. 2) (2.0 * pi) (map (pile_image Front) middle)) circular :: Span Real [Image m] -> Image m circular r a imgs = overlay (repeat (AtMiddleX,AtMiddleY)) [ (~r *. cos angle, ~r *. sin angle) \\ i <- [0.0, sign_a ..] , angle <- [i * alpha - pi / 2.0] ] [ rotate (rad (i * alpha)) img \\ i <- [0.0, sign_a ..] & img <- imgs ] (Just (empty (r *. 2) (r *. 2))) where sign_a = toReal (sign a) alpha = (toRad (normalize (rad a)))/(toReal (length imgs)) FP day january 9 2015 / TU Twente 19 Graphics.Scalable concepts, part w • Any (compositional) image is made interactive with the on-click attribute: :: OnClickAttr m = { onclick :: (m -> m) } instance <@< OnClickAttr tuneIf :: Bool (Image m) (attr m) -> Image m | <@< attr tuneIf yes img attr = if yes (img <@< attr) img FP day january 9 2015 / TU Twente 20 Custom Interaction • cards, layout, interaction row_images :: Bool [Card] -> [Image GameSt] row_images interactive row = [ tuneIf interactive (card_image Front row_card) {onclick = play_row_card row_card.back no} \\ row_card <- row & no <- [1..] ] hand_images :: Bool Hand Color -> [Image GameSt] hand_images interactive {conceal,discard} c = [ tuneIf interactive (pile_image Back conceal) {onclick = play_concealed_pile c}, space , tuneIf interactive (pile_image Front discard) {onclick = play_hand_card c} ] space = empty (card_width /. 4) zero player_image :: Bool Span Player -> Image GameSt player_image interactive r player = circular r (0.4*pi) ( row_images interactive player.row ++ [space, pile_image Front player.ligretto, space] ++ hand_images interactive player.hand player.color ) FP day january 9 2015 / TU Twente 21 Custom Interaction • cards, layout, interaction game_image :: Color GameSt -> Image GameSt game_image color game = overlay (repeat (AtMiddleX,AtMiddleY)) [] [ rotate (rad (i * angle - 0.25 * pi)) img \\ img <- [ player_image (player.color === color) r player \\ player <- game.players ] & i <- [0.0, 1.0..] ] (Just (middle_image game.middle)) where r = card_height *. 4 angle = 2.0 * pi / (toReal (length game.players)) player_perspective :: Color GameSt -> Image GameSt player_perspective color game = rotate (rad (0.05 * pi - toReal my_no * angle)) (game_image color game) where my_no = hd [i \\ player <- game.players & i <- [0 .. ] | player.color === color ] angle = 2.0 * pi / (toReal (length game.players)) FP day january 9 2015 / TU Twente 22 Graphics.Scalable to SVG challenges • SVG transformations vs Graphics.Scalable transformations • SVG maintains a Current Transformation Matrix (CTM) for each image’s axes, not the images • Every transformation alters the CTM, therefore the axes • SVG users need to think about the order in which transformations are applied • Graphics.Scalable transforms the images, not the axes • Text widths can only be calculated on the client • Width crucial for layout combinators • Round-trip to client required • SVG API is lacking regarding font metrics • ascent, descent, and x-height FP day january 9 2015 / TU Twente 23 Current and future work • More performance improvements • Complete the event model (currently limited to on-click) • Tonic: An Infrastructure to Graphically Represent the Definition and Behaviour of Tasks1 (TFP'14) • Extend Graphics.Scalable layout formalism to include task layout 1 http://link.springer.com/chapter/10.1007/978-3-319-14675-1_8 FP day january 9 2015 / TU Twente 24