#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec wish "$0" ${1+"$@"}

###
# Droid Warz
#
# Written by Sean Woods
# Copyright 2008
#
# This file is released with the BSD license, yaddity, yaddity,
# no warrenty is expressed, implied, yaddity, yaddita,
# use for your own amusement at your own risk
###

# Version 0.2
#
# Changes: Braced expressions, and I threw in my proportional control
# algorithm for those who take the time to read the code
####

# SIMPLE VECTOR ROUTINES


proc pause {} {
    after 100 {set wait 0}
    vwait wait
}

set endgame 0

set vectorcode {
set ::pi [expr 4 * atan(1)]

namespace eval ::vector {
    proc destination_angle {subject target} {
        
    }
    
    proc heading_normalize heading {
        global pi
        ###
        # Normalize the heading
        ###
        while { $heading > $pi } {
            set heading [expr [list $heading - (2.0 * $pi)]]
        }
        while { $heading < -$pi} {
            set heading [expr [list $heading + (2.0 * $pi)]]
        }
        return $heading
    }
    
    ### Assumes a 3x3 matrix
    proc multMatrix {a b} {
        set result {{0 0 0} {0 0 0} {0 0 0}}
        for {set i 0} { $i < 3 } { incr i } {
            for { set j 0 } { $j < 3 } { incr j } {
                for { set k 0} { $k < 3 } {incr k} {
                    lset result $i $j [expr [list [lindex $result $i $j] + [lindex $a $i $k] * [lindex $b $k $j]]]
                }
            }
        }
        return $result
    }
    
    proc identity_Matrix {} {
        return {{1.0 0.0 0.0} {0.0 1.0 0.0} {0.0 0.0 1.0}}
    }

    proc translation_Matrix {translation} {
        return [list \
                [list 1.0 0.0 [lindex $translation 0]] \
                [list 0.0 1.0 [lindex $translation 1]] \
                [list 0.0 0.0 1.0]]
    }

    proc rotation_Matrix {angle} {
        return [list \
                [list [expr {cos($angle)}] [expr {-1.0 * sin($angle)}] 0.0] \
                [list [expr {sin($angle)}] [expr {cos($angle)}] 0.0] \
                [list 0.0 0.0 1.0]]
    }
    proc rotate_affine {vector angle} {
        set m [rotation_Matrix $angle]
        for {set i 0} {$i < 2} {incr i} {
            lappend result [expr {[lindex $vector 0] * [lindex $m 0 $i] + [lindex $vector 1]*[lindex $m 1 $i] + [lindex $m 2 $i]}]
        }
        return $result
    }
    
    proc rotate {vector angle} {
        set result {0 0}
        lset result 0 [expr {[lindex $vector 0] * cos($angle) + [lindex $vector 1] * -sin($angle)}]
        lset result 1 [expr {[lindex $vector 0] * sin($angle) + [lindex $vector 1] * cos($angle)}]
        return $result
    }
    
    proc length {a} {
        set x [lindex $a 0]
        set y [lindex $a 1]
        return [expr {sqrt($x*$x + $y*$y)}]
    }
    
    proc distance {a b} {
        set dx [expr {[lindex $a 0] - [lindex $b 0]}]
        set dy [expr {[lindex $a 1] - [lindex $b 1]}]
        return [expr {sqrt($dx*$dx + $dy*$dy)}]
    }

    
    proc add {a b} {
        return [list [expr {[lindex $a 0] + [lindex $b 0]}] [expr {[lindex $a 1] + [lindex $b 1]}]]
    }

    proc subtract {a b} {
        return [list [expr {[lindex $a 0] - [lindex $b 0]}] [expr {[lindex $a 1] - [lindex $b 1]}]]
    }
    
    proc scale {a s} {
        return [list [expr {[lindex $a 0] * $s}] [expr {[lindex $a 1] * $s}]]
    }
}
}
eval $vectorcode

namespace eval arena {

