NAME
Game::EnergyLoop - a simple energy system
SYNOPSIS
use Game::EnergyLoop;
use Object::Pad;
class Foo {
field $name :param;
field $energy :param;
field $priority :param :reader;
field $cur_energy = 0;
method enlo_energy ( $new = undef ) {
$cur_energy = $new if defined $new;
return $cur_energy;
}
method enlo_update( $value, $epoch ) {
print "$epoch RUN $name ($priority) $value\n";
return $energy;
}
}
sub pri { @{$_[0]} = sort {$b->priority <=> $a->priority} @{$_[0]} }
my @obj = map {
Foo->new(
name => "N$_",
energy => ( 1 + int rand 8 ),
priority => int rand 2,
)
} 1 .. 3;
my $epoch = 0;
for ( 1 .. 10 ) {
$epoch += Game::EnergyLoop::update( \@obj, \&pri, $epoch );
}
See also the eg
directory of this module's distribution for example scripts.
DESCRIPTION
This module provides an update function that determines which agents in a list should move, when, with an optional initiative callback for when multiple agents run at the same time. An update function is called for each agent that moves; this function must return a new energy cost and optionally a list of new agents to append to the list of entities.
The energy cost is assumed to be a integer value; the minimum value is subtracted from each agent each update call and those entities that have zero energy have their update function called. This means an agent update function that returns zero will not advance the energy loop; this can be used to provide "free moves" to an agent, or to prevent the other agents from running until, say, some necessary input from a player becomes available. How this energy loop interacts with, say, a user interface loop may require some thought.
The caller is responsible for pruning dead agents out of the list after update is called, if relevant. Agents that move at the same time may, in some cases, have to account for being killed before they have a chance to move, or these agents may instead be considered to have moved at the same time, and code that runs after the update call may need to resolve things, such as agents now being dead. These and other details will depend on the particulars of the game or environment being simulated.
FUNCTION
The function is not exported by default.
- update animates initiative-callback arg
-
animates must be an array reference; the objects within must support enlo_energy and enlo_update methods. enlo_energy is a getter/setter that contains the integer energy value of the entity, and enlo_update is a function called with the entity object and the optional arg passed in by the caller.
initiative-callback is called if provided when two or more entities move at the exact same time; an array reference of entity objects is passed, and the function will need to reorder the entities, if necessary, to run in some particular order, for example to roll initiative and sort the array reference in place. If there is such an initiative system, then entities may need to account for being killed before it is their turn to move, e.g. Adam and Bob move at the same time; Bob wins the initiative, kills Adam, so then Adam's update function will need account for being already dead. The default order is the order of the entities in the list. A common practice is to put the player and any other special objects (e.g. a virtual entity that spawns monsters) at the start of the list so that they move first.
The minimum cost for this update and arg are passed along with the entity object to the enlo_update call. arg could be a stash, or a world object.
The enlo_update function must return a new energy value for when the entity moves next, and an optional array reference of new entities created during the update call, such as when a player summons new entities by casting a spell. New entities must not be directly added to the entity list, as there is no "this entity is new" flag to distinguish those new entities from the current ones being updated. That is, any new entities do not run until the update function finishes updating all agents that move on the current "turn". If you do need agents that update the instant they are created, you will have to figure out some way to make that happen. The usual lore is to claim that the new summons have summoning sickness, so cannot move right away.
update returns the minimum energy cost value, or how much the energy system advanced by, e.g. 0 if something got a "free move" or maybe
100
if an agent moved left. This return value can almost be used as a sort of an epoch counter, though beginnings can be difficult times.
ENERGY VALUES
Energy values should be integers to avoid problematic small but not quite zero floating point values, possible portability problems, etc. Nothing in this module wastes time enforcing integerness, however. Negative values cause errors to be thrown, so do not return those.
100
is typical for a default move cost, though one may want a larger value if entities can move much faster than the default: 50
, 25
, whoops 12.5
isn't an integer value. If diagonal moves cost more (they do not in many roguelikes) then consider using 99
and 70
, or factors thereof, as 99 divided by 70 is a pretty good approximation of the square root of two.
BUGS
These probably should be called errors, not bugs.
SEE ALSO
https://thrig.me/src/ministry-of-silly-vaults.git contains various energy system implementations in various languages, but none that are complicated by also having to work with a user interface loop.
https://thrig.me/src/sbal.git contains a similar but more complicated energy system (in animate.c
and animate.h
) that does account for objects being "new" or "dead" or "newly dead", and a perhaps too complicated method to determine whether or not the game is blocked waiting for player input, as a user interface loop does also exist. sbal is a 7DRL so was written in hurry, and as such may not have the simplest of code. Consider this module a cleaned up and simplified version of the sbal animate system.
AUTHOR
Jeremy Mates
COPYRIGHT AND LICENSE
Copyright (C) 2024 by Jeremy Mates
This program is distributed under the (Revised) BSD License.