demogame examples

Examples providing simple demonstrations of how the SVS library can be used in a 'live coding' project based on the applications in the svs_demogame package.

introduction
overview of the examples

running the examples
how to run them

areas and agents
agents responding to terrain areas

agent trail
agents changing terrain areas

life
Conway's game of 'Life'

viral life
'Life' with viral code.

pong
'Pong' game.

whisker bots
simple AI movement

introduction

The examples here provide some simple demonstrations of projects that could be developed with SVS. As the library is currently being developed for use in a gaming project (see spring_alpha) the examples are all based on simple gaming and simulation concepts. These use a grid-tile 'terrain' over which bot-like 'agents' can move, interacting with one another and the 'terrain'. Both the individual grid areas and the agents can be scripted in order to determine their behaviour and how they interact with one another. These scripts can be loaded at the start of a game, but also re-coded live during gameplay. A group of players could even improvise a game, or small simulation world, from scratch by adding code into a blank environment and building upon each others' input.

The examples all run with a combination of server, gameworld client and player clients. The player clients have a graphical interface whilst the server and gameworld client run from the command line. The demogame page provides an overview of how to use the player clients, as well as writing simple startup scripts for servers and clients. There is also a special 'tracker' client that visualises the changes in code, and how these relate to interactions between players and the code itself. The use of this is also described in the demogame page.

A good way to explore the examples is to run them and then start messing around with the code to see how they can be altered and improvised with. A listing of basic script functions for areas and agents is given in the demogame page, for more detail see the svs_demogame.terrains and svs_demogame.agents modules in the API documents.

All the files for running the examples are in the svs_demogame_examples bundle, which can be downloaded separately from the main SVS library.

running the examples

The files in the main svs_demogame_examples directory can be run as command-line scripts by entering a command such as:

./life.py

If these do not run first time, make sure that the svs packages are properly installed (see the installation instructions), and make sure the permissions for the example files are properly set by running the following command in the svs_demogame_examples directory:

chmod 755 *.py

To run one of the examples, first launch the server:

./server.py

Then the particular gameworld client for the example:

./areas.py

./trail.py

./life.py

./viral_life.py

or:

./pong.py

If you wish to see a visualisation of the code change and player interaction, launch the tracker client:

./tracker.py

and open its display with the openview or fullscreen command in the console.


fig. 1.) opening the tracker visualisation.

Launch one or more player clients:

./coder_01.py

./coder_02.py

There are scripts for 2 clients in the examples but more can be made by copying and altering these. When creating new clients, make sure you also add them to the server:

# game clients
cm.addClientAuthentication("coder_01", "k!ngc0d3r")
cm.addClientAuthentication("coder_02", "k!ngc0d3r")
cm.addClientAuthentication("coder_03", "k0d3kw33n")

When all the clients are up and running, start game play by sending the start command to the gameworld client from one of the players:


fig. 2.) starting the game.

If you are running the clients across several different machines you need to change the host address to point towards where the server is running. Change the connect function call in the client files, such as 'coder_01.py':

# original setting for localhost:
player.connect("testgroup", "localhost", 9797)

# reset for server on different machine:
player.connect("testgroup", "192.168.2.12", 9797)

page contents

areas and agents

Areas have one basic property: density. An area's density determines how easy it is to move through an area. 'Open space' areas have a density of 0, whilst 'wall' areas have a density of 1, everyhting else falls in between that. When an agent travels across an area, its overall speed of travel is influenced by the area's density, acusing it to slow down over any area with a denisty greater than 0, and blocking the agent if the denisty is 1. Area densities are displayed in the game view as different degrees of shading. An area with density of 0 is unshaded, and an area with density 1 is black, a density of 0.5 is mid-gray.

This example shows how simple 'landscapes' can be created by setting tyhe area densities, and how these affect the movement of agents. There are 5 agents which cross over 5 different 'landscapes' showing how they are differtently affected by the density settings. The top 'landscape' is like the agent going up and over a steep hill, whilst the one below is more like a gentle slope. The third is like jumping off a ridge, the next runs into a wall and the last one shows an agent crossing completely open space. All agents are moving at the same basic speed but responding differently to their different terrains.