    proc init {} {
        destroy .play
        toplevel .play
        canvas .play.c -width 800 -height 800
        grid .play.c
    }
    proc delete name {
        set c .play.c
        $c delete $name
    }
    proc draw info {
        global pi
        foreach {var val} $info {set $var $val}
        set c .play.c
        $c delete $name
        set canpos [vector::add $position {400 400}]
        set angle [expr $direction]
        if { $range > 0 } {
            $c create oval {*}[vector::add $canpos [list -$range -$range]] {*}[vector::add $canpos [list $range $range]] -tags [list $name $class] -dash .
        }
        #$c bind $name  [list ::arena::info show $name %x %y]
        #$c bind $name  [list ::arena::info hide $name %x %y]
        switch $class {
            carrier {
                foreach p {{-1 0.5} {0.5 0.5} {1 0} {0.5 -0.5} {-1 -0.5}} {
                    lappend points {*}[vector::add $canpos [::vector::scale [vector::rotate $p $angle] 10]]
                }
               $c create polygon {*}${points} -tags [list $name $class] -fill $color
               $c create text [vector::add $canpos {10 -10}] -anchor w -text $name -tags $name -font {Fixed 10}

            }
            drone {
                foreach p {{1 0} {-1 -0.5} {-1 0.5}} {
                    lappend points {*}[vector::add $canpos [::vector::scale [vector::rotate $p $angle] 7.5]]
                }
               $c create polygon {*}${points} -tags [list $name $class] -fill $color
            }
            default {
                $c create oval {*}[vector::add $canpos {-5 -5}] {*}[vector::add $canpos {5 5}] -tags [list $name $class] -fill $color
            }
        }
    }
    proc info {cmnd tag x y} {
        set c .play.c
        if { $cmnd == "show" } {
            $c create text $x [expr {$y + 5}] -text $tag -tags info
        }
        if { $cmnd == "hide" } {
            $c delete  info
        }
    }
}

namespace eval game {
    variable objects
    variable protected_fields {position velocity mass power range direction spin docked team}
    variable bombcount 0
    variable playercolors {{} red green blue purple orange black grey}
    variable playercount 0
    
    proc dump object {
        variable objects
        return [lindex [array get objects $object] 1]
    }

    proc cset {object newstate} {
        variable objects
        foreach {var val} $newstate {
            dict set objects($object) $var $val
        } 
    }
    proc cget {object field} {
        variable objects
        return [dict get $objects($object) $field]
    }
    proc newobj info {
        variable objects
        set object {
            name {}
            class {}
            team {}
            state {}
            
            position {0 0}
            velocity {0 0}
            color green
            range 0
            weapon {}

            heading 0
            direction 0
            spin 0
            turn 0
            power 0
            moment 1
            thrust 0
            mass 0
            target {}
            hitpoints 1
        }
        foreach {var val} $info {
            dict set object $var $val
        }
        set name [dict get $object name]
        switch [dict get $object class]  {
            "carrier" {
                dict set object mass 1000
                dict set object power 75
                dict set object range 100
                dict set object moment 1000
                dict set object hitpoints 100
                for {set x 1} { $x <= 16 } {incr x} {
                    lappend drones $name.$x
                }
                dict set object docked $drones
            }
            "drone"  {
                dict set object mass 0.5
                dict set object power 0.1
                dict set object range 10
                dict set object moment 10
                dict set object hitpoints 5
                switch [dict get $object weapon] {
                    "recon" {
                        dict set object moment 50
                        dict set object mass 1.0
                        dict set object range 200
                    }
                    "laser" {
                        dict set object mass 0.75
                    }
                    "bomb" {
                        dict set object moment 100
                        dict set object mass 1.5
                    }
                }
            }
            default {
                dict set object mass 0.5
            }
        }
        set objects($name) $object
        return $object
    }
    proc delete name {
        variable objects
        array unset objects $name
        arena::delete $name
    }
    proc physics {obj} {
        global pi
        set result $obj
        foreach {var val} $obj {
            set $var $val
        }
        # Normalize control values
        set heading [::vector::heading_normalize $heading]
        dict set result heading $heading
        set thrust [dict get $obj thrust]
        if { $thrust > 1.0 } {
            set thrust 1.0
        }
        if { $thrust < -1.0 } {
            set thrust -1.0
        }
        dict set result thrust $thrust
        
        ###
        # We are either turning or thrusting
        ###
        if { abs($heading - $direction) > 0.01 } {
            set maxturn [expr $power/$moment]
            set turn [expr {($direction - $heading)/$moment}]
            if { $maxturn > abs($turn) } {
                set direction $heading
            } else {
                if { $direction > $heading } {
                    set direction [expr {$direction - $maxturn}]
                } else {
                    set direction [expr {$direction + $maxturn}]
                }
            }
            dict set result direction $heading
        } else {
            dict set result direction $heading
            set p [expr {[dict get $obj power] * $thrust}]
            set a [expr {$p / $mass}]
            set velocity [vector::add $velocity [list [expr {$a * cos($heading)}] [expr {$a * sin($heading)}]]]
            dict set result velocity $velocity
            dict set result speed [::vector::length $velocity]
        }
        set position [vector::add $velocity $position]
        dict set result position $position
        return $result
    }
    
