Implementa l'algoritmo Boids


18

introduzione

L' algoritmo Boids è una dimostrazione relativamente semplice del comportamento emergente in un gruppo. Ha tre regole principali, come descritto dal suo creatore, Craig Reynolds:

Il modello di floccaggio di base consiste in tre semplici comportamenti di governo che descrivono come un individuo fa muovere manovre in base alle posizioni e alle velocità dei suoi vicini compagni di gregge:

  • Separazione : sterzare per evitare affollati compagni di gregge locali.
  • Allineamento : dirigersi verso la rotta media dei compagni di gregge locali.
  • Coesione : sterzare per spostarsi verso la posizione media dei compagni di gregge locali.

Ogni boid ha accesso diretto alla descrizione geometrica dell'intera scena, ma il floccaggio richiede che reagisca solo ai compagni di gregge all'interno di un certo piccolo quartiere attorno a sé. Il vicinato è caratterizzato da una distanza (misurata dal centro del boid) e da un angolo , misurato dalla direzione di volo del boid. I compagni di gruppo fuori da questo quartiere locale vengono ignorati. Il quartiere potrebbe essere considerato un modello di percezione limitata (come da pesci in acque torbide) ma è probabilmente più corretto pensarlo come la definizione della regione in cui i compagni di gregge influenzano uno sterzo a motore.

Non sono perfetto per spiegare le cose, quindi consiglio vivamente di controllare la fonte . Ha anche alcune foto super informative sul suo sito.

Sfida

Dato il numero di boid (entità simulate) e il numero di frame, genera un'animazione della simulazione.

  • Le boid dovrebbero essere rese come un cerchio rosso, con una linea all'interno del cerchio che mostra la sua direzione, che è la direzione in cui punta la boid:

Disegno grezzo di due "boids", uno rivolto a sinistra e l'altro rivolto a destra.

  • L'angolo di ogni boid (come descritto da Reynolds) dovrebbe essere di 300 gradi. (non 360)
  • L'intestazione iniziale e la posizione di ciascun boid dovrebbero essere uniformemente casuali (ma seminate, in modo che l'output sia ancora determinato), così come la posizione.
  • Se il raggio del boid è 1, allora il raggio del vicinato dovrebbe essere 3.
  • Il numero di boid sarà compreso tra 2 e 20.
  • Il numero di fotogrammi sarà ovunque tra 1-5000
  • L'animazione deve essere riprodotta con un minimo di 10 millisecondi per fotogramma e un massimo di 1 secondo volte il numero di boid. (2 boids = 2 secondi per frame max, 3 boids = 3 secondi per frame max, eccetera)
  • L'animazione di output dovrebbe essere di almeno 5 raggi boid per 5 raggi boid, per metà del numero di boid. Quindi, la dimensione minima per 2 boid sarebbe 10 raggi boid per 10 raggi boid, la minima per 3 boids sarebbe 15 raggi boid per 15 raggi boet, eccetera.
  • Il raggio di ogni boid deve essere un minimo di 5 pixel e un massimo di 50 pixel.
  • La velocità di ogni boid deve essere limitata in modo che non si sposti più di 1/5 del suo raggio in un frame.
  • L'output deve essere determinato, in modo che lo stesso input produca lo stesso output se eseguito più volte.
  • Se un boid raggiunge un bordo, dovrebbe tornare dall'altra parte. Allo stesso modo, anche il quartiere intorno a ogni boid dovrebbe avvolgere i confini.

Regole per l'algoritmo

