Good and bad tools for creating branching dialogues

Hello everybody :slight_smile:

I’m making a tool with the goal to help creating branching dialogues for game development. I want to make it easy to use and intuitive with the ability to export the story into different output formats. So far I think I have a solid foundation: 2 basic views for story display, very structured and scalable clean code, basic functionality to add or delete story elements.

Now I’m a little stuck because I’m not a domain expert :slight_smile: In other words I haven’t worked with dialogues for games yet (except for looking into yarn spinner, playing around with unity and unreal engine for a while and testing twine). Basically my issue is that I don’t know what tools are great and what tools are less so. Knowing what works and what doesn’t for experts would help me out a lot in figuring out what feature to implement and how to make it as intuitive and helpful as possible. The goal is to make a tool that doesn’t stand in the way of the creative process.

Maybe some of you can kick my butt in the right direction? I would greatly appreciate that :slight_smile:

And thank you in advance!

3 Likes

Hey Bic, welcome to devtalk :023:

It’s not an area I’m personally familiar with but what about looking at these:

  • Twine
  • Yarn Spinner
  • Ink (by Inkle)
  • ChatMapper

Also I’m not sure if it’s worth looking at books from @Paradox927 and @herbert, which might also contain some useful info…

1 Like

Took me a while to get back to the forums. Real life happened, but I’m back and very thankful for your response. I will look into those tools you mentioned, especially Ink and ChatMapper as those are absolutely new to me right now. I will definitely need to find out what formats there are for branching stories and what formats those tools can export to.

Maybe consider my Elixir package:

With a simple and extensible DSL you can write any story and easily translate it using po files. In recent release there is lots of extra code for a session management and so on. This way you can easily write a simple game or presentation by generating a new Phoenix (web framework) based project, adding your LiveView pages and of course using the DSL and optionally translating the content.

I have even created a simple project to demonstrate how a video talk have been transformed into a presentation. You can use for example a Rust NIF to do the GUI or use wxWidgets bindings for Erlang language or even Scenic for embedded devices.

Working on HTML-based game at the very start should be simplest option and it’s also worth to mention that we have a LiveViewNative project. Let me know how that sounds for you.

1 Like

You’re off to a strong start—clean, scalable code and a solid UI foundation already put you ahead of many early tools in this space. To kick you in the right direction: focus on how writers think, not just how devs structure logic. Look into what makes Twine fast for prototyping, why Ink by Inkle is praised for writer-friendly syntax, and how Yarn Spinner handles conditionals and variables intuitively. Key features to prioritize: non-linear navigation, variable tracking, previewing dialogue flow, and export options to JSON, Ink, or Yarn formats. Talk to narrative designers if you can—nothing beats real-world pain points to guide what not to do. You’re building the bridge between creativity and code—just make sure it’s one they want to cross.

1 Like

Okay, this is a lot to unpack but I think I understood so far. Sadly I don’t have any experience in Elixir, but I would love to see this thing in action. Thankfully it’s weekend so tomorrow I will be checking it out. Thank you very much for mentioning this library, package or tool. Not sure yet what exactly it is :slight_smile:

1 Like

First of all thank you very much for your input. I appreciate it. And you’re absolutely right. My main goal is to allow writers to do their job without being distracted by all the rest of what needs to be done in the story.

This is not an easy task because often the narrative goes hand in hand with logic. But done right allows multiple user types (let’s say writers and developers) to work on the very same story simultaneously.

Because of this my approach was that there should be multiple layers. So far the narrative layer is there and I like it. It lets writers define the overall story without going in great detail yet.

The next layer is the logic one. This one is missing. Basically now it’s about identifying functionality for developers. How can I make it easy, intuitive and solid while separating all the concerns. The logic part can be done in different ways but I really want to get away from how ChatMapper does things - Lua. It is cool on paper, but in practice it’s easy to introduce mistakes and difficult to find them. Validating Lua is not a trivial task nor is it fun for anybody. Debugging a complex story like that is hell. Also every node has the very same type in ChatMapper it seems. There is no separation of concerns, no layers. Just 1 type of node with 20 properties on it.

I believe implementing a basic logic layer with some functionality will let me improve the writer’s side as well because the logic functionality actually gets a home. Things that sneaked into the narrative layer but actually belong to the logic side (I don’t think that happened) can be moved.

All that said, my tool is highly visual, there’s no scripting support as of now. Do you think that is a valid approach? I’m not against scripting at all, just didn’t think it through yet so it’s for later.

1 Like

“Library” naming is more common in other langages when you deal with .so, .dll or similar files. In Elixir by package we mean an Elixir project packaged and shared (usually to hex or it’s company’s mirror). It’s definitely not a tool that let’s you generate a ready game with “a few clicks”.

The Elixir is a FP language, so we don’t have classes and methods. Instead we group the functions in modules. My package gives you a ready to use DSL (set of useful macros) as well as a few helpful modules to work with the generated data.

defmodule MyApp.Scenes.Main do
  use DevJoy.Scene

  part :main, page_title: "Hello!" do
    dialog :john_doe, "Hello world"
  end
end

The input starting with a : character is called atom. I think that in JavaScript to closest equivalent is a Symbol. For example we often return {:ok, some_data} to mark the output of a function successful, so we can check if the returned value have an :ok atom and if that’s true some action would happen.


The use is just another macro added to language core. In very short it’s equivalent for:

# needed to call macros
require Name.Of.Module
# the actual macro call
Name.Of.Module.__using__()

This is used for metaprogramming to generate the code. Think about it like when you extend some class, so you can call a method from other class. The only difference here is that the code are generated by the package code and not simply imported.


So if you understand above 2 and you are aware what DSL (like part and dialog) is then everything becomes clear. My packages allows you to put content with a minimal amount of code, generates the functions to fetch it and provides other helpful modules to make this work even more simpler.

How to branch i.e. put alternative scenarios? That’s very simple! All you have to do is to add the question to which user would have interact or alternatively a runtime condition to be called. Using a goto DSL you can change a scene part or even the scene module. How you organise your content is fully up o you.


Here are some interesting features my package provides:

Accumulation of items

This is useful if you want to fetch assets for a specified data. For example if you want to have background changed on some dialog then you simply put an asset before it with a :background type (you can use any atom here - it’s up to you). As long as you are accumulating them they would not be a part of the get_part_item, but get_part_acc instead. How to fetch the right acc? Simply use the same item index as you used to fetch to fetch item.

item = SceneModule.get_part_item(:part_name, 1)
assets = SceneModule.get_part_acc(:part_name, 1)

Session

Why we should bother what index each item have? Why we should care what scene or part we use? Just start with a main scene, default part and first item and then get next, next, next …

defmodule MyApp.PageLive do
  use MyAppWeb, :live_view
  # Phoenix.LiveView integration
  use DevJoy.Session.LiveView
  # default session behaviour to send messages
  use DevJoy.Session.Notifier

  alias DevJoy.Session
  alias MyApp.Scenes.Main

  # …

  def handle_event("start-game-btn-click", _params, socket) do
    # when button is clicked ask session for a specific item
    # the session would send a message with a new item
    Session.get({Main, :main, 1})
    {:noreply, socket}
  end

  def handle_event("next-item-btn-click", _params, socket) do
    # same as before, but without inital scene, part and item index data
    Session.next()
    {:noreply, socket}
  end

  def handle_info({:dev_joy, :item_changed, item}, socket) do
    # in every case we simply assign an item
    # when item changes in assigns the template engine updates modified HTML
    {:noreply, assign(socket, item: item)}
  end
end

How about accumulated items then? Session have also solution for it:

  def handle_info({:dev_joy, :item_changed, item}, socket) do
    assets = Session.current_acc()
    {:noreply, assign(socket, assets: assets, item: item)}
  end

Powerful API

You can configure how to store, fetch data and how to notify your app about new data. This way you can write integrations for other libraries like scenic (embedded devices) or distribute the data. Think that you have presentation with more than 1 person in stuff. You don’t have to ask your partner to “click” for you and you don’t need to control every part of presentation. If you use custom notifier to send data to other clients then all you have to do is to listen for new messages and apply changes just like when you handling the default notifier!

However that alone is not enough. Sometimes you have something custom. Something that no common data structure can handle, right? Usually it’s very hard to add a custom item and you would have to somehow integrate it with the rest of some library, right? Not in this case!

defmacro my_dsl_name(value1, value2, do: block) do
  fields = [key1: value1, key2: value2]
  %DevJoy.API.Item.generate(ItemModuleName, :my_dsl_name, fields, __CALLER__, ~w[key1]a, block)
