- 2 wheels - one on the left, one on the right.
- A number of sensors mounted on the front of the vehicle which respond to elements in the environment (heat, light, objects, etc)
- Connections from the sensors to the motors - these connections either cut or supply power to the motors.
#!/bin/sh #\ exec wish "$0" ${1+"$@"} # Simple simulation of Braitenburg vehicles. # Car drawing code stolen from "Car Racing in Tcl" - http://wiki.tcl.tk/4364 package require Tk set tcl_precision 17 namespace eval car { variable cars [list] } # Create a new car: # params: # name - a name for the car # c - the canvas to draw on # color - colo(u)r of this car # angle - initial direction of motion # inputs - list of inputs in form (e.g.): # left input light + -> left wheel proc car::new {name c x y color angle inputs} { variable cars lappend cars $name interp alias {} $name {} car::dispatch $name namespace eval $name { variable lspeed 10 rspeed 10 variable ldiff 0 rdiff 0 variable inputs [list] } namespace eval $name [list variable canvas $c angle $angle] interp alias {} $name: {} namespace eval ::car::$name $c create line $x [expr {$y + 5}] [expr {$x + 20}] [expr {$y + 5}] \ -tag [list $name object] -width 3 $c create line [expr {$x - 2}] [expr {$y + 40}] [expr {$x + 22}] \ [expr {$y + 40}] -tag [list $name object] -width 3 $c create poly [expr {$x + 2}] $y [expr {$x + 18}] $y \ [expr {$x + 20}] [expr {$y + 50}] $x [expr {$y + 50}]\ -fill $color -tags [list $name object] # Create the wheels wheel $name $c [expr {$x - 3}] [expr {$y + 5}] wheel $name $c [expr {$x + 23}] [expr {$y + 5}] wheel $name $c [expr {$x - 5}] [expr {$y + 40}] wheel $name $c [expr {$x + 25}] [expr {$y + 40}] canvas'rotate $c $name $angle # Run through the inputs sections adding the inputs set RE \ {(left|right|center)\s+input\s+(.+)\s+(\+|-)\s+->\s+(left|right)\s+wheel} foreach line [split $inputs \n] { if {[regexp $RE $line -> iside type posneg wside]} { # Create the input lappend ${name}::inputs [list $iside $type $posneg $wside] } } } proc car::wheel {name c x y {dx 3} {dy 6}} { set x0 [expr {$x - $dx}] set y0 [expr {$y - $dy}] set x1 [expr {$x + $dx}] set y1 [expr {$y + $dy}] $c create poly $x0 $y0 $x0 $y1 $x1 $y1 $x1 $y0 -fill black \ -tag [list $name object] } proc car::dispatch {name cmd args} {eval ::car::$cmd $name $args} proc car::move {} { variable cars global hits foreach name $cars { set c [$name: set canvas] # Work out the change in angle, by comparing the left and right wheel # speeds. set angle [$name: set angle] set rspeed [expr {[$name: set rspeed] + [$name: set rdiff]}] set lspeed [expr {[$name: set lspeed] + [$name: set ldiff]}] set nangle [expr {($rspeed - $lspeed)/5.}] # Mean speed set speed [expr {($rspeed + $lspeed)/2.}] canvas'rotate $c $name $nangle $name: set angle [expr {$angle + $nangle}] set dx [expr {-$speed * sin([$name: set angle])}] set dy [expr {-$speed * cos([$name: set angle])}] $c move $name $dx $dy # Turn back from walls foreach {x0 y0 x1 y1} [$c bbox $name] { break } if {$x1 < 0} { $c move $name [winfo width $c] 0 } if {$x0 > [winfo width $c]} { $c move $name -[winfo width $c] 0 } if {$y1 < 0} { $c move $name 0 [winfo height $c] } if {$y0 > [winfo height $c]} { $c move $name 0 -[winfo height $c] } # See if we have collided with any objects foreach {x y} [canvas'center $c $name] { break } set start {} set closest [$c find closest $x $y 30] while {$closest != $start} { if {[info exists hits($closest)]} { if {[incr hits($closest) -1] <= 0} { # Item is now "dead" set next [$c find closest $x $y 30 $closest] $c delete $closest set closest $next continue } # Flash the item to indicate it is hit set color [$c itemcget $closest -fill] $c itemconfigure $closest -fill white after 20 [list $c itemconfigure $closest -fill $color] } if {![string length $start]} {set start $closest } set closest [$c find closest $x $y 30 $closest] } # Find nearby objects and adjust speeds appropriately # Work out position of left and right inputs # Precomputed hypot and angle to corners set hypot 25.0 set phi 0.411516846067 set rx [expr {$x + $hypot * sin([$name: set angle] + $phi)}] set ry [expr {$y + $hypot * cos([$name: set angle] + $phi)}] set lx [expr {$x + $hypot * sin([$name: set angle] - $phi)}] set ly [expr {$y + $hypot * cos([$name: set angle] - $phi)}] set cx [expr {$x + $hypot * sin([$name: set angle])}] set cy [expr {$y + $hypot * cos([$name: set angle])}] array set totals { right,inputs 0 left,inputs 0 right,total 0 left,total 0 } foreach item [$name: set inputs] { foreach {iside type posneg wside} $item { break } # Find all overlapping items of the right type in a certain radius # (50 pixels) set start {} if {"$iside" == "left"} { set sx $lx set sy $ly } elseif {"$iside" == "right"} { set sx $rx set sy $ry } else { # Center set sx $cx set sy $cy } set closest [$c find closest $sx $sy 100] while {$closest != $start} { # Check the tag if {[lsearch [$c gettags $closest] $name] == -1} { if {[lsearch [$c gettags $closest] $type] > -1} { # Find distance from sensor foreach {ox oy} [canvas'center $c $closest] { break } set distance \ [expr {150-hypot(abs($ox-$sx),abs($oy-$sy))}] # Add to total set totals($wside,total) [expr $totals($wside,total) \ $posneg $distance] incr totals($wside,inputs) } } if {![string length $start]} { set start $closest } set closest [$c find closest $sx $sy 100 $closest] } } if {$totals(right,inputs) > 0} { set dr [expr {$totals(right,total) / $totals(right,inputs)}] if {$dr < -100} { set dr -100 } $name: set rdiff [expr {$dr / 10.0}] } else { $name: set rdiff 0 } if {$totals(left,inputs) > 0} { set dl [expr {$totals(left,total) / $totals(left,inputs)}] if {$dl < -100} { set dl -100 } $name: set ldiff [expr {$dl / 10.0}] } else { $name: set ldiff 0 } } } proc object {c x y} { global source hits switch $source { object { set color blue } light { set color yellow } temp { set color red } oxygen { set color cyan } organic { set color green } } set id [$c create oval $x $y [expr {$x + 20}] [expr {$y + 20}] \ -fill $color -tags $source] set hits($id) 15 $c bind $id <Button-3> [list $c delete $id] } proc canvas'center {w tag} { foreach {x0 y0 x1 y1} [$w bbox $tag] { break } list [expr {($x0 + $x1) / 2.}] [expr {($y0 + $y1) / 2.}] } proc canvas'rotate {w tag angle} { foreach {xm ym} [canvas'center $w $tag] { break } foreach item [$w find withtag $tag] { set coords {} foreach {x y} [$w coords $item] { set rad [expr {hypot($x-$xm, $y-$ym)}] set th [expr {atan2($y-$ym, $x-$xm)}] lappend coords [expr {$xm + $rad * cos($th - $angle)}] lappend coords [expr {$ym + $rad * sin($th - $angle)}] } $w coords $item $coords } } proc every {ms body} {eval $body; after $ms [info level 0]} # Create a means to add new sources to the landscape set source "light" frame .s label .s.source -text "Source:" radiobutton .s.light -text "Light" -variable ::source -value "light" radiobutton .s.obstacle -text "Object" -variable ::source -value "object" radiobutton .s.temp -text "Temp." -variable ::source -value "temp" radiobutton .s.oxygen -text "Oxygen" -variable ::source -value "oxygen" radiobutton .s.organic -text "Organic Matter" -variable ::source \ -value "organic" eval [list pack] [winfo children .s] [list -side left] pack .s -side top # Create the landscape pack [canvas .c -width 600 -height 400 -bg white] -fill both -expand 1 car::new foo .c 200 200 red 0.2 { left input light + -> right wheel right input light + -> left wheel left input temp + -> left wheel right input temp + -> right wheel left input oxygen - -> right wheel right input oxygen - -> left wheel left input organic - -> left wheel right input organic - -> right wheel } bind .c <Button-1> { object .c %x %y } bind .c <Button-3> { break } every 50 {car::move}
NEM 5June2003 Just updated the code to allow you to place various different stimuli in the environment, of different types. The initial configuration has now been changed from that described below, to the following (this corresponds to Vehicle 3: Type 3 in the second URL given at the top):
- Pair of light sensors connected with crossed excitatory connections
- Pair of temperature (temp) sensors connected with uncrossed excitatory connections
- Pair of oxygen (O2) sensors connected with crossed inhibitory connections
- Pair of organic matter sensors connected with uncrossed inhibitory connections
- The vehicle moves towards light in an aggressive manner - it attacks lights!
- The vehicle moves away from hot places,
- It prefers a well-oxygenated environment and one containing many organic molecules and will spend much of its time in such places (it loves oxygen, and it explores for organic matter),
- It moves elsewhere if the supply of oxygen or organic matter runs low.
This old configuration had two identical cars, which exhibit the following behaviour:
- Each moves towards sources of light (created by clicking left mouse button)
- Each moves away from objects (unless they are straight ahead).
proc old_config {} { car::new foo .c 150 200 red 0.2 { left input light + -> right wheel right input light + -> left wheel left input object + -> left wheel right input object + -> right wheel } }Another quite fun configuration is to make the second car move towards obstacles (and be indifferent to light). This tends to make that car more aggressive towards other cars, and so general wins when fighting them off (the other car will try to get away). To get this configuration, just change the config of the second car:
proc new_car {} { car::new bar .c 150 200 blue -0.2 { left input object + -> right wheel right input object + -> left wheel } }It's quite easy to play with new car configurations. Each line in the "script" part represents a sensor connection (# added for wiki-reapers):
# left input object + -> right wheelThe first word can be left, right or center and specifies where on the front of the car the sensor should be mounted. Next, comes the keyword "input", which is just there as sugar. After that is the type of sensor - this can be any canvas tag name. Currently, I only use "object" and "light" as types, but you can create any item on the canvas with any tag name and use it as the type. The next item is an indication of the connection type: '+' is a positive (excitory) connection, '-' is a negative (inhibitory) connection. You can use any valid expr operator here. The '->' is sugar again. Finally, we have an indication of which wheel this sensor should be connected to. You can create as many cars and objects and sensors as you wish. One of the key points that Braitenberg makes is that it is much easier to create seemingly complex behaviour from simple parts, than it is for an observer to guess the internal workings of an agent. This has interesting implications for us trying to understand systems in nature. Enjoy!
DKF: Updated so canvas resizing works...
escargo 4 Jun 2003 - While playing with this, I discovered that sometimes the car just stops moving; can you add inactivity detection (since you already added collision detection)?NEM - Note that car is supposed to stop moving at times. If it gets near enough to a stimulus for which it has inhibitory sensors connected to both sides (motors/wheels), then the chances are that it will stop moving, until the stimulus is removed. This is part of the behaviour. In the current initial configuration, such stimuli are oxygen and organic matter. It will tend to stop facing oxygen (a love relationship, in the text) and facing away from organic matter (it likes it, but is more willing to move away -- an explorer). If in fact, it stops when it is not near to any stimulus, then this is indeed some sort of bug, which needs fixing.escargo - Since the simulation is effectively dead until the environment changes, perhaps the user should be notified?