1
+ /* jshint esversion: 6, asi: true */
2
+ /* globals require */
3
+
1
4
var util = require ( 'util' )
2
5
var css = require ( 'dom-css' )
3
6
var fs = require ( 'fs' )
@@ -43,6 +46,20 @@ function filenameFrom(data) {
43
46
return experimentID + '-decompressed.json'
44
47
}
45
48
49
+ function sum ( vector ) {
50
+ return vector . reduce ( ( accumulator , currentValue ) => accumulator + currentValue , 0 ) ;
51
+ }
52
+
53
+ function softmax ( vector , temperature = 1 ) {
54
+ /* The softmax activation function. */
55
+ var new_vector = vector . map ( x => Math . pow ( x , temperature ) ) ;
56
+ if ( sum ( new_vector ) ) {
57
+ return new_vector . map ( x => x / sum ( new_vector ) ) ;
58
+ } else {
59
+ return new_vector . map ( _ => vector . length ) ;
60
+ }
61
+ }
62
+
46
63
var acsg = { } // Module namespace
47
64
48
65
acsg . Browser = ( function ( ) {
@@ -56,6 +73,7 @@ acsg.Browser = (function () {
56
73
var backgroundRngFunc = seedrandom ( this . now ( ) )
57
74
this . rBackground = new Rands ( backgroundRngFunc )
58
75
this . scoreboard = document . getElementById ( 'score' )
76
+ this . bonus = document . getElementById ( 'dollars' )
59
77
this . clock = document . getElementById ( 'clock' )
60
78
this . data = [ ]
61
79
this . background = [ ]
@@ -87,8 +105,9 @@ acsg.Browser = (function () {
87
105
return performance . now ( )
88
106
}
89
107
90
- Browser . prototype . updateScoreboard = function ( score ) {
91
- this . scoreboard . innerHTML = score
108
+ Browser . prototype . updateScoreboard = function ( ego ) {
109
+ this . scoreboard . innerHTML = ego . score
110
+ this . bonus . innerHTML = ego . payoff . toFixed ( 2 )
92
111
}
93
112
94
113
Browser . prototype . updateClock = function ( t ) {
@@ -229,7 +248,7 @@ acsg.CLI = (function () {
229
248
// Noop
230
249
}
231
250
232
- CLI . prototype . updateScoreboard = function ( score ) {
251
+ CLI . prototype . updateScoreboard = function ( ego ) {
233
252
// Noop
234
253
}
235
254
@@ -409,6 +428,7 @@ acsg.Player = (function () {
409
428
this . teamIdx = Math . floor ( Math . random ( ) * teamColors . length )
410
429
this . color = config . color || teamColors [ this . teamIdx ]
411
430
this . score = config . score || 0
431
+ this . payoff = 0
412
432
413
433
return this
414
434
}
@@ -557,6 +577,9 @@ acsg.Game = (function () {
557
577
this . opts . BLOCK_SIZE = opts . BLOCK_SIZE || 15
558
578
this . opts . BLOCK_PADDING = opts . BLOCK_PADDING || 1
559
579
this . opts . BOT_STRATEGY = opts . BOT_STRATEGY || 'random'
580
+ this . opts . INTERGROUP_COMPETITION = opts . INTERGROUP_COMPETITION || 1
581
+ this . opts . INTRAGROUP_COMPETITION = opts . INTRAGROUP_COMPETITION || 1
582
+ this . opts . DOLLARS_PER_POINT = opts . DOLLARS_PER_POINT || 0.02
560
583
this . UUID = uuidv4 ( )
561
584
this . replay = false
562
585
this . humanActions = [ ]
@@ -604,14 +627,19 @@ acsg.Game = (function () {
604
627
}
605
628
606
629
Game . prototype . serializeActions = function ( ) {
607
- return JSON . stringify ( {
630
+ var data = {
608
631
'id' : this . UUID ,
609
632
'data' : {
610
633
'actions' : this . humanActions ,
611
634
'timestamps' : this . humanActionTimestamps
612
635
} ,
613
636
'config' : opts
614
- } )
637
+ }
638
+ if ( this . opts . INCLUDE_HUMAN && this . world . players && this . world . players [ 0 ] ) {
639
+ data . data . score = this . world . players [ 0 ] . score
640
+ data . data . payoff = this . world . players [ 0 ] . payoff . toFixed ( 2 )
641
+ }
642
+ return JSON . stringify ( data )
615
643
}
616
644
617
645
Game . prototype . serializeFullState = function ( ) {
@@ -665,6 +693,58 @@ acsg.Game = (function () {
665
693
}
666
694
}
667
695
696
+ Game . prototype . computePayoffs = function ( ) {
697
+ /* Compute payoffs from scores.
698
+
699
+ A player's payoff in the game can be expressed as the product of four
700
+ factors: the grand total number of points earned by all players, the
701
+ (softmax) proportion of the total points earned by the player's group,
702
+ the (softmax) proportion of the group's points earned by the player,
703
+ and the number of dollars per point.
704
+
705
+ Softmaxing the two proportions implements intragroup and intergroup
706
+ competition. When the parameters are 1, payoff is proportional to what
707
+ was scored and so there is no extrinsic competition. Increasing the
708
+ temperature introduces competition. For example, at 2, a pair of groups
709
+ that score in a 2:1 ratio will get payoff in a 4:1 ratio, and therefore
710
+ it pays to be in the highest-scoring group. The same logic applies to
711
+ intragroup competition: when the temperature is 2, a pair of players
712
+ within a group that score in a 2:1 ratio will get payoff in a 4:1
713
+ ratio, and therefore it pays to be a group's highest-scoring member. */
714
+ var group_info , group_scores , ingroup_players ,
715
+ ingroup_scores , intra_proportions , inter_proportions ,
716
+ p , i ;
717
+ var player_groups = { }
718
+ var total_payoff = 0
719
+ var player = this . world . players [ 0 ]
720
+
721
+ for ( i = 0 ; i < this . world . players . length ; i ++ ) {
722
+ p = this . world . players [ i ]
723
+ group_info = player_groups [ p . teamIdx ]
724
+ if ( group_info === undefined ) {
725
+ player_groups [ p . teamIdx ] = group_info = { players : [ ] , scores : [ ] , total : 0 }
726
+ }
727
+ group_info . players . push ( p )
728
+ group_info . scores . push ( p . score )
729
+ group_info . total += p . score
730
+ total_payoff += p . score
731
+ }
732
+ group_scores = Object . values ( player_groups ) . map ( g => g . total ) ;
733
+ group_info = player_groups [ player . teamIdx ] ;
734
+ ingroup_players = group_info . players
735
+ ingroup_scores = group_info . scores
736
+ intra_proportions = softmax (
737
+ ingroup_scores , this . opts . INTRAGROUP_COMPETITION
738
+ ) ;
739
+ player . payoff = total_payoff * intra_proportions [ 0 ]
740
+
741
+ inter_proportions = softmax (
742
+ group_scores , this . opts . INTERGROUP_COMPETITION
743
+ ) ;
744
+ player . payoff *= inter_proportions [ player . teamIdx ]
745
+ player . payoff *= this . opts . DOLLARS_PER_POINT
746
+ }
747
+
668
748
Game . prototype . run = function ( callback ) {
669
749
var self = this
670
750
var callback = callback || function ( ) { console . log ( 'Game finished.' ) }
@@ -701,14 +781,17 @@ acsg.Game = (function () {
701
781
// Carry out human action.
702
782
lastHumanActionIdx += 1
703
783
ego . move ( self . humanActions [ lastHumanActionIdx ] )
704
- self . ui . updateScoreboard ( ego . consume ( ) )
784
+ ego . consume ( )
785
+ self . ui . updateScoreboard ( ego )
705
786
self . world . recordStateAt ( nextHumanT )
706
787
}
788
+ self . computePayoffs ( )
707
789
}
708
790
709
791
self . ui . updateGrid ( self . world )
710
792
self . ui . updateClock ( self . opts . DURATION - elapsedTime )
711
793
794
+ self . computePayoffs ( )
712
795
if ( lastBotActionIdx >= botMotion . botIds . length - 1 ) {
713
796
if ( ! self . gameOver ) {
714
797
self . gameOver = true
0 commit comments