    proc update {} {
        variable objects
        variable protected_fields
        global endgame
        
        if { $endgame } {
            return
        }
        ###
        # Update physical state of model
        ###
        
        foreach {name obj} [array get objects] {
            set newstate [physics $obj]
            dict set newstate target {}
            cset $name $newstate
            ::arena::draw $obj
        }
        
        ###
        # Update sensor picture
        ###
        set teams {}
        array unset assets
        array unset targets
        
        foreach {name obj} [array get objects] {
            set position [dict get $obj position]
            set range [dict get $obj range]
            set team [dict get $obj team]
            if { $team ni $teams } {
                lappend teams $team
            }
            foreach {iname iobj} [array get objects] {
                set iteam [dict get $iobj team]
                if { $iteam == $team } continue
                set ipos [dict get $iobj position]
                if { [::vector::distance $position $ipos] <= $range } {
                    dict set targets($team) $iname position $ipos
                    dict set targets($team) $iname velocity [dict get $iobj velocity]
                    dict set targets($team) $iname team [dict get $iobj team]
                    dict set targets($team) $iname class [dict get $iobj class]
                    dict lappend targets($team) $iname source $name
                }
            }
            dict set assets($team) $name $obj
        }
        
        foreach team $teams {
            if ![info exists targets($team)] {
                set targets($team) {}
            }
            ::process_commands $team [interp eval $team [list gameturn $team $assets($team) $targets($team)]]   
        }
        set liveteams {}
        set deadteams {}
        set deadites {}
        foreach {name obj} [array get objects] {
            if ![dict exists $obj target] continue
            if { [set t [dict get $obj target]] != {} } {
                ::game::fire $name $t
            }
        }
        foreach {name obj} [array get objects] {
            if { [dict get $obj hitpoints] <= 0 } {
                if { [dict get $obj class] == "carrier" } {
                    set deadteam $name
                    puts "team $deadteam eliminated"
                    foreach {a i} $assets($deadteam) {
                        lappend deadites $a
                    }
                    lappend deadteams $deadteam
                } else {
                    lappend deadites $name
                    puts "$name destroyed"
                }
            } else {
                if { [dict get $obj class] == "carrier" } {
                    lappend liveteams [dict get $obj team]
                }
            }
        }
        foreach name $deadites {
            delete $name
        }
        if { [llength $liveteams] == 0 } {
            puts "Game was a draw between $deadteams"
            puts "Epic Fail"
            set endgame 1
        }
        if { [llength $liveteams] == 1 } {
            puts "Game was a won by [lindex $liveteams 0]"
            puts "This sector contains high concentrations of WIN"
            set endgame 1            
        }
    }

    proc dock {carrier drone} {
        variable objects
        if { $drone == $carrier } {
            return
        }
        if ![info exists objects($drone)] {
            return
        }
        set cpos [game::cget $carrier position]
        set dpos [game::cget $drone position]
        if { [vector::distance $cpos $dpos] > 2.0 } {
            return
        }
        puts [list $drone docked with $carrier]
        ::game::delete $drone
        set docked [::game::cget $carrier docked]
        lappend docked $drone
        ::game::cset $carrier [list docked $docked]
    }
    
