-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcivPerson.go
436 lines (369 loc) · 12 KB
/
civPerson.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
package genworldvoronoi
import (
"fmt"
"math/rand"
"github.com/Flokey82/genetics"
"github.com/Flokey82/genetics/geneticshuman"
"github.com/Flokey82/go_gens/gameconstants"
"github.com/Flokey82/go_gens/genlanguage"
)
func (m *Civ) getNextPersonID() int {
m.nextPersonID++
return m.nextPersonID
}
// tickPerson advances the person by nDays and returns any new born child.å
// TODO: Twins, triplets, etc.
func (m *Civ) tickPerson(p *Person, nDays int, cf func(int) *Culture) *Person {
if !m.doesPersonExist(p) {
return nil
}
// Calculate age.
m.tickPersonAge(p, nDays)
// Advance pregnancy.
child := m.tickPersonPregnancy(p, nDays, cf)
// Check if person dies of natural causes.
m.tickPersonDeath(p, nDays)
return child
}
const (
ageOfAdulthood = 18
ageEndChildbearing = 45
)
func (m *Civ) tickPersonAge(p *Person, nDays int) {
// Calculate current age.
if m.History.GetDayOfYear() < p.Birth.Day {
p.Age = int(m.History.GetYear()) - p.Birth.Year - 1
} else {
p.Age = int(m.History.GetYear()) - p.Birth.Year
}
}
// tickPersonPregnancy advances the pregnancy of a person.
// If the pregnancy is successful, a new person is born.
func (m *Civ) tickPersonPregnancy(p *Person, nDays int, cf func(int) *Culture) *Person {
// Check if the person is pregnant.
if p.Prengancy != nil {
return m.advancePersonPregnancy(p, nDays, cf)
}
// Check if the person can get pregnant.
if p.isOfChildbearingAge() && p.canBePregnant() {
// Approximately once every 5 years if no children.
// TODO: Figure out proper chance of birth.
chance := 5 * 365
if p.Age > 40 {
// Over 40, it becomes more and more unlikely.
// TODO: Genetic variance?
chance *= (p.Age - 40)
}
// The more children, the less likely it becomes
// that more children are on the way.
//
// NOTE: Not because of biological reasons, but
// who wants more children after having some.
chance *= len(p.Children) + 1
if rand.Intn(chance) < nDays {
p.newPersonPregnancy(m.getNextPersonID(), p.Spouse)
}
}
return nil
}
func (m *Civ) tickPersonDeath(p *Person, nDays int) {
// Check if person dies of natural causes.
if gameconstants.DiesAtAgeWithinNDays(p.Age, nDays) {
// If the person just gave birth, we note that the person
// died during childbirth.
m.killPerson(p, "") // Random cause?
}
}
func (m *Civ) killPerson(p *Person, reason string) {
p.Death.Day = int(m.History.GetDayOfYear())
p.Death.Year = int(m.History.GetYear())
p.Death.Region = p.Region
// If they have a spouse, unset their spouse.
if p.Spouse != nil {
p.Spouse.Spouse = nil
}
// :(
if p.Prengancy != nil {
m.killPerson(p.Prengancy, reason)
}
var deathStr string
if reason == "" {
deathStr = fmt.Sprintf("%s died", p.Name())
} else {
deathStr = fmt.Sprintf("%s died due to %s", p.Name(), reason)
}
m.AddEvent("Death", deathStr, p.Ref())
/*
var str string
if p.Spouse != nil {
str += "[spouse]"
}
if len(p.Children) > 0 {
str += fmt.Sprintf("[%d children]", len(p.Children))
}
log.Println("killed", p.Name(), "at", p.Death.Region, "aged", p.Age, str)
*/
}
func (m *Civ) doesPersonExist(p *Person) bool {
return !p.Death.IsSet() && p.Birth.IsSet() && p.Birth.Year < int(m.History.GetYear())
}
func (m *Civ) updatePersonLocation(p *Person, r int) {
// Update location.
// NOTE: We should differentiate between people who live in the city and
// people who work in or visit the city.
p.Region = r
// p.City = m.GetCity(r)
// TODO: Add person to city population?
}
// LifeEvent represents a date and place in the world.
type LifeEvent struct {
Year int
Day int
Region int
}
// IsSet returns true if the life event is set.
func (l LifeEvent) IsSet() bool {
return l.Year != 0 || l.Day != 0 || l.Region != 0
}
// Person represents a person in the world.
// TODO: Improve efficiency of this struct.
// - We could drop age, and use day-ticks for birth and death instead.
// - Also, we can get the gender directly from the genes.
// - We might be able to drop the pregnancy counter and use the birth life event
// of the child as a counter.
// - We can use use the Region for the location and derive the city from that.
// - A lot of this stuff is identical to simvillage_simple, so we could probably
// merge the person logic somehow, or move it to a separate package.
type Person struct {
ID int // ID of the person
Region int // Location of the person
Genes genetics.Genes // Genes.
City *City // City of the person
Culture *Culture // Culture of the person
// Todo: Allow different naming conventions.
FirstName string
LastName string
NickName string
// Birth, death...
// TODO: Add death cause.
Age int // Age of the person.
Birth LifeEvent
Death LifeEvent
// Pregnancy
PregnancyCounter int // Days of pregnancy
Prengancy *Person // baby (TODO: twins, triplets, etc.)
// Family (TODO: Distinguish between known and unknown family members.)
// Maybe use a map of relations to people?
Mother *Person
Father *Person
Spouse *Person // TODO: keep track of spouses that might have perished?
Children []*Person // TODO: Split into known and unknown children.
}
func (m *Civ) newRandomPersonAt(r int, culture *Culture) *Person {
// Random genes / gender.
genes := genetics.NewRandom()
geneticshuman.SetGender(&genes, randGender())
lang := culture.Language
// Create the person.
// If the first/last name pool is large enough, we should
// start reusing names because generating new names is expensive.
//
// TODO: With increasing pool size, we should increase the chance
// of reusing names.
var firstName string
if poolSize := lang.GetFirstNamePoolSize(); poolSize > 100 && rand.Intn(poolSize) > 10 {
firstName = lang.GetFirstName()
}
if firstName == "" {
firstName = lang.MakeFirstName()
}
// Same for last names.
var lastName string
if poolSize := lang.GetLastNamePoolSize(); poolSize > 300 && rand.Intn(poolSize) > 10 {
lastName = lang.GetLastName()
}
if lastName == "" {
lastName = lang.MakeLastName()
}
// TODO: Calculate age.
p := &Person{
ID: m.getNextPersonID(),
Culture: culture,
Genes: genes,
FirstName: firstName,
LastName: lastName,
Birth: LifeEvent{
Year: int(m.History.GetYear()) - ageOfAdulthood + rand.Intn(2*ageOfAdulthood),
Day: rand.Intn(365),
Region: r, // TODO: Pick a birth region that makes sense.
},
}
// Update location.
m.updatePersonLocation(p, r)
// TODO: Random spouse, children, etc.?
m.People = append(m.People, p)
return p
}
// Name returns the name of the person.
func (p *Person) Name() string {
if p.NickName != "" {
return fmt.Sprintf("%s %q %s", p.FirstName, p.NickName, p.LastName)
}
return p.FirstName + " " + p.LastName
}
// Ref returns the object reference of the person.
func (p *Person) Ref() ObjectReference {
return ObjectReference{
ID: p.ID,
Type: ObjectTypePerson,
}
}
// String returns the string representation of the person.
func (p *Person) String() string {
return fmt.Sprintf("%s \n%s", p.Name(), geneticshuman.String(p.Genes))
}
// Gender returns the gender of the person.
func (p *Person) Gender() geneticshuman.Gender {
return geneticshuman.GetGender(&p.Genes)
}
func (p *Person) isOfChildbearingAge() bool {
return p.Age >= ageOfAdulthood && p.Age < ageEndChildbearing
}
// isElegibleSingle returns true if the person is old enough to look for a partner and single.
func (p *Person) isEligibleSingle() bool {
return p.Age > ageOfAdulthood && p.Spouse == nil // Old enough and single.
}
// canBePregnant returns true if the person is old enough and not pregnant.
func (p *Person) canBePregnant() bool {
// Female, has a spouse (implies old enough), and is currently not pregnant.
// TODO: Set randomized upper age limit.
return p.Gender() == GenderFemale && p.Spouse != nil && p.Prengancy == nil
}
const pregnancyDays = 280 // for humans
func (p *Person) newPersonPregnancy(id int, father *Person) *Person {
// Mix genes.
var genes genetics.Genes
if father != nil {
genes = genetics.Mix(p.Genes, father.Genes, 1)
} else {
genes = genetics.Mix(p.Genes, genetics.NewRandom(), 1)
}
// Fix genes wrt. gender (the genetic mix doesn't limit gender varaition)
geneticshuman.SetGender(&genes, randGender())
// We need to set the name after birth, because the parents might not know the gender of the baby
// until birth. (If there's magic, only wealthy people would be able to determine the gender before)
child := &Person{
ID: id,
Genes: genes,
Mother: p,
Father: father,
}
p.PregnancyCounter = pregnancyDays
p.Prengancy = child
return child
}
// advancePersonPregnancy advances the pregnancy of the person.
// TODO: Add twins, triplets, etc.
func (m *Civ) advancePersonPregnancy(p *Person, nDays int, cf func(int) *Culture) *Person {
// Reduce pregnancy counter.
p.PregnancyCounter -= nDays
if p.PregnancyCounter > 0 {
return nil
}
// Birth!
wasBornNDaysAgo := -p.PregnancyCounter
child := p.Prengancy
// Reset pregnancy.
p.Prengancy = nil
p.PregnancyCounter = 0
// Add child to family and name it.
// We use spouse since this is the acting father.
// TODO: Use naming convention of culture to determine if mother or father name the child.
var lang *genlanguage.Language
if p.Spouse != nil && rand.Intn(100) < 50 {
lang = p.Spouse.Culture.Language
} else {
lang = p.Culture.Language
}
// There is a random chance we generate a new name, but the larger the pool
// the less likely we are to generate a new name.
var firstName string
if poolSize := lang.GetFirstNamePoolSize(); poolSize > 100 && rand.Intn(poolSize) > 10 {
firstName = lang.GetFirstName()
}
if firstName == "" {
firstName = lang.MakeFirstName()
}
child.FirstName = firstName
// Add child to the children of the mother.
p.Children = append(p.Children, child)
// Add child to the children of the "father" uhm.. spouse.
if p.Spouse != nil {
p.Spouse.Children = append(p.Spouse.Children, child)
} else if p.Father != nil {
// TODO: What if spouse != father?
p.Father.Children = append(p.Father.Children, child)
}
// Use the mother's last name.
child.LastName = p.LastName
// Set birth date.
child.Birth.Region = p.Region
child.Birth.Year = int(m.History.GetYear())
child.Birth.Day = m.History.GetDayOfYear() - wasBornNDaysAgo
if child.Birth.Day < 0 {
child.Birth.Year--
child.Birth.Day += 365
// Age the baby for the number of days it was born ago.
m.tickPerson(child, wasBornNDaysAgo, cf)
}
// Set city.
child.City = p.City
if child.City != nil {
child.City.People = append(child.City.People, child)
child.Culture = child.City.Culture
}
// Set culture.
// NOTE: Should this be the culture of the mother or father?
// If mother and father are from different cultures, which one should it be?
// If the child is born in a different region, should the culture change?
//
// I think it'd be great to randomly determine which culture the child
// should have. This would ba an interesting source of conflict and story.
if child.Culture == nil {
child.Culture = cf(p.Region)
}
// Update location.
m.updatePersonLocation(child, p.Region)
// Add child to world.
m.People = append(m.People, child)
// log.Println("New person born:", child.Name())
return child
}
// isDead returns true if the person is dead.
func (p *Person) isDead() bool {
return p.Death.IsSet()
}
var (
GenderFemale = geneticshuman.GenderFemale
GenderMale = geneticshuman.GenderMale
)
// randGender returns a random gender.
func randGender() geneticshuman.Gender {
if rand.Intn(2) == 0 {
return GenderFemale
}
return GenderMale
}
// isRelated returns true if a and b are related (up to first degree).
func isRelated(a, b *Person) bool {
// Check if there is a parent/child relationship.
if a == b.Father || a == b.Mother || b == a.Father || b == a.Mother {
return true
}
// If either (or both) of the parents are nil, we assume that they are not related.
if (a.Father == nil && a.Mother == nil) || (b.Father == nil && b.Mother == nil) {
return false
}
// Check if there is a (half-) sibling relationship.
return a.Mother == b.Mother || a.Father == b.Father
}