#!/usr/local/bin/wishx -f

#########################################################################
# Botworld simulator written in tcl/tk by Claude Sammut (UNSW)
#
# This is a reimplementation, with modifications, of the original
# Botworld sumulator developed at Stanford by Patrick Teo
#
# Heavily modified by Malcolm Ryan (UNSW)
#
# v2.0 Added primitives for communications to/from bot - "in", "out" and
#      "err". These procs also echo to the stdout (helpful for debugging)
#
#      Also changed collision detection to be a little less kludgy - but
#      it may now be significantly slower. Will have to do comparisons.
#      Collisions only occur with other bots (bodies only) and boxes.
# 
#      Added "world(Width, Height)" predicate, which is sent to a prolog
#      process when it is created.
#
# v3.0 Allowed collisions with beacons and bars also.
#
#      Added a $thing() array, which takes a canvas object number and
#      returns a list of the form "<obj> <num>" where <obj> is the type
#      of the object (bot, bar, box, beacon, rightArm, leftArm, speech)
#      and num is the bot, box, bar, or beacon number.
#
#      Arms now go over obstacles (not under)
#
# v4.0 Collisions now work by finding all overlapping objects, and removing
#      any speech bubbles or arms, plus the botBody of the bot in question
#
#      The main program now reads a Tcl file as the "world definition",
#      given by the first command-line arg
#
# v5.0 Moved the demo stuff into a separate file, which can be run using
#      'botworld demo.tcl'
#
#      Note: You can now supply zero prolog agents, if you so desire.
#
# v6.0 Any errors reported by the Prolog interpreter are now saved to
#      bot_errors.n, where n is the number of the bot. (They used to go
#      into the bit bucket.)
#
# v7.0 Added look2 command, which returns various predicates required by
#      my TR planner.
#
#      I also changed the maths a bit, so that angles are consistent with
#      the Cartesian coordinates system. (Ie, positive angles go from the
#      +ve x-axis towards the +ve y-axis). Graphically, this means that 
#      a positive angle turns the bot clockwise.
#
# v8.0 Fixed turnTo, to use the correct coordinates & atan2 (which removes
#      a lot of the fussing about).
#
#      Fixed 'source' command in main, so that world files are run at
#      the global level.
#
#      Removed look2 & related stuff (developing that stream independantly)
#
#      Fixed demo file so that it runs.
#
# v9   Gave up fractional version numbering.
#
#      Revamped error-reporting, so that any prolog errors are output to
#      display In order to do so, I have made input from the clients to 
#      be non-blocking. This has no particular disadvantages AFAICS. 
#      But you can no longer rely on bots working in strict order, one 
#      command at a time
#
# v10  Rewrote onBar to fix division-by-zero bug. On the way, I 
#      discovered that onBar would produce "true" if the hands of the
#      robot were (approximately) on the line of the bar - regardless
#      of whether they were actually anywhere near the bar itself.
#      Fixed this also - involved remembering lots of first-year algebra.
#      Ulch.
#
# v11  Added environment variables GLOBAL_STACK and LOCAL_STACK which are
#      used to set the global and local stack sizes for the prolog processes
#
#      Made bar_length a global var (used to be a magic number)
#
# v12  Bars are no longer obstacles. (Many students breath a sigh of relief.)
#
# v13  Fixed "say" bug
#
# v14  Fixed bug in moving bars around - $barX and $barY were not being
#      updated.
#
# v15  Drop no longer crashes when you are not holding anything
#      onBar now uses 'hand_radius' variable
#
# v16  Fixed atan2(0,0) bug in turnTo
#
# v17  Changed go() to start()
#      Added constant definition for beacon_radius
#
# v18  Added different wheel radii.
#      Added collision_flag to record when a bot has collided with 
#      something.
#      Removed sonar to a separate file 'sonar.tcl'.
#      set a global variable "prolog" to show the path to prolog
#
# v18b fixed move bug  
#      put back gotoBeacon
#      Release version for cs3411 in 1998
#
#      Added a global variable to give the path the botworld_lib.pro
#
#########################################################################

#########################################################################
# Path to iProlog
#########################################################################

set prolog "/usr/local/bin/prolog"
#set prolog "/bin/sh ../../run -n -b"
set botworld_lib "./botworld_lib.pro"

