[m-users.] Mercury for Game AI

M McDonough foolkingcrown at gmail.com
Mon Jan 24 09:19:53 AEDT 2022


On Sun, 23 Jan 2022, David Epstein <davideps at umich.edu> wrote:
> There are three types (person, door, room). Each person has three Boolean
> properties (*is_alive, is_awake, is_free*). Each door has two Boolean
> properties (*is_open, is_destroyed*). Rooms do not have properties. The
> properties are used to determine the value of two more predicates through
> rules (*is_mobile, can_pass*). A person can move via the *try_move*
> predicate, which moves the person if the move is permitted.

Generally for something like this, I wouldn't maintain it as a
database to imitate how Prolog works. For instance, just hold a list
of doors, and have a single player, and put these into a "game state"
type that the game logic predicate accepts as a state variable. This
also has the benefit that you can have a separate "render" or
"graphics" and audio predicate, and that way you can do no IO inside
the game predicate at all. This makes it much easier to unit test the
game, as well as allowing for easy multi-threading or simulation (such
as running the game predicate to do an in-game demo, or ensuring logic
is properly duplicated for players and enemies, etc).

I would also not use hard-coded enum values for doors/rooms, but
rather have them share a type and set up that data according to the
game's actual world. I know that is kind of boring and probably feels
much more functional then logical, but I think it makes things much
easier. You still get a Mercury "feeling" from using unification and
determinism to operate on this data and determine game logic. And
plus, now you can load your game's data from file, which is much nicer
for creating new content than recompiling the entire game every time
you change a single bit of data on a single room.

The way I would represent your example in Mercury is something like
this (note I didn't test this code):

% I'm putting `dead` as its own functor since it precludes status.
% This can also make it easier/nicer when using rules for semidet preds.
% Probably use a 'status' type instead of bools, but for illustration:
:- type person ---> dead ; alive(awake::bool.bool, free::bool.bool).

:- type door_state ---> destroyed ; locked ; unlocked.
% Change 'destination' type to be whatever you are using to track
% locations, probably an ID or string for a name.
:- type door ---> door(leads_to::destination, state::door_state).

% This will only succeed if the person is alive, is awake and free
% to move, and the door is unlocked.
:- pred move(person::in, door::in) is semidet.
move(alive(bool.yes, bool.yes), door(_, unlocked)).

% As an example to use in other code (not top-level code).
% You will probably want the game state to be represented with
% a state variable.
% That way, !GameState can be used in each arm of the conditional,
% or ignored if no update is needed.
( if
    move(Person, Door)
then
    % Create new game state with the person moved, using
    % Door ^ leads_to to know where to go.
else
    % Couldn't move, indicate this to the player somehow.
)


More information about the users mailing list