Site Map - skip to main content - dyslexic font - mobile - text - print

Hacker Public Radio

Your ideas, projects, opinions - podcasted.

New episodes Monday through Friday.



Host Image
Host ID: 364

Eternal tinkerer of code, who occasionally writes things down at or contributes to hylang project at

episodes: 12

hpr2733 :: Writing Web Game in Haskell - News and Notifications

Released on 2019-01-23 under a CC-BY-SA license.


News and notifications are used in the game to let the players know something noteworthy has happened. It could be discovery of a new planet or construction project finally finishing.

All relevant information in the news is hyperlinked. If news mentions a planet, player can click the link and view current information of that planet.

Server interface

Server has three resources for news, although we’re concentrating only one here:

/api/message           ApiMessageR      GET POST
/api/message/#NewsId   ApiMessageIdR    DELETE
/api/icon              ApiMessageIcons  GET

First one is for retrieving all messages and posting a new one. Second one is for marking one read and third one is for retrieving all icons that players can attach to messages written by them.


Database is defined in /config/models file. For news, there’s only one table:

News json
    content Text
    factionId FactionId
    date Int
    dismissed Bool
deriving Show Read Eq

Content field contains the actual news article data as serialized JSON. This allows storing complex data, without having to have lots of columns or multiple tables.

Domain objects

There are many kinds of messages that players might see, but we’ll concentrate on one about discovering a new planet

All different kinds of articles are of same type: NewsArticle. Each different kind of article has their own value constructor (PlanetFound in this particular case). And each of those value constructors has single parameter of a specific type that holds information particular to that certain article (PlanetFoundNews in this case). Adding a new article means adding a new value constructor and record to hold the data.

data NewsArticle =
    StarFound StarFoundNews
    | PlanetFound PlanetFoundNews
    | UserWritten UserWrittenNews
    | DesignCreated DesignCreatedNews
    | ConstructionFinished ConstructionFinishedNews

