0
\$\begingroup\$

After learning Dialog, I've discovered representing game logic using the logic programming paradigm is a lot of fun; the composability and non-locality makes it feel like some kind of superior ECS. The problem is that game state becomes difficult to work with. Dialog has some facilities for handling state, but ultimately it is limited to mutable unary & binary facts and a compile-time fixed number of objects. To work around these issues introduces serious ugliness and brings into question the performance of larger game worlds.

Dialog isn't the only logic programming language, however, so I've looked into how text games can be made in Prolog and Logtalk and discovered the problem of managing state is even bigger there: one either passes around the world state as a parameter, or uses the dangerous assert/retract family to mutate the database. To my knowledge, neither option is appropriate for large, complex game worlds as an unskilled solo dev.

At this point I am stuck. Is there something I'm missing to get around these limitations in Dialog? Is there a better way to handle state in Prolog-family languages?

Clarifying Info

The reason why state is so important for me is that I'm interested in making a text-oriented game with procedural generation and RPG mechanics which requires the ability to dynamically generate objects, manage large quantities of their instances, and support AI pathing about a large world with a constantly changing connection graph. More concretely, I need to express:

  • runtime instantiation of an unbounded number of stateful objects
  • stateful objects (e.g. containers) that must present multiple instances simultaneously (e.g. a room of treasure chests)
  • pathable portals with dynamic destinations that may be moved by actors and have multiple instances in the same room

These are in service to the following gameplay outcomes:

  • Topological rooms. Instead of representing only the world as a graph of rooms with binary occupancy, I also intend to emulate old-school "zones" by using a system of waypoints and relations within a single room. By itself, this may not seem like much of an issue (you can represent this as a tree), but when one considers how zones typically contain multiple instances of the same object, we arrive back at our problem.
  • Autonomous AI. This seems unrelated, but imposes a crucial constraint - if an actor is performing an action that involves both a local object instance and a distant one, we can no longer use "floating" singleton objects to fake instances. This can be as simple as an actor by a tree looking to wave at another that happens to also be by the same kind of tree.
  • Procedural worlds. Moving on, I'd like for these zone-rooms to be procedurally populated. While you can get away with floating for stateless objects in a single-actor world, all the player needs to do is drop an object by a tree and wander over to another to discover the ruse.
  • Movable doors/rooms. Actor driven vehicles, for example.

Explanation of the Dialog Workarounds

For the limited arity of facts, you have the macro facility which allows you to bundle facts like so:

@($Obj is $Rel $Parent)
    *($Obj has relation $Rel)
    *($Obj has parent $Parent)

You may notice that this macro only works if $Obj has only one relation. This can be worked around by interpreting some datastructure composed of lists, but will miss out on optimization by the compiler.

%%Does not work because mutable facts are deterministic
@($Obj is $Rel $Parent)
    *($Obj has relation [$Rel $Parent])

%%Instead you must use linear search
@($Obj is $Rel $Parent)
    *($Obj has relations $Rels)
    *([$Rel $Parent] is one of $Rels)

Similarly, you could enumerate these composite facts with IDs, but I also anticipate performance issues there.

%%Doesn't work because numbers aren't supported as the first argument
@($Obj is $Rel $Parent ! $ID)
    *($ID has object $Obj)
    *($ID has relation $Rel)
    *($ID has parent $Parent)

%%This scans every object; inefficient
@($Obj is $Rel $Parent ! $ID)
    *(object $Obj concerns $ID)
    *(relation $Rel concerns $ID)
    *(parent $Parent concerns $ID)

For dynamic object creation, you can apply a similar tactic of assigning unique IDs to objects and tracking them with lists, but this again frustrates the compiler's optimization which is built around the object datatype (IIUC).

(new $Class is $ID)
    (there are $N instances)
    (world objects $Objs)
    (now) (world objects [$Class | $Objs])
    ($N + 1 is $ID)
    (now) (there are $ID instances)
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Can you tell us anything more about the gameplay outcomes you want to achieve with this data and relationship model? Imagine you might be speaking to readers who know some great strategies for building the kind of observable gameplay you have in mind, but aren't experienced in Dialog or its logic programming paradigm, and so can't immediately see how their expertise is relevant to this question. Make it easy for them to bridge that gap, and you expand the pool of potential experts who can help you with this question. \$\endgroup\$
    – DMGregory
    Oct 16, 2021 at 14:30
  • \$\begingroup\$ If you're really set on this idea... I think it would be far easier (maybe not easy as such) for you to embed a *Log compiler into an application coded in a more capable language like C++. You can use *Log rules-based processing in selected areas where you need it, but you can use C++ to manage overall state and do the things which your *Log variant is unable to easily do. They would need to read the data model (or at least the subset which pertains to *Log) from the same, shared fixed allocation, which C++ would probably allocate. \$\endgroup\$
    – Engineer
    Oct 16, 2021 at 15:31
  • \$\begingroup\$ @Engineer Good idea, but the issue with this is that a naive implementation ends up re-compiling the database after every mutation, passing the world state as an argument, or resorts to interpreting the code directly. I'm not aware of any solid interface/library/language that solves this problem and I'm not nearly clever enough to make it myself. \$\endgroup\$ Oct 16, 2021 at 15:45
  • \$\begingroup\$ Again, I think that limiting Dialog's scope is the only way to achieve anything moderately functional AND performant with this approach, as you've probably already realised. Process only small packets of data that you need to using Dialog; leave the rest to a more capable language. As for cross-referencing, wish I could help there, but I'm sure there are routes to answers on StackOverflow and elsewhere. \$\endgroup\$
    – Engineer
    Oct 16, 2021 at 18:52
  • \$\begingroup\$ ...And note that even a modern systems- and games-capable language such as Rust works with a non-mutable state approach, so it is doable, but quickly enough for your purposes? Only you can test that. \$\endgroup\$
    – Engineer
    Oct 16, 2021 at 18:58

0

You must log in to answer this question.

Browse other questions tagged .