Author Topic: Monster generation  (Read 33456 times)

jasonpickering

  • Rogueliker
  • ***
  • Posts: 274
  • Karma: +0/-0
    • View Profile
    • Email
Re: Monster generation
« Reply #15 on: January 19, 2014, 01:54:08 AM »
so Quendus. You still end up with an array to select from, but where I was setting the array for each level, you are using a quick formula to decide?

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Monster generation
« Reply #16 on: January 19, 2014, 07:40:40 AM »
Yes. Every level you would call this weight() function for all the monster types, and the results would fill up the array. If you don't put tokens back into the bag, then you alter the array after making each choice.

jasonpickering

  • Rogueliker
  • ***
  • Posts: 274
  • Karma: +0/-0
    • View Profile
    • Email
Re: Monster generation
« Reply #17 on: January 19, 2014, 01:41:14 PM »
that sounds good, but i wonder if perhaps my original way of just building an array for each level might have been the easiest way to go. It would take up some room, but as opposed to doing the equation to figure out how to have 3 slimes on levels 1-3 then 2 for 4-5 then 1 for the rest of the levels, it would be a bit faster.

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Monster generation
« Reply #18 on: January 19, 2014, 02:25:59 PM »
If you want a specific number of each monster type on each floor, I'm not sure there's much point doing any random generation.

Anyway, a function doesn't have to be a mathematical expression. There's nothing stopping you from writing functions like that in whatever language you use:
Slime:
If depth <= 3: weight = 3
Else if depth <= 5: weight = 2
Else: weight = 1.

naughty

  • Rogueliker
  • ***
  • Posts: 59
  • Karma: +0/-0
    • View Profile
Re: Monster generation
« Reply #19 on: January 21, 2014, 10:09:13 AM »
that sounds good, but i wonder if perhaps my original way of just building an array for each level might have been the easiest way to go. It would take up some room, but as opposed to doing the equation to figure out how to have 3 slimes on levels 1-3 then 2 for 4-5 then 1 for the rest of the levels, it would be a bit faster.

It sounds like you're trying to do something out of the ordinary. Could you specify the exact behaviour you'd like to see?

guest509

  • Guest
Re: Monster generation
« Reply #20 on: January 22, 2014, 04:18:51 AM »
Meh...

If level = 1
  Roll on table 3 times
  1 Slime
  2 Slime
  3 Slime
  4 Slime
  5 Demon
  6 Demon

Brute force. Simple to change. Easy to see what's happening. You might get all demons or all slimes, no big whoop?

You can have a table for each level or you can add a modifier and make the list longer.

Roll on table 5 times, add depth
1. nothing
2. nothing
3. slime
4. slime
5. slime
6. demon
7. demon
...
15. wolf

Clarity is good. But if complex is your thing you can invent all kinds of subtle ways to do this, as Quendus suggested. Ways that are portable and engine like.

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Monster generation
« Reply #21 on: January 22, 2014, 07:48:36 AM »
Meh...

If level = 1
  Roll on table 3 times
  1 Slime
  2 Slime
  3 Slime
  4 Slime
  5 Demon
  6 Demon

Brute force. Simple to change. Easy to see what's happening. You might get all demons or all slimes, no big whoop?
Simple to write. Not so simple to change. If you want to add another slime you've got to either put it at the end (and make the table hard to read) or put it before the demons, which means changing th numbers on the demons. Either way you've got to change the number of sides on the dice. I prefer just saying:
Slime: put 4 copies in the table
Demon: put 2 copies in the table
Then the computer adds 2 to 4 and gets a 6 sided dice. If I want to change it I just change 4 to 5 and the computer does the rest for me.
Quote
You can have a table for each level or you can add a modifier and make the list longer.

Roll on table 5 times, add depth
1. nothing
2. nothing
3. slime
4. slime
5. slime
6. demon
7. demon
...
15. wolf
Dice+depth is ok for quickly making a system that places different enemies at different depths, but it's even harder to modify than the one above because adding or removing a monster changes the length of the dungeon, unless you compensate by removing or adding something else. All of the variables are bound together so that you can't change one thing without making big changes to things you didn't want to change. It's also difficult to get key depths out of a table like that, because you can't read the first and last depth a monster appears straight off the table - you have to add and subtract the number of sides on the dice. Same goes for the levels where it's most common.

