Resolve #100: Likes now actually have an effect.
This commit is contained in:
@@ -8,7 +8,7 @@ bunch of people in the same room on phones, or on voice chat online.
|
||||
|
||||
**[Play Massive Decks][hosted]**
|
||||
|
||||
[hosted]: http://md.rereadgames.com/
|
||||
[hosted]: https://md.rereadgames.com/
|
||||
[cah]: https://cardsagainsthumanity.com/
|
||||
|
||||
## About
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
module MassiveDecks.Card.Play exposing
|
||||
( Id
|
||||
( Details
|
||||
, Id
|
||||
, Known
|
||||
, Play
|
||||
, WithLikes
|
||||
, asKnown
|
||||
, asPlay
|
||||
)
|
||||
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.User as User
|
||||
|
||||
|
||||
{-| The id for a play.
|
||||
@@ -23,6 +26,22 @@ type alias Play =
|
||||
}
|
||||
|
||||
|
||||
{-| Details we find out about a play when the round is complete.
|
||||
-}
|
||||
type alias Details =
|
||||
{ playedBy : User.Id
|
||||
, likes : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
{-| A play with the number of likes it received.
|
||||
-}
|
||||
type alias WithLikes =
|
||||
{ play : List Card.Response
|
||||
, likes : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
{-| A known play.
|
||||
-}
|
||||
type alias Known =
|
||||
|
||||
@@ -177,23 +177,24 @@ update wrap shared msg model =
|
||||
( model, cmd )
|
||||
|
||||
Like ->
|
||||
-- TODO: Actually sending this somewhere.
|
||||
let
|
||||
newRound =
|
||||
( newRound, action ) =
|
||||
case game.round of
|
||||
Round.J judging ->
|
||||
{ judging
|
||||
( { judging
|
||||
| liked =
|
||||
judging.pick
|
||||
|> Maybe.map (\p -> Set.insert p judging.liked)
|
||||
|> Maybe.withDefault judging.liked
|
||||
}
|
||||
}
|
||||
|> Round.J
|
||||
, judging.pick |> Maybe.map Actions.like |> Maybe.withDefault Cmd.none
|
||||
)
|
||||
|
||||
_ ->
|
||||
game.round
|
||||
( game.round, Cmd.none )
|
||||
in
|
||||
( { model | game = { game | round = newRound } }, Cmd.none )
|
||||
( { model | game = { game | round = newRound } }, action )
|
||||
|
||||
SetPlayStyles playStyles ->
|
||||
( { model | playStyles = playStyles }, Cmd.none )
|
||||
@@ -413,10 +414,10 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
|
||||
game =
|
||||
model.game
|
||||
|
||||
newPlayers =
|
||||
playersWithWinner =
|
||||
game.players |> Dict.update winner (Maybe.map (\p -> { p | score = p.score + 1 }))
|
||||
|
||||
( newRound, history, speech ) =
|
||||
{ newRound, history, speech, newPlayers } =
|
||||
case game.round of
|
||||
Round.J r ->
|
||||
let
|
||||
@@ -439,16 +440,36 @@ applyGameEvent wrap wrapEvent auth shared gameEvent model =
|
||||
|
||||
tts =
|
||||
Dict.get winner playsDict
|
||||
|> Maybe.map (\p -> speak shared r.call (Just p))
|
||||
|> Maybe.map (\p -> speak shared r.call (Just p.play))
|
||||
|> Maybe.withDefault Cmd.none
|
||||
|
||||
ps =
|
||||
playersWithWinner |> Dict.map (updateLikes playsDict)
|
||||
in
|
||||
( complete |> Round.C, complete :: game.history, tts )
|
||||
{ newRound = complete |> Round.C
|
||||
, history = complete :: game.history
|
||||
, speech = tts
|
||||
, newPlayers = ps
|
||||
}
|
||||
|
||||
_ ->
|
||||
-- TODO: Error
|
||||
( game.round, game.history, Cmd.none )
|
||||
{ newRound = game.round
|
||||
, history = game.history
|
||||
, speech = Cmd.none
|
||||
, newPlayers = playersWithWinner
|
||||
}
|
||||
in
|
||||
( { model | game = { game | round = newRound, players = newPlayers, history = history } }, speech )
|
||||
( { model
|
||||
| game =
|
||||
{ game
|
||||
| round = newRound
|
||||
, players = newPlayers
|
||||
, history = history
|
||||
}
|
||||
}
|
||||
, speech
|
||||
)
|
||||
|
||||
Events.RoundStarted { id, czar, players, call, drawn } ->
|
||||
let
|
||||
@@ -629,6 +650,15 @@ hotJoinPlayer player model =
|
||||
{- Private -}
|
||||
|
||||
|
||||
updateLikes : Dict User.Id Play.WithLikes -> User.Id -> Player -> Player
|
||||
updateLikes plays uid player =
|
||||
plays
|
||||
|> Dict.get uid
|
||||
|> Maybe.andThen .likes
|
||||
|> Maybe.map (\likes -> { player | likes = likes + player.likes })
|
||||
|> Maybe.withDefault player
|
||||
|
||||
|
||||
viewWinner : Shared -> Dict User.Id User -> Set User.Id -> List (Html msg)
|
||||
viewWinner shared users winners =
|
||||
[ Wl.card [ HtmlA.id "game-winner" ]
|
||||
@@ -911,9 +941,9 @@ reveal target responses play =
|
||||
play
|
||||
|
||||
|
||||
resolvePlayedBy : Dict Play.Id User.Id -> Play.Known -> Maybe ( User.Id, List Card.Response )
|
||||
resolvePlayedBy : Dict Play.Id Play.Details -> Play.Known -> Maybe ( User.Id, Play.WithLikes )
|
||||
resolvePlayedBy playedBy k =
|
||||
Dict.get k.id playedBy |> Maybe.map (\u -> ( u, k.responses ))
|
||||
Dict.get k.id playedBy |> Maybe.map (\d -> ( d.playedBy, { play = k.responses, likes = d.likes } ))
|
||||
|
||||
|
||||
type alias ActionDetails msg =
|
||||
|
||||
@@ -8,9 +8,7 @@ import Html.Events as HtmlE
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Game.Action.Model exposing (..)
|
||||
import MassiveDecks.Game.Messages as Game exposing (Msg)
|
||||
import MassiveDecks.Messages as Global
|
||||
import MassiveDecks.Model exposing (..)
|
||||
import MassiveDecks.Pages.Lobby.Messages as Lobby
|
||||
import MassiveDecks.Strings as Strings exposing (MdString)
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import Weightless.Attributes as WlA
|
||||
|
||||
@@ -8,6 +8,7 @@ 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.Play as Play
|
||||
import MassiveDecks.Card.Response as Response
|
||||
import MassiveDecks.Components as Components
|
||||
import MassiveDecks.Game.Messages exposing (Msg(..))
|
||||
@@ -54,13 +55,16 @@ viewRound : Shared -> Config -> Dict User.Id User -> Round.Complete -> ( String,
|
||||
viewRound shared config users round =
|
||||
let
|
||||
winning =
|
||||
round.plays |> Dict.get round.winner |> Maybe.withDefault [] |> List.map .body
|
||||
round.plays |> Dict.get round.winner
|
||||
|
||||
winningBody =
|
||||
winning |> Maybe.map .play |> 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 ]
|
||||
[ Plays.byLine shared users round.czar (Just Icon.gavel) Nothing
|
||||
, Html.div [] [ Call.viewFilled shared config Card.Front [] winningBody round.call ]
|
||||
]
|
||||
, HtmlK.ul [ HtmlA.class "plays cards" ]
|
||||
(round.plays |> Dict.toList |> List.map (viewPlay shared config users round.winner))
|
||||
@@ -68,15 +72,15 @@ viewRound shared config users round =
|
||||
)
|
||||
|
||||
|
||||
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, List Card.Response ) -> ( String, Html msg )
|
||||
viewPlay shared config users winner ( id, responses ) =
|
||||
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, Play.WithLikes ) -> ( String, Html msg )
|
||||
viewPlay shared config users winner ( id, { play, likes } ) =
|
||||
let
|
||||
cards =
|
||||
responses |> List.map (\r -> ( r.details.id, Html.li [] [ Response.view config Card.Front [] r ] ))
|
||||
play |> 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))
|
||||
[ Plays.byLine shared users id (Icon.trophy |> Maybe.justIf (winner == id)) likes
|
||||
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
|
||||
]
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import Set
|
||||
-}
|
||||
type alias Player =
|
||||
{ score : Score
|
||||
, likes : Int
|
||||
, presence : Presence
|
||||
}
|
||||
|
||||
@@ -28,6 +29,7 @@ type alias Player =
|
||||
default : Player
|
||||
default =
|
||||
{ score = 0
|
||||
, likes = 0
|
||||
, presence = Active
|
||||
}
|
||||
|
||||
|
||||
@@ -179,10 +179,10 @@ judging id czar players call plays startedAt timedOut =
|
||||
{-| A round that has been finished.
|
||||
-}
|
||||
type alias Complete =
|
||||
Data { plays : Dict User.Id (List Card.Response), playOrder : List User.Id, winner : User.Id }
|
||||
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 (List Card.Response) -> List User.Id -> User.Id -> Time -> Complete
|
||||
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
|
||||
|
||||
@@ -6,6 +6,7 @@ import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import Html.Keyed as HtmlK
|
||||
import MassiveDecks.Card.Model as Card
|
||||
import MassiveDecks.Card.Play as Play
|
||||
import MassiveDecks.Card.Response as Response
|
||||
import MassiveDecks.Game.Action.Model as Action
|
||||
import MassiveDecks.Game.Model exposing (..)
|
||||
@@ -29,22 +30,22 @@ view shared nextRoundReady config users round =
|
||||
, content =
|
||||
HtmlK.ul [ HtmlA.class "complete plays cards" ]
|
||||
(round.playOrder
|
||||
|> List.map (\u -> ( u, Dict.get u round.plays |> Maybe.withDefault [] ))
|
||||
|> List.map (\u -> ( u, Dict.get u round.plays ))
|
||||
|> List.map (viewPlay shared config users round.winner)
|
||||
)
|
||||
, fillCallWith = winning |> Maybe.withDefault []
|
||||
, fillCallWith = winning |> Maybe.map .play |> Maybe.withDefault []
|
||||
}
|
||||
|
||||
|
||||
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, List Card.Response ) -> ( String, Html msg )
|
||||
viewPlay shared config users winner ( id, responses ) =
|
||||
viewPlay : Shared -> Config -> Dict User.Id User -> User.Id -> ( User.Id, Maybe Play.WithLikes ) -> ( String, Html msg )
|
||||
viewPlay shared config users winner ( id, play ) =
|
||||
let
|
||||
cards =
|
||||
responses |> List.map (\r -> ( r.details.id, Html.li [] [ Response.view config Card.Front [] r ] ))
|
||||
play |> Maybe.map (.play >> List.map (\r -> ( r.details.id, Html.li [] [ Response.view config Card.Front [] r ] ))) |> Maybe.withDefault []
|
||||
in
|
||||
( id
|
||||
, Html.li [ HtmlA.class "with-byline" ]
|
||||
[ Plays.byLine shared users id (Icon.trophy |> Maybe.justIf (winner == id))
|
||||
[ Plays.byLine shared users id (Icon.trophy |> Maybe.justIf (winner == id)) (play |> Maybe.andThen .likes)
|
||||
, HtmlK.ol [ HtmlA.class "play card-set" ] cards
|
||||
]
|
||||
)
|
||||
|
||||
@@ -34,8 +34,8 @@ view class picked details =
|
||||
|
||||
{-| Create a byline.
|
||||
-}
|
||||
byLine : Shared -> Dict User.Id User -> User.Id -> Maybe Icon -> Html msg
|
||||
byLine shared users id icon =
|
||||
byLine : Shared -> Dict User.Id User -> User.Id -> Maybe Icon -> Maybe Int -> Html msg
|
||||
byLine shared users id icon likes =
|
||||
let
|
||||
name =
|
||||
users |> Dict.get id |> Maybe.map .name |> Maybe.withDefault (Strings.UnknownUser |> Lang.string shared)
|
||||
@@ -43,6 +43,7 @@ byLine shared users id icon =
|
||||
Html.span [ HtmlA.class "byline", HtmlA.title name ]
|
||||
[ icon |> Maybe.map Icon.viewIcon |> Maybe.withDefault Html.nothing
|
||||
, Html.text name
|
||||
, likes |> Maybe.map (\l -> Strings.Likes { total = l } |> Lang.html shared) |> Maybe.withDefault Html.nothing
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -310,8 +310,9 @@ rando =
|
||||
|
||||
player : Json.Decoder Player
|
||||
player =
|
||||
Json.map2 Player
|
||||
Json.map3 Player
|
||||
(Json.field "score" score)
|
||||
(Json.field "likes" Json.int)
|
||||
(Json.field "presence" playerPresence)
|
||||
|
||||
|
||||
@@ -621,7 +622,14 @@ roundFinished : Json.Decoder Events.TimedGameEvent
|
||||
roundFinished =
|
||||
Json.map2 (\wi -> \pb -> Events.RoundFinished { winner = wi, playedBy = pb })
|
||||
(Json.field "winner" userId)
|
||||
(Json.field "playedBy" (Json.dict userId))
|
||||
(Json.field "playDetails" (Json.dict playDetails))
|
||||
|
||||
|
||||
playDetails : Json.Decoder Play.Details
|
||||
playDetails =
|
||||
Json.map2 Play.Details
|
||||
(Json.field "playedBy" userId)
|
||||
(Json.maybe (Json.field "likes" Json.int))
|
||||
|
||||
|
||||
playRevealed : Json.Decoder Events.TimedGameEvent
|
||||
@@ -1026,12 +1034,19 @@ completeRound =
|
||||
(Json.field "czar" userId)
|
||||
(Json.field "players" playerSet)
|
||||
(Json.field "call" call)
|
||||
(Json.field "plays" (Json.dict (Json.list response)))
|
||||
(Json.field "plays" (Json.dict playWithLikes))
|
||||
(Json.field "playOrder" (Json.list userId))
|
||||
(Json.field "winner" userId)
|
||||
(Json.field "startedAt" Time.timeDecoder)
|
||||
|
||||
|
||||
playWithLikes : Json.Decoder Play.WithLikes
|
||||
playWithLikes =
|
||||
Json.map2 Play.WithLikes
|
||||
(Json.field "play" (Json.list response))
|
||||
(Json.maybe (Json.field "likes" Json.int))
|
||||
|
||||
|
||||
playerRole : Json.Decoder Player.Role
|
||||
playerRole =
|
||||
Json.string |> Json.andThen playerRoleByName
|
||||
|
||||
@@ -772,8 +772,11 @@ userDetails shared game userId user =
|
||||
|
||||
score =
|
||||
player |> Maybe.map (\p -> Strings.Score { total = p.score })
|
||||
|
||||
likes =
|
||||
player |> Maybe.map (\p -> Strings.Likes { total = p.likes })
|
||||
in
|
||||
( viewDetails shared details, viewDetails shared [ score ] )
|
||||
( viewDetails shared details, viewDetails shared [ score, likes ] )
|
||||
|
||||
|
||||
playStateDetail : Maybe Round -> User.Id -> Maybe MdString
|
||||
|
||||
@@ -8,6 +8,7 @@ module MassiveDecks.Pages.Lobby.Actions exposing
|
||||
, judge
|
||||
, kick
|
||||
, leave
|
||||
, like
|
||||
, redraw
|
||||
, removeDeck
|
||||
, reveal
|
||||
@@ -95,6 +96,11 @@ judge play =
|
||||
action "Judge" [ ( "winner", play |> Json.string ) ]
|
||||
|
||||
|
||||
like : Play.Id -> Cmd msg
|
||||
like play =
|
||||
action "Like" [ ( "play", play |> Json.string ) ]
|
||||
|
||||
|
||||
redraw : Cmd msg
|
||||
redraw =
|
||||
action "Redraw" []
|
||||
|
||||
@@ -90,5 +90,5 @@ type TimedGameEvent
|
||||
, drawn : Maybe (List Card.Response)
|
||||
}
|
||||
| StartRevealing { plays : List Play.Id, drawn : Maybe (List Card.Response) }
|
||||
| RoundFinished { winner : User.Id, playedBy : Dict Play.Id User.Id }
|
||||
| RoundFinished { winner : User.Id, playedBy : Dict Play.Id Play.Details }
|
||||
| PlayRevealed { id : Play.Id, play : List Card.Response }
|
||||
|
||||
@@ -7,12 +7,14 @@ import Html exposing (Html)
|
||||
import Html.Attributes as HtmlA
|
||||
import MassiveDecks.Card.Call as Call
|
||||
import MassiveDecks.Card.Model as Card exposing (Call)
|
||||
import MassiveDecks.Card.Play exposing (Play)
|
||||
import MassiveDecks.Card.Play as Play exposing (Play)
|
||||
import MassiveDecks.Card.Response as Response
|
||||
import MassiveDecks.Game.Model as Game
|
||||
import MassiveDecks.Game.Round as Round exposing (Round)
|
||||
import MassiveDecks.Model exposing (Shared)
|
||||
import MassiveDecks.Pages.Lobby.Configure.Model exposing (Config)
|
||||
import MassiveDecks.Strings as Strings
|
||||
import MassiveDecks.Strings.Languages as Lang
|
||||
import MassiveDecks.User as User exposing (User)
|
||||
import MassiveDecks.Util.Maybe as Maybe
|
||||
import Round
|
||||
@@ -62,10 +64,10 @@ viewRevealing shared config users round =
|
||||
Nothing
|
||||
|
||||
plays =
|
||||
round.plays |> List.map (\p -> ( Nothing, p.responses ))
|
||||
round.plays |> List.map (\p -> ( Nothing, p.responses |> Maybe.map (\r -> { play = r, likes = Nothing }) ))
|
||||
in
|
||||
[ viewCall shared config fillWith round.call
|
||||
, viewPlays config (round.call |> Call.slotCount) users Nothing plays
|
||||
, viewPlays shared config (round.call |> Call.slotCount) users Nothing plays
|
||||
]
|
||||
|
||||
|
||||
@@ -73,10 +75,10 @@ viewJudging : Shared -> Config -> Dict User.Id User -> Round.Judging -> List (Ht
|
||||
viewJudging shared config users round =
|
||||
let
|
||||
plays =
|
||||
round.plays |> List.map (\p -> ( Nothing, Just p.responses ))
|
||||
round.plays |> List.map (\p -> ( Nothing, Just { play = p.responses, likes = Nothing } ))
|
||||
in
|
||||
[ viewCall shared config Nothing round.call
|
||||
, viewPlays config (round.call |> Call.slotCount) users Nothing plays
|
||||
, viewPlays shared config (round.call |> Call.slotCount) users Nothing plays
|
||||
]
|
||||
|
||||
|
||||
@@ -87,10 +89,10 @@ viewComplete shared config users round =
|
||||
round.playOrder |> List.map (\u -> ( Just u, Dict.get u round.plays ))
|
||||
|
||||
winner =
|
||||
Dict.get round.winner round.plays
|
||||
Dict.get round.winner round.plays |> Maybe.map .play
|
||||
in
|
||||
[ viewCall shared config winner round.call
|
||||
, viewPlays config (round.call |> Call.slotCount) users (Just round.winner) plays
|
||||
, viewPlays shared config (round.call |> Call.slotCount) users (Just round.winner) plays
|
||||
]
|
||||
|
||||
|
||||
@@ -117,17 +119,17 @@ viewUnknownPlays slotCount playStyles players played =
|
||||
)
|
||||
|
||||
|
||||
viewPlays : Config -> Int -> Dict User.Id User -> Maybe User.Id -> List ( Maybe User.Id, Maybe (List Card.Response) ) -> Html msg
|
||||
viewPlays config slotCount users winner plays =
|
||||
viewPlays : Shared -> Config -> Int -> Dict User.Id User -> Maybe User.Id -> List ( Maybe User.Id, Maybe Play.WithLikes ) -> Html msg
|
||||
viewPlays shared config slotCount users winner plays =
|
||||
let
|
||||
angle =
|
||||
plays |> List.length |> anglePerPlay
|
||||
in
|
||||
Html.ul [ HtmlA.id "plays" ] (plays |> List.indexedMap (viewPlayByIndex config slotCount users winner angle))
|
||||
Html.ul [ HtmlA.id "plays" ] (plays |> List.indexedMap (viewPlayByIndex shared config slotCount users winner angle))
|
||||
|
||||
|
||||
viewPlayByIndex : Config -> Int -> Dict User.Id User -> Maybe User.Id -> Float -> Int -> ( Maybe User.Id, Maybe (List Card.Response) ) -> Html msg
|
||||
viewPlayByIndex config slotCount users winner angle index ( user, play ) =
|
||||
viewPlayByIndex : Shared -> Config -> Int -> Dict User.Id User -> Maybe User.Id -> Float -> Int -> ( Maybe User.Id, Maybe Play.WithLikes ) -> Html msg
|
||||
viewPlayByIndex shared config slotCount users winner angle index ( user, play ) =
|
||||
let
|
||||
isWinner =
|
||||
case winner of
|
||||
@@ -137,7 +139,7 @@ viewPlayByIndex config slotCount users winner angle index ( user, play ) =
|
||||
Nothing ->
|
||||
False
|
||||
in
|
||||
viewPlay config slotCount (toFloat index * angle) (user |> Maybe.andThen (\u -> Dict.get u users)) isWinner play
|
||||
viewPlay shared config slotCount (toFloat index * angle) (user |> Maybe.andThen (\u -> Dict.get u users)) isWinner play
|
||||
|
||||
|
||||
anglePerPlay : Int -> Float
|
||||
@@ -152,11 +154,11 @@ closeDistance =
|
||||
|
||||
farDistance : number
|
||||
farDistance =
|
||||
85
|
||||
150
|
||||
|
||||
|
||||
viewPlay : Config -> Int -> Float -> Maybe User -> Bool -> Maybe (List Card.Response) -> Html msg
|
||||
viewPlay config slotCount angle playedBy isWinner responses =
|
||||
viewPlay : Shared -> Config -> Int -> Float -> Maybe User -> Bool -> Maybe Play.WithLikes -> Html msg
|
||||
viewPlay shared config slotCount angle playedBy isWinner play =
|
||||
Html.li
|
||||
(positionFromAngle angle closeDistance)
|
||||
[ Html.div [ HtmlA.class "with-byline" ]
|
||||
@@ -164,13 +166,14 @@ viewPlay config slotCount angle playedBy isWinner responses =
|
||||
(List.filterMap identity
|
||||
[ Icon.viewIcon Icon.trophy |> Maybe.justIf isWinner
|
||||
, playedBy |> Maybe.map (.name >> Html.text)
|
||||
, play |> Maybe.andThen .likes |> Maybe.map (\l -> Strings.Likes { total = l } |> Lang.html shared)
|
||||
]
|
||||
)
|
||||
, Html.ol
|
||||
[ HtmlA.classList [ ( "play", True ), ( "card-set", True ) ]
|
||||
]
|
||||
(responses
|
||||
|> Maybe.map (List.map (\response -> Html.li [] [ Response.view config Card.Front [] response ]))
|
||||
(play
|
||||
|> Maybe.map (.play >> List.map (\response -> Html.li [] [ Response.view config Card.Front [] response ]))
|
||||
|> Maybe.withDefault (List.repeat slotCount (Html.li [] [ Response.viewUnknown [] ]))
|
||||
)
|
||||
]
|
||||
|
||||
@@ -141,6 +141,8 @@ type MdString
|
||||
| AiDescription -- A description of a player who is controlled by the computer.
|
||||
| Score { total : Int } -- A display of a score.
|
||||
| ScoreDescription -- A description of a player's score.
|
||||
| Likes { total : Int } -- A display of a number of likes.
|
||||
| LikesDescription -- A description of the number of likes a play received or a player has recieved.
|
||||
| ToggleUserList -- A description of the action of showing or hiding the user list.
|
||||
| GameMenu -- A description of the game menu.
|
||||
| UnknownUser -- A name for a user that doesn't have a known name.
|
||||
|
||||
@@ -503,6 +503,13 @@ translate mdString =
|
||||
, Text " this player has."
|
||||
]
|
||||
|
||||
Likes { total } ->
|
||||
[ Text (String.fromInt total) ]
|
||||
|
||||
LikesDescription ->
|
||||
[ Text "The number of likes received."
|
||||
]
|
||||
|
||||
ToggleUserList ->
|
||||
[ Text "Show or hide the scoreboard." ]
|
||||
|
||||
|
||||
@@ -156,6 +156,9 @@ enhanceHtml context mdString unenhanced =
|
||||
Score _ ->
|
||||
[ Html.span [ HtmlA.class "no-wrap", (ScoreDescription |> asString context) |> HtmlA.title ] (suffixed unenhanced Icon.star) ]
|
||||
|
||||
Likes _ ->
|
||||
[ Html.span [ HtmlA.class "no-wrap", (LikesDescription |> asString context) |> HtmlA.title ] (suffixed unenhanced Icon.thumbsUp) ]
|
||||
|
||||
NumberOfCards _ ->
|
||||
[ Html.span [ HtmlA.class "amount" ] unenhanced ]
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
svg {
|
||||
margin-right: 0.5em;
|
||||
> * {
|
||||
margin: 0 0.25em 0 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: $users-width - 8rem;
|
||||
}
|
||||
|
||||
ol {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
$top-bar-height: 4rem;
|
||||
$users-width: 15rem;
|
||||
$users-width: 18rem;
|
||||
|
||||
@@ -278,7 +278,7 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
devServer: {
|
||||
hot: true,
|
||||
allowedHosts: ["localhost", "f2a7ec0b.ngrok.io"],
|
||||
allowedHosts: ["localhost"],
|
||||
proxy: {
|
||||
// Forward to the server.
|
||||
"/api/**": {
|
||||
|
||||
@@ -26,13 +26,22 @@ export const handle: gameAction.Handler<Judge> = (auth, lobby, action) => {
|
||||
const lobbyRound = game.round;
|
||||
const plays = lobbyRound.plays;
|
||||
if (lobbyRound.verifyStage<round.Judging>(action, "Judging")) {
|
||||
const play = plays.find(play => play.id === action.winner);
|
||||
if (play === undefined) {
|
||||
let winningPlay = undefined;
|
||||
for (const play of plays) {
|
||||
if (play.likes.size > 0) {
|
||||
const player = game.players.get(play.playedBy) as Player;
|
||||
player.likes += play.likes.size;
|
||||
}
|
||||
if (play.id === action.winner) {
|
||||
winningPlay = play;
|
||||
}
|
||||
}
|
||||
if (winningPlay === undefined) {
|
||||
throw new InvalidActionError("Given play doesn't exist.");
|
||||
}
|
||||
const player = game.players.get(play.playedBy) as Player;
|
||||
const player = game.players.get(winningPlay.playedBy) as Player;
|
||||
player.score += 1;
|
||||
const completedRound = lobbyRound.advance(play.playedBy);
|
||||
const completedRound = lobbyRound.advance(winningPlay.playedBy);
|
||||
game.round = completedRound;
|
||||
game.history.splice(0, 0, completedRound.public());
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import wu from "wu";
|
||||
import { Action } from "../../action";
|
||||
import * as util from "../../util";
|
||||
import * as gameAction from "../game-action";
|
||||
import { Like } from "./player/like";
|
||||
import * as like from "./player/like";
|
||||
import * as submit from "./player/submit";
|
||||
import { Submit } from "./player/submit";
|
||||
import * as takeBack from "./player/take-back";
|
||||
@@ -10,9 +12,9 @@ import { TakeBack } from "./player/take-back";
|
||||
/**
|
||||
* An action only the czar can perform.
|
||||
*/
|
||||
export type Player = Submit | TakeBack;
|
||||
export type Player = Submit | TakeBack | Like;
|
||||
|
||||
const possible = new Set([submit.is, takeBack.is]);
|
||||
const possible = new Set([submit.is, takeBack.is, like.is]);
|
||||
|
||||
/**
|
||||
* Check if an action is a non-czar action.
|
||||
@@ -32,6 +34,8 @@ export const handle: gameAction.Handler<Player> = (
|
||||
return submit.handle(auth, lobby, action, server);
|
||||
} else if (takeBack.is(action)) {
|
||||
return takeBack.handle(auth, lobby, action, server);
|
||||
} else if (like.is(action)) {
|
||||
return like.handle(auth, lobby, action, server);
|
||||
} else {
|
||||
return util.assertNever(action);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Action } from "../../../action";
|
||||
import * as round from "../../../games/game/round";
|
||||
import * as gameAction from "../../game-action";
|
||||
import * as play from "../../../games/cards/play";
|
||||
|
||||
/**
|
||||
* A player plays a white card into a round.
|
||||
*/
|
||||
export interface Like {
|
||||
action: NameType;
|
||||
play: play.Id;
|
||||
}
|
||||
|
||||
type NameType = "Like";
|
||||
const name: NameType = "Like";
|
||||
|
||||
/**
|
||||
* Check if an action is a take back action.
|
||||
* @param action The action to check.
|
||||
*/
|
||||
export const is = (action: Action): action is Like => action.action === name;
|
||||
|
||||
export const handle: gameAction.Handler<Like> = (auth, lobby, action) => {
|
||||
if (
|
||||
lobby.game.round.verifyStage<round.Revealing | round.Judging>(
|
||||
action,
|
||||
"Judging"
|
||||
)
|
||||
) {
|
||||
const cRound = lobby.game.round;
|
||||
const target = cRound.plays.find(p => p.id === action.play);
|
||||
if (
|
||||
target !== undefined &&
|
||||
target.playedBy !== auth.uid &&
|
||||
!target.likes.has(auth.uid)
|
||||
) {
|
||||
target.likes.add(auth.uid);
|
||||
return {
|
||||
lobby
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
@@ -14,7 +14,7 @@ import * as gameAction from "../../game-action";
|
||||
* A player plays a white card into a round.
|
||||
*/
|
||||
export interface Submit {
|
||||
action: "Submit";
|
||||
action: NameType;
|
||||
play: card.Id[];
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ export const handle: gameAction.Handler<Submit> = (
|
||||
id: playId,
|
||||
play: extractedPlay,
|
||||
playedBy: auth.uid,
|
||||
revealed: false
|
||||
revealed: false,
|
||||
likes: new Set()
|
||||
});
|
||||
const events = [event.targetAll(playSubmitted.of(auth.uid))];
|
||||
const timeouts = [];
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as gameAction from "../../game-action";
|
||||
* A player plays a white card into a round.
|
||||
*/
|
||||
export interface TakeBack {
|
||||
action: "TakeBack";
|
||||
action: NameType;
|
||||
}
|
||||
|
||||
type NameType = "TakeBack";
|
||||
|
||||
@@ -34,6 +34,9 @@ export const Schema = {
|
||||
{
|
||||
$ref: "#/definitions/EnforceTimeLimit"
|
||||
},
|
||||
{
|
||||
$ref: "#/definitions/Like"
|
||||
},
|
||||
{
|
||||
$ref: "#/definitions/Submit"
|
||||
},
|
||||
@@ -96,7 +99,7 @@ export const Schema = {
|
||||
description: "Authenticate with the game.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_11"
|
||||
$ref: "#/definitions/NameType_14"
|
||||
},
|
||||
token: {
|
||||
$ref: "#/definitions/Token"
|
||||
@@ -161,7 +164,7 @@ export const Schema = {
|
||||
description: "Make a change to the configuration of decks for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_2"
|
||||
$ref: "#/definitions/NameType_5"
|
||||
},
|
||||
change: {
|
||||
$ref: "#/definitions/PlayerDriven"
|
||||
@@ -184,7 +187,7 @@ export const Schema = {
|
||||
description: "Set the hand size for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_3"
|
||||
$ref: "#/definitions/NameType_6"
|
||||
},
|
||||
change: {
|
||||
$ref: "#/definitions/Change"
|
||||
@@ -246,7 +249,7 @@ export const Schema = {
|
||||
description: "Change the time limit for a given stage.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_4"
|
||||
$ref: "#/definitions/NameType_7"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -270,7 +273,7 @@ export const Schema = {
|
||||
"Change the time limit mode, or disable the time limits completely.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_4"
|
||||
$ref: "#/definitions/NameType_7"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -323,7 +326,7 @@ export const Schema = {
|
||||
description: "End the current game.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_9"
|
||||
$ref: "#/definitions/NameType_12"
|
||||
}
|
||||
},
|
||||
required: ["action"],
|
||||
@@ -400,6 +403,21 @@ export const Schema = {
|
||||
required: ["action"],
|
||||
type: "object"
|
||||
},
|
||||
Like: {
|
||||
additionalProperties: false,
|
||||
defaultProperties: [],
|
||||
description: "A player plays a white card into a round.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_2"
|
||||
},
|
||||
play: {
|
||||
$ref: "#/definitions/Id"
|
||||
}
|
||||
},
|
||||
required: ["action", "play"],
|
||||
type: "object"
|
||||
},
|
||||
Name: {
|
||||
description: "The name the user goes by.",
|
||||
maxLength: 100,
|
||||
@@ -415,43 +433,55 @@ export const Schema = {
|
||||
type: "string"
|
||||
},
|
||||
NameType_10: {
|
||||
enum: ["StartGame"],
|
||||
enum: ["SetPublic"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_11: {
|
||||
enum: ["SetScoreLimit"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_12: {
|
||||
enum: ["EndGame"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_13: {
|
||||
enum: ["StartGame"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_14: {
|
||||
enum: ["Authenticate"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_2: {
|
||||
enum: ["ChangeDecks"],
|
||||
enum: ["Like"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_3: {
|
||||
enum: ["ChangeHouseRule"],
|
||||
enum: ["Submit"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_4: {
|
||||
enum: ["ChangeTimeLimit"],
|
||||
enum: ["TakeBack"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_5: {
|
||||
enum: ["SetHandSize"],
|
||||
enum: ["ChangeDecks"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_6: {
|
||||
enum: ["SetPassword"],
|
||||
enum: ["ChangeHouseRule"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_7: {
|
||||
enum: ["SetPublic"],
|
||||
enum: ["ChangeTimeLimit"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_8: {
|
||||
enum: ["SetScoreLimit"],
|
||||
enum: ["SetHandSize"],
|
||||
type: "string"
|
||||
},
|
||||
NameType_9: {
|
||||
enum: ["EndGame"],
|
||||
enum: ["SetPassword"],
|
||||
type: "string"
|
||||
},
|
||||
PackingHeat: {
|
||||
@@ -562,7 +592,7 @@ export const Schema = {
|
||||
description: "Set the hand size for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_5"
|
||||
$ref: "#/definitions/NameType_8"
|
||||
},
|
||||
handSize: {
|
||||
description: "The number of cards in each player's hand.",
|
||||
@@ -584,7 +614,7 @@ export const Schema = {
|
||||
description: "Set (or unset) the password for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_6"
|
||||
$ref: "#/definitions/NameType_9"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -658,7 +688,7 @@ export const Schema = {
|
||||
description: "Set (or unset) the password for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_7"
|
||||
$ref: "#/definitions/NameType_10"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -678,7 +708,7 @@ export const Schema = {
|
||||
description: "(Un)Set the score limit for the lobby.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_8"
|
||||
$ref: "#/definitions/NameType_11"
|
||||
},
|
||||
if: {
|
||||
description:
|
||||
@@ -705,7 +735,7 @@ export const Schema = {
|
||||
description: "Start a game in the lobby if possible.",
|
||||
properties: {
|
||||
action: {
|
||||
$ref: "#/definitions/NameType_10"
|
||||
$ref: "#/definitions/NameType_13"
|
||||
}
|
||||
},
|
||||
required: ["action"],
|
||||
@@ -717,8 +747,7 @@ export const Schema = {
|
||||
description: "A player plays a white card into a round.",
|
||||
properties: {
|
||||
action: {
|
||||
enum: ["Submit"],
|
||||
type: "string"
|
||||
$ref: "#/definitions/NameType_3"
|
||||
},
|
||||
play: {
|
||||
items: {
|
||||
@@ -736,8 +765,7 @@ export const Schema = {
|
||||
description: "A player plays a white card into a round.",
|
||||
properties: {
|
||||
action: {
|
||||
enum: ["TakeBack"],
|
||||
type: "string"
|
||||
$ref: "#/definitions/NameType_4"
|
||||
}
|
||||
},
|
||||
required: ["action"],
|
||||
|
||||
@@ -115,18 +115,18 @@ export class IncorrectUserRoleError extends ActionExecutionError {
|
||||
|
||||
interface IncorrectRoundStageDetails extends errors.Details {
|
||||
stage: round.Stage;
|
||||
expected: round.Stage;
|
||||
expected: round.Stage[];
|
||||
}
|
||||
|
||||
// Could happen if the round changes unexpectedly (e.g: czar leaves game).
|
||||
export class IncorrectRoundStageError extends ActionExecutionError {
|
||||
public readonly stage: round.Stage;
|
||||
public readonly expected: round.Stage;
|
||||
public readonly expected: round.Stage[];
|
||||
|
||||
public constructor(
|
||||
action: Action,
|
||||
stage: round.Stage,
|
||||
expected: round.Stage
|
||||
...expected: round.Stage[]
|
||||
) {
|
||||
super(
|
||||
`For this action the round must be in the ${expected} stage but is in ` +
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as gameRound from "../../games/game/round";
|
||||
import * as publicRound from "../../games/game/round/public";
|
||||
import * as user from "../../user";
|
||||
|
||||
/**
|
||||
@@ -8,11 +9,11 @@ import * as user from "../../user";
|
||||
export interface RoundFinished {
|
||||
event: "RoundFinished";
|
||||
winner: user.Id;
|
||||
playedBy: { [id: string]: user.Id };
|
||||
playDetails: { [id: string]: publicRound.PlayDetails };
|
||||
}
|
||||
|
||||
export const of = (round: gameRound.Complete): RoundFinished => ({
|
||||
event: "RoundFinished",
|
||||
winner: round.winner,
|
||||
playedBy: round.playedBy()
|
||||
playDetails: round.playDetails()
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface PotentiallyRevealed {
|
||||
/**
|
||||
* A play with its id.
|
||||
*/
|
||||
export type Revealed = PotentiallyRevealed & { play: Play };
|
||||
export type Revealed = PotentiallyRevealed & { play: Play; likes?: number };
|
||||
|
||||
/**
|
||||
* Create a new user id.
|
||||
|
||||
@@ -92,7 +92,8 @@ export const atStartOfRound = (
|
||||
id: playId,
|
||||
play: player.hand.slice(0, slotCount),
|
||||
playedBy: ai,
|
||||
revealed: false
|
||||
revealed: false,
|
||||
likes: new Set()
|
||||
});
|
||||
events.push(event.targetAll(playSubmitted.of(ai)));
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ import wu from "wu";
|
||||
import { Action } from "../../action";
|
||||
import { IncorrectRoundStageError } from "../../errors/action-execution-error";
|
||||
import * as user from "../../user";
|
||||
import * as util from "../../util";
|
||||
import * as card from "../cards/card";
|
||||
import * as play from "../cards/play";
|
||||
import { Play } from "../cards/play";
|
||||
import * as publicRound from "./round/public";
|
||||
import { Public as PublicRound } from "./round/public";
|
||||
import * as storedPlay from "./round/storedPlay";
|
||||
import { StoredPlay } from "./round/storedPlay";
|
||||
import * as util from "../../util";
|
||||
|
||||
export type Round = Playing | Revealing | Judging | Complete;
|
||||
|
||||
@@ -109,18 +108,24 @@ export class Complete extends Base<"Complete"> {
|
||||
};
|
||||
}
|
||||
|
||||
public playedBy(): { [player: string]: user.Id } {
|
||||
const obj: { [player: string]: user.Id } = {};
|
||||
public playDetails(): { [player: string]: publicRound.PlayDetails } {
|
||||
const obj: { [player: string]: publicRound.PlayDetails } = {};
|
||||
for (const roundPlay of this.plays) {
|
||||
obj[roundPlay.id] = roundPlay.playedBy;
|
||||
obj[roundPlay.id] = {
|
||||
playedBy: roundPlay.playedBy,
|
||||
...(roundPlay.likes.size > 0 ? { likes: roundPlay.likes.size } : {})
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private playsObj(): { [player: string]: Play } {
|
||||
const obj: { [player: string]: Play } = {};
|
||||
private playsObj(): { [player: string]: publicRound.PlayWithLikes } {
|
||||
const obj: { [player: string]: publicRound.PlayWithLikes } = {};
|
||||
for (const roundPlay of this.plays) {
|
||||
obj[roundPlay.playedBy] = roundPlay.play;
|
||||
obj[roundPlay.playedBy] = {
|
||||
play: roundPlay.play,
|
||||
...(roundPlay.likes.size > 0 ? { likes: roundPlay.likes.size } : {})
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -178,7 +183,10 @@ export class Judging extends Base<"Judging"> implements Timed {
|
||||
|
||||
private *revealedPlays(): Iterable<play.Revealed> {
|
||||
for (const roundPlay of this.plays) {
|
||||
yield { id: roundPlay.id, play: roundPlay.play };
|
||||
yield {
|
||||
id: roundPlay.id,
|
||||
play: roundPlay.play
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as user from "../../../user";
|
||||
import * as card from "../../cards/card";
|
||||
import { Play } from "../../cards/play";
|
||||
import * as play from "../../cards/play";
|
||||
import * as player from "../../player";
|
||||
|
||||
export type Public = Playing | Revealing | Judging | Complete;
|
||||
|
||||
@@ -33,9 +34,19 @@ export interface Judging extends Base, Timed {
|
||||
plays: play.Revealed[];
|
||||
}
|
||||
|
||||
export interface PlayWithLikes {
|
||||
play: Play;
|
||||
likes?: number;
|
||||
}
|
||||
|
||||
export interface Complete extends Base {
|
||||
stage: "Complete";
|
||||
winner: user.Id;
|
||||
plays: { [player: string]: Play };
|
||||
plays: { [player: string]: PlayWithLikes };
|
||||
playOrder: user.Id[];
|
||||
}
|
||||
|
||||
export interface PlayDetails {
|
||||
playedBy: user.Id;
|
||||
likes?: player.Likes;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface StoredPlay {
|
||||
playedBy: user.Id;
|
||||
play: Play;
|
||||
revealed: boolean;
|
||||
likes: Set<user.Id>;
|
||||
}
|
||||
|
||||
export type Unrevealed = StoredPlay & { revealed: false };
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Game } from "./game";
|
||||
*/
|
||||
export interface Public {
|
||||
score: Score;
|
||||
likes: Likes;
|
||||
presence: Presence;
|
||||
}
|
||||
|
||||
@@ -22,6 +23,13 @@ export type Role = "Czar" | "Player";
|
||||
*/
|
||||
export type Score = number;
|
||||
|
||||
/**
|
||||
* How many likes the player has received.
|
||||
* @TJS-type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
export type Likes = number;
|
||||
|
||||
/**
|
||||
* If the player is active in the game or has been marked as away.
|
||||
*/
|
||||
@@ -34,17 +42,20 @@ export class Player {
|
||||
public hand: Hand;
|
||||
public score: Score;
|
||||
public presence: Presence;
|
||||
public likes: Likes;
|
||||
|
||||
public constructor(hand: Hand) {
|
||||
this.hand = hand;
|
||||
this.score = 0;
|
||||
this.likes = 0;
|
||||
this.presence = "Active";
|
||||
}
|
||||
|
||||
public public(): Public {
|
||||
return {
|
||||
score: this.score,
|
||||
presence: this.presence
|
||||
presence: this.presence,
|
||||
likes: this.likes
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user