-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathforest.go
More file actions
350 lines (298 loc) · 7.48 KB
/
forest.go
File metadata and controls
350 lines (298 loc) · 7.48 KB
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
package main
import (
"encoding/json"
"os"
"time"
)
// The direction of the tracks indicates where a rabbit went
// from here.
type TrackDirection uint
const (
// Default value.
MinRabbits = 1
// Default value. The numbers of rabbits that exist
// at any given time.
MaxRabbits = 15
// Spawn chance for rabbits.
SpawnChance = 0.20
// Chance to ascend deeper (closer to /). The weight
// has to be fair, because ascending is very limited
// and you only have one option.
AscendChance = 0.30
// Chance to move twice instead of once.
TwoStepChance = 0.50
// How long it takes for tracks to fade. Right
// now, it's a 1/5 of the time it takes a rabbit
// to move.
TrackFadeTime = IdleTime / 5
)
const (
// No tracks.
TrackNone TrackDirection = iota
// Ascending is going "up" a directory, like cd ..
TrackAscending
// Descending is going "down" a directory, like cd ./data
TrackDescending
)
type track struct {
Timestamp time.Time
Direction TrackDirection
}
type directoryForest struct {
// List of rabbits and their locations. Only one
// rabbit per location.
rabbits map[string]*Rabbit
// Tracks at a given location. Cleared and updated after every move.
tracks map[string]track
// Number of rabbits seen.
spottedCount uint
// Number of rabbits caught.
caughtCount uint
// Number of rabbits killed. :(
killedCount uint
}
func newDirectoryForest() directoryForest {
return directoryForest{
map[string]*Rabbit{}, map[string]track{}, 0, 0, 0,
}
}
// For a location to exist, the directory must exist.
func (f *directoryForest) LocationExists(loc string) bool {
fi, err := os.Stat(loc)
if err != nil {
return false
}
return fi.IsDir()
}
// Returns a location near the passed location. Nearby is
// found by a small number of random directory changes. Will
// not be the same directory, unless it has to (can't ascend
// or descend).
//
// XXX: Currently this does not check if a rabbit already
// exists at the new location. So we just lose rabbits
// if one encounters another.
func (f *directoryForest) NearbyLocation(loc string) string {
newloc := loc
steps := 1
if chance(TwoStepChance) {
steps = 2
}
tryagain:
added := []string{}
for i := 0; i < steps; i++ {
// Can't move.
if !canAscend(newloc) && !canDescend(newloc) {
return newloc
} else if chance(AscendChance) {
if canAscend(newloc) {
newloc = ascend(newloc)
} else {
newloc = randDescension(newloc)
}
} else {
if canDescend(newloc) {
newloc = randDescension(newloc)
} else {
newloc = ascend(newloc)
}
}
added = append(added, newloc)
}
if newloc == loc {
// Guaranteed to not be the same because you must
// step twice to get to the same destination.
steps = 1
goto tryagain
}
pastLoc := loc
for _, aloc := range added {
if isAscension(aloc, pastLoc) {
f.tracks[pastLoc] = track{time.Now(), TrackAscending}
} else if isDescension(aloc, pastLoc) {
f.tracks[pastLoc] = track{time.Now(), TrackDescending}
} else {
panic("Rabbit didn't move to nearby location.")
}
pastLoc = aloc
}
return newloc
}
// A random faraway location. Rabbits typically start here
// and run here when they're fleeing. Faraway locations don't
// add tracks.
func (f *directoryForest) FarawayLocation(loc string) string {
newloc := baseLocation()
triedagain := false
steps := 1
if chance(TwoStepChance) {
steps = 2
}
tryagain:
for i := 0; i < steps; i++ {
if canDescend(newloc) {
newloc = randDescension(newloc)
}
}
if newloc == loc && !triedagain {
// Invert the steps. We can't get to the same
// location with different steps.
if steps == 1 {
steps = 2
} else if steps == 2 {
steps = 1
}
triedagain = true
goto tryagain
}
return newloc
}
// Returns true if a rabbit is here. Only useful for checking
// before performing an action.
func (f *directoryForest) IsRabbitHere() bool {
loc, _ := os.Getwd()
_, ok := f.rabbits[loc]
return ok
}
// Returns whether tracks
func (f *directoryForest) GetTracksHere() (bool, TrackDirection) {
loc, _ := os.Getwd()
t, ok := f.tracks[loc]
if ok {
return true, t.Direction
} else {
return false, TrackNone
}
}
// Anytime a location is entered, a check is performed. This
// function updates every rabbit and returns a rabbit if one
// is spotted.
func (f *directoryForest) PerformCheck() (spotted *Rabbit) {
spotted = nil
// We always check our current directory.
loc, _ := os.Getwd()
newrabbits := map[string]*Rabbit{}
for _, r := range f.rabbits {
r.DisturbanceAt(loc)
if (r.IsPlaying()) {
if r.JustSpotted() {
// It's possible for two rabbits to "wakeup"
// to the same location in the same update.
// Right now the most recent on will not be
// overridden so we spotted that one.
//
// XXX: Fix rabbits running into each other?
spotted = r
f.spottedCount++
}
newrabbits[r.Location()] = r
} else {
if r.State() == Dead {
f.killedCount++
} else if r.State() == Caught {
// Update in PerformCatch, otherwise
// catching a rabbit score won't
// be update until the next call to this
// function.
}
}
}
f.rabbits = newrabbits
// See if we should repopulate.
f.repopulate()
f.fadeTracks()
return
}
// Attempts to catch a rabbit if it's still where we are.
func (f *directoryForest) PerformCatch() bool {
loc, _ := os.Getwd()
f.fadeTracks()
rab, ok := f.rabbits[loc]
if ok {
delete(f.rabbits, rab.Location())
succ := rab.TryCatch(loc)
// We must update the table, else we can run into two rabbits.
f.rabbits[rab.Location()] = rab
if succ {
f.caughtCount++
}
return succ
}
return false
}
// Attempts to tag a rabbit if it's still where we are.
func (f *directoryForest) PerformTag(tag string) bool {
loc, _ := os.Getwd()
f.fadeTracks()
rab, ok := f.rabbits[loc]
if ok {
delete(f.rabbits, rab.Location())
succ := rab.TryTag(loc, tag)
// We must update the table, else we can run into two rabbits.
f.rabbits[rab.Location()] = rab
return succ
}
return false
}
// Repopulated the forest if under the minimum number of rabbits
// we want. Otherwise, chance a rabbit will spawn.
func (f *directoryForest) repopulate() {
for len(f.rabbits) < MinRabbits {
r := NewRabbit(f)
f.rabbits[r.Location()] = &r
}
if chance(SpawnChance) {
r := NewRabbit(f)
f.rabbits[r.Location()] = &r
}
}
// Fades the tracks depending on how old they are. Faded
// tracks are removed.
func (f *directoryForest) fadeTracks() {
list := []string{}
for loc, track := range f.tracks {
age := time.Now().Sub(track.Timestamp)
if age >= TrackFadeTime {
list = append(list, loc)
}
}
for _, loc := range list {
delete(f.tracks, loc)
}
}
// Used for marshalling/unmarshalling.
type forest struct {
Rabbits map[string]*Rabbit
Tracks map[string]track
SpottedCount uint
CaughtCount uint
KilledCount uint
}
// These are implemented because we can't encode private fields.
func (f *directoryForest) UnmarshalJSON(b []byte) error {
data := forest{}
err := json.Unmarshal(b, &data)
if err != nil {
return err
}
f.rabbits = data.Rabbits
f.tracks = data.Tracks
f.spottedCount = data.SpottedCount
f.caughtCount = data.CaughtCount
f.killedCount = data.KilledCount
// Circular reference. Couldn't marshal their home so
// we do it here.
for _, r := range f.rabbits {
r.ChangeHome(f)
}
return nil
}
func (f *directoryForest) MarshalJSON() ([]byte, error) {
return json.Marshal(&forest{
Rabbits: f.rabbits,
Tracks: f.tracks,
SpottedCount: f.spottedCount,
CaughtCount: f.caughtCount,
KilledCount: f.killedCount,
})
}