Groundwork for #19 - Eventing framework.

This commit is contained in:
Gareth Latty
2015-12-12 17:59:08 +00:00
parent 452b936271
commit 6329290d77
11 changed files with 268 additions and 82 deletions
+16 -2
View File
@@ -1,8 +1,12 @@
module MassiveDecks.Actions.Action where
import Task
import Effects
import MassiveDecks.Models.State exposing (InitialState)
import MassiveDecks.Models.Game exposing (Lobby, LobbyAndHand)
import MassiveDecks.Models.Player exposing (Secret)
import MassiveDecks.Models.Player as Player
import MassiveDecks.Actions.Event exposing (Event, events)
type APICall a
@@ -16,7 +20,7 @@ type Action
| UpdateInputValue String String
| NewLobby (APICall Lobby)
| JoinExistingLobby
| JoinLobby String Secret (APICall LobbyAndHand)
| JoinLobby String Player.Secret (APICall LobbyAndHand)
| AddDeck (APICall LobbyAndHand)
| StartGame (APICall LobbyAndHand)
| Pick Int
@@ -29,3 +33,13 @@ type Action
| NextRound
| SetInitialState InitialState
| AnimatePlayedCards (List Int)
| GameEvent Event
eventEffects : Lobby -> Lobby -> Effects.Effects Action
eventEffects oldLobby newLobby
= events oldLobby newLobby
|> List.map GameEvent
|> List.map Task.succeed
|> List.map Effects.task
|> Effects.batch
+107
View File
@@ -0,0 +1,107 @@
module MassiveDecks.Actions.Event where
import MassiveDecks.Models.Game exposing (..)
import MassiveDecks.Models.Player exposing (..)
import MassiveDecks.Models.Card exposing (..)
import MassiveDecks.Util as Util
type Event
= PlayerJoin Id
| PlayerStatus Id Status
| PlayerScore Id Int
| RoundStart Call Id
| RoundPlayed Int
| RoundJudging (List PlayedCards)
| RoundEnd Call Id (List PlayedCards) PlayedByAndWinner
events : Lobby -> Lobby -> List Event
events oldLobby newLobby = List.concat
[ diffPlayers oldLobby.players newLobby.players
, diffRound oldLobby.round newLobby.round
]
diffPlayers : List Player -> List Player -> List Event
diffPlayers oldPlayers newPlayers =
List.concatMap (diffPlayer oldPlayers) newPlayers
diffPlayer : List Player -> Player -> List Event
diffPlayer oldPlayers newPlayer =
let
id = newPlayer.id
oldPlayer = List.filter (\player -> player.id == id) oldPlayers |> List.head
in
case oldPlayer of
Just oldPlayer ->
List.concat
[ if oldPlayer.status /= newPlayer.status then [ playerStatus newPlayer ] else []
, if oldPlayer.score /= newPlayer.score then [ playerScore newPlayer ] else []
]
Nothing ->
Util.apply [ playerJoin, playerStatus, playerScore ] newPlayer
diffRound : Maybe Round -> Maybe Round -> List Event
diffRound oldRound newRound =
let
differentRound = Maybe.map .call oldRound /= Maybe.map .call newRound
in
if differentRound then
List.filterMap identity
[ Maybe.map roundEnd oldRound
, Maybe.map roundStart newRound
]
else
Maybe.map2 changedRound oldRound newRound |> Maybe.withDefault []
changedRound : Round -> Round -> List Event
changedRound oldRound newRound =
case oldRound.responses of
Hidden oldCount ->
case newRound.responses of
Hidden newCount -> if (oldCount < newCount) then [ roundPlayed (newCount - oldCount) ] else []
Revealed _ -> [ roundJudging newRound ]
Revealed _ ->
[]
{- Event Constructors -}
playerJoin : Player -> Event
playerJoin player = PlayerJoin player.id
playerStatus : Player -> Event
playerStatus player = PlayerStatus player.id player.status
playerScore : Player -> Event
playerScore player = PlayerScore player.id player.score
roundStart : Round -> Event
roundStart round = RoundStart round.call round.czar
roundPlayed : Int -> Event
roundPlayed amount = RoundPlayed amount
roundJudging : Round -> Event
roundJudging round =
let
responses = case round.responses of
Hidden _ -> Nothing
Revealed responses -> Just responses
played = Maybe.map .cards responses |> Maybe.withDefault []
in
RoundJudging played
roundEnd : Round -> Event
roundEnd round =
let
responses = case round.responses of
Hidden _ -> Nothing
Revealed responses -> Just responses
played = Maybe.map .cards responses |> Maybe.withDefault []
playedByAndWinner = responses `Maybe.andThen` .playedByAndWinner |> Maybe.withDefault (PlayedByAndWinner [] 0)
in
RoundEnd round.call round.czar played playedByAndWinner
+31 -6
View File
@@ -7,7 +7,7 @@ import Html exposing (Html)
import MassiveDecks.Models.Player exposing (Secret)
import MassiveDecks.Models.Game exposing (Lobby)
import MassiveDecks.Models.State exposing (Model, State(..), ConfigData, PlayingData, Error, Global)
import MassiveDecks.Actions.Action exposing (Action(..), APICall(..))
import MassiveDecks.Actions.Action exposing (Action(..), APICall(..), eventEffects)
import MassiveDecks.UI.Config as UI
import MassiveDecks.API as API
import MassiveDecks.Playing as Playing
@@ -27,13 +27,17 @@ update action global data = case action of
|> API.toEffect)
AddDeck (Result lobbyAndHand) ->
(model global { data | lobby = lobbyAndHand.lobby }, Effects.none)
let
(data, effects) = updateLobby data lobbyAndHand.lobby
in
(model global data, effects)
StartGame Request ->
(model global data, (API.newGame data.lobby.id data.secret) |> Task.map (StartGame << Result) |> API.toEffect)
StartGame (Result lobbyAndHand) ->
(Playing.model global (PlayingData lobbyAndHand.lobby lobbyAndHand.hand data.secret [] Nothing Nothing []), Effects.none)
(Playing.model global (PlayingData lobbyAndHand.lobby lobbyAndHand.hand data.secret [] Nothing Nothing []),
eventEffects data.lobby lobbyAndHand.lobby)
Notification lobby ->
case lobby.round of
@@ -41,13 +45,26 @@ update action global data = case action of
(API.getLobbyAndHand lobby.id data.secret)
|> Task.map (\lobbyAndHand -> JoinLobby lobby.id data.secret (Result lobbyAndHand))
|> API.toEffect)
Nothing -> (model global { data | lobby = lobby }, Effects.none)
Nothing ->
let
(data, effects) = updateLobby data lobby
in
(model global data, effects)
JoinLobby lobbyId secret (Result lobbyAndHand) ->
case lobbyAndHand.lobby.round of
Just _ -> (Playing.model global (PlayingData lobbyAndHand.lobby lobbyAndHand.hand secret [] Nothing Nothing []), Effects.none)
Nothing -> (model global { data | lobby = lobbyAndHand.lobby }, Effects.none)
Just _ ->
(Playing.model global (PlayingData lobbyAndHand.lobby lobbyAndHand.hand secret [] Nothing Nothing []),
eventEffects data.lobby lobbyAndHand.lobby)
Nothing ->
let
(data, effects) = updateLobby data lobbyAndHand.lobby
in
(model global data, effects)
GameEvent _ ->
(model global data, Effects.none)
other ->
(model global data,
DisplayError ("Got an action (" ++ (toString other) ++ ") that can't be handled in the current state (Config).")
@@ -55,6 +72,14 @@ update action global data = case action of
|> Effects.task)
updateLobby : ConfigData -> Lobby -> (ConfigData, Effects.Effects Action)
updateLobby data lobby =
let
events = eventEffects data.lobby lobby
in
({ data | lobby = lobby }, events)
model : Global -> ConfigData -> Model
model global data =
{ state = SConfig data
+8
View File
@@ -24,6 +24,14 @@ type alias Round =
}
type alias FinishedRound =
{ call : Call
, czar : Id
, responses : (List PlayedCards)
, playedByAndWinner : PlayedByAndWinner
}
type alias Lobby =
{ id : String
, config : Config
+4 -4
View File
@@ -3,9 +3,9 @@ module MassiveDecks.Models.State where
import Random
import Html exposing (Attribute)
import MassiveDecks.Models.Card exposing (Hand)
import MassiveDecks.Models.Game exposing (Config, Lobby, Round)
import MassiveDecks.Models.Player exposing (Player, Secret)
import MassiveDecks.Models.Card exposing (Hand, Call, PlayedCards)
import MassiveDecks.Models.Game exposing (Config, Lobby, Round, FinishedRound)
import MassiveDecks.Models.Player exposing (Player, Secret, Id, PlayedByAndWinner)
type alias Model =
@@ -47,7 +47,7 @@ type alias PlayingData =
, secret : Secret
, picked : List Int
, considering : Maybe Int
, lastFinishedRound : Maybe Round
, lastFinishedRound : Maybe FinishedRound
, shownPlayed : List Attribute
}
+40 -50
View File
@@ -9,9 +9,10 @@ import Random exposing (Generator, Seed, list, bool, int)
import MassiveDecks.Models.Player exposing (Secret)
import MassiveDecks.Models.Card as Card
import MassiveDecks.Models.Game exposing (Lobby)
import MassiveDecks.Models.Game exposing (Lobby, FinishedRound)
import MassiveDecks.Models.State exposing (State(..), Model, ConfigData, PlayingData, Error, Global)
import MassiveDecks.Actions.Action exposing (Action(..), APICall(..))
import MassiveDecks.Actions.Action exposing (Action(..), APICall(..), eventEffects)
import MassiveDecks.Actions.Event exposing (Event(..))
import MassiveDecks.UI.Playing as UI
import MassiveDecks.API as API
import MassiveDecks.Util as Util
@@ -39,30 +40,22 @@ update action global data = case action of
(model global data, (API.play data.lobby.id data.secret data.picked) |> Task.map (Play << Result) |> API.toEffect)
Play (Result lobbyAndHand) ->
let
(data, effect, seed) = (updateLobby data lobbyAndHand.lobby global.seed)
in
(model { global | seed = seed }
{ data | hand = lobbyAndHand.hand
(model global
{ data | lobby = lobbyAndHand.lobby
, hand = lobbyAndHand.hand
, picked = []
}, effect)
}, eventEffects data.lobby lobbyAndHand.lobby)
Notification lobby ->
case lobby.round of
Just _ ->
let
(data, effect, seed) = (updateLobby data lobby global.seed)
in
(model { global | seed = seed } data, effect)
Just _ -> (model global { data | lobby = lobby }, eventEffects data.lobby lobby)
Nothing -> (configModel global (ConfigData lobby data.secret ""), Effects.none)
JoinLobby lobbyId secret (Result lobbyAndHand) ->
case lobbyAndHand.lobby.round of
Just _ ->
let
(data, effect, seed) = (updateLobby data lobbyAndHand.lobby global.seed)
in
(model { global | seed = seed } data, effect)
Just _ -> (model global { data | lobby = lobbyAndHand.lobby
, hand = lobbyAndHand.hand
}, eventEffects data.lobby lobbyAndHand.lobby)
Nothing -> (configModel global (ConfigData lobbyAndHand.lobby data.secret ""), Effects.none)
Consider potentialWinner ->
@@ -72,13 +65,11 @@ update action global data = case action of
(model global data, (API.choose data.lobby.id data.secret winner) |> Task.map (Choose winner << Result) |> API.toEffect)
Choose winner (Result lobbyAndHand) ->
let
(data, effect, seed) = (updateLobby data lobbyAndHand.lobby global.seed)
in
(model { global | seed = seed }
{ data | hand = lobbyAndHand.hand
(model global
{ data | lobby = lobbyAndHand.lobby
, hand = lobbyAndHand.hand
, picked = []
}, effect)
}, eventEffects data.lobby lobbyAndHand.lobby)
NextRound ->
(model global { data | lastFinishedRound = Nothing }, Effects.none)
@@ -89,6 +80,20 @@ update action global data = case action of
in
(model { global | seed = seed } { data | shownPlayed = shownPlayed }, Effects.none)
GameEvent event ->
case event of
RoundPlayed amount ->
let
(shownPlayed, effects, seed) = addShownPlayed amount data.shownPlayed global.seed
in
(model { global | seed = seed } { data | shownPlayed = shownPlayed }, effects)
RoundEnd call czar responses playedByAndWinner ->
(model global { data | lastFinishedRound = Just (FinishedRound call czar responses playedByAndWinner) }, Effects.none)
_ ->
(model global data, Effects.none)
other ->
(model global data,
DisplayError ("Got an action (" ++ (toString other) ++ ") that can't be handled in the current state (Playing).")
@@ -96,34 +101,19 @@ update action global data = case action of
|> Effects.task)
updateLobby : PlayingData -> Lobby -> Seed -> (PlayingData, Effects.Effects Action, Seed)
updateLobby data lobby seed =
addShownPlayed : Int -> List Attribute -> Seed -> (List Attribute, Effects.Effects Action, Seed)
addShownPlayed amount existing seed =
let
lastFinishedRound = if (Maybe.map .call data.lobby.round) == (Maybe.map .call lobby.round) then
data.lastFinishedRound
else
data.lobby.round
oldShownPlayed = data.shownPlayed
oldShownPlayedLength = (List.length oldShownPlayed)
(shownPlayed, effects, resultSeed) = case Maybe.map .responses data.lobby.round of
Just (Card.Hidden amount) ->
let
(newShownPlayed, newSeed) =
Random.generate (list (amount - oldShownPlayedLength) initialRandomPositioning) seed
in
(List.concat [ oldShownPlayed, newShownPlayed ]
,(Task.sleep (Time.millisecond * 250)
`Task.andThen`
\_ -> (Task.succeed (AnimatePlayedCards (Util.range oldShownPlayedLength (List.length newShownPlayed)))))
|> Effects.task
,newSeed)
_ -> ([], Effects.none, seed)
existingLength = List.length existing
(new, newSeed) = Random.generate (list (amount - existingLength) initialRandomPositioning) seed
in
({ data | lobby = lobby
, lastFinishedRound = lastFinishedRound
, shownPlayed = shownPlayed
}, effects, resultSeed)
( List.concat [ existing, new ]
, (Task.sleep (Time.millisecond * 250)
`Task.andThen`
\_ -> (Task.succeed (AnimatePlayedCards (Util.range existingLength (List.length new)))))
|> Effects.task
, newSeed
)
updatePositioning : List Int -> List Attribute -> Seed -> (List Attribute, Seed)
updatePositioning toAnimate existing seed =
+1 -1
View File
@@ -109,7 +109,7 @@ emptyDeckListInfo display =
startGameWarning : Bool -> Html
startGameWarning canStart = if canStart then text "" else
span [] [ icon "info-circle", text "You will need at least two players to start the game." ]
span [] [ icon "info-circle", text " You will need at least two players to start the game." ]
startGameButton : Signal.Address Action -> Bool -> Bool -> Html
+17 -2
View File
@@ -49,12 +49,27 @@ score player = tr [ class (statusName player.status), title (statusDescription p
appHeader : List Html -> Html
appHeader contents = (header [] [ div [ class "mui-appbar mui--appbar-line-height" ]
[ div [ class "mui--appbar-line-height" ]
[ button [ class "scores-toggle mui-btn mui-btn--small mui-btn--primary mui--visible-xs-inline-block js-show-scores" ] [ icon "users" ]
, button [ class "scores-toggle mui-btn mui-btn--small mui-btn--primary mui--hidden-xs js-hide-scores" ] [ icon "users" ]
[ span [] (List.append [ scoresButton True, scoresButton False ] (scoresBadge 0))
, span [ id "title", class "mui--text-title mui--visible-xs-inline-block" ] contents
, gameMenu ] ] ])
scoresButton : Bool -> Html
scoresButton shown =
let
showHideClasses = if shown then " mui--hidden-xs js-hide-scores" else " mui--visible-xs-inline-block js-show-scores"
in
button [ class ("scores-toggle mui-btn mui-btn--small mui-btn--primary badged" ++ showHideClasses)] [ icon "users" ]
scoresBadge : Int -> List Html
scoresBadge events =
if events > 0 then
[ div [ class "badge" ] [ icon "exclamation" ] ]
else
[]
statusDescription : Status -> String
statusDescription status = case status of
NotPlayed -> "Choosing"
+6 -17
View File
@@ -7,7 +7,7 @@ import Html.Events exposing (..)
import MassiveDecks.Models.State exposing (PlayingData, Error, Global)
import MassiveDecks.Models.Card as Card
import MassiveDecks.Models.Player exposing (Player, Id, Status(..))
import MassiveDecks.Models.Game exposing (Round)
import MassiveDecks.Models.Game exposing (Round, FinishedRound)
import MassiveDecks.Models.Card exposing (Response, Responses(..), PlayedCards)
import MassiveDecks.Actions.Action exposing (Action(..), APICall(..))
import MassiveDecks.UI.Lobby as LobbyUI
@@ -73,26 +73,15 @@ consideringView address considering consideringCards isCzar =
let
extra = if isCzar then [ chooseButton address considering ] else []
in
ol [ class "considering" ]
ol [ class "considering" ]
(List.append (List.map (\card -> li [] [ (playedResponse card) ]) consideringCards) extra)
winnerContentsAndHeader : Signal.Address Action -> Round -> List Player -> (List Html, List Html)
winnerContentsAndHeader : Signal.Address Action -> FinishedRound -> List Player -> (List Html, List Html)
winnerContentsAndHeader address round players =
let
winning = case round.responses of
Revealed revealed ->
(Card.winningCards revealed.cards)
|> Maybe.andThen revealed.playedByAndWinner
|> Maybe.withDefault []
Hidden _ -> []
winner = case round.responses of
Revealed revealed ->
revealed.playedByAndWinner
|> Maybe.map .winner
|> Maybe.map (Util.get players)
|> Maybe.map .name
|> Maybe.withDefault ""
Hidden _ -> ""
cards = round.responses
winning = Card.winningCards cards round.playedByAndWinner |> Maybe.withDefault []
winner = (Util.get players round.playedByAndWinner.winner).name
in
([ div [ class "winner mui-panel" ]
[ h1 [] [ icon "trophy" ]
+5
View File
@@ -17,6 +17,7 @@ interleave list1 list2 =
y :: ys ->
y :: x :: interleave xs ys
remove : List a -> Int -> List a
remove list index =
(List.take index list) ++ (List.drop (index + 1) list)
@@ -49,3 +50,7 @@ inOrder : List (Generator a) -> Generator (List a)
inOrder generators = case generators of
[] -> Random.map (\_ -> []) Random.bool
head :: tail -> head `Random.andThen` (\value -> Random.map ((::) value) (inOrder tail))
apply : List (a -> b) -> a -> List b
apply fs value = List.map (\f -> f value) fs
+33
View File
@@ -233,6 +233,39 @@ ol.played {
padding: 0px;
}
.badge {
display: inline-flex;
width: 20px;
height: 20px;
border-radius: 50% 50%;
font-size: 14px;
line-height: 14px;
position: relative;
z-index: 10;
left: -25px;
top: -10px;
align-items: center;
justify-content: center;
overflow: visible;
white-space: nowrap;
background: #E91E63;
color: #ffffff;
@media (min-width: @degrade-width) {
display: none !important;
}
}
body.hide-scores {
.badge {
display: inline-flex !important;
}
}
body {
#scores {
position: fixed;