Implemented escaped table references, add more stuff
Getty Ritter
6 years ago
| 49 | 49 | 12: unknown/mystery |
| 50 | 50 | |
| 51 | 51 | theme |
| 52 | 1-5: @dungeon/theme/mundane | |
| 53 | 6-9: @dungeon/theme/unusual | |
| 54 |
1 |
|
| 52 | 1-5: @{self/mundane} | |
| 53 | 6-9: @{self/unusual} | |
| 54 | 10-12: @{self/extraordinary} | |
| 55 | 55 | mundane |
| 56 | 56 | 1: rot/decay |
| 57 | 57 | 2: torture/agony |
| 93 | 93 | 12: holy war |
| 94 | 94 | |
| 95 | 95 | discovery |
| 96 |
1-3: |
|
| 96 | 1-3: @{self/dressing} | |
| 97 | 4-9: @{self/feature} | |
| 98 | 10-12: @{self/find} | |
| 97 | 99 | dressing |
| 98 | 100 | 1: junk/debris |
| 99 | 101 | 2: tracks/marks |
| 106 | 108 | 9: broken door/wall |
| 107 | 109 | 10: breeze/wind/smell |
| 108 | 110 | 11: lichen/moss/fungus |
| 109 |
12: @ |
|
| 111 | 12: @{details/oddity} | |
| 112 | feature | |
| 113 | 1: cave-in/collapse | |
| 114 | 2: pit/shaft/column | |
| 115 | 3: pillars/columns | |
| 116 | 4: locked door/gate | |
| 117 | 5: alcoves/niches | |
| 118 | 6: bridge/stairs/ramp | |
| 119 | 7: fountain/well/pool | |
| 120 | 8: puzzle | |
| 121 | 9: altar/dais/platform | |
| 122 | 10: statue/idol | |
| 123 | 11: magic pool/statue/idol | |
| 124 | 12: connection to another dungeon | |
| 125 | find | |
| 126 | 1: trinkets | |
| 127 | 2: tools | |
| 128 | 3: weapons/armor | |
| 129 | 4: supplies/trade goods | |
| 130 | 5: coins/gems/jewelry | |
| 131 | 6: poisons/potions | |
| 132 | 7: adventurer/captive | |
| 133 | 8: magic item | |
| 134 | 9: scroll/book | |
| 135 | 10: magic weapon/armor | |
| 136 | 11: artifact | |
| 137 | 12: @{self} and @{self} | |
| 138 | ||
| 139 | danger | |
| 140 | 1-4: @{self/trap} | |
| 141 | 5-11: @{self/creature} | |
| 142 | 12: @{self/entity} | |
| 143 | trap | |
| 144 | 1: alarm | |
| 145 | 2: ensnaring/paralyzing | |
| 146 | 3: pit | |
| 147 | 4: crushing | |
| 148 | 5: piercing/puncturing | |
| 149 | 6: chopping/slashing | |
| 150 | 7: confusing (maze, etc.) | |
| 151 | 8: gas (poison, etc.) | |
| 152 | 9: element | |
| 153 | 10: ambush | |
| 154 | 11: magical | |
| 155 | 12: @{self} and @{self} | |
| 156 | creature: | |
| 157 | 1: waiting in ambush | |
| 158 | 2: fighting/squabbling | |
| 159 | 3: prowling/on patrol | |
| 160 | 4: looking for food | |
| 161 | 5: eating/resting | |
| 162 | 6: guarding | |
| 163 | 7: on the move | |
| 164 | 8: searching/scavenging | |
| 165 | 9: returning to den | |
| 166 | 10: making plans | |
| 167 | 11: sleeping | |
| 168 | 12: dying | |
| 169 | entity: | |
| 170 | 1: alien interloper | |
| 171 | 2: vermin lord | |
| 172 | 3: criminal mastermind | |
| 173 | 4: warlord | |
| 174 | 5: high priest | |
| 175 | 6: oracle | |
| 176 | 7: wizard/witch/alchemist | |
| 177 | 8: monster lord | |
| 178 | 9: evil spirit/ghost | |
| 179 | 10: undead lord | |
| 180 | 11: demon | |
| 181 | 12: dark god | |
| 182 | ||
| 183 | creature | |
| 184 | beast | |
| 185 | 1-7: @{self/earthbound} | |
| 186 | 8-10: @{self/airborne} | |
| 187 | 11-12: @{self/aquatic} | |
| 188 | earthbound | |
| 189 | 1: termite/tick/louse | |
| 190 | 2: snail/slug/worm | |
| 191 | 3: ant/centipede/scorpion | |
| 192 | 4: snake/lizard | |
| 193 | 5: vole/rat/weasel | |
| 194 | 6: boar/pig | |
| 195 | 7: dog/fox/wolf | |
| 196 | 8: cat/lion/panther | |
| 197 | 9: deer/horse/camel | |
| 198 | 10: ox/rhino | |
| 199 | 11: bear/ape/gorilla | |
| 200 | 12: mammoth/dinosaur | |
| 201 | ||
| 202 | airborne | |
| 203 | 1: mosquito/firefly | |
| 204 | 2: locust/dragonfly/moth | |
| 205 | 3: bee/wasp | |
| 206 | 4: chicken/duck/goose | |
| 207 | 5: songbird/parrot | |
| 208 | 6: gull/waterbird | |
| 209 | 7: heron/crane/stork | |
| 210 | 8: crow/raven | |
| 211 | 9: hawk/falcon | |
| 212 | 10: eagle/owl | |
| 213 | 11: condor | |
| 214 | 12: pteranodon | |
| 215 | ||
| 216 | aquatic | |
| 217 | 1: insect | |
| 218 | 2: jelly/anemone | |
| 219 | 3: clam/oyster/snail | |
| 220 | 4: eel/snake | |
| 221 | 5: frog/toad | |
| 222 | 6: fish | |
| 223 | 7: crab/lobster | |
| 224 | 8: turtle | |
| 225 | 9: alligator/crocodile | |
| 226 | 10: dolphin/shark | |
| 227 | 11: squid/octopus | |
| 228 | 12: whale | |
| 229 | ||
| 230 | humanoid | |
| 231 | 1-7: @{self/common} | |
| 232 | 8-10: @{self/uncommon} | |
| 233 | 11-12: @{self/hybrid} | |
| 234 | ||
| 235 | common | |
| 236 | 1-3: halfling (small) | |
| 237 | 4-5: goblin/kobold (small) | |
| 238 | 6-7: dwarf/gnome (small) | |
| 239 | 8-9: orc/hobgoblin/gnoll | |
| 240 | 10-11: half-elf/half-orc, etc. | |
| 241 | 12: elf | |
| 242 | ||
| 243 | uncommon | |
| 244 | 1: fey (tiny) | |
| 245 | 2-3: catfolk/dogfolk | |
| 246 | 4-6: lizardfolk/merfolk | |
| 247 | 7: birdfolk | |
| 248 | 8-10: ogre/troll (large) | |
| 249 | 11-12: cycops/giant (large) | |
| 250 | ||
| 251 | hybrid | |
| 252 | 1-2: centaur | |
| 253 | 3-5: werewolf/werebear | |
| 254 | 6: were-@{creature/beast} | |
| 255 | 7-10: human and a @{creature/beast} | |
| 256 | 11-12: human and two @{creature/beast}s | |
| 257 | ||
| 258 | monster | |
| 259 | 1-7: @{self/unusual} | |
| 260 | 8-10: @{self/rare} | |
| 261 | 11-12: @{self/legendary} | |
| 262 | unusual | |
| 263 | 1-3: plant/fungus | |
| 264 | 4-5: undead human | |
| 265 | 6: undead @{creature/humanoid} | |
| 266 | 7-8: @{creature/beast} + @{creature/beast} | |
| 267 | 9-10: @{creature/beast} with the @{details/ability} ability | |
| 268 | 11-12: @{details/feature} @{creature/beast} | |
| 269 | ||
| 270 | rare | |
| 271 | 1-3: slime/ooze (amorphous) | |
| 272 | 4-6: creation (construct) | |
| 273 | 7-9: @{creature/beast}, with oddity @{details/oddity} | |
| 274 | 10-12: UNNATURAL ENTITY | |
| 275 | ||
| 276 | legendary | |
| 277 | 1-3: dragon/colossus (huge) | |
| 278 | 4-6: huge @{creature/monster/unusual} | |
| 279 | 7-9: huge @{creature/monster/rare} | |
| 280 | 10: @{creature/beast}-dragon | |
| 281 | 11: @{creature/monster/unusual}-dragon | |
| 282 | 12: @{creature/monster/rare}-dragon | |
| 110 | 283 | |
| 111 | 284 | details |
| 285 | ability | |
| 286 | 1: bless/curse | |
| 287 | 2: entagle/trap/snare | |
| 288 | 3: poison/disease | |
| 289 | 4: paralyze/petrify | |
| 290 | 5: mimic/camouflage | |
| 291 | 6: seduce/hypnotize | |
| 292 | 7: dissolve/disintegrate | |
| 293 | 8: @{details/magic type} | |
| 294 | 9: drain life/magic | |
| 295 | 10: immunity to @{details/element} | |
| 296 | 11: read/control minds | |
| 297 | 12: both @{self} and @{self} | |
| 298 | ||
| 299 | element | |
| 300 | 1: air | |
| 301 | 2: earth | |
| 302 | 3: fire | |
| 303 | 4: water | |
| 304 | 5: light | |
| 305 | 6: dark | |
| 306 | ||
| 307 | magic type | |
| 308 | 1: divination | |
| 309 | 2: enchantment | |
| 310 | 3: evocation | |
| 311 | 4: illusion | |
| 312 | 5: necromancy | |
| 313 | 6: summoning | |
| 314 | ||
| 112 | 315 | oddity |
| 113 | 316 | 1: weird color/smell/sound |
| 114 | 317 | 2: geometric |
| 121 | 324 | 9: magnetic/repellant |
| 122 | 325 | 10: devoid of life |
| 123 | 326 | 11: unexpectedly alive |
| 124 |
12: |
|
| 327 | 12: @{self} and @{self} | |
| 328 | ||
| 329 | feature | |
| 330 | 1: heavily armored | |
| 331 | 2-3: winged/flying | |
| 332 | 4: many-headed | |
| 333 | 5: many-eyed or one-eyed | |
| 334 | 6: many-limbed or many-tailed | |
| 335 | 7: tentacled | |
| 336 | 8: ASPECT | |
| 337 | 9: @{details/element} | |
| 338 | 10: @{details/magic type} | |
| 339 | 11: @{details/oddity} | |
| 340 | 12: @{self} and @{self} | |
| 40 | 40 | Nothing -> do |
| 41 | 41 | putStrLn "farewell" |
| 42 | 42 | Exit.exitSuccess |
| 43 | Just ":q" -> do | |
| 44 | putStrLn "farewell" | |
| 45 | Exit.exitSuccess | |
| 46 | ||
| 43 | 47 | Just "" -> pure () |
| 48 | ||
| 44 | 49 | Just ":l" -> do |
| 45 | 50 | tables <- IO.readIORef tablesRef |
| 46 | 51 | Text.putStrLn "Available tables: " |
| 47 | 52 | Text.putStrLn (" " <> Text.unwords (Map.keys tables)) |
| 48 | 53 | Just ":r" -> |
| 49 | 54 | IO.writeIORef tablesRef =<< readMap "perilous-wilds.txt" |
| 55 | ||
| 50 | 56 | Just choice -> do |
| 51 | 57 | tables <- IO.readIORef tablesRef |
| 52 | 58 | let names = Text.unwords (Map.keys tables) |
| 55 | 61 | Nothing -> do |
| 56 | 62 | Text.putStrLn ("table not found: " <> Text.pack (show choice)) |
| 57 | 63 | Text.putStrLn (" valid tables include: " <> names) |
| 58 |
Just t -> Types.rollTable tables t |
|
| 64 | Just t -> Types.rollTable tables t >>= (Text.putStrLn . Types.valueMsg) | |
| 25 | 25 | m = read (Text.unpack (Text.tail y)) |
| 26 | 26 | in Range n m |
| 27 | 27 | |
| 28 | parseResult :: Text.Text -> Result | |
| 29 | parseResult t | |
| 30 | | "@" `Text.isPrefixOf` Text.strip t = | |
| 31 | ResultRoll (Text.tail (Text.strip t)) | |
| 32 | | otherwise = | |
| 33 | ResultText (Text.strip t) | |
| 28 | parseFragments :: Text.Text -> [Fragment] | |
| 29 | parseFragments t = | |
| 30 | let (frag, roll) = Text.breakOn "@{" t | |
| 31 | in case roll of | |
| 32 | "" -> [FragText frag] | |
| 33 | _ -> | |
| 34 | let (name, rest) = Text.breakOn "}" (Text.drop 2 roll) | |
| 35 | in FragText frag : FragRoll name : parseFragments (Text.tail rest) | |
| 34 | 36 | |
| 35 | 37 | parseLines :: [Text.Text] -> [LineType] |
| 36 | 38 | parseLines = go |
| 41 | 43 | | Text.any (== ':') t = |
| 42 | 44 | let (rangeTxt, message) = Text.breakOn ":" t |
| 43 | 45 | range = parseRange rangeTxt |
| 44 | msg = parseResult (Text.tail message) | |
| 45 | in TableEntry range msg : go ts | |
| 46 | msg = parseFragments (Text.tail message) | |
| 47 | in TableEntry range (Result msg) : go ts | |
| 46 | 48 | | otherwise = |
| 47 | 49 | TableDecl (indentAmount t) (Text.strip t) : go ts |
| 48 | 50 | |
| 17 | 17 | , tableChoices :: [(Range, Result)] |
| 18 | 18 | } deriving (Eq, Show) |
| 19 | 19 | |
| 20 | data Result | |
| 21 | = ResultText Text.Text | |
| 22 |
|
|
| 20 | data Fragment | |
| 21 | = FragText Text.Text | |
| 22 | | FragRoll Text.Text | |
| 23 | 23 | deriving (Eq, Show) |
| 24 | 24 | |
| 25 | computeResult :: Int -> TableMap -> Result -> IO () | |
| 26 | computeResult r _ (ResultText msg) = do | |
| 27 | Text.putStr ("\x1b[36m" <> Text.pack (show r) <> ":\x1b[39m ") | |
| 28 | Text.putStrLn msg | |
| 29 | computeResult r ts (ResultRoll name) | |
| 30 | | Just t <- Map.lookup name ts = do | |
| 31 | Text.putStr ("\x1b[36m" <> Text.pack (show r)) | |
| 32 | Text.putStrLn (": (roll " <> name <> ")\x1b[39m") | |
| 33 | rollTable ts t | |
| 34 | | otherwise = Text.putStrLn ("error: no such table: " <> name) | |
| 25 | data Result = Result { fromResult :: [Fragment] } | |
| 26 | deriving (Eq, Show) | |
| 27 | ||
| 28 | data Value = Value | |
| 29 | { valueMsg :: Text.Text | |
| 30 | } deriving (Eq, Show) | |
| 31 | ||
| 32 | stripValue :: Value -> Value | |
| 33 | stripValue = Value . Text.strip . valueMsg | |
| 34 | ||
| 35 | data Context = Context | |
| 36 | { ctxMap :: TableMap | |
| 37 | , ctxRoll :: Int | |
| 38 | , ctxSelf :: Text.Text | |
| 39 | } | |
| 40 | ||
| 41 | findTable :: Text.Text -> Context -> Maybe Table | |
| 42 | findTable name ctx = Map.lookup name (ctxMap ctx) | |
| 43 | ||
| 44 | computeFragments :: Context -> Fragment -> IO Value | |
| 45 | computeFragments _ (FragText msg) = pure (Value msg) | |
| 46 | computeFragments ctx (FragRoll name) = | |
| 47 | let absolute = case Text.stripPrefix "self" name of | |
| 48 | Just rest -> ctxSelf ctx <> rest | |
| 49 | Nothing -> name | |
| 50 | in case findTable absolute ctx of | |
| 51 | Just t -> rollTable (ctxMap ctx) t | |
| 52 | Nothing -> error ("no such table: " ++ show absolute) | |
| 53 | ||
| 54 | computeResult :: Context -> Result -> IO Value | |
| 55 | computeResult ctx (Result msgs) = do | |
| 56 | vs <- mapM (computeFragments ctx) msgs | |
| 57 | pure (Value (foldMap valueMsg vs)) | |
| 35 | 58 | |
| 36 | 59 | tableDie :: Table -> Int |
| 37 | 60 | tableDie t = maximum [ x | (Range _ x, _) <- tableChoices t ] |
| 38 | 61 | |
| 39 |
rollTable :: TableMap -> Table -> IO |
|
| 62 | rollTable :: TableMap -> Table -> IO Value | |
| 40 | 63 | rollTable tables t = do |
| 41 | 64 | roll <- Rand.randomRIO (1, tableDie t) |
| 65 | let ctx = Context | |
| 66 | { ctxMap = tables | |
| 67 | , ctxRoll = roll | |
| 68 | , ctxSelf = tableName t | |
| 69 | } | |
| 42 | 70 | case [ result |
| 43 | 71 | | (range, result) <- tableChoices t |
| 44 | 72 | , roll >= rFrom range && roll <= rTo range |
| 45 | 73 | ] of |
| 46 | [choice] -> computeResult roll tables choice | |
| 47 | _ -> Text.putStrLn $ Text.unwords | |
| 74 | [choice] -> stripValue <$> computeResult ctx choice | |
| 75 | _ -> error $ unwords | |
| 48 | 76 | [ "bad table " |
| 49 |
, |
|
| 77 | , Text.unpack (tableName t) | |
| 50 | 78 | , "(roll of" |
| 51 |
, |
|
| 79 | , show roll | |
| 52 | 80 | , "has no matching result)" |
| 53 | 81 | ] |