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!