An Active 9 colony. Its three types (fighters, gatherers and constructors) are all visible.
Gatherer with active dodging
21 April 2012
As its name suggests, Active was the first side with active dodging. The implementation is extremely complex, but the result is impressive. It is nearly immune to long-range blasters, it ignores shots that won't hit, it makes its enemies appear to narrowly miss by accident, and it wins tournaments.
Its complexity makes it difficult to learn from, but don't be intimidated; Active uses a complex multilayered form of dodging. It can be done in much simpler ways.
Early versions that had fighters with eaters could eat while fighting and dodging. For example, it would sometimes dodge by moving between two nearby foods. This prevented it from running out of energy in combat. Since version 7(?) the fighters no longer have eaters so it doesn't do this anymore. The gatherers do fine with passive dodging and running away, so Warren hasn't tried giving the gatherers this active dodging while eating feature.
Old versions were weakened by rule changes that made movement harder, but versions 7 and 8 separated combat from economy and retweaked dodging, so Active 8 was the best side as of July 2012. It has since been dethroned by its variant Big Bertha.
- Active Large is a variant with larger cells to exploit their combat advantages.
- Big Bertha is a variant with a giant one-cell seed that later creates an Active colony.
#side Active 9 #author Warren #color f08 #seed 1 2 3 2 3 2 2 3 Changes in version 9 include: * Gatherers passive dodge when see danger, even when eating (circle food). * Keeps track of center of mass of the constructor cells (see COLONY_POSITION). This is not used much yet, but is quite helpful in the derived Big Bertha side. Changes in version 8 include: * Redone active dodging. * Gatherers run away better. * Partial but effective friendly fire avoidance. * Removed enemy-syphon from fighters. Increased blaster size to compensate. My impression is that the friendly fire protection is the primary reason for the improved performance compared to version 7. ;################ SHARED ################# #code ;;;;types #const CTOR_TYPE 1 #const GATHERER_TYPE 2 #const FIGHTER_TYPE 3 ;;;;message channels ;;#const help-channel 1 ;position time #const kill-channel 2 #const safe-channel 3 ;#const hungry-channel 5 #const noff-channel 6 ; SM_id position #const food-channel 10 ; position amount time #const hungry-channel 4 ; id type ;;;;;;;;;;;;;;;;shared memory and related;;;;;;;;;;;;;;;;;;;;; ;001-660 are ally stats. When ALLY_SIZE=10, 10I-9 to 10I stores info for id I, e.g. 001-010 is id 1. ;format: x y vx vy t energy-level ;these offsets include the base implicitly, so e.g. velocity of I is at ROBOT_STAT_SIZE * I + ALLY_VEL_OFFSET #const ALLY_POS_OFFSET -5 #const ALLY_VEL_OFFSET -3 #const ALLY_TIME_OFFSET -1 #const ALLY_HUNGER_OFFSET -0 #const NUM_ROBOTS 110 #const ALLY_SIZE 6 ;;do 10 for easy debugging #const UPPER_ROBOT_AREA 660 ;; NUM_ROBOTS * ALLY_SIZE, the highest address in the ALLY shared memory ;701-950 are food claims ;format: claim-time #const FOOD_CLAIM_BASE 701 #const NUM_FOOD_CLAIMS 250 #const DANGER_TIME 970 #const COLONY_POSITION 975 ;981-990 are side records. Currently only one item is stored, namely the time at which we have/will make peace with each side #const SIDE_BASE 980 #const SIDE_SIZE 1 ;################# SHARED VARS ###################### #var temp #vector vtemp #var hunger #var my-id ;;usually id, but if id>NUM_ROBOTS we assign manually ;;;;;;;;;;; real shared code ;;;;;;;;;;;; call-for-help: position time 3 kill-channel send return ;; called at initialization to set my-id to an unused slot assign-id: ;; -- id NUM_ROBOTS <= if id my-id! else ;; take a dead 'bots ID ALLY_SIZE ALLY_TIME_OFFSET + temp! do temp read time 1000 - < if sync temp read time 1000 - < and-if ;;sync and check again to avoid two robots claiming same id time temp write ;;claim it temp ALLY_TIME_OFFSET - ALLY_SIZE / my-id! return then temp ALLY_SIZE + temp! temp UPPER_ROBOT_AREA > until-loop stop ;;population over NUM_ROBOTS; panic! then return ;Streamlined version of equivalent from Walled City 2. ;Looks like it should work on any CPU 7 or greater. #var food-hash claim-food: food-position drop world-width / ;stack: between 0 and 1 NUM_FOOD_CLAIMS * floor ;stack: presumably between 0 inclusive and NUM_FOOD_CLAIMS exclusive FOOD_CLAIM_BASE + food-hash! ;staack empty time 400 + ;put on stack for later food-hash sync read time < ClaimExpired& ifg ;valid claim already not ;;drops and then pushes "0" since time+400 != 0. return ClaimExpired: ;stack: time+400 food-hash write 1 return ;; adress -- quality ;; calculate-sink-quality: temp! temp ALLY_POS_OFFSET + vread position syphon-range in-range ;stack: in-range or not temp ALLY_HUNGER_OFFSET + read * ;stack: quality return #var last-id-announce -1000 #var energy-sink-adr 0 ; 0 for none. #var energy-sink-quality #var received-adr #var received-type write-our-posn: my-id ALLY_SIZE * temp! position temp ALLY_POS_OFFSET + velocity temp ALLY_VEL_OFFSET + time temp ALLY_TIME_OFFSET + hunger temp ALLY_HUNGER_OFFSET + ;;stack: stuff to write sync write write vwrite vwrite return announce-if-hungry: time last-id-announce 100 + > if hunger 0.05 > and-if my-id 1 hungry-channel send time last-id-announce! then return listen-to-hungry: energy-sink-adr if energy-sink-adr ALLY_TIME_OFFSET + read time 30 - > and-if ;we have an up to date sink energy-sink-adr calculate-sink-quality^ energy-sink-quality! else 0 energy-sink-adr! then do #var skipped 0 population 10 / floor random-int dup skipped! hungry-channel skip-messages hungry-channel receive while ALLY_SIZE * received-adr! syphon-range 2 > if ;; i.e. if this robot is a type with a syphon update-energy-sink^ then ; process-announcement^ loop return ;;This function writes our data to shared memory, announces our existence, and listen to other announcements ;; -- communicate-unused: ;;first write our own stuff write-our-posn^ ;;second announce if hungry announce-if-hungry^ ;;third listen to other announcements listen-to-hungry^ return update-energy-sink: received-adr my-id ALLY_SIZE * = ifr energy-sink-adr replace-sink& nifg received-adr calculate-sink-quality^ temp! temp energy-sink-quality <= ifr replace-sink: received-adr energy-sink-adr! temp energy-sink-quality! return ;;This function transfers energy to neediest friend in range ;; -- transfer-energy: energy-sink-adr energy-sink-quality and if energy max-energy / 0.2 > and-if hunger energy-sink-adr ALLY_HUNGER_OFFSET + read < and-if syphon-max-rate negate syphon-rate! energy-sink-adr ALLY_POS_OFFSET + vread position v- energy-sink-adr ALLY_VEL_OFFSET + vread time energy-sink-adr ALLY_TIME_OFFSET + read - 5 + vs* v+ velocity 5 vs* v- rect-to-polar syphon-direction! syphon-distance! else 0 syphon-rate! then return ;;################################################# #type Ctor ;#decoration 0f0 cross #color 00f #hardware processor 18 energy 600 3 armor 120 engine 0.09 solar-cells 0.02 shot-sensor 5 robot-sensor 4 constructor 2.0 ;;2.3 #code ; -- ; This procedure responds to a received type/id pair, passed in received-* variables, and responds as needed. ; For Ctor, we keep track of process-announcement: return out-of-bounds: ;x y -> bool 2dup 0 < swap 0 < or if 2drop 1 return then world-height > swap world-width > or return #var gutter set-gutter: ;-- population sqrt 2 * gutter! ;;was 3 return keep-away-edges: ; x y -> x y gutter max world-height gutter - min swap gutter max world-width gutter - min swap return edge-dist: ;x y -> d 2dup min swap world-height swap - min swap world-width swap - min return #vector home #var last-armor #var last-birth-time 0 #var baby-cost #var num-children 0 #start max-armor last-armor! assign-id^ constructor-type if ;;seeded with fetus time last-birth-time! constructor-remaining baby-cost! ;;;hack then max-repair-rate repair-rate! time 10 > if set-gutter^ do position 2 population sqrt 4 * + random-angle polar-to-rect v+ home! home edge-dist^ gutter > until-loop else position COLONY_POSITION vwrite ;set home, snugging against nearby walls set-gutter^ #const max-snug-dist 28 #const snug-edge-dist 10 #var snugged 0 position drop world-width 2 / - abs world-width 2 / max-snug-dist - > if position drop world-width 2 / > world-width snug-edge-dist - snug-edge-dist ifev 1 snugged! else position drop then ;stack: home x position nip world-height 2 / - abs world-height 2 / max-snug-dist - > if position nip world-height 2 / > world-height snug-edge-dist - snug-edge-dist ifev 1 snugged! else position nip then ;stack: home x, y snugged nif 5 position world-width world-height 2 vs/ v- angle polar-to-rect v+ then ;;move towards wall keep-away-edges^ home! then #var loop-time do friendly-collision position home 3 in-range and if position 1 random-angle polar-to-rect v+ home! then ;;fix home here set-gutter^ home keep-away-edges^ home! time loop-time! position sync COLONY_POSITION vread 100 vs* v+ 101 vs/ COLONY_POSITION vwrite ;;move colony position towards us constructor-type nif last-birth-time if #var power-consumption baby-cost time last-birth-time - / power-consumption! ;base type on how long it's taken us to reproduce constructor-max-rate 1.0 num-children 0.1 * - 0.7 max * power-consumption < and-if CTOR_TYPE constructor-type! else ;either we're newly born ourselves or else reproduction was slow last time. FIGHTER_TYPE type-population population / 0.45 / 1 min random-bool GATHERER_TYPE FIGHTER_TYPE ifev constructor-type! then constructor-remaining constructor-type CTOR_TYPE = if 0.5 * then baby-cost! ;contains hack so we don't build CTOR twice in a row time last-birth-time! num-children 1 + num-children! then constructor-progress 200 > 0.04 0.2 ifev max-energy * energy < constructor-max-rate 0 ifev constructor-rate! #var last-danger-time -555 14 periodic-shot-sensor if shot-found if shot-sensor-time last-danger-time! time 300 + shot-side SIDE_BASE + write call-for-help^ home position 5 in-range nif position shot-velocity unitize 15 vs* v+ home! else shot-velocity unitize 3 population sqrt + vs* home v+ home! then then then 30 periodic-robot-sensor if robot-found if robot-sensor-time last-danger-time! then then armor dup last-armor < if time last-danger-time! then last-armor! time last-danger-time 200 + < if home position v- 0.05 vs* 0.2 time 9 / polar-to-rect v+ engine-velocity! ;;period is 2pi * 7 engine-max-power engine-power! else home seek-location then 1 energy max-energy / - 1.5 / 0.8 energy exponent + hunger! write-our-posn^ announce-if-hungry^ forever ;;################################################# #type Gatherer ;#decoration 0f0 cross #color 0f0 #hardware processor 23 energy 400 15 armor 150 engine 0.05 food-sensor 9 3 shot-sensor 5 robot-sensor 4 eater 2.0 syphon 1.4 30 #code assert-stack-empty: stack nifr pause return ;shared variables #var typical-food-amount ;message received variables #vector received-food-position #var received-food-amount #var received-food-time #vector next-meal-position #vector wander-position #var begin-food-chase-time #vector birth-place #var scary-shot-found 0 #var flee-type #vector flee-position #var flee-time Update-stats-food-msg: time received-food-time - 50 < if received-food-position position dist 30 < and-if received-food-amount 0.1 * typical-food-amount 0.9 * + typical-food-amount! then return #const edge-space 4 ;this subreutine copied from eventually 12 random-edge-position: 0 1 random-int if 0 1 random-int edge-space world-width edge-space - ifev edge-space world-height edge-space - random else edge-space world-width edge-space - random 0 1 random-int edge-space world-height edge-space - ifev then return out-of-bounds: ;x y -> bool 2dup 0 < swap 0 < or if 2drop 1 return then world-height > swap world-width > or return #const gutter 5 keep-away-edges: ; x y -> x y gutter max world-height gutter - min swap gutter max world-width gutter - min swap return new-wander-position: population 30 > if random-edge-position^ else birth-place 25 population + random-angle polar-to-rect v+ keep-away-edges^ then return #var last-armor Defend: energy max-energy / 0.1 > if time robot-sensor-time - time flee-time 300 + > 15 8 ifev > if fire-robot-sensor fire-shot-sensor sync shot-found shot-side side <> shot-type 3 <> or and scary-shot-found! ;;shot-type 3 is friendly-syphon scary-shot-found if ;;ignore friendly-syphons. Call-for-help^ ;;Same-side recording is harmless ; shot-side side <> if time 300 + shot-side SIDE_BASE + write ; then else last-armor armor > robot-found if robot-found 2 >= robot-side SIDE_BASE + read time > or or then Call-for-help& ifc then then ;time to fire sensor else ;no energy to sense 0 scary-shot-found! then last-armor armor dup last-armor! > scary-shot-found or energy max-energy / 0.05 > and if rdrop Begin-fleeing& jump then return common: CTOR_TYPE type-population 3 <= if COLONY_POSITION vread birth-place! then 0.7 energy exponent hunger! write-our-posn^ listen-to-hungry^ announce-if-hungry^ transfer-energy^ return ;;;;;;;;;;; START #start 1 shot-sensor-sees-friendly! assign-id^ position birth-place! Begin-seeking-food: armor last-armor! new-wander-position^ wander-position! do common^ ; energy max-energy / 0.08 > if defend^ ; then wander-position position dist 5 < if new-wander-position^ wander-position! then wander-position position v- unitize 0.2 0.09 energy max-energy / 0.02 > ifev vs* engine-velocity! engine-max-power engine-power! birth-place position v- rect-to-polar food-sensor-focus-direction! 4 / food-sensor-focus-distance! ;food code copied from cyclops 30 periodic-food-sensor if food-found if Food-check-loop: food-velocity norm nif claim-food^ and-if food-position next-meal-position! Begin-eating-food& jump else next-food Food-check-loop& ifg then then ;food-found then transfer-energy^ ;;also called in common^ do food-channel receive while received-food-time! received-food-amount! received-food-position! update-stats-food-msg^ received-food-amount 10 / received-food-position position dist > if ;ooooooo ffoooooodd received-food-position next-meal-position! Begin-eating-food& jump then loop forever adjust-engine-eating: time flee-time 500 + < if next-meal-position position radius 2 * in-range and-if ;passive dodge circling while touching food 0.05 radius 3 / position next-meal-position v- angle 1 + polar-to-rect next-meal-position v+ position v- angle polar-to-rect engine-velocity! engine-max-power engine-power! else ;straight to food next-meal-position position v- 0.07 vs* engine-velocity! next-meal-position position velocity 5 vs* v+ radius 2 / in-range not engine-max-power * engine-power! then return Begin-eating-food: time begin-food-chase-time! do adjust-engine-eating^ common^ adjust-engine-eating^ do food-channel receive while received-food-time! received-food-amount! received-food-position! update-stats-food-msg^ loop ; energy max-energy / 0.05 > if defend^ ; then adjust-engine-eating^ transfer-energy^ adjust-engine-eating^ eaten not if time begin-food-chase-time - 200 300 random > ;long time position next-meal-position radius in-range or ;on food and-if ; Begin-seeking-food& jump then energy max-energy / 0.99 > birth-place position 25 in-range energy-sink-adr energy-sink-quality and and not and Begin-homing& ifg forever Begin-fleeing: scary-shot-found if ;;run away from both shot position and shot velocity. ;;this calculation ensures we never flee towards either. position shot-position v- rect-to-polar swap 1.5 > swap polar-to-rect ;;unit vector if norm >1, zero otherwise. This means relative position of close shots ignored. shot-velocity unitize shot-side side = if vnegate then v+ unitize 15 vs* position v+ flee-position! 1 flee-type! ; shot-side side <> if ; 15 shot-velocity angle polar-to-rect position v+ flee-position! ; else ; ;;it's our team's shot. Run away from the shot itself (a combat zone) ; position shot-position v- unitize 15 vs* position v+ flee-position! ; then else robot-found time robot-sensor-time - 20 < and if robot-position position v- unitize -20 vs* position v+ flee-position! 2 flee-type! else #comment do safe-channel receive while time swap - 100 < if 2dup position dist 50 < if flee-position! Got-safe-place& jump else 2drop then else 2drop then loop ; random-edge-position^ flee-position! Got-safe-place: #code ; velocity norm if ; 13 velocity angle pi + polar-to-rect position v+ flee-position! 3 flee-type! ; else 13 birth-place position v- angle polar-to-rect position v+ flee-position! 4 flee-type! ; then then ;robot-found then ;shot-found time flee-time! ; armor last-armor! do common^ 0.3 flee-position position v- angle time flee-time - 80 mod 40 >= 0.6 -0.6 ifev + polar-to-rect engine-velocity! engine-max-power engine-power! time flee-time - 85 > flee-position position dist 4 < or energy max-energy / 0.02 < or if Begin-seeking-food& jump then energy max-energy / 0.05 > time flee-time - 100 > and if time shot-sensor-time - 15 > and-if fire-shot-sensor sync shot-found shot-side side <> shot-type 3 <> or and scary-shot-found! ;;shot-type 3 is friendly-syphon scary-shot-found if ;;ignore friendly-syphons. Call-for-help^ time 300 + shot-side SIDE_BASE + write ;;run away from both shot position and shot velocity. ;;this calculation ensures we never flee towards either. position shot-position v- rect-to-polar swap 1.5 > swap polar-to-rect ;;unit vector if norm >1, zero otherwise. This means relative position of close shots ignored. shot-velocity unitize shot-side side = if vnegate then v+ unitize 15 vs* position v+ flee-position! 5 flee-type! then else 0 scary-shot-found! ;;is this needed? then ;time to fire sensor forever Begin-homing: do common^ birth-place seek-location birth-place position 20 in-range energy max-energy / 0.3 < or if Begin-seeking-food& jump then forever ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Fighter #type Fighter #decoration fcc cross #color f00 #hardware energy 700 80 ;;includes extra for syphon resistance armor 180 repair-rate 0.025 processor 50 ;to dodge in a few frames engine 0.15 robot-sensor 15 3 shot-sensor 9 4 blaster 40.4 14.5 26 ; large blaster enemy-syphon 0.0 13.5 ;negligible #code #var temp2 #const pseudo-max-energy 400 #const RUN_HOME_ENERGY 100 ;communication with Dodge-and-move: #vector desired-velocity 0 0 #var speed-slop 0.01 ;communication with Eat-and-move: #vector desired-position random-position: 0 world-width random 0 world-height random return ;returns distance to nearest wall wall-distance: ; -> dist position min world-size position v- min min return #const WALL_OFFSET 5 restrict-location: ;x y -> x y world-height WALL_OFFSET - min WALL_OFFSET max swap world-width WALL_OFFSET - min WALL_OFFSET max swap return #const FIGHT_DISTANCE 12 ;14 #var CRUSADE_DIST #const MIN_CRUSADE_DIST 10 #var have-crusade 0 #var crusade-time #vector crusade-position #vector birth-place #var wander #var wait-far-from-home-factor ;;between 0 and 8 buddy-quality: ;; id -> quality ALLY_SIZE * temp! temp ALLY_TIME_OFFSET + read time 50 - < if 0 ;;out of date record else 1000 temp ALLY_POS_OFFSET + vread position dist - then return #var buddy-one-id 70 #var buddy-two-id 75 #var buddy-one-quality #var buddy-two-quality #var buddy-rec-id #var buddy-rec-quality #vector buddy-one-pos #vector buddy-two-pos #vector buddy-one-cur-pos #vector buddy-two-cur-pos #vector buddy-one-vel #vector buddy-two-vel #var buddy-one-time #var buddy-two-time #var have-target ;;bool #vector wander-loc -99 -99 #const gutter 11 keep-away-edges: ; x y -> x y gutter max world-height gutter - min swap gutter max world-width gutter - min swap return #start ;shot-sensor-firing-cost print 0.3 random-bool FIGHTER_TYPE type-population 4 >= and wander! assign-id^ 22 45 random-int CRUSADE_DIST! 5 20 random wait-far-from-home-factor! position birth-place! position desired-position! Begin-normaling: do CTOR_TYPE type-population 3 <= if COLONY_POSITION vread birth-place! then birth-place keep-away-edges^ birth-place! ;;;;keep track of two nearest buddies (as defined in buddy-quality proc) #var buddy-announce-time -999 time buddy-announce-time - 50 > if my-id 1 noff-channel send time buddy-announce-time! then buddy-one-id buddy-quality^ buddy-one-quality! buddy-two-id buddy-quality^ buddy-two-quality! dodge-and-move^ do noff-channel receive while ; id buddy-rec-id! buddy-rec-id my-id <> buddy-rec-id buddy-one-id <> buddy-rec-id buddy-two-id <> and and if buddy-rec-id buddy-quality^ buddy-rec-quality! buddy-rec-quality buddy-one-quality buddy-two-quality min > and-if ;;replace one of them buddy-one-quality buddy-two-quality < if buddy-rec-id buddy-one-id! buddy-rec-quality buddy-one-quality! else buddy-rec-id buddy-two-id! buddy-rec-quality buddy-two-quality! then then loop dodge-and-move^ energy pseudo-max-energy / shot-found 0.09 0.2 ifev > No-target& nifg Wait: ;;unused label do blaster-cooldown while blaster-cooldown 2 >= if sync dodge-and-move^ else sync then loop Scan: ;;unused label robot-sensor-time robot-found have-crusade or blaster-reload-time 60 ifev + time <= if sync buddy-one-id ALLY_SIZE * temp! temp ALLY_POS_OFFSET + vread buddy-one-pos! temp ALLY_VEL_OFFSET + vread buddy-one-vel! temp ALLY_TIME_OFFSET + read buddy-one-time! buddy-one-pos buddy-one-vel time buddy-one-time - vs* v+ buddy-one-cur-pos! buddy-two-id ALLY_SIZE * temp! temp ALLY_POS_OFFSET + vread buddy-two-pos! temp ALLY_VEL_OFFSET + vread buddy-two-vel! temp ALLY_TIME_OFFSET + read buddy-two-time! buddy-two-pos buddy-two-vel time buddy-two-time - vs* v+ buddy-two-cur-pos! fire-robot-sensor sync robot-found if NextRobot: robot-position robot-velocity 50 vs* v+ position 7 population sqrt + in-range if time 300 + robot-side SIDE_BASE + write then robot-side SIDE_BASE + read FIGHTER_TYPE type-population 8 > if 5000 + then time < TryNextTarget& ifg ;;not hostile ;; blaster-cooldown NoShoot& ifg ;;unnecessary conditioning? robot-position robot-velocity velocity v- robot-distance blaster-speed / vs* v+ position v- unitize blaster-speed vs* velocity v+ vtemp! ;vtemp is velocity of blaster after firing. buddy-one-cur-pos position v- vtemp dot 0 > buddy-one-quality and if buddy-one-cur-pos position v- buddy-one-vel vtemp v- unitize cross abs 1.5 < NoShoot& ifg then buddy-two-cur-pos position v- vtemp dot 0 > buddy-two-quality and if buddy-two-cur-pos position v- buddy-two-vel vtemp v- unitize cross abs 1.5 < NoShoot& ifg then robot-position robot-velocity lead-blaster NoShoot: dodge-and-move^ 1 have-crusade! time crusade-time! robot-position crusade-position! ;call for help if they're closing us, they're reloading, or we see a shot robot-velocity robot-position position v- unitize dot -0.1 < robot-reloading or shot-found or if robot-position robot-sensor-time 3 kill-channel send then robot-position robot-velocity 10 vs* v+ vtemp! FIGHT_DISTANCE birth-place vtemp dist 5 - 5 max min ;;FIGHT_DISTANCE, but closer if close to home position vtemp v- angle ;;time 5 / my-id + reorient 30 / + polar-to-rect vtemp v+ ;;stack: tentative desired position ;;Now add correction to stay away from buddies buddy-one-quality if position buddy-one-cur-pos v- rect-to-polar ;stack: tent-des-pos ally-dist ally-angle swap 1 max reciprocal square swap polar-to-rect v+ then buddy-two-quality if position buddy-two-cur-pos v- rect-to-polar ;stack: tent-des-pos ally-dist ally-angle swap 1 max reciprocal square swap polar-to-rect v+ then desired-position! ;;stack empty 1 have-target! Done-targeting& jump TryNextTarget: next-robot NextRobot& ifg then else Done-targeting& jump then ;time to fire No-target: 0 have-target! have-crusade if crusade-position position 5 in-range time crusade-time - 200 > or and-if 0 have-crusade! then dodge-and-move^ have-crusade nif energy pseudo-max-energy / 0.3 > and-if kill-channel messages 2 - 0 max 0 random-int kill-channel skip-messages do kill-channel receive while ;stack: x y t crusade-time! crusade-position! time crusade-time - 100 < if ;newish crusade-position position dist CRUSADE_DIST < and-if crusade-position position dist MIN_CRUSADE_DIST > and-if 1 have-crusade! done-chatting& jump then ;acceptable crusade loop Done-chatting: then ;have-crusade not dodge-and-move^ have-crusade if energy pseudo-max-energy / 0.15 > and-if energy pseudo-max-energy / 0.2 > if crusade-position else position then desired-position! else population 10 < if population temp! else population sqrt wait-far-from-home-factor * energy RUN_HOME_ENERGY < if 0.5 * then temp! then position birth-place temp in-range nif birth-place else energy pseudo-max-energy / 0.5 > if wander and-if wander-loc birth-place temp in-range not wander-loc position 5 in-range or if birth-place temp random-angle polar-to-rect v+ restrict-location^ wander-loc! then wander-loc else position then then desired-position! then ;have crusade Done-targeting: ;;jump here if we have a target eat-and-move^ dodge-and-move^ energy pseudo-max-energy / 0.2 > max-repair-rate 0 ifev repair-rate! write-our-posn^ 1 energy pseudo-max-energy / - hunger! announce-if-hungry^ Dodge-and-move^ forever #const MAX_SPEED 0.19 ;in: desired-position ;out: desired-velocity, speed-slop ;As might be guessed from the name this procedure used to be responsible for eating too. ;Now that this fighter no longer eats it might be cleaner to have the main code set desired-velocity directly and eliminate this procedure. eat-and-move: desired-position position v- rect-to-polar swap 0.05 * MAX_SPEED min swap polar-to-rect desired-velocity! radius 0.05 * speed-slop! return ;end of eat-and-move #var shots-angle 0 ;;;the angle which the shots we're dodging are coming from (for sensor-focus aiming) #var last-danger-time -500 ;;;shot-sensor is fired more frequently after danger #vector assumed-self-velocity ;;;an initial guess at our velocity in the near future #vector dv ;;;;shot-velocity assumed-self-velocity v- #vector perp_hat ;;;;a unit vector perpendicular to dv (rotated 90 degrees CCW) #var assumed_miss ;;;miss distance if we travel at asssumed-self-velocity #const MIN_MISS_DIST 2.1 ;;;minimum acceptable distance between shot center and our center ;#const MIN_GRENADE_MISS_DIST 3 ;;grenades have shot-type 2. Treating grenades differently is not worth the extra 5ish instructions probably. #vector dodged-shot-posn ;;for debugging, the position of the shot we dodged #var shot-sense-time ;;for debug ;Dodge has inputs: ;-desired-velocity, the desired medium-term engine velocity ;-speed-slop, the amount the velocity can differ before engine is used ;Dodge then sets engine-velocity and power to avoid shots while staying as close to desired-velocity as possible. Dodge-and-move: time shot-sensor-time - time last-danger-time 100 + > 8 5 ifev < ifr write-our-posn^ ;;not really dodge and move related but it wants to be done a lot energy pseudo-max-energy / 0.05 < Obey-user& ifg ;do as much computation as possible before firing shot sensor to get up-to-date info desired-velocity rect-to-polar swap 0.05 min swap polar-to-rect vtemp! ;;vtemp is desired-vel limited to speed 0.1 velocity vtemp dist 0.05 <= if vtemp else velocity vtemp v- unitize -0.05 vs* velocity v+ then assumed-self-velocity! ;stack empty 6 shot-sensor-focus-distance! shots-angle shot-sensor-focus-direction! ;;;From here until engine-velocity is set instructions have been squeezed out. ;;;; Here through the commented-out HaveShot label is structured weird to save a few precious instructions. ;;;; No unnecessary jumps but hard to read. fire-shot-sensor sync shot-sensor-time shot-sense-time! ;;for debug shot-found Obey-User& nifg shot-process-loop: shot-velocity norm 2 < CheckPos& ifg NextShot: next-shot shot-process-loop& ifg Obey-User: ;This ObeyUser stuff doesn't belong here, but putting it here saves an instruction (pushing address for ifeg). desired-velocity shot-found 2 > if 0.1 time 7 / polar-to-rect v+ then ;;;passive dodge engine-velocity! velocity desired-velocity v- norm speed-slop < 0 engine-max-power ifev engine-power! CheckNewHostiles& jump CheckPos: ;don't dodge super-fast shots such as force fields shot-velocity assumed-self-velocity v- dv! dv unitize shot-position position v- dot -2.0 < NextShot& nifg ;;don't dodge shots too late to dodge dv norm 0.05 < NextShot& ifg ;;prevents divide by zero errors from dv norm and dv project. ;HaveShot: #var dodging-needed dv unitize negate swap perp_hat! ;;perp_hat is a unit vector perpendicular to dv (rotated pi/2 CCW) ;;one of the key ideas of this active dodging routine is to express vectors in a basis of dv and perp_hat. perp_hat shot-position position v- dot assumed_miss! ;negative "assumed_miss" means shot miss to left, dodge right (facing shot's origin) if we go assumed_vel ;positive means miss right dodge left perp_hat ;;put on stack for use in a while ;The following line has a bug if assumed_miss==0, but who cares as that's unlikely and using signum saves a few instructions. shot-position position v- perp_hat MIN_MISS_DIST assumed_miss signum * vs* v- ;stack: perp_hat, our change in position before intercept if shot is to barely miss shot-distance dv norm / ;this line is time to intercept. vs/ shot-velocity v+ ;stack: perp_hat, velocity so that we barely miss 2 vs* velocity v- ;;adjust velocity by twice to account for slow acceleration perp_hat dot ;;target velocity to barely miss, perp_hat direction 0.25 min -0.25 max ;;limit attempted speed to attainable desired-velocity perp_hat dot ;;now the stack has the old perp_hat, perp_hat component velocity to barely miss, and perp_hat component velocity if we ;; don't dodge and simply go at desired. Now we see if desired velocity would allow us tomiss. assumed_miss 0 > if 2dup < dodging-needed! min else 2dup > dodging-needed! max then ;stack: perp_hat, desired perp_hat component of engine_velocity vs* ;use the perp_hat pushed on stack many lines ago dodging-needed nif desired-velocity else assumed-self-velocity then dv project v+ engine-velocity! ;stack empty now engine-max-power engine-power! shot-velocity angle pi + shots-angle! shot-position dodged-shot-posn! ;;for debugging CheckNewHostiles: shot-found if ;have-crusade nif shot-velocity norm have-target not and if shot-position shot-velocity -100 vs* v+ crusade-position! shot-sensor-time crusade-time! 1 have-crusade! then time last-danger-time! do time 500 + shot-side SIDE_BASE + write next-shot while-loop then return ;end of Dodge subroutine #end