Overall, it's good if you want to quickly make a monster generation system for a 7DRL, but for anything where you might want to later change the monster list or rebalance because a difficult monster is too common, it's better to choose a more flexible system - a "read-write" one instead of a "read-only" one.
Quote
Clarity is good. But if complex is your thing you can invent all kinds of subtle ways to do this, as Quendus suggested. Ways that are portable and engine like.
Clarity is good. I'm not advocating for complexity here, I just think that the relationship between what a developer wants from monster generation and the numbers a developer has to type out to get that system should be as simple as possible. I think that a developer most likely thinks of a range of depths and a relative frequency for each monster, and so the computer should work from information of that kind. That way the developer doesn't have to manually translate to some other kind of information that doesn't directly relate to what they want. I prefer to write code once that takes information in my terms and makes a dice table from it than to translate from my terms to a dice table and back over and over and over again.

tl;dr I don't write games in machine code and I don't write dice tables either.

jasonpickering

  • Rogueliker
  • ***
  • Posts: 274
  • Karma: +0/-0
    • View Profile
    • Email
Re: Monster generation
« Reply #22 on: January 22, 2014, 03:55:43 PM »
So I had some special requirements for this, because it was being used for my game MicRogue. I wanted it so Enemies were never removed from the spawn list, so there was always a chance to spawn, but also that there needed to be a good variety of enemies in a level, because the game is very boring when all the enemies are the same. I ended up just going with a simple switch and an enemy array. Its probably not the most elegant way, but its easy to change and add monsters and it was quick to code.

// Create the Monster Array
var Monster_Array:Array = []
         
// 1 = Slime
// 2 = Large Fire
// 3 = Skeleton Warrior
// 4 = Demon
// 5 = Eye
// 6 = Ninja
// 7 = Cockatrice
         
switch (Floor)
{
   case 1:    { Monster_Array = [1, 1, 1, 2, 5] }; break;
   case 2:    { Monster_Array = [1, 1, 1, 2, 5] }; break;
   case 3:    { Monster_Array = [1, 1, 1, 2, 2, 3, 4, 5] }; break;
   case 4:    { Monster_Array = [1, 1, 2, 2, 3, 4, 5, 5, 6] }; break;
   case 5:    { Monster_Array = [1, 1, 2, 2, 3, 4, 5, 5, 6, 7] }; break;
   case 6:    { Monster_Array = [1, 1, 2, 2, 2, 3, 4, 5, 5, 6, 7] }; break;
   case 7:    { Monster_Array = [1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7] }; break;   
   case 8:    { Monster_Array = [1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7] }; break;
   case 9:    { Monster_Array = [1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7] }; break;
   case 10:   { Monster_Array = [1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7] }; break;
}

guest509

  • Guest
Re: Monster generation
« Reply #23 on: January 22, 2014, 04:20:39 PM »
Looks pretty clear. Does it work in practice? I think that's the real test.

@Quendus - You have to remember that you are an above average programmer, maybe even a great one I dunno, but some people just want to write games. I understand the weights thing, but it's just not as easy for me. So I'd never use it. I'd come back to it and simply forget how it worked or have to parse through it again to fix something. Tables might be harder to change but still trivial in a 10 level game. It's not bad coding and it's not lazy if games are getting made.

jasonpickering

  • Rogueliker
  • ***
  • Posts: 274
  • Karma: +0/-0
    • View Profile
    • Email
Re: Monster generation
« Reply #24 on: January 22, 2014, 06:42:49 PM »
works pretty well at giving me a good range of guys. The more monsters in the larger arrays give a decent variety. I also added a special enemy system. Each spawned enemy has a chance of being a special type of that enemy.

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Monster generation
« Reply #25 on: January 22, 2014, 08:56:58 PM »
Jason: Looks like a reasonably flexible system. Might not look elegant, but it's got the essentials - you can see at a glance which levels each type of monster is on, and what's on each level, you can change what's on one level without affecting others, you can change one monster's rarity without affecting other stuff, you don't have to do any arithmetic to change stuff (assuming arrays in your language know their own length), you can easily add and remove monsters and levels, and everything's in one place. Pretty sweet. The only disadvantage is the typing time, but copy/paste can partially mitigate that (presumably you did).

