Names

(1306 words)

I’ve done a lot last week, but haven’t quiet finished it, so I’m only going to talk about the other stuff I’ve done this week. I’ll wait until next week to talk about everything else.

I started by adding in some gameplay components. Things like Skill, Person, MaidenName, Single, Partner, and Random.

Skill is just an enumeration of the different skills that a person could have. The thought is it have a maximum of say eight skills per person and that will direct them towards a particular job or role in the city. Random is another easy one. There may be a lot of things that are going to be random on a person, for example, what color clothes they are wearing. I’ve encoded this into a single random 128 bit number. Different parts of the code can therefore grab some random bits out of this random number and use that as the random value they need.

Of course, adding a u128 value into the ECS system meant that a number of places had to be updated. A new rand_u128 function was added to generate the random numbers. The saving of a component also had to add the ability to save such large values. Similarly the script to check these save files also had to add the ability to decode the values. That wasn’t quite as elegant as python doesn’t really support 128-bit integers directly, especially in the struct.unpack function.

The Person component has three fields:

The first and middle names are either male names or female names. This is based off the least significant bit in the Random component. The last name is just a family name, or surname, depending on your preference.

These are just u32 random numbers, that are modded down to a list of names. Interestingly, the US census publishes a list of first and last names. The first names were last published in 1990, but the last names are fairly recent. Also interestingly, there are significantly more female first names and male first names. Not sure why, but that’s the data I have available. For the moment, I’ve just stuck that list of names into simple arrays, and then mod the random number by the length of those arrays.

pub fn get_first_name(sex: bool, index: u32) []const u8 {
    if (sex) {
        return male_first_names[index % male_first_names.len];
    } else {
        return female_first_names[index % female_first_names.len];
    }
}

The last name is even easier.

pub fn get_last_name(index: u32) []const u8 {
    return last_names[index % last_names.len];
}

Of course, we have to create those names. Randomizing a first and last name is easy.

pub fn random_first_name() u32 {
    return ng.rand_u32();
}

pub fn random_last_name() u32 {
    return ng.rand_u32();
}

But randomizing a middle name is a little more interesting. We don’t want somebody to have the same first and middle names. ‘John John Smith’ might be interesting to some, and might even be somebodies name in real life, but I’d prefer to have different first and middle names.

pub fn random_middle_name(sex: bool, first_name: u32) u32 {
    var middle_name = ng.rand_u32();
    const names_len = if (sex) male_first_names.len else female_first_names.len;

    while (middle_name % names_len == first_name % names_len) {
        middle_name = ng.rand_u32();
    }

    return middle_name;
}

Therefore, we first pick a random number. Work out the length of the names, which depends on the sex of the person. The convention here is that sex == true means male. Logically, it could be an enumeration, and I may change that in the future to be more explicit.

Then we do a while loop, that repeats only when the middle_name modulo names_len results in the same as the first_name modulo names_len. If that is true, then the first and middle names would be the same so we pick another middle name and try again.

The Single component is just a tag. It denotes if somebody is single or not. This allows a single_person_system to be executed against those entities to try to pair them off with another person of a different sex.

fn single_person_system(iter: *const ng.SystemIterator) void {
    for (iter.entities) |entity| {
        const rentity = entity.get(com.Random) orelse continue;
        const person = entity.getPtr(com.Person) orelse continue;
        const sex = names.get_sex(rentity.rand);

We might be changing the value of the last name of a person, so that component is obtained using getPtr rather than just get. And we can find out the sex by using the random component value rentity.

var other_iter = state.single_persons_query.iterator();
while (other_iter.next()) |other_entity| {
    if (other_entity != entity) {
        const rother = other_entity.get(com.Random) orelse continue;
        const other_person = other_entity.getPtr(com.Person) orelse continue;
        const other_sex = names.get_sex(rother.rand);

We then loop through all other single persons. This means that not only do we have a single person system but also a single person query. These are logically two separate things from the ECS point of view.

ng.register_system(
    .{
        .name = "single_persons",
        .phase = .update,
    },
    single_person_system,
    .{
        com.Single,
        *com.Person,
    },
);

state.single_persons_query = ng.register_query(
    .{
        .name = "single persons",
    },
    .{
        com.Single,
        com.Person,
    },
);

Okay, back to the system implementation. Obviously, we are looping through all the single people, and trying to find another single person that is not this person.

if (sex != other_sex) {

And those two people have different sexes.

if (!std.mem.eql(
    u8,
    names.get_last_name(person.last_name),
    names.get_last_name(other_person.last_name),
)) {

And we want those people to have different last names. We do this comparison on the string of their lane name and not the value last_name because of the modulo operation could result in two people with different values having same last name. This may look weird to a player, so we forbid that.

Finally, we have the make-a-partner code.

entity.set(com.Partner{ .partner = other_entity });
other_entity.set(com.Partner{ .partner = entity });
entity.remove(com.Single);
other_entity.remove(com.Single);

We first set the partners of each person to the other and remove the Single component from both.

if (names.get_sex(rentity.rand)) {
    other_entity.set(com.MaidenName{
        .last_name = other_person.last_name,
    });
    other_person.last_name = person.last_name;
} else {
    entity.set(com.MaidenName{ .last_name = person.last_name });
    person.last_name = other_person.last_name;
}

If this person is male, then the other person will take their last name and record their original name as a MaidenName. Otherwise, it is the other way around.

One other thing that is interesting. At the end of this, I do an iter.set_interval (0); I’ve added the concept that within a system, you can change the interval that this system is called. The realization came when looking at all the possible partners paired up, but potentially still some other single people around. In this situation, this system would iterate other all these single people every frame. Not very useful. Therefore, if we do pair up two persons, we set the interval to zero, and return immediately. This means that the next frame we will do one other pairing. If nobody is paired up, and the code falls to the end of the function, then we set the interval to 1. This system will then only run one second from now.

pub fn set_interval(self: *const SystemIterator, interval: f32) void {
    self.system.wait_time = interval;
    self.system.interval = interval;
}

This is a member function on the SystemIterator and just updates the wait_time and interval accordingly. This also meant that the SystemIterator had the system field added to allow this redirection.

That’s about it for the moment. I’ll talk more about the other thing I’ve been working on next week. In terms of lines of code, that is a lot, although the actual amount of code is a lot smaller than it looks.

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Zig                             34           2479           1986          91061
-------------------------------------------------------------------------------

If we remove the 4221 lines of code in the next week topic, that means we’ve added 71385 lines of code. And yes, most of that is in names.zig.

This one is short, but next week will be long. We’ve got over four thousand lines of code to go through…

Fourth, Game