-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathengine.go
1655 lines (1410 loc) · 79.2 KB
/
engine.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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Here is the main engine app.
package main
import (
_ "github.com/go-sql-driver/mysql"
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/jaytaylor/html2text" // converts HTML to pretty-printed text! (20170807)
"github.com/spf13/viper"
"golang.org/x/net/websocket"
"gopkg.in/guregu/null.v3/zero"
"html/template"
"io/ioutil"
"math"
"math/rand"
"net/http"
"sort"
"strings"
"strconv"
"sync/atomic" // used for sync'ing values across goroutines at a low level
"time"
)
// Define a communications procotol with the client, so that we can selectively
// send messages to turn options on and off, etc.
// Messages will be JSON.
type WsMessageType struct {
Type zero.String `json:"type"`
SubType zero.String `json:"subtype"`
Text zero.String `json:"text"`
Id zero.String `json:"id"`
}
// New creates a new WsMessage out of 4 strings
func (wsM *WsMessageType) New(msgType string, msgSubType string, msgText string, msgId string) *WsMessageType {
wsM.Type = zero.StringFrom(msgType)
wsM.SubType = zero.StringFrom(msgSubType)
wsM.Text = zero.StringFrom(msgText)
wsM.Id = zero.StringFrom(msgId)
return wsM
}
// Constants for genetic algorithm. Names are retained from the PHP version.
// TODO(gwyneth): Have these constants as variables which are read from the configuration file.
const OS_NPC_SIT_NOW = "0"
// Constants used in genetic algorithm.
const RADIUS = 10.0 // this is the size of the grid that is thrown around the avatar
const POPULATION_SIZE = 50 // was 50
const GENERATIONS = 20 // was 20 for 20x20 grid
const CHROMOSOMES = 7 // was 28 for 20x20 grid
const CROSSOVER_RATE = 90.0 // = 90%, we use a random number generator for 0-100
const MUTATION_RATE = 5.0 // = 0.005%, we use a random number generator for 0-1000 - TODO(gwyneth): try later with 0.01
const WALKING_SPEED = 3.19 // avatar walking speed in meters per second)
// Weights for Shi & Cui
const W1 = 1.0 // Sub-function of Path Length
const W2 = 10.0 // Sub-function of Path Security
const W3 = 5.0 // Sub-function of Smoothness
// When transposing from the PHP version, we now cannot avoid having a few structs and types, since Go
// is a strongly-typed language (20170726)
// This was moved out of the GA code body because some external functions need those types (20170727)
// chromosomeType is just a point in a path, really.
type chromosomeType struct {
x, y, z, distance, obstacle, angle, smoothness float64
}
// popType represents each population as a list of points (= chromosomes) indicating a possible path; it also includes the fitness for this particular path.
type popType struct {
Fitness float64
chromosomes []chromosomeType
}
// movementJob is used in the worker goroutine which processes the points to move the avatars to, which needs to wait until the avatars have moved.
// Go is so quick in recalculating generations that the avatars never get a chance to reach their destination until we wait for them!
// So the commands to move the avatars need to go into a separate goroutine, to wait on avatars, while the main engine continues (20170730).
type movementJob struct {
agentUUID string // Agent UUID to move
masterControllerPermURL string // masterController to use (note that the engine may pick one of several active ones)
agentPermURL string // unfortunately the masterController cannot get or set Energy...
destPoint chromosomeType // destination to go to; it's a chromosome so that we get distance information as well to calculate
// for how long we need to sleep until the avatar reaches destination
}
// movementJobChannel is the blocking channel to which we write points for the next bot movement
var movementJobChannel = make(chan movementJob, 1) // for now, we'll try with just 1 point
// Go is tricky. While we send and receive WebSocket messages as it would be expected on a 'normal'
// programming language, we actually have an insane amount of goroutines all in parallel. So what we do is to
// send messages to a 'channel' (Go's version of a semaphore) and receive them from a different one; two sets
// of goroutines will have their fun reading and sending messages to the client and updating the channels,
// so other goroutines only send and receive to the channels and have no concept of 'WebSocket messages'
// This is sort of neat because it solves parallelism (goroutines block on sockets) but it also allows
// us to build in other transfer mechanisms and make them abstract using Go channels (20170703)
var wsSendMessage = make(chan WsMessageType)
var wsReceiveMessage = make(chan WsMessageType)
var webSocketActive atomic.Value // this is an attempt to check if we have an active WebSocket, to avoid too many timeouts (20170728)
// serveWs - this is what is 'called' from the outside, and I need to talk to a socket here.
func serveWs(ws *websocket.Conn) {
// see also how it is implemented here: http://eli.thegreenplace.net/2016/go-websocket-server-sample/ (20170703)
var err error // to avoid constant redeclarations in tight loop below
if ws == nil {
Log.Panic("Received nil WebSocket — I have no idea why or how this happened!")
}
/*
log.Printf("Client connected from %s", ws.RemoteAddr())
log.Println("entering serveWs with connection config:", ws.Config())
*/
webSocketActive.Store(true)
defer webSocketActive.Store(false)
go func() {
// log.Println("entering send loop")
for {
sendMessage := <-wsSendMessage
if err = websocket.JSON.Send(ws, sendMessage); err != nil {
Log.Error("Can't send; error:", err)
break
}
}
}()
//log.Println("entering receive loop")
var receiveMessage WsMessageType
for {
if err = websocket.JSON.Receive(ws, &receiveMessage); err != nil {
Log.Error("Can't receive; error:", err)
break
}
// Log.Debugf("Received back from client: type '%s' subtype '%s' text '%s' id '%s'\n", *receiveMessage.Type.Ptr(), *receiveMessage.SubType.Ptr(), *receiveMessage.Text.Ptr(), *receiveMessage.Id.Ptr())
wsReceiveMessage <- receiveMessage
}
}
// convertLocPos converts a SL/OpenSim Location and Position into a single region name and (x,y,z) position coordinates
func convertLocPos(location string, position string) (regionName string, xyz []string) {
regionName = location[:strings.Index(location, "(")-1]
coords := strings.Trim(position, "() \t\n\r")
xyz = strings.Split(coords, ",")
return regionName, xyz
}
// calcDistance calculates the distance between two points, which are actually arrays of x,y,z string coordinates.
// TODO(gwyneth): Now that we have a strongly-typed language, we should create real objects for this.
func calcDistance(vec1, vec2 []float64) float64 {
deltaX := vec2[0] - vec1[0] // using extra variables because multiplication is probably
deltaY := vec2[1] - vec1[1] // simpler than calling the math.Pow() function (20170725)
deltaZ := vec2[2] - vec1[2]
return math.Sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ)
}
// engineHandler is still being implemented, it uses the old Go websockets interface to try to keep the page updated.
func backofficeEngine(w http.ResponseWriter, r *http.Request) {
// start gathering the cubes and agents for the Engine form
checkSession(w, r)
// Collect a list of existing bots and their PermURLs for the form
db, err := sql.Open(PDO_Prefix, GoBotDSN)
checkErr(err)
// query for in-world objects that are cubes (i.e. not Bot Controllers)
rows, err := db.Query("SELECT UUID, Name, ObjectType, ObjectClass, Location, Position FROM Positions WHERE ObjectType <> 'Bot Controller' ORDER BY Name")
checkErr(err)
defer rows.Close()
var (
cubes, regionName = "", ""
uuid, name, objType, objClass, location, position = "", "", "", "", "", ""
xyz []string
)
cubes = "\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + NullUUID + "\">Clean selection (let engine figure out next cube)</option>\n"
// As on backofficeCommands, but a little more complicated
for rows.Next() {
err = rows.Scan(&uuid, &name, &objType, &objClass, &location, &position)
checkErr(err)
// parse name of the region and coordinates
regionName, xyz = convertLocPos(location, position)
cubes += fmt.Sprintf("\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"%s\">%s (%s/%s) [%s (%s,%s,%s)]</option>\n", uuid, name, objType, objClass, regionName, xyz[0], xyz[1], xyz[2])
}
rows, err = db.Query("SELECT Name, UUID, Location, Position FROM Agents ORDER BY Name")
checkErr(err)
var uuidAgent, agentNames = "", ""
agentNames = "\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + NullUUID + "\">Clean selection (let engine figure out next agent)</option>\n"
// To-Do: Agent options should also have location etc.
// find all Names and OwnerKeys and create select options for each of them
for rows.Next() {
err = rows.Scan(&name, &uuidAgent, &location, &position)
checkErr(err)
regionName, xyz = convertLocPos(location, position)
agentNames += fmt.Sprintf("\t\t\t\t\t\t\t\t\t\t\t\t\t<option value=\"%s\">%s (%s) [%s (%s,%s,%s)]</option>\n", uuidAgent, name, uuidAgent, regionName, xyz) // not obvious for the Go linter, but xyz is an array of 3 elements (gwyneth 20210711)
}
rows.Close() // closing after deferring to close is probably not good, but I'll try it anyway (20170723)
db.Close()
tplParams := templateParameters{ "Title": "Gobot Administrator Panel - engine",
"URLPathPrefix": template.HTML(URLPathPrefix),
"Host": template.HTML(Host),
"DestinationOptions": template.HTML(cubes),
"AgentOptions": template.HTML(agentNames),
"ServerPort": template.HTML(ServerPort),
"Content": template.HTML("<hr />"),
}
err = GobotTemplates.gobotRenderer(w, r, "engine", tplParams)
checkErr(err)
}
// EngineRunning is the equivalent of a semaphore which starts or stops the engine.
// OneStep allows the engine to run once, and then it stops.
// These are an exported global (atomic) variables because we need to access it from the configuration function and from the SIGHUP/SIGCONT (20170811, 20170919).
var EngineRunning, OneStep atomic.Value
// engine does everything but the kitchen sink.
// Notably, it does not only run the GA. It also deals on a separate goroutine with message handling for WebSockets, which also includes
// the ability to start or stop the GA. And it launches another goroutine to deal with buffering commands to the virtual world. It really does
// a lot, and possibly it ought to be simplified somehow. But this is the core, the essence, the kernel, the locus of all the rest!
func engine() {
// we use sync/atomic for making sure we can read a value that is set by a different goroutine
// see https://texlution.com/post/golang-lock-free-values-with-atomic-value/ among others (20170704)
var (
receiveMessage WsMessageType
userDestCube atomic.Value // using sync/atomic to make values consistent among goroutines (20170704)
curAgent atomic.Value
)
// EngineRunning.Store(true) // we start by running the engine; note that this may very well happen before we even have WebSockets up (20170704)
// now we let this be set via configuration file; the default is true; and a SIGHUP will start/stop the engine (20170811)
userDestCube.Store(NullUUID) // we start to nullify these atomic values, either they will be changed by the user,
curAgent.Store(NullUUID) // or the engine will simply go through all agents (20170725)
OneStep.Store(false) // in theory, the engine starts or stops; one step is a special case if the client is connected (20170919)
webSocketActive.Store(false) // as soon as we know that we have a connection to the client, we set this to true (20170728)
sendMessageToBrowser("status", "info", "Entering the engine goroutine", "") // browser might not even know we're sending messages to it, so this will just gracefully timeout and be ignored and just appear on the log; changed message to display that we don't know if the engine is going to run or not (20170811)
// Launch the movement worker goroutine. This is needed because Go is so fast calculating populations that it keeps giving the agents
// contradictory movement commands. This uses a blocking channel and calculates how long the avatar needs to reach its destination
// and sleeps for that time. There was something similar done in PHP as well, but PHP took long enough recalculating everything, so
// it was deemed not to be necessary. (20170730)
go movementWorker()
// Now, this is a message handler to receive messages while inside the engine, we
// block on a message and run a goroutine in the background, so we can safely continue
// to run the engine without blocking or errors
// I have no idea yet if this is a good idea or not (20170703)
// At least it works (20170704)
go func() {
var messageType, messageSubType string
for {
receiveMessage = <-wsReceiveMessage
if (receiveMessage.Type.Ptr() != nil) {
messageType = *receiveMessage.Type.Ptr()
} else {
messageType = "empty"
}
if (receiveMessage.SubType.Ptr() != nil) {
messageSubType = *receiveMessage.SubType.Ptr()
} else {
messageSubType = "empty"
}
switch messageType {
case "status":
switch messageSubType {
case "ready": // this is what we get when WebSockets are established on the client
webSocketActive.Store(true)
// check for engine running or not and set the controls
switch EngineRunning.Load().(bool) {
case true:
sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
case false:
sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
default: // should never happen, but turn all buttons off just in case
sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
}
case "gone": // The client has gone, we have no more websocket for this one (20170704)
Log.Info("Client just told us that it went away, we continue on our own")
webSocketActive.Store(false)
default: // no other special functions for now, just echo what the client has sent...
unknownMessage := "<nil>"
if receiveMessage.Text.Ptr() != nil {
unknownMessage = *receiveMessage.Text.Ptr()
}
Log.Warning("Received from client unknown status message with subtype",
messageSubType, "text:", unknownMessage, " — ignoring...")
}
case "formSubmit":
var messageText string
if receiveMessage.Text.Ptr() != nil {
messageText = *receiveMessage.Text.Ptr()
} else {
messageText = NullUUID + "|" + NullUUID // a bit stupid, we could skip this and do direct assigns, but this way we do a bit more effort wasting CPU cycles for the sake of code clarity (20170704)
}
returnValues := strings.Split(messageText, "|")
userDestCube.Store(returnValues[0])
curAgent.Store(returnValues[1])
// Commented out because we know this works and we'll print it out later on anyway (20170730)
// log.Println("Destination: ", userDestCube.Load().(string), "Agent:", curAgent.Load().(string))
// sendMessageToBrowser("status", "info", "Received '" + userDestCube.Load().(string) + "|" + curAgent.Load().(string) + "'<br />", "")
case "engineControl":
switch messageSubType {
case "start":
sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
EngineRunning.Store(true)
OneStep.Store(false)
case "one-step":
sendMessageToBrowser("htmlControl", "disable", "", "startEngine")
sendMessageToBrowser("htmlControl", "disable", "", "oneStep")
sendMessageToBrowser("htmlControl", "enable", "", "stopEngine")
EngineRunning.Store(true)
OneStep.Store(true)
Log.Debug("OneStep is now", OneStep.Load().(bool))
/*
case "stop":
sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
EngineRunning.Store(false)
OneStep.Store(false)
*/
default: // anything else will stop the engine!
sendMessageToBrowser("htmlControl", "enable", "", "startEngine")
sendMessageToBrowser("htmlControl", "enable", "", "oneStep")
sendMessageToBrowser("htmlControl", "disable", "", "stopEngine")
EngineRunning.Store(false)
OneStep.Store(false)
}
sendMessageToBrowser("status", "", "Engine " + messageSubType + "<br />", "")
default:
Log.Warning("Unknown message type", messageType)
}
}
}()
// We continue with engine. Things may happen in the background, and theoretically we
// will be able to catch them. (20170703)
// load whole database in memory. Really. It's so much faster that way! (20170722)
var (
Agent AgentType // temporary way to store what comes from database
lastAgentToRunUUID string // temporary storage of the last UUID agent that ran, when we pick one randomly, so we give others a chance (20170807)
// Agents map[string]AgentType // we OUGHT to have a type without those strange zero.String, but it's tough to keep two structs in perfect sync (20170722); this is mapped by Agent UUID (20170725)
Position PositionType
Cubes map[string]PositionType // name to be compatible with PHP version; mapped by UUID (20170725).
Object ObjectType
Obstacles []ObjectType
masterController PositionType // set to the most recent Bot Master Controller to send commands (name is the same as in former PHP code).
)
// NOTE(gwyneth): The reason why we use maps and not slices (slices may be faster) is just because that way we can
// directly address the element by UUID, instead of doing array searches (20170725)
// prepare data to be saved as a CSV/XML file for later import into Excel and do nice graphics
var export_rows []string // we place it here because of potential scope issues later on...
// Theoretically endless loop follows (20170730)
for {
// Now, the problem with the approach of going through the list of Agents is that new Agents might appear, old
// might be deleted, and then we're stuck! (Remember, the updating of the Agents table is done in parallel to this)
// The idea of running goroutines for each Agent will also suffer from the same problem: what if the Agent dies and we don't know
// about it? Of course we can check with a ping first. What about *new* Agents? How do we launch new goroutines for them if we
// don't know about them beforehand? (20170801)
// Second approach (20170801): initialise lastAgentRunning with NullUUID; pick one agent from the database; if it's the
// same as before, pick a new one; if the user has provided us with an agent, use that one instead. This will at least provide
// all agents with a chance of running, while allowing new Agents to appear and old ones to die (20170801). The cost of this
// solution is that *some* Agents may not have a chance to run (since they're picked randomly), so we might be a little more
// evil and use some magical pseudo-random generators from Go which allow a sequence of non-repeated random numbers to be
// generated, and try to follow that order if possible, which means reloading the Agent table every cycle, but it might still be
// worth it (20170801).
// NOTE(gwyneth): From 20170807 onwards, the for loop runs forever, each cycle one Agent is picked to run
// Note that the Agent table does not get reloaded each cycle, only a list of UUIDs, one of which is picked randomly and just one
// Agent is loaded (20170807).
if EngineRunning.Load().(bool) {
// Open database
// sanity check first, I have no idea why this happens sometimes:
if PDO_Prefix == "" {
PDO_Prefix = viper.GetString("gobot.PDO_Prefix")
}
if GoBotDSN == "" {
GoBotDSN = viper.GetString("gobot.GoBotDSN")
}
db, err := sql.Open(PDO_Prefix, GoBotDSN)
checkErr(err)
defer db.Close() // needed?
// load in Agents! We need them to call the movement algorithm for each one
// BUG(gwyneth): what if the number of agents _change_ while we're running the engine? We need a way to reset the engine somehow. We have a hack at the moment: send a SIGCONT, it will try to restart the engine in a new goroutine
// Changes 20170807: we now pick one agent randomly
// First check if the end-user hasn't sent us an Agent UUID to use:
userSetAgentUUID := curAgent.Load().(string)
possibleAgentUUID := NullUUID
// Log.Debug("userSetAgent is", userSetAgent)
if userSetAgentUUID == NullUUID {
// we need to pick one agent at random
// Since apparenty MySQL is not very efficient at picking a row randomly, we load in a temporary number of UUIDs and
// select one randomly in Go; then we just get the row from the database (20170807)
rows, err := db.Query("SELECT UUID FROM Agents")
if err != nil { // NOTE(gwyneth): caught that error when the grid is not operational yet! (20170816)
sendMessageToBrowser("status", "error", fmt.Sprintf("Database error when selecting Agent to run: %v", err)," ")
time.Sleep(10 * time.Second)
continue // now we simply wait...
}
defer rows.Close() // needed? The problem here is with a continue on the check below...
var agentUUIDs []string
tempUUID := ""
for rows.Next() {
err = rows.Scan(&tempUUID)
checkErr(err)
agentUUIDs = append(agentUUIDs, tempUUID)
}
// if we have zero agents, we cannot go on!
// TODO(gwyneth): be more graceful handling this, because the engine will stop forever this way
// TODO(gwyneth): Better to randomly pick an agent from the database, and if none is available, skip a cycle (20170730).
if len(agentUUIDs) == 0 {
sendMessageToBrowser("status", "error", "Error: no Agents found. Engine cannot run. Aborted. Add an Agent and try sending a <code>SIGCONT</code> to restart engine again<br />"," ")
time.Sleep(10 * time.Second)
continue // now we simply wait...
}
// Log.Debug("We got a bunch of UUIDs:", agentUUIDs)
possibleAgentUUID = agentUUIDs[0] // make sure we have at least a valid UUID!!
// Generate a random index, search for it in agentUUIDs; if it's the same one as last time, try again; test for edge case,
// i.e. that we have just 1 Agent in the database. (20170807)
if len(agentUUIDs) > 1 && lastAgentToRunUUID != NullUUID { // edge case: on initialisation, both are set to NullID, so both are equal
for index := 0; lastAgentToRunUUID == possibleAgentUUID; {
index = rand.Intn(len(agentUUIDs))
possibleAgentUUID = agentUUIDs[index]
//if lastAgentToRunUUID != possibleAgentUUID {
// break
//}
// Log.Debug("Index picked:", index, "possibleAgentUUID", possibleAgentUUID, "Last agent was", lastAgentToRunUUID)
}
}
} else {
possibleAgentUUID = userSetAgentUUID
sendMessageToBrowser("status", "info", fmt.Sprintf("Using agent UUID %s set by end-user", possibleAgentUUID), "")
}
lastAgentToRunUUID = possibleAgentUUID
if possibleAgentUUID == NullUUID {
Log.Critical("My logic is still borked!!") // NOTE(gwyneth): if this situation still happens, I need to revisit this! (20170813)
}
err = db.QueryRow("SELECT * FROM Agents where UUID=?", possibleAgentUUID).Scan(
&Agent.UUID,
&Agent.Name,
&Agent.OwnerName,
&Agent.OwnerKey,
&Agent.Location,
&Agent.Position,
&Agent.Rotation,
&Agent.Velocity,
&Agent.Energy,
&Agent.Money,
&Agent.Happiness,
&Agent.Class,
&Agent.SubType,
&Agent.PermURL,
&Agent.LastUpdate,
&Agent.BestPath,
&Agent.SecondBestPath,
&Agent.CurrentTarget,
)
if err != nil || !Agent.OwnerKey.Valid {
sendMessageToBrowser("status", "error", fmt.Sprintf("Error %v: no Agent found for UUID %s, or invalid OwnerKey for this agent. Engine cannot run. Aborted. Fix the database and try sending a <code>SIGCONT</code> to restart engine again<br />", err, possibleAgentUUID)," ")
time.Sleep(10 * time.Second)
continue // wait until situation improves...
}
// do the magic to extract the actual coords
Agent.Coords_xyz = strings.Split(strings.Trim(*Agent.Position.Ptr(), "() \t\n\r"), ",")
// we should extract the region name from Agent.Location, but I'm lazy!
Log.Info("Starting to manipulate Agent", *Agent.Name.Ptr(), " (", *Agent.UUID.Ptr(), ")")
// We need to refresh all the data about cubes and positions again!
// do stuff while it runs, e.g. open databases, search for agents and so forth
Log.Debug("Reloading database for Cubes (Positions) and Obstacles...")
// Load in the 'special' objects (cubes). Because the Master Controllers can be somewhere in here, to save code.
// and a database query, we simply skip all the Master Controllers until we get the most recent one, which gets saved
// The rest of the objects are cubes, so we will need them in the ObjectType array (20170722).
// BUG(gwyneth): Does not work across regions! We will probably need a map of bot controllers for that and check which one to call depending on the region of the current agent; simple, but I'm lazy (20170722).
Cubes = make(map[string]PositionType) // clear array, let the Go garbage collector deal with the memory (20170723)
rows, err := db.Query("SELECT * FROM Positions ORDER BY LastUpdate ASC")
checkErr(err)
for rows.Next() {
err = rows.Scan(
&Position.PermURL,
&Position.UUID,
&Position.Name,
&Position.OwnerName,
&Position.Location,
&Position.Position,
&Position.Rotation,
&Position.Velocity,
&Position.LastUpdate,
&Position.OwnerKey,
&Position.ObjectType,
&Position.ObjectClass,
&Position.RateEnergy,
&Position.RateMoney,
&Position.RateHappiness,
)
checkErr(err)
Position.Coords_xyz = strings.Split(strings.Trim(*Position.Position.Ptr(), "() \t\n\r"), ",")
// check if we got a Master Bot Controller!
if (*Position.ObjectType.Ptr() == "Bot Controller") {
masterController = Position // this will get overwritten until we get the last, most recent one
} else {
Cubes[*Position.UUID.Ptr()] = Position // if not a controller, it must be a cube! add it to array!
}
}
// we need at least ONE masterController, this will be nil if got none (20170807).
if !masterController.PermURL.Valid {
Log.Error(funcName() + ": Major error with database, we need at least one valid masterController to proceed. Sleeping for 10 seconds for user to correct this...")
time.Sleep(10 * time.Second)
continue // go to next iteration, this one has borked data (20170801)
}
// load in everything we found out so far on our region(s) but ignore phantom objects
// end-users ought to set their cubes to phantom as well, or else the agents will think of them as obstacles!
Obstacles = nil
rows, err = db.Query("SELECT * FROM Obstacles WHERE Phantom = 0")
checkErr(err)
for rows.Next() {
err = rows.Scan(
&Object.UUID,
&Object.Name,
&Object.BotKey,
&Object.BotName,
&Object.Type,
&Object.Position,
&Object.Rotation,
&Object.Velocity,
&Object.LastUpdate,
&Object.Origin,
&Object.Phantom,
&Object.Prims,
&Object.BBHi,
&Object.BBLo,
)
checkErr(err)
Object.Coords_xyz = strings.Split(strings.Trim(*Object.Position.Ptr(), "() \t\n\r"), ",")
Obstacles = append(Obstacles, Object)
}
rows.Close()
// Do not trust the database with the exact Agent position: ask the master controller directly
// NOTE(gwyneth): Perhaps it's better to try asking the agent first, and if it refuses answering, try the master controller. (20170813)
// I believe we go through the master controller because the agent might be too busy informing the database about sensor data.
Log.Debug("master controller URL:", *masterController.PermURL.Ptr(), "Agent:", *Agent.Name.Ptr(), "Agent's OwnerKey:", *Agent.OwnerKey.Ptr())
// WHY Agent.Ownerkey?!?! Why not Agent.UUID?!?!?
// The answer is NOT obvious: NPCs created by the master controller are owned by the avatar owning the master controller
// and somehow to contact them we need the ownerkey, which is weird; newer versions of OpenSim are supposed to have fixed
// this by adding a flag for NPCs not to be owned by anyone. Using this might mean to change a lot of code! (20170806)
curPos_raw, err := callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcGetPos")
// NOTE(gwyneth): Apparently the web server will reply to ALL possible requests, even if the Agent doesn't exist any more;
// I still don't know what to do in that situation, so we skip this cycle and try the next one (20170730).
if curPos_raw == "" || curPos_raw == "No response could be obtained" || err != nil {
Log.Error("Error in figuring out the response for agent", *Agent.Name.Ptr(), "so we will try to skip this cycle...")
continue
}
sendMessageToBrowser("status", "info", "Grid reports that agent '" + *Agent.Name.Ptr() + "' is at position: " + curPos_raw + "...</p>\n", "")
// update database with new position
_, err = db.Exec("UPDATE Agents SET Position =? WHERE OwnerKey =?", strings.Trim(curPos_raw, " ()<>"), *Agent.OwnerKey.Ptr())
checkErr(err)
db.Close()
// sanitize
Agent.Coords_xyz = strings.Split(strings.Trim(curPos_raw, " <>()\t\n\r"), ",")
curPos := make([]float64, 3) // to be more similar to the PHP version
Log.Debug("curPos_raw is", curPos_raw)
_, err = fmt.Sscanf(curPos_raw, "<%f, %f, %f>", &curPos[0], &curPos[1], &curPos[2]) // best way to convert strings to floats! (20170728)
checkErr(err)
sendMessageToBrowser("status", "", fmt.Sprintf("Avatar '%s' (%s) raw position was %v; recalculated to: %v<br />", *Agent.Name.Ptr(), *Agent.Name.Ptr(), curPos_raw, curPos), "")
// Now we select where to go to!
// This will eventually become more complex and *possibly* part of the GA (20170811).
// For now, we just see what attribute is more 'urgent' and choose a cube of the appropriate type.
whatCubeTypeNext := "energy" // by default it will be energy
// convert to floats, we could actually change that in the database but I'm lazy... (20170811)
energyAgent, err := strconv.ParseFloat(*Agent.Energy.Ptr(), 64)
checkErr(err)
moneyAgent, err := strconv.ParseFloat(*Agent.Money.Ptr(), 64)
checkErr(err)
happinessAgent, err := strconv.ParseFloat(*Agent.Happiness.Ptr(), 64)
checkErr(err)
// Simple way to make a choice, but this will get much more complicated in the future (I hope!) (20170811)
if moneyAgent < energyAgent {
whatCubeTypeNext = "money"
}
if (happinessAgent < moneyAgent) && (happinessAgent < energyAgent) {
whatCubeTypeNext = "happiness"
}
Log.Debug(*Agent.Name.Ptr(), "has energy:", energyAgent, "money:", moneyAgent, "happiness:", happinessAgent, "so obviously we will pick a", whatCubeTypeNext, "cube to move to.")
// calculate distances to nearest obstacles and cubes
// TODO(gwyneth): these might become globals, outside the loop, so we don't need to declare them
var smallestDistanceToObstacle = 1024.0 // will be used later on
var nearestObstacle ObjectType
var smallestDistanceToCube = 1024.0 // will be used later on
var nearestCube PositionType
obstaclePosition := make([]float64, 3)
cubePosition := make([]float64, 3)
var distance float64
// pretty-print some nice tables for nearest obstacles and nearest cubes (20170806).
outputBuffer := "<div class='table-responsive'><table class='table table-striped table-bordered table-hover'><caption>Obstacles</caption><thead><tr><th>#</th><th>Name</th><th>Position</th><th>Distance</th></tr></thead><tbody>\n"
for k, point := range Obstacles {
_, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &obstaclePosition[0], &obstaclePosition[1], &obstaclePosition[2])
checkErr(err)
distance = calcDistance(curPos, obstaclePosition)
outputBuffer += fmt.Sprintf("<tr><td>%v</td><td>%s</td><td>%v</td><td>%.4f</td></tr>\n", k, *point.Name.Ptr(), *point.Position.Ptr(), distance)
if distance < smallestDistanceToObstacle {
smallestDistanceToObstacle = distance
nearestObstacle = point
}
}
outputBuffer += "</tbody><tfoot><tr><th>#</th><th>Name</th><th>Position</th><th>Distance</th></tr></tfoot></table></div>\n"
sendMessageToBrowser("status", "", outputBuffer, "")
sendMessageToBrowser("status", "info", fmt.Sprintf("Nearest obstacle to agent %s: '%s' (distance: %.4f m)<br />", *Agent.Name.Ptr(), *nearestObstacle.Name.Ptr(), smallestDistanceToObstacle), "")
// now pretty-print nearest cubes (20170806).
outputBuffer = "<div class='table-responsive'><table class='table table-striped table-bordered table-hover'><caption>Cubes (Positions)</caption><thead><tr><th>UUID</th><th>Name</th><th>Position</th><th>ObjectType</th><th>Distance</th></tr></thead><tbody>\n"
for k, point := range Cubes {
_, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &cubePosition[0], &cubePosition[1], &cubePosition[2])
checkErr(err)
distance = calcDistance(curPos, cubePosition)
point.DistanceToAgent = distance // hope this works, we're saving the distance so that later on we can use this as a weight
outputBuffer += fmt.Sprintf("<tr><td>%v</td><td>%s</td><td>%v</td><td>%s</td><td>%.4f</td></tr>\n", k, *point.Name.Ptr(), *point.Position.Ptr(), *point.ObjectType.Ptr(), point.DistanceToAgent)
if distance < smallestDistanceToCube && *point.ObjectType.Ptr() == whatCubeTypeNext {
smallestDistanceToCube = distance
nearestCube = point
}
}
outputBuffer += "</tbody><tfoot><tr><th>UUID</th><th>Name</th><th>Position</th><th>ObjectType</th><th>Distance</th></tr></tfoot></table></div>\n"
sendMessageToBrowser("status", "", outputBuffer, "")
sendMessageToBrowser("status", "info", fmt.Sprintf("Nearest %s cube to agent %s: '%s' (distance: %.4f m)<br />", whatCubeTypeNext, *Agent.Name.Ptr(), *nearestCube.Name.Ptr(), smallestDistanceToCube), "")
/* Idea for the GA:
1. Start with a 20x20 matrix (based loosely on Cosío and Castañeda) around the bot, which contain sensor data (we just sense up to 10 m around the bot). This might need adjustment (i.e. smaller size).
- This represents the space of possible solutions
- Active cube will determine attraction point (see later)
- Chromosomes: randomly generated points (inside the 20x20 matrix) that the robot has to travel. Start perhaps with 50 with a length of 28 (Castañeda use 7 for 10x10 matrix). Points are bounded within the 20x20 matrix
Now evaluate each chromosome with fitness function:
- for each point: see if it's "too near" to an obstacle (potential collision)
- ray casts are more precise, so give it a highest weight (not implemented yet)
- normal sensor data give lower weigth
- we can add modifiers: see number of prims of each obstacle (more prims, more weight, because object might be bigger than predicted); see if the obstacle is an agent (initially: agents might act as deflectors; later: interaction matrix will see if the bot goes nearer to the agent or avoids it)
- for each point: see if it's closest to the cube. Lowest distance reduces weight. In theory, we wish to find the path with the least distance (less energy wasted)
- sort chromosomes according to fitness
- do 20 generations and find next expected point. Move bot to it. Reduce energy calculation on bot. See if it dies!
- repeat for next bot position
20130520 — Results don't converge. It's hard to get the 'bot in less than a 10m radius.
Attempt #2 - use a 10x10 matrix, just 7 points, like Castañeda
Gotshall & Rylander (2002) suggest a population size of about 100-200 for 7 chromosomes
Attempt #3 - Algorithm from Ismail & Sheta was badly implemented!!
Attempt #4 - (to-do) implement Shi & Cui (2010) algorithm for fitness function
Attempt #5 - Shi & Cui (2010) use a strange way to calculate path smoothness. Attempting Qu, Xing & Alexander (2013) which use angles. Modified mutation function, instead of the classical approach (switching two elements in the path), add random ±x, ±y to point
André Neubauer (circular schema theorem, cited by Qu et al.) suggest two-point crossover
Qu et al. suggest sorting path points, after crossover/mutation
*/
/* goal/target/attractor: where the 'bot is going to go next
at some point, this ought to be included in the chromosome as well
for now, we'll hard-code it (walk to the nearest cube)
on stage two, we'll do a simple check:
- see what attributes are lowest
- go to the nearest cube that replenishes the attribute
- since this will be iterated every time the 'bot moves, we hope it won't die from starvation,
as moving elsewhere becomes prioritary
*/
// nearestCube is where we go (20140526 changing it to selected cube by user, named destCube)
var destCube PositionType
// BUG(gwyneth): Somehow, the code below will just be valid once! (20170728) - this needs more testing, I think
// it was a `clear` somewhere at the end of the iteration, but we got to check it. Also, the submit button for
// changing cube/agent does not go away and the visual feedback is weird (20170806).
// Still working on it, it somehow works sometimes, but it's hard to debug because the GA does so many things (20170813).
Log.Info("User-set destination cube for", *Agent.Name.Ptr(), ":", userDestCube.Load().(string), "(NullUUID means no destination manually set)")
if userDestCube.Load().(string) != NullUUID {
destCube = Cubes[userDestCube.Load().(string)]
Log.Info("User has supplied us with a destination cube for", *Agent.Name.Ptr(), "named:", *destCube.Name.Ptr())
} else {
destCube = nearestCube
Log.Info("Automatically selecting nearest cube for", *Agent.Name.Ptr(), "to go:", *destCube.Name.Ptr())
}
// This is just a test without the GA (20170725)
// Commented out in 20170730 — forgot completely about this!!
/*
sendMessageToBrowser("status", "info", "GA will attempt to move agent '" + *Agent.Name.Ptr() + "' to cube '" + *destCube.Name.Ptr() + "' at position " + *destCube.Position.Ptr(), "")
_, err = callURL(*masterController.PermURL.Ptr(), "npc=" + *Agent.OwnerKey.Ptr() + "&command=osNpcMoveToTarget&vector=<" + *destCube.Position.Ptr() + ">&integer=1")
checkErr(err)
*/
time_start := time.Now()
// Genetic algorithm for movement
// generate 50 strings (= individuals in the population) with 28 random points (= 1 chromosome) at curpos ± 10m
population := make([]popType, POPULATION_SIZE) // create a population; unlike PHP, Go has to have a few clues about what is being created (20170726)
// Log.Debug("population len", len(population))
// initialise the slices of chromosomes; Go needs this to know how much memory to allocate (unlike PHP)
for k := range population {
population[k].chromosomes = make([]chromosomeType, CHROMOSOMES)
// Log.Debug("Chromosome", k, "population len", len(population[k].chromosomes))
}
// We calculate now the distance from each point to the destination
// Because this is computationally intensive, we will not repeat it every time during each generation
// Works well unless the destination moves! Then our calculations might be wrong
// But we will catch up on the _next_ iteration (hopefully, unless it moves too fast)
// We also use the best and second best path from a previous run of the GA
// get from the database the last two 'best paths' (if it makes sense)
start_pop := 0 // if we have no best paths, we will generate everything from scratch
// Maybe it makes sense to keep around the last best paths if we're still moving towards the same
// cube; so check for this first, and discard the last best paths if the destination changed
// NOTE(gwyneth): Unlike the PHP version, the Go version deals simultaneously with an automated choice of path as well as manual
// setting of destination, through user input; so the code here is slightly different. We *already* have the
// destination cube in destCube (20170726).
// We already have the cubePosition with the correct data (array of 3 float64 values for x,y,z).
// NOTE(gwyneth): I have a doubt here, when the algorithm runs again, should the agent keep the CurrentTarget in mind? (20170726)
// The PHP code seems to assume that, but it wasn't ready yet for automated runs...
// calculate the center point between current position and target
// needs to be global for path sorting function (Ruhe's algorithm)
// NOTE(gwyneth): in PHP we had a global $centerPoint; Go uses capital letters to designate globality (20170726).
// NOTE(gwyneth): Ruhe's algorithm is not used any more, so we can safely forget this declaration (20170805).
/*
CenterPoint := struct {
x, y, z float64
}{
x: 0.5 * (cubePosition[0] + curPos[0]),
y: 0.5 * (cubePosition[1] + curPos[1]),
z: 0.5 * (cubePosition[2] + curPos[2]),
}
sendMessageToBrowser("status", "", fmt.Sprintf("Center point for this iteration is <%f, %f, %f><br/>", CenterPoint.x, CenterPoint.y, CenterPoint.z), "")
*/
// Now generate from scratch the remaining population
for i := start_pop; i < POPULATION_SIZE; i++ {
population[i].Fitness = 0.0
for y := 0; y < CHROMOSOMES; y++ {
// Ismail & Sheta recommend to use the distance between points as part of the fitness
// edge cases: first point, which is the distance to the current position of the agent
// and last point, which is the distance between the last point and the target
// that's why the first and last point have been inserted differently in the population
// Log.Debug("i", i, "y", y)
if y == 0 { // first point is (approx.) current position
population[i].chromosomes[y].x = math.Trunc(curPos[0])
population[i].chromosomes[y].y = math.Trunc(curPos[1])
population[i].chromosomes[y].z = math.Trunc(curPos[2])
} else if y == (CHROMOSOMES - 1) { // last point is (approx.) position of target
population[i].chromosomes[y].x = math.Trunc(cubePosition[0])
population[i].chromosomes[y].y = math.Trunc(cubePosition[1])
population[i].chromosomes[y].z = math.Trunc(cubePosition[2])
} else { // others are scattered around the current position
population[i].chromosomes[y].x = math.Trunc(curPos[0] + (rand.Float64() * 2)*RADIUS - RADIUS)
if population[i].chromosomes[y].x < 0.0 {
population[i].chromosomes[y].x = 0.0
} else if population[i].chromosomes[y].x > 255.0 {
population[i].chromosomes[y].x = 255.0
}
population[i].chromosomes[y].y = math.Trunc(curPos[1] + (rand.Float64() * 2)*RADIUS - RADIUS)
if population[i].chromosomes[y].y < 0.0 {
population[i].chromosomes[y].y = 0.0
} else if population[i].chromosomes[y].y > 255.0 {
population[i].chromosomes[y].y = 255.0
}
population[i].chromosomes[y].z = math.Trunc((cubePosition[2] + curPos[2])/2) // will work for flat terrain but not more
}
// To implement Shi & Cui (2010) or Qu et al. (2013) we add these distances to obstacles together
// If there are no obstacles in our radius, then we keep it clear
population[i].chromosomes[y].obstacle = RADIUS // anything beyond that we don't care
var point ObjectType
for _, point = range Obstacles {
_, err = fmt.Sscanf(*point.Position.Ptr(), "%f, %f, %f", &obstaclePosition[0], &obstaclePosition[1], &obstaclePosition[2])
checkErr(err)
distance = calcDistance([]float64 {population[i].chromosomes[y].x,
population[i].chromosomes[y].y,
population[i].chromosomes[y].z },
obstaclePosition)
// Shi & Cui and Qu et al. apparently just uses the distance to the nearest obstacle
if distance < population[i].chromosomes[y].obstacle {
population[i].chromosomes[y].obstacle = 1/distance
// we use the inverse here, because if we have many distant obstacles it's
// better than a single one that is close by
}
// TODO(gwyneth): obstacles flagged as ray-casting are far more precise, so they ought to be
// more weighted.
// TODO(gwyneth): obstacles could also have bounding box calculations: bigger objects should
// be more weighted. However, HUGE objects might have holes in it. We ought to
// include the bounding box only for ray-casting, or else navigation would be impossible!
// Note that probably OpenSim raycasts only via bounding boxes (need confirmation)
// so maybe this is never a good approach. Lots of tests to be done here!
// NOTE(gwyneth): The latest version of llRayCast, v3 on BulletSim, does NOT use bounding boxes. Confirmed 20170730.
}
if RADIUS - population[i].chromosomes[y].obstacle < 0.00001 { // we use a delta to deal with rounding errors with floats
population[i].chromosomes[y].obstacle = 0.0
}
// calculate, for this point, its distance to the destination, currently $destCube
// (exploded to array $cubePosition)
// might not need this
population[i].chromosomes[y].distance = calcDistance([]float64 { population[i].chromosomes[y].x,
population[i].chromosomes[y].y,
population[i].chromosomes[y].z},
cubePosition)
// abandoned: initialize smoothness for Shi & Cui
// adopted (20140523): smoothness using angles, like Qu et al.
population[i].chromosomes[y].smoothness = 0.0
population[i].chromosomes[y].angle = 0.0 // maybe initialize it here
} // endfor y
// now sort this path. According to Qu et al. (2013) this gets us a smoother path
// hope it's true, because it's computationally intensive
// (20140523) we're using Ruhe's algorithm for sorting according to angle, hope it works
// (20140524) Abandoned Ruhe, using a simple comparison like Qu et al.
//echo "Before sorting, point i is: "; var_dump(population[i]); echo "<br />\n";
/*
$popsort = substr(population[i], 1, -1);
$first_individual = population[i][0];
$last_individual = population[i][CHROMOSOMES - 1];
usort($popsort, 'point_cmp');
population[i] = array_merge((array)$first_individual, $popsort, (array)$last_individual);
*/
pop := population[i].chromosomes
sort.Slice(pop, func(a, b int) bool {
// NOTE(gwyneth): these is still the old PHP comments, kept here for historical reasons
// global $centerPoint;
/* Attempt #1: Ruhe's algorithm
$theta_a = atan2($a.y - $centerPoint.y, $a.x - $centerPoint.x);
$angle_a = fmod(M_PI - M_PI_4 + $theta_a, 2 * M_PI);
$theta_b = atan2($b.y - $centerPoint.y, $b.x - $centerPoint.x);
$angle_b = fmod(M_PI - M_PI_4 + $theta_a, 2 * M_PI);
if ($angle_a == $angle_b)
return 0;
return ($angle_a < $angle_b) ? -1 : 1;
*/
/*
// Attempt #2: just angles
if ($a.angle == $b.angle)
return 0;
return (abs($a.angle) < abs($b.angle)) ? -1 : 1;
*/
// Attempt #3: Just compare x,y! This is a terrible solution but better than nothing
// using algorithm from Anonymous on http://www.php.net/manual/en/function.usort.php
/*
if ($a.x == $b.x)
{
if ($a.y == $b.y)
{
return 0;
}
elseif ($a.y > $b.y)
{
return 1;
}
elseif ($a.y < $b.y)
{
return -1;
}
}
elseif ($a.x > $b.x)
{
return 1;
}
elseif ($a.x < $b.x)
{
return -1;
}
*/
// Attempt #4: order by shortest distance to the target?
return pop[a].distance < pop[b].distance
})
} // endfor i
// testing printing the current population (with json we get strange results!)
// showPopulation(population, "Current population")
// We're not finished yet! We need to calculate angles between all points (duh!) to establish smoothness
// Let's do it from scratch:
for i := 0; i < POPULATION_SIZE; i++ {
population[i].chromosomes[0].angle = 0.0 // curPos has (obviously) angle 0
for j := 1; j < CHROMOSOMES; j++ {
population[i].chromosomes[j].angle =
math.Atan2(population[i].chromosomes[j].y - population[i].chromosomes[j-1].y,
population[i].chromosomes[j].x - population[i].chromosomes[j-1].x)
// sendMessageToBrowser("status", "", fmt.Sprintf("Pop %v, Chromosome %v - Angle is %v<br />", i, j, population[i].chromosomes[j].angle), "")
}
}
// Initial population done; now loop over generations
for generation := 0; generation < GENERATIONS; generation++ {
// Calculate fitness
// Log.Debug("Generating fitness for generation ", generation, " (out of ", GENERATIONS, ") for agent", *Agent.Name.Ptr(), "...")
// When calculating a new population, each element will have its chromosomes reordered
// So we have no choice but to calculate fitness for all population elements _again_
for i := 0; i < POPULATION_SIZE; i++ {
fitnessW1 := 0.0
fitnessW2 := 0.0
fitnessW3 := 0.0
// note that first point is current location; we start from the second point onwards
for y := 1; y < CHROMOSOMES; y++ {
// Sub-function of Path Length (using Shi & Cui)