Terapkan algoritma Boids

18

pengantar

The Algoritma Boids adalah demonstrasi yang relatif sederhana dari perilaku muncul dalam kelompok. Ia memiliki tiga aturan utama, seperti yang dijelaskan oleh penciptanya, Craig Reynolds:

Model berkelompok dasar terdiri dari tiga perilaku kemudi sederhana yang menggambarkan bagaimana seorang individu dapat melakukan manuver berdasarkan posisi dan kecepatan teman-teman sekawanan terdekatnya:

  • Pemisahan : mengarahkan untuk menghindari berkerumunnya kawanan domba setempat.
  • Alignment : mengarahkan ke arah rata-rata teman kawanan lokal.
  • Kohesi : mengarahkan untuk bergerak menuju posisi rata-rata teman sekawanan lokal.

Setiap boid memiliki akses langsung ke deskripsi geometris seluruh adegan, tetapi berbondong-bondong mengharuskan hanya bereaksi terhadap teman flock dalam lingkungan kecil tertentu di sekitarnya. Lingkungan dicirikan oleh jarak (diukur dari pusat boid) dan sudut , diukur dari arah terbang boid. Teman kawanan di luar lingkungan lokal ini diabaikan. Lingkungan tersebut dapat dianggap sebagai model persepsi terbatas (seperti oleh ikan di air keruh) tetapi mungkin lebih tepat untuk menganggapnya sebagai mendefinisikan wilayah di mana teman sekawanan domba mempengaruhi kemudi pengendara.

Saya tidak sempurna saat menjelaskan sesuatu, jadi saya sangat merekomendasikan untuk memeriksa sumbernya . Dia juga memiliki beberapa gambar super informatif di situsnya.

Tantangan

Mengingat jumlah boids (entitas yang disimulasikan) dan jumlah frame, output animasi dari simulasi.

  • Boids harus dirender sebagai lingkaran merah, dengan garis di dalam lingkaran menunjukkan headingnya, yang merupakan arah boid menunjuk:

Gambar kasar dua "boids", satu menghadap ke kiri, dan yang lainnya menghadap ke kanan.

  • Sudut setiap boid (seperti yang dijelaskan oleh Reynolds) harus 300 derajat penuh. (bukan 360)
  • Judul awal dan posisi setiap boid harus acak secara acak (tetapi diunggulkan, sehingga outputnya masih ditentukan), serta posisinya.
  • Jika radius boid adalah 1, maka radius lingkungan harus 3.
  • Jumlah boid akan berkisar antara 2-20.
  • Jumlah bingkai akan berkisar 1-5000
  • Animasi harus dimainkan dengan minimum 10 milidetik per bingkai dan maksimum 1 detik kali jumlah boid. (2 boids = 2 detik per frame max, 3 boids = 3 detik per frame max, dan sebagainya)
  • Animasi output harus minimal 5 boid-radius dengan 5 boid-jari-jari, kali setengah jumlah boid. Jadi, ukuran minimum untuk 2 boid adalah 10 boid-radius dengan 10 boid-radius, minimum untuk 3 boid adalah 15 boid-radius dengan 15 boid-radius, dan lain-lain.
  • Jari-jari setiap boid harus minimal 5 piksel dan maksimal 50 piksel.
  • Kecepatan setiap boid perlu dibatasi sehingga tidak bergerak lebih dari 1/5 dari jari-jarinya dalam satu frame.
  • Output perlu ditentukan, sehingga input yang sama akan menghasilkan output yang sama jika dijalankan beberapa kali.
  • Jika boid mencapai perbatasan, itu harus membungkus kembali ke sisi lain. Demikian juga, lingkungan di sekitar setiap boid juga harus membungkus perbatasan.

Aturan untuk algoritme