end

All you have to do is to simply write your ItemModuleName. That would be a module that defines an Elixir struct. What’s struct? It’s a sub-type of map that have predefined keys. The rest is just a documentation and typespecs.

You have to pass from 4 to 6 arguments:

  1. The item module - simply the generated function would return a structure and therefore we need to know which module defines it

  2. DSL name - this is used in po translation files as the context, so we can see what DSL have generated specific text to translate

  3. The fields list - a key-value list (in Elixir it’s called a keyword - it’s similar to map except that it can contain a duplicate keys and it’s easier to use in macros)

  4. The caller - it’s a struct containing environment information (like the file or line when the specific DSL were called) - this is also very useful information stored as a reference in a po file

  5. An optional list of keys to translate - if you want to support translating your custom content here you define which data passed to your DSL would be generated in pot(template for po file) and used in po files to fetch translation.

  6. An optional block - it contains the AST (Abstract Syntax Tree) i.e. the representation of code that could be modified and later compiled. In this case we use it to support a nested DSL (see menu or question DSL for example).

This way even developer not experienced in metaprogramming can simply add their own DSL without a worries for implementation details, compatibility with other parts of package or translations support. It’s really batteries-included!


In theory a publicly documented Session and History modules can help you with writing your own Session or History. If you need something special in history navigation it should be much easier to implement only custom features and let existing ones do the rest.


You can test this package in action very easily. All you have to do is to run an existing demo project:

You can find some screenshots in previously linked forum topic.


It would be a big help if you would let me know what you think about it. I open to add more functionality or improve a documentaion, but I need some feedback for that. What I have now works already (as in said demo project) and so far I did not received any issuer, PR or a comment for my package.

1 Like

Sounds interesting. And I think I understood what you said. If I’m not mistaken, you basically created a DSL that supports a list-like chain of actions to execute. Those can be branching and are highly customizable too, but in simple terms the engine picks the next in line after it processed the current one and just runs like that.

I will check out your project after I wake up. I can think of both advantages and disadvantages, but it’s just an educated guess at this point. Definitely need to take a look first before I can give any valuable feedback.

1 Like

Yes, but there are 2 tiny mistakes:

  1. There is no processing of anything. The DSL generates structs and puts them into modules. It’s like you fill a class/module with a methods/functions that contains plain jsons/maps. The Scene part makes it automated witth a beautiful DSL syntax.

  2. Since there is no proceeding of data you decide when next item is taken (if any). You can fetch first item, wait a minute or hour and nothing happens until you request next one. This usually happen on some event like a button/dialog message click or shortcut.

All you do is to put your data into DSL, fetch them and deal with generated structs. You write the logic (like handling button click), but you use the package to fetch data. The pot file is automatically generated, so you can translate it without even a single line of code.

So how then translations work? By default dev_joy uses en language. You can pass an optional argument (to generated scene function) or an option in optional opts argument (session functions).

So in short for all scenes, for all parts, for all items, for all languages generate (in compile time) a function returning the translated item struct in run time (so it’s very fast when calling a function).

In my opinion it brillantly follows Elixir’s 10s less code rule as “this or other way” you have to put your content, so there is almost no “cost” in DSL part. However you gain translations, session and all fetching functions for free. So in cost of putting your content into DSL you gain a lot of features. Imagine that for example putting the content into JSON would give you session and translation support out-of-the-box. Using my package in the real code you can focus exactly on the thing you work on (i.e. deal with content and events) and not how to store or translate said content.


However the best is runtime speed. Think that iin compilation time macros would generate something like:

function get_part_item(scene, part, index, lang) {
  if(scene == …) {
    if(part == …) {
      if(index == …) {
        if(lang == …) {
          let translatedItem = {…};
          return translatedItem;;
        } else if(…) {
          …
        } …
      } else if(…) {
        …
      } …
    } else if(…) {
      …
    } …
  } else if(…) {
    …
  } …
}

The point is in Elixir we have something called pattern-matching and the generates does not generate one huge blob function, but many tiny functions having compile-time guards. Think that something like this would be possible in JavaScript:

/* skip if any condition fails */
function get_part_item(scene if scene == …, part if part == …, index if index == …, lang if lang == …) {
  let translatedItem = {…};
  return translatedItem;;
}

It’s like instead of writing calculator math logic you pre-define all inputs and expected output and return it automatically without any calculation.

function add(1, 1) { return 2; }
function add(1, 2) { return 3; }
…

Ah, just for sure … You are not limited to goto macro in a choice. You can pass an anonymous function there too! So basically you tell my package what and when to do something. It’s not assuming anything for you and in case the basic functionality is not enough you can extend it easily.

# simplest anonymous function
fn -> IO.puts("Hello world!") end
# short version of: fn -> SomeLogicModule.function_name() end
&SomeLogicModule.function_name/0
# same but calls a function in the current module instead
&function_name/0
1 Like

I tried to clone your project and run it, however cloning fails because of a file name. Here’s the command I ran: git clone https://gitlab.com/ramurix-software/visual-talk.git

And here’s the error message:

git clone https://gitlab.com/ramurix-software/visual-talk.git
Cloning into 'visual-talk'...
remote: Enumerating objects: 208, done.
remote: Counting objects: 100% (208/208), done.
remote: Compressing objects: 100% (195/195), done.
remote: Total 208 (delta 21), reused 175 (delta 6), pack-reused 0 (from 0)
Receiving objects: 100% (208/208), 27.23 MiB | 13.12 MiB/s, done.
Resolving deltas: 100% (21/21), done.
error: invalid path 'priv/talks/Keynote: Celebrating the 10 Years of Elixir | José Valim | ElixirConf EU 2022.txt'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/'

Basically some characters are not allowed in folder and file names under windows. Those are reserved for internal functionality. Characters like: < > : " / \ | ? *.

Which tells me you’re either on linux or macos. However you might want to rename files like that so they are cross-os compatible.

Now the funny part is git says “Clone succeeded”. However there are no files in the project folder at all. I assume git is a little misleading here. I could run this in the WSL, but I believe a long term fix would be to rename the file(s). Will you update the repo?

1 Like

Windows … :sweat_smile:

Yes, I’m using Linux and I simply don’t remember all weird OS-specific stuff like that. Thanks for your info. Will take a look at it soon.

Edit: This commit should fix the path problem on Windows:

1 Like

Yes, that definitely fixed it :slight_smile:

I checked out the project. Thank you for that opportunity to learn. I do have some feedback but also some questions. Hopefully my feedback helps you in any way.


README

The README was really helpful in getting the project to run. It was an okay onboarding process, It doesn’t necessarily have to be maximally smooth, however if you plan on getting more people to check it out and give feedback, you might want to make that process as smooth as possible. These are the things that README did well:

+ Includes name of the tool/project
+ Explains how to run and access the server
+ Gives directions where to find additional documentation

These are the things that the README currently does NOT do but what might make it smoother:

