These examples can be loaded into the EWE applet by using the Scenario and Script menus - see my guide to using the applet for more details.
For more detail on the EWEScript language itself, consult the Language Reference.
Rotator | Random Head | Programmed | Walls | Random Walker | Hungry | Needy | Chasing | Maslow
This example shows the basic ideas in EWEScript.
AGENT Rotator IS "ANIMAL" { scale IS 2 x IS 60 y IS -20 TRIGGER system.tick DO rotate rotate: h = h+5 }
Basic definitions are given for scale
, x
and
y
. Every system.tick
(once per animation frame),
the rotate
action is performed. This increments
h
, which means that the sheep appears to rotate on the
screen.
Instead of rotating the image, we could set it to a random heading each time.
AGENT RandomHead IS "VEGETABLE" { image IS "wmb" TRIGGER system.tick DO changeH changeH: h = RANDOM(360) }
The term "VEGETABLE"
is not intended to refer to the image
;). "ANIMAL"
and "VEGETABLE"
do pretty much the
same thing in EWE at the moment - the difference is that they have
different default image
settings (Larry the lamb for
"ANIMAL"
, just a bush for "VEGETABLE"
). In a
future version of EWE, I might restrict what a vegetable can see. Make sure
that you spell "VEGETABLE"
correctly! :)
You can write sequential-type "programs" for EWEScript - this sheep behaves a little bit like a particularly dum alien in a "shoot it!" type game.
AGENT Programmed IS "ANIMAL" { scaling IS 5 xMoves IS {1, 1, 0, -1, 1, 0} yMoves IS {0, -1, -1, 1, 1, 0} index IS 1 TRIGGER system.tick PRIORITY 2 DO changeIndex TRIGGER system.tick PRIORITY 1 DO move changeIndex: index = MOD(index, 6) + 1 move: { x = x + xMoves[index] * scaling y = y + yMoves[index] * scaling } }
I've implemented lists in EWEScript in the hope that they will be quite
general and powerful. They are, but only to a point. Note the use of the
MOD
function, which makes sure that the index
variable is always between 1
and 6
. (Yes, list
indexes in EWEScript start from 1
, to avoid confusing
non-computer scientists!).
The sheep Walls thinks that it has a brick wall both in front and behind it. It is fairly dim, so just walks forward until it hits a wall, then turns around and walks back the other way.
AGENT Walls IS "ANIMAL" { againstWallRight IS x>=15 againstWallLeft IS x<=-15 movingRight IS TRUE h IS (-180*movingRight)+90 TRIGGER againstWallRight OR againstWallLeft PRIORITY 2 DO changeDirection TRIGGER system.tick AND NOT againstWallRight AND movingRight PRIORITY 1 DO shiftRight TRIGGER system.tick AND NOT againstWallLeft AND NOT movingRight PRIORITY 1 DO shiftLeft changeDirection: movingRight = NOT movingRight shiftRight: x = x + 3 shiftLeft: x = x - 3 }
Note the definition for h
(the sheep heading) which uses
the fact that when multiplying in EWEScript, FALSE == 0
and
TRUE == 1
.
Now for something a little more realistic. This sheep actually walks in the direction that it is headed.
AGENT RandomWalker IS "ANIMAL" { firstTime IS TRUE TRIGGER firstTime DO setInitialPosition setInitialPosition: { x = RANDOM(400)-200 y = RANDOM(100)-50 h = RANDOM(360) sleepPercent = RANDOM(100) stepSize = RANDOM(5) firstTime IS FALSE } awake IS RANDOM(100)>sleepPercent TRIGGER (MOD(system.clock, 3) == 0) AND awake DO changeDirection TRIGGER awake DO moveForward changeDirection: h = h + RANDOM(60) - 20 moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize } }
The first part of the script randomly sets up the sheeps
"personality". This works during the first Step, so you will see it jump to
a random place when you first Start or Step the system. You can add more
than one walker to the system by changing the AGENT
name
(RandomWalker
in this case) to something else (say
RandomWalker2
), and then press the Enter button again.
I've simulated the sheep sleeping for a certain percentage of the time, and it changes direction only every three ticks.
I know that sheep spend most of their time eating grass, but this causes a problem in EWE. Grass is presumably everywhere, which makes homing a little too easy. And so I've introduced the bush as a discrete thing for the sheep to look for, move towards and eventually eat.
AGENT Hungry IS "ANIMAL" { # SEE() returns {{distance, bearing, size, red, green, blue}, {distance... view IS SEE() # Looking for something to eat, so a large distance is bad, # bearing is not important, size is important, green is very important... weight IS {-0.5, 0, 0.5, 0, 1, 0} weightedViews IS view*weight indexOfFavourite IS INDEXOFMAX(SUM(weightedViews)) headingChange IS view[indexOfFavourite, 2] # 2 is the index of the bearing distance IS view[indexOfFavourite, 1] # 1 is the index of the distance # in units of 'me' TRIGGER system.tick DO moveForward TRIGGER system.tick DO changeHeading TRIGGER distance<1 DO eatNoise eatNoise: dummy=PLAY("chewgulp") changeHeading: h = h + headingChange stepSize IS 3 moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize } } AGENT Tasty IS "VEGETABLE" { x IS 100 y IS 10 }
This script defines the bush agent Tasty
, which the sheep
agent Hungry
looks for, using the SEE()
function. The sightings information returned by SEE()
is
multiplied by a weighting, which makes some parameters in the sighting more
important than others. Each weighted sighting, one of which might look like
{-10.5, 0, 0.2, 0, 255, 0}
, then has its elements added up by
SUM()
, producing a result 244.7
in this
case. Then the sighting with the highest result is scanned for with
INDEXOFMAX
, and the sheep is sent off in the direction of this
object.
If the sheep is near enough to the bush to eat it, the
PLAY()
function is used to make a chewing and gulping
sound. The bush size does not actually decrease - this is possible by
adding one simple line of script.
Unless our sheep is a particular loner, it will want to be near other sheep. This script makes the sheep tend to home towards other objects in the field.
AGENT Needy IS "ANIMAL" { firstTime IS TRUE TRIGGER firstTime DO setInitialPosition setInitialPosition: { x = RANDOM(400)-200 y = RANDOM(100)-50 h = RANDOM(360) stepSize = RANDOM(5) firstTime IS FALSE } # SEE() returns {{distance, bearing, size, red, green, blue}, {distance... view IS SEE() distanceFilter IS {1, 0, 0, 0, 0, 0} filteredViews IS view*distanceFilter distancesList IS SUM(filteredViews) indexOfNearest IS INDEXOFMIN(distancesList) bearingOfNearest IS view[indexOfNearest, 2] TRIGGER system.tick DO moveForward TRIGGER system.tick DO changeHeading changeHeading: h = h + bearingOfNearest + RANDOM(20) - 10 moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize } }
I've introduced a random factor into the change of heading, which makes the movement slightly more interesting.
You can see that the final result of this is that all the sheep end up on top of each other. The script could be extended so that each sheep has its own idea of a personal "comfort distance" which it wants to maintain from all other sheep.
Maybe the utopia ("EWEtopia?" - sorry) presented in the above example is a bit inaccurate - I would guess that sheep would not like to near each other all the time. This example presents two sheep: one (probably male) wants to be as close to the other as possible. The other (yes, probably female) would like to be as far away as possible.
AGENT Horny IS "ANIMAL" { # SEE() returns {{distance, bearing, size, red, green, blue}, {distance... view IS SEE() weight IS {0, 0, 0, 1, 1, 1} weightedViews IS view*weight indexOfFavourite IS INDEXOFMAX(SUM(weightedViews)) headingChange IS view[indexOfFavourite, 2] TRIGGER system.tick DO moveForward TRIGGER system.tick DO changeHeading changeHeading: h = h + headingChange + RANDOM(20) - 10 moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize } } AGENT Unwilling IS "ANIMAL" { # SEE() returns {{distance, bearing, size, red, green, blue}, {distance... view IS SEE() weight IS {0, 0, 0, 1, 1, 1} weightedViews IS view*weight indexOfFavourite IS INDEXOFMAX(SUM(weightedViews)) bearingOfSomething IS view[indexOfFavourite, 2] headingChange IS bearingOfSomething + 180 TRIGGER system.tick DO moveForward TRIGGER system.tick DO changeHeading changeHeading: h = h + headingChange + RANDOM(20) - 10 moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize } } AGENT initialSetUp { firstTime IS TRUE TRIGGER firstTime DO setInitialPositions setInitialPositions: { Horny.x = RANDOM(400)-200 Unwilling.x = RANDOM(400)-200 Horny.y = RANDOM(100)-50 Unwilling.y = RANDOM(100)-50 Horny.stepSize = RANDOM(5) Unwilling.stepSize = RANDOM(5) firstTime IS FALSE } }
I've moved the initialisation (which moves each sheep to a random position and assigns them random speeds) into a separate agent: this makes things a little clearer.
And finally, here is a fairly complex sheep that has to choose between various things to do. It's based (very loosely) on Maslow's hierarchy of needs.
AGENT Maslow IS "ANIMAL" { # How fast this sheep can move. stepSize IS 3 # Priorities (derived from Maslow's hierarchy of needs) are: # (1) Eating # (2) Drinking # (3) Sex # (4) Sleep # (5) Sing songs (ba ba black sheep?) # Priority (1) Eating foodInStomach IS .8 hungryImportance IS 1 - foodInStomach # Priority (2) Drinking drinkInStomach IS .7 thirstyImportance IS 1 - drinkInStomach # Priority (3) Sex timeSinceRapture IS 15 # This is a complicated way of saying MIN(timeSinceRapture/100, 1) lustMaxList IS {timeSinceRapture / 100, 1} lustImportance IS lustMaxList[INDEXOFMIN(lustMaxList)] # Priority (4) Sleep timeSinceSleep IS 25 # This is a complicated way of saying MIN(timeSinceSleep/100, 1) sleepMaxList IS {timeSinceSleep / 100, 1} sleepImportance IS sleepMaxList[INDEXOFMIN(sleepMaxList)] # Priority (5) Sing singImportance IS 0.2 # What is most important at the moment? # It is intended that the "importance" values range from 0 to 1. priorities IS {hungryImportance, thirstyImportance, lustImportance, \ sleepImportance, singImportance} priorityNow IS INDEXOFMAX(priorities) wantToEat IS priorityNow == 1 wantToDrink IS priorityNow == 2 wantToDoRudeThings IS priorityNow == 3 wantToSleep IS priorityNow == 4 wantToSing IS priorityNow == 5 weightingNow IS w[priorityNow] # These definitions don't do anything useful for the model. EWE currently # prints out the actions being performed at each step in the Java # console, so this is a useful way of seeing what the sheep's state is. TRIGGER wantToEat DO printWANTTOEAT TRIGGER wantToDrink DO printWANTTODRINK TRIGGER wantToDoRudeThings DO printWANTTODORUDETHINGS TRIGGER wantToSleep DO printWANTTOSLEEP TRIGGER wantToSing DO printWANTTOSING printWANTTOEAT: { } printWANTTODRINK: { } printWANTTODORUDETHINGS: { } printWANTTOSLEEP: { } printWANTTOSING: { } # SEE() returns {{distance, bearing, size, red, green, blue}, {distance... view IS SEE() # Weightings for hungry, thirsty, lust, sleep, sing... basically what # to look for in these states. w IS { {-0.5, 0, 0.5, 0, 0.4, 0}, \ {-0.5, 0, 0.5, 0, 0, 0.4}, \ {-0.5, 0, 0.5, 0.1, 0.1, 0.1}, \ { 0, 0, 0, 0, 0, 0}, \ { 0, 0, 0, 0, 0, 0} } # Out of the things that we can see, which is the most important # to us at the moment? weightedViews IS view*weightingNow importanceOfViews IS SUM(weightedViews) indexOfFavourite IS INDEXOFMAX(importanceOfViews) bearing IS view[indexOfFavourite, 2] # 2 is the index of the bearing distance IS view[indexOfFavourite, 1] # 1 is the index of the distance # in units of 'me' TRIGGER wantToEat AND distance<1 DO eat TRIGGER wantToDrink AND distance<1 DO drink TRIGGER wantToDoRudeThings AND distance<1 DO birdsAndBees TRIGGER wantToSleep DO sleep TRIGGER wantToSing DO sing TRIGGER NOT wantToSleep AND NOT wantToSing AND system.tick DO moveForward TRIGGER NOT wantToSleep AND NOT wantToSing AND system.tick DO changeHeading TRIGGER system.tick DO increaseTimers eat: { dummy=PLAY("chewgulp") # Each eating step fills our stomach up by another 0.5%. foodInStomach = foodInStomach + 0.05 } drink: { dummy=PLAY("glug") # Each drinking step fills our stomach up by another 0.3%. drinkInStomach = drinkInStomach + 0.03 } birdsAndBees: { dummy=PLAY("rapturebaa") timeSinceRapture IS 0 } sleep: { dummy=PLAY("zzzz") timeSinceSleep IS 0 } sing: dummy=PLAY("singbaa") increaseTimers: { timeSinceRapture = timeSinceRapture + 1 timeSinceSleep = timeSinceSleep + 1 } changeHeading: h = h + bearing moveForward: { x = x - SIN(h) * stepSize y = y + COS(h) * stepSize # We have moved, which used energy, so decrease stuff in stomach. foodInStomach = foodInStomach - 0.003 drinkInStomach = drinkInStomach - 0.003 } } AGENT ToDrink IS "VEGETABLE" { image IS "puddle" x IS 40 y IS -10 h IS 32 } AGENT ToEat IS "VEGETABLE" { scale IS 1.5 x IS -50 y IS 0 } AGENT ToEnjoy IS "ANIMAL" { x IS 10 y IS 50 }
I've included a puddle to drink, a bush to eat, and a sheep to have fun
with. The example could be enhanced in many ways - the sheep could have
parameters for blind
, drunk
, male
(female IS NOT male
: asexual sheep are not allowed
:). Maslow
in this example will only sleep for one step - this
could be made more realistic. stepSize
could also be related
to the energy that Maslow
has at that point in time. And so
on. Good luck!