In questo caso, ogni boid ha intorno a sé un settore che si estende per 300 gradi, centrato sulla sua direzione. Qualsiasi altro boid in questo "quartiere" è considerato "vicino", o (per usare il termine di Reynolds) "compagni di gregge".

  1. Ogni boid dovrebbe regolare la sua direzione per evitare collisioni e mantenere una distanza confortevole di un raggio boid con i suoi vicini. (Questo è l'aspetto "Separazione" dell'algoritmo. L'unico raggio di boid può essere bypassato, ma dovrebbe essere come un elastico, che ritorna in posizione.)

  2. Ogni boid dovrebbe inoltre regolare la propria rotta in modo da essere più vicina alla media delle altre boid del vicinato, purché non interferisca con la prima regola. (Questo è l'aspetto "Allineamento" dell'algoritmo)

  3. Ogni boid dovrebbe volgersi verso la posizione media dei suoi compagni di flock, purché ciò non causi collisioni o interferisca significativamente con la seconda regola.

Nel suo articolo sull'argomento , spiega questo come segue:

Per costruire un gregge simulato, iniziamo con un modello boid che supporta il volo geometrico. Aggiungiamo comportamenti che corrispondono alle forze opposte di evitare le collisioni e l'impulso di unirsi al gregge. Dichiarati brevemente come regole, e in ordine di precedenza decrescente, i comportamenti che portano al floccaggio simulato sono:

  • Evitare le collisioni: evitare le collisioni con i compagni di gregge vicini
  • Corrispondenza di velocità: tenta di abbinare la velocità con i compagni di flock vicini
  • Centratura floccata: cerca di stare vicino ai compagni di gregge vicini

Descrizione più dettagliata del movimento:

  • L'implementazione standard dell'algoritmo Boids di solito esegue un calcolo per ciascuna delle regole e la unisce.
  • Per la prima regola, il boid passa attraverso l'elenco dei boid vicini nel suo vicinato, e se la distanza tra se stesso e il vicino è inferiore a un certo valore, un vettore che spinge il boid lontano dal suo vicino viene applicato alla direzione del boid.
  • Per la seconda regola, il boid calcola l'intestazione media dei suoi vicini e aggiunge una piccola porzione (useremo 1/10 in questa sfida) della differenza tra l'intestazione corrente e l'intestazione media nella direzione corrente.
  • Per la terza e ultima regola, il boid calcola la media delle posizioni dei suoi vicini, calcola un vettore che punta verso questa posizione. Questo vettore viene moltiplicato per un numero ancora più piccolo di quello utilizzato per la regola 2 (per questa sfida, verrà utilizzato 1/50) e applicato all'intestazione.
  • Il boid viene quindi spostato nella direzione della sua direzione

Ecco una utile implementazione dello pseudocodice dell'algoritmo Boids.

Esempio di input e output

Ingresso:

5, 190 (5 boids, 190 frame)

Produzione:

Animazione a 190 frame dell'algoritmo Boids con 5 boids.

Criterio vincente

Questo è , quindi vince la soluzione più piccola in byte.


7
"Ovviamente, c'è di più nell'algoritmo, quindi consiglio vivamente di controllare la fonte." - è tutto necessario qui o no? In caso contrario, consiglierei di risolverlo.
Jonathan Allan,

1
Si prega di utilizzare la sandbox prima di pubblicare problemi, come consigliato nella pagina di richiesta .
flawr

@JonathanAllan Sì, tutto ciò che è necessario è qui, ma alla fonte sono disponibili spiegazioni più approfondite che potrebbero avere più senso per gli altri utenti.
iPhoenix,

11
Questa è una sfida interessante (trovo che i comportamenti di floccaggio siano affascinanti) ma dovrà essere ben specificato, specialmente per un code-golf, altrimenti la pressione per ridurre la lunghezza del codice causerà ogni possibile deviazione dallo spirito della sfida a essere incentivato.
trichoplax,

Risposte:


7

Elaborazione 3.3.6 (Java) ,932 931 940 928 957 917 904 byte

-1 byte da Jonathan Frech
+11 byte per abbinare meglio le specifiche
-2 byte da Kevin Cruijssen
-12 byte per cambiare args in t ()
+29 byte perché stavo facendo un ghosting sbagliato, vedere la versione commentata sotto
-40 byte per usare per loop anziché chiamate separate per ogni fantasma
-13 byte per l'utilizzo di frameRate predefinito, 30

Bene, è un inizio, per qualcuno che non ha Java-golf. :)

int n=15,f=400,i,j,z=255,w=500;float d=200./n;PVector m;B[]a=new B[n];void setup(){size(500,500);fill(z,0,0);randomSeed(n);for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));}void draw(){background(z);for(B b:a)b.u();if(frameCount%f<1)setup();}class B{PVector p,v,e,q,r;ArrayList<B>n;B(PVector m,PVector o){p=m;v=o;}void u(){e=v.copy();n=new ArrayList();for(B b:a){if(b!=this)for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);}if(n.size()>0){q=new PVector();r=q.copy();for(B b:n){q.add(b.v);r.add(b.p);if(p.dist(b.p)<=d)e.add(p).sub(b.p);}e.add(q.div(n.size()).sub(v).div(10));e.add(r.div(n.size()).sub(p).div(50));}p.add(e.limit(d/10));v=e.mult(10);p.set((p.x+w)%w,(p.y+w)%w);noStroke();ellipse(p.x,p.y,d,d);stroke(0,0,z);line(p.x,p.y,p.x+v.x,p.y+v.y);}void t(int x,int y,B o){m=o.p.copy().add(x,y);if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)n.add(new B(m,o.v));}}