#########################################################################
# Create the initial window with an arbitrary width and height.
# The fill option on the pack command means that the canvas will resize
# with the window.
#########################################################################

set width 400
set height 350

canvas .c -width $width -height $height
pack .c -fill both

#########################################################################
# sin, cos and tan require arguements in radians.
# The following constants are for converting to and from degrees.
#########################################################################

set pi 3.141592653589793
set radians [expr $pi/180]
set degrees [expr 180/$pi]

#########################################################################
# These parameters describe the bot geometry
#########################################################################

set bot_radius 15
set arm_length 12
set arm_angle  30
set hand_radius 3
set reach [expr ($bot_radius+$arm_length)*cos($arm_angle*$radians)]

set bar_length 50
set beacon_radius 5

# wheel_ratio is the ratio of sizes of the wheels
# if wheel_ratio > 1, it will veer left
# if wheel_ratio < 1, it will veer right
# if wheel_ratio = 1, it will go straight
#
# wheel_separation is the distance between the two wheels

set wheel_ratio 1.0
set wheel_separation $bot_radius

#########################################################################
# Comms
#
# Standard routines for I/O to a bot, and error reporting.
# Echos all I/O to stdout, with the syntax "b >", "b <", "b !" meaning
# "output to", "input from" and "error about" bot b, respectively.
#########################################################################

proc out {bot string} {
    global client

    puts $client($bot) $string
    puts "$bot > $string"
}

proc in {bot} {
    global client

    set string [gets $client($bot)]

    if {$string != ""} {
	puts "$bot < $string"
    }

    expr {$string}
}

proc err {bot string} {

    puts "$bot ! $string"
}

proc tell_errors {} {

    global errors nbots
    
    for {set bot 1} {$bot <= $nbots} {incr bot} {
	while {[gets $errors($bot) line] >=0} {
	    err $bot $line
	}
    }
}

#########################################################################
# Create an obstacle
#########################################################################

proc box {new x1 y1 x2 y2} {
    global box thing

    set box($new) [.c create rectangle $x1 $y1 $x2 $y2 -fill green \
	    -tags box]

    set thing($box($new)) "box $new"

#    puts "Created thing($box($new)) with value $thing($box($new))"
    

}

#########################################################################
# Create a beacon
#########################################################################

proc beacon {new x y} {
    global beaconX beaconY thing beacon_radius

    set b [.c create oval [expr $x-$beacon_radius] [expr $y-$beacon_radius] \
	    [expr $x+$beacon_radius] [expr $y+$beacon_radius] \
	    -outline black -fill white -tags beacon]
    set b2 [.c create oval [expr $x-2] [expr $y-2] [expr $x+2] [expr $y+2] \
	    -fill black]

    set thing($b) "beacon $new"
    set thing($b2) "beacon $new"

#    puts "Created thing($b) with value $thing($b)"
#    puts "Created thing($b2) with value $thing($b2)"

    set beaconX($new) $x
    set beaconY($new) $y
}

#########################################################################
# Move a bar to the specified position and angle
#########################################################################

proc placeBar {b x y angle} {
	global radians bar barX barY barAngle bar_length

	if {$angle < 0} {set angle [expr 360 - $angle]}
	set dx [expr $bar_length / 2 * cos($angle*$radians)]
	set dy [expr $bar_length / 2 * sin($angle*$radians)]

	.c coords $bar($b) [expr $x+$dx] [expr $y+$dy] [expr $x-$dx] [expr $y-$dy]

	set barX($b) $x
	set barY($b) $y
	set barAngle($b) $angle
}

#########################################################################
# Create a bar and put it at its initial position and angle.
# Held is a boolean array for recording if a bar is currently being
# held by a bot.
#########################################################################

proc bar {new x y angle} {
    global bar held thing

    set bar($new) [.c create line 0 0 0 0 -width 2 -tags bar]
	
    set thing($bar($new)) "bar $new"
#    puts "Created thing($bar($new)) with value $thing($bar($new))"

    set held($new) 0
    placeBar $new $x $y $angle
}

