Recent Posts

Compiling External Resources with Elixir

Posted on 05 Mar 2018

The other week I started playing around with the @external_resource tag in Elixir. I wanted to do something similar to a translations file for the admin panel in ExVenture for help text. I didn't want to default to a YAML file as I've heard the elixir community isn't thrilled with it, so I took a look around to see what I could do.

What I ended up with was a macro that could compile a text file into Elixir functions. It also live reloads with the Phoenix code reloader in development because of @external_resource, this is really cool to see working.

Help File Format

This is the file format I wanted to end up with. Keys and values essentially separated by new lines.

  Room ecology changes the color of the map "icon" in the map grid.

  Room features are appended to the end of a room's description.

You can see the full file here.

Help Macro

The end result of the macro will give us an API as follows:

iex> Web.Help.get("room.feature")
"Room features are appended to the end of a room's description."

This works via a macro (full file here) that loads the file and compiles it into quoted functions. The import sections are copied below:

defmacro __using__(file) do
  help_file = Path.join(:code.priv_dir(:ex_venture), file)
  {:ok, help_text} =
  quotes = generate_gets(help_text)
  quotes = Enum.reverse([default_get() | quotes])
  [external_resource(help_file) | quotes]

defp generate_gets(help_text) do
  |> String.split("\n")
  |> Enum.reject(&(&1 == ""))
  |> convert_to_map()
  |> {key, val} ->
    quote do
      def get(unquote(key)) do

defp external_resource(help_file) do
  quote do
    @external_resource unquote(help_file)

The __using__ macro reads the file and generates the get quotes and the external resource quote. The external_resource/1 function is how the code live reloads when editing the text file. The generate_gets/1 function parses the help file to generate a list of quoted functions, something I've never tried before. It was pretty cool to see you can return a list of quoted functions and get the same result.


It is really cool to see the live reloading work. This may or may not be the best way to do what I want, but I had a lot of fun writing this. I think if these help files grow to be hundreds of keys long I will want to change up how this is parsing, but for now it works out well.

Lastly, you can check out more ExVenture at and see my running instance at

ExVenture Updates for February 2018

Posted on 26 Feb 2018

The last month of ExVenture was a lot of small tweaks here and there, along with a revamp of the skills system.

The documentation website is You can see the latest additions here on MidMUD, my running instance of ExVenture. There is also a public Trello board now.


Skills used to be directly attached to classes. You only had what the class had. Now they are split apart from classes. You can train as many skills as you have experience for. You train them from NPCs around the world.

Skills can also be global and can still be attached to classes. When they are global or attached to a class, players will automatically train them when they become the appropriate level to unlock it. Races can also have skills now, and act in the same manner.


There are a few tweaks to rooms this month. They can now have features, which let players look further into details of a room. These features show up at the end of a room description and will highlight a keyword a user might look at to view more information.

The admin panel also had rooms spruced up, so they look more like they do in game.

Room Admin

Mail system

The mail system lets you send mail now. You can view it in game and also via the web site. If you are offline and have an email on your account, you will receive an email notification. You will also receive an in-game notification of new mail.

Mail view

Handle crashes

I worked on handling crashes better. Now all of the processes that are "in" a room are linked together. When an NPC or player enters a room, they link against it. This way if any of them die, then they all die together. When they die, they load from the database to make sure they have a well known good copy of state.

Players will see a brief "Session crashed, restoring..." message and that is all they should notice (aside from losing up to 15 seconds of save data.) This works across telnet and websocket connections.

New commands

There are several new commands available. You can give items to other players and NPCs. Quests can tie into this as well.

The recall command is new. It will return you to the zone's graveyard, but only if you have 90% of your movement left.

Lastly, there are social commands that you can add via the admin panel. They can be with or without a target.



The security of the application has been increased. Telnet logins require a one time password after signing into the website.

One Time Passwords

When signing into the website, you can now add two factor authentication to you account.


Small Tweaks

  • Ran the elixir formatter over the codebase
  • Display flags for users in the who list
  • Push more events through the notify systems
  • Stop ticking every 2 seconds, have systems react when they notice work to be done
  • Send email after signing up
  • Rearrange the admin sidebar
  • Switch sessions to the dynamic supervisor
  • Refactor the {:user, session, user} tuple out of the codebase
  • Simple state machine for the telnet color formatter
  • Keep newlines when wrapping text
  • Damage is randomized, +/- 25%
  • Dyanmic channels
  • Web notifications for in game messaging
  • say/random npc action, to pick a random message each tick

Next Month