Non conosco alcun modo ragionevole per fare input in Processing, quindi le prime due variabili sono gli input (e non ho contato i loro valori (5 byte) per il conteggio dei byte). Se questo è un problema, posso provare altre cose.

Inoltre non conosco un buon modo per consentire di provarlo online (il progetto Processing.js non può gestire questo stile di codice) senza ospitare le cose da solo; e questo è qualcosa che non sono desideroso di tentare. Fammi sapere se c'è qualcosa di intelligente che posso fare.

Codice formattato, con commenti

int n=15, // Number of boids
    f=400, // Number of frames
    i,j,z=255,w=500; // temp*2, and two constants
float d=200./n; // Boid diameter
PVector m; // temp
B[]a=new B[n];
void setup(){ // This is automatically called at startup
  size(500,500); // Can't use variables for this without extra bytes for settings()
  fill(z,0,0);
  randomSeed(n); // seeded from number of Boids, so that n=19 is very different from n=20
  for(i=0;i<n;a[i++]=new B(new PVector(random(w),random(w)),m.fromAngle(random(TAU))));
}
void draw(){ // This is automatically called each frame
  background(z);
  for(B b:a)
    b.u();
  if(frameCount%f<1) // When desired frames length is hit, reset everything.
    setup();         // Could also use noLoop() instead of setup() to just stop instead.
                     // Or, remove this if statement altogether to go on to infinity.
}
class B{ // Boid
  PVector p,v,e,q,r; // Position, Velocity, Next velocity, and two temp vectors
  ArrayList<B>n; // List of neighbors
  B(PVector m,PVector o){
    p=m;
    v=o;
  }
  void u(){ // Update function, does rules and redraw for this Boid
    e=v.copy();
    n=new ArrayList();
    for(B b:a){ // Test a Boid and its eight ghosts for neighborship
      if(b!=this) // Note: Assumes neighborhood diameter < min(width,height)
        // The ghosts are to check if it'd be closer to measure by wrapping
        // We need eight for wrapping north, east, south, west, northeast,
        // northwest, southeast, and southwest. And also the non-wrapped one.
        // The above assumption ensures that each ghost is further apart than
        // the neighborhood diameter, meaning that only one neighbor might be
        // found for each boid. To test this, place a boid in each corner, right
        // to the edge, facing away from center. Each boid should find three
        // neighbors, that are the three other boids.
        for(i=-w;i<=w;i+=w)for(j=-w;j<=w;j+=w)t(i,j,b);
    }
    if(n.size()>0){
      q=new PVector();
      r=q.copy();
      for(B b:n){
        q.add(b.v); // Velocity matching, pt 1
        r.add(b.p); // Flock centering, pt 1
        if(p.dist(b.p)<=d)  
          e.add(p).sub(b.p); // Collision avoidance
      }
      e.add(q.div(n.size()).sub(v).div(10)); // Velocity matching, pt 2
      e.add(r.div(n.size()).sub(p).div(50)); // Flock centering, pt 2
    }
    p.add(e.limit(d/10)); // Update vectors
    v=e.mult(10);
    p.set((p.x+w)%w,(p.y+w)%w); // Wrapping
    noStroke();
    ellipse(p.x,p.y,d,d); // Draw Boid, finally
    stroke(0,0,z);
    line(p.x,p.y,p.x+v.x,p.y+v.y);
  }
  void t(int x,int y,B o){ // Test if a Boid (or a ghost) is a neighbor
    m=o.p.copy().add(x,y);
    if(2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6)
      n.add(new B(m,o.v));
  }
}

Uscita campione

n = 15, frame = 400:

boidi

O, la stessa animazione, ma mostra il vicinato di ogni boid.


1
Non 2*PIpuoi diventare TAUper salvare un byte?
Jonathan Frech,

@JonathanFrech Sì, può; Inizialmente avevo -PI, PI e stavo andando in quel modo, ma mi sono allontanato.
Phlarx,

Il mio programma (che è stato scritto in js e html) non ha esportato una gif, ma ha disegnato un'immagine e ho usato un programma di cattura dello schermo e convertito il video che ha esportato in una gif. C'è una cosa che ho notato, però. Le boids hanno un contorno blu, che non segue le specifiche :)
iPhoenix,

Solo un altro promemoria amichevole, questa risposta non segue le specifiche, quindi non otterrà la generosità.
iPhoenix il