#########################################################################
# A bot consists of an oval body and two lines for the arms.
# This proc creates the components and draws then in their initial
# positions. botX and botY store the centre point of the oval. This is
# also taken to be the position of the bot.
# We also initialise arrays which store which bar the bot holds and the
# speech string.
#########################################################################

proc bot {new x y angle} {
    global botBody botX botY leftArm rightArm botAngle \
	radians bot_radius arm_angle arm_length holding speech thing \
	speech_text collision_flag

    set botBody($new) [.c create oval [expr $x-10] [expr $y-10] \
	    [expr $x+10] [expr $y+10] -outline black -fill red -tags bot]

    set thing($botBody($new)) "bot $new"

#    puts "Created thing($botBody($new)) with value $thing($botBody($new))"

    set botX($new) $x
    set botY($new) $y
    set botAngle($new) $angle
    set holding($new) 0             
    # 0 -> nothing
    set speech($new) 0
    set speech_text($new) ""
    set collision_flag($new) 0

    set cosx [expr cos(($angle+$arm_angle)*$radians)]
    set sinx [expr sin(($angle+$arm_angle)*$radians)]
    set arm_x2 [expr $x + (($bot_radius + $arm_length) * $cosx)]
    set arm_y2 [expr $y + (($bot_radius + $arm_length) * $sinx)]
    set leftArm($new) [.c create line $x $y $arm_x2 $arm_y2 -tags arm]

    set thing($leftArm($new)) "leftArm $new"
#    puts "Created thing($leftArm($new)) with value $thing($leftArm($new))"

    set cosx [expr cos(($angle-$arm_angle)*$radians)]
    set sinx [expr sin(($angle-$arm_angle)*$radians)]
    set arm_x2 [expr $x + (($bot_radius + $arm_length) * $cosx)]
    set arm_y2 [expr $y + (($bot_radius + $arm_length) * $sinx)]
    set rightArm($new) [.c create line $x $y $arm_x2 $arm_y2 -tags arm]
    
    set thing($rightArm($new)) "rightArm $new"
#    puts "Created thing($rightArm($new)) with value $thing($rightArm($new))"

    .c raise $botBody($new)
}

#########################################################################
# Collisions are now done by getting the list of things overlapping the
# bot's bounding box, and removing any speech bubbles or bot arms from 
# that list (plus the bot's own body). Using the $thing() array, we can
# identify whatever with have collided with.
#########################################################################

proc ldelete {list1 list2} {

    set result $list1

    foreach x $list2 {

	set i [lsearch -exact $result $x]
	if {$i != -1} {
	    set result [lreplace $result $i $i]
	}
    }

    return $result
}

proc collision {bot} {
    global botBody thing 

    set b [.c bbox $botBody($bot)]
    set x0 [lindex $b 0]
    set y0 [lindex $b 1]
    set x1 [lindex $b 2]
    set y1 [lindex $b 3]

    # find all overlapping objects

    set collisions [.c find overlapping $x0 $y0 $x1 $y1]

    # remove the bot itself

    set collisions [ldelete $collisions $botBody($bot)]

    # remove unwanted objects (this is not the neatest way to do this.
    # it would be better to specify the wanted rather than the unwanted objs)

    # remove all arms

    set collisions [ldelete $collisions [.c find withtag arm]]

    # remove all speech bubbles

    set collisions [ldelete $collisions [.c find withtag speech]]

    # remove all bars

    set collisions [ldelete $collisions [.c find withtag bar]]

    # remove all drawing lines

    set collisions [ldelete $collisions [.c find withtag draw]]

    foreach obj $collisions {
	err $bot "Collided with $thing($obj)"
    }

    return [expr [llength $collisions] > 0]
}

#########################################################################
# Create a new bot at a randomly chosen position and angle. If the bot
# is in collision with another object, randmly move it somewhere else
# until it is clear.
#########################################################################

proc new_bot {n} {
	global width height attached

	bot $n [random $width] [random $height] [random 360]

	while {[collision $n]} {
		moveTo $n [random $width] [random $height]
	}
}

#########################################################################
# Attach a speech string to a specfied bot
#########################################################################

