diff --git a/client/src/elm/MassiveDecks/Card/Source.elm b/client/src/elm/MassiveDecks/Card/Source.elm index 2559c5c1..1df39d27 100644 --- a/client/src/elm/MassiveDecks/Card/Source.elm +++ b/client/src/elm/MassiveDecks/Card/Source.elm @@ -21,6 +21,7 @@ import Html.Attributes as HtmlA import MassiveDecks.Card.Source.BuiltIn as BuiltIn import MassiveDecks.Card.Source.Custom as Player import MassiveDecks.Card.Source.Fake as Fake +import MassiveDecks.Card.Source.Generated as Generated import MassiveDecks.Card.Source.JsonAgainstHumanity as JsonAgainstHumanity import MassiveDecks.Card.Source.ManyDecks as ManyDecks import MassiveDecks.Card.Source.Methods exposing (..) @@ -250,6 +251,9 @@ methods source = Custom -> Player.methods + Generated -> + Generated.methods + Fake fakeName -> Fake.methods fakeName diff --git a/client/src/elm/MassiveDecks/Card/Source/Generated.elm b/client/src/elm/MassiveDecks/Card/Source/Generated.elm new file mode 100644 index 00000000..42d18a7e --- /dev/null +++ b/client/src/elm/MassiveDecks/Card/Source/Generated.elm @@ -0,0 +1,32 @@ +module MassiveDecks.Card.Source.Generated exposing (..) + +import MassiveDecks.Card.Source.Methods as Source +import MassiveDecks.Model exposing (Shared) +import MassiveDecks.Strings as Strings +import MassiveDecks.Strings.Languages as Lang + + +methods : Source.Methods msg +methods = + { name = \() -> Strings.Generated + , logo = \() -> Nothing + , defaultDetails = + \shared -> + { name = name shared + , url = Nothing + , translator = Nothing + , author = Nothing + , language = Nothing + } + , tooltip = \_ -> Nothing + , messages = \() -> [] + } + + + +{- Private -} + + +name : Shared -> String +name shared = + Strings.Generated |> Lang.string shared diff --git a/client/src/elm/MassiveDecks/Card/Source/Model.elm b/client/src/elm/MassiveDecks/Card/Source/Model.elm index c86a5eed..96b12dda 100644 --- a/client/src/elm/MassiveDecks/Card/Source/Model.elm +++ b/client/src/elm/MassiveDecks/Card/Source/Model.elm @@ -29,13 +29,15 @@ type General {-| Details on where game data came from. - `Ex`: External sources are the main sources of cards users can add. - - `Player`: These are cards written by the given player. + - `Custom`: These are cards written by the given player. + - `Generated`: These are cards generated during a game for reasons such as house rules. - `Fake`: These are cards used for presentation outside of a game environment. -} type Source = Ex External | Custom + | Generated | Fake (Maybe String) diff --git a/client/src/elm/MassiveDecks/Game/Rules.elm b/client/src/elm/MassiveDecks/Game/Rules.elm index f94454f5..886ebf03 100644 --- a/client/src/elm/MassiveDecks/Game/Rules.elm +++ b/client/src/elm/MassiveDecks/Game/Rules.elm @@ -1,5 +1,6 @@ module MassiveDecks.Game.Rules exposing ( ComedyWriter + , HappyEnding , HouseRules , NeverHaveIEver , PackingHeat @@ -31,6 +32,7 @@ type alias HouseRules = , reboot : Maybe Reboot , comedyWriter : Maybe ComedyWriter , neverHaveIEver : Maybe NeverHaveIEver + , happyEnding : Maybe HappyEnding } @@ -73,3 +75,7 @@ type alias ComedyWriter = type alias NeverHaveIEver = {} + + +type alias HappyEnding = + {} diff --git a/client/src/elm/MassiveDecks/Models/Decoders.elm b/client/src/elm/MassiveDecks/Models/Decoders.elm index 75bd723d..45d09adc 100644 --- a/client/src/elm/MassiveDecks/Models/Decoders.elm +++ b/client/src/elm/MassiveDecks/Models/Decoders.elm @@ -228,6 +228,9 @@ sourceByName name = "Custom" -> Json.succeed Source.Custom + "Generated" -> + Json.succeed Source.Generated + _ -> Json.field "source" Source.generalDecoder |> Json.andThen externalSourceByGeneral |> Json.map Source.Ex @@ -410,6 +413,7 @@ houseRules = |> Json.optional "reboot" (reboot |> Json.map Just) Nothing |> Json.optional "comedyWriter" (comedyWriter |> Json.map Just) Nothing |> Json.optional "neverHaveIEver" (neverHaveIEver |> Json.map Just) Nothing + |> Json.optional "happyEnding" (happyEnding |> Json.map Just) Nothing comedyWriter : Json.Decoder Rules.ComedyWriter @@ -429,6 +433,11 @@ neverHaveIEver = {} |> Json.succeed +happyEnding : Json.Decoder Rules.HappyEnding +happyEnding = + {} |> Json.succeed + + reboot : Json.Decoder Rules.Reboot reboot = Json.map Rules.Reboot diff --git a/client/src/elm/MassiveDecks/Models/Encoders.elm b/client/src/elm/MassiveDecks/Models/Encoders.elm index 12a61bd3..15aa2de6 100644 --- a/client/src/elm/MassiveDecks/Models/Encoders.elm +++ b/client/src/elm/MassiveDecks/Models/Encoders.elm @@ -117,6 +117,7 @@ houseRules h = , h.reboot |> Maybe.map (\r -> ( "reboot", reboot r )) , h.comedyWriter |> Maybe.map (\c -> ( "comedyWriter", comedyWriter c )) , h.neverHaveIEver |> Maybe.map (\n -> ( "neverHaveIEver", neverHaveIEver n )) + , h.happyEnding |> Maybe.map (\e -> ( "happyEnding", happyEnding e )) ] ) @@ -141,6 +142,11 @@ neverHaveIEver _ = Json.object [] +happyEnding : Rules.HappyEnding -> Json.Value +happyEnding _ = + Json.object [] + + comedyWriter : Rules.ComedyWriter -> Json.Value comedyWriter { number, exclusive } = Json.object [ ( "number", number |> Json.int ), ( "exclusive", exclusive |> Json.bool ) ] diff --git a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Model.elm b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Model.elm index 2d7d53f9..0b76cf99 100644 --- a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Model.elm +++ b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Model.elm @@ -71,6 +71,7 @@ fake = , reboot = Nothing , comedyWriter = Nothing , neverHaveIEver = Nothing + , happyEnding = Nothing } , stages = { mode = Rules.Soft diff --git a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules.elm b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules.elm index 9fe76905..6193451e 100644 --- a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules.elm +++ b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules.elm @@ -5,6 +5,7 @@ import MassiveDecks.Pages.Lobby.Configure.Configurable as Configurable import MassiveDecks.Pages.Lobby.Configure.Configurable.Editor as Editor import MassiveDecks.Pages.Lobby.Configure.Configurable.Model exposing (Configurable) import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.ComedyWriter as ComedyWriter +import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.HappyEnding as HappyEnding import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Model exposing (..) import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.NeverHaveIEver as NeverHaveIEver import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.PackingHeat as PackingHeat @@ -24,5 +25,6 @@ all = , Reboot.all |> Configurable.wrap RebootId (.reboot >> Just) (\v p -> { p | reboot = v }) , ComedyWriter.all |> Configurable.wrap ComedyWriterId (.comedyWriter >> Just) (\v p -> { p | comedyWriter = v }) , NeverHaveIEver.all |> Configurable.wrap NeverHaveIEverId (.neverHaveIEver >> Just) (\v p -> { p | neverHaveIEver = v }) + , HappyEnding.all |> Configurable.wrap HappyEndingId (.happyEnding >> Just) (\v p -> { p | happyEnding = v }) ] } diff --git a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding.elm b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding.elm new file mode 100644 index 00000000..a9deed35 --- /dev/null +++ b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding.elm @@ -0,0 +1,31 @@ +module MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.HappyEnding exposing (all) + +import MassiveDecks.Components.Form.Message as Message +import MassiveDecks.Game.Rules as Rules +import MassiveDecks.Pages.Lobby.Configure.Configurable as Configurable +import MassiveDecks.Pages.Lobby.Configure.Configurable.Editor as Editor +import MassiveDecks.Pages.Lobby.Configure.Configurable.Model exposing (Configurable) +import MassiveDecks.Pages.Lobby.Configure.Configurable.Validator as Validator +import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.HappyEnding.Model exposing (..) +import MassiveDecks.Strings as Strings + + +all : Configurable Id (Maybe Rules.HappyEnding) model msg +all = + Configurable.group + { id = All + , editor = Editor.group Nothing False False + , children = + [ enabled |> Configurable.wrapAsToggle {} + ] + } + + +enabled : Configurable Id Bool model msg +enabled = + Configurable.value + { id = Enabled + , editor = Editor.bool Strings.HouseRuleHappyEnding + , validator = Validator.none + , messages = always [ Message.info Strings.HouseRuleHappyEndingDescription ] + } diff --git a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding/Model.elm b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding/Model.elm new file mode 100644 index 00000000..740e1f3a --- /dev/null +++ b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/HappyEnding/Model.elm @@ -0,0 +1,6 @@ +module MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.HappyEnding.Model exposing (Id(..)) + + +type Id + = All + | Enabled diff --git a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/Model.elm b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/Model.elm index b6adae43..afa07bb2 100644 --- a/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/Model.elm +++ b/client/src/elm/MassiveDecks/Pages/Lobby/Configure/Rules/HouseRules/Model.elm @@ -2,6 +2,7 @@ module MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Model exposing (Id(.. import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.ComedyWriter.Model as ComedyWriter import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.NeverHaveIEver.Model as NeverHaveIEver +import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.HappyEnding.Model as HappyEnding import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.PackingHeat.Model as PackingHeat import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Rando.Model as Rando import MassiveDecks.Pages.Lobby.Configure.Rules.HouseRules.Reboot.Model as Reboot @@ -14,3 +15,4 @@ type Id | ComedyWriterId ComedyWriter.Id | RebootId Reboot.Id | NeverHaveIEverId NeverHaveIEver.Id + | HappyEndingId HappyEnding.Id diff --git a/client/src/elm/MassiveDecks/Pages/Start.elm b/client/src/elm/MassiveDecks/Pages/Start.elm index 80421cca..2eedae0d 100644 --- a/client/src/elm/MassiveDecks/Pages/Start.elm +++ b/client/src/elm/MassiveDecks/Pages/Start.elm @@ -583,6 +583,7 @@ houseRules = , ( Strings.HouseRuleRandoCardrissian, Strings.HouseRuleRandoCardrissianDescription ) , ( Strings.HouseRuleComedyWriter, Strings.HouseRuleComedyWriterDescription ) , ( Strings.HouseRuleNeverHaveIEver, Strings.HouseRuleNeverHaveIEverDescription ) + , ( Strings.HouseRuleHappyEnding, Strings.HouseRuleHappyEnding ) ] diff --git a/client/src/elm/MassiveDecks/Strings.elm b/client/src/elm/MassiveDecks/Strings.elm index ec871230..5c37e648 100644 --- a/client/src/elm/MassiveDecks/Strings.elm +++ b/client/src/elm/MassiveDecks/Strings.elm @@ -116,6 +116,8 @@ type MdString | HouseRuleRandoCardrissianNumberDescription -- A description of the setting for the number of bots added to the game. | HouseRuleNeverHaveIEver -- The name of the house rule where players can discard cards, sharing the discarded card. | HouseRuleNeverHaveIEverDescription -- A description of the house rule where players can discard cards, sharing the discarded card. + | HouseRuleHappyEnding -- The name of the house rule where the game ends with the haiku card. + | HouseRuleHappyEndingDescription -- A description of the house rule where the game ends with the haiku card. | MustBeMoreThanOrEqualValidationError { min : Int } -- An error when a configuration value must be more than or equal to the given value. | MustBeLessThanOrEqualValidationError { max : Int } -- An error when a configuration value must be less than or equal to the given value. | SetValue { value : Int } -- A description of the action of resolving a problem by setting the value to the given one. @@ -254,6 +256,7 @@ type MdString | JsonAgainstHumanityAbout -- A short description of the JSON Against Humanity source. | BuiltIn -- A term referring to decks of cards that are provided by this instance of the game. | APlayer -- A short description of a generic player in the game in the context of being the author of a card. + | Generated -- A short description of a card generated during the game. | DeckAlreadyAdded -- A description of the problem of the deck already being added to the game configuration. | ConfigureDecks -- A name for the section of the configuration screen for changing the decks for the game. | ConfigureRules -- A name for the section of the configuration screen for changing the rules for the game. diff --git a/client/src/elm/MassiveDecks/Strings/Languages/De.elm b/client/src/elm/MassiveDecks/Strings/Languages/De.elm index e9672d7a..c97f558e 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/De.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/De.elm @@ -346,6 +346,14 @@ translate _ mdString = , Text "eingestehen: die Karte wird öffentlich geteilt." ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "Der Wert muss mindestens ", Text (String.fromInt min), Text " betragen." ] @@ -803,6 +811,10 @@ translate _ mdString = APlayer -> [ Text "Ein Spieler" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Dieser Kartensatz ist bereits im Spiel." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm b/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm index 2d95f293..97c8aa62 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm @@ -346,6 +346,14 @@ translate _ mdString = , Text "eingestehen: die Karte wird öffentlich geteilt." ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "Der Wert muss mindestens ", Text (String.fromInt min), Text " betragen." ] @@ -802,6 +810,10 @@ translate _ mdString = APlayer -> [ Text "Ein Spieler" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Dieser Kartensatz ist bereits im Spiel." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm b/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm index ba732199..49f32662 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm @@ -301,6 +301,12 @@ translate _ mdString = , Text "ignorance: the card is shared publicly." ] + HouseRuleHappyEnding -> + [ Text "Happy Ending" ] + + HouseRuleHappyEndingDescription -> + [ Text "When the game ends, the final round is a 'Make a Haiku' black card." ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "The value must be at least ", Text (String.fromInt min), Text "." ] @@ -758,6 +764,9 @@ translate _ mdString = APlayer -> [ Text "A Player" ] + Generated -> + [ Text "Generated" ] + DeckAlreadyAdded -> [ Text "This deck is already in the game." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/Id.elm b/client/src/elm/MassiveDecks/Strings/Languages/Id.elm index a381c043..ce1aa89a 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/Id.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/Id.elm @@ -336,6 +336,14 @@ translate _ mdString = , Text "ketidaktahuan: kartu dibagikan secara publik." ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "Nilai minimal harus ", Text (String.fromInt min), Text "." ] @@ -793,6 +801,10 @@ translate _ mdString = APlayer -> [ Text "Seorang Player" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Dek ini sudah ada dalam game." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/It.elm b/client/src/elm/MassiveDecks/Strings/Languages/It.elm index 81121e91..886072ad 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/It.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/It.elm @@ -350,6 +350,14 @@ translate _ mdString = HouseRuleNeverHaveIEverDescription -> [ Missing ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "Il valore deve essere almeno ", Text (String.fromInt min), Text "." ] @@ -817,6 +825,10 @@ translate _ mdString = APlayer -> [ Text "Un giocatore" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Questo mazzo è già nel gioco." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm b/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm index 52ed975e..c1142e93 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm @@ -379,6 +379,14 @@ translate maybeDeclCase mdString = , Text "ignorancję: wyrzucona karta będzie publicznie widoczna." ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "Wartość musi być większa lub równa ", Text (String.fromInt min), Text "." ] @@ -905,6 +913,10 @@ translate maybeDeclCase mdString = APlayer -> [ Text "Gracz" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Ta talia jest już w grze." ] diff --git a/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm b/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm index c2ef28bb..fe2ace01 100644 --- a/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm +++ b/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm @@ -346,6 +346,14 @@ translate _ mdString = , Text "ignorância: a carta é compartilhada publicamente." ] + -- TODO: Translate + HouseRuleHappyEnding -> + [ Missing ] + + -- TODO: Translate + HouseRuleHappyEndingDescription -> + [ Missing ] + MustBeMoreThanOrEqualValidationError { min } -> [ Text "O valor deve ser ao menos ", Text (String.fromInt min), Text "." ] @@ -804,6 +812,10 @@ translate _ mdString = APlayer -> [ Text "Um jogador" ] + -- TODO: Translate + Generated -> + [ Missing ] + DeckAlreadyAdded -> [ Text "Este deck já está no jogo." ] diff --git a/client/src/elm/MassiveDecks/Strings/Render.elm b/client/src/elm/MassiveDecks/Strings/Render.elm index e84074bf..68f6101d 100644 --- a/client/src/elm/MassiveDecks/Strings/Render.elm +++ b/client/src/elm/MassiveDecks/Strings/Render.elm @@ -211,6 +211,9 @@ enhanceHtml context mdString unenhanced = HouseRuleNeverHaveIEver -> prefixed unenhanced Icon.trash + HouseRuleHappyEnding -> + prefixed unenhanced Icon.smile + RereadGames -> [ Html.blankA [ HtmlA.class "no-wrap", HtmlA.href "https://www.rereadgames.com/" ] unenhanced ] diff --git a/server/config.json5 b/server/config.json5 index f8b9e0b2..557a7982 100644 --- a/server/config.json5 +++ b/server/config.json5 @@ -61,7 +61,8 @@ // reboot: { cost: 1 }, // comedyWriter: { number: 300, exclusive: true }, // rando: { number: 1 }, - // neverHaveIEver: {} + // neverHaveIEver: {}, + // happyEnding: {} }, stages: { timeLimitMode: "Soft", diff --git a/server/src/ts/action/validation.validator.ts b/server/src/ts/action/validation.validator.ts index a9c4c3b5..6ee36a92 100644 --- a/server/src/ts/action/validation.validator.ts +++ b/server/src/ts/action/validation.validator.ts @@ -383,6 +383,18 @@ export const Schema = { required: ["action", "card", "text"], type: "object", }, + HappyEnding: { + $ref: "#/definitions/HappyEnding_1", + description: + "Configuration for the \"Happy Ending\" house rule.\nWhen the game ends, the final round is a 'Make a Haiku' black card.", + }, + HappyEnding_1: { + additionalProperties: false, + defaultProperties: [], + description: + "Configuration for the \"Happy Ending\" house rule.\nWhen the game ends, the final round is a 'Make a Haiku' black card.", + type: "object", + }, Id: { description: "A unique id for an instance of a card.", type: "string", @@ -627,6 +639,9 @@ export const Schema = { comedyWriter: { $ref: "#/definitions/ComedyWriter", }, + happyEnding: { + $ref: "#/definitions/HappyEnding", + }, neverHaveIEver: { $ref: "#/definitions/NeverHaveIEver", }, diff --git a/server/src/ts/games/cards/source.ts b/server/src/ts/games/cards/source.ts index c04a565e..e4cebf83 100644 --- a/server/src/ts/games/cards/source.ts +++ b/server/src/ts/games/cards/source.ts @@ -1,6 +1,7 @@ import * as Cache from "../../cache"; import * as Decks from "./decks"; import { Custom } from "./sources/custom"; +import { Generated } from "./sources/generated"; import { BuiltIn } from "./sources/builtIn"; import { ManyDecks } from "./sources/many-decks"; import { JsonAgainstHumanity } from "./sources/json-against-humanity"; @@ -8,7 +9,7 @@ import { JsonAgainstHumanity } from "./sources/json-against-humanity"; /** * A source for a card or deck. */ -export type Source = External | Custom; +export type Source = External | Custom | Generated; /** * An external source for a card or deck. diff --git a/server/src/ts/games/cards/sources.ts b/server/src/ts/games/cards/sources.ts index 85fa90cc..34d7db84 100644 --- a/server/src/ts/games/cards/sources.ts +++ b/server/src/ts/games/cards/sources.ts @@ -7,6 +7,7 @@ import * as BuiltIn from "./sources/builtIn"; import { SourceNotFoundError } from "../../errors/action-execution-error"; import * as ManyDecks from "./sources/many-decks"; import * as JsonAgainstHumanity from "./sources/json-against-humanity"; +import * as Generated from "./sources/generated"; async function loadIfEnabled( config: Config | undefined, @@ -122,6 +123,9 @@ export class Sources { case "Custom": return Player.details(source); + case "Generated": + return Generated.details(source); + default: return await this.resolver(cache, source).details(); } diff --git a/server/src/ts/games/cards/sources/generated.ts b/server/src/ts/games/cards/sources/generated.ts new file mode 100644 index 00000000..59a3c98c --- /dev/null +++ b/server/src/ts/games/cards/sources/generated.ts @@ -0,0 +1,15 @@ +import * as Source from "../source"; + +/** + * A source for cards generated during the game for reasons such as house rules. + */ +export interface Generated { + source: "Generated"; +} + +/** + * Get the details for a given source. + */ +export const details = (_: Generated): Source.Details => ({ + name: "Generated", +}); diff --git a/server/src/ts/games/game.ts b/server/src/ts/games/game.ts index ca103cb7..31772c6c 100644 --- a/server/src/ts/games/game.ts +++ b/server/src/ts/games/game.ts @@ -19,6 +19,7 @@ import * as Round from "./game/round"; import * as PublicRound from "./game/round/public"; import * as Player from "./player"; import * as Rules from "./rules"; +import { happyEndingCall } from "./rules/happyEnding"; export interface Public { round: PublicRound.Public; @@ -259,6 +260,14 @@ export class Game { (this.round as Round.Base).plays.flatMap((play) => play.play) ); this.round = new Round.Playing(roundId, czar, playersInRound, call); + if (this.rules.houseRules.happyEnding?.inFinalRound) { + this.round = new Round.Playing( + roundId, + czar, + playersInRound, + happyEndingCall + ); + } const updatedGame = this as Game & { round: Round.Playing }; const atStart = Game.atStartOfRound(server, false, updatedGame); return { diff --git a/server/src/ts/games/rules.ts b/server/src/ts/games/rules.ts index 37e2abac..c8a99928 100644 --- a/server/src/ts/games/rules.ts +++ b/server/src/ts/games/rules.ts @@ -1,5 +1,6 @@ import * as HouseRules from "./rules/houseRules"; import * as Rando from "./rules/rando"; +import * as HappyEnding from "./rules/happyEnding"; /** The rules for a standard game. */ diff --git a/server/src/ts/games/rules/happyEnding.ts b/server/src/ts/games/rules/happyEnding.ts new file mode 100644 index 00000000..654a86a0 --- /dev/null +++ b/server/src/ts/games/rules/happyEnding.ts @@ -0,0 +1,35 @@ +import * as Card from "../cards/card"; + +/** + * Configuration for the "Happy Ending" house rule. + * When the game ends, the final round is a 'Make a Haiku' black card. + */ +export interface HappyEnding { + inFinalRound?: boolean; +} + +export const happyEndingCall: Card.Call = { + id: Card.id(), + parts: [ + ["Make a haiku."], + [ + { + transform: "Capitalize", + }, + ",", + ], + [ + { + transform: "Capitalize", + }, + ",", + ], + [ + { + transform: "Capitalize", + }, + ".", + ], + ], + source: { source: "Generated" }, +}; diff --git a/server/src/ts/games/rules/houseRules.ts b/server/src/ts/games/rules/houseRules.ts index ffc8931e..11f66f6a 100644 --- a/server/src/ts/games/rules/houseRules.ts +++ b/server/src/ts/games/rules/houseRules.ts @@ -1,5 +1,6 @@ import { PackingHeat, Reboot, ComedyWriter, NeverHaveIEver } from "../rules"; import * as Rando from "./rando"; +import * as HappyEnding from "./happyEnding"; /** * Non-standard rules that can be applied to a game. @@ -10,6 +11,7 @@ export interface HouseRules { comedyWriter?: ComedyWriter; rando: Rando.Rando; neverHaveIEver?: NeverHaveIEver; + happyEnding?: HappyEnding.HappyEnding; } /** @@ -21,6 +23,7 @@ export interface Public { comedyWriter?: ComedyWriter; rando?: Rando.Public; neverHaveIEver?: NeverHaveIEver; + happyEnding?: HappyEnding.HappyEnding; } export const censor = (houseRules: HouseRules): Public => ({ diff --git a/server/src/ts/timeout/round-start.ts b/server/src/ts/timeout/round-start.ts index a7f67278..298e8a36 100644 --- a/server/src/ts/timeout/round-start.ts +++ b/server/src/ts/timeout/round-start.ts @@ -39,13 +39,22 @@ export const handle: Timeout.Handler = ( } } if (winners.length > 0) { - lobbyGame.winner = winners; - events.push(Event.targetAll(GameEnded.of(...winners))); + if ( + lobbyGame.rules.houseRules.happyEnding && + !lobbyGame.rules.houseRules.happyEnding.inFinalRound + ) { + // If someone has won and the happy ending house rule is enabled but hasn't happened yet, + // set the final round boolean to true. One more round will happen now. + lobbyGame.rules.houseRules.happyEnding.inFinalRound = true; + } else { + lobbyGame.winner = winners; + events.push(Event.targetAll(GameEnded.of(...winners))); - return { - inLobby, - events, - }; + return { + inLobby, + events, + }; + } } }