Javascript: mueve una estrella al azar si se reduce la distancia media
(con una animación del proceso)
Esto no da una animación tan ocupada como mi primera respuesta, ya que tiene largos períodos sin movimiento ya que las posibles reorganizaciones se prueban y rechazan. Sin embargo, el resultado final tiene una distancia media menor, por lo que este método es una mejora.
El enfoque sigue siendo muy simple:
- Elige una estrella al azar
- Muévelo una distancia aleatoria en una dirección aleatoria
- Si se reduce la distancia media, mantenga la nueva posición
Este proceso se repite una gran cantidad de veces, disminuyendo gradualmente la cantidad de movimiento de las estrellas. La elección aleatoria de la distancia para moverse está sesgada hacia distancias más pequeñas, por lo que el progreso está en pequeñas alteraciones intercaladas con el salto ocasional más grande. Cada paso lleva más tiempo que en mi primera respuesta, ya que medir la distancia media es un proceso lento que requiere muestrear todo el cantón.
Como lo requiere la pregunta, esto no usa la función aleatoria incorporada, sino que usa xorshift .
Gran parte del código cubre la configuración y la animación: la parte que aplica el algoritmo es la función adjustStars.
Código
Puede ver el proceso en curso en el Fragmento de pila a continuación.
stars = [];
timeoutId = 0;
resetRandomNumberGenerator();
function resetRandomNumberGenerator() {
rng_x = 114; // Numbers for the random number generator.
rng_y = 342;
rng_z = 982;
rng_w = 443;
}
$(document).ready(function() {
c = document.getElementById('canton');
ctx = c.getContext('2d');
resizeCanvas();
});
function stop() {
clearTimeout(timeoutId);
}
function arrange() {
clearTimeout(timeoutId);
resetStars();
resetRandomNumberGenerator();
maxStepSize = Math.min(cantonWidth, cantonHeight) / 16;
adjustStars(maxStepSize, 7000, 15000);
}
function resizeCanvas() {
cantonWidth = parseFloat($('#width').val());
cantonHeight = parseFloat($('#height').val());
starRadius = cantonHeight / 20;
document.getElementById('canton').width = cantonWidth;
document.getElementById('canton').height = cantonHeight;
ctx.fillStyle = 'white';
resetStars();
}
function resetStars() {
stop();
stars = [];
population = parseInt($('#stars').val(), 10);
shortSide = Math.floor(Math.sqrt(population));
longSide = Math.ceil(population / shortSide);
if (cantonWidth < cantonHeight) {
horizontalStars = shortSide;
verticalStars = longSide;
} else {
horizontalStars = longSide;
verticalStars = shortSide;
}
horizontalSpacing = cantonWidth / horizontalStars;
verticalSpacing = cantonHeight / verticalStars;
for (var i = 0; i < population; i++) {
x = (0.5 + (i % horizontalStars)) * horizontalSpacing;
y = (0.5 + Math.floor(i / horizontalStars)) * verticalSpacing;
stars.push([x, y]);
}
drawStars();
updateOutputText();
}
function adjustStars(stepSize, maxSteps, numberOfPoints) {
$('#stepsRemaining').text(maxSteps + ' steps remaining');
var points = randomPoints(numberOfPoints);
currentMean = meanDistance(stars, points);
potentialStars = shiftedStars(stepSize);
potentialMean = meanDistance(potentialStars, points);
if (potentialMean < currentMean) {
stars = potentialStars;
}
drawStars();
updateOutputText();
if (maxSteps > 0) {
timeoutId = setTimeout(adjustStars, 10, stepSize * 0.999, maxSteps - 1, numberOfPoints);
}
}
function shiftedStars(stepSize) {
shifted = [];
chosenOne = Math.floor(xorshift() * stars.length);
for (i = 0; i < stars.length; i++) {
star = stars[i];
x = star[0];
y = star[1];
if (i === chosenOne) {
for (n = 0; n < 10; n++) {
x += xorshift() * stepSize;
x -= xorshift() * stepSize;
y += xorshift() * stepSize;
y -= xorshift() * stepSize;
}
if (x < 0) x = 0;
if (x > cantonWidth) x = cantonWidth;
if (y < 0) y = 0;
if (y > cantonHeight) y = cantonHeight;
}
shifted.push([x, y]);
}
return shifted;
}
function meanDistance(arrayOfStars, arrayOfPoints) {
var totalDistance = 0;
for (i = 0; i < arrayOfPoints.length; i++) {
point = arrayOfPoints[i];
x = point[0];
y = point[1];
totalDistance += nearestStarDistance(x, y, arrayOfStars);
}
return totalDistance / arrayOfPoints.length;
}
function randomPoints(numberOfPoints) {
var arrayOfPoints = [];
for (i = 0; i < numberOfPoints; i++) {
x = xorshift() * cantonWidth;
y = xorshift() * cantonHeight;
arrayOfPoints.push([x, y]);
}
return arrayOfPoints;
}
function updateOutputText() {
starText = '';
for (var i = 0; i < stars.length; i++) {
starText += stars[i][0] + ', ' + stars[i][1] + '\n';
}
$('#coordinateList').text(starText);
}
function xorshift() {
rng_t = rng_x ^ (rng_x << 11);
rng_x = rng_y;
rng_y = rng_z;
rng_z = rng_w;
rng_w = rng_w ^ (rng_w >> 19) ^ rng_t ^ (rng_t >> 8);
result = rng_w / 2147483648
return result
}
function nearestStarDistance(x, y, starsToUse) {
var distances = [];
for (var i = 0; i < stars.length; i++) {
star = starsToUse[i];
distances.push(distance(x, y, star[0], star[1]));
}
minimum = Infinity;
for (i = 0; i < distances.length; i++) {
if (distances[i] < minimum) {
minimum = distances[i];
}
}
return minimum;
}
function distance(x1, y1, x2, y2) {
var x = x2 - x1;
var y = y2 - y1;
return Math.sqrt(x * x + y * y);
}
function drawStars() {
ctx.clearRect(0, 0, cantonWidth, cantonHeight);
for (i = 0; i < stars.length; i++) {
star = stars[i];
x = star[0];
y = star[1];
drawStar(x, y);
}
}
function drawStar(x, y) {
ctx.beginPath();
ctx.moveTo(x, y - starRadius);
ctx.lineTo(x - 0.588 * starRadius, y + 0.809 * starRadius);
ctx.lineTo(x + 0.951 * starRadius, y - 0.309 * starRadius);
ctx.lineTo(x - 0.951 * starRadius, y - 0.309 * starRadius);
ctx.lineTo(x + 0.588 * starRadius, y + 0.809 * starRadius);
ctx.fill();
}
canvas {
margin: 0;
border: medium solid gray;
background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input id='stars' onchange='resetStars()' type='number' value='13' min='13' max='50' maxlength='2' step='1'>stars
<br>
<input id='width' onchange='resizeCanvas()' type='number' value='494' min='1' max='500' maxlength='3' step='any'>width
<br>
<input id='height' onchange='resizeCanvas()' type='number' value='350' min='1' max='500' maxlength='3' step='any'>height
<br>
<button type='button' onclick='resetStars()'>Reset Stars</button>
<button type='button' onclick='arrange()'>Arrange Stars</button>
<button type='button' onclick='stop()'>Stop</button>
<textarea id='stepsRemaining' rows='1' readonly></textarea>
<br>
<canvas id='canton' width='494' height='350'></canvas>
<br>
<textarea id='coordinateList' rows='50' cols='40' readonly></textarea>
Salida para 50 estrellas
(ancho = 1.4, altura = 1.0)
Distancia media estimada en 0.06402754713808706.
Coordenadas
0.08147037630270487, 0.07571240182553095
0.24516777356538358, 0.0803538189052793
0.431021735247462, 0.07821284835132788
0.6001163609128221, 0.08278495286739646
0.7668077034213632, 0.0821321119375313
0.941333266969696, 0.08040530195264808
1.1229190363750599, 0.07255685332834291
1.3074771164489858, 0.07681674948141588
0.09227450444336446, 0.2257047798057907
0.33559513774978766, 0.20668611954667682
0.5182463448452704, 0.23841239342827816
0.6630614113293541, 0.26097114328053417
0.821886619004045, 0.23577904321258844
1.012597304977012, 0.23308200382761057
1.174938874706673, 0.22593017096601203
1.3285181935709358, 0.24024108928169902
0.0746772556909648, 0.3920030109869904
0.23006559905554042, 0.3204287339854068
0.4086004105498357, 0.3507788129168045
0.5669847710831315, 0.4371913211100495
0.7399474422203116, 0.41599441829489137
0.9099913571857917, 0.36933063808924294
1.1170137101288482, 0.3905679602615213
1.3037811235560612, 0.3979526346564911
0.09290206345982034, 0.5678420747594305
0.23463227399157258, 0.47552307265325633
0.4042403660145938, 0.5030345851947539
0.6611151741402685, 0.5918138006294138
0.8237963249937061, 0.5663224022272697
0.9812774216782155, 0.5032518469083094
1.146386501309064, 0.570255729516661
1.3185563715676663, 0.5571870810112576
0.07541940949872694, 0.7356649763259809
0.2877585652075202, 0.6321535875762999
0.4952646673275116, 0.6343336480073624
0.6965646728710738, 0.9178076185211137
0.7903485281657828, 0.7508031981325222
0.9774998621426763, 0.6683301268754337
1.1539480102558823, 0.7513836972857155
1.3177199931376755, 0.7245296168327016
0.22215183098388988, 0.7769843436963862
0.4048364942297627, 0.7779653803681718
0.5021290208205218, 0.9254525763987298
0.6058821167972933, 0.7683130432395833
0.8777330967719849, 0.9201076171801651
0.9894820530574747, 0.8172934111543102
1.1143371956097312, 0.9265012354173626
1.3045771339439551, 0.9069856484512913
0.0930066325438706, 0.9157592790749175
0.2959676633891297, 0.9251379492518523