    proc fire {shooter target} {
        ###
        # Does the shooter have a laser?
        ###
        variable objects
        if ![info exists objects($shooter)] return
        if ![info exists objects($target)] return

        set sinfo [dump $shooter]
        set tinfo [dump $target]
        if ![dict exists $sinfo position] return
        if ![dict exists $tinfo position] return

        set shots 0
        if { [dict get $sinfo class] == "carrier" } {
            set shots 2
        }
        if { [dict get $sinfo class] == "drone" && [dict get $sinfo weapon] == "laser" } {
            set shots 1
        }
        if !$shots {
            return
        }
        ###
        # Measure relative distance and velocity
        ###
        set deltapos [::vector::distance [dict get $sinfo position] [dict get $tinfo position]]
        set deltavel [::vector::distance [dict get $sinfo velocity] [dict get $tinfo velocity]]
        set difficulty [expr {($deltapos * $deltapos) * (.001 + $deltavel * $deltavel)}]
        ###
        # The probability starts at 100, divided by the distance and relative velocty
        ###
        # Carriers, with two cannons, have two chances to hit
        ###
        for {set x 0} {$x < $shots} {incr x} {
            set hit [expr {rand() * $difficulty}]
            if { $hit < 5.0 } {
                puts [list $shooter hit $target [game::cget $target hitpoints] $hit $difficulty]
                hit $target
            } else {
                #puts [list $shooter miss $target $hit $difficulty]

            }
        }
    }

    proc hit {target {points 1}} {
        set hitpoints [cget $target hitpoints]
        incr hitpoints [expr {-1 * int($points)}]
        cset $target [list hitpoints $hitpoints]
    }

    proc launch {carrier drone info} {
        puts [list $carrier launch $drone $info]
        set state [dump $carrier]
        set docked [dict get $state docked]
        set idx [lsearch $docked $drone]
        if {$idx < 0 } {
            error "$drone is not docked"
        }
        set docked [lreplace $docked $idx $idx]
        ::game::cset $carrier [list docked $docked]
        dict unset state docked
        dict set state name $drone
        dict set state thrust 1
        dict set state weapon laser
        dict set state class drone
        foreach {var val} $info {
            dict set state $var $val
        }
        ::game::newobj $state
    }

    proc bomb_release {team drone} {
        variable bombcount
        set state [dump $drone]
        if { [dict get $state weapon] != "bomb" } return
        set heading [dict get $state heading]
        set heading [expr {$heading + $::pi / 2.0}]
        set position [dict get $state position]
        set velocity [dict get $state velocity]
        cset $drone [list mass 0.5 weapon {} heading $heading thrust 1]
        set name BOMB.[incr bombcount]
        puts [list BOMB RELEASE $drone $team]
        newobj [list class bomb name $name position $position velocity $velocity team $team]

    }
    
    proc bomb_detonate {team bomb} {
        set state [dump $bomb]
        if ![dict exists $state position] {
            return
        }
        set position [dict get $state position]
        variable objects
        foreach {name obj} [array get objects] {
            if { $name == $bomb } continue
            set p [dict get $obj position]
            set distance [::vector::distance $p $position]
            if { $distance == 0.0 } {
                set damage 100
            } else {
                set damage [expr int(1000/($distance * $distance))]
            }
            if { $damage > 0 } {
                puts [list BLAST DAMAGE $bomb $name $damage [expr int($distance)]]
                hit $name $damage
            }
        }
        cset $bomb {hitpoints 0}
    }    

    proc player {team botcode} {
        global pi vectorcode
        variable playercount
        variable playercolors

        set state {
        name {}
        team {}
        class carrier
        direction 0
        heading 0
        position {200 200}
        color blue
        range 100
        }
        dict set state name $team
        dict set state team $team
        set heading [expr {rand() * $pi / 8 + $pi/3 * $playercount}]
        dict set state position [list [expr {(rand()*100 + 200) * cos($heading)}] [expr {(rand()*100 + 200) * sin($heading)}]]
        set heading [expr {$pi - rand() * $pi * 2}]
        dict set state heading $heading
        dict set state direction $heading
        dict set state color [lindex $playercolors [incr playercount]]
        game::newobj $state
        
        if [interp exists $team] {
            interp delete $team
        }
        interp create $team
        interp eval $team $vectorcode
        interp eval $team $botcode
    }
    
}


proc process_commands {team commandlist} {
    foreach command $commandlist {
        switch [lindex $command 0] {
            launch {
                ::game::launch $team [lindex $command 1] [lindex $command 2]
            }
            dock {
                ::game::dock $team [lindex $command 1]
            }
            bomb_release {
                ::game::bomb_release $team [lindex $command 1]
            }
            bomb_detonate {
                ::game::bomb_detonate $team [lindex $command 1]
            }
            maneuver {
                foreach {field value} [lindex $command 2] {
                    if {$field in {thrust heading}} {
                        ::game::cset [lindex $command 1] [list $field $value]
                    }
                }
            }
            fire {
                ::game::cset [lindex $command 1] [list target [lindex $command 2]]
            }
            {} {
            }
            default {
                puts [list ? $team $command]
            }
        }
    }
}

