이 훈련의 요점이 무엇인지는 잘 모르겠지만 Bob의 이름은 알게 되었습니다. 큰 방에 우리 여러 명이 있었고, 우리는 각각 친구와 적이 지정되었습니다(비밀스럽게, 공유된 정보는 없습니다). 우리는 적에게서 멀어지고 친구를 향해 움직이려고 방 안을 돌아다녔습니다. 잠시 후 우리는 멈췄고 트레이너는 우리를 데려온 두 사람이 누구인지 물었습니다. 그들의 비밀의 친구/적. 나는 그렇지 않았고, 그렇게 주장하는 사람들이 궁금했습니다. 다음은 JMP가 게임 시뮬레이션을 실행하는 동영상입니다. 여러 가지 색상으로 구분된 친구 루프가 있으며, 일부 사람들이 친구를 쉽게 식별할 수 있는 이유를 알 수 있습니다. 대부분의 사람들은 원으로 코드화되어 있지만 두 개의 다이아몬드 쌍도 있습니다. 이 불쌍한 사람들은 서로 친구/적 관계를 맺고 있습니다. 또한 포인트에 빨간색과 녹색 벡터 화살표가 표시됩니다. 다이아몬드 쌍을 제외하고 빨간색 적 벡터 화살표는 빠르게 짧아집니다. 녹색 친구 벡터는 친구 그룹을 하나로 끌어당깁니다.
아마도 이 JSL에서 가장 흥미로운 점은 명시적인 루프 없이 모든 플레이어로부터 정보를 수집하는 행렬 수학일 것입니다. (댓글 근처 밀집된 지점을 분리합니다.) 거리를 계산하고 거리의 역수로 벡터에 가중치를 부여하여 현재 플레이어를 가장 가까운 다른 플레이어로부터 멀리 이동시킵니다. 현재 지점과 그 자체의 거리에 대해 0으로 나누는 이점을 활용합니다. 결측값은 가중 평균을 변경하지 않습니다. 영상을 보시면 포인트들이 서로 피하고 있는 모습을 보실 수 있습니다. 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 );
);
);