@Quendus - You have to remember that you are an above average programmer, maybe even a great one I dunno, but some people just want to write games. I understand the weights thing, but it's just not as easy for me. So I'd never use it. I'd come back to it and simply forget how it worked or have to parse through it again to fix something. Tables might be harder to change but still trivial in a 10 level game. It's not bad coding and it's not lazy if games are getting made.

Jo: I never said anything was bad or lazy coding, I believe in using the right tool for the job. That depends on the job (not using heavy tools on a light problem), future prospects for the job (it may or may not be important that you and others can understand or alter it in future), and the person using them (different tools may be easier for different people to use). When I read the OP, based on Jason's history I imagined a mid-weight problem (multiple monsters, multiple levels), probable future development (because I remembered seeing a lot of iterative development in the Microgue thread here), and a programmer with a reasonable level of experience (I don't know their background, but all Jason's games that I remembered were in Flash, and all the ways I've heard of to make Flash games involved typing code, maybe even object-oriented programming).

Based on those guesses I posted a simple algorithm (it really is simple, all it takes is a few arrays and a couple loops) that's flexible enough to let the designer do just about anything without having to type it all out by hand. I posted the slightly more complicated version (using [3,2,3,0,1] instead of [1,1,1,2,2,3,3,3,5]) just to cover my ass in case someone decided to berate me later about it being inefficient to store so many copies of the same number... instead I get berated because it's too complicated. Either way, at least one of the versions is within the OP's grasp and that's what matters. If the OP ever changes any of the stuff mentioned above, I'll have saved them some time and frustration.

You weren't asking for advice so I wouldn't presume to tell you what methods to use. Especially since using loops and arrays might be really awkward in Game Maker (I don't know how GM works, but I used the Games Factory when I was a kid and I wouldn't have had a clue how to do this kind of stuff). For simple tasks like monster generation, if you have a working solution and the only available improvements come with hefty pricetags like learning arrays and loops, then you're totally right to stick with what you've got. You might write a game sometime where you really do need to learn how this stuff works, in which case the cost attached to this option would go down a long way.

We're alike - I don't make 3D games because the time cost of learning OpenGL properly is big and the monetary cost of getting Unity without plastering "TRIAL VERSION" over the user's screen isn't zero. I still haven't found a game idea that motivates me enough to do either.

jasonpickering

  • Rogueliker
  • ***
  • Posts: 274
  • Karma: +0/-0
    • View Profile
    • Email
Re: Monster generation
« Reply #26 on: January 22, 2014, 09:40:52 PM »
typing it all in wasn't bad. I planned out all the level stuff in a spreadsheet and then typed it out. Originally I actually used Strings instead of numbers. That was a mistake. My first floor was:

case 1:    { Monster_Array = ["Slime", "Slime", "Slime", "Large Fire", "Eye"] }; break;

It ended up being way to much work, so I dropped it to a simple integer. Way easier to work with. I also started with less enemies and added them slowly so it took a bit to type in. To add a monster I just select one randomly from the array and remove it, and The monster count is always less then the total array length. so My first level has 4 guys, it will never have more then 4 slimes.

guest509

  • Guest
Re: Monster generation
« Reply #27 on: January 22, 2014, 11:56:42 PM »
Arrays and other complex data structures are easy in GM, but 2D arrays are about the most complex structure I use, with lots of loops and such. Only code nazis would berate you from doing something less efficient but easier.

As long as a game comes out of your code that's what matters.

I am going to have to up my game considerably if I plan to make any headway on my SuperHero project, but my next 7DRL is going to feature exactly the monster tables we have discussed here.

BTW my last 7DRL had over 3000 lines of code, making anything in Gamemaker requires using the scripting language for the effects of actions. Game Maker Language (GML) is a fully functional high level language.

Paul Jeffries

  • 7DRL Reviewer
  • Rogueliker
  • *
  • Posts: 257
  • Karma: +1/-0
    • View Profile
    • Vitruality.com
