Je ne suis pas sûr du but de cet exercice de formation, mais j'ai appris le nom de Bob. Nous étions plusieurs dans une grande pièce et on nous assignait chacun un ami et un ennemi (en secret, aucune information partagée). Nous avons parcouru la pièce, essayant de nous éloigner de notre ennemi et de nous rapprocher de notre ami. Au bout d'un moment, nous nous sommes arrêtés et l'entraîneur nous a demandé si nous avions identifié les deux personnes qui nous tenaient. leur ami/ennemi secret. Je ne l'avais pas fait, et je me suis interrogé sur les gens qui prétendaient le faire. Voici une vidéo de JMP exécutant une simulation du jeu. Il existe plusieurs boucles d'amis codées par couleur, et je peux comprendre pourquoi certaines personnes pourraient avoir facilement identifié leurs amis. Presque tout le monde est également codé avec un cercle, mais il existe également deux paires de diamants ; ces pauvres gens ont une relation mutuelle ami/ennemi. Vous verrez également des flèches vectorielles rouges et vertes sur les points ; les flèches vectorielles rouges de l'ennemi se raccourcissent rapidement, sauf sur les paires de diamants. Les vecteurs amis verts rassemblent les groupes d’amis.
La chose la plus intéressante dans ce JSL est probablement le calcul matriciel qui rassemble les informations de tous les joueurs sans boucle explicite. (À côté du commentaire écartez les points encombrés.) Il éloigne le joueur actuel des autres joueurs les plus proches en calculant leurs distances et en pondérant leurs vecteurs par l'inverse de la distance. Il profite d'une division par zéro pour la distance du point courant à lui-même ; la valeur manquante ne modifie pas la moyenne pondérée. Lorsque vous regardez la vidéo, vous pouvez voir que les points s'esquivent. Lorsque vous exécutez le JSL, consultez les curseurs.
// 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 );
);
);