diff --git a/src/Decoder.elm b/src/Decoder.elm new file mode 100644 index 0000000..a489656 --- /dev/null +++ b/src/Decoder.elm @@ -0,0 +1,129 @@ +module Decoder exposing (..) + +import Dict +import FastDict +import Json.Decode as D +import Json.Encode as E + + +type Information a + = Information + { decoder : D.Decoder a + , encoder : a -> E.Value + , keys : List String + , default : a + } + + +bool : List String -> Bool -> Information Bool +bool keys default = + Information + { decoder = D.bool + , encoder = E.bool + , keys = keys + , default = default + } + + +dict : List String -> Dict.Dict comparable value -> (comparable -> String) -> (String -> comparable) -> (value -> E.Value) -> D.Decoder value -> Information (Dict.Dict comparable value) +dict keys default fromKey toKey fromValue valueDecoder = + Information + { decoder = + D.keyValuePairs valueDecoder + |> D.map (List.map (Tuple.mapFirst toKey)) + |> D.map Dict.fromList + , encoder = E.dict fromKey fromValue + , keys = keys + , default = default + } + + +fastdict : List String -> FastDict.Dict comparable value -> (comparable -> String) -> (String -> comparable) -> (value -> E.Value) -> D.Decoder value -> Information (FastDict.Dict comparable value) +fastdict keys default fromKey toKey fromValue valueDecoder = + Information + { decoder = + D.keyValuePairs valueDecoder + |> D.map (List.map (Tuple.mapFirst toKey)) + |> D.map FastDict.fromList + , encoder = + FastDict.toList + >> List.map (Tuple.mapBoth fromKey fromValue) + >> E.object + , keys = keys + , default = default + } + + +int : List String -> Int -> Information Int +int keys default = + Information + { decoder = D.int + , encoder = E.int + , keys = keys + , default = default + } + + +list : List String -> List a -> (a -> E.Value) -> D.Decoder a -> Information (List a) +list keys default fromValue valueDecoder = + Information + { decoder = D.list valueDecoder + , encoder = E.list fromValue + , keys = keys + , default = default + } + + +{-| The website allows users to overwrite the pre-generated values. +Hence, if a value already exists, it is returned. If it does not, it will be +generated. +-} +lookUp : E.Value -> Information a -> a +lookUp value (Information { decoder, keys, default }) = + case D.decodeValue (D.at keys decoder) value of + Ok v -> + v + + Err _ -> + default + + +overwrite : a -> Information a -> E.Value -> E.Value +overwrite newValue (Information info) obj = + case info.keys of + [] -> + info.encoder newValue + + head :: tail -> + case D.decodeValue (D.keyValuePairs D.value) obj of + Ok o -> + o + |> FastDict.fromList + |> FastDict.update head + (\mv -> + overwrite newValue + (Information + { encoder = info.encoder + , decoder = info.decoder + , keys = tail + , default = info.default + } + ) + (mv |> Maybe.withDefault (E.dict identity identity Dict.empty)) + |> Just + ) + |> FastDict.toCoreDict + |> E.dict identity identity + + Err _ -> + obj + + +string : List String -> String -> Information String +string keys default = + Information + { encoder = E.string + , decoder = D.string + , keys = keys + , default = default + } diff --git a/src/Defaults.elm b/src/Defaults.elm new file mode 100644 index 0000000..e38810c --- /dev/null +++ b/src/Defaults.elm @@ -0,0 +1,341 @@ +module Defaults exposing (..) + +{-| The Defaults module exposes config values. These values can be overwritten +but they are usually set by default. +-} + +import FastDict as Dict + + +commonerOccupations : Dict.Dict String Int +commonerOccupations = + Dict.fromList + [ ( "Baker", 25 ) + , ( "Shoemaker", 37 ) + , ( "Furrier", 62 ) + , ( "Household helper", 62 ) + , ( "Tailor", 62 ) + , ( "Barber", 87 ) + , ( "Unlicensed Doctor", 87 ) + , ( "Jeweler", 100 ) + , ( "Old cloth seller", 100 ) + , ( "Pastry baker", 125 ) + , ( "Mason", 125 ) + , ( "Carpenter", 137 ) + , ( "Ostler", 150 ) + , ( "Porter", 150 ) + , ( "Weaver", 150 ) + , ( "Chandler", 175 ) + , ( "Mercer", 175 ) + , ( "Cooper", 175 ) + , ( "Cobbler", 180 ) + , ( "Watercarrier", 212 ) + , ( "Scabbardmaker", 212 ) + , ( "Wine-seller", 225 ) + , ( "Hatmaker", 237 ) + , ( "Saddler", 250 ) + , ( "Chicken butcher", 250 ) + , ( "Pursemaker", 275 ) + , ( "Soapmaker", 275 ) + , ( "Goldsmith", 300 ) + , ( "Butcher", 300 ) + , ( "Farrier", 300 ) + , ( "Fishmonger", 300 ) + , ( "Beer seller", 350 ) + , ( "Buckle Maker", 350 ) + , ( "Plasterer", 350 ) + , ( "Spice Merchant", 350 ) + , ( "Hedge-Mage", 350 ) + , ( "Painter", 375 ) + , ( "Hunter", 400 ) + , ( "Licensed doctor", 425 ) + , ( "Roofer", 450 ) + , ( "Locksmith", 475 ) + , ( "Bather", 475 ) + , ( "Ropemaker", 475 ) + , ( "Inn", 500 ) + , ( "Tanner", 500 ) + , ( "Copyist", 500 ) + , ( "Sculptor", 500 ) + , ( "Rugmaker", 500 ) + , ( "Dyer", 500 ) + , ( "Harness-Maker", 500 ) + , ( "Bleacher", 525 ) + , ( "Hay Merchant", 575 ) + , ( "Cutler", 575 ) + , ( "Glovemaker", 600 ) + , ( "Woodcarver", 600 ) + , ( "Woodseller", 600 ) + , ( "Herald", 600 ) + , ( "Lorimer", 600 ) + , ( "Penmaker", 650 ) + , ( "Horse Trainer", 700 ) + , ( "Bookbinder", 750 ) + , ( "Stationer (Ink/Pen Maker)", 700 ) + , ( "Spurrer", 800 ) + , ( "Dung Carter", 800 ) + , ( "Costermonger", 800 ) + , ( "Fortune Teller", 900 ) + , ( "Chirurgeon", 900 ) + , ( "Silversmith", 920 ) + , ( "Illuminator", 975 ) + , ( "Coppersmith", 1100 ) + , ( "Master of Hounds", 1200 ) + , ( "Leech Collector", 1300 ) + , ( "Tinsmith", 1400 ) + , ( "Castellan", 1500 ) + , ( "Bookseller", 1575 ) + , ( "Composer", 1600 ) + , ( "Limeburner", 1600 ) + , ( "Ale-conner", 1700 ) + , ( "Amanuensis", 1800 ) + , ( "Gong Farmer", 1800 ) + , ( "Coiner", 1900 ) + , ( "Philosopher", 2000 ) + , ( "Noble", 3000 ) + ] + + +type alias RaceData = + { density : Int + , maturesAt : Int + , livesUntil : Int + , becomesParentUntil : Int + } + + +races : Dict.Dict String RaceData +races = + Dict.fromList + [ ( "Human" + , { density = 800000 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Half-elf" + , { density = 100000 + , maturesAt = 20 + , livesUntil = 200 + , becomesParentUntil = 160 + } + ) + , ( "Dwarf" + , { density = 60000 + , maturesAt = 50 + , livesUntil = 350 + , becomesParentUntil = 150 + } + ) + , ( "Elf" + , { density = 35000 + , maturesAt = 100 + , livesUntil = 750 + , becomesParentUntil = 500 + } + ) + , ( "Half-orc" + , { density = 25000 + , maturesAt = 14 + , livesUntil = 75 + , becomesParentUntil = 30 + } + ) + , ( "Halfling" + , { density = 20000 + , maturesAt = 20 + , livesUntil = 65 + , becomesParentUntil = 40 + } + ) + , ( "Dragonborn" + , { density = 10000 + , maturesAt = 15 + , livesUntil = 80 + , becomesParentUntil = 40 + } + ) + , ( "Tiefling" + , { density = 10000 + , maturesAt = 20 + , livesUntil = 110 + , becomesParentUntil = 45 + } + ) + , ( "Gnome" + , { density = 10000 + , maturesAt = 40 + , livesUntil = 500 + , becomesParentUntil = 200 + } + ) + , ( "Aasimar" + , { density = 9000 + , maturesAt = 20 + , livesUntil = 160 + , becomesParentUntil = 60 + } + ) + , ( "Orc" + , { density = 8000 + , maturesAt = 12 + , livesUntil = 50 + , becomesParentUntil = 30 + } + ) + , ( "Goliath" + , { density = 7500 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Tabaxi" + , { density = 5000 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Hobgoblin" + , { density = 2500 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Lizardfolk" + , { density = 1500 + , maturesAt = 14 + , livesUntil = 60 + , becomesParentUntil = 30 + } + ) + , ( "Goblin" + , { density = 1000 + , maturesAt = 8 + , livesUntil = 60 + , becomesParentUntil = 30 + } + ) + , ( "Yuan-Ti Pureblood" + , { density = 1000 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Bugbear" + , { density = 1000 + , maturesAt = 16 + , livesUntil = 80 + , becomesParentUntil = 35 + } + ) + , ( "Tortle" + , { density = 1000 + , maturesAt = 15 + , livesUntil = 50 + , becomesParentUntil = 30 + } + ) + , ( "Warforged" + , { density = 1000 + , maturesAt = 0 + , livesUntil = 5000 + , becomesParentUntil = 5000 + } + ) + , ( "Kobold" + , { density = 800 + , maturesAt = 6 + , livesUntil = 120 + , becomesParentUntil = 25 + } + ) + , ( "Shifter" + , { density = 750 + , maturesAt = 10 + , livesUntil = 70 + , becomesParentUntil = 30 + } + ) + , ( "Triton" + , { density = 400 + , maturesAt = 15 + , livesUntil = 200 + , becomesParentUntil = 110 + } + ) + , ( "Firbolg" + , { density = 300 + , maturesAt = 30 + , livesUntil = 500 + , becomesParentUntil = 300 + } + ) + , ( "Kalashtar" + , { density = 250 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Aarakocra" + , { density = 250 + , maturesAt = 3 + , livesUntil = 30 + , becomesParentUntil = 20 + } + ) + , ( "Kenku" + , { density = 200 + , maturesAt = 12 + , livesUntil = 60 + , becomesParentUntil = 36 + } + ) + , ( "Changeling" + , { density = 50 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Gith" + , { density = 75 + , maturesAt = 20 + , livesUntil = 90 + , becomesParentUntil = 40 + } + ) + , ( "Air Genasi" + , { density = 10 + , maturesAt = 20 + , livesUntil = 120 + , becomesParentUntil = 50 + } + ) + , ( "Earth Genasi" + , { density = 10 + , maturesAt = 20 + , livesUntil = 120 + , becomesParentUntil = 50 + } + ) + , ( "Fire Genasi" + , { density = 10 + , maturesAt = 20 + , livesUntil = 120 + , becomesParentUntil = 50 + } + ) + , ( "Water Genasi" + , { density = 10 + , maturesAt = 20 + , livesUntil = 120 + , becomesParentUntil = 50 + } + ) + ] diff --git a/src/Generatable.elm b/src/Generatable.elm index b5c37b1..baac630 100644 --- a/src/Generatable.elm +++ b/src/Generatable.elm @@ -1,4 +1,4 @@ -module Generatable exposing (..) +module Generatable exposing (Generatable, content, fromGenerator, generateAndReturnFromSeed, generateFromSeed, generate, map, regenerate, setContent, removeContent, updateGenerator) {-| The Generatable type contains information that can be stored, modified, generated or regenerated. -} @@ -26,6 +26,25 @@ fromGenerator : Random.Generator a -> Generatable a fromGenerator gr = ToBeGenerated gr +{-| Get the generator with which new values can be generated. +-} +generator : Generatable a -> Random.Generator a +generator gen = + case gen of + ToBeGenerated gr -> + gr + + Generated _ gr -> + gr + +{-| Create a new Generatable from a given seed, and return the value. +-} +generateAndReturnFromSeed : Random.Seed -> Generatable a -> ( Generatable a, a ) +generateAndReturnFromSeed seed gen = + case Random.step (generator gen) seed of + ( new, _ ) -> + ( Generated new (generator gen), new ) + {-| Create a new generatable from a given seed. -} generateFromSeed : Random.Seed -> Generatable a -> Generatable a @@ -65,23 +84,13 @@ already generated. -} regenerate : (a -> msg) -> Generatable a -> Cmd msg regenerate toMsg gen = - case gen of - ToBeGenerated gr -> - Random.generate toMsg gr - - Generated _ gr -> - Random.generate toMsg gr + Random.generate toMsg (generator gen) {-| Override the content with freshly generated content. -} setContent : a -> Generatable a -> Generatable a setContent x gen = - case gen of - ToBeGenerated gr -> - Generated x gr - - Generated _ gr -> - Generated x gr + Generated x (generator gen) {-| Remove any previously generated content from the generatable. -} diff --git a/src/Generator.elm b/src/Generator.elm new file mode 100644 index 0000000..8ab8e79 --- /dev/null +++ b/src/Generator.elm @@ -0,0 +1,121 @@ +module Generator exposing (..) + +{-| The Generator module helps generate values based on a given hash. +-} + +import Pick exposing (Generator) + + +commonerOccupation : Generator (Maybe String) +commonerOccupation = + Pick.inverseWeighted + [ ( "Baker", 25 ) + , ( "Shoemaker", 37 ) + , ( "Furrier", 62 ) + , ( "Household helper", 62 ) + , ( "Tailor", 62 ) + , ( "Barber", 87 ) + , ( "Unlicensed Doctor", 87 ) + , ( "Jeweler", 100 ) + , ( "Old cloth seller", 100 ) + , ( "Pastry baker", 125 ) + , ( "Mason", 125 ) + , ( "Carpenter", 137 ) + , ( "Ostler", 150 ) + , ( "Porter", 150 ) + , ( "Weaver", 150 ) + , ( "Chandler", 175 ) + , ( "Mercer", 175 ) + , ( "Cooper", 175 ) + , ( "Cobbler", 180 ) + , ( "Watercarrier", 212 ) + , ( "Scabbardmaker", 212 ) + , ( "Wine-seller", 225 ) + , ( "Hatmaker", 237 ) + , ( "Saddler", 250 ) + , ( "Chicken butcher", 250 ) + , ( "Pursemaker", 275 ) + , ( "Soapmaker", 275 ) + , ( "Goldsmith", 300 ) + , ( "Butcher", 300 ) + , ( "Farrier", 300 ) + , ( "Fishmonger", 300 ) + , ( "Beer seller", 350 ) + , ( "Buckle Maker", 350 ) + , ( "Plasterer", 350 ) + , ( "Spice Merchant", 350 ) + , ( "Hedge-Mage", 350 ) + , ( "Painter", 375 ) + , ( "Hunter", 400 ) + , ( "Licensed doctor", 425 ) + , ( "Roofer", 450 ) + , ( "Locksmith", 475 ) + , ( "Bather", 475 ) + , ( "Ropemaker", 475 ) + , ( "Inn", 500 ) + , ( "Tanner", 500 ) + , ( "Copyist", 500 ) + , ( "Sculptor", 500 ) + , ( "Rugmaker", 500 ) + , ( "Dyer", 500 ) + , ( "Harness-Maker", 500 ) + , ( "Bleacher", 525 ) + , ( "Hay Merchant", 575 ) + , ( "Cutler", 575 ) + , ( "Glovemaker", 600 ) + , ( "Woodcarver", 600 ) + , ( "Woodseller", 600 ) + , ( "Herald", 600 ) + , ( "Lorimer", 600 ) + , ( "Penmaker", 650 ) + , ( "Horse Trainer", 700 ) + , ( "Bookbinder", 750 ) + , ( "Stationer (Ink/Pen Maker)", 700 ) + , ( "Spurrer", 800 ) + , ( "Dung Carter", 800 ) + , ( "Costermonger", 800 ) + , ( "Fortune Teller", 900 ) + , ( "Chirurgeon", 900 ) + , ( "Silversmith", 920 ) + , ( "Illuminator", 975 ) + , ( "Coppersmith", 1100 ) + , ( "Master of Hounds", 1200 ) + , ( "Leech Collector", 1300 ) + , ( "Tinsmith", 1400 ) + , ( "Castellan", 1500 ) + , ( "Bookseller", 1575 ) + , ( "Composer", 1600 ) + , ( "Limeburner", 1600 ) + , ( "Ale-conner", 1700 ) + , ( "Amanuensis", 1800 ) + , ( "Gong Farmer", 1800 ) + , ( "Coiner", 1900 ) + , ( "Philosopher", 2000 ) + , ( "Noble", 3000 ) + ] + + +townName : Generator String +townName = + Pick.map2 (++) + (Pick.fromList + "Auden" + [ "Caia" + , "Ex Caia" + , "Gold" + , "Hirey" + , "Ideato" + , "Jairo" + , "Kalares" + , "Oosterdorp" + ] + ) + (Pick.fromList + "" + [ "" + , "" + , " City" + , " Town" + , "'s Creek" + ] + ) diff --git a/src/Objects.elm b/src/Objects.elm new file mode 100644 index 0000000..d3bb99a --- /dev/null +++ b/src/Objects.elm @@ -0,0 +1,4 @@ +module Objects exposing (..) + +{-| This module exposes all types that can be generated. +-} diff --git a/src/Objects/District.elm b/src/Objects/District.elm new file mode 100644 index 0000000..457cb95 --- /dev/null +++ b/src/Objects/District.elm @@ -0,0 +1,9 @@ +module Objects.District exposing (..) +{-| Districts are sections of a town. These are mostly segregated to keep the +design of the procedurally generated town simple. +-} + +{-| A district is a part of town. Usually, it is a neighbourhood. +-} +type District + = District {} \ No newline at end of file diff --git a/src/Objects/NPC.elm b/src/Objects/NPC.elm new file mode 100644 index 0000000..debfdb9 --- /dev/null +++ b/src/Objects/NPC.elm @@ -0,0 +1,33 @@ +module Objects.NPC exposing (..) + +{-| The NPC is the character that is most ordinary. +-} + +import Hash exposing (Hash) +import Objects.TownName as Town + + +type NPC + = NPC { town : Town.TownName, id : Int } + + +fromTown : { town : Town.TownName, id : Int } -> NPC +fromTown = + NPC + + +toTown : NPC -> Town.TownName +toTown (NPC { town }) = + town + + +toId : NPC -> Int +toId (NPC { id }) = + id + + +toHash : NPC -> Hash +toHash (NPC { town, id }) = + Hash.fromString "NPC" + |> Hash.dependent (Town.toHash town) + |> Hash.dependent (Hash.fromInt id) diff --git a/src/Objects/Town.elm b/src/Objects/Town.elm new file mode 100644 index 0000000..b9d289d --- /dev/null +++ b/src/Objects/Town.elm @@ -0,0 +1,105 @@ +module Objects.Town exposing (..) + +import Array exposing (Array) +import Hash +import Random +import Generatable exposing (Generatable) +import Objects.District as District exposing (District) + +{-| The Msg is the update telling what adjustments are made to the model. +-} +type Msg + = GenerateSize + +{-| The Town is the comprehensive model that contains all the information about +a town. +-} +type Town + = Town + { districts : Generatable (Array (Generatable District)) + , name : String + , size : TownSize + } + +type TownSize + = Tiny + | Small + | Medium + | Average + | Big + | Huge + -- | Humongous + -- | Massive + -- | Colossal + +{-| Determine the number of districts in the town based on its size. +-} +districtNumberGenerator : TownSize -> Random.Generator Int +districtNumberGenerator ts = + case ts of + Tiny -> + Random.int 1 2 + + Small -> + Random.int 5 8 + + Medium -> + Random.int 15 20 + + Average -> + Random.int 35 45 + + Big -> + Random.int 90 140 + + Huge -> + Random.int 250 470 + +{-| Init a new town. +-} +init : { name : String, size : TownSize } -> Town +init data = + Town + { districts = + data.size + |> districtNumberGenerator + |> Random.map (always never) + |> Generatable.fromGenerator + , name = data.name + , size = data.size + } + +{-| Get a town's name. +-} +name : Town -> String +name (Town town) = town.name + +{-| Get a town's size. +-} +size : Town -> Maybe Int +size (Town town) = + Generatable.content town.size + +{-| Get a town's size. If it isn't generated yet, generate it. +-} +sizeGenerate : Town -> ( Town, Int ) +sizeGenerate ((Town t) as town) = + case Generatable.content t.size of + Just s -> + ( town, s ) + + Nothing -> + case Generatable.generateAndReturnFromSeed (sizeSeed town) t.size of + ( newSize, s ) -> + ( Town { t | size = newSize }, s ) + +{-| Generate a seed for the town's size. +-} +sizeSeed : Town -> Random.Seed +sizeSeed (Town town) = + Hash.fromString town.name + |> Hash.dependent (Hash.fromString "size") + |> Hash.toString + |> String.toInt + |> Maybe.withDefault 0 + |> Random.initialSeed diff --git a/src/Objects/TownName.elm b/src/Objects/TownName.elm new file mode 100644 index 0000000..d5f146c --- /dev/null +++ b/src/Objects/TownName.elm @@ -0,0 +1,28 @@ +module Objects.TownName exposing (TownName, fromString, toHash, toString) + +import Hash exposing (Hash) + + +type TownName + = TownName String + + +{-| Decode a town name from a string. +-} +fromString : String -> TownName +fromString = + TownName + + +{-| Encode a town name into a string. +-} +toString : TownName -> String +toString (TownName name) = + name + + +{-| Hash a town name. +-} +toHash : TownName -> Hash +toHash (TownName name) = + Hash.dependent (Hash.fromString "town-name") (Hash.fromString name) diff --git a/src/Pick.elm b/src/Pick.elm new file mode 100644 index 0000000..ae32d05 --- /dev/null +++ b/src/Pick.elm @@ -0,0 +1,68 @@ +module Pick exposing (..) + +{-| The pick module helps pick random values based on a given hash. +-} + +import Hash exposing (Hash) +import Random + + +type alias Generator a = + Random.Generator a + + +generate : Hash -> Generator a -> a +generate hash picker = + let + seed : Random.Seed + seed = + hash + |> Hash.toString + |> String.toInt + |> Maybe.withDefault 0 + |> Random.initialSeed + in + Random.step + + +fromList : a -> List a -> Generator a +fromList head tail = + Random.uniform head tail + + +inverseWeighted : List ( a, Int ) -> Generator (Maybe a) +inverseWeighted items = + let + chanceSum : Float + chanceSum = + items + |> List.map Tuple.second + |> List.map toFloat + |> List.map ((/) 1) + |> List.sum + in + items + |> List.map (Tuple.mapSecond (\i -> 1 / toFloat i)) + |> List.map (\( a, b ) -> ( b, a )) + |> List.map (Tuple.mapSecond Just) + |> weighted ( max 0 (1 - chanceSum), Nothing ) + + +map : (a -> b) -> Generator a -> Generator b +map = + Random.map + + +map2 : (a -> b -> c) -> Generator a -> Generator b -> Generator c +map2 = + Random.map2 + + +value : a -> Generator a +value = + Random.constant + + +weighted : ( Float, a ) -> List ( Float, a ) -> Generator a +weighted = + Random.weighted diff --git a/src/Seed/Town.elm b/src/Seed/Town.elm new file mode 100644 index 0000000..931a3e5 --- /dev/null +++ b/src/Seed/Town.elm @@ -0,0 +1,24 @@ +module Seed.Town exposing (TownSeed, fromName, toHash, toSeed) +{-| The Town Seed helps generate different towns. +-} + +import Hash +import Random + +type TownSeed = TownSeed String + +fromName : String -> TownSeed +fromName name = TownSeed name + +toHash : TownSeed -> Hash.Hash +toHash (TownSeed name) = + Hash.fromString name + +toSeed : TownSeed -> Random.Seed +toSeed seed = + seed + |> toHash + |> Hash.toString + |> String.toInt + |> Maybe.withDefault 0 + |> Random.initialSeed