この訓練に何の意味があるのか分かりませんが、ボブの名前は分かりました。 広い部屋に私たちが大勢いて、それぞれに敵と味方が割り当てられました(秘密裏に、情報は共有されませんでした)。 私たちは部屋中を歩き回り、敵から離れて友人の方に進もうとした。 しばらくして私たちは立ち止まり、トレーナーは私たちを乗せた二人の人物を特定したかどうか尋ねました。彼らの秘密の友人/敵。 私はそうしなかったし、そう主張した人々について疑問に思った。 これは、JMP がゲームのシミュレーションを実行しているビデオです。 色分けされた友達ループがいくつかあり、友達を簡単に特定できる人がいるのもわかります。 ほとんどの人は円でコード化されていますが、ダイヤモンドのペアも 2 つあります。これらの貧しい人々はお互いに友人/敵の関係にあります。 ポイント上に赤と緑のベクトル矢印も表示されます。赤い敵のベクトル矢印は、ダイヤモンドのペアを除いてすぐに短くなります。 緑の友人ベクトルは友人グループを結び付けます。
おそらくこの 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 );
);
);