proc say {bot str} {
    global botX botY speech thing speech_text

    set speech_text($bot) $str
    
    if {$speech($bot)} {
	.c delete $speech($bot)
    }
    set x [expr $botX($bot) + 15]
    set y [expr $botY($bot) - 15]
    set speech($bot) [.c create text $x $y -anchor sw -width 1.5c \
	    -text $str -tags speech]

    set thing($speech($bot)) "speech $bot"
#    puts "Created thing($speech($bot)) with value $thing($speech($bot))"
    
    update
}

#########################################################################
# Remove a speech string from the specified bot
#########################################################################

proc silent {bot} {
	global speech speech_text

	if {$speech($bot)} {
		.c delete $speech($bot)
	}

	set speech($bot) 0
	set speech_text($bot) ""

	update
}

#########################################################################
# Move a bot to a new position.
# If the bot is holding a bar, move that too.
# Call "update" to ensure the display is updated as the bot moves.
#########################################################################

proc moveTo {bot x y} {
	global botX botY botAngle botBody leftArm rightArm speech holding \
		radians reach

	set xDist [expr $x - $botX($bot)]
	set yDist [expr $y - $botY($bot)]

	.c move $botBody($bot) $xDist $yDist
	.c move $leftArm($bot) $xDist $yDist
	.c move $rightArm($bot) $xDist $yDist
	if {$speech($bot)} {
		.c move $speech($bot) $xDist $yDist
	}

	if {$holding($bot) != 0} {
	    set cosx	[expr cos(($botAngle($bot))*$radians)]
	    set sinx	[expr sin(($botAngle($bot))*$radians)]
	    set barx	[expr $botX($bot) + ($reach * $cosx)]
	    set bary	[expr $botY($bot) + ($reach * $sinx)]

	    placeBar $holding($bot) $barx $bary [expr $botAngle($bot) + 90]
	}
	update

	set botX($bot) $x
	set botY($bot) $y
}

#########################################################################
# This is the main routine for moving a bot, including collision
# detection. The bot is moved one unit at a time until the required
# distance is covered. 
#########################################################################

proc move {bot dist} {
    global botBody botX botY botAngle leftArm rightArm radians degrees
    global wheel_ratio wheel_separation collision_flag

    set cosa [expr cos($botAngle($bot)*$radians)]
    set sina [expr sin($botAngle($bot)*$radians)]
    set x $botX($bot)
    set y $botY($bot)
    
    set forward [expr $dist > 0]
    set dist [expr abs($dist)]
    
    for {set i 1} {$i <= $dist} {incr i} {
	
	set prevX $x
	set prevY $y

	if {$forward} {
	    set x [expr $x + $cosa]
	    set y [expr $y + $sina]
	} else {
	    set x [expr $x - $cosa]
	    set y [expr $y - $sina]
	}	
	
	# because one wheel turns faster than the other, the robot turns as it moves.

	set d_theta [expr ((1 - $wheel_ratio) / $wheel_separation) * $degrees]

	if {$forward} {
	    turn $bot $d_theta
	} else {
	    turn $bot [expr - $d_theta]
	}
	
	set cosa [expr cos($botAngle($bot)*$radians)]
	set sina [expr sin($botAngle($bot)*$radians)]

	moveTo $bot $x $y
	
	if {[collision $bot]} {

	    set collision_flag($bot) 1

	    moveTo $bot $prevX $prevY 
	    return 0
	}
    }
    return 1
}    

#########################################################################
# Turn a bot and any bar that it is holding.
# Angles are measure in degees, with 0 lying on the +ve x axis.
# Turn angles may be negative, so the first thing to do is map the new
# angle into the right range.
#########################################################################

