Resolve #48 - Added house rule support and a first implementation.
This commit is contained in:
@@ -32,6 +32,16 @@
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.action-menu {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 5px;
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.round-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding: 0px 0px 10px 0px;
|
||||
margin: 0px 0px 20px 0px;
|
||||
margin: 0px 0px 10px 0px;
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -24,6 +24,11 @@
|
||||
margin: 15px auto 0px auto;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
text-align: center;
|
||||
padding: 0px 0px 10px 0px;
|
||||
}
|
||||
|
||||
.mui-textfield {
|
||||
width: 90%;
|
||||
margin: 10px auto 0px auto;
|
||||
|
||||
@@ -30,6 +30,9 @@ object Actions {
|
||||
case class GetLobbyAndHand(secret: Secret) extends Command
|
||||
case class Skip(secret: Secret, players: List[Id]) extends Command
|
||||
case class Back(secret: Secret) extends Command
|
||||
case class EnableRule(secret: Secret, rule: String) extends Command
|
||||
case class DisableRule(secret: Secret, rule: String) extends Command
|
||||
case class Redraw(secret: Secret) extends Command
|
||||
|
||||
object Action {
|
||||
def apply(json: JsValue): Option[Command] =
|
||||
@@ -44,6 +47,9 @@ object Actions {
|
||||
case "getLobbyAndHand" => Json.fromJson[GetLobbyAndHand](command).asOpt
|
||||
case "skip" => Json.fromJson[Skip](command).asOpt
|
||||
case "back" => Json.fromJson[Back](command).asOpt
|
||||
case "enableRule" => Json.fromJson[EnableRule](command).asOpt
|
||||
case "disableRule" => Json.fromJson[DisableRule](command).asOpt
|
||||
case "redraw" => Json.fromJson[Redraw](command).asOpt
|
||||
case _ => None
|
||||
}
|
||||
})
|
||||
@@ -60,6 +66,9 @@ object Actions {
|
||||
implicit val getLobbyAndHandFormat: Format[GetLobbyAndHand] = Json.format[GetLobbyAndHand]
|
||||
implicit val skipFormat: Format[Skip] = Json.format[Skip]
|
||||
implicit val backFormat: Format[Back] = Json.format[Back]
|
||||
implicit val redrawFormat: Format[Redraw] = Json.format[Redraw]
|
||||
implicit val enableRuleFormat: Format[EnableRule] = Json.format[EnableRule]
|
||||
implicit val disableRuleFormat: Format[DisableRule] = Json.format[DisableRule]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,24 @@ class Game @Inject()(private val state: State, @Assisted private val id: String)
|
||||
Json.toJson(state.lobbyAndHand(secret))
|
||||
}
|
||||
|
||||
case Redraw(secret) =>
|
||||
sender() ! Try {
|
||||
state.redraw(secret)
|
||||
Json.toJson(state.lobbyAndHand(secret))
|
||||
}
|
||||
|
||||
case EnableRule(secret, rule) =>
|
||||
sender() ! Try {
|
||||
state.enableRule(secret, rule)
|
||||
Json.toJson(state.lobbyAndHand(secret))
|
||||
}
|
||||
|
||||
case DisableRule(secret, rule) =>
|
||||
sender() ! Try {
|
||||
state.disableRule(secret, rule)
|
||||
Json.toJson(state.lobbyAndHand(secret))
|
||||
}
|
||||
|
||||
case _ =>
|
||||
sender() ! Try {
|
||||
throw new Exception("Unknown message: " + message)
|
||||
|
||||
@@ -22,6 +22,7 @@ import play.api.Play.current
|
||||
|
||||
class State @Inject()(private val cardCast: CardcastAPI, @Assisted val gameCode: String)(implicit ec: ExecutionContext) {
|
||||
private var decks: Set[CardcastDeck] = Set()
|
||||
private var rules: Set[String] = Set()
|
||||
private var players: List[Player] = List()
|
||||
private var lastPlayerId: Int = -1
|
||||
private var secrets: Map[Id, Secret] = Map()
|
||||
@@ -34,7 +35,7 @@ class State @Inject()(private val cardCast: CardcastAPI, @Assisted val gameCode:
|
||||
private var ais: Set[Secret] = Set()
|
||||
private var connected: Set[Id] = Set()
|
||||
|
||||
def config = Config(decks.map(deck => deck.info).toList)
|
||||
def config = Config(decks.map(deck => deck.info).toList, rules)
|
||||
def lobby = Lobby(gameCode, config, players, game.map(game => game.round))
|
||||
|
||||
def newAi(): Unit = {
|
||||
@@ -251,6 +252,31 @@ class State @Inject()(private val cardCast: CardcastAPI, @Assisted val gameCode:
|
||||
sendNotifications()
|
||||
}
|
||||
|
||||
def redraw(secret: Secret): Unit = {
|
||||
val id = validateSecretAndGetId(secret)
|
||||
val state = validateInGameAndGetState()
|
||||
verify(rules.contains("reboot"), "rule-not-enabled")
|
||||
players = players.map(player => if (player.id == id) {
|
||||
verify(player.score > 0, "not-enough-points-to-redraw")
|
||||
player.copy(score = player.score - 1)
|
||||
} else {
|
||||
player
|
||||
})
|
||||
var hands = state.hands
|
||||
hands += (id -> Hand(state.deck.drawResponses(Hand.size)))
|
||||
game = Some(state.copy(hands = hands))
|
||||
}
|
||||
|
||||
def enableRule(secret: Secret, rule: String): Unit = {
|
||||
val id = validateSecretAndGetId(secret)
|
||||
rules += rule
|
||||
}
|
||||
|
||||
def disableRule(secret: Secret, rule: String): Unit = {
|
||||
val id = validateSecretAndGetId(secret)
|
||||
rules -= rule
|
||||
}
|
||||
|
||||
def register(secret: Secret, socket: ActorRef): Unit = {
|
||||
val id = validateSecretAndGetId(secret)
|
||||
val player = playerForId(id)
|
||||
|
||||
@@ -22,7 +22,7 @@ object Game {
|
||||
|
||||
case class DeckInfo(id: String, name: String, calls: Int, responses: Int)
|
||||
|
||||
case class Config(decks: List[DeckInfo])
|
||||
case class Config(decks: List[DeckInfo], houseRules: Set[String])
|
||||
|
||||
case class Round(czar: Player.Id, call: Call, responses: Responses) {
|
||||
require(responses.revealed.isEmpty ||
|
||||
|
||||
@@ -4,6 +4,7 @@ import Json.Decode as Decode exposing ((:=))
|
||||
import Json.Encode as Encode
|
||||
|
||||
import MassiveDecks.API.Request exposing (..)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
import MassiveDecks.Models.Game as Game
|
||||
import MassiveDecks.Models.Player as Player exposing (Player)
|
||||
import MassiveDecks.Models.JSON.Decode exposing (..)
|
||||
@@ -22,7 +23,7 @@ createLobby = request "POST" "/lobbies" Nothing [] lobbyDecoder
|
||||
-}
|
||||
type NewPlayerError
|
||||
= NameInUse
|
||||
| LobbyNotFound
|
||||
| NewPlayerLobbyNotFound
|
||||
|
||||
{-| Makes a request to add a new player to the given lobby. On success, returns a `Secret` for that player.
|
||||
-}
|
||||
@@ -33,15 +34,22 @@ newPlayer gameCode name =
|
||||
("/lobbies/" ++ gameCode ++ "/players")
|
||||
(Just (encodeName name))
|
||||
[ ((400, "name-in-use"), Decode.succeed NameInUse)
|
||||
, ((404, "lobby-not-found"), Decode.succeed LobbyNotFound)
|
||||
, ((404, "lobby-not-found"), Decode.succeed NewPlayerLobbyNotFound)
|
||||
]
|
||||
playerSecretDecoder
|
||||
|
||||
|
||||
{-| Errors specific to getting the lobby and hand.
|
||||
* `LobbyNotFound` - The given lobby does not exist.
|
||||
-}
|
||||
type GetLobbyAndHandError
|
||||
= LobbyNotFound
|
||||
|
||||
|
||||
{-| Get the lobby and the hand for the player with the given secret (using it to authenticate).
|
||||
-}
|
||||
getLobbyAndHand : String -> Player.Secret -> Request Never Game.LobbyAndHand
|
||||
getLobbyAndHand = commandRequest "getLobbyAndHand" [] []
|
||||
getLobbyAndHand : String -> Player.Secret -> Request GetLobbyAndHandError Game.LobbyAndHand
|
||||
getLobbyAndHand = commandRequest "getLobbyAndHand" [] [ ((404, "lobby-not-found"), Decode.succeed LobbyNotFound) ]
|
||||
|
||||
|
||||
{-| Errors specific to add deck requests.
|
||||
@@ -198,6 +206,18 @@ redraw =
|
||||
]
|
||||
|
||||
|
||||
{-| Make a request to enable a house rule.
|
||||
-}
|
||||
enableRule : HouseRule.Id -> String -> Player.Secret -> Request Never Game.LobbyAndHand
|
||||
enableRule rule gameCode secret = commandRequest "enableRule" [ ("rule", rule |> HouseRule.toString |> Encode.string) ] [] gameCode secret
|
||||
|
||||
|
||||
{-| Make a request to disable a house rule.
|
||||
-}
|
||||
disableRule : HouseRule.Id -> String -> Player.Secret -> Request Never Game.LobbyAndHand
|
||||
disableRule rule gameCode secret = commandRequest "disableRule" [ ("rule", rule |> HouseRule.toString |> Encode.string) ] [] gameCode secret
|
||||
|
||||
|
||||
{- Private -}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import Dict exposing (Dict)
|
||||
import Http exposing (Value(..))
|
||||
|
||||
import MassiveDecks.Components.Errors as Errors
|
||||
import MassiveDecks.Util as Util
|
||||
|
||||
|
||||
{-| Send a request to the server.
|
||||
@@ -32,13 +33,7 @@ send request knownHandler errorMessageWrapper resultToMessage =
|
||||
{-| Same as 'send', but for requests with no known errors.
|
||||
-}
|
||||
send' : Request Never result -> (Errors.Message -> message) -> (result -> message) -> Cmd message
|
||||
send' request errorMessageWrapper resultToMessage = send request noKnownErrors errorMessageWrapper resultToMessage
|
||||
|
||||
|
||||
{-| It's impossible to get an instance of Never, this will never happen, so just crash.
|
||||
-}
|
||||
noKnownErrors : Never -> message
|
||||
noKnownErrors noPossibleValue = Debug.crash "Got an impossible response."
|
||||
send' request errorMessageWrapper resultToMessage = send request Util.impossible errorMessageWrapper resultToMessage
|
||||
|
||||
|
||||
{-| A request to the API.
|
||||
|
||||
@@ -2,6 +2,7 @@ module MassiveDecks.Models.Game exposing (..)
|
||||
|
||||
import MassiveDecks.Models.Card as Card
|
||||
import MassiveDecks.Models.Player as Player exposing (Player)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
|
||||
|
||||
{-| The required information to rejoin a lobby - the ID and the secret.
|
||||
@@ -22,6 +23,7 @@ type alias GameCode = String
|
||||
-}
|
||||
type alias Config =
|
||||
{ decks : List DeckInfo
|
||||
, houseRules : List HouseRule.Id
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import Json.Decode exposing (..)
|
||||
import MassiveDecks.Models.Game exposing (..)
|
||||
import MassiveDecks.Models.Player exposing (..)
|
||||
import MassiveDecks.Models.Card exposing (..)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
|
||||
|
||||
lobbyAndHandDecoder : Decoder LobbyAndHand
|
||||
@@ -30,8 +31,9 @@ deckInfoDecoder = object4 DeckInfo
|
||||
|
||||
|
||||
configDecoder : Decoder Config
|
||||
configDecoder = object1 Config
|
||||
configDecoder = object2 Config
|
||||
("decks" := (list deckInfoDecoder))
|
||||
("houseRules" := (list houseRuleDecoder))
|
||||
|
||||
|
||||
handDecoder : Decoder Hand
|
||||
@@ -50,7 +52,7 @@ playerDecoder = object6 Player
|
||||
|
||||
|
||||
playerStatusDecoder : Decoder Status
|
||||
playerStatusDecoder = customDecoder (string) (\name -> Result.Ok (Maybe.withDefault NotPlayed (nameToStatus name)))
|
||||
playerStatusDecoder = customDecoder (string) (\name -> nameToStatus name |> Result.fromMaybe ("Unknown player status '" ++ name ++ "'."))
|
||||
|
||||
|
||||
roundDecoder : Decoder Round
|
||||
@@ -104,6 +106,16 @@ playerSecretDecoder = object2 Secret
|
||||
("secret" := string)
|
||||
|
||||
|
||||
houseRuleDecoder : Decoder HouseRule.Id
|
||||
houseRuleDecoder = customDecoder (string) (\name -> ruleNameToId name |> Result.fromMaybe ("Unknown house rule '" ++ name ++ "'."))
|
||||
|
||||
ruleNameToId : String -> Maybe HouseRule.Id
|
||||
ruleNameToId name =
|
||||
case name of
|
||||
"reboot" -> Just HouseRule.Reboot
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
responsesTransportDecoder : Decoder ResponsesTransport
|
||||
responsesTransportDecoder = object2 ResponsesTransport
|
||||
(maybe ("hidden" := int))
|
||||
|
||||
@@ -78,6 +78,12 @@ update message lobbyModel =
|
||||
GameStarted lobbyAndHand ->
|
||||
(model, Util.cmd (LobbyUpdate lobbyAndHand))
|
||||
|
||||
EnableRule rule ->
|
||||
(model, Request.send' (API.enableRule rule gameCode secret) ErrorMessage LobbyUpdate)
|
||||
|
||||
DisableRule rule ->
|
||||
(model, Request.send' (API.disableRule rule gameCode secret) ErrorMessage LobbyUpdate)
|
||||
|
||||
NoOp ->
|
||||
(model, Cmd.none)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ module MassiveDecks.Scenes.Config.Messages exposing (..)
|
||||
import MassiveDecks.Components.Input as Input
|
||||
import MassiveDecks.Components.Errors as Errors
|
||||
import MassiveDecks.Models.Game as Game
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
|
||||
|
||||
{-| This type is used for all sending of messages, allowing us to send messages handled outside this scene.
|
||||
@@ -21,6 +22,8 @@ type Message
|
||||
| AddAi
|
||||
| StartGame
|
||||
| GameStarted Game.LobbyAndHand
|
||||
| EnableRule HouseRule.Id
|
||||
| DisableRule HouseRule.Id
|
||||
| NoOp
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import MassiveDecks.Components.Icon as Icon
|
||||
import MassiveDecks.Components.Input as Input
|
||||
import MassiveDecks.Models.Game as Game
|
||||
import MassiveDecks.Scenes.Lobby.Models as Lobby
|
||||
import MassiveDecks.Scenes.Playing.HouseRule as HouseRule exposing (HouseRule)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Available exposing (houseRules)
|
||||
import MassiveDecks.Scenes.Config.Messages exposing (Message(..), InputId(..), Deck(..))
|
||||
import MassiveDecks.Util as Util
|
||||
|
||||
@@ -38,13 +41,14 @@ view lobbyModel =
|
||||
]
|
||||
, div [ id "decks", class "mui-tabs__pane mui--is-active" ]
|
||||
[ deckList decks model.loadingDecks model.deckIdInput ]
|
||||
, div [ id "house-rules", class "mui-tabs__pane" ] [ rando ]
|
||||
, div [ id "house-rules", class "mui-tabs__pane" ]
|
||||
([ rando ] ++ (List.map (\rule -> houseRule (List.member rule.id lobbyModel.lobby.config.houseRules) rule) houseRules))
|
||||
, div [ class "mui-divider" ] []
|
||||
, startGameButton enoughPlayers enoughCards
|
||||
]
|
||||
]
|
||||
|
||||
invite : String -> String -> Html Message
|
||||
invite : String -> String -> Html msg
|
||||
invite appUrl lobbyId =
|
||||
let
|
||||
url = Util.lobbyUrl appUrl lobbyId
|
||||
@@ -99,7 +103,7 @@ deckList decks loadingDecks deckId =
|
||||
]
|
||||
|
||||
|
||||
deckLink : String -> Html Message
|
||||
deckLink : String -> Html msg
|
||||
deckLink id = a [ href ("https://www.cardcastgame.com/browse/deck/" ++ id), target "_blank" ] [ text id ]
|
||||
|
||||
|
||||
@@ -125,8 +129,20 @@ emptyDeckListInfo display =
|
||||
[]
|
||||
|
||||
|
||||
houseRule : String -> String -> String -> String -> String -> Message -> Html Message
|
||||
houseRule id' title icon description buttonText message =
|
||||
houseRule : Bool -> HouseRule -> Html Message
|
||||
houseRule enabled rule =
|
||||
let
|
||||
(buttonText, command) =
|
||||
if enabled then
|
||||
("Disable", DisableRule rule.id)
|
||||
else
|
||||
("Enable", EnableRule rule.id)
|
||||
in
|
||||
houseRuleTemplate (HouseRule.toString rule.id) rule.name rule.icon rule.description buttonText command
|
||||
|
||||
|
||||
houseRuleTemplate : String -> String -> String -> String -> String -> msg -> Html msg
|
||||
houseRuleTemplate id' title icon description buttonText message =
|
||||
div [ id id', class "house-rule" ]
|
||||
[ div [] [ h3 [] [ Icon.icon icon, text " ", text title ]
|
||||
, button [ class "mui-btn mui-btn--small mui-btn--primary", onClick message ]
|
||||
@@ -137,12 +153,12 @@ houseRule id' title icon description buttonText message =
|
||||
|
||||
|
||||
rando : Html Message
|
||||
rando = houseRule "rando" "Rando Cardrissian" "cogs"
|
||||
rando = houseRuleTemplate "rando" "Rando Cardrissian" "cogs"
|
||||
"Every round, one random card will be played for an imaginary player named Rando Cardrissian, if he wins, all players go home in a state of everlasting shame."
|
||||
"Add an AI player" AddAi
|
||||
|
||||
|
||||
startGameWarning : Bool -> Html Message
|
||||
startGameWarning : Bool -> Html msg
|
||||
startGameWarning canStart = if canStart then text "" else
|
||||
span [] [ Icon.icon "info-circle", text " You will need at least two players to start the game." ]
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ import MassiveDecks.Scenes.Lobby.Models as Lobby
|
||||
import MassiveDecks.Scenes.Playing.UI as UI
|
||||
import MassiveDecks.Scenes.Playing.Models exposing (Model, ShownPlayedCards)
|
||||
import MassiveDecks.Scenes.Playing.Messages exposing (ConsumerMessage(..), Message(..))
|
||||
import MassiveDecks.Scenes.Playing.HouseRule as HouseRule exposing (HouseRule)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Reboot as Reboot
|
||||
import MassiveDecks.Util as Util exposing ((:>))
|
||||
|
||||
|
||||
@@ -34,6 +37,12 @@ init init =
|
||||
}
|
||||
|
||||
|
||||
houseRule : HouseRule.Id -> HouseRule
|
||||
houseRule id =
|
||||
case id of
|
||||
HouseRule.Reboot -> Reboot.rule
|
||||
|
||||
|
||||
{-| We shouldn't need to do this!
|
||||
int flags blow up at the moment. For now, we pass a string, but we should take an int from JS in the future.
|
||||
-}
|
||||
@@ -129,13 +138,16 @@ update message lobbyModel =
|
||||
(model, Request.send' (API.back gameCode secret) ErrorMessage (UpdateLobbyAndHand >> LocalMessage))
|
||||
|
||||
UpdateLobbyAndHand lobbyAndHand ->
|
||||
lobbyModel |> updateLobbyAndHand lobbyAndHand
|
||||
updateLobbyAndHand lobbyAndHand lobbyModel
|
||||
|
||||
Redraw ->
|
||||
(model, Request.send (API.redraw lobbyModel.lobby.gameCode lobbyModel.secret) redrawErrorHandler ErrorMessage (UpdateLobbyAndHand >> LocalMessage))
|
||||
|
||||
NoOp ->
|
||||
(model, Cmd.none)
|
||||
|
||||
|
||||
type alias Update = Lobby.Model -> (Model, Cmd ConsumerMessage)
|
||||
|
||||
|
||||
updateLobbyAndHand : Game.LobbyAndHand -> Update
|
||||
updateLobbyAndHand : Game.LobbyAndHand -> Lobby.Model -> (Model, Cmd ConsumerMessage)
|
||||
updateLobbyAndHand lobbyAndHand lobbyModel =
|
||||
let
|
||||
lobby = lobbyAndHand.lobby
|
||||
@@ -162,6 +174,12 @@ updateLobbyAndHand lobbyAndHand lobbyModel =
|
||||
(newModel, Util.cmd (LobbyUpdate lobbyAndHand))
|
||||
|
||||
|
||||
redrawErrorHandler : API.RedrawError -> ConsumerMessage
|
||||
redrawErrorHandler error =
|
||||
case error of
|
||||
API.NotEnoughPoints -> ErrorMessage <| Errors.New "You do not have enough points to redraw your hand." False
|
||||
|
||||
|
||||
chooseErrorHandler : API.ChooseError -> ConsumerMessage
|
||||
chooseErrorHandler error =
|
||||
case error of
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
module MassiveDecks.Scenes.Playing.HouseRule exposing (HouseRule, Action)
|
||||
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id exposing (Id)
|
||||
import MassiveDecks.Scenes.Playing.Messages as Playing
|
||||
import MassiveDecks.Scenes.Lobby.Models as Lobby
|
||||
|
||||
|
||||
type alias HouseRule =
|
||||
{ id : Id
|
||||
, icon : String
|
||||
, name : String
|
||||
, description : String
|
||||
, actions : List Action
|
||||
}
|
||||
|
||||
|
||||
type alias Action =
|
||||
{ icon : String
|
||||
, text : String
|
||||
, description : String
|
||||
, onClick : Playing.Message
|
||||
, enabled : Lobby.Model -> Bool
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module MassiveDecks.Scenes.Playing.HouseRule.Available exposing (houseRules)
|
||||
|
||||
import MassiveDecks.Scenes.Playing.HouseRule as HouseRule exposing (HouseRule)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Reboot as Reboot
|
||||
|
||||
|
||||
houseRules : List HouseRule
|
||||
houseRules = [ Reboot.rule ]
|
||||
@@ -0,0 +1,11 @@
|
||||
module MassiveDecks.Scenes.Playing.HouseRule.Id exposing (Id(..), toString)
|
||||
|
||||
|
||||
type Id
|
||||
= Reboot
|
||||
|
||||
|
||||
toString : Id -> String
|
||||
toString id =
|
||||
case id of
|
||||
Reboot -> "reboot"
|
||||
@@ -0,0 +1,34 @@
|
||||
module MassiveDecks.Scenes.Playing.HouseRule.Reboot exposing (rule)
|
||||
|
||||
import MassiveDecks.Models.Player as Player
|
||||
import MassiveDecks.Scenes.Playing.HouseRule as HouseRule exposing (HouseRule)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Id as HouseRule
|
||||
import MassiveDecks.Scenes.Playing.Messages as Playing
|
||||
import MassiveDecks.Scenes.Lobby.Models as Lobby
|
||||
|
||||
|
||||
rule : HouseRule
|
||||
rule =
|
||||
{ id = HouseRule.Reboot
|
||||
, icon = "recycle"
|
||||
, name = "Rebooting the Universe"
|
||||
, description = "At any time, players may trade in a point to discard their hand and redraw."
|
||||
, actions = [ rebootAction ]
|
||||
}
|
||||
|
||||
|
||||
rebootAction : HouseRule.Action
|
||||
rebootAction =
|
||||
{ icon = "recycle"
|
||||
, text = "Redraw"
|
||||
, description = "Lose one point to discard your hand and draw a new one."
|
||||
, onClick = Playing.Redraw
|
||||
, enabled = checkEnabled
|
||||
}
|
||||
|
||||
|
||||
checkEnabled : Lobby.Model -> Bool
|
||||
checkEnabled lobbyModel =
|
||||
Player.byId lobbyModel.secret.id lobbyModel.lobby.players
|
||||
|> Maybe.map (.score >> ((>) 0))
|
||||
|> Maybe.withDefault False
|
||||
@@ -26,4 +26,6 @@ type Message
|
||||
| CheckForPlayedCardsToAnimate
|
||||
| Skip (List Player.Id)
|
||||
| Back
|
||||
| Redraw
|
||||
| UpdateLobbyAndHand Game.LobbyAndHand
|
||||
| NoOp
|
||||
|
||||
@@ -10,6 +10,8 @@ import MassiveDecks.Models.Player as Player exposing (Player)
|
||||
import MassiveDecks.Models.Card as Card
|
||||
import MassiveDecks.Scenes.Lobby.Models as Lobby
|
||||
import MassiveDecks.Scenes.Playing.Messages exposing (Message(..))
|
||||
import MassiveDecks.Scenes.Playing.HouseRule as HouseRule exposing (HouseRule)
|
||||
import MassiveDecks.Scenes.Playing.HouseRule.Available exposing (houseRules)
|
||||
import MassiveDecks.Util as Util
|
||||
|
||||
|
||||
@@ -32,6 +34,45 @@ view lobbyModel =
|
||||
([], [])
|
||||
|
||||
|
||||
houseRuleMenu : Lobby.Model -> List (Html Message)
|
||||
houseRuleMenu lobbyModel =
|
||||
let
|
||||
enabled = List.filter (\rule -> List.member rule.id lobbyModel.lobby.config.houseRules) houseRules
|
||||
in
|
||||
if List.isEmpty enabled then
|
||||
[]
|
||||
else
|
||||
[ div [ class "action-menu mui-dropdown"]
|
||||
[ button [ class "mui-btn mui-btn--small mui-btn--fab"
|
||||
, title "Game actions."
|
||||
, attribute "data-mui-toggle" "dropdown"
|
||||
]
|
||||
[ Icon.icon "bars" ]
|
||||
, ul [ class "mui-dropdown__menu mui-dropdown__menu--right" ]
|
||||
(List.concatMap (houseRuleMenuItems lobbyModel) enabled)
|
||||
]
|
||||
]
|
||||
|
||||
houseRuleMenuItems : Lobby.Model -> HouseRule -> List (Html Message)
|
||||
houseRuleMenuItems lobbyModel rule = List.map (houseRuleMenuItem lobbyModel rule) rule.actions
|
||||
|
||||
|
||||
houseRuleMenuItem : Lobby.Model -> HouseRule -> HouseRule.Action -> Html Message
|
||||
houseRuleMenuItem lobbyModel rule action =
|
||||
let
|
||||
enabled = action.enabled lobbyModel
|
||||
message = if enabled then action.onClick else NoOp
|
||||
in
|
||||
li [] [ a [ classList [ ("link", True), ("disabled", not enabled) ]
|
||||
, title action.description
|
||||
, attribute "tabindex" "0"
|
||||
, attribute "role" "button"
|
||||
, onClick message
|
||||
]
|
||||
[ Icon.fwIcon action.icon, text " ", text action.text ]
|
||||
]
|
||||
|
||||
|
||||
roundContents : Lobby.Model -> Game.Round -> List (Html Message)
|
||||
roundContents lobbyModel round =
|
||||
let
|
||||
@@ -63,9 +104,9 @@ roundContents lobbyModel round =
|
||||
Card.Hidden _ -> handView model.picked (not canPlay) hand
|
||||
in
|
||||
[ playArea
|
||||
[ div [ class "round-area" ] (List.concat [ [ call round.call callFill ], pickedOrChosen ])
|
||||
, playedOrHand
|
||||
]
|
||||
([ div [ class "round-area" ] (List.concat [ [ call round.call callFill ], pickedOrChosen ])
|
||||
, playedOrHand
|
||||
] ++ houseRuleMenu lobbyModel)
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ init init =
|
||||
, init = init
|
||||
, nameInput = Input.init Name "name-input" [ text "Your name in the game." ] "" "Nickname" InputMessage
|
||||
, gameCodeInput = Input.init GameCode "game-code-input" [ text "The code for the game to join." ] (init.gameCode |> Maybe.withDefault "") "" InputMessage
|
||||
, info = Nothing
|
||||
, errors = Errors.init
|
||||
}
|
||||
, command)
|
||||
@@ -80,6 +81,9 @@ update message model =
|
||||
in
|
||||
({ model | errors = newErrors }, Cmd.map ErrorMessage cmd)
|
||||
|
||||
ShowInfoMessage message ->
|
||||
({ model | info = Just message }, Cmd.none)
|
||||
|
||||
CreateLobby ->
|
||||
(model, Request.send' API.createLobby ErrorMessage (\lobby -> JoinLobbyAsNewPlayer lobby.gameCode))
|
||||
|
||||
@@ -90,7 +94,7 @@ update message model =
|
||||
{- TODO: We send the secret to the websocket here for crazy reasons - remove - Bug workaround! -}
|
||||
model !
|
||||
[ Lobby.sendSecretToWebSocket model.init.url gameCode secret
|
||||
, Request.send' (API.getLobbyAndHand gameCode secret) ErrorMessage (JoinLobby secret)
|
||||
, Request.send (API.getLobbyAndHand gameCode secret) getLobbyAndHandErrorHandler ErrorMessage (JoinLobby secret)
|
||||
, Storage.storeInGame (Game.GameCodeAndSecret gameCode secret)
|
||||
]
|
||||
|
||||
@@ -133,4 +137,10 @@ newPlayerErrorHandler : API.NewPlayerError -> Message
|
||||
newPlayerErrorHandler error =
|
||||
case error of
|
||||
API.NameInUse -> (Name, Just "This name is already in use in this game, try something else." |> Input.Error) |> InputMessage
|
||||
API.LobbyNotFound -> (GameCode, Just "This game doesn't exist - check you have the right code." |> Input.Error) |> InputMessage
|
||||
API.NewPlayerLobbyNotFound -> (GameCode, Just "This game doesn't exist - check you have the right code." |> Input.Error) |> InputMessage
|
||||
|
||||
|
||||
getLobbyAndHandErrorHandler : API.GetLobbyAndHandError -> Message
|
||||
getLobbyAndHandErrorHandler error =
|
||||
case error of
|
||||
API.LobbyNotFound -> "The game you were in has ended." |> ShowInfoMessage
|
||||
|
||||
@@ -11,6 +11,7 @@ import MassiveDecks.Models.Player as Player
|
||||
-}
|
||||
type Message
|
||||
= CreateLobby
|
||||
| ShowInfoMessage String
|
||||
| JoinLobbyAsNewPlayer String
|
||||
| JoinLobbyAsExistingPlayer Player.Secret String
|
||||
| JoinLobby Player.Secret Game.LobbyAndHand
|
||||
|
||||
@@ -14,5 +14,6 @@ type alias Model =
|
||||
, init : Init
|
||||
, nameInput : Input.Model InputId Message
|
||||
, gameCodeInput : Input.Model InputId Message
|
||||
, info : Maybe String
|
||||
, errors : Errors.Model
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
|
||||
import MassiveDecks.Components.Icon exposing (..)
|
||||
import MassiveDecks.Components.About exposing (..)
|
||||
import MassiveDecks.Components.Icon as Icon
|
||||
import MassiveDecks.Components.About as About
|
||||
import MassiveDecks.Components.Input as Input
|
||||
import MassiveDecks.Scenes.Start.Models exposing (Model)
|
||||
import MassiveDecks.Scenes.Start.Messages exposing (Message(..))
|
||||
@@ -27,27 +27,28 @@ view model =
|
||||
in
|
||||
div [ id "start-screen" ]
|
||||
[ div [ id "start-screen-content", class "mui-panel" ]
|
||||
[ h1 [ class "mui--divider-bottom" ] [ text "Massive Decks" ]
|
||||
, Input.view model.nameInput
|
||||
, ul [ class "mui-tabs__bar mui-tabs__bar--justified" ]
|
||||
[ li createLiClass [ a [ attribute "data-mui-toggle" "tab", attribute "data-mui-controls" "pane-new" ] [ text "Create" ] ]
|
||||
, li joinLiClass [ a [ attribute "data-mui-toggle" "tab", attribute "data-mui-controls" "pane-existing" ] [ text "Join" ] ]
|
||||
([ h1 [ class "mui--divider-bottom" ] [ text "Massive Decks" ]
|
||||
] ++ (model.info |> Maybe.map (\message -> [ div [ class "info-message mui--divider-bottom" ] [ Icon.icon "info-circle", text " ", text message ] ]) |> Maybe.withDefault []) ++
|
||||
[ Input.view model.nameInput
|
||||
, ul [ class "mui-tabs__bar mui-tabs__bar--justified" ]
|
||||
[ li createLiClass [ a [ attribute "data-mui-toggle" "tab", attribute "data-mui-controls" "pane-new" ] [ text "Create" ] ]
|
||||
, li joinLiClass [ a [ attribute "data-mui-toggle" "tab", attribute "data-mui-controls" "pane-existing" ] [ text "Join" ] ]
|
||||
]
|
||||
, div [ class ("mui-tabs__pane" ++ createDivClass), id "pane-new" ] [ createLobbyButton nameEntered ]
|
||||
, div [ class ("mui-tabs__pane" ++ joinDivClass), id "pane-existing" ]
|
||||
[ Input.view model.gameCodeInput
|
||||
, joinLobbyButton model.gameCodeInput.value (nameEntered && gameCodeEntered)
|
||||
]
|
||||
, a [ class "about-link mui--divider-top link"
|
||||
, attribute "tabindex" "0"
|
||||
, attribute "role" "button"
|
||||
, attribute "onClick" "aboutOverlay()"
|
||||
]
|
||||
[ icon "question-circle" , text " About" ]
|
||||
, aboutOverlay
|
||||
, div [ id "forkongithub" ] [ div [] [ a [ href "https://github.com/lattyware/massivedecks", target "_blank" ]
|
||||
[ icon "github", text " Fork me on GitHub" ] ] ]
|
||||
]
|
||||
, div [ class ("mui-tabs__pane" ++ createDivClass), id "pane-new" ] [ createLobbyButton nameEntered ]
|
||||
, div [ class ("mui-tabs__pane" ++ joinDivClass), id "pane-existing" ]
|
||||
[ Input.view model.gameCodeInput
|
||||
, joinLobbyButton model.gameCodeInput.value (nameEntered && gameCodeEntered)
|
||||
]
|
||||
, a [ class "about-link mui--divider-top link"
|
||||
, attribute "tabindex" "0"
|
||||
, attribute "role" "button"
|
||||
, attribute "onClick" "aboutOverlay()"
|
||||
]
|
||||
[ Icon.icon "question-circle" , text " About" ]
|
||||
, About.aboutOverlay
|
||||
, div [ id "forkongithub" ] [ div [] [ a [ href "https://github.com/lattyware/massivedecks", target "_blank" ]
|
||||
[ Icon.icon "github", text " Fork me on GitHub" ] ] ]
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@ import Task
|
||||
import String
|
||||
|
||||
|
||||
{-| Since a value of `Never` can never exist, this can't ever actually happen. Used to fill gaps where the type system
|
||||
requires a method, but it'll never get called.
|
||||
-}
|
||||
impossible : Never -> a
|
||||
impossible n = impossible n
|
||||
|
||||
|
||||
{-| Add an item to a list if the value is not nothing.
|
||||
-}
|
||||
andMaybe : List a -> Maybe a -> List a
|
||||
|
||||
Reference in New Issue
Block a user