Dalam hal ini, setiap boid memiliki sektor di sekitarnya yang mencakup 300 derajat, berpusat pada heading boid. Setiap boids lain di "lingkungan" ini dianggap "tetangga", atau (untuk menggunakan istilah Reynolds) "teman sekawanan".

  1. Setiap boid harus menyesuaikan headingnya untuk menghindari tabrakan dan menjaga jarak nyaman satu radius boid dengan tetangganya. (Ini adalah aspek "Pemisahan" dari algoritma. Yang satu jari-jari dapat dilewati, tetapi harus seperti karet gelang, terpasang kembali ke tempatnya.)

  2. Setiap boid juga harus menyesuaikan headingnya agar lebih dekat dengan heading rata-rata boid lain di lingkungannya, selama itu tidak mengganggu aturan pertama. (Ini adalah aspek "Alignment" dari algoritma)

  3. Setiap boid harus berbelok ke posisi rata-rata teman kawanannya, selama ini tidak menyebabkan tabrakan atau secara signifikan mengganggu aturan kedua.

Dalam makalahnya tentang subjek , ia menjelaskan ini sebagai berikut:

Untuk membuat kawanan yang disimulasikan, kita mulai dengan model boid yang mendukung penerbangan geometris. Kami menambahkan perilaku yang sesuai dengan kekuatan lawan dari penghindaran tabrakan dan keinginan untuk bergabung dengan kawanan. Dinyatakan secara singkat sebagai aturan, dan dalam urutan menurun diutamakan, perilaku yang mengarah ke simulasi berkelompok adalah:

  • Penghindaran Tabrakan: hindari tabrakan dengan teman sekawanan terdekat
  • Pencocokan Kecepatan: upaya untuk mencocokkan kecepatan dengan teman kawanan terdekat
  • Flock Centering: berusaha untuk tetap dekat dengan teman flock terdekat

Deskripsi gerakan yang lebih terperinci:

  • Implementasi standar dari Algoritma Boids biasanya melakukan perhitungan untuk setiap aturan, dan menggabungkannya bersama.
  • Untuk aturan pertama, boid menelusuri daftar boid tetangga dalam lingkungannya, dan jika jarak antara dirinya dan tetangga kurang dari nilai tertentu, vektor yang mendorong boid menjauh dari tetangga diterapkan ke heading boid.
  • Untuk aturan kedua, boid menghitung rata-rata heading tetangganya, dan menambahkan sebagian kecil (kita akan menggunakan 1/10 dalam tantangan ini) dari perbedaan antara heading saat ini dan rata-rata heading ke heading saat ini.
  • Untuk aturan ketiga dan terakhir, boid rata-rata posisi tetangganya, menghitung vektor yang menunjuk ke lokasi ini. Vektor ini dikalikan dengan angka yang bahkan lebih kecil dari yang digunakan untuk aturan 2 (untuk tantangan ini, 1/50 akan digunakan) dan diterapkan ke pos.
  • Boid kemudian dipindahkan ke arah posnya

Berikut ini adalah implementasi pseudocode yang bermanfaat dari Algoritma Boids.

Contoh Input dan Output

Memasukkan:

5, 190 (5 boids, 190 frame)

Keluaran:

Animasi 190-frame dari Algoritma Boids dengan 5 boids.

Kriteria menang

Ini adalah , jadi solusi terkecil dalam byte menang.

iPhoenix
sumber
7
"Tentu saja ada lebih banyak pada algoritme, jadi saya sangat merekomendasikan memeriksa sumbernya." - Apakah semua yang diperlukan di sini atau tidak? Jika tidak, saya akan merekomendasikan memperbaikinya.
Jonathan Allan
1
Silakan gunakan kotak pasir sebelum memposting tantangan, seperti yang disarankan pada halaman bertanya .
flawr
@ JonathanAllan Ya, semua yang diperlukan ada di sini, tetapi lebih banyak penjelasan mendalam yang mungkin lebih masuk akal bagi pengguna lain tersedia di sumbernya.
iPhoenix
11
Ini adalah tantangan yang menarik (saya menemukan perilaku berkelompok menarik) tetapi perlu ditentukan dengan baik, terutama untuk kode-golf, jika tidak tekanan untuk mengurangi panjang kode akan menyebabkan setiap kemungkinan penyimpangan dari semangat tantangan untuk diberi insentif.
trichoplax

Jawaban:

7

Memproses 3.3.6 (Jawa) ,932 931 940 928 957 917 904 byte