proc turn {bot angle} {
	global	botX botY leftArm rightArm botAngle holding\
		radians bot_radius arm_angle arm_length reach

	set botAngle($bot) [expr $botAngle($bot) + $angle]

	if {$botAngle($bot) > 360} {
		set botAngle($bot) [expr $botAngle($bot) - 360]
	}
	if {$botAngle($bot) < 0} {
		set botAngle($bot) [expr 360 + $botAngle($bot)]
	}

	set cosx	[expr cos(($botAngle($bot)+$arm_angle)*$radians)]
	set sinx	[expr sin(($botAngle($bot)+$arm_angle)*$radians)]
	set arm_x2	[expr $botX($bot) + (($bot_radius + $arm_length) * $cosx)]
	set arm_y2	[expr $botY($bot) + (($bot_radius + $arm_length) * $sinx)]

	.c coords $leftArm($bot) $botX($bot) $botY($bot) $arm_x2 $arm_y2

	set cosx	[expr cos(($botAngle($bot)-$arm_angle)*$radians)]
	set sinx	[expr sin(($botAngle($bot)-$arm_angle)*$radians)]
	set arm_x2	[expr $botX($bot) + (($bot_radius + $arm_length) * $cosx)]
	set arm_y2	[expr $botY($bot) + (($bot_radius + $arm_length) * $sinx)]

	.c coords $rightArm($bot) $botX($bot) $botY($bot) $arm_x2 $arm_y2

	if {$holding($bot) != 0} {

	    set cosx	[expr cos(($botAngle($bot))*$radians)]
	    set sinx	[expr sin(($botAngle($bot))*$radians)]
	    set barx	[expr $botX($bot) + ($reach * $cosx)]
	    set bary	[expr $botY($bot) + ($reach * $sinx)]

	    placeBar $holding($bot) $barx $bary [expr $botAngle($bot) + 90]
	}

	return $botAngle($bot)
}

#########################################################################
# We often want the point the bot towards a particular location.
# This proc works out the angle that the bot has to turn to and calls
# the routing that actually turns the bot.
# This proc also returns the distance to the target point.
#########################################################################

proc turnTo {bot x y} {
    global botX botY botAngle degrees

    if {($x == $botX($bot)) && ($y == $botY($bot))} {
	return 0
    }

    set dx [expr $x - $botX($bot)]
    set dy [expr $y - $botY($bot)]

    set theta [expr atan2($dy, $dx)*$degrees]
        
    turn $bot [expr $theta - $botAngle($bot)]
    
    return [expr sqrt($dx*$dx + $dy*$dy)]
}

#########################################################################
# Given a target location, turn the bot towards it and move, in a
# straight line toward the target. If the bot hits an obstacle it,
# stops just before the obstacle.
#########################################################################

proc goto {bot x y} {
	move $bot [turnTo $bot $x $y]
}

#########################################################################
# Go to a beacon
#########################################################################

proc gotoBeacon {bot beacon} {
	global beaconX beaconY
	goto $bot $beaconX($beacon) $beaconY($beacon)
}

#########################################################################
# To go to a bar, find a position on one side or the other of the bar
# which is at arms length from the bot. Chose the closest point.
# Try to go straight to the point. If goto returns 0 a collision has
# occurred. If there was no collision, turn to face the bar.
#########################################################################

proc gotoBar {bot bar} {
	global barX barY barAngle reach radians

	set angle [expr $barAngle($bar)]
	set x1 [expr $barX($bar) + $reach*sin($angle*$radians)]
	set y1 [expr $barY($bar) - $reach*cos($angle*$radians)]
	set x2 [expr $barX($bar) - $reach*sin($angle*$radians)]
	set y2 [expr $barY($bar) + $reach*cos($angle*$radians)]

	if {[turnTo $bot $x1 $y1] < [turnTo $bot $x2 $y2]} {
		set x $x1
		set y $y1
	} else {
		set x $x2
		set y $y2
	}

	if {[goto $bot $x $y]} {
		turnTo $bot $barX($bar) $barY($bar)
		return 1
	} else {
		return 0
	}
}


#########################################################################
# onBar works out if the hands of the bot are over the bar so that it
# can be grabbed. We just work out the equation of the line for the bar
# and solve for the end points of the arms.
#########################################################################