- Gives a short explanation what problem it solves
- Gives a short preview of how it's used, highlighting its benefits
- Mentions requirements like Elixir but also erlang
- Explains where to get those requirements
- Gives a step by step installation for various systems (this might
  not be necessary right now but wouldn't hurt in the future)

Installation and access

These are the things that went well when trying to install and access it:

+ Installation was smooth and responsive and gave a clear indication it
  finished
+ Running the server clearly mentions how to access the endpoint
+ The endpoint worked as expected

These are the things you might want to consider in the future:

- Maybe dockerizing your tool is an option that would make the
  installation even smoother

Presentation

This went well:

+ Straight forward on what the controls are
+ Basic navigation available to jump back or to the beginning
+ Showcases the integration of images, texts but also videos

These are the things that are currently not implemented but might improve the presentation of your tool:

- A small demo beneficial for your project:
    - "small":      Current demo is rather long and repeatedly highlights
                    the same functionality.
    - "beneficial": Current demo does not showcase strengths of your
                    project beyond showing that it works, but not how.
    - "your":       Current demo clashes with the talk about Elixir. This
                    talk is not relevant to your tool and steals the show.
- Whenever the upper content stays the same but the text below changes, the
  upper content gets reloaded and animated as well. It's probably just a
  matter of project polish, but maybe you want to implement that if it's
  missing.

Debug

Since I’m absolutely not familiar with the Elixir development process, the language itself but also how things are handles in your project, I will try to be language and project agnostic here.

What works well:

+ Events triggered by the user are reflected in the debug

This is currently not implemented but could improve it even more:

- Timestamps that explicitly show when something happened
- New lines between entries for better readability
- Simple but intuitive color coding used sparingly:
    - "simple":    More then 3 colors usually tend to be overwhelming.
                   I would go with 1 shade and 1-3 colors.
    - "intuitive": What pieces of information do you tend to look for
                   when reading debug? Those should probably receive
                   colors accordingly.
    - "sparingly": If you find yourself using 3 color only but
                   reusing them a lot across the debug, this might
                   indicate that you're trying to output too much or
                   the output format is not the best.

Functionality

In general I think this is an interesting and efficient approach. Especially it proves more than solid in terms of working as expected, especially in terms of efficiency. The final structure seems to only need to do a quick lookup.

Because I didn’t get to see an actual example of what you’d need to write to get a specific result, it’s difficult to give feedback on that. The reason this is important is that the format everything is written or created in should depend on your target user type.

If your user type is a developer you have a lot of flexibility. You can use whatever you want the developer is already used to seeing code. For a domain expert you need a clean structure that doesn’t make his brain explode with external things like “poor naming” and such.

I’m not a domain expert, but for instance in ChatMapper they call the speaker “Actor” and the listener is called “Conversant”. This does not seem to make sense to me. First of all, why is it limited to a 1:1? When somebody chants a spell in a group it’s usually multiple people. The name should reflect that it’s possible multiple people are saying something. Also the name “Actor” itself makes no sense in my books because it should simply reflect a name or a descriptive text about an entity communicating something. This so called “Actor” doesn’t actually do anything, especially if it’s just the wind making a sound. So I doubt writers would call it that. Again, I’m no domain expert, but because of that I’d need to ask an actual domain expert, aka writer, what I need to call it. Once I know I need to stick to it.

This same principle would apply to your project I’d say. It depends if your target user is only you, only developers, only writers, developers + writers or even developers + writers + readers.


All that said, these are the questions I have:

  • What would you say is your target user type?
  • Is there an isolated file I can look at to see what is necessary from the developer’s/writer’s perspective to create a simple story? Maybe an example story?

I would actually love to try to create a simple branching story in your tool just to get a feel for it. However it’s not easily possible out of the box (probably because I don’t know Elixir).

I hope my feedback was useful to you in any way. Checking out your project definitely helped me improve my understanding of my own project. Looking forward to an example story definition if you decide to make one available.

1 Like

Thanks, will add those to the documentation. For now I will reply here …

Allow to easily put the content into the application and work on it without using the content within logic code.

# no
my_dialog_content = "…"
draw_dialog(%{content: my_dialog_content, type: :dialog})
# yes
dialog = SomeModule.get_part_item(…)
draw_dialog(dialog)
# or with session support:
Session.next()
…
def handle_info(…) do
  draw_dialog(dialog)
end

Could you please explain this one? Did you mean some kind of video where I’m working with my package?

Usually since it’s Elixir package for Elixir projects we know that both Erlang and Elixir are reuired. Sometime we mention if we support x number of version back, but those dependencies are rather obvious as you cannot even download the package without them. For sure the demo project could have something more than the generated documentation, but it’s not really a priority.

We usually mention additional dependencies when we are working with a libraries or packages from other languages or with a tools and requires them installed. That’s why I’m helping you more on this forum as not everyone here even have idea about Elixir.

The “installation” for a package is exactly same for all Elixir environments. As long as Elixir supports specific OS and there are no bugs there should be no differences. On every OS you simply add the dependency to the mix.exs file as README.md explains.

Also there is no “the only one way” to install Erlang and Elixir. I may use asdf, but someone else even on same Linux distribution may use another versioning tool. I guess the environment setup is much better to a separate guide (on some kind of blog) and that’s most probably the biggest problem. I announced it once and not “advertising” it since then.

The answer would be very similar to previous 3 points … You don’t tell a HTML developer to install a browser and there is really no need to dockerize a browser for every HTML project. I would say that in Elixir ecosystem it’s very similar. However I could consider it again for a demo project …

Interesting … I was rather thinking about a “real-world” example rather than a short “lorem ipsum” example. The example covers 2 use cases:

  1. Game-like where you don’t care about the url in browser.
  2. Presentation-like where you want to share a url to the slide you’ve found interesting
    Since both cases uses same module it shows what’s the difference is between them.

Also in the example I focused on showing how content is separated. It’s separated so well that the content is imported from a separate file and adding new talks requires a very small amount of work (assuming you already have the content to share).

However I get what you mean here and I fully agree with that. I should create a demo that works like a first “click here to …” hints when firstly launching some app or page. In fact that would be much easier to create and translate. That’s a brilliant idea! I would definitely consider that, but I would not work on it immediately.

Not sure what do you mean here … I pay attention to avoid such case. For sure the image/video may change the size, because sometimes there is more or less text, but this is expected. I’ve implemented the layout to fix the content. However you can use a fullscreen icon in the upper right corner, so the layout would change. The image/video should cover all space and the dialog text is visible only when hovering over it or moving a mouse.

There are also some cases where slides are really similar. For example some points in the list becomes green, but the text does not change. Since it’s a different image it gets a new animation and that’s intended.

If you have noticed other case please let me know and how to reproduce this issue.

That’s the best! You don’t really need that! There are 2 cases:

  1. Fetching data directly - the data is returned almost immediately. I don’t think it could be optimized even more except maybe some language or VM optimizations.
# let's define some anonymous function
# to call each line right after another one in one call
func = fn ->
  IO.inspect(Time.utc_now())
  VisualTalk.Scenes.Main.get_part_item();
  IO.inspect(Time.utc_now())
  :ok
end

# here's the call:
func.()
~T[19:47:54.671094]
~T[19:47:54.671127]

As you can see the difference is around 33µs and everyone can check that without any problem …

  1. Fetching data from Session
func = fn ->
  IO.inspect(Time.utc_now())
  DevJoy.Session.get({VisualTalk.Scenes.Main, :main, 1})
  # receive the message send to current process
  # and call the function that was send in message
  receive do {:dev_joy, :delayed_job, delayed_func} -> delayed_func.() end
  IO.inspect(Time.utc_now())
  :ok
end

func.()
~T[19:56:57.721288]
~T[19:56:57.722333]

Ok, here we need a bit more time, because working with the process messages takes a bit more. In this case it’s 1045µs, but look this means that the time needed for the fetching data is around 3% (3.1578947368421053 to be precise :wink:) and we rather don’t care worry how much time the messages need to be received as in the project we set the time option to 250ms for an animation (which is rather good and average time for animation) which is almost 2.4x than we need (2.3923444976076556 times more) to send a message.

As said all API is mostly limited only by the language itself. There is almost no delay, and definitely not a noticeable one, for fetching data. Since you control when you call and when you receive data you can debug time and that’s why I do not need to include it. Also what you should care the most is the time needed to render - this cover the Phoenix web framework itself.

Edit: When writing response for next point I get that you mean the debug messages. That can be configured in the format option (see reply for next point).

Do you mean logs in the console? This can be easily configured! It rather up to you and the demo project uses the defaults.

# see config/config.exs for default and config/dev.exs for dev environment:
# you can add use 1, 2 or 100 newlines if you wish
# it's not really related to the package or demo project
config :logger, :console, format: "[$level] $message\n"

That’s said it’s only about requests which phoenix package handles. The dev_joy package does not generate much logs:

  1. It logs debug information if the application does not implements any character module, so it falls back to some generated data with a helpful log message.
  2. It logs debug information if the condition DSL fails in the passed function i.e. when it does not return any matching choice to follow.
  3. Most of the rest code is handled by tests simply to verify if the package is logging messages when needed

Oh, looks like the Logger does not support multiple colours. It supports only one for each type of log, see: Logger.Formatter.new/1 documentation for more information. However it’s possible to write your own formatter. Luckily looks like there is one that you can use:

My target user is primarily the Elixir developer that wants to create his first (and hopefully every next) presentation or simple game using my package. However I’m open to changes in documentation if something is unclear. Please keep in mind I’m not a native English speaker. I do my best, but it would never be “the best”.

That’s why I decided to go with a simple DSL. As you can see there is no much code. Oh, there is a lot of code in a demo project as it imports text from a text file. Yeah, I definitely need a simpler demo project …

However I was thinking that the naming for the DSL is good. I tried to stick to the most common words people use in Visual Novel-like games (that’s why it’s called Visual Talk btw.) …

Here is the DSL naming for the “domain experts”:


For more complex scenarios you have also accumulate DSL. The rest things in DevJoy.Scene module if for developer.

Unfortunately no and now I see the biggest problem in my package. However all you (as a writer/domain expert) have to do is to preapre a scene module like:

defmodule MyApp.SceneName do
  use DevJoy.Scene

  # …
end

and fill it only with the DSL calls. If you do not generate content from other source you don’t need to write any extra code here.

The biggest “pain” is configurability here simply not all cases can be solved with a simple goto or go_back features and sometimes you would simply need to tell the DSL to call some function.

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :condition do
    condition :natalie, &HeyDev.please_choose_the_path_depending_on_how_much_natalie_likes_me/1 do
      choice :choiceA, goto(:she_hates_me)
      choice :choiceB, goto(:she_ignores_me)
      choice :choiceB, goto(:she_is_interested)
      choice :choiceB, goto(:she_love_me)
    end
  end

  part :she_hates_me do
    # …
  end

  part :she_ignores_me do
    # …
  end

  part :she_is_interested do
    # …
  end

  part :she_love_me do
    # …
  end
end

In that case you would have a very difficult talk to the domain expert … You would have to explain that such functions names are too long for you and you would prefer to use a comment like:

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :condition do
    # Please choose the path depending on how much natalie likes me
    condition :natalie, &HeyDev.do_something_about_that/1 do
      choice :choiceA, goto(:she_hates_me)
      choice :choiceB, goto(:she_ignores_me)
      choice :choiceB, goto(:she_is_interested)
      choice :choiceB, goto(:she_love_me)
    end
  end

  part :she_hates_me do
    # …
  end

  part :she_ignores_me do
    # …
  end

  part :she_is_interested do
    # …
  end

  part :she_love_me do
    # …
  end
end

Later it would be only more difficult … How would have to find out how to have more respect in your team so that people would create:

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :condition do
    # Please choose the path depending on how much natalie likes me
    condition :natalie, &DearBoos.could_you_help_here_pretty_please?/1 do
      choice :choiceA, goto(:she_hates_me)
      choice :choiceB, goto(:she_ignores_me)
      choice :choiceB, goto(:she_is_interested)
      choice :choiceB, goto(:she_love_me)
    end
  end

  part :she_hates_me do
    # …
  end

  part :she_ignores_me do
    # …
  end

  part :she_is_interested do
    # …
  end

  part :she_love_me do
    # …
  end
end

:joy:

Oh, the point is I did not created any tool to visualize any kind of content you put into the DSL (especially having in mind how configurable it is). You can start with writing some content in DSL. Then you can start with fetching the data. For start you don’t really need session, do you?

The point is you can start an Elixir shell within your project. The shell is called iex and to start it within your project you add -S mix argument:

$ iex -S mix
iex> IO.puts "Hello world!"
Hello world!
:ok

The :ok at the end is a return of the function. For DSL it would return the structs. The shell is printing IO messages (see example above) and logger messages. You can use that to preview the generated data:

$ cat lib/my_scene.ex
defmodule MyScene do
  use DevJoy.Scene

  part :main do
    dialog :john_doe, "Dialog content"
  end
end
$ iex -S mix
iex> MyScene.get_part_item(:main, 1)
%DevJoy.Scene.Dialog{
  character: %DevJoy.Character{
    animation: nil,
    expression: nil,
    full_name: "John Doe",
    id: :john_doe,
    info: nil,
    portrait: nil,
    sprite: nil,
    data: [],
    position: :left
  },
  content: "Dialog content",
  data: [],
  type: :normal
}

Start writing dialogs, add questions and gotos, then create a parts you have just referenced and keep that until the story ends.

Keep in mind that while you have to tell what items should be accumulated, the choice (provided by dev_joy package) is always accumulated, so you would not see it in get_part_item results. However this would not happen if for some reason you would decide to create your own choice, let’s call it my_choice DSL. For accumulated items you simply need to change item to acc in function name i.e. get_part_acc. Keep in mind that depending on how you configure the accumulator and what content you place some items may not have anything accumulated.

Yes, it was more than helpful. I understand that I made a good job with the package itself and what kind of demos/guides I have missed.

I would, but content creating right now is not my #1 priority. I’m working on another package at the same time and yes, it’s also based on metaprogramming.


I have some basic idea for a “tutorial” story, but it’s really more like a tutorial rather than a content. If you think you have some story for a demo project I would be more than happy to work on it as long as you would give me all scene modules that I should use in the code.

Maybe we can even create our own HTML game? I’m fine with that, but you would have to give me assets (like background images, music etc.) as I’m not really a content creator. If we would pair programming I can teach you about Elixir if you are interested. If that would be the case I can make such project a priority. :+1:

1 Like

I see, makes sense.

For the README part I think a simple code example would do that shows a simple “Hello world!” story. Maybe you need to create 1 file only, maybe it’s 2-3, but those would show what needs to be done to get something up and running. If you feel a little fancier you could add an image of two what the result looks like. And if it makes sense and you’re feeling extra luxurious it could be a gif even.

This part is just to give a first impression what the user of this project would need to write to get a result. It’s basically a “look how cool, quick and stable this is” all inside the README so the user can recreate it easily and check it out.

If it’s straight forward for Elixir anyway, there’s probably no reason to bother including that. If your target audience is Elixir developers anyway, then especially :slight_smile:
However if the idea is to provide a tool to people outside the Elixir/developer community, I think the demo should have some basic instructions.

But as you mentioned, not priority per se. Just thought I’ll mention it :slight_smile:

Yes, something like that. It also doesn’t really have any priority per se, but it would onboard people quicker. Especially if you’d explain how your tool works while the user is in your tool! That’s kind of a statement of how well it works.

I see what you mean. So when not in full screen, the dialogue text length can change. Sometimes this means that the dialogue takes up 1 line. If the next dialogue text takes up 2 lines, the image above will rescale creating a jumpy effect. It looks better in full screen because it doesn’t do that then.

In those cases you might want to check if it makes sense to introduce a different kind of transition. Currently the transition is a (zoom out + fade out) for old slide followed by (fade in + zoom in) for new slide. This makes it more difficult to spot the difference because everything is moving around.

Because of this you might want to experiment with a different transition for such cases (if that’s possible in your tool). The transition might look like this: (fade out for old slide WHILE fade in for new slide). Of course this would only work for similar images, but I believe this would have a magical effect in those cases. Give it a try :slight_smile:

No, just the one I described above. The rest looks as expected I’d say.

I fully agree that the efficiency is definitely not the problem here. That’s not the reason I’d include it. The only reason I’d do it is to be able to backtrack on what happened when. For instance. When writing and then playing a story, I want to find a mistake I made. For this I consult the debug console. In order to be able to see what happens, I explicitly wait a couple of seconds between my actions (clicks for instance). This way I can isolate messages in the console because I know exactly what time stamp to look for. I wouldn’t bother with efficiency output at all to be honest. Or if at all, I’d put it on the very right side - cool to know but should never be an issue.

Yes, I meant the logs in the console :slight_smile: I think it’s a great place to see what happens in the tool-specific context. For instance when loading the next slide (sorry for me butchering the naming) the console log could reflect that for easier debugging. Just an example.

I think after making sure the underlying technologies work properly I’d generate tool-specific things. When you have a basically non-branching story, that might be a trivial thing to ask for. But as soon as you have to work with a slightly complex story that branches, has logic in it and react differently depending on what you do, you might want that feature.

Absolutely. If you want to use color coding, you should be able to write that. Consoles usually support custom coloring (if you’re willing to fight your way through how to color things). But once you have a helper function (I assume that’d be a function in Elixir) you should be good to go.

That said you really might not need any explanation on how to install anything in the README :smiley:

That makes a lot of sense.

I assume having a short example would make it easy to include it in the README too

I think if you have Elixir devs as your target user type you might not necessarily need an elaborate vocabulary for domain experts. As long as you can easily write stories in it it already does a good job.

So if you don’t plan on expanding the target user types then I wouldn’t introduce domain terms. However if you actually think about expanding, I would suggest you look into that topic. Currently developers in other languages are more or less excluded and so are writers. But that might be a feature :slight_smile:

For the context:
The tool I’m making uses a clear structure using words/terms writers use: Story > Arc > Scene > Logic Step. This way you can now clearly separate concerns. A writer can (and SHOULD) take care of the Story, Arcs, Scenes. Those together are a coherent unit and therefore should make sense story wise. Meanwhile a developer can take care of the Logic Steps (which as far as I know does not exist as a term in the writer’s vocabulary). But a developer would understand. On the other hand a dev usually has no clue what an Arc is. At least not exactly. Let’s say now a professional writer tells me “Hey, when writers talk about arcs, we assume that those have X, Y, Z. Currently the Arc in your tool lacks those. Can we please have that?”. This way it’s very easy to expand functionality because we know exactly where this functionality should go and how it would expand an Arc. Additionally it allows both the writer and the developer to work together on the seemingly very same part.

But that’s very project-specific. My project is highly visual. No code for now, maybe only to import existing stories from known formats.

In my opinion the best approach would be a clear separation of responsibilities. That would mean your story definition is as isolated as possible from the actual internal code. The story of course can use code, but it should be possible to simply replace 1-2 files and it would produce a completely different story. This way you decouple responsibilities which makes testing, developing and exchanging elements super easy. For instance “replacing” a story with another painlessly.

By the way it would be interesting to see that in an example.

I think this is problematic for certain scenarios. If you want to make a simple story without any complex logic, this would work like a charm. As soon as you want to create something complicated it gets really messy. But that’s definitely the very same problem with other script based approaches as well. I don’t think it’s necessarily an issue in your project.

Basically the more characters you use to describe the same functionality the more space you give the user to make mistakes. Additionally if the syntax decreases readability it’s obviously worse. But your syntax is actually pretty clear and structured I believe.

Yeah… friction. The reason a domain expert might need those things is because working on a large project introduces different problems. For instance quickly finding the right place within a story becomes critical. If you waste 1 minute on every search, you’ll go crazy. Because of that writers might want to have a flexible structure for that.

But this is not an issue for you - Elixir devs won’t complain since they know the limitations and good practices within the language.

That did not work for me at all. So you’d basically write your story definitions in the terminal then?

It would be easier it I knew what file contains the story definitions. This way it’s possible to reverse engineer things. But that should be a small example story as I mentioned before. I will look into it a little bit later though. Sounds interesting.

:slight_smile:

If you’re worried about not being able to make something big, don’t be. As an example story I’d literally just create a simple 3-5 node story. Nothing fancy. Doesn’t even need to make “sense” story wise. Only showcase the project a little.

Yes, I’d just go with something like that really :slight_smile:

Sadly no short story here. I do have a good story idea but it’s not a short one.

I wouldn’t mind working on something like that honestly as a side project but don’t forget the actual story needs to be written too. Might need +1 person :joy:

That sounds like a plan actually, but not much time for that this year. I’m currently getting into c# (new job requires it). It’s not really complex, but has some quirks I need to get used to. After that I wouldn’t mind.

I can actually show you my project as well some time. Currently I’m finishing my second mile stone, then the base is 75% finished.

1 Like

This is what README.md is doing now. The first tab describes adding the dependency, the second tab gives a hello world example, 3rd is for fetchin data, 4th for session, 5th shows a plain content rendering, 6th describes translating project and the last tab is linking to the JavaScript documentation.

The rest is about how to use different DSL, so instead of dialog you type asset and provide the expected arguments as in the documentation. You have a problem because you have no idea about Elixir ecosystem. Just think that in a language you know someone refers a most common web framework.

Showing a part of LiveView code is enough for every developer who have created even a simple pet project using it. I also mention that the package does not limits itself to a specific framework as you can use hologram and scenic respectively. The first one have just a different template syntax. The second one is for building embedded graphical applications.

This or other way you have to “render” the dialog, so before you start working on everything else you would need to know how to render data in simplest project which usually is a web app. I agree this is really not helpful for people who don’t know Phoenix or LiveView, but there is no sense in describing how those are working especially if my package does not require them.

As said while I’m more than ok to improve documentation, the package would still be a package. It’s not a library that a C program could easily use or a standalone tool. It’s hard to use Rust crates if you have no idea about Rust at all. That’s said this is not for a senior developers - even people who previously only created a single pet project should be good to go.

Oh, on your screenshot there are much more interesting things … I have even no idea what gone wrong there. :sweat_smile:


Border, shadow, colors and contrasts are for some reason completely wrong … How did that happen?

That would overcomplicate the implementation. Since everything is generated I would need to explicitly tell my code which slides are similar. That’s just a demo app - it’s not supposed to be the best in styles as that’s completely not related to the features my package provides. For sure Visual Talk is just a simple web application (not a tool) and it’s used to show game/presentation - like UI (menu, slides, dialogs etc.). The core package is Dev Joy and it provides various quality of life features making your logic much simpler. It does not affects how you render it. Your app may have 50 animations and be used in 10 different UI frameworks and Dev Joy does not take care about that.

If you mean a typo in the text then you should not even take care about console. There are many ways to see that. For example if in your app you create a routes like:

get /scenes/:scene_name/:part_name/:item_index

then simply following url and the way you organize the routes you can tell which item is bad. Not even telling that you use a content in DSL, so you can easily search for specific text with typo or just call a grep (especially if it’s about translations).

However if you mean a mistake in the order of items then you have nothing to worry about as the items are indexed for each part from top to bottom. If you need some debug you can write it on your own and you don’t really need to use a package to do that. Look that if you use a Session way then all your items are in handle_info function, so you can debug all of the items at once!

defmodule MyApp.PageLive do
  # …

  def handle_info({:dev_joy, :item_changed, item}, socket) do
    # replace this comment with a debug you like
    # you can print any message with any colours you like
    {:noreply, assign(socket, item: item)}
  end
end

You may prefer to disable the logger or configure the phoenix framework to not log messages (never tried both, but should be possible) and debug everything on yourself in app you would create. Phoenix only debugs when you are navigating and not when dealing with a package. You may find many other information much more helpful. As said it’s configurable, so everyone can do it on your own. This demo app is not created for logging purposes, but as said just to show a simple game/presentation-like UI using my package. It’s not a unique tool to work with any content you put into DSL.

That’s perfect description of app-specific needs. Nobody would guess when you need to debug something and when not. For example in javaScript you put console.log() calls when you have to debug something and not everywhere for each method call. Look that one of the primary purposes is a runtime speed. That would be against the purpose of package to debug everything with some complex logic when it’s worth and when not.

However you gave me an idea … When I would go back to my package for low-priority issues then I plan to implement a debug version of Notifier API. What’s the point here? Instead of actually fetching the data (in session) all your calls ends on debug messages. That would allow to work on the content and debug every Session.next() call, so instead of rendering there would be logging. I would need to think how to support condition and question DSL there …

You didn’t get the point. You focus on features that are not even part of the package or demo app. Those logs are just like in any other app/service that you can see in .log files. I guess there is also a project to write logs to file. Coloring them does not brings anything useful for the demo app as nobody is looking there. If you are working only on story then you may not even use any web framework and therefore there would be no talk about any logs. Those are just some simple request informations the framework gives out-of-the-box and it’s not relevant in any way.

There is also a short example for importing data from CSV in README.md file. :wink:

So the story is in DSL. The Arc (different paths) are parts, goto and related features (also in DSL). By Scene you most probably mean asset, right? My packages divides it into 2 parts:

  1. App/Game content - DSL and external files referenced in assets
  2. Logic - fetching and drawing

Oh, maybe my package lacks events i.e. a way to tell developer to do something in the logic, for example:

# or any other player statuses like health, mana etc.
event :character_id, :change_opinion_by, 2
# or
event :character_id, :change_opinion_by, -3

The Dev does not need to have a clue what the Arc is. There are just some structs that the dev have to process and visualise (draw or render template).

Then you can create your own DSL very easy. I even have an idea how to improve it later.

Depending on what you change even 1 DSL call may change everything. Think that some question is added that could lead player straight to bad ending. :smiling_imp:

It isn’t! Just think about it … In the condition you tell the developer that you expect one of few cases to happen and you just need to ask about good module and function names.

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :condition do
    # if player have small hp (20 or less)
    # go with first choice; otherwise second
    condition :john_doe, &SomeModuleName.write_do_not_even_see/1 do
      choice :choiceA, goto(:partA)
      choice :choiceB, goto(:partB)
    end
  end
end

So depending on logic if for example a player have low hp the function returns :choiceA and then goto(:partA) is activated!

The dev does not care what story is about. It only needs to change the comments into SomeModuleName.write_do_not_even_see function implementation. All the logic would be in separate module only for dev. I said “pain” for fun here.

It’s also possible, but you better do that in a separate file. What I mean is that you can call iex -S mix to access console where you can test the content by fetching it. Nothing really fancy here.

Oh, that’s simple. Best would be to create a separate directory for all scene modules, so you know exactly where the content is and you don’t have to filter the files with the app logic. You simply create a file with .ex extension (Elixir source code file). While common is to use snake_case for file name and same name in CamelCase format for module name you have a freedom calling your files and modules as you wish.

$ cat /path/to/my/project/lib/scenes/my-MOM-died.ex
defmodule ItWasActuallyMyDad do
  # …
end

Inside each module you have to put use DevJoy.Scene as a first line and then you can start adding your DSL calls.

Yeah, I would do all the logic and need from you (or said other person) a story and assets - that’s all. You can message me anytime you want about that.

1 Like

Not sure what you mean by “tab”.

Yes and because of this it’s difficult to say what exactly is missing from the package README. But the fact I managed to get it running relatively easily as a developer who doesn’t do anything in Elixir actually shows it’s doing its job :slight_smile:

Makes sense.

That’s a good question indeed. Maybe differences between OS as well? I wouldn’t expect those to happen though. Or maybe I missed one step of the installation?

Technically you could make a comparison between images to check what percentage of pixels is identical. If let’s say 80-90 % is identical you could do a “fade out fade in” transition. However if you had a case where you explicitly need a different transition - yes, you’d need a way to explicitly tell what transition should happen.

As this is beyond the scope of your project anyway, no need to add this. It’s just an idea :slight_smile:

I’m talking about debugging the logic flows. Let’s say you use acc instead of the item function. I assume things can get a little more complicated then. Typos are irrelevant because those are easy to fix. Search the text for the typo and change it.

I see. So technically it would also be possible to have a navigation within the rendered project. As in freely jumping to a selected slide. Maybe that’s an interesting feature.

That makes sense if somebody decided to debug, they can.

You’re right, you can’t know what another person wants to debug. But you also don’t have to. You can identify what parts are interesting by default because they’re so central or important. Those you might want to “debug” by default. I mean following your logic you would need to remove all currently existing debugs altogether.

What I mean though is streamlining those might be a good approach. In my tool the user should not be able to create bugs, so there are no classic debug at all. The only debugs are the ones containing TODOs. This way I can eliminate them because I want the console to be clean. The process is stream lined in like that. It could be stream lined here as well, then explained in the README. Just an idea.

That’s a nice idea. Basically the package then only says what it would do instead of actually doing it.

I mean then the question is what’s the exact goal of this demo. If it’s about writing a story, showing how it works in general but not attracting any potential user, that is fair. Then that’s the way it should be. It’s doing exactly as it should and whoever decides to work with it can add these features themselves.

But if the goal is to have somebody write a story and work with that package, then I think the debug is a good tool that is already provided for free but not used at all. And I got the vibe your goal is that this is more than just the demo. Maybe I’m off here but we also didn’t fully define that :slight_smile:

I can only see this version:

Okay, here for clarification. Maybe it’s easier to follow this structure in reversed order as the higher we get the more abstract it gets.

Logic Step - An actual command that would be something like a set_variable, goto in your package
Scene - Contains a collection of Logic Steps that are connected to each other and groups them into a scene that plays out for the viewer.
Arc - Contains a collection of Scenes that are connected to each other and groups them into an arc. This is basically a bigger story unit.
Story - Contains a collection of Arcs that are connected to each other and groups them into a final unit, the story. This is the complete representation of what the writer writes.

I let ChatGPT write an example for this structure and it nailed it:

Story: The Matrix
→ the full narrative including Neo’s awakening, training, betrayal, and final
  transformation.

Arc: Neo's Awakening
→ from Thomas Anderson living as a hacker to realizing the Matrix exists and
  meeting Morpheus.

Scene: Red Pill or Blue Pill
→ the conversation where Morpheus offers Neo the choice to learn the truth.

Logic Steps:
    show_text("This is your last chance.")
    option("Take the red pill") → next scene
    option("Take the blue pill") → end
    goto(scene_id_based_on_choice)

I mean for your package the developer doesn’t need that. It’s not a project for collaboration and also targeted at Elixir developers only anyway.

However I want to give you a perspective on this approach because I believe it would help you create a widely-accepted package if you choose to go that way. I believe if the developer does not know what an arc (or whatever word you identified as domain-specific and accepted) is, it creates some problems.

The reality is that people who write stories came up with some concepts on how to do it. Those concepts received names and because of that a good writer can evaluate the “implementation” of those concepts within a story. He would have a clear understanding how to check it.

When a developer creates a tool that is supposed to help writers write stories it would be a sane idea to use the same words and hopefully understanding them too eventually. This way the developer can try to simulate the “reality” of this domain. Bugs are then something that differs from how writers deal with a story in the real world - easy to spot.

Concepts have a clear boundary, meaning writers can help you structure your code without even knowing it. Also writers have some checks they do on the story manually. But by simulating all these concepts it’s possible to give them tools to do those checks. Also it’d make it easy to see why something in the project is not working (it just doesn’t align with reality). A writer can tell you that before you start implementing even.

When you strive away from writers’ understanding on what is what you create the problem where the developer has to define something on his own. Potentially missing the expertise the writers bring. The developer might even try to solve problems that are not problems in a writer’s world. Additionally developer’s support is very weak this way. The developer is the only one who lives in this definition and nobody can help out unless they dived into this definition as well. There’s a lot of friction here.

You absolutely can create your own definitions, but to “sell” those to anybody you need to provide an argument in favor of your definition. This argument however should include what the writer’s definition is not that great and why yours is better. It should be a smooth transition that looks something like this:

[writer's definition] --[, but this one is even better]--> [developer's definition]
    provides arguments and examples --^  ^-- is ready to counter writer's arguments

And this is super difficult to do when you’re not a domain expert yourself. It’s like trying to explain to a mathematician why he doesn’t need the square root of -1 without understanding why he absolutely needs it.

Yes, I think that’s normal that you can totally skew the story with a changed call like that. But what I meant is there must be some level of modularity where you can replace story files to “load” a different story altogether. Otherwise your story is highly coupled to the project.

That makes sense, but who writes the comments?

This command does not work for me at all for some reason. Might need to check it out later.

It’s probably simple and straight forward to somebody with Elixir background, but to me this belongs in the README file with existing examples. This way I can’t screw up mid-way.

Gotcha :slight_smile:

1 Like

Oh, Elixir’s documentation supports some additional syntax for markdown that enables tabs. It’s about comments, so you do not notice it on the GitLab, see:
https://hexdocs.pm/dev_joy/2.0.0/introduction.html
The default page (Introduction) is said README.md file with extra syntax support. That should be much more clear for you. Generally if you see an interesting package on hex.pm then firstly take a look at the documentation.

Did you call mix setup for Visual Talk (demo app)? It’s not OS-related as it’s more about SASS or CSS problem …

First you fetch the item and then acc. When reaching the scene module directly you can simply inspect function return. In Session case (when message is send) you can do same when you handle the message. You can pretty print any Elixir term using IO.inspect(stuff_to_inspect).

Yes, that’s possible. The Visual Talk demo shows you 2 examples - one is with a single url (like in a typical game) and the second one called push navigation changes url every time item is changed. You can copy said url and paste in different browser and you would see same slide.

The example I made is not by coincidence. There are 2 files:
https://gitlab.com/ramurix-software/visual-talk/-/blob/main/lib/visual_talk/live/push_navigation.ex?ref_type=heads
https://gitlab.com/ramurix-software/visual-talk/-/blob/main/lib/visual_talk/live/single.ex?ref_type=heads

You should understand what they do after what I said and their names. Now since both do same thing (just one have extra path-related work) I have moved the shared part to another file:
https://gitlab.com/ramurix-software/visual-talk/-/blob/main/lib/visual_talk/live/live.ex?ref_type=heads

Each module simply calls use VisualTalk.Live and this way the shared code is added to each module. Now you can compare how simple is single url example and how much work you need to do to add paths support. Of course each app would have it’s own routing, so there would always be some differences.

Now if you would go back to the shared code you would see that 99% of it is handle_* functions. This means that you don’t have to write many code for initializing something. Just wait for events (from the Session in handle_info or from your app like button click in handle_event).

Yes, because they are not related to the package. Think that you write a shell script for handling of some JSON API responses. Now someone asks you to improve the curl or wget (or Windows/macOS alternative to them) verbose logs when all they do is to just log the requests/response headers and not any call to the script.

Again let’s simplify it for an Calculator example. We don’t want to debug each math call, but only those we have problem with. That’s the difference between debugging in a package/library and the app. The package returns simple and generated data. There is not much things to debug there. It’s like when user adds 1 to 1 return 2 - the result is obvious because it’s generated.

It’s not a tool you would work with as long as you are not interested in adding more talks. Again it’s not some kind of game engine or ready logic to copy-paste. It’s an example showing that when you create logic you can focus on handling the events instead of how to organise and work with the content (story).

This is a README.md for a demo app. :sweat_smile:

Here is a link for a package:

and here is a link for a demo project that uses the package to show game/presentation-like UI:

The package have all important documentation and the demo app shows just how to start it.

Oh, we have misunderstanding. The goto is not really a logic in my package. Yeah, it does the logic but it allows the writer to give a decisions for user. For example depending on how the user would answer the question the story gives n number of possibilities like continue story or bad ending.

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :main do
    menu "Awesome game menu" do
      choice "Start your dream game", goto(:start_game)
      choice "Game credits", goto(:credits)
    end
  end

  part :start_game do
    question :john_doe, "Would you like to start the game?" do
      choice "No", goto(:main)
      choice "Yes", continue()
    end

    dialog :john_doe, "You would see this dialog box if you decide to start the game"
  end

  part :credits do
    dialog :john_doe, "Thanks to dear devil that gave me the inspiration! 😈"
  end
end

Here the logic is:

  1. Fetch 1st item from :main part
  2. Display menu
  3. If choice is to start game display a question
  4. Display the dialog

It’s about fetching and dealing with generated structs. Here the developer does not care which path we go as long as all returns are supported.

There is no such thing here. Simply “start the game” and deal with player stats (condition DSL) or user anwers (question DSL) - the rest like a dialog is simply rendered on screen and all of the “logic” here is to handle the click event on dialog to fetch next session item (if you use session you don’t have to care about indexes).

Oh, in my case a content simply references other contents. It’s up to you how you organise it in scene modules and their parts. For example a you can put game menu and credits in one scene and let the other one to contain everything else or you can have all content in one module. How you divide it is up to you. If you think one module becomes too long you can divide it in 2 or more. The only recommendation here would be to put them in same directory. So when browsing source code it’s easy to follow which file you are interested in.

In my case everything is more … atomic? One module can have many parts, one parts may have many items. In the logic you don’t work on some “cryptic” Story/Arc or other naming, but on simple structures like %Dialog{content: "some text goes here", …}.

The content writer should have only basic knowledge of Elixir as same as in other projects you know markdown or JSON syntax.

Think that you are writing an app to convert a book into markdown. 99% of time you only need to recognize the text and the rest is put it up together in a markdown. The dev works on structs, player stats and rendering of all the data. It’s not important if we are in the middle of story or we are on the right way for early bad ending. The developer only have (in very, very short) to diplsay next dialog struct in user friendly way.

The only one part where developer needs to be aware of something is a game state. See my example with condition DSL in previous reply. The developer needs to know which option to enter depending on game state. Again, developer does not care what’s going on after the choice is made as long as the logic does not have bug. In question DSL it’s even simpler as the user would choose which option to go (not the dev), so no matter what user chooses the dev only needs to “render next dialog” (again in very short).

Oh, in this case the content writer does not need to write any logic here. Just give options and let user/dev follow them and let dev to implement all logic.

You say Arc, I say Chapter (like in books). There are many alternative ways for a naming things. My package does not care how you name it.

defmodule MyApp.Arcs.One.NeoAwakening do
   use DevJoy.Scene

  part :introduction_to_chapter do
    # …
  end

  part :beginning do
    # …
  end

  part :cumlination do
    # …
  end

  part :ending do
    # …
  end

  part :after_story do
    # …
  end
end

# or

defmodule MyApp.Arcs.One.NeoAwakening.Ending do
   use DevJoy.Scene

  part :main do
    # the Neo's decision content goes here …
  end

  part :good_one do
    dialog :john_doe, "Neo survives"
  end

  part :bad_one do
    dialog :john_doe, "Neo dies"
  end
end

It’s up to you how you call the modules and parts.

No, there are just many naming ways and I choose the one that’s know by most people (not just content creators). With my package you can simply create you own story and add assets. You don’t have to be expert in Elixir for a simple story, right? This is easy to start and easy to master way for everyone (new developers, seniors or those that just want to create their own small game).

The point is the package is flexible and the language itself is flexible. As said you can use any naming in the module and part you want. There is no the only one good way to name something. There are many synonyms that changes in context (for example book vs game), but all of them have the same parts (just named differently), so they golden way is flexibility and generic naming here … For example much more people would understand what chapter is and less people would know arc naming.

Simply goto a good or bad ending. The goto supports also additional modules, so as said you have the flexibility here. You say in which case the story changes and on what condition.

Simply a content writer that is not working on the logic can leave a few useful comments for dev explaining in which cases a condition DSL should follow one or other choice. Let’s see on example:

defmodule MyApp.SceneName do
  use DevJoy.Scene

  part :condition do
    # if player have small hp (20 or less)
    # go with first choice; otherwise second
    condition :john_doe, &SomeModuleName.write_do_not_even_see/1 do
      choice :choiceA, goto(:partA)
      choice :choiceB, goto(:partB)
    end
  end
end

# Developer is creating a new file with module called SomeModuleName
# and a function called write_do_not_even_see
defmodule SomeModuleName do
  # takes damage from specified character
  def write_do_not_even_see(character) do
    PlayerState.take_damage_from(character)

    if PlayerState.get(:hp) <= 20 do
      :choiceA
    else
      :choiceB
    end
  end
end

Look that the content write does not have idea how to change and fetch player hp and the dev have no idea what the story is about. He only know that in some storage the player hp needs to be stored and depending on the current player hp the specific choice is returned.

As said in question case it’s even much simpler as the user have to click a button and the dev handling click would do the goto instruction the writer have prepared. This way you can simply work on the app logic (not how to store and fetch dialog etc.) and ask someone to write a story for you.

You have to enter the project directory first. iex -S mix would fail if you are not in a project root directory as simply there is no project to load. If you enter a normal shell (just iex) the project would not be loaded and then the shell will let you know that the modules are not available.

That clarified so much :smiley:

Yes, mix setup it was.

Yes, that clarifies it, thanks! :slight_smile:

Okay, I understand what you mean, but I disagree on the goto not being logic.

However this helped me improve an existing feature description for my tool. In my project since there is a clear separation of responsibilities the writer and developer are only doing their part. The writer can’t edit logic and the developer cannot edit the story. Unless of course you add their roles specifically. But let’s say the writer has no developer rights. Because of that he might need to communicate something to the developer.

So the writer would create a scene but not necessarily say what other scenes it connects to because that’s logic. However he might still want to prototype this, previously I had TODOs the writer can leave behind, explaining in plain text what logic should be added at this point. The developer would see those as something that needs his attention and implement the logic. But this would create dependency I didn’t like.

The new idea is that the writer can actually add a prototyping logic step that allows to connect to other scenes. However those aren’t proper logic and would alert the developer he needs to take care of this as a TODO. Until those are done the story is not functional or in other words can’t be exported and used in a game engine.

What this tells me is that if you have a writer, he can’t structure his own story. He either relies on the developer to do that for him or he has to learn Elixir. Because of that when the story grows the writer might request a change in structure and the developer, as developers do with refactorings, will answer “but this will cost us 2 months”.

This would work for small stories with a writer or bigger stories without a writer. But in both cases I think this doesn’t scale too well.

Maybe I missed something.

I see this as a huge problem. I doubt there are many domain experts who want a language-specific way to write their story. That’s why yarn spinner is better for them than yaml and yaml better than json. It’s more intuitive and doesn’t block the creative process that much.

Additionally depending on a developer to write something for them might feel like they are being babysit by the developer. They should be able to structure their part of the responsibility from A to Z.

It might be working out if the developer has a structured approach and can actually code in a maintainable and scalable way (most can’t) while adhering to writer’s expectations. This includes prestructuring the project accordingly too. However this is also a scalability issue I believe.

So both work on the very same file then in order to communicate?

If they do it opens up a whole new can of worms. Now the writer not only needs to understand Elixir but also git and github or similar. Even crazier. He now needs to be able to solve git conflicts. Those will be inevitable.

This would scare a writer away easily, which would eliminate like 99% of good writers, they simply don’t care about these topics. Which would only allow people with technical background to write stories like that. And let’s face it, developers are not writers.

Yes, I’m not fully set on “Arc” yet, it could be a “Chapter”. But it has a name, which gives structure and makes it more familiar to domain experts. Not naming it at all creates friction and uncertainty. Imagine how a writer would talk to the developer without these names. He’d say “Chapter”. And the developer would go “You mean function? What file and line are you talking about?”. The writer does not care about those files and lines the developer structured everything in. That’s friction. And the only reason it exists here is because of things that have no name.

I think I did that. Same directory as mix setup?


Long story short: I strongly believe that if you can name something to make it clear, you should. If you can separate concerns, you should. If you can make something more intuitive, you should.

I believe people have never been good with unlimited freedom especially when collaboration is key. That’s why we have standards and naming conventions.

Also I believe you’re not limiting creativity but guiding it with establishes structures this way.

1 Like

I believe that my package separates roles really well and I don’t see how it could be improved. goto and related calls needs to stay there and they are really not complicated. They are completely unrelated to the logic as the writer have no idea how for example player stats are stored and the developer on the other side does not really need to take care about the content used in the DSL.

There are of course atom identifiers sure, but please keep in mind that :john_doe is a character identifier. This also should not be part of writer job? I don’t think that way. The writer adds content with identifiers. Based on identifiers navigation between parts and modules is made. The content writer is supposed to “branch” story, so what’s wrong with requiring to use an identifier for each part he create?

Let’s say the writer would not write any identifier. How the developer would know what to do when user answers the question? He does not know story, so even if he would be responsible to set the identifiers then how would he know which part comes next? None of parts are named, there are no references. Just think that the whole branched story with multiple endings is flatten to a plain text. How to parse it? It does not makes any sense.

Ok, so instead of goto(:somename) you prefer to write few sentences describing each story part and where it should go? That’s a complete waste of time. Both writer and developer only doubles the work that could be done by just using an identifier. It’s not really a application logic - just branching of story. Think that you write a story in plain text. Instead of create few files called beginning.txt, bad_ending.txt and good_ending.txt you say that’s horrible for a write to branch a story and provide file name.

The developer would get a one .txt blob and has to process whole text most probably asking many questions to writer. Only in theory writer does not have to branch story, but in practice it sends instructions to the developer describing how to branch story even if doing such thing from start would not require any info from him. The developer also instead of working on player stats would have to read story he doesn’t care about, guess what author was thinking and again spend time asking how to properly branch story.

At the end they could be even in conflict as the story may be completely wrong to what writer send and what exactly we win here? You only lost a time of 2 people and in some cases you may even get the project cancelled. Maybe you are seeing things differently, but this way looks pointless for me. How story starts, what variants it have and how it ends should be up to content writer.

The point is he does not have to learn Elixir. Do you have to learn the whole JavaScript language to create a JSON? No matter what format you have you have to branch story. If you would name some part if comment or in some DSL does not matter. Sorry, but the DSL is extremely simple and does not require from you doing simplest things like a math everybody should do. It’s just a different syntax to tell the app where to go next and not a completely new game engine feature.

How long you learn defmodule, part having in mind you basically can copy-paste it and simply replace contents? How long do you learn dialog or goto naming that is common for everyone? 5 mins? If you have a writer that needs more than a day for that then I would honestly search for someone else. Look if someone cannot stick with some format the no matter what format he would have. He may write comments, but if they would not be consistent then it does not makes sense. You cannot parse it, because a writer was creating the story for weeks and somewhere in middle have started using different naming in comments. How would you parse it without asking a developer to do everything by hand which is supposed to be done by writer?

What’s the different between:

goto(:some_part_name)

and

# go to "some part name"

Only a format, right? Now think there is a typo like:

# go to "some part name

and then it needs to be fixed by hand or handled by developer. 2 months? If a developer is supposed not only to write the logic, but also to branch story then I’m not sure if 2 extra months are possible for a bigger story.

Having a consistent DSL saves lots of time. Look that if there would be typo in the part name then the pattern matching would simply fail. The developer would get an error message describing what function was called and with what arguments. It’s very simple to find a typo here. Developer have to fix one character and again he does not have to branch the story that’s not his responsibility after all.

Oh, so same writer can work on small stories, but cannot on bigger ones? Sorry, but this sounds funny. Everything have some format. Even stories have some kind of format (beginning, development and ending). Now think that someone says that he does not care about it at all. He’s just saying some story like he would write a diary. Everything is messy, not even every part is interesting for a reader and there is no order of events and he claims that the developer is responsible to make his story complete.

I would seriously not accept such a job offer … Again the writer needs to tell the complete story and branch it properly. Describing alternative endings or branching a story is not an application logic - it’s part of the story (no matter what format you use). Writer describes what’s happened and what would happen next and I don’t see things working in any other way. “Guess what author means” may be one of the the worst things in the development you may ever find.

Wait a second … So now YAML files are seeing as complex? Not sure what writes you know, but can they write a story without a helpful UI (you know the old, good way)? Honestly write some diary and send it to publisher as a complete story for book. Would that really work? A blob of text without any format? Even if YAML files are too much for such people then I honestly have no idea how to make things even more simple and I’m not sure if I want to know that …

Exactly! That’s why the writer should write a story, a complete story in a proper format and not a diary-like text, right?

Oh, I see … so the writers have problem even with YAML files and developers can’t write a maintanable and scalable code … This story starts to be interesting. You know that you are saying it to senior dev, right? :joy:

I believe that in my package there is no scalability issue. The only problem may be in the team organization and the individual skills. It would be a big relief if every developer applying for a job would say in XXI century that YAML have too complex syntax! Ahh … how simple the life would be. :smiling_face:

No, they agree to have one or many files for the logic that would be called from DSL (only matters in condition DSL as it requires from app to decide which way to follow).

You declare that some function (it does not need to exist yet) would return one of the possible options. Then depending on if it’s for exmaple choiceA we goto some branch and when choiceB then we go to another one. It could be another part or even module. What identifiers you use is up to you.

Oh no! How could someone in 2025 year require understanding of git! Man, now I’m saying half-seriously here … I know YAML and I know git well enough. If you have a job for me then I’m open for a new contract. I understand that people who never worked on game or programming don’t know git, but a writer? Maybe easier question … What they can or can they even use PC? I hope that by writer you don’t mean somebody that writes everything by hand and asks someone to send it by mail, right?

I’m really confused here. So you say there are job opportunities for people who don’t know how to work, don’t know any formats or apps/services that are used in industry (where those people apply for) for decades? It sounds either as a really low paid job or some kind of a heaven.

Look the writer and developer does not really need to use the same file, but if you say that some writers have so many missing skills then I have seriously no idea how to help them. Sorry, but if we cannot use even simplest formats then I don’t know how to reply for it. We live in the time of automation. I had no idea that someone that can barely enter some plain text can survive anywhere around apps or games. Yeah, some people don’t even need to know how PC work, but how would you support such people? Today it’s a topic like teaching someone how to read and write text. We talk about two completely different words.

The abilities of writes scares me much more than they are scared seeing YAML file. Sorry, but to build a good house you need a good foundations. If someone don’t even know how to put helmet then there is no hope for them. I’m really surprised that there are some people that can require so much from others not giving anything in advance. Yes, they write story, but the developer writes app logic. If they aren’t based on any standards it’s just a matter of time things would go seriously wrong …

Oh, so they are skilled enough to have their own specialized naming and they have no problem to study that, but they can’t learn how to use basic tools? You need to agree that this sounds funny …

Oh, so the writes does not care about dev work, but the dev is supposed to help writer with branching story? The more you say the more one-way it looks like …

Nope, naming here does not matter. If someone have a problem with for exmaple turning on PC then we cannot talk how to search something on the net, right?

yes

I cannot separate it even more. More than that is a plain text. A text that someone needs to read before making anything working. That’s one-way road and I cannot do much here. I understand that Elixir may be a bit too much even if we require a tiny subset of it’s syntax. However if you say that some writers may have problems with JSON or YAML then sorry, but nobody can help them here. They are one of the simplest formats and if that’s too much then nobody can help here. The only way is a GUI app with “click next” feature. There is no place for a cooperation with a dev. As said a one-way road.

Yeah standards, like JSON, YAML and git. Look it’s not possible to create a repository for someone that cannot even use git, because the repository itself is using git! I’m a senior developer. I know that I’m good, but not “that” good …

What’s established structure? Standard, right? As said everything needs to have some standard, format, pattern (however you name it). I can’t help people that can’t learn basic tools, but only pays attention to their work and naming.

Content creator: I want to collaborate in your repository
Developer: Sure things, go on!
Content creator: I don't care about git.
Developer: But you need to fetch it to get the repository.
Content creator: I don't care about it - I just want to contribute to your repository.
Developer: You know that's a `git` repository, right?.
Content creator: I don't care
Developer: …
System: User "Content creator" has been silenced by you.

"LIFE IS BRUTAL" - BAD ENDING

Looks like I have a simple story for my next demo project.
:joy:


I can’t help with missing git (and other tools) knowledge. I don’t plan to create any GUI app for a content creators right now. As said I would stay with more common naming that is most likely known by much more group of people. However I will note all other points and I will apply changes after I would finish work on my current project.