Re: Monster generation
« Reply #28 on: January 23, 2014, 08:18:53 PM »
Jason seems to have found a system that works for him, but for the benefit of anybody else considering the same problem, I thought I'd just point out that depending on what language you're using, there are some standard data structures out there that can make the kind of weighted list that Quendus is suggesting both extremely easy to implement and also very fast.

For example, with Java's TreeMap you can add each enemy type keyed with a cumulative weight score, then generate a random number in the appropriate range and use ceilingEntry to find the next cumulative weight above that:

Code: [Select]
TreeMap<Double,String> enemies = new TreeMap<Double,String>();

//Add enemies:
enemies.put(1.0, "Monkey");
enemies.put(enemies.lastKey() + 3.0, "Giant Rat");
enemies.put(enemies.lastKey() + 1.0, "Orc");
enemies.put(enemies.lastKey() + 1.5, "Jehovah's Witness");
enemies.put(enemies.lastKey() + 0.1, "Dragon");

//Pick random enemy:
Random rand = new Random();
double randomNumber = rand.nextDouble()*enemies.lastKey();
String randomEnemy = enemies.ceilingEntry(randomNumber).getValue();

You can do exactly the same thing with any collection ordered by a key, such as C#/VB.NET's SortedList or C++'s std::map, although you may have to loop through to find the next highest value yourself.

As a barely-relevant aside, on the off chance that anybody is interested in seeing a real-world application of this: I recently used the same basic algorithm to generate a gradient seating pattern for a sports stadium by randomly mixing three different seat colours, with the weighting of each colour changing as you go up the bowl to produce the gradient: https://pbs.twimg.com/media/Bc0Xrx_IQAEyEkT.jpg:large

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Monster generation
« Reply #29 on: January 23, 2014, 11:02:03 PM »
Jason seems to have found a system that works for him, but for the benefit of anybody else considering the same problem, I thought I'd just point out that depending on what language you're using, there are some standard data structures out there that can make the kind of weighted list that Quendus is suggesting both extremely easy to implement and also very fast.

For example, with Java's TreeMap you can add each enemy type keyed with a cumulative weight score, then generate a random number in the appropriate range and use ceilingEntry to find the next cumulative weight above that:

Code: [Select]
TreeMap<Double,String> enemies = new TreeMap<Double,String>();

//Add enemies:
enemies.put(1.0, "Monkey");
enemies.put(enemies.lastKey() + 3.0, "Giant Rat");
enemies.put(enemies.lastKey() + 1.0, "Orc");
enemies.put(enemies.lastKey() + 1.5, "Jehovah's Witness");
enemies.put(enemies.lastKey() + 0.1, "Dragon");

//Pick random enemy:
Random rand = new Random();
double randomNumber = rand.nextDouble()*enemies.lastKey();
String randomEnemy = enemies.ceilingEntry(randomNumber).getValue();

You can do exactly the same thing with any collection ordered by a key, such as C#/VB.NET's SortedList or C++'s std::map, although you may have to loop through to find the next highest value yourself.

As a barely-relevant aside, on the off chance that anybody is interested in seeing a real-world application of this: I recently used the same basic algorithm to generate a gradient seating pattern for a sports stadium by randomly mixing three different seat colours, with the weighting of each colour changing as you go up the bowl to produce the gradient: https://pbs.twimg.com/media/Bc0Xrx_IQAEyEkT.jpg:large

Nice effect! The photo's not great but you can still see the effect clearly. I'm using a similar routine in my own work - I have a histogram generated from a sample of angles, and I use this to produce random angles with the same distribution as the histogram.

I don't know about C#, but in C++ the code wouldn't be much different - you'd more or less replace lastKey() with crbegin().first and ceilingEntry with lower_bound. In C++11 you can just let the standard library handle the whole thing:
Code: [Select]
// Hoops to jump through to use shiny new random functions
static std::random_device rd;
static std::mt19937 rng(rd());
// Enemy names
std::vector<std::string> enemies = {"Slime", "Demon", "Tomato", "Gorgonzola"}
// Probability distribution
std::discrete_distribution<float> enemy_distribution({2.f, 1.f, 0.2f, 0.0001f});
// Choose an enemy from the distribution
std::string monster_of_the_week = enemies[enemy_distribution(rng)];
[\code]