Introduction

View Source

DevJoy

Easier creation of the Visual Novel-like games or presentations.

Setup

Except drawing you only have to provide a content to the game and fetch it. To "draw" your game all you need to do is to convert structs into hologram or phoenix templates or scenic graphs and style them respectively.

Mix dependency:

def deps do
  [
    {:dev_joy, "~> 2.0"}
  ]
end

Rebar dependency:

{deps,[
  {dev_joy, "~> 2.0"}
]}.

Example: Importing data from external sources

Mix.install [:dev_joy, :nimble_csv]

priv =
  __DIR__
  |> Path.absname()
  |> Path.join("script_priv")
  |> tap(&File.mkdir_p!/1)

defmodule MainScene do
  use DevJoy.Scene, priv: priv

  alias NimbleCSV.RFC4180, as: CSV

  data = CSV.parse_string("""
  string id,dialog content
  john_doe,Hello world!
  """)

  part :main do
    for [name, content] <- data do
      # Note: prefer String.to_existing_atom/1 instead
      dialog String.to_atom(name), content
    end
  end
end

dialog = MainScene.get_part_item(:main, 1)
# => %DevJoy.Scene.Dialog{…}

dialog.character.id
# => :john_doe

dialog.content
# => "Hello world!"

Example: Using Item API to create the DSL for code samples

defmodule MyApp.CodeSample do
  @moduledoc """
  The code sample is visually represented as a snipped containing the highlighted text

  > ### Usage {: .info}
  > 
  > The [`code sample`](`t:t/0`) always requires its [`content`](`t:content/0`) and [`language`](`t:lang/0`).
  > You can also optionally specify additional [`data`](`t:data/0`).
  > 
  >     defmodule MyApp.Scenes.Main do
  >       use DevJoy.Scene
  >
  >       import MyApp.DSL
  > 
  >       part :main do
  >         code :elixir, ~S[IO.puts("Hello world!")]
  > 
  >         code :elixir, [some: :data], ~S[IO.puts("Hello world!")]
  >     
  >         code :elixir, [some: :data] do
  >           ~S[IO.puts("Hello world!")]
  >         end
  >       end
  >     end
  """

  @enforce_keys ~w[content lang]a

  @doc """
  The [`code sample`](`t:t/0`) structure contains the following keys:

  - [`content`](`t:content/0`) - a content of the code sample
  - [`data`](`t:data/0`) - a keyword list of additional code sample data
  - [`lang`](`t:lang/0`) - a programming language of the code sample
  """
  defstruct [:content, :lang, data: []]

  @typedoc """
  The type representing the [`code sample`](`__struct__/0`) structure.
  """
  @typedoc section: :main
  @type t :: %__MODULE__{
          content: content(),
          data: data(),
          lang: lang()
        }

  @typedoc """
  The type representing the content of the code sample.
  """
  @typedoc section: :field
  @type content :: String.t()

  @typedoc """
  The type representing the additional data of the code sample.
  """
  @typedoc section: :field
  @type data :: Keyword.t()

  @typedoc """
  The type representing the lang of the code sample.
  """
  @typedoc section: :field
  @type lang :: atom
end

Example: Implementing the Character base module for a Discourse forum API

defmodule MyApp.Forum do
  @moduledoc "Fetches forum data using username and Discourse API."

  @behaviour DevJoy.Character

  alias DevJoy.API.Item
  alias DevJoy.Character

  @avatar_size "48"

  @impl DevJoy.Character
  def get_character_fields(id) do
    {:ok, _app_list} = Application.ensure_all_started(:req)
    fetch_forum_data(id)
  end

  @spec fetch_forum_data(Character.id()) :: Item.fields()
  defp fetch_forum_data(id) do
    :dev_joy
    |> Application.get_env(__MODULE__, endpoint: "https://elixirforum.com")
    |> Keyword.fetch!(:endpoint)
    |> then(fn endpoint ->
      endpoint
      |> Path.join("u/#{id}.json")
      |> Req.get!()
      |> then(& &1.body["user"])
      |> then(&[avatar: avatar_from_template(&1["avatar_template"], endpoint), full_name: &1["name"]])
    end)
  end

  @spec avatar_from_template(String.t(), String.t()) :: Character.portrait()
  defp avatar_from_template(template, endpoint) do
    template
    |> String.replace("{size}", @avatar_size)
    |> then(&Path.join(endpoint, &1))
  end
end