For next month, I am hoping to fill in the world detail more. I am thinking senses, lighting, and time are next. Making things more dynamic, such as damage types, is something I want to continue doing. I also need to do a refresh of the documention, some of the new features are missing and screenshots are generally out of date.

Filtering Ecto with a Behaviour

Posted on 02 Feb 2018

For my project ExVenture I wanted to have a very nice admin interface and with that comes filtering of tables. This is what I came up with, which I think has been very extendable and worked out well.

This is what we'll end up with:


Let's first start at a controller to see what it looks like there. The controller is incredibly simple, pulling out the filter parameters and passing it into a web specific module, Web.Item. You can view the full file here on github.

def index(conn, params) do
  %{page: page, per: per} = conn.assigns
  filter = Map.get(params, "item", %{})
  %{page: items, pagination: pagination} = Item.all(filter: filter, page: page, per: per)
  conn |> render("index.html", items: items, filter: filter, pagination: pagination)

This also includes my pagination module, but I will be glossing over that part. We will see it again in the Web.Item module.


Next let's look at the Web.Item module. This module contains the all/1 function used above. It implements the Filter's behaviour that requires a single function filter_on_attribute/2.

The map of filters passed in from the controller is looped over and passed into this function of the module. Inside the filter_on_attribute/2 function you can tweak the query to do whatever is needed to perform a filter based on that attributes.

defmodule Web.Item do
  alias Data.Item
  alias Web.Filter

  @behaviour Filter

  @doc """
  Load all items
  @spec all(Keyword.t()) :: [Item.t()]
  def all(opts \\ []) do
    opts = Enum.into(opts, %{})

    |> order_by([i],
    |> preload([:item_aspects])
    |> Filter.filter(opts[:filter], __MODULE__)
    |> Pagination.paginate(opts)

  @impl Filter
  def filter_on_attribute({"level_from", level}, query) do
    query |> where([i], i.level >= ^level)

  def filter_on_attribute({"level_to", level}, query) do
    query |> where([i], i.level <= ^level)

  def filter_on_attribute({"tag", value}, query) do
    query |> where([n], fragment("? @> ?::varchar[]", n.tags, [^value]))

  def filter_on_attribute(_, query), do: query

You can see here that I'm doing a fragment to look into an array of strings. You could also do a join or other more complicated query manipulation.


Finally the Web.Filter module itself. This is very simple, it defines a callback and a single function. The function also requires the using module to be passed in as the last argument. This is very simple with the __MODULE__ macro.

defmodule Web.Filter do
  @moduledoc """
  Filter an `Ecto.Query` by a map of parameters. Modules
  that use this should follow it's behaviour.

  @doc """
  This will be reduced from the query params that are
  passed into `filter/3`.
  @callback filter_on_attribute(
              {attribute :: String.t(), value :: String.t()},
              query :: Ecto.Query.t()
            ) :: Ecto.Query.t()

  @doc """
  Common elements of filtering a query
  @spec filter(Ecto.Query.t(), map(), atom()) :: Ecto.Query.t()
  def filter(query, nil, _), do: query

  def filter(query, filter, module) do
    |> Enum.reject(&(elem(&1, 1) == ""))
    |> Enum.reduce(query, &module.filter_on_attribute/2)

There are two other small things that this does, it pattern matches on a nil map and simply passes the query through. Lastly it filters out values that are an empty string since that is an empty form field.

The Template

I won't go into detail about this, but you can view it on github.


This has worked out really well in crafting my admin panel. Adding a new filter is as simple as adding the template code and a single function, no extra magic needed.

ExVenture Updates for January 2018

Posted on 23 Jan 2018

The last month of ExVenture had some big additions. New are NPC conversations and questing, along with other small tweaks to make NPCs feel more part of the world.

The documentation website is You can see the latest additions here on midmud, my running instance of ExVenture.


I got stickers for midmud within the last week.


NPC Conversations and Scripts

NPCs now have a script that will let players whisper to them and respond with. An example interaction is as follows:

You greet Guard
Guard replies, "Hello, are you here to find out about our bandit problem?"
You tell Guard, "huh?"
Guard replies, "I don't know about that, but I can help you with bandits",
You tell Guard, "yes"
Guard replies, "I heard they are hiding down in a cave."
You have a new quest.

You can find more about the format at


The even bigger feature is questing. NPCs can hand out quests as seen above in the example conversation. Quests replace the default NPC script if a quest is available for a player. A player doesn't necessarily get a quest when conversing with an NPC that is trying to give one out. They must hit an end of the conversation that has a trigger: quest.

You can see more about questing at

Handing out a quest

