EWEScript examples


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

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.


Random Head

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! :)


Programmed

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!).


Walls

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.


Random walker

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.


Hungry

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.


Needy

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.


Chasing

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.


Maslow

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!