我不確定這次訓練的目的是什麼,但我確實知道了鮑伯的名字。 我們一群人在一個大房間裡,我們每個人都被分配了一個朋友和一個敵人(秘密地,沒有共享資訊)。 我們在房間裡轉來轉去,試圖遠離我們的敵人,走向我們的朋友。 過了一會兒,我們停下來,教練問我們是否已經認出那兩個讓我們他們的秘密的朋友/敵人。 我沒有,我想知道那些聲稱他們有的人。 這是 JMP 運行遊戲模擬的影片。 有幾個顏色編碼的朋友循環,我可以理解為什麼有些人可能很容易識別朋友。 大多數人也用圓圈編碼,但也有兩對鑽石;這些可憐的人有著共同的朋友/敵人的關係。 您還會在點上看到紅色和綠色向量箭頭;紅色敵人向量箭頭很快就會變短,除了菱形對之外。 綠色的朋友向量將朋友群體拉在一起。
這個 JSL 中最有趣的事情可能是矩陣數學,它在沒有顯式循環的情況下從所有玩家收集資訊。 (評論附近將擁擠的點推開。)它透過計算距離並按距離的倒數對向量進行加權,將當前玩家移離最近的其他玩家。 它利用當前點與其自身的距離除以零;缺失值不會改變加權平均值。 當您觀看影片時,您可以看到這些點正在互相躲避。 當您執行 JSL 時,請檢查滑桿。
// Friend & Foe game
nPlayers = 39; // 3 or more required, by 9 there are usually 2 disjoint friend loops.
// tweaked for many players, smaller numbers may need some help passing the tests below.
// also tweaked for 4K images, smaller will be faster, font sizes etc...
//directory = "f:/FriendFoe/Pics/";
//Delete Directory( directory );
//Create Directory( directory );
nIterations = 14000;
result = J( nIterations, 2 * nPlayers/*x-y coords*/, . ); // collect for analysis?
fsize = 950; // fits my screen. tweak as needed.
xyaxisMin = -5;
xyaxisMax = 5;
self = 1 :: nPlayers;
While( 1, // keep trying until additional rules are satisfied
// randomly assign friends and foes until
// no player has the same other player for
// both friend and foe and no player is its
// own friend or foe.
attempts = 0;
While( 1, // keep trying until...
friends = Random Shuffle( self );
foes = Random Shuffle( self );
If( !Any( friends == foes ) & !Any( friends == self ) & !Any( foes == self ),
Break()
);
attempts += 1;
Show( attempts ); // surprisingly, this never gets too bad.
);
dtGame = New Table( "game",
invisible,
addrows( nPlayers ),
New Column( "id", Numeric, "nominal", Set Values( self ) ),
New Column( "x", Numeric, "Continuous", Format( "Best", 6 ), Set Values( J( nPlayers, 1, Random Uniform( -1, 1 ) ) ) ),
New Column( "y", Numeric, "Continuous", Format( "Best", 6 ), Set Values( J( nPlayers, 1, Random Uniform( -1, 1 ) ) ) ),
New Column( "friend", Numeric, "nominal", Set Values( friends ) ),
New Column( "foe", Numeric, "nominal", Set Values( foes ) ),
// needed for the graph's green and red vectors...
New Column( "xfriend", Numeric, "Continuous", Format( "Best", 6 ), Set each Value( 0 ) ),
New Column( "yfriend", Numeric, "Continuous", Format( "Best", 6 ), Set each Value( 0 ) ),
New Column( "xfoe", Numeric, "Continuous", Format( "Best", 6 ), Set each Value( 0 ) ),
New Column( "yfoe", Numeric, "Continuous", Format( "Best", 6 ), Set each Value( 0 ) ),
New Column( "loopid", Numeric, "Continuous", Format( "Best", 6 ), Set each Value( . ) )
);
// color code the friend loops just to make the video pretty
nLoops = 0;
For( irow = 1, irow <= N Rows( dtGame ), irow += 1,
If( Is Missing( dtGame:loopid[irow] ),
nLoops += 1;
// the loop of friends should come back to the start
For( jrow = dtGame:friend[irow], Is Missing( dtGame:loopid[jrow] ), jrow = dtGame:friend[jrow],
dtGame:loopid[jrow] = nLoops;
lastjrow = jrow;
);
If( lastjrow != irow,
Throw( "weird loop" )
);
)
);
// above counted disjoint friend loops
If( nloops >= 4 | nPlayers < 20,
dtGame << Color or Mark by Column( :loopid, Color Theme( "Jet" ), Marker( 0 ) );
For Each Row( dtGame, Row State() = Combine States( Marker State( 12/*8circle*/ ), (Row State()) ) );
paired = 0;
// above set the color and circle marker, now look for the friend-foe pairs and
// set a diamond marker.
For Each( {r}, dtGame << getrowswhere( dtGame:friend[dtGame:foe] == dtGame:id | dtGame:foe[dtGame:friend] == dtGame:id ),
Show( r, (Row State( dtGame, r )) );
paired += 1;
Row State( dtGame, r ) = Combine States( Marker State( 16/*4diamond*/ ), (Row State( dtGame, r )) );
);
Show( paired );
Wait( 0 );
If( paired >= 4 | nPlayers < 6,
Break() // finally! found a set we like.
);
);
Close( dtGame, nosave );
);
New Window( "dance",
pvlb = V List Box(
// extra spacer boxes to force the window to size for the video...un-tweak them is fine
//Spacer Box( size( 4200, 100 ), color( "white" ) ),
hlb = H List Box(
//Spacer Box( size( 20, 2500 ), color( "white" ) ),
vlb = V List Box(),
gb = dtGame << Graph Builder(
Size( fsize, fsize ),
Show Control Panel( 0 ),
Show Legend( 0 ),
Show Title( 0 ),
Show X Axis Title( 0 ),
Show Y Axis Title( 0 ),
Variables( X( :x ), Y( :y ) ),
Elements( Points( X, Y, Legend( 5 ) ) ),
SendToReport(
Dispatch( {}, "Graph Builder", OutlineBox, {Set Title( "" ), Image Export Display( Normal )} ),
Dispatch( {}, "Graph Builder", FrameBox, {Marker Size( 18 )} )
)
),
//ndb = dtGame << newdatabox// add an embedded data table to the display for the video, makes it a bit slower
,
//Spacer Box( size( 10, 2500 ), color( "white" ) )
),
//Spacer Box( size( 4200, 10 ), color( "white" ) )
)
);
try(ndb << Close Side Panels); // tighten up the display by removing the left side panels of the embedded table
temperature = .;
// add the script to the GraphBuilder to show the connections
Report( gb )[framebox( 1 )] << addgraphicsscript(
Local( {irow, friendlength, foelength, myPos, ifriend, ifoe, foepos, friendpos},
friendlength = 0;
foelength = 0;
For( irow = 1, irow <= N Rows( dtGame ), irow += 1, // every row in the table has a connection to a friend and a foe
myPos = dtGame[irow, {x, y}];
myFoeVec = 1000 * Exp( ArrowScale ) * dtGame[irow, {xFoe, yFoe}];
foelen = Sqrt( Sum( myFoeVec ^ 2 ) );
minlen = 0.06; // tune minimum arrow length for dot size
If( foelen < minlen,
myFoeVec *= minlen / foelen
);
myFriendVec = 1000 * Exp( ArrowScale ) * dtGame[irow, {xFriend, yFriend}];
friendlen = Sqrt( Sum( myFriendVec ^ 2 ) );
If( friendlen < minlen,
myFriendVec *= minlen / friendlen
);
ifriend = dtGame:friend[irow];
ifoe = dtGame:foe[irow];
foepos = dtGame[ifoe, {x, y}];
friendpos = dtGame[ifriend, {x, y}];
Transparency( 1 );
foelength += Sqrt( Sum( (mypos - foepos) ^ 2 ) );
Transparency( .2 );
Pen Color( "black" );
Pen Size( 3 );
Line( {mypos[1], mypos[2]}, {foepos[1], foepos[2]} );
Transparency( 1 );
Pen Color( "dark red" );
Pen Size( 7 );
Arrow( {mypos[1], mypos[2]}, {mypos[1] + myFoeVec[1], mypos[2] + myFoeVec[2]} );
friendlength += Sqrt( Sum( (mypos - friendpos) ^ 2 ) );
Transparency( .2 );
Pen Color( "black" );
Pen Size( 3 );
Line( {mypos[1], mypos[2]}, {friendpos[1], friendpos[2]} );
Transparency( 1 );
Pen Color( "dark green" );
Pen Size( 7 );
Arrow( {mypos[1], mypos[2]}, {mypos[1] + myFriendVec[1], mypos[2] + myFriendVec[2]} );
);
Transparency( 1 );
Pen Color( "black" );
Text Color( "black" );
Text(
rightjustified,
{X Origin() + X Range() - X Range() / 20, Y Origin() + Y Range() / 50},
"foe/friend " || Char( foelength / friendlength, 5, 3 )
);
Text( {X Origin() + X Range() / 20, Y Origin() + Y Range() / 50}, "Temperature " || Char( temperature, 7, 2 ) );
)
);
stop = 0;
Report( gb ) << onclose(
stop = 1;
dtGame << setdirty( 0 );
);
step = 0.00001;
PushFoe = Log( 1.0 * step );
PullFriend = Log( 1.0 * step );
PullCenter = Log( 0.1 * step );
PushNearest = Log( 30.0 * step );
ArrowScale = Log( 20 );
vlb << append( H List Box( Text Box( "push foe" ), Slider Box( Log( step * .001 ), Log( step * 50 ), PushFoe ) ) );
vlb << append( H List Box( Text Box( "pull friend" ), Slider Box( Log( step * .001 ), Log( step * 50 ), PullFriend ) ) );
vlb << append( H List Box( Text Box( "pull center" ), Slider Box( Log( step * .001 ), Log( step * 50 ), PullCenter ) ) );
vlb << append( H List Box( Text Box( "push near" ), Slider Box( Log( step * .001 ), Log( step * 5 ), PushNearest ) ) );
vlb << append( H List Box( Text Box( "arrow scale" ), Slider Box( Log( .1 ), Log( 1000 ), ArrowScale ) ) );
Wait( 0.1 );
hlb << maximizewindow;
iteration = 0;
While( !stop & (iteration += 1) <= nIterations,
temperature = Interpolate( // annealing schedule, partially chosen for pretty video
iteration, // high temperature at the beginning makes bigger jumps
0.00 * nIterations, 5000,
0.20 * nIterations, 4000,
0.40 * nIterations, 3000,
0.60 * nIterations, 2000,
0.80 * nIterations, 1000,
1.00 * nIterations, 500
);
oldPos = dtGame[0, {x, y}];
dtGame << Begin Data Update;
For( irow = 1, irow <= N Rows( dtGame ) & !stop, irow += 1,
myPos = oldPos[irow, 0];
foepos = oldPos[dtGame:foe[irow], 0];
friendpos = oldPos[dtGame:friend[irow], 0];
// push crowded points apart...
deltaPos = Repeat( myPos, nPlayers ) - oldPos;
push = 1 / (deltapos[0, 1] ^ 2 + deltapos[0, 2] ^ 2);// closer pushes harder, I'm a missing value.
angles = ATan( deltaPos[0, 2], deltaPos[0, 1] );
deltaPos = temperature * Exp( PushNearest ) * (Mean( Cos( angles ) :*</img> push ) || Mean( Sin( angles ) :*</img> push ));
// pull back to center, more pull far from center
dist = Exp( PullCenter ) * Sqrt( myPos[1] ^ 2 + myPos[2] ^ 2 );
centering = (myPos :*</img> dist);
deltaPos -= temperature * centering;
// move away from foe. push harder when near.
delta = mypos - foepos;
push = 1 / (delta[1] ^ 2 + delta[2] ^ 2);
angle = ATan( delta[2], delta[1] );
foepush = Exp( PushFoe ) * (Cos( angle ) * push || Sin( angle ) * push);
dtGame[irow, {xfoe, yfoe}] = foepush;
deltaPos += temperature * foepush;
// move towards friend. pull harder when far.
delta = mypos - friendpos;
push = Sqrt( delta[1] ^ 2 + delta[2] ^ 2 );
angle = ATan( delta[2], delta[1] );
friendpull = -Exp( PullFriend ) * (Cos( angle ) * push || Sin( angle ) * push);
dtGame[irow, {xfriend, yfriend}] = friendpull;
deltaPos += temperature * friendpull;
// apply the forces...
While( Any( Abs( deltaPos ) > 2 ), deltaPos /= 2 );// limit
dtGame[irow, {x, y}] += deltaPos;
);
dtGame << End Data Update;
If( !stop,
xyrange = Range( dtGame[0, {x, y}] );
xydelta = xyrange[2] - xyrange[1];
targetxymin = xyrange[1] - 3 * xydelta / 100;
xyaxisMin = (99 * xyaxisMin + targetxymin) / 100;
targetxymax = xyrange[2] + 3 * xydelta / 100;
xyaxisMax = (99 * xyaxisMax + targetxymax) / 100;
Report( gb )[framebox( 1 )] << xaxis( {Format( "Fixed Dec", 12, 1 ), Min( xyaxisMin ), Max( xyaxisMax ), Inc( 0.5 )} ) <<
yaxis( {Format( "Fixed Dec", 12, 1 ), Min( xyaxisMin ), Max( xyaxisMax ), Inc( 0.5 )} );
wait(.01);
// pvlb << savepicture( directory || Right( Char( 1e9 + iteration ), 8 ) || ".png", "png" );
result[iteration, 0] = Shape( dtGame[0, {x, y}], 1, 2 * nPlayers );
);
);