Quests currently can require items or NPCs to be killed in order to trigger them. NPCs are tracked and counted when they are killed by a player. Items are removed when turning in the quest. Quests can also be in a quest chain, requiring parents to be completed before being offered children quests. This can create a linear story that helps move a player through an area.

NPC status line

NPCs now have a status line that displays instead of {name} is here. This area will also display if the NPC is a quest giver so players know to greet them to start a quest.

NPC status

Note Pad

There is a general note pad area now. Notes are simple records that have a name and a body that displays as markdown. I wanted to have something where I could record more free form ideas about the game that I could come back to later.

Mail system

A mail system is currently being worked on, before I was distracted by questing. Right now it is read only in the game, but you can view and read mail if you have one.

Small tweaks

  • Removed a lot of passing around of the session pid
  • Start running some of the modules through the elixir formatter
  • Tweak NPC death
  • Giving a reason for leaving or entering a room
  • NPCs have a status line and description now
  • Look at items, players, npcs
  • Basic session statistics are stored: how long, what commands were used
  • Color codes don't for an early wrap, no longer leaving odd length lines

Next Month

I have a few ideas on how to rework the class and skill system. I might get rid of classes as a rigid architype after reading Designing Virtual Worlds by Richard Bartle. He talks about having an initial class system be a starting point of skills and being free form from there. That way new players can instantly get started with minimal thinking and then later on they can completely change what they want to be. This has slowly grown on me and I really like the idea of it.

I would also like to finish the mail system.

A Tweak to Phoenix Contexts

Posted on 10 Jan 2018

I would like to start this post out by saying this isn't a negative post against the bounded context approach Phoenix is now geared towards. I wanted to showcase what has worked out really well with my side project, ExVenture, which has grown to a fairly hefty size. As always you can see the source on GitHub and a running instance at MidMUD.

So what do you have instead?

Instead of the context approach where everything is hidden beneath the context I instead have several separate module namespaces that are my "contexts". Here is my lib folder structure:

├── data
├── ex_venture
├── game
├── metrics
├── networking
└── web

Each of those top level folders are a loose context.


This level contains all of my ecto schemas and modules that deal with data.

├── bug.ex
├── class.ex
├── config.ex
├── effect.ex
├── event.ex
└── ...


This level contains what I would call the business logic of the app. It has all of the user processing commands, gen servers, and is the core of the app.

├── command
├── command.ex
├── format
├── format.ex
├── item.ex
├── npc
├── npc.ex
├── room
├── room.ex
├── session
├── session.ex
└── ...

Inside this namespace I also split into more sub modules based in general on GenServer processes. For instance Game.Session has a lot of sub modules to help create a session process for when someone starts playing the app. This is fairly close to the general theme of the Bounded Contexts in Phoenix.


This level contains all of my Prometheus metrics modules and is fairly slim.


This level contains telnet related modules, including the ranch protocol.


This level contains the Phoenix layer. I should note that I consider ExVenture to be an OTP app first, and a Phoenix app a distant second.

├── bug.ex
├── channels
├── class.ex
├── config.ex
├── controllers
├── endpoint.ex
├── filter.ex
├── gettext.ex
├── item.ex
├── npc.ex
├── pagination.ex
├── plug
├── prometheus_exporter.ex
├── room.ex
├── router.ex
├── supervisor.ex
├── templates
├── user.ex
├── views
└── ...

Small aside: I really like naming this module Web and not ExVentureWeb or similar, it really helps out my tab complete habit to be simply named Web.

How This All Fits Together

So with all of these different modules as contexts, how does this all fit together? I will show this by giving an example of the NPC (the non-player characters in the game) slice.


This module is a simple ecto schema, it simply validates the data in a changeset.


This is a GenServer process for the NPC. It's a mix of an NPC struct and an NPC Spawner struct. I have a lot of sub modules for this dealing with events that the NPC may act on, respawning if it is killed, and other helper modules like the Supervisor for NPCs.


The web module is a helper module to fit between the Phoenix layer and the core of my app. All of the controllers deal with this module and not a deeper part of the app. It handles creating NPCs, their spawners, adding items, and ensuring the live processes are kept up to date as all of these changes happen. This is probably the closest thing to a standard Phoenix context that I have.


As you can see what I ended up with is only a slight tweak on the general principles behind Bounded Contexts.

I like having a general schema area and making sure nothing else goes in there. I like having a lot of modules that are related to the process they're attached to. I really like having the web layer barely know about the core of the app.

I hope this helps make you think about the structure of your app more and realize that bounded contexts are there to make you think about your app, and not be the only way.

Eric Oestrich
I am:
All posts
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.