set botcommon {
    ###
    # Common Bot Code
    ###
    
    ###
    # Improved version of proper_heading that uses
    # an error correcting proportional control algorith
    # that minumizes turns while elimination the "Hey
    # lets come to a stop to change direction" behavior
    # of the default"
    ####

    proc proper_heading {objectinfo destination flank} {
        global pi
        dict with objectinfo {
        set px [lindex $position 0]
        set py [lindex $position 1]
        if { abs($px) > 390 || abs($px) > 390 } {
            set destination {0 0}
        }
        ###
        # Compute our error in V
        ###
        set v $velocity
        set delta [::vector::subtract $destination $position]
        set desired_theta [expr atan2([lindex $delta 1],[lindex $delta 0])]
        set desiredv [list [expr $flank * cos($desired_theta)] [expr $flank * sin($desired_theta)]]
        set dv [vector::subtract $desiredv $velocity]
        set thrust [expr [::vector::length $dv] * $mass]
        set heading [expr atan2([lindex $dv 1],[lindex $dv 0])]
        return [list thrust $thrust heading $heading]
        }
    }
    
    ###
    # Select the proper heading to align our craft with
    # our intended destination
    #
    # This is a broken and crufted algorithm that is only left in so we can
    # seperate the men from the boys. If you copy and paste this code into
    # your bot, you deserve the inherent fail that befalls you
    ####
    proc proper_heading {objectinfo destination flank} {
        global pi
        dict with objectinfo {
        set px [lindex $position 0]
        set py [lindex $position 1]
        if { abs($px) > 390 || abs($px) > 390 } {
            set destination {0 0}
        }
        
        set delta [::vector::subtract $destination $position]
        
        set desired [expr {atan2([lindex $delta 1],[lindex $delta 0])}]
        set vd      [expr {atan2([lindex $velocity 1],[lindex $velocity 0])}]

        ###
        # brake until the direction is right
        ####
        if { abs($desired - $vd) > 0.001 && $speed > 0.01 } {
            set thrust [expr {$speed / $mass / $power}]
            return [list thrust [expr {$thrust * -1.0}] heading $vd]
        }
        set thrust [expr {($flank - $speed)/$mass/$power}]
        ###
        # Speed control
        ###
        #if { [vector::length $velocity] > 10.0 } {
        #    return [list thrust 0.0 heading $desired]
        #}
        return [list thrust $thrust heading $desired]
        }
    }
    
    proc select.target {asset info targets} {
        dict with info {
            ###
            # Find closest target
            ###
            set mytarget {}
            set mydistance 1000
            set myposition {}
            set myvelocity {}

            foreach {target tinfo} $targets {
                set tpos [dict get $tinfo position]
                set distance [::vector::distance $position $tpos] 
                if { $distance < $mydistance } {
                    set mytarget $target
                    set mydistance $distance
                    set myposition $tpos
                    set myvelocity [dict get $tinfo velocity]
                }
            }
            if { $mytarget != {} } {
              return $mytarget
            }
        }
    }

    proc enemy_trail {myinfo targetinfo} {
        set dx [vector::add [dict get $targetinfo position] [dict get $targetinfo velocity]]
        set p [dict get $myinfo position]
        set distance [vector::distance $p $dx]
        if { $distance < 125 } {
            set heading [proper_heading $myinfo $dx 2.0]
            set angle [dict get $heading heading]
            dict set heading heading [expr {$angle + $::pi}]
            return [list maneuver [dict get $myinfo name] $heading]
        }
        # Sweet spot, stay put
        if { $distance < 200 } {
            set heading [proper_heading $myinfo $dx 0.01]
            return [list maneuver [dict get $myinfo name] $heading]
        }
        set heading [proper_heading $myinfo $dx 2.0]
        return [list maneuver [dict get $myinfo name] $heading]
    }
    
    proc carrier_dock {asset info} {
        dict with info {
            set cp $::carrier_location
            set cv $::carrier_velocity
            set distance [::vector::distance $position $cp]
            set cs [vector::length $cv]
            if { $distance < 2.0 } {
                return [list dock $asset]
            }
            set speed [expr {0.1 + sqrt($distance)/10.0 + $cs}]
            if { $distance < 2.0 } {
                set speed $cs
            }
            set heading [list maneuver $asset [proper_heading $info $cp $speed]]
        }
    }

    proc obj.drone.recon {asset info} {
        global pi
        dict with info {
            if { $hitpoints < 4 } {
                return [carrier_dock $asset $info]
            }
            if { $::rallypoint != {0 0} } {
            set delta [::vector::subtract $::rallypoint $position]
            if {[vector::length $delta] > ($range * 1.5) } {
                ###
                # Out of bounds? Aim for the center
                ###
                set heading [proper_heading $info $::rallypoint 5.0]
                return [list maneuver $asset $heading]
            }
            if {[vector::length $delta] > ($range * 1.05) } {
                ###
                # Out of bounds? Aim for the center
                ###
                set heading [proper_heading $info $::rallypoint 0.5]
                return [list maneuver $asset $heading]
            }
            if {[vector::length $delta] > ($range * .95) } {
                ###
                # Out of bounds? Aim for the center
                ###
                set heading [proper_heading $info $::rallypoint 0.1]
                return [list maneuver $asset $heading]
            }
            if {[vector::length $delta] < ($range * 0.90)} {
                ###
                # Out of bounds? Aim for the center
                ###
                set heading [proper_heading $info $::rallypoint 0.1]
                set angle [dict get $heading heading]
                dict set heading heading [expr {$angle + $::pi / 2.0}]
                return [list maneuver $asset $heading]
            }
            }
            set thrust [expr {(1.0 - $speed)/$mass}]
            return [list maneuver $asset [list thrust $thrust]]
        }
    }
    
    proc obj.drone.laser {asset info targets} {
        dict with info {
            ###
            # Find closest target
            ###
            set firedistance 300
            set target [select.target $asset $info $targets]
            if { $target == {} } {
                if { $hitpoints < 4 } {
                    return [carrier_dock $asset $info]
                }
                return [list maneuver $asset [proper_heading $info $::rallypoint 5.0]]
            }
            set dv [vector::add [dict get $targets $target position] [dict get $targets $target velocity]]
            set speed [expr {[::vector::length [dict get $targets $target velocity]] + 2}]
            return [list maneuver $asset [proper_heading $info $dv $speed]]

        }
    }
    
    proc obj.drone.bomb {myinfo targetinfo dockvar} {
        dict with myinfo {
            upvar 1 dockvar docking
            if { $weapon != "bomb" } {
                set docking 1
                return [carrier_dock $name $myinfo]
            }
            # Lead the target
            set dx [vector::add [dict get $targetinfo position] [::vector::scale [dict get $targetinfo velocity] 3.0]]
            set distance [vector::distance $position $dx]
            if { $distance < 250 && $speed > 10.0 } {
                return [list bomb_release $name]
            }
            if { $distance < 120 && $speed > 1.0} {
                set docking 1
                return [list bomb_release $name]
            }
            set heading [proper_heading $myinfo $dx 20.0]
            return [list maneuver $name $heading]
        }
    }
    
    proc obj.bomb {bomb info assets targets} {
        dict with info {
        global sympathetic_detonation
        global lastdistance
        if ![info exists lastdistance($bomb)] {
            set lastdistance($bomb) 2000
        }
        set detonate 0
        set firedistance 300
        set fire {}

        foreach {target tinfo} $targets {
            if { [dict get $tinfo class] != "carrier" } continue
            set tpos [dict get $tinfo position]
            set distance [::vector::distance $position $tpos] 
            if { $distance > $lastdistance($bomb) && $distance < 100.0 } {
                # We are starting to veer away
                set detonate 1
                set mytarget $target
                set mydistance $distance
                set myposition $tpos
                set myvelocity [dict get $tinfo velocity]
                
            }
            set lastdistance($bomb) $distance
        }
        if !$detonate {
            set distance [::vector::distance $position $::rallypoint]
            if { $distance < 5.0 } {
                set detonate 1
            }
        }
        if { $detonate } {
            lappend fire [list bomb_detonate $bomb]
            foreach {asset ainfo} $assets {
                set apos [dict get $ainfo position]
                set aclass [dict get $ainfo class]
                if {[vector::distance $apos $position] < 60 && $aclass == "bomb" } {
                    lappend fire [list bomb_detonate $asset]
                }
            }
        }
        return $fire
        }
    }
}

