It took a few prompts for ChatGPT 3.5 to create it, but I had to fix some of it's mistakes. From Wikipedia, for those who don't know what a boids simulation is, it's an artificial life program, developed by Craig Reynolds in 1986, which simulates the flocking behaviour of birds, and related group motion.
In this boids simulation project you can change the number of boids in the simulation and pause their movement. 4 boids is the smallest size a flock can be. The boids avoid the mouse cursor, so you can observe the new counts of flocks, trios, pairs and lone boids. It runs fine when you resize the form.
The capture above is from the 1st version of the boids simulation project. The updated winforms and Wpf versions have filled-in triangles, and can handle up to 1000 boids.
The code below is also from the 1st version. It needs 1 panel docked at the bottom and a PictureBox to fill the remaining space in the Form. It also needs 2 timers named "SimulationTimer" and "CountingTimer", 7 labels, a textbox, and two buttons. Button2 is the pause button.
The code has enough comments to explain everything. All required classes will be in the next two comments.
Main class:
Code:
Option Strict On
Public Class Form1
' Maximum speed of boids
Private MaxSpeed As Integer = 7
' Range within which boids perceive and interact with each other
Public VisualRange As Integer = 40
' Range within which boids avoid collisions
Private ProtectedRange As Integer = 20
' List to store individual boid instances representing the flock.
Private BoidsList As List(Of Boid)
' Buffered bitmap for off-screen rendering
Private buffer As Bitmap
' Variables for mouse cursor avoidance.
Private BoidsMousePosition As Point
Private MouseAvoidanceThreshold As Double = 25
' Instances for alignment, cohesion, and separation rules for boid behavior.
' MaxSpeed parameter is set to the maximum speed allowed for boids.
Private alignmentRule As New Alignment(MaxSpeed)
Private cohesionRule As New Cohesion()
Private separationRule As New Separation()
' The initial number of boids to be created and initialized
Private Const InitialBoidCount As Integer = 400
' Flag to track pause state
Private IsPaused As Boolean = False
' BoidCounterInstance represents an instance of the BoidCounter class,
' which is responsible for counting various configurations of boids within a specified visual range.
Public BoidCounterInstance As New BoidCounter()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Set the DoubleBuffered property to True to enable double buffering
Me.DoubleBuffered = True
' Initialize the buffered bitmap
buffer = New Bitmap(PictureBox1.ClientSize.Width, PictureBox1.ClientSize.Height)
' Set VisualRange for BoidCounterInstance
BoidCounterInstance.VisualRange = VisualRange
' Initialize the list of boids
BoidsList = New List(Of Boid)()
InitializeBoids(InitialBoidCount)
' Start the simulation timer
SimulationTimer.Start()
CountingTimer.Start()
End Sub
' Initializes a specified number of boids with random positions and velocities.
Private Sub InitializeBoids(ByVal count As Integer)
' Create a random number generator.
Dim random As Random = New Random()
' Generate boids with random positions and velocities.
For i As Integer = 0 To count - 1
Dim boid As Boid = New Boid()
boid.Location = New Point(random.Next(0, PictureBox1.ClientSize.Width), random.Next(0, PictureBox1.ClientSize.Height))
boid.Velocity = New Point(random.Next(-2, 3), random.Next(-2, 3))
BoidsList.Add(boid)
Next
End Sub
Private Sub SimulationTimer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SimulationTimer.Tick
If Not IsPaused Then
' Update boids position
For Each boid As Boid In BoidsList
UpdateBoid(boid)
Next
' Draw boids to the buffer
DrawBoidsToBuffer()
End If
End Sub
' Updates the position and behavior of a given boid based on its interactions with other boids and the environment.
Private Sub UpdateBoid(ByVal boid As Boid)
' Variables to store averages, counts, and distances for boid interactions
Dim averageXposition, averageYposition, averageXvelocity, averageYvelocity, neighboringBoids, closeDx, closeDy As Double
' Loop through every other boid in the flock
For Each otherBoid As Boid In BoidsList
' Check if the boid is not itself
If Not boid.Equals(otherBoid) Then
Dim dx As Double = boid.Location.X - otherBoid.Location.X
Dim dy As Double = boid.Location.Y - otherBoid.Location.Y
Dim squaredDistance As Double = dx * dx + dy * dy
' Check if the boids are within the protected range
If squaredDistance < ProtectedRange * ProtectedRange Then
closeDx += boid.Location.X - otherBoid.Location.X
closeDy += boid.Location.Y - otherBoid.Location.Y
ElseIf squaredDistance < VisualRange * VisualRange Then
' Update averages for boids within the visual range
averageXposition += otherBoid.Location.X
averageYposition += otherBoid.Location.Y
averageXvelocity += otherBoid.Velocity.X
averageYvelocity += otherBoid.Velocity.Y
neighboringBoids += 1
End If
End If
Next
' Check if there are neighboring boids
If neighboringBoids > 0 Then
' Calculate average velocity and position
Dim avgVelocity As New Point(CInt(averageXvelocity / neighboringBoids), CInt(averageYvelocity / neighboringBoids))
Dim avgPosition As New Point(CInt(averageXposition / neighboringBoids), CInt(averageYposition / neighboringBoids))
' Calculate distance to the mouse
Dim mouseDistance As Double = Math.Sqrt((boid.Location.X - BoidsMousePosition.X) ^ 2 + (boid.Location.Y - BoidsMousePosition.Y) ^ 2)
' Skip alignment and cohesion when boid is near the mouse
If mouseDistance > VisualRange Then
' Apply alignment rule
alignmentRule.ApplyAlignmentRule(boid, avgVelocity)
' Apply cohesion rule
cohesionRule.ApplyCohesionRule(boid, avgPosition)
End If
End If
' Apply separation rule
separationRule.ApplySeparationRule(boid, closeDx, closeDy)
' Move boid
boid.Location = New Point(boid.Location.X + boid.Velocity.X, boid.Location.Y + boid.Velocity.Y)
' Apply mouse avoidance
Dim distanceToMouse As Double = Math.Sqrt((boid.Location.X - BoidsMousePosition.X) ^ 2 + (boid.Location.Y - BoidsMousePosition.Y) ^ 2)
If distanceToMouse < MouseAvoidanceThreshold Then
' Adjust velocity to avoid the mouse more strongly
Dim angle As Double = Math.Atan2(boid.Location.Y - BoidsMousePosition.Y, boid.Location.X - BoidsMousePosition.X)
boid.Velocity = New Point(CInt(boid.Velocity.X + Math.Cos(angle) * 4), CInt(boid.Velocity.Y + Math.Sin(angle) * 4))
End If
' Keep boids within the PictureBox boundaries
If boid.Location.X < 0 Then
boid.Location = New Point(0, boid.Location.Y)
boid.Velocity = New Point(-boid.Velocity.X, boid.Velocity.Y)
ElseIf boid.Location.X > PictureBox1.ClientSize.Width Then
boid.Location = New Point(PictureBox1.ClientSize.Width, boid.Location.Y)
boid.Velocity = New Point(-boid.Velocity.X, boid.Velocity.Y)
End If
If boid.Location.Y < 0 Then
boid.Location = New Point(boid.Location.X, 0)
boid.Velocity = New Point(boid.Velocity.X, -boid.Velocity.Y)
ElseIf boid.Location.Y > PictureBox1.ClientSize.Height Then
boid.Location = New Point(boid.Location.X, PictureBox1.ClientSize.Height)
boid.Velocity = New Point(boid.Velocity.X, -boid.Velocity.Y)
End If
' Normalize velocity
Dim length As Double = Math.Sqrt(boid.Velocity.X ^ 2 + boid.Velocity.Y ^ 2)
If length > MaxSpeed Then
' Scale velocity back to the maximum speed
boid.Velocity = New Point(CInt(MaxSpeed * boid.Velocity.X / length), CInt(MaxSpeed * boid.Velocity.Y / length))
End If
End Sub
Private Sub CountingTimer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CountingTimer.Tick
' Set the BoidsList property before counting
BoidCounterInstance.BoidsList = BoidsList
' Counts flocks, trios, pairs, and lone boids
BoidCounterInstance.CountFlocksTriosPairsLoners(BoidsList)
' Update labels outside the loop with the calculated counts.
Label1.Text = "Flocks: " & BoidCounterInstance.FlockCount
Label2.Text = "Trios: " & BoidCounterInstance.TrioCount
Label3.Text = "Pairs: " & BoidCounterInstance.PairCount
Label4.Text = "Loners: " & BoidCounterInstance.LoneCount
End Sub
Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
' Update mouse position
BoidsMousePosition = e.Location
End Sub
Private Sub DrawBoidsToBuffer()
' Draw boids as small narrow triangles on the off-screen buffer
Using g As Graphics = Graphics.FromImage(buffer)
g.Clear(Color.White) ' You can choose a different background color if needed
For Each boid As Boid In BoidsList
' Calculate the angle of the velocity vector
Dim angle As Double = Math.Atan2(boid.Velocity.Y, boid.Velocity.X) ' The angle now directly represents the direction of the top of the triangle
' Calculate the points of the triangle
Dim points As Point() = {
New Point(boid.Location.X + CInt(7 * Math.Cos(angle)), boid.Location.Y + CInt(8 * Math.Sin(angle))),
New Point(boid.Location.X + CInt(3 * Math.Cos(angle - 2 * Math.PI / 3)), boid.Location.Y + CInt(3 * Math.Sin(angle - 2 * Math.PI / 3))),
New Point(boid.Location.X + CInt(3 * Math.Cos(angle + 2 * Math.PI / 3)), boid.Location.Y + CInt(3 * Math.Sin(angle + 2 * Math.PI / 3)))
}
' Draw the triangle (body) on the off-screen buffer
g.DrawPolygon(Pens.Blue, points)
Next
End Using
' Trigger the Paint event to display the buffered bitmap on the PictureBox
PictureBox1.Invalidate()
End Sub
Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
' Draw the buffered image onto the PictureBox
e.Graphics.DrawImage(buffer, 0, 0)
End Sub
Private Sub PictureBox1_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles PictureBox1.SizeChanged
' Resize the buffered bitmap when the PictureBox size changes
buffer = New Bitmap(PictureBox1.ClientSize.Width, PictureBox1.ClientSize.Height)
' Update the label displaying the display area size
Label7.Text = "Display Area: " & PictureBox1.ClientSize.Width & " x " & PictureBox1.ClientSize.Height
' Invalidate the form to trigger a redraw
Me.Invalidate()
End Sub
Private Sub Panel1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
' Draw a black line at the top of the panel
Dim topBorderPen As New Pen(Color.Black)
e.Graphics.DrawLine(topBorderPen, 0, 0, Panel1.Width, 0)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' Attempt to parse the input as an integer
Dim newBoidCount As Integer
If Integer.TryParse(TextBox1.Text, newBoidCount) Then
If CInt(TextBox1.Text) > 1000 Then
' Input integer value is too high, display an error message
MessageBox.Show("The integer value is too high")
Else
' Input is a valid integer, reset the list and initialize new boids
BoidsList = New List(Of Boid)()
InitializeBoids(newBoidCount)
' Check if the simulation is paused and unpause if needed
If IsPaused Then
IsPaused = False
' Update button text when resumed
PauseButton.Text = "Pause"
End If
End If
Else
' Input is not a valid integer, display an error message
MessageBox.Show("Invalid input for boid count." & vbCrLf & vbCrLf & "Please enter a valid integer value.")
' clears invalid input
TextBox1.Text = ""
End If
End Sub
Private Sub PauseButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PauseButton.Click
' Toggle the pause state
IsPaused = Not IsPaused
' Update button text based on the pause state
If IsPaused Then
' Update button text when paused
PauseButton.Text = "Resume"
Else
' Update button text when resumed
PauseButton.Text = "Pause"
End If
End Sub
End Class
The boids simulation was coded in Visual Basic 2010, Framework 4, but the code provided above and in the next two comments should work with any Framework.
The "ChatGPT Boid Simulation Update.zip" project's and "Wpf Boids Simulation.zip" project's performance is the same. The only thing, when you minimize the ""ChatGPT Boid Simulation Update.zip" version to the taskbar, it crashes. I can't figure out why it does that.
The capture above was taken soon after I changed the boid count. The boids eventually unite into two or 3 flocks. Large flocks sometimes split-up.
Update, Dec 8th:
I updated the WPF boids simulation project zip below. It no longer goes by a previous canvas size when changing the boid count, but the actual canvas size after resizing the window. It also displays the actual canvas size while resizing.
Last edited by Peter Porter; Dec 8th, 2023 at 09:22 AM.