Allow liking plays once the round is complete.

This commit is contained in:
Gareth Latty
2020-09-06 16:27:35 +01:00
parent 4670403b60
commit de7cf37d75
27 changed files with 4798 additions and 19364 deletions
+3450 -2183
View File
File diff suppressed because it is too large Load Diff
+33 -32
View File
@@ -22,60 +22,61 @@
"> 0.2%"
],
"dependencies": {
"@fortawesome/fontawesome-free": "^5.12.1",
"@material/card": "^6.0.0",
"@material/mwc-button": "^0.15.0",
"@material/mwc-fab": "^0.15.0",
"@material/mwc-icon-button": "^0.15.0",
"@material/mwc-linear-progress": "^0.15.0",
"@material/mwc-menu": "^0.15.0",
"@material/mwc-select": "^0.15.0",
"@material/mwc-slider": "^0.15.0",
"@material/mwc-switch": "^0.15.0",
"@material/mwc-tab": "^0.15.0",
"@material/mwc-tab-bar": "^0.15.0",
"@material/mwc-textarea": "^0.15.0",
"@material/mwc-textfield": "^0.15.0",
"@fortawesome/fontawesome-free": "^5.14.0",
"@material/card": "^7.0.0",
"@material/mwc-button": "^0.18.0",
"@material/mwc-fab": "^0.18.0",
"@material/mwc-icon-button": "^0.18.0",
"@material/mwc-linear-progress": "^0.18.0",
"@material/mwc-menu": "^0.18.0",
"@material/mwc-select": "^0.18.0",
"@material/mwc-slider": "^0.18.0",
"@material/mwc-switch": "^0.18.0",
"@material/mwc-tab": "^0.18.0",
"@material/mwc-tab-bar": "^0.18.0",
"@material/mwc-textarea": "^0.18.0",
"@material/mwc-textfield": "^0.18.0",
"@polymer/paper-tooltip": "^3.0.1",
"@webcomponents/webcomponentsjs": "^2.4.3",
"@webcomponents/webcomponentsjs": "^2.4.4",
"canvas-confetti": "^1.2.0"
},
"devDependencies": {
"@types/canvas-confetti": "^1.0.0",
"@types/chrome": "^0.0.114",
"@types/chromecast-caf-receiver": "^5.0.5",
"@types/chrome": "^0.0.122",
"@types/chromecast-caf-receiver": "^5.0.11",
"@types/chromecast-caf-sender": "^1.0.3",
"app-manifest-loader": "^2.4.1",
"chromecast-device-emulator": "^1.2.7",
"clean-webpack-plugin": "^3.0.0",
"closure-webpack-plugin": "^2.3.0",
"compression-webpack-plugin": "^4.0.0",
"compression-webpack-plugin": "^5.0.2",
"css-hot-loader": "^1.4.4",
"css-loader": "^3.6.0",
"css-loader": "^4.2.2",
"cssnano": "^4.1.10",
"elm": "^0.19.1-3",
"elm-hot-webpack-loader": "^1.1.6",
"elm-analyse": "^0.16.5",
"elm-hot-webpack-loader": "^1.1.7",
"elm-webpack-loader": "^6.0.1",
"extract-loader": "^5.1.0",
"fibers": "^5.0.0",
"file-loader": "^6.0.0",
"google-closure-compiler": "^20200517.0.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.3.0",
"file-loader": "^6.1.0",
"google-closure-compiler": "^20200830.0.0",
"html-loader": "^1.3.0",
"html-webpack-plugin": "^4.4.1",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"prettier": "2.0.5",
"prettier": "2.1.1",
"prettier-plugin-elm": "^0.7.0",
"resolve-url-loader": "^3.1.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"sass": "^1.26.10",
"sass-loader": "^10.0.2",
"style-loader": "^1.2.1",
"terser-webpack-plugin": "^3.0.4",
"ts-loader": "^7.0.5",
"typescript": "^3.9.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"terser-webpack-plugin": "^4.1.0",
"ts-loader": "^8.0.3",
"typescript": "^4.0.2",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
}
+240 -225
View File
@@ -69,22 +69,25 @@ init wrap game hand pick =
model =
emptyModel game
( round, roundCmd ) =
case game.round of
Round.P r ->
round =
model.game.round
( newRound, roundCmd ) =
case round.stage of
Round.P stage ->
let
( playing, cmd ) =
Playing.init wrap r pick
Playing.init wrap (Round.withStage stage round) pick
in
( Round.P playing, cmd )
( playing, cmd )
_ ->
( game.round, Cmd.none )
( round, Cmd.none )
timeCmd =
Time.now (UpdateTimer >> wrap)
in
( { model | hand = hand, game = { game | round = round } }, Cmd.batch [ roundCmd, timeCmd ] )
( { model | hand = hand, game = { game | round = newRound } }, Cmd.batch [ roundCmd, timeCmd ] )
update : (Msg -> msg) -> Shared -> Msg -> Model -> ( Model, Cmd msg )
@@ -92,14 +95,17 @@ update wrap shared msg model =
let
game =
model.game
round =
game.round
in
case msg of
Pick maybeFor played ->
case game.round of
Round.P playingRound ->
case round.stage of
Round.P stage ->
let
picks =
playingRound.pick
stage.pick
picked =
case maybeFor of
@@ -113,7 +119,7 @@ update wrap shared msg model =
else
let
missing =
Parts.missingSlotIndices picks.cards playingRound.call.body |> Set.toList
Parts.missingSlotIndices picks.cards round.call.body |> Set.toList
in
case missing of
next :: _ ->
@@ -126,33 +132,33 @@ update wrap shared msg model =
in
picks.cards |> Dict.insert last played
newRound =
Round.P { playingRound | pick = { picks | cards = picked } }
newStage =
Round.P { stage | pick = { picks | cards = picked } }
focus =
Dom.focus played
|> Task.onError (\_ -> Task.succeed ())
|> Task.perform (\_ -> wrap NoOp)
in
( { model | game = { game | round = newRound } }, focus )
( { model | game = { game | round = { round | stage = newStage } } }, focus )
_ ->
( model, Cmd.none )
Unpick slotId ->
case game.round of
Round.P playingRound ->
case round.stage of
Round.P stage ->
let
picks =
playingRound.pick
stage.pick
picked =
picks.cards |> Dict.remove slotId
newRound =
Round.P { playingRound | pick = { picks | cards = picked } }
newStage =
Round.P { stage | pick = { picks | cards = picked } }
in
( { model | game = { game | round = newRound } }, Cmd.none )
( { model | game = { game | round = { round | stage = newStage } } }, Cmd.none )
_ ->
( model, Cmd.none )
@@ -162,12 +168,12 @@ update wrap shared msg model =
( dragDrop, result ) =
DragDrop.update dragDropMsg model.dragDrop
newRound =
case game.round of
Round.P playingRound ->
newStage =
case round.stage of
Round.P stage ->
let
picks =
playingRound.pick
stage.pick
picked =
case result of
@@ -179,17 +185,17 @@ update wrap shared msg model =
Nothing ->
picks.cards
in
Round.P { playingRound | pick = { picks | cards = picked } }
Round.P { stage | pick = { picks | cards = picked } }
_ ->
game.round
stage ->
stage
in
( { model | game = { game | round = newRound }, dragDrop = dragDrop }, Cmd.none )
( { model | game = { game | round = { round | stage = newStage } }, dragDrop = dragDrop }, Cmd.none )
EditBlank id text ->
case game.round of
Round.P playingRound ->
if playingRound.pick.state /= Round.Submitted then
case round.stage of
Round.P stage ->
if stage.pick.state /= Round.Submitted then
( { model | filledCards = model.filledCards |> Dict.insert id text }, Cmd.none )
else
@@ -219,36 +225,36 @@ update wrap shared msg model =
PickPlay id ->
let
makePick r wrapRound =
makePick stage wrapRound =
let
pick =
if r.pick == Just id then
if stage.pick == Just id then
Nothing
else
Just id
in
{ r | pick = pick } |> wrapRound
{ stage | pick = pick } |> wrapRound
newRound =
case game.round of
newStage =
case round.stage of
Round.J judging ->
makePick judging Round.J
Round.R revealing ->
makePick revealing Round.R
_ ->
game.round
stage ->
stage
in
( { model | game = { game | round = newRound } }, Cmd.none )
( { model | game = { game | round = { round | stage = newStage } } }, Cmd.none )
Submit ->
case game.round of
Round.P playingRound ->
case round.stage of
Round.P stage ->
let
picks =
playingRound.pick
stage.pick
fillIfNeeded card =
if card.details.source == Source.Custom then
@@ -276,10 +282,10 @@ update wrap shared msg model =
fills =
model.hand |> List.filterMap fillIfNeeded
newRound =
Round.P { playingRound | pick = { picks | state = Round.Submitted } }
newStage =
Round.P { stage | pick = { picks | state = Round.Submitted } }
in
( { model | game = { game | round = newRound } }
( { model | game = { game | round = { round | stage = newStage } } }
, Cmd.batch (fills ++ [ picks.cards |> Dict.values |> Actions.submit ])
)
@@ -287,16 +293,16 @@ update wrap shared msg model =
( model, Cmd.none )
TakeBack ->
case game.round of
Round.P playingRound ->
case round.stage of
Round.P stage ->
let
picks =
playingRound.pick
stage.pick
newRound =
Round.P { playingRound | pick = { picks | state = Round.Selected } }
newStage =
Round.P { stage | pick = { picks | state = Round.Selected } }
in
( { model | game = { game | round = newRound } }, Actions.takeBack )
( { model | game = { game | round = { round | stage = newStage } } }, Actions.takeBack )
_ ->
( model, Cmd.none )
@@ -310,7 +316,7 @@ update wrap shared msg model =
Judge ->
let
cmd =
case game.round of
case round.stage of
Round.J judging ->
judging.pick |> Maybe.map Actions.judge |> Maybe.withDefault Cmd.none
@@ -321,36 +327,36 @@ update wrap shared msg model =
Like ->
let
( newRound, action ) =
( newStage, action ) =
let
like r roundWrap =
like stage roundWrap =
let
likeDetail =
r.likeDetail
stage.likeDetail
insert =
r.pick
stage.pick
|> Maybe.map Set.insert
|> Maybe.withDefault identity
newLikeDetail =
{ likeDetail | liked = insert likeDetail.liked }
in
( { r | pick = Nothing, likeDetail = newLikeDetail } |> roundWrap
, r.pick |> Maybe.map Actions.like |> Maybe.withDefault Cmd.none
( { stage | pick = Nothing, likeDetail = newLikeDetail } |> roundWrap
, stage.pick |> Maybe.map Actions.like |> Maybe.withDefault Cmd.none
)
in
case game.round of
case round.stage of
Round.J judging ->
like judging Round.J
Round.R revealing ->
like revealing Round.R
_ ->
( game.round, Cmd.none )
stage ->
( stage, Cmd.none )
in
( { model | game = { game | round = newRound } }, action )
( { model | game = { game | round = { round | stage = newStage } } }, action )
SetPlayStyles playStyles ->
( { model | playStyles = playStyles }, Cmd.none )
@@ -359,11 +365,8 @@ update wrap shared msg model =
case model.completeRound of
Just _ ->
let
round =
model.game.round
tts =
speak shared (round |> Round.data |> .call) Nothing
speak shared round.call Nothing
in
( { model | completeRound = Nothing }
, tts
@@ -376,11 +379,11 @@ update wrap shared msg model =
( model, Actions.redraw )
Discard ->
case model.game.round of
Round.P p ->
case round.stage of
Round.P stage ->
let
action =
p.pick.cards |> Dict.values |> List.head |> Maybe.map Actions.discard
stage.pick.cards |> Dict.values |> List.head |> Maybe.map Actions.discard
in
( model, action |> Maybe.withDefault Cmd.none )
@@ -407,7 +410,7 @@ update wrap shared msg model =
( { model | time = Just time }, Cmd.none )
EnforceTimeLimit ->
( model, Actions.enforceTimeLimit (model.game.round |> Round.data |> .id) (model.game.round |> Round.stage) )
( model, Actions.enforceTimeLimit round.id (round.stage |> Round.stage) )
ToggleHelp ->
( { model | helpVisible = not model.helpVisible }, Cmd.none )
@@ -493,12 +496,16 @@ view wrapLobby wrap shared auth timeAnchor name config users model =
applyGameEvent : (Msg -> msg) -> (Event -> msg) -> Lobby.Auth -> Shared -> Events.GameEvent -> Model -> ( Model, Cmd msg )
applyGameEvent wrap wrapEvent auth shared gameEvent model =
let
game =
model.game
round =
game.round
in
case gameEvent of
Events.HandRedrawn { player, hand } ->
let
game =
model.game
-- TODO: Error, if the rule isn't enabled, we are out of sync.
cost =
game.rules.houseRules.reboot |> Maybe.map .cost |> Maybe.withDefault 0
@@ -510,9 +517,9 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
newHand =
hand |> Maybe.andThen (Maybe.justIf (player == auth.claims.uid)) |> Maybe.withDefault model.hand
newRound =
newStage =
if player == auth.claims.uid then
case game.round of
case round.stage of
Round.P playing ->
let
pick =
@@ -520,25 +527,25 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
in
Round.P { playing | pick = { pick | cards = Dict.empty } }
other ->
other
stage ->
stage
else
game.round
round.stage
players =
game.players |> Dict.map updatePlayer
in
( { model | game = { game | players = players, round = newRound }, hand = newHand }, Cmd.none )
( { model | game = { game | players = players, round = { round | stage = newStage } }, hand = newHand }, Cmd.none )
Events.PlaySubmitted { by } ->
case model.game.round of
Round.P round ->
case round.stage of
Round.P stage ->
let
game =
model.game
newStage =
Round.P { stage | played = Set.insert by stage.played }
in
( { model | game = { game | round = Round.P { round | played = Set.insert by round.played } } }
( { model | game = { game | round = { round | stage = newStage } } }
, Cmd.none
)
@@ -547,13 +554,31 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
( model, Cmd.none )
Events.PlayTakenBack { by } ->
case model.game.round of
Round.P round ->
case round.stage of
Round.P stage ->
let
game =
model.game
newStage =
Round.P { stage | played = Set.remove by stage.played }
in
( { model | game = { game | round = Round.P { round | played = Set.remove by round.played } } }
( { model | game = { game | round = { round | stage = newStage } } }
, Cmd.none
)
_ ->
-- TODO: Error
( model, Cmd.none )
Events.PlayLiked { play } ->
case round.stage of
Round.C stage ->
let
increment withLikes =
{ withLikes | likes = withLikes.likes |> Maybe.withDefault 0 |> (+) 1 |> Just }
newStage =
{ stage | plays = Dict.update play (Maybe.map increment) stage.plays }
in
( { model | game = { game | round = { round | stage = Round.C newStage } } }
, Cmd.none
)
@@ -575,7 +600,7 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Events.Timed (Events.WithTime { event, time }) ->
case event of
Events.StartRevealing { plays, afterPlaying } ->
case model.game.round of
case round.stage of
Round.P r ->
let
{ played, drawn } =
@@ -584,15 +609,12 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
oldGame =
model.game
newRound =
Round.revealing
(Just { played = played, liked = Set.empty })
r.id
r.czar
r.players
r.call
newStage =
Round.Revealing
{ played = played, liked = Set.empty }
Nothing
Nothing
(plays |> List.map (\id -> Play id Nothing))
time
False
|> Round.R
@@ -605,7 +627,7 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
|> (\h -> h ++ (drawn |> Maybe.withDefault []))
role =
Player.role newRound auth.claims.uid
Player.role round auth.claims.uid
notification =
if role == Player.RCzar then
@@ -617,7 +639,9 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
else
Cmd.none
in
( { model | game = { oldGame | round = newRound }, hand = newHand }, notification )
( { model | game = { oldGame | round = Round.withStage newStage round }, hand = newHand }
, notification
)
_ ->
-- TODO: Error
@@ -625,32 +649,24 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Events.RoundFinished { winner, playedBy } ->
let
game =
model.game
playersWithWinner =
game.players |> Dict.update winner (Maybe.map (\p -> { p | score = p.score + 1 }))
{ newRound, history, speech, newPlayers, filledCards } =
case game.round of
case round.stage of
Round.J r ->
let
plays =
r.plays |> List.filterMap (resolvePlayedBy playedBy)
playsDict =
plays |> Dict.fromList
( by, plays, playOrder ) =
resolvePlayedBy r.plays playedBy
complete =
Round.complete
r.id
r.czar
r.players
r.call
playsDict
(plays |> List.map (\( u, _ ) -> u))
Round.Complete
r.likeDetail
r.pick
by
plays
playOrder
winner
time
handIds =
model.hand |> List.map (.details >> .id) |> Set.fromList
@@ -659,15 +675,15 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Set.member id handIds
tts =
Dict.get winner playsDict
|> Maybe.map (\p -> speak shared r.call (p.play |> Parts.fillsFromPlay |> Just))
Dict.get winner plays
|> Maybe.map (\p -> speak shared round.call (p.play |> Parts.fillsFromPlay |> Just))
|> Maybe.withDefault Cmd.none
ps =
playersWithWinner |> Dict.map (updateLikes playsDict)
playersWithWinner |> Dict.map (updateLikes plays)
in
{ newRound = complete |> Round.C
, history = complete :: game.history
{ newRound = Round.withStage (Round.C complete) round
, history = Round.withStage complete round :: game.history
, speech = tts
, newPlayers = ps
, filledCards = model.filledCards |> Dict.filter isStillInHand
@@ -675,7 +691,7 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
_ ->
-- TODO: Error
{ newRound = game.round
{ newRound = round
, history = game.history
, speech = Cmd.none
, newPlayers = playersWithWinner
@@ -696,14 +712,21 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Events.RoundStarted { id, czar, players, call, drawn } ->
let
game =
model.game
drawnAsList =
drawn |> Maybe.withDefault []
( newRound, cmd ) =
Playing.init wrap (Round.playing id czar players call Set.empty time False) Round.noPick
let
r =
{ id = id
, czar = czar
, players = players
, call = call
, startedAt = time
, stage = Round.Playing Round.noPick Set.empty False
}
in
Playing.init wrap r Round.noPick
notification =
if Set.member auth.claims.uid players then
@@ -720,15 +743,15 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Nothing
else
case model.game.round of
Round.C c ->
Just c
case round.stage of
Round.C stage ->
round |> Round.withStage stage |> Just
_ ->
Nothing
in
( { model
| game = { game | round = Round.P newRound }
| game = { game | round = newRound }
, completeRound = completeRound
, hand = model.hand ++ drawnAsList
}
@@ -737,27 +760,24 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Events.PlayRevealed { id, play } ->
let
game =
model.game
( newRound, speech ) =
case model.game.round of
Round.R r ->
case round.stage of
Round.R stage ->
let
plays =
r.plays |> List.map (reveal id play)
stage.plays |> List.map (reveal id play)
round =
{ r | plays = plays, lastRevealed = Just id } |> Round.R
newStage =
{ stage | plays = plays, lastRevealed = Just id } |> Round.R
tts =
speak shared r.call (play |> Parts.fillsFromPlay |> Just)
speak shared round.call (play |> Parts.fillsFromPlay |> Just)
in
( round, tts )
( { round | stage = newStage }, tts )
_ ->
-- TODO: Error
( model.game.round, Cmd.none )
( round, Cmd.none )
in
( { model | game = { game | round = newRound } }, speech )
@@ -766,63 +786,53 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
{ played, drawn } =
afterPlaying
game =
model.game
makeNewRound r pick ld known =
makeNewRound stage pick ld known =
case known of
Just k ->
Round.judging pick
Round.Judging
ld
r.id
r.czar
r.players
r.call
pick
k
time
False
|> Round.J
Nothing ->
-- TODO: Error.
game.round
stage
( newRound, newHand ) =
case game.round of
Round.P r ->
( newStage, newHand ) =
case round.stage of
Round.P stage ->
let
picked =
r.pick.cards |> Dict.values |> Set.fromList
stage.pick.cards |> Dict.values |> Set.fromList
nH =
model.hand
|> List.filter (\c -> not (Set.member c.details.id picked))
|> (\h -> h ++ (drawn |> Maybe.withDefault []))
in
( makeNewRound r Nothing (Just { played = played, liked = Set.empty }) plays
( makeNewRound round.stage Nothing { played = played, liked = Set.empty } plays
, nH
)
Round.R r ->
Round.R stage ->
let
p =
plays
|> Maybe.withDefault (r.plays |> List.filterMap Play.asKnown)
|> Maybe.withDefault (stage.plays |> List.filterMap Play.asKnown)
|> Just
in
( makeNewRound r r.pick (Just r.likeDetail) p, model.hand )
( makeNewRound round.stage stage.pick stage.likeDetail p, model.hand )
_ ->
stage ->
-- TODO: Error.
( game.round, model.hand )
( stage, model.hand )
in
( { model | game = { game | round = newRound }, hand = newHand }, Cmd.none )
( { model | game = { game | round = { round | stage = newStage } }, hand = newHand }, Cmd.none )
Events.PlayerAway { player } ->
let
game =
model.game
players =
game.players |> Dict.map (setPresence player Player.Away)
in
@@ -830,37 +840,26 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Events.PlayerBack { player } ->
let
game =
model.game
players =
game.players |> Dict.map (setPresence player Player.Active)
in
( { model | game = { game | players = players } }, Cmd.none )
Events.Paused ->
let
game =
model.game
in
( { model | game = { game | paused = True } }, Cmd.none )
Events.Continued ->
let
game =
model.game
in
( { model | game = { game | paused = False } }, Cmd.none )
Events.StageTimerDone { round, stage } ->
Events.StageTimerDone details ->
let
game =
model.game
oldRound =
round
in
if (game.round |> Round.data |> .id) == round && Round.stage game.round == stage then
if oldRound.id == details.round && Round.stage oldRound.stage == details.stage then
let
newRound =
case game.round of
newStage =
case oldRound.stage of
Round.P playing ->
Round.P { playing | timedOut = True }
@@ -873,24 +872,17 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
Round.C complete ->
Round.C complete
in
( { model | game = { game | round = newRound } }, Cmd.none )
( { model | game = { game | round = { oldRound | stage = newStage } } }, Cmd.none )
else
( model, Cmd.none )
Events.GameEnded { winner } ->
let
game =
model.game
in
( { model | game = { game | winner = Just winner }, confetti = False }, Cmd.none )
Events.CardDiscarded { player, card, replacement } ->
let
game =
model.game
( hand, round, discarded ) =
( hand, stage, discarded ) =
case replacement of
Just replacementCard ->
let
@@ -901,11 +893,8 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
else
c
r =
game.round
newRound =
case r of
newStage =
case round.stage of
Round.P playing ->
let
pick =
@@ -922,22 +911,24 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
other ->
other
in
( model.hand |> List.map replace, newRound, model.discarded )
( model.hand |> List.map replace, newStage, model.discarded )
Nothing ->
( model.hand, game.round, model.discarded ++ [ ( player, card ) ] )
( model.hand, round.stage, model.discarded ++ [ ( player, card ) ] )
in
( { model | hand = hand, discarded = discarded, game = { game | round = round } }, Cmd.none )
( { model | hand = hand, discarded = discarded, game = { game | round = { round | stage = stage } } }
, Cmd.none
)
applyGameStarted : (Msg -> msg) -> Lobby -> Round.Playing -> List Card.Response -> ( Model, Cmd msg )
applyGameStarted : (Msg -> msg) -> Lobby -> Round -> List Card.Response -> ( Model, Cmd msg )
applyGameStarted wrap lobby round hand =
let
users =
lobby.users |> Dict.toList |> List.map (\( id, _ ) -> id)
game =
{ round = Round.P round
{ round = round
, history = []
, playerOrder = users
, players =
@@ -979,6 +970,29 @@ hotJoinPlayer player user model =
{- Private -}
resolvePlayedBy :
List Play.Known
-> Dict Play.Id Play.Details
-> ( Dict User.Id Play.Id, Dict Play.Id Play.WithLikes, List User.Id )
resolvePlayedBy plays playedBy =
let
step known ( pb, ps, po ) =
let
( newPb, newPs ) =
case playedBy |> Dict.get known.id of
Just details ->
( pb |> Dict.insert details.playedBy known.id
, ps |> Dict.insert known.id (Play.WithLikes known.responses details.likes)
)
Nothing ->
( pb, ps )
in
( newPb, newPs, known.id :: po )
in
plays |> List.foldr step ( Dict.empty, Dict.empty, [] )
updateLikes : Dict User.Id Play.WithLikes -> User.Id -> Player -> Player
updateLikes plays uid player =
plays
@@ -1033,24 +1047,27 @@ viewWinnerListItem shared users user =
viewRound : (Msg -> msg) -> Shared -> Lobby.Auth -> Time.Anchor -> Config -> Dict User.Id User -> Model -> List (Html msg)
viewRound wrap shared auth timeAnchor config users model =
let
round =
model.game.round
( call, { instruction, action, content, slotAttrs, fillCallWith, roundAttrs } ) =
case model.completeRound of
Just completeRound ->
( completeRound.call, Complete.view shared True config users completeRound )
( completeRound.call, Complete.view wrap shared auth True config users completeRound )
Nothing ->
case game.round of
Round.P round ->
( round.call, Playing.view wrap auth shared config users model round )
case round.stage of
Round.P stage ->
( round.call, Playing.view wrap auth shared config users model (Round.withStage stage round) )
Round.R round ->
( round.call, Revealing.view wrap auth shared config round )
Round.R stage ->
( round.call, Revealing.view wrap auth shared users config (Round.withStage stage round) )
Round.J round ->
( round.call, Judging.view wrap auth shared config round )
Round.J stage ->
( round.call, Judging.view wrap auth shared users config (Round.withStage stage round) )
Round.C round ->
( round.call, Complete.view shared False config users round )
Round.C stage ->
( round.call, Complete.view wrap shared auth False config users (Round.withStage stage round) )
game =
model.game
@@ -1188,19 +1205,22 @@ roundTimeDetails game =
let
stages =
game.rules.stages
round =
game.round
in
case game.round of
case round.stage of
Round.P playing ->
( playing.startedAt, stages.playing.duration, playing.timedOut )
( round.startedAt, stages.playing.duration, playing.timedOut )
Round.R revealing ->
( revealing.startedAt, stages.revealing |> Maybe.andThen .duration, revealing.timedOut )
( round.startedAt, stages.revealing |> Maybe.andThen .duration, revealing.timedOut )
Round.J judging ->
( judging.startedAt, stages.judging.duration, judging.timedOut )
( round.startedAt, stages.judging.duration, judging.timedOut )
Round.C complete ->
( complete.startedAt, Nothing, False )
Round.C _ ->
( round.startedAt, Nothing, False )
minorActions : (Msg -> msg) -> Shared -> Lobby.Auth -> Game -> Bool -> Html msg
@@ -1240,7 +1260,7 @@ discardButton : (Msg -> msg) -> Shared -> Game -> Html msg
discardButton wrap shared game =
let
action =
case game.round of
case game.round.stage of
Round.P p ->
Discard |> wrap |> Maybe.justIf (p.pick.cards |> Dict.size |> (==) 1)
@@ -1329,11 +1349,6 @@ reveal target responses play =
play
resolvePlayedBy : Dict Play.Id Play.Details -> Play.Known -> Maybe ( User.Id, Play.WithLikes )
resolvePlayedBy playedBy k =
Dict.get k.id playedBy |> Maybe.map (\d -> ( d.playedBy, { play = k.responses, likes = d.likes } ))
type alias ActionDetails msg =
{ content : List (Html msg)
, title : MdString
+9 -6
View File
@@ -22,7 +22,7 @@ import MassiveDecks.Util.NeList as NeList
import Material.IconButton as IconButton
view : (Msg -> msg) -> Shared -> Config -> Dict User.Id User -> String -> List Round.Complete -> List (Html msg)
view : (Msg -> msg) -> Shared -> Config -> Dict User.Id User -> String -> List (Round.Specific Round.Complete) -> List (Html msg)
view wrap shared config users name history =
[ Html.div [ HtmlA.id "top-content" ]
[ Html.div [ HtmlA.id "minor-actions" ]
@@ -49,28 +49,31 @@ view wrap shared config users name history =
{- Private -}
viewRound : Shared -> Config -> Dict User.Id User -> Round.Complete -> ( String, Html msg )
viewRound : Shared -> Config -> Dict User.Id User -> Round.Specific Round.Complete -> ( String, Html msg )
viewRound shared config users round =
let
winning =
round.plays |> Dict.get round.winner
round.stage.plays |> Dict.get round.stage.winner
winningBody =
winning
|> Maybe.map .play
|> Maybe.map (List.indexedMap (\i v -> ( i, v.body )) >> Dict.fromList)
|> Maybe.withDefault Dict.empty
byLine =
{ by = round.czar, specialRole = Just Plays.Czar, likes = Nothing }
in
( Round.idString round.id
, Html.li [ HtmlA.class "historic-round" ]
[ Html.div [ HtmlA.class "spacer" ]
[ Html.div [ HtmlA.class "historic-call with-byline" ]
[ Plays.byLine shared users round.czar (Just ( "czar", Icon.gavel )) Nothing
[ Plays.viewByLine shared users byLine
, Call.viewFilled shared config Card.Front [] (always []) winningBody round.call
]
]
, HtmlK.ul [ HtmlA.class "plays cards" ]
(round.plays |> Dict.toList |> List.map (viewPlay shared config users round.winner))
(round.stage.plays |> Dict.toList |> List.map (viewPlay shared config users round.stage.winner))
]
)
@@ -83,7 +86,7 @@ viewPlay shared config users winner ( id, { play, likes } ) =
in
( id
, Html.li [ HtmlA.class "with-byline" ]
[ Plays.byLine shared users id (( "trophy", Icon.trophy ) |> Maybe.justIf (winner == id)) likes
[ Plays.viewByLine shared users (Plays.ByLine id (Plays.Winner |> Maybe.justIf (winner == id)) likes)
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
]
)
+2 -2
View File
@@ -30,7 +30,7 @@ type alias Model =
, hand : List Card.Response
, filledCards : Dict Card.Id String
, playStyles : PlayStyles
, completeRound : Maybe Round.Complete
, completeRound : Maybe (Round.Specific Round.Complete)
, viewingHistory : Bool
, time : Maybe Time
, helpVisible : Bool
@@ -62,7 +62,7 @@ emptyModel game =
-}
type alias Game =
{ round : Round
, history : List Round.Complete
, history : List (Round.Specific Round.Complete)
, playerOrder : List User.Id
, players : Dict User.Id Player
, rules : Rules
+5 -5
View File
@@ -72,7 +72,7 @@ type PlayState
| Played
role : Round -> User.Id -> Role
role : Round.Specific stageDetails -> User.Id -> Role
role round id =
if isCzar round id then
RCzar
@@ -81,15 +81,15 @@ role round id =
RPlayer
isCzar : Round -> User.Id -> Bool
isCzar : Round.Specific stageDetails -> User.Id -> Bool
isCzar round id =
(round |> Round.data |> .czar) == id
(round |> .czar) == id
playState : Round.Playing -> User.Id -> PlayState
playState : Round.Specific Round.Playing -> User.Id -> PlayState
playState round id =
if Set.member id round.players then
if Set.member id round.played then
if Set.member id round.stage.played then
Played
else
+73 -141
View File
@@ -1,6 +1,5 @@
module MassiveDecks.Game.Round exposing
( Complete
, Data
, Id
, Judging
, LikeDetail
@@ -8,19 +7,18 @@ module MassiveDecks.Game.Round exposing
, PickState(..)
, Playing
, Revealing
, Round(..)
, Round
, Specific
, Stage(..)
, complete
, data
, StageDetails(..)
, defaultLikeDetail
, idDecoder
, idString
, judging
, noPick
, playing
, revealing
, stage
, stageDescription
, stageToName
, stageToString
, withStage
)
import Dict exposing (Dict)
@@ -60,9 +58,9 @@ type Stage
{-| Get the stage of the given round.
-}
stage : Round -> Stage
stage round =
case round of
stage : StageDetails -> Stage
stage specific =
case specific of
P _ ->
SPlaying
@@ -78,8 +76,8 @@ stage round =
{-| Get the serializing name for the stage.
-}
stageToName : Stage -> String
stageToName s =
stageToString : Stage -> String
stageToString s =
case s of
SPlaying ->
"Playing"
@@ -114,32 +112,38 @@ stageDescription toDescribe =
{-| A round during a game.
-}
type Round
type alias Round =
Specific StageDetails
type alias Specific stageDetails =
{ id : Id
, czar : User.Id
, players : Set User.Id
, call : Card.Call
, startedAt : Time
, stage : stageDetails
}
withStage : stageDetails -> Specific oldDetails -> Specific stageDetails
withStage newStage round =
{ id = round.id
, czar = round.czar
, players = round.players
, call = round.call
, startedAt = round.startedAt
, stage = newStage
}
type StageDetails
= P Playing
| R Revealing
| J Judging
| C Complete
{-| A round while users are playing a round.
-}
type alias Playing =
Data { played : Set User.Id, pick : Pick, timedOut : Bool }
playing : Id -> User.Id -> Set User.Id -> Card.Call -> Set User.Id -> Time -> Bool -> Playing
playing id czar players call played startedAt timedOut =
{ id = id
, czar = czar
, players = players
, call = call
, played = played
, pick = { state = Selected, cards = Dict.empty }
, startedAt = startedAt
, timedOut = timedOut
}
type alias LikeDetail =
{ played : Maybe Play.Id
, liked : Set Play.Id
@@ -153,97 +157,6 @@ defaultLikeDetail =
}
type alias Revealing =
Data
{ plays : List Play
, lastRevealed : Maybe Play.Id
, pick : Maybe Play.Id
, likeDetail : LikeDetail
, timedOut : Bool
}
revealing : Maybe LikeDetail -> Id -> User.Id -> Set User.Id -> Card.Call -> List Play -> Time -> Bool -> Revealing
revealing likeDetail id czar players call plays startedAt timedOut =
{ id = id
, czar = czar
, players = players
, call = call
, plays = plays
, lastRevealed = Nothing
, startedAt = startedAt
, pick = Nothing
, likeDetail = likeDetail |> Maybe.withDefault defaultLikeDetail
, timedOut = timedOut
}
{-| A round while the czar is judging a round.
-}
type alias Judging =
Data
{ plays : List Play.Known
, pick : Maybe Play.Id
, likeDetail : LikeDetail
, timedOut : Bool
}
judging :
Maybe Play.Id
-> Maybe LikeDetail
-> Id
-> User.Id
-> Set User.Id
-> Card.Call
-> List Play.Known
-> Time
-> Bool
-> Judging
judging pick likeDetail id czar players call plays startedAt timedOut =
{ id = id
, czar = czar
, players = players
, call = call
, plays = plays
, pick = pick
, likeDetail = likeDetail |> Maybe.withDefault defaultLikeDetail
, startedAt = startedAt
, timedOut = timedOut
}
{-| A round that has been finished.
-}
type alias Complete =
Data { plays : Dict User.Id Play.WithLikes, playOrder : List User.Id, winner : User.Id }
complete : Id -> User.Id -> Set User.Id -> Card.Call -> Dict User.Id Play.WithLikes -> List User.Id -> User.Id -> Time -> Complete
complete id czar players call plays playOrder winner startedAt =
{ id = id
, czar = czar
, players = players
, call = call
, plays = plays
, playOrder = playOrder
, winner = winner
, startedAt = startedAt
}
{-| Data common to all rounds.
-}
type alias Data specific =
{ specific
| id : Id
, czar : User.Id
, players : Set User.Id
, call : Card.Call
, startedAt : Time
}
{-| The user's pick for the round.
-}
type alias Pick =
@@ -262,22 +175,41 @@ type PickState
| Submitted
data : Round -> Data {}
data round =
case round of
P rd ->
extract rd
J rd ->
extract rd
C rd ->
extract rd
R rd ->
extract rd
{-| A round while users are playing a round.
-}
type alias Playing =
{ pick : Pick
, played : Set User.Id
, timedOut : Bool
}
extract : Data a -> Data {}
extract rd =
{ id = rd.id, call = rd.call, czar = rd.czar, players = rd.players, startedAt = rd.startedAt }
type alias Revealing =
{ likeDetail : LikeDetail
, lastRevealed : Maybe Play.Id
, pick : Maybe Play.Id
, plays : List Play
, timedOut : Bool
}
{-| A round while the czar is judging a round.
-}
type alias Judging =
{ likeDetail : LikeDetail
, pick : Maybe Play.Id
, plays : List Play.Known
, timedOut : Bool
}
{-| A round that has been finished.
-}
type alias Complete =
{ likeDetail : LikeDetail
, pick : Maybe Play.Id
, playedBy : Dict User.Id Play.Id
, plays : Dict Play.Id Play.WithLikes
, playOrder : List User.Id
, winner : User.Id
}
@@ -10,47 +10,95 @@ import MassiveDecks.Card.Parts as Parts
import MassiveDecks.Card.Play as Play
import MassiveDecks.Card.Response as Response
import MassiveDecks.Game.Action.Model as Action
import MassiveDecks.Game.Messages exposing (..)
import MassiveDecks.Game.Model exposing (..)
import MassiveDecks.Game.Round as Round
import MassiveDecks.Game.Player as Player
import MassiveDecks.Game.Round as Round exposing (Round)
import MassiveDecks.Game.Round.Plays as Plays
import MassiveDecks.Model exposing (..)
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
import MassiveDecks.Pages.Lobby.Model exposing (Auth)
import MassiveDecks.Strings as Strings
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Maybe as Maybe
import Set
view : Shared -> Bool -> Config -> Dict User.Id User -> Round.Complete -> RoundView msg
view shared nextRoundReady config users round =
view : (Msg -> msg) -> Shared -> Auth -> Bool -> Config -> Dict User.Id User -> Round.Specific Round.Complete -> RoundView msg
view _ shared auth nextRoundReady config users round =
let
role =
Player.role round auth.claims.uid
stage =
round.stage
winning =
round.plays |> Dict.get round.winner
stage.plays |> Dict.get stage.winner
likeAction =
let
canBeLiked play =
Just play /= stage.likeDetail.played && not (Set.member play stage.likeDetail.liked)
likeIfCanBeLiked pick =
Maybe.justIf (canBeLiked pick) Action.Like
in
case role of
Player.RCzar ->
Nothing
Player.RPlayer ->
stage.pick |> Maybe.andThen likeIfCanBeLiked
msg _ =
Nothing
userAndPlay userId =
let
tuple : Play.Id -> Play.WithLikes -> ( User.Id, Play.Id, Play.WithLikes )
tuple id p =
( userId, id, p )
play id =
Dict.get id stage.plays
playId =
Dict.get userId stage.playedBy
in
Maybe.map2 tuple playId (playId |> Maybe.andThen play)
in
{ instruction = Strings.AdvanceRoundInstruction |> Maybe.justIf nextRoundReady
, action = Action.Advance |> Maybe.justIf nextRoundReady
, action =
Maybe.first
[ Action.Advance |> Maybe.justIf nextRoundReady
, likeAction
]
, content =
HtmlK.ul [ HtmlA.class "complete plays cards" ]
(round.playOrder
|> List.map (\u -> ( u, Dict.get u round.plays ))
|> List.map (viewPlay shared config users round.winner)
)
stage.playOrder
|> List.filterMap userAndPlay
|> List.map (details shared config stage.winner msg)
|> Plays.view shared users [ ( "complete", True ) ] stage.likeDetail.liked stage.pick
, slotAttrs = always []
, fillCallWith = winning |> Maybe.map (.play >> Parts.fillsFromPlay) |> Maybe.withDefault Dict.empty
, roundAttrs = []
}
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, Maybe Play.WithLikes ) -> ( String, Html msg )
viewPlay shared config users winner ( id, play ) =
details : Shared -> Config -> User.Id -> (Play.Id -> Maybe msg) -> ( User.Id, Play.Id, Play.WithLikes ) -> Plays.Details msg
details shared config winner maybeMsg ( player, id, play ) =
let
cards =
play
|> Maybe.map (.play >> List.map (\r -> ( r.details.id, Html.li [] [ Response.view shared config Card.Front [] r ] )))
|> Maybe.withDefault []
play.play |> List.map (\r -> Html.li [] [ Response.view shared config Card.Front [] r ])
specialRole =
if winner == player then
Just Plays.Winner
else
Nothing
byLine =
Plays.ByLine player specialRole play.likes
in
( id
, Html.li [ HtmlA.class "with-byline" ]
[ Plays.byLine shared users id (( "trophy", Icon.trophy ) |> Maybe.justIf (winner == id)) (play |> Maybe.andThen .likes)
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
]
)
Plays.Details id cards (id |> maybeMsg) (Just byLine)
@@ -1,14 +1,13 @@
module MassiveDecks.Game.Round.Judging exposing (view)
import Dict
import Html.Attributes as HtmlA
import Dict exposing (Dict)
import MassiveDecks.Card.Model as Card
import MassiveDecks.Card.Parts as Parts
import MassiveDecks.Card.Play as Play
import MassiveDecks.Card.Response as Response
import MassiveDecks.Game.Action.Model as Action
import MassiveDecks.Game.Messages exposing (..)
import MassiveDecks.Game.Model exposing (..)
import MassiveDecks.Game.Messages exposing (Msg(..))
import MassiveDecks.Game.Model exposing (RoundView)
import MassiveDecks.Game.Player as Player
import MassiveDecks.Game.Round as Round
import MassiveDecks.Game.Round.Plays as Plays
@@ -16,20 +15,24 @@ import MassiveDecks.Model exposing (Shared)
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
import MassiveDecks.Pages.Lobby.Model exposing (Auth)
import MassiveDecks.Strings as Strings
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Maybe as Maybe
import Set exposing (Set)
view : (Msg -> msg) -> Auth -> Shared -> Config -> Round.Judging -> RoundView msg
view wrap auth shared config round =
view : (Msg -> msg) -> Auth -> Shared -> Dict User.Id User -> Config -> Round.Specific Round.Judging -> RoundView msg
view wrap auth shared users config round =
let
role =
Player.role (Round.J round) auth.claims.uid
Player.role round auth.claims.uid
stage =
round.stage
{ action, msg, instruction, isCzar } =
case role of
Player.RCzar ->
{ action = Maybe.map (always Action.Judge) round.pick
{ action = Maybe.map (always Action.Judge) stage.pick
, msg = \p -> p |> PickPlay |> wrap |> Just
, instruction = Strings.RevealPlaysInstruction
, isCzar = True
@@ -38,27 +41,27 @@ view wrap auth shared config round =
Player.RPlayer ->
let
canBeLiked play =
Just play /= round.likeDetail.played && not (Set.member play round.likeDetail.liked)
Just play /= stage.likeDetail.played && not (Set.member play stage.likeDetail.liked)
in
{ action = Maybe.andThen (\p -> Maybe.justIf (canBeLiked p) Action.Like) round.pick
{ action = Maybe.andThen (\p -> Maybe.justIf (canBeLiked p) Action.Like) stage.pick
, msg = \p -> p |> PickPlay |> wrap |> Maybe.justIf (canBeLiked p)
, instruction = Strings.WaitingForCzarInstruction
, isCzar = False
}
picked =
round.plays
|> List.filter (\play -> Just play.id == round.pick)
stage.plays
|> List.filter (\play -> Just play.id == stage.pick)
|> List.head
|> Maybe.map (.responses >> Parts.fillsFromPlay)
|> Maybe.withDefault Dict.empty
details =
round.plays |> List.map (playDetails shared config round.likeDetail.liked msg)
stage.plays |> List.map (playDetails shared config msg)
in
{ instruction = Just instruction
, action = action
, content = details |> Plays.view [ ( "judging", True ), ( "is-czar", isCzar ) ] round.pick
, content = details |> Plays.view shared users [ ( "judging", True ), ( "is-czar", isCzar ) ] stage.likeDetail.liked stage.pick
, slotAttrs = always []
, fillCallWith = picked
, roundAttrs = []
@@ -69,24 +72,14 @@ view wrap auth shared config round =
{- Private -}
playDetails : Shared -> Config -> Set Play.Id -> (Play.Id -> Maybe msg) -> Play.Known -> Plays.Details msg
playDetails shared config liked msg { id, responses } =
playDetails : Shared -> Config -> (Play.Id -> Maybe msg) -> Play.Known -> Plays.Details msg
playDetails shared config msg { id, responses } =
let
maybeMsg =
msg id
cls =
if maybeMsg /= Nothing then
[ HtmlA.class "active" ]
else
[]
cards =
responses
|> List.map (\r -> Response.view shared config Card.Front [] r)
attrs =
[ HtmlA.class "liked" ] |> Maybe.justIf (Set.member id liked) |> Maybe.withDefault []
in
Plays.Details id cards maybeMsg (attrs ++ cls)
Plays.Details id cards maybeMsg Nothing
@@ -19,47 +19,52 @@ import MassiveDecks.Game.Action.Model as Action
import MassiveDecks.Game.Messages as Game exposing (Msg)
import MassiveDecks.Game.Model exposing (..)
import MassiveDecks.Game.Player as Player
import MassiveDecks.Game.Round as Round
import MassiveDecks.Game.Round as Round exposing (Round)
import MassiveDecks.Model exposing (Shared)
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
import MassiveDecks.Pages.Lobby.Model as Lobby
import MassiveDecks.Strings as Strings
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Html as Html
import MassiveDecks.Util.Html.Attributes as HtmlA
import MassiveDecks.Util.Random as Random
import Random
import Set exposing (Set)
init : (Msg -> msg) -> Round.Playing -> Round.Pick -> ( Round.Playing, Cmd msg )
init : (Msg -> msg) -> Round.Specific Round.Playing -> Round.Pick -> ( Round, Cmd msg )
init wrap round pick =
let
cmd =
round.players
|> playStylesGenerator (Call.slotCount round.call)
|> Random.generate (Game.SetPlayStyles >> wrap)
stage =
round.stage
in
( { round | pick = pick }
( round |> Round.withStage (Round.P { stage | pick = pick })
, cmd
)
view : (Msg -> msg) -> Lobby.Auth -> Shared -> Config -> Dict User.Id User -> Model -> Round.Playing -> RoundView msg
view : (Msg -> msg) -> Lobby.Auth -> Shared -> Config -> Dict User.Id User -> Model -> Round.Specific Round.Playing -> RoundView msg
view wrap auth shared config _ model round =
let
slots =
Call.slotCount round.call
stage =
round.stage
missingFromPick =
slots - (round.pick.cards |> Dict.size)
slots - (stage.pick.cards |> Dict.size)
self =
auth.claims.uid
( action, instruction, notPlaying ) =
if round.players |> Set.member self then
case round.pick.state of
case stage.pick.state of
Round.Selected ->
if missingFromPick > 0 then
( Nothing, Strings.PlayInstruction { numberOfCards = missingFromPick }, False )
@@ -70,7 +75,7 @@ view wrap auth shared config _ model round =
Round.Submitted ->
( Just Action.TakeBack, Strings.WaitingForPlaysInstruction, True )
else if Player.isCzar (Round.P round) self then
else if Player.isCzar round self then
( Nothing, Strings.CzarsDontPlayInstruction, True )
else
@@ -86,11 +91,11 @@ view wrap auth shared config _ model round =
, ( "pick-full", missingFromPick < 1 )
]
]
(model.hand |> List.map (round.pick.cards |> viewHandCard wrap shared config model.filledCards))
(model.hand |> List.map (stage.pick.cards |> viewHandCard wrap shared config model.filledCards))
backgroundPlays =
Html.div [ HtmlA.class "background-plays" ]
(round.players |> Set.toList |> List.map (viewBackgroundPlay shared model.playStyles slots round.played))
(round.players |> Set.toList |> List.map (viewBackgroundPlay shared model.playStyles slots stage.played))
fillCustom _ p =
List.find (\c -> c.details.id == p) model.hand
@@ -98,7 +103,7 @@ view wrap auth shared config _ model round =
|> Maybe.withDefault ""
picked =
round.pick.cards |> Dict.map fillCustom
stage.pick.cards |> Dict.map fillCustom
showNonObviousSlotIndices =
if round.call.body |> Parts.nonObviousSlotIndices then
@@ -111,7 +116,7 @@ view wrap auth shared config _ model round =
List.concat
[ [ Game.Unpick i |> wrap |> HtmlE.onClick ]
, DragDrop.droppable (Game.Drag >> wrap) i
, round.pick.cards
, stage.pick.cards
|> Dict.get i
|> Maybe.map (DragDrop.draggable (Game.Drag >> wrap))
|> Maybe.withDefault []
@@ -1,7 +1,14 @@
module MassiveDecks.Game.Round.Plays exposing (Details, byLine, view)
module MassiveDecks.Game.Round.Plays exposing
( ByLine
, Details
, SpecialRole(..)
, view
, viewByLine
)
import Dict exposing (Dict)
import FontAwesome.Icon as Icon exposing (Icon)
import FontAwesome.Solid as Icon
import Html exposing (Html)
import Html.Attributes as HtmlA
import Html.Events as HtmlE
@@ -13,6 +20,7 @@ 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
import Set exposing (Set)
{-| The details needed to render a specific play.
@@ -21,50 +29,83 @@ type alias Details msg =
{ id : Play.Id
, responses : List (Html msg)
, action : Maybe msg
, attrs : List (Html.Attribute msg)
, byLine : Maybe ByLine
}
{-| The details needed for a by line.
-}
type alias ByLine =
{ by : User.Id
, specialRole : Maybe SpecialRole
, likes : Maybe Int
}
{-| If the user had a special role in the game, then this defines it.
-}
type SpecialRole
= Winner
| Czar
{-| View a collection of plays.
-}
view : List ( String, Bool ) -> Maybe Play.Id -> List (Details msg) -> Html msg
view classes picked details =
view : Shared -> Dict User.Id User -> List ( String, Bool ) -> Set Play.Id -> Maybe Play.Id -> List (Details msg) -> Html msg
view shared users classes liked picked details =
HtmlK.ul [ HtmlA.classList (classes ++ [ ( "cards", True ), ( "plays", True ) ]) ]
(details |> List.map (viewPlay picked))
(details |> List.map (viewPlay shared users liked picked))
{-| Create a byline.
-}
byLine : Shared -> Dict User.Id User -> User.Id -> Maybe ( String, Icon ) -> Maybe Int -> Html msg
byLine shared users id icon likes =
viewByLine : Shared -> Dict User.Id User -> ByLine -> Html msg
viewByLine shared users { by, specialRole, likes } =
let
roleIcon role =
let
( cls, icon ) =
case role of
Winner ->
( "trophy", Icon.trophy )
Czar ->
( "czar", Icon.gavel )
in
Html.span [ HtmlA.class cls ] [ Icon.viewIcon icon ]
name =
users |> Dict.get id |> Maybe.map .name |> Maybe.withDefault (Strings.UnknownUser |> Lang.string shared)
users |> Dict.get by |> Maybe.map .name |> Maybe.withDefault (Strings.UnknownUser |> Lang.string shared)
contents =
[ specialRole |> Maybe.map roleIcon
, Html.span [ HtmlA.class "name" ] [ Html.text name ] |> Just
, likes
|> Maybe.map (\l -> Html.span [ HtmlA.class "likes" ] [ Strings.Likes { total = l } |> Lang.html shared ])
]
in
Html.span [ HtmlA.class "byline", HtmlA.title name ]
[ icon
|> Maybe.map (\( cls, i ) -> Html.span [ HtmlA.class cls ] [ Icon.viewIcon i ])
|> Maybe.withDefault Html.nothing
, Html.span [ HtmlA.class "name" ] [ Html.text name ]
, likes
|> Maybe.map (\l -> Html.span [ HtmlA.class "likes" ] [ Strings.Likes { total = l } |> Lang.html shared ])
|> Maybe.withDefault Html.nothing
]
Html.span [ HtmlA.class "byline", HtmlA.title name ] (contents |> List.filterMap identity)
{- Private -}
viewPlay : Maybe Play.Id -> Details msg -> ( String, Html msg )
viewPlay picked { id, responses, action, attrs } =
viewPlay : Shared -> Dict User.Id User -> Set Play.Id -> Maybe Play.Id -> Details msg -> ( String, Html msg )
viewPlay shared users liked picked { id, responses, action, byLine } =
( id
, Html.li []
[ Html.ol
(HtmlA.classList [ ( "play", True ), ( "card-set", True ), ( "picked", picked == Just id ) ]
:: (action |> Maybe.map HtmlE.onClick |> Maybe.withDefault HtmlA.nothing)
:: attrs
)
, Html.li [ HtmlA.classList [ ( "with-byline", byLine /= Nothing ) ] ]
[ byLine |> Maybe.map (viewByLine shared users) |> Maybe.withDefault Html.nothing
, Html.ol
[ HtmlA.classList
[ ( "play", True )
, ( "card-set", True )
, ( "active", action /= Nothing )
, ( "picked", picked == Just id )
, ( "liked", Set.member id liked )
]
, action |> Maybe.map HtmlE.onClick |> Maybe.withDefault HtmlA.nothing
]
(responses |> List.map (\card -> Html.li [] [ card ]))
]
)
@@ -1,6 +1,6 @@
module MassiveDecks.Game.Round.Revealing exposing (view)
import Dict
import Dict exposing (Dict)
import Html.Attributes as HtmlA
import MassiveDecks.Card.Call as Call
import MassiveDecks.Card.Model as Card
@@ -17,15 +17,19 @@ import MassiveDecks.Model exposing (Shared)
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
import MassiveDecks.Pages.Lobby.Model exposing (Auth)
import MassiveDecks.Strings as Strings
import MassiveDecks.User as User exposing (User)
import MassiveDecks.Util.Maybe as Maybe
import Set exposing (Set)
view : (Msg -> msg) -> Auth -> Shared -> Config -> Round.Revealing -> RoundView msg
view wrap auth shared config round =
view : (Msg -> msg) -> Auth -> Shared -> Dict User.Id User -> Config -> Round.Specific Round.Revealing -> RoundView msg
view wrap auth shared users config round =
let
role =
Player.role (Round.R round) auth.claims.uid
Player.role round auth.claims.uid
stage =
round.stage
{ msg, action, instruction, isCzar } =
case role of
@@ -43,13 +47,13 @@ view wrap auth shared config round =
Player.RPlayer ->
let
canBeLiked id =
(Just id /= round.likeDetail.played) && not (Set.member id round.likeDetail.liked)
(Just id /= stage.likeDetail.played) && not (Set.member id stage.likeDetail.liked)
msgPick { id, responses } =
id |> PickPlay |> wrap |> Maybe.justIf (canBeLiked id && (responses /= Nothing))
in
{ msg = msgPick
, action = Maybe.andThen (\p -> Action.Like |> Maybe.justIf (canBeLiked p)) round.pick
, action = Maybe.andThen (\p -> Action.Like |> Maybe.justIf (canBeLiked p)) stage.pick
, instruction = Strings.WaitingForCzarInstruction
, isCzar = False
}
@@ -58,10 +62,10 @@ view wrap auth shared config round =
Call.slotCount round.call
plays =
round.plays |> List.map (playDetails shared config round.likeDetail.liked slots msg)
stage.plays |> List.map (playDetails shared config slots msg)
lastRevealed =
case round.plays |> List.filter (\p -> Just p.id == round.lastRevealed) of
case stage.plays |> List.filter (\p -> Just p.id == stage.lastRevealed) of
play :: [] ->
play.responses |> Maybe.map Parts.fillsFromPlay
@@ -70,7 +74,7 @@ view wrap auth shared config round =
in
{ instruction = Just instruction
, action = action
, content = plays |> Plays.view [ ( "revealing", True ), ( "is-czar", isCzar ) ] round.pick
, content = plays |> Plays.view shared users [ ( "revealing", True ), ( "is-czar", isCzar ) ] stage.likeDetail.liked stage.pick
, slotAttrs = always []
, fillCallWith = lastRevealed |> Maybe.withDefault Dict.empty
, roundAttrs = []
@@ -81,8 +85,8 @@ view wrap auth shared config round =
{- Private -}
playDetails : Shared -> Config -> Set Play.Id -> Int -> (Play -> Maybe msg) -> Play -> Plays.Details msg
playDetails shared config liked slots msg play =
playDetails : Shared -> Config -> Int -> (Play -> Maybe msg) -> Play -> Plays.Details msg
playDetails shared config slots msg play =
let
{ id, responses } =
play
@@ -90,19 +94,9 @@ playDetails shared config liked slots msg play =
maybeMsg =
msg play
cls =
if maybeMsg /= Nothing then
[ HtmlA.class "active" ]
else
[]
cards =
responses
|> Maybe.map (List.map (\r -> Response.view shared config Card.Front [] r))
|> Maybe.withDefault (List.repeat slots (Response.viewUnknown shared []))
attrs =
[ HtmlA.class "liked" ] |> Maybe.justIf (Set.member id liked) |> Maybe.withDefault []
in
Plays.Details id cards maybeMsg (attrs ++ cls)
Plays.Details id cards maybeMsg Nothing
+44 -49
View File
@@ -18,6 +18,7 @@ module MassiveDecks.Models.Decoders exposing
, revealingRound
, settings
, sourceInfo
, specificRound
, tokenValidity
, userId
, userSummary
@@ -286,7 +287,7 @@ game : Maybe LikeDetail -> Json.Decoder Game
game ld =
Json.succeed Game
|> Json.required "round" (round ld)
|> Json.required "history" (Json.list completeRound)
|> Json.required "history" (Json.list (specificRound (completeRound Nothing)))
|> Json.required "playerOrder" (Json.list userId)
|> Json.required "players" (Json.dict player)
|> Json.required "rules" rules
@@ -862,7 +863,7 @@ privilegeChanged =
gameStarted : Json.Decoder Events.Event
gameStarted =
Json.succeed (\r -> \h -> { round = r, hand = h } |> Events.GameStarted)
|> Json.required "round" playingRound
|> Json.required "round" (specificRound playingRound)
|> Json.optional "hand" (Json.list response |> Json.map Just) Nothing
@@ -1094,26 +1095,39 @@ styleByName name =
round : Maybe LikeDetail -> Json.Decoder Round
round ld =
Json.field "stage" Json.string |> Json.andThen (roundByName ld)
let
stageDetailsByName name =
case name of
"Playing" ->
playingRound |> Json.map Round.P
"Revealing" ->
revealingRound ld |> Json.map Round.R
"Judging" ->
judgingRound ld |> Json.map Round.J
"Complete" ->
completeRound ld |> Json.map Round.C
_ ->
unknownValue "round stage" name
byName stageName =
specificRound (Json.field "stage" (stageDetailsByName stageName))
in
Json.field "stage" Json.string |> Json.andThen byName
roundByName : Maybe LikeDetail -> String -> Json.Decoder Round
roundByName ld name =
case name of
"Playing" ->
playingRound |> Json.map Round.P
"Revealing" ->
revealingRound ld |> Json.map Round.R
"Judging" ->
judgingRound ld |> Json.map Round.J
"Complete" ->
completeRound |> Json.map Round.C
_ ->
unknownValue "round stage" name
specificRound : Json.Decoder stageDetails -> Json.Decoder (Round.Specific stageDetails)
specificRound stageDetails =
Json.succeed Round.Specific
|> Json.required "id" Round.idDecoder
|> Json.required "czar" userId
|> Json.required "players" playerSet
|> Json.required "call" call
|> Json.required "startedAt" Time.timeDecoder
|> Json.custom stageDetails
playerSet : Json.Decoder (Set User.Id)
@@ -1123,13 +1137,8 @@ playerSet =
playingRound : Json.Decoder Round.Playing
playingRound =
Json.succeed Round.playing
|> Json.required "id" Round.idDecoder
|> Json.required "czar" userId
|> Json.required "players" playerSet
|> Json.required "call" call
Json.succeed (Round.Playing Round.noPick)
|> Json.required "played" playerSet
|> Json.required "startedAt" Time.timeDecoder
|> Json.optional "timedOut" Json.bool False
@@ -1142,39 +1151,25 @@ likeDetail =
revealingRound : Maybe Round.LikeDetail -> Json.Decoder Round.Revealing
revealingRound ld =
Json.succeed (Round.revealing ld)
|> Json.required "id" Round.idDecoder
|> Json.required "czar" userId
|> Json.required "players" playerSet
|> Json.required "call" call
Json.succeed (Round.Revealing (ld |> Maybe.withDefault Round.defaultLikeDetail) Nothing Nothing)
|> Json.required "plays" (Json.list play)
|> Json.required "startedAt" Time.timeDecoder
|> Json.optional "timedOut" Json.bool False
judgingRound : Maybe Round.LikeDetail -> Json.Decoder Round.Judging
judgingRound ld =
Json.succeed (Round.judging Nothing ld)
|> Json.required "id" Round.idDecoder
|> Json.required "czar" userId
|> Json.required "players" playerSet
|> Json.required "call" call
Json.succeed (Round.Judging (ld |> Maybe.withDefault Round.defaultLikeDetail) Nothing)
|> Json.required "plays" (Json.list knownPlay)
|> Json.required "startedAt" Time.timeDecoder
|> Json.optional "timedOut" Json.bool False
completeRound : Json.Decoder Round.Complete
completeRound =
Json.map8 Round.complete
(Json.field "id" Round.idDecoder)
(Json.field "czar" userId)
(Json.field "players" playerSet)
(Json.field "call" call)
(Json.field "plays" (Json.dict playWithLikes))
(Json.field "playOrder" (Json.list userId))
(Json.field "winner" userId)
(Json.field "startedAt" Time.timeDecoder)
completeRound : Maybe Round.LikeDetail -> Json.Decoder Round.Complete
completeRound ld =
Json.succeed (Round.Complete (ld |> Maybe.withDefault Round.defaultLikeDetail) Nothing)
|> Json.required "playedBy" (Json.dict playId)
|> Json.required "plays" (Json.dict playWithLikes)
|> Json.required "playOrder" (Json.list userId)
|> Json.required "winner" userId
playWithLikes : Json.Decoder Play.WithLikes
@@ -227,7 +227,7 @@ roundId =
stage : Round.Stage -> Json.Value
stage =
Round.stageToName >> Json.string
Round.stageToString >> Json.string
checkAlive : List Lobby.Token -> Json.Value
+21 -13
View File
@@ -197,7 +197,11 @@ update wrap shared msg model =
Events.GameStarted { round, hand } ->
let
( g, gameCmd ) =
Game.applyGameStarted (GameMsg >> wrap) lobby round (hand |> Maybe.withDefault [])
Game.applyGameStarted
(GameMsg >> wrap)
lobby
(round |> Round.withStage (Round.P round.stage))
(hand |> Maybe.withDefault [])
r =
model.route
@@ -1157,21 +1161,25 @@ userDetails shared game userId user =
playStateDetail : Maybe Round -> User.Id -> Maybe ( String, MdString )
playStateDetail round userId =
case round of
Just (Round.P p) ->
case Player.playState p userId of
Player.Playing ->
Just ( "playing", Strings.StillPlaying )
playStateDetail maybeRound userId =
let
givenRound round =
case round.stage of
Round.P stage ->
case Player.playState (Round.withStage stage round) userId of
Player.Playing ->
Just ( "playing", Strings.StillPlaying )
Player.Played ->
Just ( "played", Strings.Played )
Player.Played ->
Just ( "played", Strings.Played )
Player.NotInRound ->
Player.NotInRound ->
Nothing
_ ->
Nothing
_ ->
Nothing
in
maybeRound |> Maybe.andThen givenRound
viewDetails : Shared -> List (Maybe ( String, MdString )) -> List (Html msg)
@@ -32,7 +32,7 @@ type Event
| Presence { user : User.Id, state : PresenceState }
| Configured { change : Json.Patch }
-- Not a game event because we don't need to be in a game
| GameStarted { round : Round.Playing, hand : Maybe (List Card.Response) }
| GameStarted { round : Round.Specific Round.Playing, hand : Maybe (List Card.Response) }
| Game GameEvent
| PrivilegeChanged { user : User.Id, privilege : User.Privilege }
| UserRoleChanged { user : User.Id, role : User.Role, hand : Maybe (List Card.Response) }
@@ -51,6 +51,7 @@ type GameEvent
| CardDiscarded { player : User.Id, card : Card.Response, replacement : Maybe Card.Response }
| PlaySubmitted { by : User.Id }
| PlayTakenBack { by : User.Id }
| PlayLiked { play : Play.Id }
| PlayerAway { player : User.Id }
| PlayerBack { player : User.Id }
| Timed TimedState
@@ -24,40 +24,44 @@ import Set exposing (Set)
view : Shared -> Config -> Dict User.Id User -> Game.Model -> List (Html msg)
view shared config users game =
case game.game.round of
let
round =
game.game.round
in
case round.stage of
Round.P playing ->
viewPlaying shared config game.playStyles playing
viewPlaying shared config game.playStyles round playing
Round.R revealing ->
viewRevealing shared config users revealing
viewRevealing shared config users round revealing
Round.J judging ->
viewJudging shared config users judging
viewJudging shared config users round judging
Round.C complete ->
viewComplete shared config users complete
viewComplete shared config users round complete
{- Private -}
viewPlaying : Shared -> Config -> Game.PlayStyles -> Round.Playing -> List (Html msg)
viewPlaying shared config playStyles round =
viewPlaying : Shared -> Config -> Game.PlayStyles -> Round -> Round.Playing -> List (Html msg)
viewPlaying shared config playStyles round stage =
let
slots =
round.call |> Call.slotCount
in
[ viewCall shared config Nothing round.call
, viewUnknownPlays shared slots playStyles round.players round.played
, viewUnknownPlays shared slots playStyles round.players stage.played
]
viewRevealing : Shared -> Config -> Dict User.Id User -> Round.Revealing -> List (Html msg)
viewRevealing shared config users round =
viewRevealing : Shared -> Config -> Dict User.Id User -> Round -> Round.Revealing -> List (Html msg)
viewRevealing shared config users round stage =
let
fillWith =
case round.plays |> List.filter (\p -> Just p.id == round.lastRevealed) of
case stage.plays |> List.filter (\p -> Just p.id == stage.lastRevealed) of
play :: [] ->
play.responses |> Maybe.map Parts.fillsFromPlay
@@ -65,35 +69,35 @@ viewRevealing shared config users round =
Nothing
plays =
round.plays |> List.map (\p -> ( Nothing, p.responses |> Maybe.map (\r -> { play = r, likes = Nothing }) ))
stage.plays |> List.map (\p -> ( Nothing, p.responses |> Maybe.map (\r -> { play = r, likes = Nothing }) ))
in
[ viewCall shared config fillWith round.call
, viewPlays shared config (round.call |> Call.slotCount) users Nothing plays
]
viewJudging : Shared -> Config -> Dict User.Id User -> Round.Judging -> List (Html msg)
viewJudging shared config users round =
viewJudging : Shared -> Config -> Dict User.Id User -> Round -> Round.Judging -> List (Html msg)
viewJudging shared config users round stage =
let
plays =
round.plays |> List.map (\p -> ( Nothing, Just { play = p.responses, likes = Nothing } ))
stage.plays |> List.map (\p -> ( Nothing, Just { play = p.responses, likes = Nothing } ))
in
[ viewCall shared config Nothing round.call
, viewPlays shared config (round.call |> Call.slotCount) users Nothing plays
]
viewComplete : Shared -> Config -> Dict User.Id User -> Round.Complete -> List (Html msg)
viewComplete shared config users round =
viewComplete : Shared -> Config -> Dict User.Id User -> Round -> Round.Complete -> List (Html msg)
viewComplete shared config users round stage =
let
plays =
round.playOrder |> List.map (\u -> ( Just u, Dict.get u round.plays ))
stage.playOrder |> List.map (\u -> ( Just u, Dict.get u stage.plays ))
winner =
Dict.get round.winner round.plays |> Maybe.map (.play >> Parts.fillsFromPlay)
Dict.get stage.winner stage.plays |> Maybe.map (.play >> Parts.fillsFromPlay)
in
[ viewCall shared config winner round.call
, viewPlays shared config (round.call |> Call.slotCount) users (Just round.winner) plays
, viewPlays shared config (round.call |> Call.slotCount) users (Just stage.winner) plays
]
-15686
View File
File diff suppressed because it is too large Load Diff
+608 -836
View File
File diff suppressed because it is too large Load Diff
+24 -24
View File
@@ -28,51 +28,51 @@
"config.json5"
],
"dependencies": {
"ajv": "^6.12.2",
"axios": "^0.19.2",
"ajv": "^6.12.4",
"axios": "^0.20.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-winston": "^4.0.3",
"express-winston": "^4.0.5",
"express-ws": "^4.0.0",
"fast-json-patch": "^3.0.0-1",
"generic-pool": "^3.7.1",
"hashids": "^2.2.1",
"helmet": "^3.23.0",
"http-status-codes": "^1.3.2",
"helmet": "^4.1.0",
"http-status-codes": "^2.1.2",
"json5": "^2.1.3",
"jsonwebtoken": "^8.5.1",
"moment": "^2.26.0",
"pg": "^8.2.1",
"rfc6902": "^3.0.4",
"moment": "^2.27.0",
"pg": "^8.3.3",
"rfc6902": "^4.0.0",
"source-map-support": "^0.5.19",
"uuid": "^8.1.0",
"winston": "^3.2.1",
"uuid": "^8.3.0",
"winston": "^3.3.3",
"wu": "^2.1.0"
},
"devDependencies": {
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.7",
"@types/express": "^4.17.8",
"@types/express-serve-static-core": "^4.17.12",
"@types/express-winston": "^4.0.0",
"@types/express-ws": "^3.0.0",
"@types/generic-pool": "^3.1.9",
"@types/hashids": "^1.0.30",
"@types/helmet": "^0.0.47",
"@types/hashids": "^1.0.31",
"@types/helmet": "^0.0.48",
"@types/json5": "0.0.30",
"@types/jsonwebtoken": "^8.5.0",
"@types/pg": "^7.14.3",
"@types/qs": "^6.9.3",
"@types/source-map-support": "^0.5.1",
"@types/uuid": "^8.0.0",
"@types/pg": "^7.14.4",
"@types/qs": "^6.9.4",
"@types/source-map-support": "^0.5.3",
"@types/uuid": "^8.3.0",
"@types/wu": "^2.1.42",
"@typescript-eslint/eslint-plugin": "^3.2.0",
"@typescript-eslint/parser": "^3.2.0",
"eslint": "^7.1.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"eslint": "^7.8.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-prettier": "^3.1.4",
"nodemon": "^2.0.4",
"prettier": "2.0.5",
"typescript": "^3.9.3",
"prettier": "2.1.1",
"typescript": "^4.0.2",
"typescript-json-validator": "^2.4.2"
}
}
+5 -2
View File
@@ -12,6 +12,7 @@ import * as EnforceTimeLimit from "./game-action/enforce-time-limit";
import * as PlayerAction from "./game-action/player";
import * as Redraw from "./game-action/redraw";
import * as SetPresence from "./game-action/set-presence";
import * as Like from "./game-action/like";
/**
* An action only a player can perform.
@@ -21,7 +22,8 @@ export type GameAction =
| Czar.Czar
| Redraw.Redraw
| EnforceTimeLimit.EnforceTimeLimit
| SetPresence.SetPresence;
| SetPresence.SetPresence
| Like.Like;
class GameActions extends Actions.Group<
Action.Action,
@@ -35,7 +37,8 @@ class GameActions extends Actions.Group<
Czar.actions,
Redraw.actions,
EnforceTimeLimit.actions,
SetPresence.actions
SetPresence.actions,
Like.actions
);
}
@@ -1,12 +1,14 @@
import * as Play from "../../../games/cards/play";
import * as Round from "../../../games/game/round";
import * as Lobby from "../../../lobby";
import * as Actions from "../../actions";
import * as Handler from "../../handler";
import { Player } from "../player";
import * as Play from "../../games/cards/play";
import * as Round from "../../games/game/round";
import * as Lobby from "../../lobby";
import * as Actions from "../actions";
import * as Handler from "../handler";
import { GameAction } from "../game-action";
import * as PlayLiked from "../../events/game-event/play-liked";
import * as Event from "../../event";
/**
* A player plays a white card into a round.
* A player or spectator likes a play.
*/
export interface Like {
action: "Like";
@@ -14,7 +16,7 @@ export interface Like {
}
class LikeActions extends Actions.Implementation<
Player,
GameAction,
Like,
"Like",
Lobby.WithActiveGame
@@ -27,11 +29,9 @@ class LikeActions extends Actions.Implementation<
action
) => {
if (
lobby.game.round.verifyStage<Round.Revealing | Round.Judging>(
action,
"Revealing",
"Judging"
)
lobby.game.round.verifyStage<
Round.Revealing | Round.Judging | Round.Complete
>(action, "Revealing", "Judging", "Complete")
) {
const cRound = lobby.game.round;
const target = cRound.plays.find((p) => p.id === action.play);
@@ -41,8 +41,13 @@ class LikeActions extends Actions.Implementation<
target.likes.find((id) => id === auth.uid) === undefined
) {
target.likes.push(auth.uid);
const events =
lobby.game.round.stage === "Complete"
? [Event.targetAll(PlayLiked.of(action.play))]
: [];
return {
lobby,
events,
};
} else {
return {};
+25 -9
View File
@@ -1,9 +1,12 @@
import * as Actions from "./../actions";
import * as Like from "./player/like";
import * as Submit from "./player/submit";
import * as TakeBack from "./player/take-back";
import * as Fill from "./player/fill";
import * as Discard from "./player/discard";
import * as Action from "../../action";
import * as Lobby from "../../lobby";
import * as Token from "../../user/token";
import * as GameAction from "../game-action";
/**
* An action only players can perform.
@@ -11,14 +14,27 @@ import * as Discard from "./player/discard";
export type Player =
| Submit.Submit
| TakeBack.TakeBack
| Like.Like
| Fill.Fill
| Discard.Discard;
export const actions = new Actions.PassThroughGroup(
Submit.actions,
TakeBack.actions,
Like.actions,
Fill.actions,
Discard.actions
);
class PlayerActions extends Actions.Group<
Action.Action,
Player,
Lobby.WithActiveGame,
Lobby.WithActiveGame
> {
constructor() {
super(Submit.actions, TakeBack.actions, Fill.actions, Discard.actions);
}
limit(
auth: Token.Claims,
lobby: Lobby.WithActiveGame,
action: Player
): lobby is Lobby.WithActiveGame {
GameAction.expectRole(auth, action, lobby.game, "Player");
return true;
}
}
export const actions = new PlayerActions();
+5 -5
View File
@@ -39,9 +39,6 @@ export const Schema = {
{
$ref: "#/definitions/TakeBack",
},
{
$ref: "#/definitions/Like",
},
{
$ref: "#/definitions/Fill",
},
@@ -60,6 +57,9 @@ export const Schema = {
{
$ref: "#/definitions/EnforceTimeLimit",
},
{
$ref: "#/definitions/Like",
},
{
$ref: "#/definitions/Configure",
},
@@ -384,7 +384,7 @@ export const Schema = {
type: "object",
},
Id: {
description: "A unique id for a play.",
description: "A unique id for an instance of a card.",
type: "string",
},
Id_1: {
@@ -455,7 +455,7 @@ export const Schema = {
Like: {
additionalProperties: false,
defaultProperties: [],
description: "A player plays a white card into a round.",
description: "A player or spectator likes a play.",
properties: {
action: {
enum: ["Like"],
+3 -1
View File
@@ -12,6 +12,7 @@ import { StageTimerDone } from "./game-event/stage-timer-done";
import { StartRevealing } from "./game-event/start-revealing";
import { StartJudging } from "./game-event/start-judging";
import { CardDiscarded } from "./game-event/card-discarded";
import { PlayLiked } from "./game-event/play-liked";
export type GameEvent =
| GameStarted
@@ -27,4 +28,5 @@ export type GameEvent =
| PlayerPresenceChanged
| PauseStateChanged
| StageTimerDone
| GameEnded;
| GameEnded
| PlayLiked;
@@ -0,0 +1,15 @@
import * as Play from "../../games/cards/play";
/**
* Indicates a play was liked.
* Note this is only sent out after likes are visible (when the round is complete).
*/
export interface PlayLiked {
event: "PlayLiked";
id: Play.Id;
}
export const of = (id: Play.Id): PlayLiked => ({
event: "PlayLiked",
id,
});
+3 -3
View File
@@ -34,14 +34,14 @@ export function reduce<T, L extends Lobby>(
toChange: (lobby: L, item: T) => ConstrainedChange<L>
): ConstrainedChange<L> {
let currentLobby = lobby;
let lobbyUnchanged = true;
let lobbyChanged = false;
const events: Event.Distributor[] = [];
const timeouts: Timeout.After[] = [];
const tasks: Task[] = [];
for (const item of items) {
const result = toChange(currentLobby, item);
if (result.lobby !== undefined) {
lobbyUnchanged = false;
lobbyChanged = true;
currentLobby = result.lobby;
}
if (result.events !== undefined) {
@@ -55,7 +55,7 @@ export function reduce<T, L extends Lobby>(
}
}
return {
...(lobbyUnchanged ? { lobby: currentLobby } : {}),
...(lobbyChanged ? { lobby: currentLobby } : {}),
...(events.length > 0 ? { events } : {}),
...(timeouts.length > 0 ? { timeouts } : {}),
...(tasks.length > 0 ? { tasks } : {}),