set unibot $botcommon
append unibot {
    ###
    # A "Housebot" algorithm with known limitations
    #
    ###
    set init 0
    set aistate search
    set rallypoint {0 0}
    set slice -1
    set scouts {}
    set bombers {}
    set fighters {}
    set timestamp 0
 
    proc gameturn {team assets targets} {
        global init pi
        global drones
        global carrier_location
        global slice
        global aistate
        global scouts fighters bombers
        set commands {}
        set sector [expr 2.0 * $pi / 6.0]
        incr ::timestamp

        set ::sympathetic_detonation {}
        
        set docked [lsort -dictionary [dict get $assets $team docked]]
        if { $scouts == {} } {
            set scouts {}
            set fighters {}
            set bombers {}
            
            lappend scouts [lindex $docked 0]
            foreach d [lrange $docked 1 end] {
                switch [expr int(rand() * 6.0)] {
                    0 - 1 { lappend scouts $d }
                    2 - 3 - 4 { lappend bombers $d }
                    4 - 5 - 6 { lappend fighters $d }
                }
            }
            
            foreach drone [lrange $docked 0 5] {
                set heading [list thrust 1 heading [expr $sector * [incr slice]]]
                dict set heading weapon recon
                lappend commands [list launch $drone $heading]
            }
        }
        set ::carrier_location [dict get $assets $team position]
        set ::carrier_velocity [dict get $assets $team velocity]

        foreach {name tinfo} $targets {
            if { [dict get $tinfo class] == "carrier" } {
                set ::destroytarget $tinfo
                set ::rallypoint [dict get $tinfo position]
                set ::aistate destroy
            }
        }
        
        foreach {asset info} $assets {
            dict with info {
                if { ($class == "droid" && $weapon == "laser") || $class == "carrier" } {
                    if { [set t [select.target $asset $info $targets]] != {} } {
                        lappend commands [list fire $asset $t]
                    }
                }
                if { $class == "bomb" } {
                    foreach c [obj.bomb $asset $info $assets $targets] {
                        lappend commands $c
                    }
                }
            }
        }

        set docking 0
        if { $::aistate == "search" } {
            foreach {asset info} $assets {
                lappend commands [obj.drone.recon $asset $info]
            }
            return $commands
        }
        foreach drone $docked {
            ###
            # Launch in the direction of the enemy carrier
            ###
            set heading [proper_heading [dict get $assets $team] [vector::add $::rallypoint [list [expr 10 - rand()*20] [expr 10 - rand()*20]]] 10.0]
            dict set heading thrust 1.0
            if { $drone in $scouts } {
                dict set heading weapon recon
            }
            if { $drone in $fighters } {
                dict set heading weapon laser
            }
            if { $drone in $bombers } {
                dict set heading weapon bomb
            }
            lappend commands [list launch $drone $heading]
            break
        }
        foreach {asset info} $assets {
            dict with info {
            if { $weapon == "recon" && $asset in $scouts} {
                lappend commands [enemy_trail $info $::destroytarget]
            } elseif { $weapon == "laser" } {
                lappend commands [obj.drone.laser $asset $info $targets]
            } elseif { $weapon == "bomb" } {
                lappend commands [obj.drone.bomb $info $::destroytarget docking]
            } elseif { $class == "carrier" } {
                
            } else {
                lappend commands [carrier_dock $asset $info]
            }
            }
        }
        set init 1
        return $commands
    }  
}


proc step {count} {
    for {set x 0} {$x < $count} {incr x} {
        ::game::update
        pause
        if { $::endgame } return
    }
}

proc demo {} {
    global pi unibot
    global endgame
    set endgame 0
    array unset ::game::objects
    ::arena::init
    wm geometry .play +0+0
    ::game::player PLAYER1 $unibot
    ::game::player PLAYER2 $unibot
    for {set x 0} {$x < 1000} {incr x} {
        ::game::update
        pause
        if $endgame break
    }
}

demo