Planet Roguelike-Dev

November 04, 2017

ULTIMA RATIO REGUM

Interlude II: The Interluding

Hello everyone! It has been a little while since the first Interlude, so I thought it would be appropriate to post a sequel.

Firstly, thank you all so much for the amazingly kind words on the other entry (and which I’ve had through email, Facebook, etc) – they really mean so much to me, and they are deeply deeply appreciated.

Secondly, I’ve now successfully moved to Canada, found a flat (or rather, an apartment), signed the contract, and done the majority of all the admin and bureaucracy stuff that comes from moving to an entirely different country; I still need to get a mobile phone that functions in this country, and there’s one or two University-admin things I need to complete, but otherwise I’m settled, moved, I’m “in the system” in the Canadian bureaucracy, and I’m getting ready to officially begin this job just a couple of days from now. This has been a pretty huge task in the last fortnight, but it’s now coming to an end.

Thirdly, on the health front, the physical symptoms are improving, and the psychological symptoms are (more slowly) also improving. Things are still tough, but I’m making some good choices to improve the newfound psychological difficulties this complication from my older illness has dumped on me. In the short-to-mid term, I think things might be on the up (slowly), but it’s always so hard to know.

Fourthly, here’s the cover for my upcoming book with Bloomsbury. I’m so happy with the design! I should have more information soon about an exact publication date, but there’s lots of roguelike-y goodness in there to be had.

Fifthly, any of you folks who are interested in Twitch and live streaming might want to read this paper I recently published about it – you can find a paywall-free version here. In it we explore the backgrounds of live streamers, the everyday work and labour of being a professional live streamer, and their hopes and fears about the future of their practice. This is part of a larger project on Twitch I’ve been developing alongside my colleague Jamie Woodcock in the last year, and we should have some more exciting stuff on this front to announce soon. Stay tuned.

So yes, that’s everything for now. I’ll hopefully be able to post more again once I have some kind of stability. I’ve also been thinking over some pretty fundamental questions about the website, how I blog, how often I blog, my general online visibility, these sorts of things, so there might be some big changes coming in the future (once I feel a little stronger). In the mean time, take care, everyone.

by Ultima Ratio Regum at November 04, 2017 06:48 PM

November 01, 2017

Dungeon Mercenary

Flexible object-oriented generation of monsters

Recently, I’ve revamped Dungeon Mercenary’s monsters generation. Previously I was using a simple PriorityTable which mapped every monster kind to an int probability. This means that, to generate n monsters, a dice was rolled ‘n’ times, on the interval 0-sum of probability of all monsters and the monster whose probability-interval was rolled on was picked. For example, the probabilities for depth 5 was expressed as follows:

protected int likeliness(MKind mkind) {
switch (depth) {
case ...:
case 5:
if (mkind == KOBOLD)
return 1;
else if (mkind == CHAOS_DOG)
return 2;
else if (mkind == CHAOS_FAIRY)
return 1;
else if (mkind == GOBLIN)
return 3;
else if (mkind == GOBLIN_NECROMANCER)
return 1;
else if (mkind == GOBLIN_NINJA)
return 1;
else if (mkind == ORC)
return 4;
else if (mkind == ORC_SHAMAN)
return 1;
else
return 0;
...

The main problem with this approach was that it was inappropriate to generate groups of monsters. In my mind, monsters often come in group, with a few special monsters in there; to spice it up. This was the objective of the generator shown above with the goblin necromancers and the goblin ninjas. My intent was that they would spice up the “vanilla” goblins. However a probability table doesn’t work very well for that purpose, as a few “misplaced” rolls in the table above could make the generator generate more spicy goblins than vanilla goblins. To circumvent that, I’ve introduced a “roulette” generator, that is organized differently. It takes as input the “vanilla” monsters and the spicy monsters; and generate a spicy monsters every 2 or 3 vanilla monsters. I took the occasion to introduce a few combinators to combine subgenerators which I’ll describe too; as the resulting ensemble seems nice to me. Here’s the main interface that is implemented by a variety of small generators, which are combined:

public interface IMonstersGroupGenerator<U, T extends IAnimate> {

/**
* @param factory
A function {@code U -> T}
* @param rng
* The {@link RNG} to use.
* @param acc
* Where to record created monsters.
*/

public void generate(IMonstersFactory<U, T> factory, RNG rng, Collection<T> acc);

}

I know that non object-oriented people will cry at this interface, that has 2 type parameters, and a bound. It’s clearly not the shortest API, but it’s the right way to do it in a library. In this interface, U is the identifier of monsters; which is an enumeration in Dungeon Mercenary and T is the concrete type of monsters (i.e. a single monster, with its attributes, health, damages, etc.). The attentive reader will note that this interface doesn’t mention the number of monsters to generate. This is important when generating group of monsters, as you don’t want the subgenerators to be aware of the number of monsters. It bloats implementations, and is better delegated to the top-level client of these generators (more on that later). There are 3 base generators that implement IMonstersGroupGenerator: Single, And, and Or (that are all defined in IMonsterGroupGenerator’s file). The Single generator generates a single monster every time it is called, the And generator generates all monsters of the generators to which it delegates (it’s a conjunction), and the Or generator is the old ProbabilityTable-based one (it’s a disjunction). At this point, note that handling the size in these generators would be at best cumbersome. To these 3 base generators, I’ve added the roulette generator described above. This allows to rewrite the code I’ve shown on top of this post as follows:

if (depth == 3) {
final ProbabilityTable<IMonstersGroupGenerator<MKind, IAnimate>> table = ProbabilityTable.create(5); // input to a disjunction
table.add(KOBOLD, /* probability */ 1);
table.add(CHAOS_DOG, 2);
table.add(CHAOS_FAIRY, 1);
table.add(roulette(GOBLIN, GOBLINS_SPICING), 3);
table.add(roulette(ORC, ORC_SHAMAN), 4);
return MonstersGenerator.create(or(table)); // Calls the Or generator (disjunction), that takes a probability table as input
}

In this code, GOBLINS_SPICING is a disjunctive-generator that generates either a goblin necromancer or a goblin ninja. This framework is quite expressive and concise. This is in part due to the fact that the type of identifiers (U in IMonstersGroupGenerator) is itself an implementation of IMonstersGroupGenerator, which allows to pass a monster’s identifier as a generator (it behaves exaclty like the Single generator). To be able to do that, the group generators must be real functions, i.e. they should not keep any state (as identifiers are inadequate to do that). Keeping state would anyway be cumbersome, for example you don’t want to store the RNG. It’s also important to have functions if you wanna save allocations by making some generators static. For example, GOBLINS_SPICING is static, as it’s a fixed disjunction on the fixed identifiers of spicy goblins. Another example of flexibility is when you want to force generation to include some monster(s). In this case, you simply conjunct a generator for these “must” monsters with your usual generator.

Finally, to generate a given number of monsters, it suffices to repeatedly call a group generator, until the accumulator (the third parameter of IMonstersGroupGenerator’s only method) has the appropriate size. Because the roulette generator generates spicy monsters after bunches of vanilla monsters, you’ll never end up with more spicy monsters than vanilla monsters when iteratively calling a roulette generator. For clarity, I’ve introduced a different interface for generators that are size-aware: IMonstersGenerator.

As usual with my libraries, all this code is in the public domain (in library hgamesrhogue), so you head over to this link, you steal what you want, you modify it; it’s all up to you!

Written with StackEdit.

by noreply@blogger.com (smelC) at November 01, 2017 09:23 PM