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:
- first name
- middle name
- last name
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…