data PlanetFoundNews = PlanetFoundNews
    { planetFoundNewsPlanetName :: Text
    , planetFoundNewsSystemName :: Text
    , planetFoundNewsSystemId   :: Key StarSystem
    , planetFoundNewsPlanetId   :: Key Planet
    , planetFoundNewsDate       :: Int

Given a News object, we can turn it into NewsArticle. These are much nicer to deal with that densely packed News that is stored in database:

parseNews :: News -> Maybe NewsArticle
parseNews =
    decode . toLazyByteString . encodeUtf8Builder . newsContent

Because parsing arbitrary JSON might fail, we get Maybe NewsArticle, instead of NewsArticle. It is possible to write the same code in longer way:

parseNews news =
        content = newsContent news
        utf8Encoded = encodeUtf8Builder content
        byteString = toLazyByteString utf8Encoded
        decode byteString

Similarly there’s two other functions for dealing with Entities (primary key, data - pair really) and list of Entities. Note that parseNewsEntities filters out all News that it didn’t manage to turn into NewsArticle. They have following signatures:

parseNewsEntity :: Entity News -> (Key News, Maybe NewsArticle)

parseNewsEntities :: [Entity News] -> [(Key News, NewsArticle)]

Writing JSON encoding and decoding is tedious, template Haskell can help us here:

$(deriveJSON defaultOptions ''PlanetFoundNews)
$(deriveJSON defaultOptions ''NewsArticle)

Turning Articles into JSON

News articles aren’t much use if they stay on the server, we need to send them to clients too. We can’t have multiple declarations of same typeclass for any type, so we declare complete new type and copy data there before turning it into JSON and sending to client (this is one way of doing this).

First step, define our types (concentrating on planet found news here):

data NewsArticleDto =
    StarFoundDto StarFoundNewsDto
    | PlanetFoundDto PlanetFoundNewsDto
    | UserWrittenDto UserWrittenNewsDto
    | DesignCreatedDto DesignCreatedNewsDto
    | ConstructionFinishedDto ConstructionFinishedNewsDto
    deriving (Show, Read, Eq)

data PlanetFoundNewsDto = PlanetFoundNewsDto
    { planetFoundNewsDtoPlanetName :: Text
    , planetFoundNewsDtoSystemName :: Text
    , planetFoundNewsDtoSystemId   :: Key StarSystem
    , planetFoundNewsDtoPlanetId   :: Key Planet
    , planetFoundNewsDtoDate       :: Int
    deriving (Show, Read, Eq)

We need way to move data into dto and thus define a type class for that operation:

class (ToJSON d) => ToDto c d | c -> d where
    toDto :: c -> d

For more information about functional dependencies, check following links: and

Writing instances for our type class:

instance ToDto PlanetFoundNews PlanetFoundNewsDto where
    toDto news =
        PlanetFoundNewsDto { planetFoundNewsDtoPlanetName = planetFoundNewsPlanetName news
                           , planetFoundNewsDtoSystemName = planetFoundNewsSystemName news
                           , planetFoundNewsDtoSystemId = planetFoundNewsSystemId news
                           , planetFoundNewsDtoPlanetId = planetFoundNewsPlanetId news
                           , planetFoundNewsDtoDate = planetFoundNewsDate news

instance ToDto NewsArticle NewsArticleDto where
    toDto news =
        case news of
            (StarFound x) -> StarFoundDto $ toDto x
            (PlanetFound x) -> PlanetFoundDto $ toDto x
            (UserWritten x) -> UserWrittenDto $ toDto x
            (DesignCreated x) -> DesignCreatedDto $ toDto x
            (ConstructionFinished x) -> ConstructionFinishedDto $ toDto x

Finally, we want to wrap our news into something that has all the common info (id and link to icon to show)

data NewsDto = NewsDto
    { newsDtoId    :: Key News
    , newsContents :: NewsArticleDto
    , newsIcon     :: Text
    deriving (Show, Read, Eq)

IconMapper knows how to turn NewsArticleDto (in this case) to corresponding link to the icon. Notice how our ToDto instance includes IconMapper in addition to Key and NewsArticle:

instance ToDto ((Key News, NewsArticle), (IconMapper NewsArticleDto)) NewsDto where
    toDto ((nId, article), icons) =
            content = toDto article
        NewsDto { newsDtoId = nId
                , newsContents = content
                , newsIcon = runIconMapper icons content

Sideshow: IconMapper

IconMapper is a function that knows how to retrieve url to icon that matches the given parameter (for example NewsArticleDto in this case):

newtype IconMapper a =
    IconMapper { runIconMapper :: a -> Text }

One possible implementation that knows how to deal with NewsArticleDto. We have two levels of hierarchicy here, because UserNewsDto has special rules for figuring out which icon to use:

iconMapper :: (Route App -> Text) -> IconMapper UserNewsIconDto -> IconMapper NewsArticleDto
iconMapper render userIconMapper =
    IconMapper $ article ->
        case article of
            PlanetFoundDto _->
                render $ StaticR images_news_planet_png

            UserWrittenDto details ->
                runIconMapper userIconMapper $ userWrittenNewsDtoIcon details

Back to JSON

I wrote ToJSON and FromJSON instances by hand, because I wanted full control on how the resulting JSON looks like. It’s possible to configure how template Haskell names fields for example, but I think that writing these out couple of times is good practice and makes sure that I understand what’s going on behind the scenes if I use template Haskell later.

instance ToJSON NewsDto where
    toJSON (NewsDto { newsDtoId = nId
                    , newsContents = contents
                    , newsIcon = icon }) =
        object [ "id" .= nId
               , "contents" .= contents
               , "tag" .= jsonTag contents
               , "icon" .= icon
               , "starDate" .= newsStarDate contents

instance ToJSON PlanetFoundNewsDto where
    toJSON (PlanetFoundNewsDto { planetFoundNewsDtoPlanetName = pName
                               , planetFoundNewsDtoSystemId = sId
                               , planetFoundNewsDtoPlanetId = pId
                               , planetFoundNewsDtoSystemName = sName
                               }) =
        object [ "planetName" .= pName
               , "systemName" .= sName
               , "planetId" .= pId
               , "systemId" .= sId

Time to put it all together

Handler function authenticates user, check they’re member of a faction and then loads all the news:

getApiMessageR :: Handler Value
getApiMessageR = do
    (_, _, fId) <- apiRequireFaction
    loadAllMessages fId

Loading messages involves multiple steps:

  • retrieve News from database
    • correct faction, not dismissed, sort by date
  • parse them into ( Key News, NewsArticle )
  • get Url render function
  • create mapper for user icons
  • map all NewsArticles into ( NewsArticleDto, IconMapper )
  • turn them into JSON and return that to client
loadAllMessages :: Key Faction -> HandlerFor App Value
loadAllMessages fId = do
    loadedMessages <- runDB $ selectList [ NewsFactionId ==. fId
                                         , NewsDismissed ==. False ] [ Desc NewsDate ]
    let parsedMessages = parseNewsEntities loadedMessages
    render <- getUrlRender
    let userIcons = userNewsIconMapper render
    return $ toJSON $ map (toDto . (flip (,) (iconMapper render userIcons))) parsedMessages

hpr2723 :: Using Elm in context of 4X game client

Released on 2019-01-09 under a CC-BY-SA license.

Original idea I had with my toy game project was to have Yesod render most of the user interface as static HTML and have as little client side scripting as possible. Later I realized that there would be parts with significant amount of client side code and it might be better if whole site was written in Elm.

Couple goals I had in my mind when I started this:

  • easy to work with
  • type safe
  • extensible
  • user authorization
    • regular player
    • administrator

Backend is written in Haskell and front end in Elm. Communication between them is via REST interface and most of the data is in JSON. All JSON encoding / decoding is centralized (more or less), same with initiating requests to server.

API Endpoints

End points used for REST calls are defined in single data type that captures their name and parameters. These are used when initiating requests, meaning there’s smaller chance of typo slipping through.

type Endpoint
    = ApiStarDate
    | ApiResources
    | ApiStarSystem
    | ApiStar
    | ApiPlanet
    | ApiPopulation PlanetId
    | ApiBuilding PlanetId
    | ApiConstructionQueue PlanetId
    | ApiConstruction Construction
    | ApiBuildingConstruction
    | ApiAvailableBuildings

For example, sending a GET request to retrieve all construction projects on a planet is done as:

Http.send (ApiMsgCompleted << ConstructionsReceived) (get (ApiConstructionQueue planetId) (list constructionDecoder))

GET Request is sent to ApiConstructionQueue endpoint and it has planetId as parameter. When server sends response, our program will parse content of it will be a list that is parsed with constructionDecoder and create “ApiMsgCompleted ConstructionsReceived” message with result of the parsing. Update function will process this and store list of constructions somewhere safe for further use.

Update function

Update function is in charge of reacting to messages (mouse clicks, page changes, responses from server). In a large program update function will quickly get big and unwieldy. Breaking it into smaller pieces (per page for example), will make maintenance easier. This way each page has their own message type and own update function to handle it. In addition there’s few extra ones (cleaning error display, processing API messages and reacting to page changes).

Same way as API end points are encoded in a type, pages are too:

type Route
    = HomeR
    | ProfileR
    | StarSystemsR
    | StarSystemR StarSystemId
    | PlanetR StarSystemId PlanetId
    | BasesR
    | FleetR
    | DesignerR
    | ConstructionR
    | MessagesR
    | AdminR
    | LogoutR
    | ResearchR

routeToString function is used to map Route into String, that can be placed in hyperlink. Below is an excerp:

routeToString : Route -> String
routeToString route =
    case route of
        HomeR ->

        StarSystemR (StarSystemId sId) ->
            "/starsystem/" ++ String.fromInt sId

        PlanetR (StarSystemId sId) (PlanetId pId) ->
            "/starsystem/" ++ String.fromInt sId ++ "/" ++ String.fromInt pId

Because mapping needs to be bi-directional (Route used to define content of a href and string from a href used to define Route), there’s mapping to other direction too:

routes : Parser (Route -> a) a
routes =
        [ map HomeR top
        , map ProfileR (s "profile")
        , map ResearchR (s "research")
        , map StarSystemsR (s "starsystem")
        , map StarSystemR (s "starsystem" </> starSystemId)
        , map PlanetR (s "starsystem" </> starSystemId </> planetId)
        , map BasesR (s "base")
        , map FleetR (s "fleet")
        , map DesignerR (s "designer")
        , map ConstructionR (s "construction")
        , map MessagesR (s "message")
        , map AdminR (s "admin")
        , map LogoutR (s "logout")

Result of parsing is Maybe Route, meaning that failure will return Nothing. Detecting and handling this is responsibility of the calling code, usually I just default to HomeR.

Borrowing from Yesod, client uses recursive function to define breadcrumb path. This is hierarchical view of current location in the application, allowing user to quickly navigate backwards where they came.

Breadcrumb path consists of segments that are tuple of (String, Maybe Route). String tells text to display and Route is possible parent route of the segment. This allows hierarchical definition: “Home / Star systems / Sol / Earth”. Because route has only (for example) PlanetId, we need to pass Model too, so that the data retrieved from server can be used to figure out what name such a planet has.

{-| Build complete breadcrumb path and wrap it in enclosing HTML
breadcrumbPath : Model -> Html Msg

{-| Recursively build list of breadcrumbs from segments
Last one is plain text, while parents of it are links
breadcrumb : Model -> Bool -> Route -> List (Html Msg)

{-| Get segment of given route in form of ( String, Maybe Route )
String denotes text describing the segment, Maybe Route is possible parent
segment : Model -> Route -> ( String, Maybe Route )

hpr2713 :: Resources in 4x game

Released on 2018-12-26 under a CC-BY-SA license.

Raw resources are integral part for most 4x games. Here’s one way of modeling them in Haskell. I wanted a system that is easy to use, doesn’t require too much typing and is type safe.

RawResource is basic building block:

newtype RawResource a = RawResource { unRawResource :: Int }
    deriving (Show, Read, Eq)

It can be parametrised with anything, but I’m using three different types:

data Biological = Biological
data Mechanical = Mechanical
data Chemical = Chemical

Example of defining harvest being 100 units of biological raw resources:

  harvest :: RawResource Biological
  harvest = RawResource 100

Raw resources are often manipulated (added and subtracted mostly). Defining Num instance allows us to use them as numbers:

instance Num (RawResource t) where
    (+) (RawResource a) (RawResource b) = RawResource $ a + b
    (-) (RawResource a) (RawResource b) = RawResource $ a - b
    (*) (RawResource a) (RawResource b) = RawResource $ a * b
    abs (RawResource a) = RawResource $ abs a
    signum (RawResource a) = RawResource $ signum a
    fromInteger a = RawResource $ fromInteger a

For example, adding harvest to stock pile:

  stock :: RawResource Biological
  stock = RawResource 1000

  harvest :: RawResource Biological
  harvest = RawResource 100

  newStock = stock + harvest

Comparing size of two resource piles is common operation. Ord instance has methods we need for comparing:

instance Ord (RawResource t) where
    (<=) (RawResource a) (RawResource b) = a <= b

One function is enough, as rest is defined in terms of it. Sometimes (usually for reasons of optimization), one might want to define other functions too.

Another way to add bunch of resources of same type together is defining Monoid instance:

instance Semigroup (RawResource t) where
    (<>) a b = a + b

instance Monoid (RawResource t) where
    mempty = RawResource 0

For example, combining harvests of many fields can be achieved as:

  harvests :: [RawResource Biological]
  harvests = [RawResource 20, RawResource 50, RawResource 25]

  total :: RawResource Biological
  total = mappend harvests

All these functions keep track of type of resources being manipulated. Compiler will emit an error if two different types of resources are being mixed together.

Raw resources are often grouped together for specific purpose. This again uses phantom types to keep track the intended usage:

data RawResources a = RawResources
    { ccdMechanicalCost :: RawResource Mechanical
    , ccdBiologicalCost :: RawResource Biological
    , ccdChemicalCost :: RawResource Chemical
    } deriving (Show, Read, Eq)

data ResourceCost = ResourceCost
data ConstructionSpeed = ConstructionSpeed
data ConstructionLeft = ConstructionLeft
data ConstructionDone = ConstructionDone
data ResourcesAvailable = ResourcesAvailable

And in order to be able to combine piles of RawResources, we’ll define Semigroup and Monoid instances. Notice how both instances make use of Semigroup and Monoid instances of RawResource:

instance Semigroup (RawResources t) where
    (<>) a b = RawResources
        { ccdMechanicalCost = ccdMechanicalCost a <> ccdMechanicalCost b
        , ccdBiologicalCost = ccdBiologicalCost a <> ccdBiologicalCost b
        , ccdChemicalCost = ccdChemicalCost a <> ccdChemicalCost b

instance Monoid (RawResources t) where
    mempty = RawResources
        { ccdMechanicalCost = mempty
        , ccdBiologicalCost = mempty
        , ccdChemicalCost = mempty

For those interested seeing some code, source is available at ( is situation before lots of Elm related changes that I mentioned in passing in the previous episode)

hpr2703 :: Fog of war in Yesod based game

Released on 2018-12-12 under a CC-BY-SA license.

Duality of the universe: there's true state of the universe used in simulation and there's state the the players perceive. These most likely will always be in conflict. One possible solution is to separate these completely. Perform simulation in one system and record what players see in other.

For every type of entity in the game, there's two sets of data: real and reported. Reports are tied to time and faction. Examples are given for planets. Thus, we have Planet, PlanetReport and CollatedPlanetReport. First is the real entity, second is report of that entity tied in time and faction. Third one is aggregated information a faction has of given entity. In database two first ones are:

Planet json
    name Text
    position Int
    starSystemId StarSystemId
    ownerId FactionId Maybe
    gravity Double
    SystemPosition starSystemId position
    deriving Show

PlanetReport json
    planetId PlanetId
    ownerId  FactionId Maybe
    starSystemId StarSystemId
    name Text Maybe
    position Int Maybe
    gravity Double Maybe
    factionId FactionId
    date Int
    deriving Show

Third one is defined as a datatype:

data CollatedPlanetReport = CollatedPlanetReport
    { cprPlanetId :: Key Planet
    , cprSystemId :: Key StarSystem
    , cprOwnerId  :: Maybe (Key Faction)
    , cprName     :: Maybe Text
    , cprPosition :: Maybe Int
    , cprGravity  :: Maybe Double
    , cprDate     :: Int
    } deriving Show

Data from database need to be transformed before working on it. Usually it's 1:1 mapping, but sometimes it makes sense to enrich it (turning IDs into names for example). For this we use ReportTransform type class:

-- | Class to transform a report stored in db to respective collated report
class ReportTransform a b where
    fromReport :: a -> b

instance ReportTransform PlanetReport CollatedPlanetReport where
    fromReport report =
	CollatedPlanetReport (planetReportPlanetId report)
			     (planetReportStarSystemId report)
			     (planetReportOwnerId report)
			     (planetReportName report)
			     (planetReportPosition report)
			     (planetReportGravity report)
			     (planetReportDate report)

To easily combine bunch of collated reports together, we define instances of semigroup and monoid for collated report data. Semigroup defines an associative binary operation (<>) and monoid defines a zero or empty item (mempty). My explanation about Monoid and Semigroup were a bit rambling, so maybe have a look at which explains it in detail.

instance Semigroup CollatedPlanetReport where
    (<>) a b = CollatedPlanetReport (cprPlanetId a)
				    (cprSystemId a)
				    (cprOwnerId a <|> cprOwnerId b)
				    (cprName a <|> cprName b)
				    (cprPosition a <|> cprPosition b)
				    (cprGravity a <|> cprGravity b)
				    (max (cprDate a) (cprDate b))

instance Monoid CollatedPlanetReport where
    mempty = CollatedPlanetReport (toSqlKey 0) (toSqlKey 0) Nothing Nothing Nothing Nothing 0

In some cases there might be a list of collated reports that are about different entities of same type (several reports for every planet in solar system). For those cases, we need a way to tell what reports belong together:

-- | Class to indicate if two reports are about same entity
class Grouped a where
    sameGroup :: a -> a -> Bool

instance Grouped PlanetReport where
    sameGroup a b =
	planetReportPlanetId a == planetReportPlanetId b

After this, processing a list of reports for same entity is short amount of very general code:

-- | Combine list of reports and form a single collated report
--   Resulting report will have facts from the possibly partially empty reports
--   If a fact is not present for a given field, Nothing is left there
collateReport :: (Monoid a, ReportTransform b a) => [b] -> a
collateReport reports = mconcat (map fromReport reports)

For reports of multiple entities is bit more complex, as they need to be sorted first, but the code is similarly general:

-- | Combine list of reports and form a list of collated reports
--   Each reported entity is given their own report
collateReports :: (Grouped b, Monoid a, ReportTransform b a) => [b] -> [a]
collateReports [] = []
collateReports s@(x:_) = collateReport itemsOfKind : collateReports restOfItems
    where split = span (sameGroup x) s
	  itemsOfKind = fst split
	  restOfItems = snd split

Final step is to either render reports as HTML or send them as JSON back to client. For JSON case we need one more type class instance (ToJSON) that can be automatically generated. After that handler function can be defined. After authenticating the user and checking that they are member of a faction, reports of specific planet (defined by its primary key) are retrieved from database, collated, turned into JSON and sent back to client:

$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''CollatedPlanetReport)

getApiPlanetR :: Key Planet -> Handler Value
getApiPlanetR planetId = do
    (_, _, fId) <- apiRequireFaction
    loadedPlanetReports <- runDB $ selectList [ PlanetReportPlanetId ==. planetId
					      , PlanetReportFactionId ==. fId ] [ Asc PlanetReportDate ]
    let planetReport = collateReport $ map entityVal loadedPlanetReports :: CollatedPlanetReport
    return $ toJSON planetReport

For those interested seeing some code, source is available at ( is situation before lots of Elm related changes that I mentioned in passing in the previous episode)

hpr2693 :: Getting started with web based game in Haskell and Elm

Released on 2018-11-28 under a CC-BY-SA license.

Haskell Stack:

Stack is a build tool for Haskell with focus on reproducible build plans, multi-package projects, and a consistent, easy-to-learn interface. With stack, one can create new project: stack new my-project yesod-sqlite (more in the quick start guide:

models is used to define shape of the data and Yesod uses it to generate datatypes and database for you. For example, to define a Star that has name, spectral type, luminosity class and link to StarSystem, one can write:

Star json
    name Text
    starSystemId StarSystemId
    spectralType SpectralType
    luminosityClass LuminosityClass

Custom types, like LuminosityClass, need mapping between datatype and database. In simple cases like this, Yesod can do that:

data LuminosityClass = Iap | Ia | Iab | Ib | II | III | IV | V | VI | VII
    deriving (Show, Read, Eq)
derivePersistField "LuminosityClass"

The "derivePersistField" part is template haskell call that will generate mapping needed.

For those interested seeing some code, source is available at ( is situation before lots of Elm related changes that I mentioned in passing in the episode)

hpr2633 :: Elm - First Impressions

Released on 2018-09-05 under a CC-BY-SA license.

hpr2618 :: Yesod - First Impressions

Released on 2018-08-15 under a CC-BY-SA license.

First place to start is probably Yesod’s web site at:

Often recommended environment for developing Haskell programs is Stack:

My road to Haskell started with Learn You a Haskell for Great Good: and going through lecture notes of CIS 194:

hpr2608 :: BattleTech

Released on 2018-08-01 under a CC-BY-SA license.

Following links might help you to get more familiar with the game.

hpr2598 :: Calculating planetary orbits in Haskell

Released on 2018-07-18 under a CC-BY-SA license.

Function signatures (it might or might not be helpful to have these at hand while listening):

  • Helpers:
    radToDeg :: Floating a => a -> a
    degToRad :: Floating a => a -> a
    clamp :: Float -> Float
  • Time:

    day :: Int -> Int -> Int -> Float -> Day Float
  • Orbital parameters:
    longitudeOfAscendingNode :: Orbit body center => body -> center -> Day d -> LongAscNode body center
    inclinationToEcliptic :: Orbit body center => body -> center -> Day d -> InclToEcl body center
    argumentOfPeriapsis :: Orbit body center => body -> center -> Day d -> ArgPeri body center
    semiMajorAxis :: Orbit body center => body -> center -> Day d -> SemiMajor body center
    eccentricity :: Orbit body center => body -> center -> Day d -> Ecc body center
    meanAnomaly :: Orbit body center => body -> center -> Day d -> MeanAno body center
  • Calculating location on orbital plane:
    eccAnomaly :: MeanAno a b -> Ecc a b -> EccAnomaly a b
    trueAnomaly :: EccAnomaly a b -> Ecc a b -> TrueAnomaly a b
    dist :: EccAnomaly a b -> Ecc a b -> SemiMajor a b -> Distance a b
  • Translating between coordinate systems:
    toEclCoord :: TrueAnomaly a b -> Distance a b -> LongAscNode a b -> ArgPeri a b -> InclToEcl a b -> EclCoord a b
    toEqCoordinates :: EclCoord body Earth -> Day Float -> EqCoord body

Some helpful links:

hpr2593 :: Intro to De Bellis Antiquitatis

Released on 2018-07-11 under a CC-BY-SA license.

In this episode tuturto paints rambles about De Bellis Antiquitatis while painting more toy soldiers, so expect long pauses and missing thoughts as he tries to do two things at the same time.

De Bellis Antiquitatis (or DBA for short):

While the original site seems to be gone, WADBAG unofficial guide to DBA can be found at:

hpr2588 :: Miniature painting

Released on 2018-07-04 under a CC-BY-SA license.

tuturto rambles about miniature painting while painting some ancient British units (horses for chariots to be specific) for De Bellis Antiquitatis.

hpr2524 :: General problem solver

Released on 2018-04-05 under a CC-BY-SA license.

Become a Correspondent