proc onBar {bot barNo} {
    global bar leftArm rightArm hand_radius
    
    set coords [.c coords $bar($barNo)]
    set x1 [lindex $coords 0]
    set y1 [lindex $coords 1]
    set x2 [lindex $coords 2]
    set y2 [lindex $coords 3]
 
    # The equation of the line: ax + by = c 
    
    set a [expr $y1 - $y2]
    set b [expr $x2 - $x1]
    set c [expr $x2 * $y1 - $x1 * $y2]

    set d [expr sqrt($a * $a + $b * $b)]

    # the coordinates of the left hand

    set hand [.c coords $leftArm($bot)]
    set xh [lindex $hand 2]
    set yh [lindex $hand 3]

    # check the distance from the line of the bar 

    set dp [expr ($a * $xh + $b * $yh - $c) / $d]
    
    if {abs($dp) > $hand_radius} {return 0}

    # project it onto the normal

    set xp [expr $xh - ($a * $dp / $d)]
    set yp [expr $yh - ($b * $dp / $d)]
    
    # check this hand lies within the interval of the bar

    if {$x1 > $x2} {
	if {$xp > $x1} {return 0}
	if {$xp < $x2} {return 0}
    } else {
	if {$xp < $x1} {return 0}
	if {$xp > $x2} {return 0}
    }

    if {$y1 > $y2} {
	if {$yp > $y1} {return 0}
	if {$yp < $y2} {return 0}
    } else {
	if {$yp < $y1} {return 0}
	if {$yp > $y2} {return 0}
    }

    # the coordinates of the right hand

    set hand [.c coords $rightArm($bot)]
    set xh [lindex $hand 2]
    set yh [lindex $hand 3]

    # check the distance from the line of the bar

    set dp [expr ($a * $xh + $b * $yh - $c) / $d]
    
    if {abs($dp) > $hand_radius} {return 0}

    # project it onto the normal

    set xp [expr $xh - ($a * $dp / $d)]
    set yp [expr $yh - ($b * $dp / $d)]

    # check this hand lies within the interval of the bar

    if {$x1 > $x2} {
	if {$xp > $x1} {return 0}
	if {$xp < $x2} {return 0}
    } else {
	if {$xp < $x1} {return 0}
	if {$xp > $x2} {return 0}
    }

    if {$y1 > $y2} {
	if {$yp > $y1} {return 0}
	if {$yp < $y2} {return 0}
    } else {
	if {$yp < $y1} {return 0}
	if {$yp > $y2} {return 0}
    }

    return 1
}

#########################################################################
# Try to grab a bar. First check if the bot is already holding something
# or the bar is being held by another bot. Also call onBar to check of
# the bot's hands are over the bar.
# Update the "holding" and "held" entries and add arrows to the bar as
# a visual indicator that the grab succeeded.
#########################################################################

proc grab {bot b} {
	global holding held bar

	if {$holding($bot) != 0} {
		err $bot "already holding $holding($bot)"
		return 0
	}
	if {$held($b)} {
		err $bot "Bar has already been grabbed by another bot"
		return 0
	}
	if {[onBar $bot $b]} {
		.c itemconfigure $bar($b) -arrow both
		set holding($bot) $b
		set held($b) 1
		return 1
	} else {
		err $bot "Not close enough to bar"
		return 0
	}
}

#########################################################################
# drop just reverses the actions of grab.
#########################################################################

proc drop {bot} {
    global holding held bar
    
    if {$holding($bot) == 0} {
	err $bot "Not holding anything."
    } else {
	.c itemconfigure $bar($holding($bot)) -arrow none
	set held($holding($bot)) 0
	set holding($bot) 0
	# 0 -> nothing
    }
}

#########################################################################
# Output on given channel, Prolog facts describing the fixed objects
# in the botworld. The fixed obijects are boxes and beacons.
# First we abolish any previous facts.
#########################################################################

proc fixed_objects {bot} {
	global box beaconX beaconY

	out $bot "abolish(box, 5)?"

	for {set i 1} {$i <= [array size box]} {incr i} {
		set output "box($i"
		foreach x [.c coords $box($i)] {
			set output [concat $output [format ", %1.0f" $x]]
		}
		set output [concat $output ")."]

		out $bot $output
	}

	out $bot "abolish(beacon, 3)?"

	for {set i 1} {$i <= [array size beaconX]} {incr i} {
		out $bot "beacon($i, $beaconX($i), $beaconY($i))."
	}
}

#########################################################################
# Output on given channel, Prolog facts describing the movable objects
# in the botworld. The movable objects are bots and bars.
# 
# This information will be sent to the running prolog program, and not to
# the interpreter, so it is up to the program to abolish any old facts
#########################################################################

