No estoy seguro de cuál era el objetivo de este ejercicio de entrenamiento, pero aprendí el nombre de Bob. Éramos un grupo de nosotros en una sala grande y a cada uno se nos asignó un amigo y un enemigo (en secreto, sin información compartida). Damos vueltas por la habitación, tratando de alejarnos de nuestro enemigo y acercarnos a nuestro amigo. Después de un rato nos detuvimos y el entrenador nos preguntó si habíamos identificado a las dos personas que nos tenían como su amigo/enemigo secreto. No lo había hecho, y me preguntaba acerca de las personas que afirmaban que sí. Aquí hay un vídeo de JMP ejecutando una simulación del juego. Hay varios bucles de amigos codificados por colores y puedo ver por qué algunas personas pueden haber identificado amigos fácilmente. Casi todo el mundo también está codificado con un círculo, pero también hay dos pares de diamantes; Estos pobres tipos tienen una relación mutua de amigo/enemigo. También verás flechas vectoriales rojas y verdes en los puntos; las flechas rojas del vector enemigo se acortan rápidamente, excepto en los pares de diamantes. Los vectores amigos verdes unen a los grupos de amigos.
Probablemente lo más interesante de este JSL es la matriz matemática que recopila información de todos los jugadores sin un bucle explícito. (Cerca del comentario separar los puntos abarrotados.) Aleja al jugador actual de los otros jugadores más cercanos calculando sus distancias y ponderando sus vectores por la inversa de la distancia. Aprovecha una división por cero para la distancia del punto actual a sí mismo; el valor faltante no cambia la media ponderada. Cuando miras el video puedes ver que los puntos se esquivan entre sí. Cuando ejecute JSL, consulte los controles deslizantes.
// 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 );
);
);