fig. 1.) effect of area density on agent movement.

The simplest way to change an area's density is with the setDensity function being called from an update event:

def update():
  me.setDensity(0.5)

The density can changed gradually over time using a looped update function:

LOOP = 60

def update():
  me.setDensity(me.density + 0.01)

The game client for this example is run from the 'areas.py' file in svs_demogame_examples:

./areas.py

And the gameworld file is 'areas.xml' in the game_data directory of svs_demogame_examples.

page contents

agent trail

This example shows how agents can change the terrain and also their own behaviour in repsonse to gameworld events, as well as used stored data on the agent.

At the start of play the agents are set with a randomised pathcolour value between 0.0 and 1.0. They then start moving across the terrain. As they enter a new area, they set the density of the area to the stored pathcolour value. When they leave, they turn in a random direction. This way they wander across the space leaving a trail in their wake.


fig. 2.) trails left by agents.

Each agent is controlled by the same script:

def startPlay():
  from whrandom import randint
  set('pathcolor', 0.1 * randint(2, 8))

def update():
  me.go(10)

def enterArea(area):
  area.setDensity(get('pathcolor'))

def exitArea(area):
  from whrandom import randint
  me.turn(randint(0, 360))

The game client for this example is run from the 'trail.py' file in svs_demogame_examples:

./trail.py

And the gameworld file is 'trail.xml' in the game_data directory of svs_demogame_examples.

page contents

life

This example shows how areas can interact with one another. Each area is able to access the areas adjacent to it by calling the getNeighbours function. This is used to create a version of John Conway's 'Life' game, a cellular automata system in which the cells (represented by the gameworld areas) can be either in an 'on' state or 'off' state. This is shown in the gameview by the areas being either black or white in colour. The areas change over time according to Life's rules, and are updated on every clock cycle of the gameworld.


fig. 3.) two stages in an evolution of 'Life'.

Every area has the same script attached to it, and the resulting patterns emerge as a result of the areas interacting with one another on a purely localised basis.

LOOP = -1

def startPlay():
  from whrandom import randint
  me.setDensity(randint(0,1))

def stopPlay():
  me.setDensity(0)

def update():
  nAreas = me.getNeighbours()
  nSum = 0
  for nArea in nAreas.values():
    nSum += nArea.density
  if nSum == 3 and me.density == 0:
    me.setDensity(1)
  elif (nSum == 2 or nSum == 3) and me.density == 1:
    me.setDensity(0)

It is possible to influence the patterns by changing the scripts on some of the areas, for exmaple simply setting them in one fixed state:

def update():
  me.setDensity(1)

The script can be reset to the original version by clicking on the 'history' button of the player client and restoring the older version.

The game client for this example is run from the 'life.py' file in svs_demogame_examples:

./life.py

And the gameworld file is 'life.xml' in the game_data directory of svs_demogame_examples.

page contents

viral life

Agents and areas can change each others code scripts, thereby enabling new scripts to spread 'virally' across the gameworld. This example combines the agent trail example with the life game, except that this time, instead of the agents changing the density of the area they enter, they change its code. This virus is quite a benign one, all it does is change the colour coding used to represent the states of the areas as they change between 'on' and 'off', with the 'infected' areas showing up in a darker combination of colours.


fig. 4.) code virus spreading through 'Life'.

The areas use a slightly modified version of the script in the first life example above. In the first example the area's density is used to directly record the state, in this version a separate lifestate variable is used. There is also an array variable, infected, used to keep track of which neighbours the area has infected when it becomes viral. This is just to prevent unnecessary duplication oif code changes which would slow the game engine down.

LOOP = -1

def startPlay():
  from whrandom import randint
  me.lifestate = randint(0,1)
  me.setDensity(0.4 * me.lifestate)
  me.infected = []

def stopPlay():
  me.setDensity(0)

def update():
  nAreas = me.getNeighbours()
  nSum = 0
  for nArea in nAreas.values():
    nSum += nArea.lifestate
  if nSum == 3 and me.lifestate == 0:
    me.lifestate = 1
    me.setDensity(0.4)
  elif (nSum == 2 or nSum == 3) and me.lifestate == 1:
    me.lifestate = 0
    me.setDensity(0)