-1 byte dari Jonathan Frech
+11 byte untuk lebih cocok dengan spec
-2 byte dari Kevin Cruijssen
-12 byte untuk mengubah args ke t ()
+29 byte karena saya melakukan ghosting yang salah, lihat versi komentar di bawah
-40 byte untuk menggunakan untuk loop alih-alih panggilan terpisah untuk setiap hantu
-13 byte untuk menggunakan frameRate default, 30

Yah, ini awal, bagi seseorang yang tidak menggunakan 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));}}

Saya tidak tahu cara yang masuk akal untuk melakukan input dalam Pemrosesan, jadi dua variabel pertama adalah input (dan saya tidak menghitung nilainya (5 byte) terhadap jumlah byte). Jika ini masalah, saya bisa mencoba hal lain.

Saya juga tidak tahu cara yang baik untuk memungkinkan mencobanya secara online (proyek Processing.js tidak dapat menangani gaya kode ini) tanpa hosting sendiri; dan itu adalah sesuatu yang tidak ingin saya coba. Beri tahu saya jika ada hal pintar yang bisa saya lakukan.

Kode yang diformat, dengan komentar

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));
  }
}

Output sampel

n = 15, bingkai = 400:

boids

Atau, animasi yang sama, tetapi menunjukkan lingkungan masing-masing boid.

Phlarx
sumber
1
Bisa 2*PItidak menjadi TAUuntuk menyimpan byte?
Jonathan Frech
@ JonathanFrech Ya itu bisa; Saya awalnya punya -PI, PI dan saya akan ke arah sana, tetapi teralihkan.
Phlarx
Program saya (yang ditulis dalam js dan html) tidak mengekspor gif, tetapi membuat gambar dan saya menggunakan program menangkap layar dan mengonversi video yang diekspor ke gif. Tapi ada satu hal yang saya perhatikan. Boids memiliki garis biru, yang tidak mengikuti spesifikasi :)
iPhoenix
Hanya pengingat ramah lainnya, jawaban ini tidak mengikuti spesifikasi, sehingga tidak akan mendapatkan hadiah.
iPhoenix
1
Saya tidak tahu Memproses, tapi saya pikir Anda bisa bermain golf hal-hal berikut: ,i,untuk ,i=0,dan kemudian menghapus bagian i=0dalam for-loop. (-1 byte); frameCount%f==0ke frameCount%f<1(1 byte); &&untuk &di final jika 2*d>=p.dist(m)&q.angleBetween(v,q.sub(m,p))<=5*PI/6(-1 byte). Sekali lagi, tidak yakin apakah ini mungkin, tetapi karena Pemrosesan tampaknya cukup mirip dengan Java, saya pikir begitu. Anda juga dapat mencoba membuat gif dengan screentogif.com .
Kevin Cruijssen
4

JavaScript (ES6) + HTML5, 1200 byte

Inilah solusi saya saat ini menggunakan Canvas API. The eval()mengembalikan fungsi kari yang input pertama adalah Boidpopulasi, dan kedua adalah jumlah frame animasi. Anda dapat menggunakan Infinityuntuk animasi berkelanjutan.

Ini eval(...)adalah 1187 byte dan <canvas id=c>13 byte, menghasilkan total 1200. CSS tidak perlu, tetapi untuk kenyamanan, ini memungkinkan Anda untuk melihat tepi kanvas.

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>

Edit

Seperti yang diminta, cuplikan lain dengan input untuk populasi 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>

Patrick Roberts
sumber
Perahu tampaknya tidak berinteraksi ketika saya menjalankan cuplikan
Jo King
@ Mengajaknya itu harus diperbaiki sekarang
Patrick Roberts
Masalahnya adalah karena babel minifier membayangi variabel global dalam satu fungsi dengan nama parameter, dan typecast tersirat pada angka tidak menyebabkan kesalahan, sehingga fungsi tersebut gagal secara diam-diam dan tidak pernah mendeteksi tetangga.
Patrick Roberts
Saya akan mencoba membuat demo interaktif besok malam tapi saya kehabisan tenaga untuk malam ini.
Patrick Roberts
Hanya sebuah catatan: di mana bunyinya t.a+v+l/10+f/50, jika Anda mengubahnya ke t.a+v/3+l/10+f/50, itu menghasilkan perilaku yang agak lebih menarik, tetapi program saat ini lebih kecil dan masih spek.
Patrick Roberts