what is ligretto? - universiteit twentejankuper/fp-dag/ligretto.pdfwhat is ligretto? a case study in...
TRANSCRIPT
What is Ligretto? A case study in compositional and functional programming
Peter Achten, Jurriën Stutterheim, László Domoszlai, Rinus Plasmeijer [email protected], [email protected], [email protected], [email protected]
Radboud University Nijmegen, Netherlands, ICIS, MBSD
What is Ligretto?
FP day january 9 2015 / TU Twente 2
• 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
iTask = FP + Interaction + Coordination
FP day january 9 2015 / TU Twente 3
FP TOP
• TOP paradigm for developing distributed multi-user applications • Abstraction of work performed by computers and humans • Tasks are observable and first-class citizens
iTask = FP + Interaction + Coordination
FP day january 9 2015 / TU Twente 4
• Typed abstraction: (Task m) • Coordination via combinator functions (step (>>*), parallel, shared data sources, ...) • Interaction via pure functions and editors
FP TOP viewSharedInformation :: Title [ViewOption r ] (RWShared r w) -> Task r | iTask r viewInformation :: Title [ViewOption 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, ... • Works in any browser
Separation of concerns, part u
FP day january 9 2015 / TU Twente 5
FP model
TOP coordination
:: Card = { back :: Color , front :: Color , no :: Int } :: Color = Red | Green | Blue | Yellow
... updateSharedInformation name [] game >>* [OnValue player_wins] ...
C C D
iTask = FP + Interaction + Coordination
FP day january 9 2015 / TU Twente 6
FP TOP
imageView :: (m -> Image m) -> ViewOption m | iTask m
imageUpdate :: (r -> m) (m -> Image m) (r m -> w) -> UpdateOption r w | iTask m
viewSharedInformation :: Title [ViewOption r ] (RWShared r w) -> Task r | iTask r viewInformation :: Title [ViewOption 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
customized
iTask = FP + Interaction + Coordination
FP day january 9 2015 / TU Twente 7
• Typed abstraction: (Image m) • Interaction via pure functions and editors
FP TOP viewSharedInformation :: Title [ViewOption r ] (RWShared r w) -> Task r | iTask r viewInformation :: Title [ViewOption 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
• Scalable by design • XML-based standard by W3C • Supported by major browsers • Declarative by nature (mostly) • Excellent hit detection
customized
Separation of concerns, part v
FP day january 9 2015 / TU Twente 8
FP model
Graphics.Scalable custom
interaction
TOP coordination
:: Card = { back :: Color , front :: Color , no :: Int } :: Color = Red | Green | Blue | Yellow
... updateSharedInformation name [imageUpdate ...] game >>* [OnValue player_wins] ... C
A compositional Ligretto specification
FP day january 9 2015 / TU Twente 9
Model • entities, relations • rules of the game
Custom Interaction • cards, layout, interaction
Coordination • overall game structure
FP day january 9 2015 / TU Twente 10
Model • entities, relations • rules of the game
:: Card = { back :: Color , front :: Color , no :: Int } :: Color = Red | Green | Blue | Yellow :: Pile :== [Card] :: Player = { color :: Color , row :: [Card] , ligretto :: Pile , hand :: Hand , seed :: Int } :: Hand = { conceal :: Pile , discard :: Pile } :: GameSt = { middle :: [Pile] , players :: [Player] } :: NoOfPlayers :== Int
play_concealed_pile :: Color GameSt -> GameSt play_hand_card :: Color GameSt -> GameSt play_row_card :: Color Int GameSt -> GameSt get_player :: Color GameSt -> Player colors :: NoOfPlayers -> [Color] initial_player :: NoOfPlayers Color Int -> Player
Coordination • overall game structure
FP day january 9 2015 / TU Twente 11
play_Ligretto :: Task (Color,User) play_Ligretto = get currentUser >>= \me -> invite_friends >>= \you -> let us = zip2 (colors (1+length you)) [me:you] in allTasks (repeatn (length us) (get randomInt)) >>= \rs -> 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)
Coordination • overall game structure
FP day january 9 2015 / TU Twente 12
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
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 class <@< attr :: (Image m) (attr m) -> Image m
FP day january 9 2015 / TU Twente 14
same as SVG name-attribute pairs
Custom Interaction • cards, layout, interaction
FP day january 9 2015 / TU Twente 15
card_width = px 58.5 card_height = px 90.0 no_stroke_color Red = Blue no_stroke_color Green = Red no_stroke_color Blue = Green no_stroke_color Yellow = Green instance toSVGColor Color where toSVGColor Red = toSVGColor "darkred" toSVGColor Green = toSVGColor "darkgreen" toSVGColor Blue = toSVGColor "midnightblue" toSVGColor Yellow = toSVGColor "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 }
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
FP day january 9 2015 / TU Twente 17
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}
Custom Interaction • cards, layout, interaction
FP day january 9 2015 / TU Twente 18
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)
Custom Interaction • cards, layout, interaction
FP day january 9 2015 / TU Twente 19
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))
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
FP day january 9 2015 / TU Twente 21
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 )
Custom Interaction • cards, layout, interaction
FP day january 9 2015 / TU Twente 22
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))
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
FP day january 9 2015 / TU Twente 24
1 http://link.springer.com/chapter/10.1007/978-3-319-14675-1_8