The script for the agents stores a version of the 'viral' script. When it enters an area, it copies this to the area. If you double-click on one of the 'infected' areas you can see that its code has been changed.

def startPlay():
  viralscript = \
"""
LOOP = -1
def update():
  nAreas = me.getNeighbours()
  nSum = 0
  for nArea in nAreas.values():
    nSum += nArea.lifestate
    #if not nArea.lifestate and nArea not in me.infected:
      #nArea.setScript(me.getScript())
      #me.infected.append(nArea)
  if nSum == 3 and me.lifestate == 0:
    me.lifestate = 1
    me.setDensity(0.6)
  elif (nSum == 2 or nSum == 3) and me.lifestate == 1:
    me.lifestate = 0
    me.setDensity(0.2)
"""
  set('viralscript', viralscript)

def update():
  me.go(10)

def enterArea(area):
  area.setScript(get('viralscript'))

def exitArea(area):
  from whrandom import randint
  me.turn(randint(0, 360))
  area.lifestate = 0
  me.setDensity(0.2)

You will notice that there are 3 lines of code commented out in the 'viralscript' of the agent:

#if not nArea.lifestate and nArea not in me.infected:
  #nArea.setScript(me.getScript())
  #me.infected.append(nArea)

Double-click on one of the agents and delete the comments from these lines and click 'Enter the code back into the gameworld. The next time the agent 'infects' an area it will have a more virulent strain of the virus. This time, not only are the agents spreading the code but the area is infecting each of tits neighbours, who in turn will infect their neighbours. As the infection is dependent on the neighbour being in its 'off' state, however, the spread of the virus is partly determined by other activity in the gameworld. Due to this pockets of 'resistance' to the virus appear.

The game client for this example is run from the 'viral_life.py' file in svs_demogame_examples:

./viral_life.py

And the gameworld file is 'viral_life.xml' in the game_data directory of svs_demogame_examples.

page contents

pong

This is a version of the classic 'Pong' game in which an agent takes the place of the pong ball, and the game behaviour emerges from the code built into the game environment.

The agent bounces around inside the rectangle defined by the two horizontal rows of shaded areas. There are two teams represented by the column of areas on the left and right edges of the gameworld terrain. When it hits either the left or right side, a point is scored by that team. A 'scorekeeper' behaviour is programmed into the area in the top left of the terrain. This keeps track of scores. When one team wins the screen is cleared and the game can begin again.

Playing the game follows the same basic principle of classic 'Pong': stopping the ball from hitting your side and getting it to hit the opponent side. This time, however, instead moving a small paddle up and down the screen, the game is played by two teams who have to recode unused areas in the terrain to make the agent bounce in the right direction.

This game should be played with the tracker client running so that the two teams can see who's coding what, stripping out code that is advantages to the other team, and restoring your own code after its been hacked - 'all your script are belong to us'.


fig. 5.) pong at play.

The 'scorekeeper' script shows a way in which new methods, scoreTeam_1 and scoreTeam_2, can be added onto an area:

def startPlay():
  me.totalScore = 10.0
  me.team_1 = 0
  me.team_2 = 0
  me.scoreTeam_1 = scoreTeam_1
  me.scoreTeam_2 = scoreTeam_2
  me.areasTeam_1 = []
  me.areasTeam_2 = []
  GAME_CLIENT.scorekeeper = me

def scoreTeam_1():
  me.team_1 += 1
  d = me.team_1 / me.totalScore
  for area in me.areasTeam_1:area.setDensity(d)
  if me.team_2 >= me.totalScore:
    allareas(0)
    for area in me.areasTeam_1:area.setDensity(1)
    GAME_CLIENT.stopGame()

def scoreTeam_2():
  me.team_2 += 1
  d = me.team_2 / me.totalScore
  for area in me.areasTeam_2:area.setDensity(d)
  if me.team_2 >= me.totalScore:
    allareas(0)
    for area in me.areasTeam_2:area.setDensity(1)
    GAME_CLIENT.stopGame()

