Add game history view.

This commit is contained in:
Gareth Latty
2019-07-31 11:07:35 +01:00
parent 110acdb70f
commit fa8d80bec2
16 changed files with 288 additions and 67 deletions
+61 -38
View File
@@ -19,10 +19,10 @@ import MassiveDecks.Card.Model as Card
import MassiveDecks.Card.Play as Play exposing (Play)
import MassiveDecks.Components as Components
import MassiveDecks.Game.Action as Action
import MassiveDecks.Game.Action.Model as Action exposing (Action)
import MassiveDecks.Game.Action.Model exposing (Action)
import MassiveDecks.Game.History as History
import MassiveDecks.Game.Messages exposing (..)
import MassiveDecks.Game.Model exposing (..)
import MassiveDecks.Game.Player as Player exposing (Player)
import MassiveDecks.Game.Round as Round exposing (Round)
import MassiveDecks.Game.Round.Complete as Complete
import MassiveDecks.Game.Round.Judging as Judging
@@ -191,40 +191,53 @@ update msg model =
Redraw ->
( model, Actions.redraw )
ToggleHistoryView ->
( { model | viewingHistory = not model.viewingHistory }, Cmd.none )
subscriptions : Model -> Sub Global.Msg
subscriptions model =
subscriptions _ =
Sub.none
view : Shared -> Lobby.Auth -> Config -> Dict User.Id User -> Model -> Html Global.Msg
view shared auth config users model =
view : Shared -> Lobby.Auth -> String -> Config -> Dict User.Id User -> Model -> Html Global.Msg
view shared auth name config users model =
let
{ instruction, action, content, fillCallWith } =
gameView =
if model.viewingHistory then
History.view shared config users name model.game.history
else
viewRound shared auth config users model
in
Html.div [ HtmlA.id "game" ] gameView
viewRound : Shared -> Lobby.Auth -> Config -> Dict User.Id User -> Model -> List (Html Global.Msg)
viewRound shared auth config users model =
let
( call, { instruction, action, content, fillCallWith } ) =
case model.completeRound of
Just completeRound ->
Complete.view shared (Round.C completeRound /= game.round) config users completeRound
( completeRound.call, Complete.view shared True config users completeRound )
Nothing ->
case game.round of
Round.P round ->
Playing.view auth config model round
( round.call, Playing.view auth config model round )
Round.R round ->
Revealing.view auth config round
( round.call, Revealing.view auth config round )
Round.J round ->
Judging.view auth config round
( round.call, Judging.view auth config round )
Round.C round ->
Complete.view shared False config users round
( round.call, Complete.view shared False config users round )
game =
model.game
{ call } =
Round.data game.round
parts =
fillCallWith |> List.map .body
@@ -257,27 +270,23 @@ view shared auth config users model =
in
Html.div [ HtmlA.id "context-help" ] helpContent
in
Html.div [ HtmlA.id "game" ]
[ minorActions shared auth game
, help
, Html.div [ HtmlA.class "round" ]
[ renderedCall
, viewAction shared action
]
, content
, Html.div [ HtmlA.class "scroll-top-spacer" ] []
[ minorActions shared auth game
, help
, Html.div [ HtmlA.class "round" ] [ renderedCall, viewAction shared action ]
, content
, Html.div [ HtmlA.class "scroll-top-spacer" ] []
-- TODO: Hide this when at top. Waiting on native elm scroll events, as currently we'd have to ping constantly.
, Html.div [ HtmlA.class "scroll-top" ]
[ Wl.button
[ WlA.flat
, WlA.fab
, WlA.inverted
, ScrollToTop |> lift |> HtmlE.onClick
]
[ Icon.view Icon.arrowUp ]
-- TODO: Hide this when at top. Waiting on native elm scroll events, as currently we'd have to ping constantly.
, Html.div [ HtmlA.class "scroll-top" ]
[ Wl.button
[ WlA.flat
, WlA.fab
, WlA.inverted
, ScrollToTop |> lift |> HtmlE.onClick
]
[ Icon.view Icon.arrowUp ]
]
]
minorActions : Shared -> Lobby.Auth -> Game -> Html Global.Msg
@@ -288,13 +297,24 @@ minorActions shared auth game =
in
Html.div [ HtmlA.id "minor-actions" ]
(List.filterMap identity
[ Maybe.map2 (\score -> \reboot -> rebootButton shared score reboot)
[ Just (historyButton shared)
, Maybe.map2 (\score -> \reboot -> rebootButton shared score reboot)
(localPlayer |> Maybe.map .score)
game.rules.houseRules.reboot
]
)
historyButton : Shared -> Html Global.Msg
historyButton shared =
Components.iconButton
[ HtmlA.id "history-button"
, Strings.ViewGameHistoryAction |> Lang.title shared
, ToggleHistoryView |> lift |> HtmlE.onClick
]
Icon.history
rebootButton : Shared -> Int -> Rules.Reboot -> Html Global.Msg
rebootButton shared score reboot =
Components.iconButton
@@ -401,20 +421,23 @@ applyGameEvent auth gameEvent model =
newPlayers =
game.players |> Dict.update winner (Maybe.map (\p -> { p | score = p.score + 1 }))
newRound =
case model.game.round of
( newRound, history ) =
case game.round of
Round.J r ->
let
plays =
r.plays |> List.filterMap (resolvePlayedBy playedBy) |> Dict.fromList
complete =
Round.complete r.id r.czar r.players r.call plays winner
in
Round.complete r.id r.czar r.players r.call plays winner |> Round.C
( complete |> Round.C, complete :: game.history )
_ ->
-- TODO: Error
model.game.round
( game.round, game.history )
in
( { model | game = { game | round = newRound, players = newPlayers } }, Cmd.none )
( { model | game = { game | round = newRound, players = newPlayers, history = history } }, Cmd.none )
Events.RoundStarted { id, czar, players, call, drawn } ->
let
@@ -0,0 +1,87 @@
module MassiveDecks.Game.History exposing (view)
import Dict exposing (Dict)
import FontAwesome.Solid as Icon
import Html exposing (Html)
import Html.Attributes as HtmlA
import Html.Events as HtmlE
import Html.Keyed as HtmlK
import MassiveDecks.Card.Call as Call
import MassiveDecks.Card.Model as Card
import MassiveDecks.Card.Response as Response
import MassiveDecks.Components as Components
import MassiveDecks.Game.Messages exposing (Msg(..))
import MassiveDecks.Game.Round as Round
import MassiveDecks.Game.Round.Plays as Plays
import MassiveDecks.Messages as Global
import MassiveDecks.Model exposing (Shared)
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
import MassiveDecks.Pages.Lobby.Messages as Lobby
import MassiveDecks.Strings as Strings
import MassiveDecks.Strings.Languages as Lang
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Maybe as Maybe
view : Shared -> Config -> Dict User.Id User -> String -> List Round.Complete -> List (Html Global.Msg)
view shared config users name history =
[ Html.div [ HtmlA.id "minor-actions" ]
[ Components.iconButton
[ HtmlA.id "return-to-game-button"
, Strings.ViewGameHistoryAction |> Lang.title shared
, ToggleHistoryView |> lift |> HtmlE.onClick
]
Icon.arrowLeft
]
, Html.div [ HtmlA.id "history" ]
[ Html.h2 [] [ Html.text name ]
, HtmlK.ol
[ HtmlA.class "historic-rounds"
, HtmlA.reversed True
, HtmlA.style "counter-reset"
("historic-round-number " ++ (history |> List.length |> (+) 1 |> String.fromInt))
]
(history |> List.map (viewRound shared config users))
]
]
{- Private -}
viewRound : Shared -> Config -> Dict User.Id User -> Round.Complete -> ( String, Html msg )
viewRound shared config users round =
let
winning =
round.plays |> Dict.get round.winner |> Maybe.withDefault [] |> List.map .body
in
( Round.idString round.id
, Html.li [ HtmlA.class "historic-round" ]
[ Html.div [ HtmlA.class "historic-call with-byline" ]
[ Plays.byLine shared users round.czar (Just Icon.gavel)
, Html.div [] [ Call.viewFilled shared config Card.Front [] winning round.call ]
]
, HtmlK.ul [ HtmlA.class "plays cards" ]
(round.plays |> Dict.toList |> List.map (viewPlay shared config users round.winner))
]
)
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, List Card.Response ) -> ( String, Html msg )
viewPlay shared config users winner ( id, responses ) =
let
cards =
responses |> List.map (\r -> ( r.details.id, Html.li [] [ Response.view config Card.Front [] r ] ))
in
( id
, Html.li [ HtmlA.class "with-byline" ]
[ Plays.byLine shared users id (Icon.trophy |> Maybe.justIf (winner == id))
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
]
)
lift : Msg -> Global.Msg
lift msg =
msg |> Lobby.GameMsg |> Global.LobbyMsg
@@ -17,3 +17,4 @@ type Msg
| SetPlayStyles PlayStyles
| AdvanceRound
| Redraw
| ToggleHistoryView
@@ -26,6 +26,7 @@ type alias Model =
, hand : List Card.Response
, playStyles : PlayStyles
, completeRound : Maybe Round.Complete
, viewingHistory : Bool
}
@@ -37,6 +38,7 @@ emptyModel game =
, hand = []
, playStyles = Dict.empty
, completeRound = Nothing
, viewingHistory = False
}
@@ -12,6 +12,7 @@ module MassiveDecks.Game.Round exposing
, complete
, data
, idDecoder
, idString
, judging
, noPick
, playing
@@ -40,6 +41,11 @@ idDecoder =
Json.string |> Json.map Id
idString : Id -> String
idString (Id id) =
id
{-| The stage of the round.
-}
type Stage
@@ -1,7 +1,6 @@
module MassiveDecks.Game.Round.Complete exposing (view)
import Dict exposing (Dict)
import FontAwesome.Icon as Icon
import FontAwesome.Solid as Icon
import Html exposing (Html)
import Html.Attributes as HtmlA
@@ -11,13 +10,12 @@ import MassiveDecks.Card.Response as Response
import MassiveDecks.Game.Action.Model as Action
import MassiveDecks.Game.Model exposing (..)
import MassiveDecks.Game.Round as Round
import MassiveDecks.Game.Round.Plays as Plays
import MassiveDecks.Messages as Global
import MassiveDecks.Model exposing (..)
import MassiveDecks.Pages.Lobby.Configure.Model as Configure exposing (Config)
import MassiveDecks.Strings as Strings
import MassiveDecks.Strings.Languages as Lang
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Html as Html
import MassiveDecks.Util.Maybe as Maybe
@@ -40,19 +38,11 @@ viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, List C
viewPlay shared config users winner ( id, responses ) =
let
cards =
responses |> List.map (\r -> ( r.details.id, Response.view config Card.Front [] r ))
playedBy =
users |> Dict.get id |> Maybe.map .name |> Maybe.withDefault (Strings.UnknownUser |> Lang.string shared)
byline =
[ Icon.view Icon.trophy |> Maybe.justIf (winner == id) |> Maybe.withDefault Html.nothing
, Html.text playedBy
]
responses |> List.map (\r -> ( r.details.id, Html.li [] [ Response.view config Card.Front [] r ] ))
in
( id
, Html.li [ HtmlA.class "play-with-byline" ]
[ Html.span [ HtmlA.class "byline" ] byline
, Html.li [ HtmlA.class "with-byline" ]
[ Plays.byLine shared users id (Icon.trophy |> Maybe.justIf (winner == id))
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
]
)
@@ -1,10 +1,17 @@
module MassiveDecks.Game.Round.Plays exposing (Details, view)
module MassiveDecks.Game.Round.Plays exposing (Details, byLine, view)
import Dict exposing (Dict)
import FontAwesome.Icon as Icon exposing (Icon)
import Html exposing (Html)
import Html.Attributes as HtmlA
import Html.Events as HtmlE
import Html.Keyed as HtmlK
import MassiveDecks.Card.Play as Play
import MassiveDecks.Model exposing (Shared)
import MassiveDecks.Strings as Strings
import MassiveDecks.Strings.Languages as Lang
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Html as Html
import MassiveDecks.Util.Html.Attributes as HtmlA
@@ -25,6 +32,20 @@ view class picked details =
HtmlK.ul [ HtmlA.classList [ ( class, True ), ( "cards", True ), ( "plays", True ) ] ] (details |> List.map (viewPlay picked))
{-| Create a byline.
-}
byLine : Shared -> Dict User.Id User -> User.Id -> Maybe Icon -> Html msg
byLine shared users id icon =
let
name =
users |> Dict.get id |> Maybe.map .name |> Maybe.withDefault (Strings.UnknownUser |> Lang.string shared)
in
Html.span [ HtmlA.class "byline", HtmlA.title name ]
[ icon |> Maybe.map Icon.view |> Maybe.withDefault Html.nothing
, Html.text name
]
{- Private -}
+3 -5
View File
@@ -503,7 +503,7 @@ viewLobby shared configure auth lobby =
[ viewUsers shared auth.claims.uid lobby.users game
, Html.div [ HtmlA.id "scroll-frame" ]
[ lobby.game
|> Maybe.map (Game.view shared auth lobby.config lobby.users)
|> Maybe.map (Game.view shared auth lobby.name lobby.config lobby.users)
|> Maybe.withDefault (Configure.view shared privileged configure auth.claims.gc lobby lobby.config)
]
]
@@ -624,10 +624,8 @@ viewUser shared player game ( userId, user ) =
, clickable
, HtmlA.id id
]
[ Html.div [ HtmlA.class "user compressed-terms" ]
(Html.span [ HtmlA.title user.name ] [ Html.text user.name ]
:: secondary
)
[ Html.span [ HtmlA.class "user", HtmlA.title user.name ] [ Html.text user.name ]
, Html.div [ HtmlA.class "compressed-terms" ] secondary
, Html.span [ WlA.listItemSlot WlA.AfterItem ] score
]
, menu
+1
View File
@@ -185,6 +185,7 @@ type MdString
| Revealing -- A description of the stage of the round where the czar is revealing the plays.
| Judging -- A description of the stage of the round where the czar is picking a winner.
| Complete -- A description of the stage of the round where it is finished.
| ViewGameHistoryAction -- A description of the action of viewing the history of the game.
-- Instructions
| WhatToDo -- A description of the action of asking for help on what to do in the game at this time.
| PlayInstruction { numberOfCards : Int } -- Instruction to the player on how to play cards.
@@ -650,6 +650,9 @@ translate mdString =
Complete ->
[ Text "Finished" ]
ViewGameHistoryAction ->
[ Text "View previous rounds from this game." ]
-- Instructions
WhatToDo ->
[ Text "What should I be doing?" ]
+5 -1
View File
@@ -6,7 +6,7 @@ $response-background: $call-color;
$card-width: 18em;
$card-margin: 0.5em;
$card-overlap: 2em;
$card-overlap: 4em;
$card-ratio: 5/7;
$card-compact-ratio: 1;
@@ -15,6 +15,10 @@ $card-compact-ratio: 1;
padding-top: #{calc(1 / #{$ratio} * 100%)};
}
@function height($width, $ratio) {
@return calc(#{$width} * 1 / #{$ratio});
}
@function strip-unit($value) {
@return $value / ($value * 0 + 1);
}
+24 -1
View File
@@ -4,6 +4,7 @@
@import "./game/judging";
@import "./game/playing";
@import "./game/plays";
@import "./game/history";
@import "./pages/lobby/variables";
// The main game area.
@@ -47,9 +48,11 @@
right: 0;
z-index: 1;
--card-padding: 0.5em;
margin: 1rem;
#context-help-button {
font-size: 1.2rem;
margin: 0;
}
wl-popover-card {
@@ -65,9 +68,11 @@
flex-direction: column;
transition: left 0.3s;
z-index: 1;
margin: 1rem;
wl-button {
font-size: 1.2rem;
margin: 0;
}
}
@@ -113,7 +118,6 @@
flex-direction: column;
justify-content: center;
align-items: center;
min-width: $card-width;
padding: 1em 0;
}
@@ -137,12 +141,31 @@ ul.cards {
list-style: none;
padding: 0;
margin-top: 0.5em;
li {
min-width: $card-width;
}
}
// Multiple cards that are grouped together.
ol.card-set {
margin: 0.5em;
display: flex;
flex-wrap: wrap;
justify-content: center;
list-style: none;
padding: 0;
li {
height: $card-overlap;
overflow: visible;
&:hover {
z-index: 1;
}
&:last-child {
height: height($card-width, var(--card-aspect-ratio));
}
}
}
+57
View File
@@ -0,0 +1,57 @@
@import "../colors";
@import "../card/size";
#history {
h2 {
text-align: center;
font-size: 2em;
margin: 1em 3em;
word-break: break-word;
}
.historic-rounds {
list-style: none;
padding: 0;
counter-reset: historic-round-number;
.historic-round:nth-child(even) {
background-color: transparentize($primary-light, 0.6);
}
.historic-round:nth-child(odd) {
background-color: transparentize($secondary-light, 0.6);
}
}
.historic-round {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
counter-increment: historic-round-number -1;
padding: 1em 0 0.5em 0;
&::before {
display: flex;
flex-direction: column;
justify-content: center;
content: counter(historic-round-number);
align-self: stretch;
font-size: 3em;
padding: 0 0.2em;
font-weight: bold;
}
.historic-call {
flex-basis: $card-width;
min-width: $card-width;
& > div {
margin: 0.5em;
}
}
.plays {
justify-content: center;
}
}
}
+7 -1
View File
@@ -1,14 +1,20 @@
// A play with the user's name.
.play-with-byline {
.with-byline {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.5em;
}
// The user's name.
.byline {
font-size: 2em;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
max-width: 100%;
overflow: hidden;
svg {
margin-right: 0.5em;
+4 -6
View File
@@ -98,18 +98,16 @@
transition: width 0.3s;
--list-item-padding: 0.5em;
--list-item-left-margin: 0.5em;
.collapsible {
padding: 1em;
}
.user {
position: relative;
display: flex;
.term {
margin-left: 0.5em;
}
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
ol {
@@ -43,6 +43,7 @@ export const handle: gameAction.Handler<Judge> = (
winner: play.playedBy
};
lobby.game.round = completedRound;
lobby.game.history.splice(0, 0, completedRound);
return {
lobby,
events: [event.targetAll(roundFinished.of(completedRound))],