1
Non conosco Processing, ma penso che tu possa giocare a golf le seguenti cose: ,i,to ,i=0,e poi rimuovere l' i=0interno del for-loop. (-1 byte); frameCount%f==0a frameCount%f<1(1 byte); &&a &nel if finale 2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6(-1 byte). Ancora una volta, non sono sicuro che siano possibili, ma poiché l'elaborazione sembra abbastanza simile a Java, penso che lo sia. Inoltre, potresti provare a creare una gif con screentogif.com .
Kevin Cruijssen,

4

JavaScript (ES6) + HTML5, 1200 byte

Ecco la mia attuale soluzione utilizzando l'API Canvas. La eval()restituisce una funzione al curry il cui primo ingresso è la Boidpopolazione, e il secondo è il numero di fotogrammi di animazione. È possibile utilizzare Infinityper l'animazione continua.

Il eval(...)è 1187 byte e <canvas id=c>è 13 byte, per un totale di 1200. Il CSS non è necessaria, ma per comodità, ti permette di vedere i bordi della tela.

eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))
(10)(Infinity)
canvas{border:1px solid}
<canvas id=c>

modificare

Come richiesto, un altro frammento con un input per la popolazione Boid:

b.onchange=()=>{eval("L7F7{function B8{t=this,t.a=o8*T,t.x=o8*S,t.y=o8*S}C=this.c,D=C.getContext`2d`,({abs:z,random:o,atan2:k,cos:u,sin:g,PI:P,T=2*P,G={c:_7A[r='filter'](b7b!=t)[i](9)79)),n:_7A[r](b7b!=t)[i](9)7({a,x,y:y-S})),s:_7A[r](b7b!=t)[i](9)7({a,x,y:y+S})),e:_7A[r](b7b!=t)[i](9)7({a,x:x-S,y})),w:_7A[r](b7b!=t)[i](9)7({a,x:x+S,y}))},M=I7[I,I+T,I-T][p]((a,x)7z(x)<z(a)?x:a)}=Math),B.prototype={d8{with(D)save8,translate(x,y),rotate(a),beginPath8,arc(0,0,5,0,T),fillStyle='red',fill8,beginPath8,moveTo(0,0),lineTo(10,0),strokeStyle='blue',stroke8,restore8},n:_7(({c,n,s,e,w}=G),c8.concat(n8,s8,e8,w8)[r](b7(d=b.x-x,f=b.y-y,400>d*d+f*f&&z(z(k(f,d)-a)/P-1)>1/6))),s8{q=(j=t.n8).length,v=t.v8||0,l=t.l8||0,f=t.f8||0,a=t.a=(t.a+v/3+l/10+f/50)%T,t.x=(x+u(a)+S)%S,t.y=(y+g(a)+S)%S},v:_7([d,f]=j[r](b7225>(b.x-x)**2+(b.y-y)**2)[p='reduce'](([d,f],b)7[x+d-b.x,y+f-b.y],[0,0]),d||f?M(k(f,d)-a):0),l:_7j[i](b7M(b.a-a))[p]((a,x)7a+x,0)/q,f:_7([d,f]=j[p](([d,f],b)7[d+b.x,f+b.y],[-x*q,-y*q]),d||f?M(k(f,d)-a):0)},S=C.width=C.height=50*L,A=Array(L).fill().map(_7new B),R=_7{D.clearRect(0,0,S,S),A[i='map'](b79=b).d8),A[i](b79=t=b).s8),F--&&setTimeout(R,10)},R8}".replace(/[789]/g,m=>['=>','()','({a,x,y}'][m-7]))(+b.value)(Infinity);b.remove()}
input{display:block}canvas{border:1px solid}
<input id=b><canvas id=c>


Le boids non sembrano interagire quando eseguo lo snippet
Jo King

@JoKing ora dovrebbe essere risolto
Patrick Roberts

Il problema era perché il minificatore di babele oscurava una variabile globale in una funzione con un nome di parametro e il typecast implicito in un numero non generava un errore, quindi la funzione falliva silenziosamente e non rilevava mai alcun vicino.
Patrick Roberts,

Proverò a fare una demo interattiva domani sera, ma stasera ho esaurito il ritmo.
Patrick Roberts,

Solo una nota: dove legge t.a+v+l/10+f/50, se lo cambi t.a+v/3+l/10+f/50, produce un comportamento un po 'più interessante, ma il programma attuale è più piccolo e ancora alle specifiche.
Patrick Roberts,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.