HPR2797: Writing Web Game in Haskell - Simulation at high level


Manage episode 231845272 series 108988
By Discovered by Player FM and our community — copyright is owned by the publisher, not Player FM, and audio streamed directly from their servers.

So far we have been concentrating on separate pieces of the game. Now it’s time to put some of them together as a simulation.

Overview of simulation

Simulation is done in discrete steps. Each step is roughly 1 earth month (completely arbitrary decision). Shorter than that and there might not be enough happening during turns to keep things interesting. Much longer than that and player might not have enough control on how to react things.

In any case, current time is stored in database in table time. There should be only one row in that table at any given time. And that row has only one value, current time. Time is stored as integer as I didn’t want to deal with problems that you get when adding fractions to a float time after time. So current time (March 2019) would be 2019.3 in game terms and stored as 20193 in database.

Main processing is done in function called processTurn that is shown below. It advances time for one decimal month, removes all expired statuses as explained in episode 2768 and then loads all factions.

After that, various steps of the simulation are carried out for all loaded factions. These include handling special events as explained in episode 2748 and doing observations and report writing in manner described episode 2703.

processTurn :: (BaseBackend backend ~ SqlBackend, BackendCompatible SqlBackend backend, PersistUniqueRead backend, PersistQueryWrite backend, PersistQueryRead backend, PersistStoreWrite backend, MonadIO m) => ReaderT backend m Time processTurn = do newTime <- advanceTime _ <- removeExpiredStatuses newTime factions <- selectList [] [ Asc FactionId ] _ <- mapM (handleFactionEvents newTime) factions mapM_ handleFactionFood factions mapM_ (handleFactionConstruction newTime) factions _ <- mapM (addSpecialEvents newTime) factions -- Doing observations should always be done last to ensure players have -- recent reports of property they have full control, ie. planets. -- Otherwise it's possible that they'll receive reports that are one -- turn out of sync. mapM_ (handleFactionObservations newTime) factions return newTime

More mapping

Remember map and fmap that are used to run a function to each element in a list or general structure? mapM works in a similar way, but is used in monadic context. In processTurn function, we’re dealing with input and output and have IO monad present to allow us to do that (MonadIO m part of the type signature).

If you step back a bit and squint a bit, then map :: (a -> b) -> [a] -> [b] and fmap :: (a -> b) -> f a -> f b and mapM :: Monad m => (a -> m b) -> t a -> m (t b) look pretty similar. Each take a function, structure and produce a new structure which values were created by running the given function for each element of the original structure.

The difference is that map works only for lists, fmap works for functors (that were covered in episode 2778) and mapM works for structures in monadic context.

Best way to contact me nowadays is either by email or through fediverse where I’m tuturto@mastodon.social.

2857 episodes available. A new episode about every day .