These can then be called by other areas:

def startPlay():
  GAME_CLIENT.scorekeeper.areasTeam_1.append(me)

def agentEntered(agent):
  from whrandom import randint
  agent.placeAt(100, 100)
  agent.go(10 * randint(1, 4), randint(0, 360))
  GAME_CLIENT.scorekeeper.scoreTeam_1()

The game client for this example is run from the 'pong.py' file in svs_demogame_examples:

./pong.py

And the gameworld file is 'pong.xml' in the game_data directory of svs_demogame_examples.

page contents

whisker bots

This example demonstrates a simple AI technique for handling autonomous movement in game agents. It is based on an idea originally developed for simple robotics systems. Each agent has a sensor on each of its four sides (north, south, east, west). In the original robotics system these were four 'whiskers', simple metal rods that reposnded to being pushed or hitting something, hence the 'whisker bot' name. In this demo, the 'sensors' simply check each of the adjacent terrain areas on the four sides. On each update of the game, the agent tests to see if there are any obstacles in each of the sensor areas, an obstacle can be another agent, the edges of the gameworld, or an area that has a density of 1.0. If obstacles are found in a specific area, a repulsion force is added to move the agent in the opposite direction. The actual route taken by the agent will be an accumulation of the repulsion on all four sides. A decelaration is used to slow the agent down as it moves away, otherwise it would just shoot off into infinity. The whisker behaviour is based on an article by Mike Mika and Chris Charla, "Simple, Cheap Pathfinding", in the book AI Game programming Wisdom (Charles River Media).

The code for enabling the whisker behaviour is loaded from the 'whisker.xml' game data file:

LOOP = -1

def startPlay():
  me.weightEast = 0.0
  me.weightWest = 0.0
  me.weightNorth = 0.0
  me.weightSouth = 0.0
  me.repel = 10.0 # sensor repel strength
  me.biasEast = 0.0 # bias, used to push bot in desired direction
  me.biasWest = 0.0
  me.biasNorth = 0.0
  me.biasSouth = 0.0
  me.deceleration = 0.9 # erodes repulsion over time

def update():
  areas = me.getSurroundingAreas()
  if not areas:return
  # west sensor
  if not areas['W'].isAccessible():me.weightWest += me.repel
  # east sensor
  if not areas['E'].isAccessible():me.weightEast += me.repel
  # north sensor
  if not areas['N'].isAccessible():me.weightNorth += me.repel
  # south sensor
  if not areas['S'].isAccessible():me.weightSouth += me.repel
  # update movement
  me.moveTowards(me.weightWest - me.weightEast, me.weightNorth - me.weightSouth)
  # erode movement
  me.weightWest *= me.deceleration
  if me.weightWest < 0.0:me.weightWest = 0.0
  me.weightEast *= me.deceleration
  if me.weightEast < 0.0:me.weightEast = 0.0
  me.weightNorth *= me.deceleration
  if me.weightNorth < 0.0:me.weightNorth = 0.0
  me.weightSouth *= me.deceleration
  if me.weightSouth < 0.0:me.weightSouth = 0.0
  # add bias for attraction
  me.weightWest += me.biasWest
  me.weightEast += me.biasEast
  me.weightNorth += me.biasNorth
  me.weightSouth += me.biasSouth

You can adjust the behaviour of the agents by changing the initial values for the me.repel, and me.deceleration properties. The 'bias' properties (me.biasNorth, etc.) can be used to make the agent actively move toward a particular direction, this could be used as a way of more consciously steering the agent.

Variations on this could be created by adding the viral scripting abilities from the viral life example, or using code in specific areas to alter the bias properties on an agent. The whisker behaviour could also be added into the pong example.

When the game starts, the agents are just stationary. To get them moving, set one of the adjacent areas to a density of 1.0, and the agent will start to move in the opposite direction:

def update():
  me.setDensity(1.0)


fig. 6.) whisker bots in action.

The game client for this example is run from the 'whisker.py' file in svs_demogame_examples:

./whisker.py

And the gameworld file is 'whisker.xml' in the game_data directory of svs_demogame_examples.

page contents