Imports System.IO

Public Class Form1

  Private Debugging As Boolean = False

  Dim Crash As System.Media.SoundPlayer = New System.Media.SoundPlayer(My.Resources.car_crash)
  Dim Skid As System.Media.SoundPlayer = New System.Media.SoundPlayer(My.Resources.car_skid)
  Dim Carskidding As Boolean = False
  Dim SkidPlayCounter As Integer = 0
  Dim MainBitmap As New System.Drawing.Bitmap(1200, 800)
  Dim MaxNoOfCars = 300
  Dim Car(MaxNoOfCars) As Vehicle
  Dim Interval As Double = 0.2
  Dim TickCounter As Integer = 0
  Dim stopw As New Stopwatch
  Dim RND As New Random()
  Dim AverageSpeed As Double = 0
  Dim SpeedLimit As Double = 31.1
  Dim MaxDecel As Double = -10
  Dim MinSeparation As Double = 10
  Dim Visibility As Double
  Dim SeparationHistogram(19) As Integer
  Dim SpeedHistogram(19) As Integer
  Dim StartObstruction As Integer = 4000
  Dim EndObstruction As Integer = 5000
  Dim StartIcyPatch As Integer = 3750
  Dim EndIcyPatch As Integer = 4250
  Dim GraphFont As New Font("Ariel", 10)
  Private Const White As Integer = &HFFFFFFFF
  Private Const Grey As Integer = &HFFEEEEEE
  Private Const BlueGrey As Integer = &HFFF0F8FF
  Private Const Black As Integer = &HFF000000
  Private Const Red As Integer = &HFFFF0000
  Private Const Amber As Integer = &HFFFF8000
  Private Const LightGreen As Integer = &HFF00F000

  Private Blue As Integer = IntFromCol(Color.Blue)
  Private LightBlue As Integer = IntFromCol(Color.DeepSkyBlue)
  Private Brown As Integer = IntFromCol(Color.SaddleBrown)
  Private Purple As Integer = IntFromCol(Color.Magenta)
  Private DarkGreen As Integer = IntFromCol(Color.Green)

  Structure Vehicle
    Public Valid As Boolean
    Public Type As String
    Public Position As Double
    Public Speed As Double
    Public TargetSpeed As Double
    Public Acceleration As Double
    Public TyreCondition As Double ' scale factor. 1.0 is excellent, 0.8 is poor
    Public Seconds As Double ' for cars obeying the 2 second rule, this = 2
    Public Colour As Integer
  End Structure

  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Button1.Enabled = False
    Button2.Enabled = True
    RadioButton1.Checked = True
    RadioButton7.Checked = True ' 70 mph
    RadioButton1.ForeColor = Color.DeepSkyBlue
    RadioButton2.ForeColor = Color.SaddleBrown
    RadioButton3.ForeColor = Color.Magenta
    RadioButton4.ForeColor = Color.Green
    TBVisibility.Value = 20
    TBDecel.Value = 10
    TBDensity.Value = 6
    For i As Integer = 1 To MaxNoOfCars
      Car(i).Type = "Normal"
    Next
    ReSetAllCars()
    Timer1.Interval = 10
    Timer1.Enabled = True
    Timer1.Start()
  End Sub
  Private Sub ClearBitmap()
    Dim g As Graphics = Graphics.FromImage(MainBitmap)
    g.Clear(Color.White)
    g.Dispose()
    PictureBox1.Image = MainBitmap
  End Sub
  Private Sub ReSetAllCars()
    For i As Integer = 1 To MaxNoOfCars
      Car(i).Valid = False
    Next
    If Debugging Then
      TwoCars()
    Else
      UpdateTrafficDensity()
    End If
    ChangeSpeedLimit()
    ClearBitmap()
  End Sub

  Private Sub TwoCars()
    Dim k As Integer = 1
    Car(k).Valid = True
    Car(k).Type = "Normal"
    Car(k).Position = 1000
    Car(k).Speed = SpeedLimit
    Car(k).Acceleration = 0
    Car(k).TyreCondition = 1
    SetSecondsRule(k)
    SetTargetSpeed(k)
    SetNormalColour(k)
    k = 2
    Car(k).Valid = True
    Car(k).Type = "Normal"
    Car(k).Position = 1100
    Car(k).Speed = SpeedLimit - 2
    Car(k).Acceleration = 0
    Car(k).TyreCondition = 1
    SetSecondsRule(k)
    SetTargetSpeed(k)
    SetNormalColour(k)
  End Sub

  Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    For x As Integer = 0 To 19
      SeparationHistogram(x) = 0
      SpeedHistogram(x) = 0
    Next
    AverageSpeed = 0
    Dim NumberOfCars As Integer = 0
    carskidding = False
    For i As Integer = 1 To MaxNoOfCars
      If Car(i).Valid Then
        UpdateCar(i)
        NumberOfCars += 1
        AverageSpeed += Car(i).Speed
      End If
    Next
    If Carskidding And SkidPlayCounter = 0 And CheckBox3.Checked Then
      ' play skid and wait for 50 ticks (ie 1s)
      Skid.Play() : SkidPlayCounter = 50
    End If
    If SkidPlayCounter > 0 Then SkidPlayCounter -= 1
    If NumberOfCars > 0 Then AverageSpeed = AverageSpeed \ NumberOfCars
    UpdatePicture()
    PictureBox1.Invalidate()
    PictureBox2.Invalidate()
    PictureBox3.Invalidate()
  End Sub

  Private Sub UpdatePicture()
    Dim DAB As New DirectAccessBitmap(MainBitmap)
    DAB.LoadBitmap()
    TickCounter += 1
    Dim Col As Integer = White
    ' Draw grid lines
    If TickCounter = 50 Then
      Col = Grey : TickCounter = 0
    End If
    For x As Integer = 0 To MainBitmap.Width - 1
      DAB.Pixel(x) = Col
      If (x \ 100) * 100 = x Then DAB.Pixel(x) = Grey
    Next
    ' Draw obstruction
    If CheckBox1.Checked Then
      For x As Integer = StartObstruction \ 5 To EndObstruction \ 5
        DAB.Pixel(x) = Grey
      Next
    End If
    ' Draw icy patch
    If CheckBox2.Checked Then
      For x As Integer = StartIcyPatch \ 5 To EndIcyPatch \ 5
        DAB.Pixel(x) = BlueGrey
      Next
    End If
    ' Draw vehicles
    For i As Integer = 1 To MaxNoOfCars
      If Car(i).Valid Then
        DAB.Pixel(Car(i).Position \ 5) = Car(i).Colour
        If Car(i).Speed > 2 And Car(i).Colour <> Blue Then
          If Car(i).Position > 5 And Car(i).Colour <> Amber Then DAB.Pixel(Car(i).Position \ 5 - 1) = Car(i).Colour
          If Car(i).Position < 5990 Then DAB.Pixel(Car(i).Position \ 5 + 1) = Car(i).Colour
        End If
      End If
    Next
    DAB.UnloadBitmap()
  End Sub

  Private Sub UpdateCar(ByVal i)
    Dim j As Integer = GetCarInFront(i)
    Dim separation As Double = GetSeparation(i, j)
    MinSeparation = 10
    If Car(i).Type = "Cautious" Then MinSeparation = 15
    If Car(i).Speed > 10 And Car(i).Type <> "Drunk then" Then
      MinSeparation = 20
      If (Car(j).Colour = Red Or Car(j).Colour = Amber) Then
        MinSeparation = 30
      End If
    End If
    Dim SafeSeparation As Double = GetSafeDistance(i, j)
    If Debugging And i = 1 Then Debug.Print(Format(separation, "0.00") & Format(SafeSeparation, " 0.00") & Format(Car(i).Speed, " 0.00") & Format(Car(j).Speed, " 0.00"))
    Visibility = TBVisibility.Value * 10
    If Car(i).Type = "Drunk" Then
      If RND.Next(100) < 10 Then
        SetTargetSpeed(i)
      End If
    End If
    SetNormalColour(i)
    Dim ss As Double = Get2SecondDistance(j)
    Dim index As Integer = 2.5 * (separation + ss) \ ss
    If index > 19 Then index = 19
    If index < 0 Then index = 0
    SeparationHistogram(index) += 1
    Dim TargetSpeed As Double = Car(i).TargetSpeed
    MaxDecel = -TBDecel.Value * Car(i).TyreCondition
    If CheckBox1.Checked Then ' obstruction
      Dim vis As Double = Visibility
      If vis > 50 Then vis = 50
      If Car(i).Position > StartObstruction - vis And Car(i).Position < EndObstruction Then TargetSpeed = 13
    End If
    If CheckBox2.Checked Then ' icy patch
      If Car(i).Position > StartIcyPatch And Car(i).Position < EndIcyPatch And MaxDecel < -2 Then
        ' make some cars decelerate more quickly than others
        ' if they all decelerate at the same rate you don't get any crashes!
        If i And 1 Then MaxDecel = -1.5 Else MaxDecel = -2.5
      End If
    End If
    Dim DefaultAcceleration As Double = GetDefaultAcceleration(i, TargetSpeed)
    Dim v As Double = Car(i).Speed - Car(j).Speed ' relative speed of approach
    If separation < 5 And Car(i).Type <> "Crashed" Then
      ' two cars have crashed
      If Debugging Then Debug.Print("**********  CRASH   ***************")
      Car(i).Type = "Crashed"
      Car(j).Type = "Crashed"
      Dim sp As Double = Car(i).Speed + Car(j).Speed
      Car(i).Speed = 0.4 * sp : Car(j).Speed = 0.6 * sp
      If CheckBox3.Checked Then Crash.Play()
    End If
    If Car(i).Type = "Crashed" Then
      Car(i).Acceleration = MaxDecel
      Car(i).Colour = Black
    Else
      ' not crashed
      If separation > Visibility Then
        ' if the next car is out of sight then accelerate up or down to target speed
        Car(i).Acceleration = DefaultAcceleration
      Else
        ' if the car in front is visible
        Select Case True
          Case Car(i).Speed = 0 And Car(j).Speed < 1
            ' we are stationary and car ahead is not moving significantly
            Car(i).Acceleration = 0
          Case v > 5
            ' approaching fast
            If separation > SafeSeparation Then
              If Debugging Then Debug.Print("Case 1")
              ' move up to safe separation with a max deceleration of 6
              Dim a As Double = -v ^ 2 / 2 / (separation - SafeSeparation) - 1
              If a > 6 Then a = 6
              ' if the chosen deceleration will cause you to go below minseparation
              ' then aim for minseparation
              If separation - v ^ 2 / 2 / a < MinSeparation Then
                a = -v ^ 2 / 2 / (separation - MinSeparation)
              End If
              Car(i).Acceleration = a
            Else
              If separation > MinSeparation Then
                If Debugging Then Debug.Print("Case 2a")
                ' between safeseparation and minseparation
                ' aim for minseparation
                Car(i).Acceleration = -v ^ 2 / 2 / (separation - MinSeparation)
              Else
                ' at or less than minseparation
                ' slam on the brakes
                If Debugging Then Debug.Print("Case 2b")
                If separation < MinSeparation / 2 Then Carskidding = True
                Car(i).Acceleration = MaxDecel
              End If
            End If
          Case v > 0
            ' approaching slowly
            Select Case True
              Case separation > SafeSeparation + 10
                If Debugging Then Debug.Print("Case 3")
                ' ignore car ahead
                Car(i).Acceleration = DefaultAcceleration
              Case separation > SafeSeparation
                If Debugging Then Debug.Print("Case 3")
                ' if near safe separation then coast
                Car(i).Acceleration = 0
              Case Else
                If separation > MinSeparation + 1 Then
                  If Debugging Then Debug.Print("Case 4a")
                  ' getting close, start to decelerate
                  Car(i).Acceleration = -v ^ 2 / 2 / (separation - MinSeparation)
                  ' add some extra deceleration so we drop back
                  Car(i).Acceleration -= (SafeSeparation - separation) / (SafeSeparation - MinSeparation)
                Else
                  If Debugging Then Debug.Print("Case 4b")
                  ' at or less than minseparation
                  ' slam on the brakes
                  Car(i).Acceleration = MaxDecel
                End If
            End Select
          Case Else ' moving at same speed or slower
            If separation > SafeSeparation Then
              If Debugging Then Debug.Print("Case 5")
              ' ignore car ahead
              Car(i).Acceleration = DefaultAcceleration
            Else
              If v < -3 Then
                If Debugging Then Debug.Print("Case 6a")
                ' a bit close so drop back without changing speed
                Car(i).Acceleration = 0 ' drop back at constant speed
              Else
                If Debugging Then Debug.Print("Case 6b")
                ' a bit close but not moving back fast enough so apply a bit of brake
                Car(i).Acceleration = -1 ' drop back
              End If
            End If
        End Select
      End If
      Select Case True
        Case Car(i).Acceleration <= MaxDecel
          Car(i).Colour = Red : Car(i).Acceleration = MaxDecel
        Case Car(i).Acceleration < -0.5
          Car(i).Colour = Amber
      End Select
      ' never accelerate more than the default
      If Car(i).Acceleration > DefaultAcceleration Then Car(i).Acceleration = DefaultAcceleration
    End If
    Car(i).Speed += Car(i).Acceleration * Interval
    If Car(i).Speed < 0.1 Then Car(i).Speed = 0 : Car(i).Acceleration = 0 ' cars can't crawl or reverse
    Car(i).Position = (Car(i).Position + Car(i).Speed * Interval)
    If Car(i).Position >= 6000 Then
      Car(i).Position -= 6000
      If separation > 100 Then Car(i).Position += RND.Next(separation - 40)
    End If
    index = (Car(i).Speed + 0.9) \ 2
    If index > 19 Then index = 19
    SpeedHistogram(index) += 1
    If Debugging And i = 1 Then Debug.Print(Format(v, "0.0") & " " & Format(Car(i).Acceleration, "0.0"))
  End Sub

  Private Function GetCarInFront(ByVal i)
    Dim separation As Double = 0
    Dim leastseparation As Double = 6000
    Dim index As Integer = 0
    For j As Integer = 1 To MaxNoOfCars
      If Car(j).Valid And j <> i Then
        separation = GetSeparation(i, j)
        If leastseparation > separation Then
          leastseparation = separation
          index = j
        End If
      End If
    Next
    Return index
  End Function
  Private Function GetSeparation(ByVal i, ByVal j)
    Dim s As Double = Car(j).Position - Car(i).Position
    If s < 0 Then s += 6000
    Return s
  End Function
  Private Function GetSafeDistance(ByVal i, ByVal j)
    ' returns the safe distance behind a car moving at speed s according to the second rule
    ' with a minimum distance of minseparation
    ' NB the addition factor allows for the length of a car and has a critical effect on the behaviour of bunches
    Dim d As Double = Car(j).Speed * Car(i).Seconds + 6
    If d < MinSeparation Then d = MinSeparation ' SafeSeparation >= MinSeparation
    Return d
  End Function
  Private Function Get2SecondDistance(ByVal j)
    If Car(j).Speed > 5 Then Return 2 * Car(j).Speed
    Return 10
  End Function
  Private Function GetDefaultAcceleration(ByVal i, ByVal TargetSpeed)
    Dim a As Double = 0.8 ' standard acceleration/deceleration
    If Car(i).Type = "Agressive" Or Car(i).Type = "Drunk" Then a = 2
    ' go for approximate speed
    If Car(i).Speed < TargetSpeed - 1 Then Return a
    If Car(i).Speed > TargetSpeed + 1 Then Return LimitDeceleration(-a)
    Return 0
  End Function

  Private Function LimitDeceleration(ByVal a)
    ' remember, a and maxdecel are negative
    If a < MaxDecel Then a = MaxDecel
    Return a
  End Function

  Private Sub PictureBox2_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox2.Paint
    DrawHistogram(e.Graphics, SeparationHistogram, "")
  End Sub
  Private Sub PictureBox3_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox3.Paint
    DrawHistogram(e.Graphics, SpeedHistogram, "Average:" & Str(AverageSpeed) & " m/s")
  End Sub
  Private Sub DrawHistogram(ByVal g As Graphics, ByVal data() As Integer, ByVal text As String)
    g.TranslateTransform(10, 90)
    g.ScaleTransform(1, -1)
    g.DrawLine(Pens.Black, 0, 0, 0, 80)
    g.DrawLine(Pens.Black, 0, 0, 170, 0)
    For i As Integer = 1 To 4
      g.DrawLine(Pens.LightGray, 40 * i, 0, 40 * i, 80)
    Next
    For x = 0 To 19
      g.FillRectangle(Brushes.Blue, 8 * x - 4, 0, 8, data(x))
    Next
    g.ResetTransform()
    g.DrawString(text, GraphFont, Brushes.Black, 80, 4)
  End Sub

  Private Sub Start_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Timer1.Start()
    Button1.Enabled = False
    Button2.Enabled = True
  End Sub
  Private Sub Stop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    Timer1.Stop()
    Button1.Enabled = True
    Button2.Enabled = False
  End Sub
  Private Function AddCar()
    ' remove crashed cars but keep other types
    Dim k As Integer = NewIndex(False)
    If k < 0 Then Return False
    Dim n As Integer = CountCars()
    If n < 6 Then
      ' add first two cars at km intervals
      Car(k).Valid = True
      If Car(k).Type = Nothing Or Car(k).Type = "Crashed" Then Car(k).Type = "Normal"
      Car(k).Position = 1000 * n
      Car(k).Speed = SpeedLimit
      Car(k).Acceleration = 0
      Car(k).TyreCondition = 1
      SetSecondsRule(k)
      SetTargetSpeed(k)
      SetNormalColour(k)
      Return True
    Else
      Dim i, j As Integer
      Dim separation As Double
      For z = 1 To 20 ' try 20 times
        i = NewIndex(True)
        If i < 0 Then Return False
        j = GetCarInFront(i)
        separation = GetSeparation(i, j)
        If separation > 100 - 4 * z Then ' reduce the minimum separation you are looking for
          Car(k).Valid = True
          If Car(k).Type = Nothing Or Car(k).Type = "Crashed" Then Car(k).Type = "Normal"
          Car(k).Position = Car(i).Position + separation / 2
          Car(k).Speed = (Car(i).Speed + Car(j).Speed) / 2
          Car(k).Acceleration = 0
          Car(k).TyreCondition = 1
          SetSecondsRule(k)
          SetTargetSpeed(k)
          SetNormalColour(k)
          Return True
        End If
      Next
      Return False
    End If
  End Function
  Private Function RemoveCar()
    Dim i As Integer = NewIndex(True)
    If i < 0 Then Return False
    Car(i).Valid = False : Return True
    Return False
  End Function
  Private Sub SetSecondsRule(ByVal i)
    Select Case Car(i).Type
      Case "Aggressive" : Car(i).Seconds = 0.5
      Case "Normal" : Car(i).Seconds = 2
      Case "Cautious" : Car(i).Seconds = 4
      Case "Drunk" : Car(i).Seconds = 1
      Case "Slow" : Car(i).Seconds = 2
      Case "Crashed" : Car(i).Seconds = 0
      Case Else : Stop
    End Select
  End Sub
  Private Sub SetTargetSpeed(ByVal i)
    Select Case Car(i).Type
      Case "Aggressive" : Car(i).TargetSpeed = SpeedLimit + 5
      Case "Normal" : Car(i).TargetSpeed = SpeedLimit
      Case "Cautious" : Car(i).TargetSpeed = SpeedLimit
      Case "Slow" : Car(i).TargetSpeed = 3 * SpeedLimit / 4
      Case "Drunk"
        If RND.Next(2) = 1 Then
          Car(i).TargetSpeed = SpeedLimit
        Else
          If RND.Next(100) < 20 Then ' random speed below speed limit
            Car(i).TargetSpeed = RND.Next(10, SpeedLimit)
          End If
        End If
        If Visibility > 50 Then Visibility = 50
    End Select
    Car(i).TargetSpeed += RND.Next(3) - 1 ' add a bit of randomness to all target speeds
  End Sub
  Private Sub SetNormalColour(ByVal i)
    Select Case Car(i).Type
      Case "Aggressive" : Car(i).Colour = Purple
      Case "Normal" : Car(i).Colour = Blue
      Case "Cautious" : Car(i).Colour = LightBlue
      Case "Slow" : Car(i).Colour = Brown
      Case "Drunk" : Car(i).Colour = DarkGreen
    End Select
  End Sub
  Private Sub Reset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
    ReSetAllCars()
  End Sub
  Private Sub ChangeSome_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
    Dim status As String = GetTypeX()
    Dim r As Integer
    Dim n As Integer = CountCars() / 10
    If n < 10 Then n = 10
    For i As Integer = 1 To 100
      r = RND.Next(MaxNoOfCars) + 1
      If Car(r).Type = "Normal" Then
        Car(r).Type = status
        SetSecondsRule(r)
        SetTargetSpeed(r)
        SetNormalColour(r)
        n -= 1
      End If
      If n = 0 Then Exit For
    Next
  End Sub
  Private Sub ChangeAll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button10.Click
    Dim status As String = GetTypeX()
    For i As Integer = 1 To MaxNoOfCars
      Car(i).Type = status
      SetSecondsRule(i)
      SetTargetSpeed(i)
      SetNormalColour(i)
    Next
  End Sub
  Private Sub RemoveThis_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
    ' normalise all cars with a given status
    Dim status As String = GetTypeX()
    For i As Integer = 1 To MaxNoOfCars
      If Car(i).Type = status Then
        Car(i).Type = "Normal"
        SetSecondsRule(i)
        SetTargetSpeed(i)
        SetNormalColour(i)
      End If
    Next
  End Sub
  Private Sub RemoveAll_Click(sender As System.Object, e As System.EventArgs) Handles Button9.Click
    ' normalise all cars
    For i As Integer = 1 To MaxNoOfCars
        Car(i).Type = "Normal"
        SetSecondsRule(i)
        SetTargetSpeed(i)
        SetNormalColour(i)
    Next
  End Sub
  Private Function GetTypeX()
    Dim status As String = ""
    Select Case True
      Case RadioButton1.Checked : status = "Cautious"
      Case RadioButton2.Checked : status = "Slow"
      Case RadioButton3.Checked : status = "Aggressive"
      Case RadioButton4.Checked : status = "Drunk"
    End Select
    Return status
  End Function
  Private Sub Accident_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
    For i As Integer = 1 To MaxNoOfCars
      If Car(i).Valid And Car(i).Position > 4000 And Car(i).Position < 5000 Then
        Car(i).Type = "Crashed"
        If CheckBox3.Checked Then Crash.Play()
        Exit Sub
      End If
    Next
  End Sub
  Private Sub Clear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click
    For i As Integer = 1 To MaxNoOfCars
      If Car(i).Type = "Crashed" Then Car(i).Valid = False
    Next
  End Sub
  Private Function NewIndex(ByVal valid As Boolean)
    ' returns a random index with appropriate validity
    Dim j As Integer
    Dim r As Integer = RND.Next(MaxNoOfCars) ' select a random starting point
    For i As Integer = 1 To MaxNoOfCars
      j = r + i
      If j > MaxNoOfCars Then j -= MaxNoOfCars
      If Car(j).Valid = valid Then Return j
    Next
    Return -1
  End Function
  Private Function CountCars()
    Dim total As Integer = 0
    For i As Integer = 0 To MaxNoOfCars - 1
      If Car(i).Valid Then total += 1
    Next
    Return total
  End Function
  Private Sub TBDensity_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TBDensity.ValueChanged
    UpdateTrafficDensity()
  End Sub
  Private Sub UpdateTrafficDensity()
    Dim TargetNumber = TBDensity.Value * 6
    Dim n As Integer = CountCars()
    While TargetNumber > n
      If AddCar() Then n += 1 Else Exit While
    End While
    While TargetNumber < n
      If RemoveCar() Then n -= 1 Else Exit While
    End While
    Label5.Text = "Traffic density =" & Str(TBDensity.Value) & " cars/km"
  End Sub
  Private Sub TBVisibility_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TBVisibility.ValueChanged
    Label1.Text = "Visibility =" & Str(TBVisibility.Value * 10) & " m"
  End Sub
  Private Sub TBDecel_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TBDecel.ValueChanged
    Label3.Text = "Maximum deceleration =" & Str(TBDecel.Value) & " m/s²"
  End Sub

  Private Sub SpeedLimit_Changed(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton7.CheckedChanged, RadioButton8.CheckedChanged, RadioButton9.CheckedChanged
    ChangeSpeedLimit()
  End Sub
  Private Sub ChangeSpeedLimit()
    Select Case True
      Case RadioButton7.Checked : SpeedLimit = 31.1
      Case RadioButton8.Checked : SpeedLimit = 21.5
      Case RadioButton9.Checked : SpeedLimit = 13.3
    End Select
    For i As Integer = 1 To MaxNoOfCars
      SetTargetSpeed(i)
      SetNormalColour(i)
    Next
  End Sub

  Private Sub Showhelp_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click
    If File.Exists("Traffic Jam Simulator Help.chm") Then
      Process.Start("Traffic Jam Simulator Help.chm")
    Else
      MsgBox("I cannot find a help file")
    End If
  End Sub

  Private Function IntFromCol(c As Color)
    Dim a As Integer = &HFF
    a = a << 8 Or c.R
    a = a << 8 Or c.G
    a = a << 8 Or c.B
    Return a
  End Function


End Class