proc movable_objects {bot} {
	global	bar barX barY barAngle \
		botX botY botAngle holding speech_text

#
# bar(Id, X, Y, Angle).
#

    for {set i 1} {$i <= [array size bar]} {incr i} {
	out $bot "bar($i, $barX($i), $barY($i), $barAngle($i))."
    }
    
#
# bot(Id, X, Y, Angle, Holding, Speech).
#

    for {set i 1} {$i <= [array size botX]} {incr i} {
	out $bot "bot($i, $botX($i), $botY($i), $botAngle($i), $holding($i), \"$speech_text($i)\")."
    }
}

#########################################################################
# Send sensor readings to bot
#########################################################################

proc sensors {bot} {
	global bar

	for {set i 1} {$i <= [array size bar]} {incr i} {
		if {[onBar $bot $i]} {
		        out $bot "onBar($bot, $i)."
		}
	}
}

#########################################################################
# This is provided as a hook for the agent to request an update of the
# state of the world
#########################################################################

proc look {bot} {
	movable_objects $bot
	sensors $bot
	out $bot "end."
}

#########################################################################
# Initialise all the client processes by opening a channel to them.
# Given the name of a file containing a Prolog program, Tcl forks a new
# prolog process and creates a pipe.
# The channel must use full buffering, otherwise botworld will block
# while waiting for input.
# The "attached" array records the fact that bot n will be attached to a
# this process.
# Send to the client descriptions of the fixed objects in the world,
# create and tell it which bot it controls.
#########################################################################

proc initialise_clients {agents} {
    global prolog botworld_lib env client attached width height errors nbots
    
    set i 0

    if {[catch {set global_stack "-g $env(GLOBAL_STACK)"}]} {
	set global_stack ""
    } else {
	puts "Global stack size: $env(GLOBAL_STACK)k"
    }
    
    if {[catch {set local_stack "-l $env(LOCAL_STACK)"}]} {
	set local_stack ""
    } else {
	puts "Local stack size: $env(LOCAL_STACK)k"
    }

    foreach p $agents {
	incr i

	set err_file "bot_errors.$i"

	err $i "Opening pipe to $p"

	set client($i) \
		[open "|$prolog $botworld_lib $p 2> $err_file" r+]
#		[open "|$prolog $global_stack $local_stack $botworld_lib $p \
#		2> $err_file" r+]
	fconfigure $client($i) -buffering line -blocking 0
	set attached($i) 1

	set errors($i) [open $err_file r]

	out $i "abolish(world, 2)?"
	out $i "world($width, $height)."
	fixed_objects $i
	new_bot $i
	update
	out $i "start($i)?"
    }

    # save the number of bots in a global var.

    set nbots $i
}

#########################################################################
# Detach the bot from its controlling processes. This must be invoked
# from the process as its last action.
#########################################################################

proc detach {b} {
	global attached

	set attached($b) 0
        err $b "Bot detached"

}

#########################################################################
# If botworld is invoked with command line arguments, these are assumed
# to be the names of Prolog programs, which will control their own bot.
# The clients are initialised and then polled for instructions to the
# bots. Instructions are tcl commands which are evaluated.
#
# In each cycle, the program checks to see if a client is still attached
# to its bot. If it is, an update of the states of the movable objects
# is sent to the client and then it is asked for its next move. All
# communication is accomplished by writing Prolog commands on the pipe
# and reading tcl commands from the pipe.
# Thus, the Prolog program must have a "move" predicate to respond to
# botworld and the Prolog program must write to standard output strings
# which are valid tcl commands to move, turn, etc.
#
# The Prolog program can send only one comamnd at a time. Note that if
# the Prolog program fails to respond, botworld will block.
#
# When the Prolog program has completed its task, it should send a
# "detach" command.
#########################################################################

proc main {argc argv} {
    global attached

    if {$argc < 1} {
	puts stderr \
		"Usage: botworld <world.tcl> \[<prolog program> ...\]"
	exit 0
    }
    
    uplevel #0 source [lindex $argv 0]
    
    set argv [lreplace $argv 0 0]
    set argc [llength $argv]
    
    initialise_clients $argv
    
    set input_ready 1
    while {$input_ready} {
	set input_ready 0
	for {set i 1} {$i <= $argc} {incr i} {
	    
	    tell_errors
	    
	    if {$attached($i)} {
		
		set input_ready 1
		eval [in $i]
		update
	    }
	}
    }
    
    puts "Done."
}

main $argc $argv
