Skip to content
Browse files

First commit

  • Loading branch information...
1 parent d8d87a4 commit 7d60d3a53806ef4b925695a93cd746531ef2e702 @schaul committed Jun 18, 2012
View
6 README.md
@@ -1,4 +1,8 @@
py-vgdl
=======
-A video game description language (VGDL) built on top pf pygame.
+A high-level video game description language (VGDL) built on top of pygame.
+
+The aim is to decompose game descriptions into two parts: 1) a very high-level description, close to human language, to specify the dynamics, which builds on 2) an ontology of preprogrammed concepts for dynamics, interactions, control.
+Programmers extend the possibilities of (1) by writing modules in (2), and game designers can very quickly compose new games from those components without programming.
+
View
62 examples/gridphysics/aliens.py
@@ -0,0 +1,62 @@
+'''
+VGDL example: a simplified variant of the classic space-invaders.
+
+@author: Tom Schaul
+'''
+
+# The game dynamics are specified as a paragraph of text
+
+aliens_game="""
+BasicGame
+ SpriteSet
+ base > Immovable color=WHITE
+ avatar > FlakAvatar shoot=sam
+ missile > Missile
+ sam > direction=UP color=BLUE singleton=True
+ bomb > direction=DOWN color=RED speedup=0.5
+ alien > Bomber stype=bomb prob=0.02 cooldown=4
+ portal > SpawnPoint stype=alien delay=16 total=20
+
+ LevelMapping
+ 0 > base
+ 1 > avatar
+ 2 > portal
+
+ TerminationSet
+ SpriteCounter stype=avatar limit=0 win=False
+ MultiSpriteCounter stype1=portal stype2=alien limit=0 win=True
+
+ InteractionSet
+ avatar EOS > stepBack
+ alien EOS > turnAround
+ missile EOS > killSprite
+ missile base > killSprite
+ base missile > killSprite
+ base alien > killSprite
+ avatar alien > killSprite
+ avatar bomb > killSprite
+ alien sam > killSprite
+"""
+
+# and the (initial) level as a block of characters too
+aliens_level = """
+wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+w w
+w2 w
+w000 w
+w000 w
+w w
+w w
+w w
+w w
+w 000 000000 000 w
+w 00000 00000000 00000 w
+w 0 0 00 00 00000 w
+w 1 w
+wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ # parse, run and play.
+ VGDLParser.playGame(aliens_game, aliens_level)
View
53 examples/gridphysics/butterflies.py
@@ -0,0 +1,53 @@
+'''
+VGDL example: a simple chasing butterflies game.
+
+If a butterfly hits a cocooned one, if frees it.
+If they all are freed, you lose.
+
+@author: Tom Schaul
+'''
+
+
+chase_game = """
+BasicGame
+ SpriteSet
+ wall > Immovable
+ cocoon > Immovable color=BLUE
+ animal > physicstype=GridPhysics
+ avatar > MovingAvatar speedup=1.5
+ butterfly > RandomNPC
+
+ TerminationSet
+ SpriteCounter stype=butterfly win=True
+ SpriteCounter stype=cocoon win=False
+
+ InteractionSet
+ butterfly avatar > killSprite
+ butterfly cocoon > cloneSprite
+ cocoon butterfly > killSprite
+ animal wall > stepBack
+
+ LevelMapping
+ w > wall
+ 1 > avatar
+ 2 > butterfly
+ 3 > cocoon
+"""
+
+chase_level = """
+wwwwwwwwwwwwwwwwwwwwwwwwwwww
+w 2 2 w 3 3 3 3w333w
+w 2 w333w
+w 2 3 1 w333w
+wwwwwwwwwwww 33w
+w3 w ww
+w3 2 w
+w3 wwwww 2 3w
+wwwww w w
+w 3 3 3 3 3 w3 3w
+wwwwwwwwwwwwwwwwwwwwwwwwwwww
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ VGDLParser.playGame(chase_game, chase_level)
View
61 examples/gridphysics/dodge.py
@@ -0,0 +1,61 @@
+'''
+VGDL example: a simple dodge-the-bullets game
+
+@author: Tom Schaul
+'''
+
+bullet_level = """
+wwwwwwwwwwwwwwwwwww
+w1 w < - 2w
+w w- w
+w ww w
+w < w ^ w w
+w ^ w V V ww
+w - v w
+ww < www w
+w w
+www v www
+wwwwwwwwwwwwwwwwwww
+"""
+
+bullet_game = """
+BasicGame
+ SpriteSet
+ bullet > Missile
+ slowbullet > speedup=0.1 color=ORANGE
+ upslow > direction=UP
+ downslow > direction=DOWN
+ leftslow > direction=LEFT
+ rightslow > direction=RIGHT
+ fastbullet > speedup=0.2 color=RED
+ rightfast > direction=RIGHT
+ downfast > direction=DOWN
+ wall > Immovable
+ goal > Immovable color=GREEN
+ avatar > MovingAvatar
+
+ InteractionSet
+ goal avatar > killSprite
+ avatar bullet > killSprite
+ avatar wall > stepBack
+ bullet EOS > wrapAround
+
+ TerminationSet
+ SpriteCounter stype=goal limit=0 win=True
+ SpriteCounter stype=avatar limit=0 win=False
+
+ LevelMapping
+ w > wall
+ ^ > upslow
+ < > leftslow
+ v > downslow
+ - > rightslow
+ = > rightfast
+ V > downfast
+ 1 > avatar
+ 2 > goal
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ VGDLParser.playGame(bullet_game, bullet_level)
View
68 examples/gridphysics/frogs.py
@@ -0,0 +1,68 @@
+'''
+VGDL example: a simplified variant of the classic frogger game.
+
+Logs spawn randomly, but trucks wrap around the screen and come back.
+
+@author: Tom Schaul
+'''
+
+frog_level = """
+wwwwwwwwwwwwwwwwwwwwwwwwwwww
+w w2w w
+w00==000000===0000=====000=4
+w0000====0000000000====00034
+w00===000===000====0000===04
+www ww www www wwwww
+w ---- --- - ---- w
+w- xxx xxx xx w
+w - --- - ---- -- w
+w 1 w
+wwwwwwwwwwwwwwwwwwwwwwwwwwww
+"""
+
+
+frog_game = """
+BasicGame
+ SpriteSet
+ forest > SpawnPoint stype=log prob=0.4 delay=10
+ structure > Immovable
+ water > color=BLUE
+ goal > color=GREEN
+ log > Missile direction=LEFT speedup=0.1 color=BROWN
+ truck > Missile direction=RIGHT
+ fasttruck > speedup=0.2 color=ORANGE
+ slowtruck > speedup=0.1 color=RED
+ avatar > Frog
+ # defining 'wall' last, makes the walls show on top of all other sprites
+ wall > Immovable
+
+ InteractionSet
+ goal avatar > killSprite
+ avatar log > drownSafe
+ avatar log > pullWithIt # note how one collision can have multiple effects
+ avatar wall > stepBack
+ avatar water > drownSprite
+ avatar truck > killSprite
+ log EOS > killSprite
+ truck EOS > wrapAround
+
+ TerminationSet
+ SpriteCounter stype=goal limit=0 win=True
+ SpriteCounter stype=avatar limit=0 win=False
+
+ LevelMapping
+ w > wall
+ 0 > water
+ 1 > avatar
+ 2 > goal
+ 3 > forest water # note how a single character can spawn multiple sprites
+ 4 > forest wall log
+ - > slowtruck
+ x > fasttruck
+ = > log water
+
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ VGDLParser.playGame(frog_game, frog_level)
View
69 examples/gridphysics/portals.py
@@ -0,0 +1,69 @@
+'''
+VGDL example: a simple teleport-and-avoid-fire game.
+
+@author: Tom Schaul
+'''
+
+portal_level = """
+wwwwwwwwwwwwwwwwwww
+w1 w v < wO 2w
+wo iw wx w
+wwwww o wwwwww
+w w r w ow
+w < wwwwwww ww
+w x < w
+wwwww www w
+wr i w
+wwwIw v x www
+wwwwwwwwwwwwwwwwwww
+"""
+
+portal_game = """
+BasicGame
+ SpriteSet
+ bullet > color=ORANGE
+ sitting > Immovable
+ random > RandomNPC speedup=0.25
+ straight > Missile speedup=0.5
+ vertical > direction=UP
+ horizontal > direction=LEFT
+ structure > Immovable
+ wall >
+ goal > color=GREEN
+ portalentry > Portal color=BLUE
+ entry1 > stype=exit1
+ entry2 > stype=exit2
+ portalexit > color=BROWN
+ exit1 >
+ exit2 >
+ avatar > MovingAvatar
+
+ InteractionSet
+ goal avatar > killSprite
+ avatar bullet > killSprite
+ avatar wall > stepBack
+ straight wall > changeDirection
+ avatar portalentry > teleportToExit
+
+ TerminationSet
+ SpriteCounter stype=goal limit=0 win=True
+ SpriteCounter stype=avatar limit=0 win=False
+
+ LevelMapping
+ w > wall
+ < > horizontal
+ v > vertical
+ x > sitting
+ r > random
+ 1 > avatar
+ 2 > goal
+ i > entry1
+ I > entry2
+ o > exit1
+ O > exit2
+
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ VGDLParser.playGame(portal_game, portal_level)
View
51 examples/gridphysics/sokoban.py
@@ -0,0 +1,51 @@
+'''
+VGDL example: a simplified Sokoban variant: push the boxes into the holes
+
+@author: Tom Schaul
+'''
+
+box_level = """
+wwwwwwwwwwwww
+w w w
+w 2 w
+w 1 2 w 0ww
+www w2 wwwww
+w w 0 w
+w 2 ww
+w ww
+wwwwwwwwwwwww
+"""
+
+
+push_game = """
+BasicGame frame_rate=30
+ SpriteSet
+ wall > Immovable
+ hole > Immovable color=GREEN
+ movable >
+ avatar > MovingAvatar cooldown=10
+ box > Passive
+
+ LevelMapping
+ w > wall
+ 0 > hole
+ 1 > avatar
+ 2 > box
+
+ InteractionSet
+ avatar wall > stepBack
+ box avatar > bounceForward
+ box wall > undoAll
+ box box > undoAll
+ movable hole> killSprite
+
+ TerminationSet
+ Timeout limit=10000
+ SpriteCounter stype=box limit=0 win=True
+ SpriteCounter stype=avatar limit=0 win=False
+
+"""
+
+if __name__ == "__main__":
+ from core import VGDLParser
+ VGDLParser.playGame(push_game, box_level)
View
15 todo.txt
@@ -0,0 +1,15 @@
+Game dynamics:
+- non-grid physics
+- resource-type objects (health, heat, ...)
+- chasing NPCs
+
+Example games:
+- pacman
+- lunar lander
+- super mario
+- asteroids
+
+Other:
+- nicer graphics
+- interface to artificial agents
+- full score system
View
325 vgdl/core.py
@@ -0,0 +1,325 @@
+'''
+Video game description language -- parser, framework and core game classes.
+
+@author: Tom Schaul
+'''
+
+import pygame
+from random import choice
+from tools import Node, indentTreeParser
+
+
+class VGDLParser(object):
+ """ Parses a string into a Game object. """
+
+ verbose = False
+
+ @staticmethod
+ def playGame(game_str, map_str):
+ """ Parses the game and level map strings, and starts the game. """
+ g = VGDLParser().parseGame(game_str)
+ g.buildLevel(map_str)
+ g.startGame()
+
+ def parseGame(self, tree):
+ """ Accepts either a string, or a tree. """
+ if not isinstance(tree, Node):
+ tree = indentTreeParser(tree).children[0]
+ sclass, args = self._parseArgs(tree.content)
+ self.game = sclass(**args)
+ for c in tree.children:
+ if c.content == "SpriteSet":
+ self.parseSprites(c.children)
+ if c.content == "InteractionSet":
+ self.parseInteractions(c.children)
+ if c.content == "LevelMapping":
+ self.parseMappings(c.children)
+ if c.content == "TerminationSet":
+ self.parseTerminations(c.children)
+ return self.game
+
+ def _eval(self, estr):
+ """ Whatever is visible in the global namespace (after importing the ontologies)
+ can be used in the VGDL, and is evaluated.
+ """
+ from ontology import * #@UnusedWildImport
+ return eval(estr)
+
+ def parseInteractions(self, inodes):
+ for inode in inodes:
+ if ">" in inode.content:
+ pair, edef = [x.strip() for x in inode.content.split(">")]
+ self.game.collision_eff.append(tuple([x.strip() for x in pair.split(" ") if len(x)>0]
+ +[self._eval(edef)]))
+ if self.verbose:
+ print "Collision", pair, "has effect:", edef
+
+ def parseTerminations(self, tnodes):
+ for tn in tnodes:
+ sclass, args = self._parseArgs(tn.content)
+ if self.verbose:
+ print "Adding:", sclass, args
+ self.game.terminations.append(sclass(**args))
+
+ def parseSprites(self, snodes, parentclass=None, parentargs={}, parenttypes=[]):
+ for sn in snodes:
+ assert ">" in sn.content
+ key, sdef = [x.strip() for x in sn.content.split(">")]
+ sclass, args = self._parseArgs(sdef, parentclass, parentargs.copy())
+ stypes = parenttypes+[key]
+
+ if len(sn.children) == 0:
+ if self.verbose:
+ print "Defining:", key, sclass, args, stypes
+ self.game.sprite_constr[key] = (sclass, args, stypes)
+ self.game.sprite_groups[key] = []
+ self.game.sprite_order.append(key)
+ else:
+ #self.game.sprite_abstractgroups[key] = []
+ self.parseSprites(sn.children, sclass, args, stypes)
+
+ def parseMappings(self, mnodes):
+ for mn in mnodes:
+ c, val = [x.strip() for x in mn.content.split(">")]
+ assert len(c) == 1, "Only single character mappings allowed."
+ # a char can map to multiple sprites
+ keys = [x.strip() for x in val.split(" ") if len(x)>0]
+ if self.verbose:
+ print "Mapping", c, keys
+ self.game.char_mapping[c] = keys
+
+ def _parseArgs(self, s, sclass=None, args=None):
+ if not args:
+ args = {}
+ sparts = [x.strip() for x in s.split(" ") if len(x) > 0]
+ if len(sparts) == 0:
+ return sclass, args
+ if not '=' in sparts[0]:
+ sclass = self._eval(sparts[0])
+ sparts = sparts[1:]
+ for sp in sparts:
+ k, val = sp.split("=")
+ try:
+ args[k] = self._eval(val)
+ except:
+ args[k] = val
+ return sclass, args
+
+
+class BasicGame(object):
+ """ This regroups all the components of a game's dynamics, after parsing. """
+ MAX_SPRITES = 1000
+
+ def __init__(self, block_size=10, frame_rate=20):
+ self.block_size = block_size
+ self.frame_rate = frame_rate
+ # contains mappings to constructor
+ self.sprite_constr = {}
+ # z-level of sprite types (in case of overlap)
+ self.sprite_order = []
+ # contains instance lists
+ self.sprite_groups = {}
+ # collision effects
+ self.collision_eff = []
+ # for reading levels
+ self.char_mapping = {}
+ # termination criteria
+ self.terminations = [Termination()]
+ self.num_sprites = 0
+
+ def buildLevel(self, lstr):
+ lines = [l for l in lstr.split("\n") if len(l)>0]
+ lengths = map(len, lines)
+ assert min(lengths)==max(lengths), "Inconsistent line lengths."
+ width = lengths[0]
+ height = len(lines)
+ assert width > 1 and height > 1, "Level too small."
+
+ # rescale pixels per block to adapt to the level
+ self.block_size = max(1,int(500/max(width, height)))*2
+ self._initScreen((width*self.block_size, height*self.block_size))
+
+ # create sprites
+ for row, l in enumerate(lines):
+ for col, c in enumerate(l):
+ if c in self.char_mapping:
+ pos = (col*self.block_size, row*self.block_size)
+ self._createSprite(self.char_mapping[c], pos)
+
+ def _createSprite(self, keys, pos):
+ for key in keys:
+ if self.num_sprites > self.MAX_SPRITES:
+ print "Sprite limit reached."
+ return
+ sclass, args, stypes = self.sprite_constr[key]
+ if 'singleton' in args and args['singleton']==True:
+ if len(self.sprite_groups[key]) > 0:
+ continue
+ else:
+ args = args.copy()
+ del args['singleton']
+
+ s = sclass(pos=pos, size=(self.block_size, self.block_size), **args)
+ s.stypes = stypes
+ s.name = key
+ self.sprite_groups[key].append(s)
+ self.num_sprites += 1
+
+ def _initScreen(self, size):
+ pygame.init()
+ self.screensize = size
+ self.screen = pygame.display.set_mode(size)
+ self.background = pygame.Surface(size)
+ self.screen.blit(self.background, (0,0))
+
+ def __iter__(self):
+ """ Iterator over all sprites """
+ for key in self.sprite_order:
+ for s in self.sprite_groups[key]:
+ yield s
+
+ def _clearAll(self):
+ for s in set(self.kill_list):
+ s._clear(self.screen, self.background, double=True)
+ self.sprite_groups[s.name].remove(s)
+ for s in self:
+ s._clear(self.screen, self.background)
+ self.kill_list = []
+
+ def _drawAll(self):
+ for s in self:
+ s._draw(self.screen)
+
+ def _updateCollisionDict(self):
+ # create a dictionary that maps type pairs to a list of sprite pairs
+ self.lastcollisions = {}
+ nonstatics = [s for s in self if not s.is_static]
+ statics = [s for s in self if s.is_static]
+ for i, s1 in enumerate(nonstatics):
+ for s2 in (nonstatics+statics)[i+1:]:
+ assert s1 != s2
+ if s1.rect.colliderect(s2.rect):
+ for key1 in s1.stypes:
+ for key2 in s2.stypes:
+ if (key1, key2) not in self.lastcollisions:
+ self.lastcollisions[(key1, key2)] = []
+ self.lastcollisions[(key2, key1)] = []
+ self.lastcollisions[(key1, key2)].append((s1, s2))
+ self.lastcollisions[(key2, key1)].append((s2, s1))
+ # detect end-of-screen
+ if not pygame.Rect((0,0), self.screensize).contains(s1.rect):
+ for key1 in s1.stypes:
+ if (key1, 'EOS') not in self.lastcollisions:
+ self.lastcollisions[(key1, 'EOS')] = []
+ self.lastcollisions[(key1, 'EOS')].append((s1, None))
+
+ def startGame(self):
+ clock = pygame.time.Clock()
+ self.time = 0
+ self.kill_list=[]
+ pygame.display.flip()
+ ended = False
+ win = False
+ while not ended:
+ clock.tick(self.frame_rate)
+ self.time += 1
+ self._clearAll()
+ # gather events
+ pygame.event.pump()
+ self.keystate = pygame.key.get_pressed()
+ # termination criteria
+ for t in self.terminations:
+ ended, win = t.isDone(self)
+ if ended:
+ break
+ # update sprites
+ for s in self:
+ s.update(self)
+ # handle collision effects
+ self._updateCollisionDict()
+ for g1, g2, effect in self.collision_eff:
+ if (g1, g2) in self.lastcollisions:
+ for s1, s2 in set(self.lastcollisions[(g1, g2)]):
+ effect(s1, s2, self)
+ self._drawAll()
+ pygame.display.update(VGDLSprite.dirtyrects)
+ VGDLSprite.dirtyrects = []
+
+ if win:
+ print "Dude, you're a born winner!"
+ else:
+ print "Dang. Try again..."
+ pygame.time.wait(50)
+
+
+class VGDLSprite(object):
+ """ Base class for all sprite types. """
+
+ COLOR_DISC = [20,80,140,200]
+ dirtyrects = []
+
+ is_static= False
+ color = None
+ speedup = 1
+ cooldown = 0 # pause ticks in-between two moves
+
+ def __init__(self, pos, size=(10,10), color=None, speedup=None, cooldown=None, physicstype=None):
+ self.rect = pygame.Rect(pos, size)
+ self.lastrect = self.rect
+ if not physicstype:
+ from ontology import GridPhysics
+ physicstype = GridPhysics
+ self.physics = physicstype(size)
+ if speedup is not None:
+ self.speedup = speedup
+ if cooldown is not None:
+ self.cooldown = cooldown
+ if color:
+ self.color = color
+ elif self.color is None:
+ self.color = (choice(self.COLOR_DISC), choice(self.COLOR_DISC), choice(self.COLOR_DISC))
+ self.lastmove = 0
+
+ def update(self, game):
+ """ The main place where subclasses differ. """
+ self.lastmove += 1
+ self.lastrect = self.rect
+
+ def _updatePos(self, direction, speedup=None):
+ if not speedup:
+ speedup = self.speedup
+ self.lastrect = self.rect
+ if self.cooldown > self.lastmove:
+ self.lastmove += 1
+ elif abs(direction[0])+abs(direction[1])==0:
+ # no need to redraw if nothing was updated
+ self.lastmove += 1
+ else:
+ self.rect = self.rect.move((direction[0]*speedup, direction[1]*speedup))
+ self.lastmove = 0
+
+ @property
+ def lastdirection(self):
+ return (self.rect[0]-self.lastrect[0], self.rect[1]-self.lastrect[1])
+
+ def _draw(self, screen):
+ r = screen.fill(self.color, self.rect)
+ VGDLSprite.dirtyrects.append(r)
+
+ def _clear(self, screen, background, double=False):
+ r = screen.blit(background, self.rect, self.rect)
+ VGDLSprite.dirtyrects.append(r)
+ if double:
+ r = screen.blit(background, self.lastrect, self.lastrect)
+ VGDLSprite.dirtyrects.append(r)
+
+
+class Termination(object):
+ """ Base class for all termination criteria. """
+ def isDone(self, game):
+ """ returns whether the game is over, with a win/lose flag """
+ from pygame.locals import K_ESCAPE, QUIT
+ if game.keystate[K_ESCAPE] or pygame.event.peek(QUIT):
+ return True, False
+ else:
+ return False, None
View
273 vgdl/ontology.py
@@ -0,0 +1,273 @@
+'''
+Video game description language -- ontology of concepts.
+
+@author: Tom Schaul
+'''
+
+from random import choice, random
+
+# ---------------------------------------------------------------------
+# Constants
+# ---------------------------------------------------------------------
+GREEN = (0,200,0)
+BLUE = (0,0,200)
+RED = (200,0,0)
+GRAY = (90,90,90)
+WHITE = (250,250,250)
+BROWN = (140, 120, 100)
+BLACK = (0, 0, 0)
+ORANGE = (250, 160, 0)
+
+UP = (0, -1)
+DOWN = (0, 1)
+LEFT = (-1, 0)
+RIGHT = ( 1, 0)
+
+
+# ---------------------------------------------------------------------
+# Types of physics
+# ---------------------------------------------------------------------
+class GridPhysics():
+ """ Define actions and key-mappings for grid-world dynamics. """
+
+ def __init__(self, gridsize=(10,10)):
+ self.gridsize = gridsize
+
+ def _scaleDir(self, d):
+ return (d[0]*self.gridsize[0], d[1]*self.gridsize[1])
+
+ def keyDirection(self, keystate):
+ from pygame.locals import K_LEFT, K_RIGHT, K_UP, K_DOWN
+ if keystate[K_RIGHT]: return self._scaleDir(RIGHT)
+ elif keystate[K_LEFT]: return self._scaleDir(LEFT)
+ elif keystate[K_UP]: return self._scaleDir(UP)
+ elif keystate[K_DOWN]: return self._scaleDir(DOWN)
+ else: return (0,0)
+
+ def allDirections(self):
+ """ Returns the 4 direction vectors. """
+ return [self._scaleDir(UP), self._scaleDir(RIGHT), self._scaleDir(DOWN), self._scaleDir(LEFT)]
+
+
+# ---------------------------------------------------------------------
+# Sprite types
+# ---------------------------------------------------------------------
+from core import VGDLSprite
+
+class Immovable(VGDLSprite):
+ """ A gray square that does not budge. """
+ color = GRAY
+ is_static = True
+
+class Passive(VGDLSprite):
+ """ A square that may budge. """
+ color = RED
+
+class Portal(Immovable):
+ def __init__(self, stype=None, **kwargs):
+ self.stype = stype
+ VGDLSprite.__init__(self, **kwargs)
+
+class RandomNPC(VGDLSprite):
+ """ Chooses randomly from all available actions each step. """
+ def update(self, game):
+ self._updatePos(choice(self.physics.allDirections()))
+
+class MovingAvatar(VGDLSprite):
+ color=WHITE
+
+ def update(self, game):
+ self._updatePos(self.physics.keyDirection(game.keystate))
+
+class Frog(MovingAvatar):
+ """ Has an additional flag that keeps if from drowning if true. """
+ drowning_safe = False
+
+ def update(self, game):
+ MovingAvatar.update(self, game)
+ self.drowning_safe = False
+
+class FlakAvatar(VGDLSprite):
+ """ Only horizontal moves. Hitting the space button creates a sprite of the
+ specified type at its location. """
+ color=GREEN
+ def __init__(self, shoot=None, **kwargs):
+ self.shoot = shoot
+ VGDLSprite.__init__(self, **kwargs)
+
+ def update(self, game):
+ from pygame.locals import K_SPACE
+ self._updatePos((self.physics.keyDirection(game.keystate)[0], 0))
+ if self.shoot and game.keystate[K_SPACE]:
+ game._createSprite([self.shoot], (self.rect.left, self.rect.top))
+
+class Missile(VGDLSprite):
+ """ A sprite that constantly moves in the same direction. """
+ def __init__(self, direction=None, **kwargs):
+ VGDLSprite.__init__(self, **kwargs)
+ if direction:
+ self.direction=direction
+ else:
+ self.direction=RIGHT
+
+ def update(self, game):
+ self._updatePos(self.physics._scaleDir(self.direction))
+
+class RandomMissile(Missile):
+ def __init__(self, **kwargs):
+ Missile.__init__(self, direction=choice([UP, DOWN, LEFT, RIGHT]),
+ speedup = choice([0.1, 0.2, 0.4]), **kwargs)
+
+class SpawnPoint(Immovable):
+ prob = None
+ delay = None
+ total = None
+ color = BLACK
+
+ def __init__(self, stype=None, delay=1, prob=1, total=None, **kwargs):
+ VGDLSprite.__init__(self, **kwargs)
+ self.stype = stype
+ if prob:
+ self.prob = prob
+ if delay:
+ self.delay = delay
+ if total:
+ self.total = total
+ self.counter = 0
+
+ def update(self, game):
+ if (game.time%self.delay == 0 and random() < self.prob):
+ game._createSprite([self.stype], (self.rect.left, self.rect.top))
+ self.counter += 1
+
+ if self.total and self.counter >= self.total:
+ killSprite(self, None, game)
+
+class Bomber(SpawnPoint, Missile):
+ direction = RIGHT
+ color = ORANGE
+ is_static = False
+ def update(self, game):
+ Missile.update(self, game)
+ SpawnPoint.update(self, game)
+
+
+# ---------------------------------------------------------------------
+# Termination criteria
+# ---------------------------------------------------------------------
+from core import Termination
+
+class Timeout(Termination):
+ def __init__(self, limit=0):
+ self.limit = limit
+
+ def isDone(self, game):
+ if game.time >= self.limit:
+ return True, False
+ else:
+ return False, None
+
+class SpriteCounter(Termination):
+ """ Game ends when the number of sprites of type 'stype' hits 'limit'. """
+ def __init__(self, limit=0, stype=None, win=True):
+ self.limit = limit
+ self.stype = stype
+ self.win = win
+
+ def isDone(self, game):
+ if len(game.sprite_groups[self.stype]) == self.limit:
+ return True, self.win
+ else:
+ return False, None
+
+class MultiSpriteCounter(Termination):
+ """ Game ends when the sum of all sprites of types 'stypes' hits 'limit'. """
+ def __init__(self, limit=0, win=True, **kwargs):
+ self.limit = limit
+ self.win = win
+ self.stypes = kwargs.values()
+
+ def isDone(self, game):
+ if sum([len(game.sprite_groups[st]) for st in self.stypes])== self.limit:
+ return True, self.win
+ else:
+ return False, None
+
+
+# ---------------------------------------------------------------------
+# Effect types (invoked after an event).
+# ---------------------------------------------------------------------
+def killSprite(sprite, partner, game):
+ """ Kill command """
+ game.kill_list.append(sprite)
+
+def cloneSprite(sprite, partner, game):
+ game._createSprite([sprite.name], (sprite.rect.left, sprite.rect.top))
+
+def stepBack(sprite, partner, game):
+ """ Revert last move. """
+ sprite.rect=sprite.lastrect
+
+def undoAll(sprite, partner, game):
+ """ Revert last moves of all sprites. """
+ for s in game:
+ s.rect=s.lastrect
+
+def bounceForward(sprite, partner, game):
+ """ The partner sprite pushed, so if possible move in the opposite direction. """
+ sprite._updatePos(partner.lastdirection)
+ # check for new induced collisions
+ game._updateCollisionDict()
+
+def turnAround(sprite, partner, game):
+ sprite.rect=sprite.lastrect
+ sprite.lastmove = 100
+ sprite._updatePos(sprite.physics._scaleDir(DOWN))
+ sprite.lastmove = 100
+ sprite._updatePos(sprite.physics._scaleDir(DOWN))
+ changeDirection(sprite, partner, game)
+
+def changeDirection(sprite, partner, game):
+ if sprite.direction==RIGHT:
+ sprite.direction=LEFT
+ elif sprite.direction==LEFT:
+ sprite.direction=RIGHT
+ elif sprite.direction==UP:
+ sprite.direction=DOWN
+ elif sprite.direction==DOWN:
+ sprite.direction=UP
+
+def drownSprite(sprite, partner, game):
+ if not sprite.drowning_safe:
+ killSprite(sprite, partner, game)
+
+def drownSafe(sprite, partner, game):
+ sprite.drowning_safe = True
+
+def wrapAround(sprite, partner, game):
+ """ Move to the edge of the screen in the direction the sprite is coming from. """
+ if sprite.direction[0] > 0:
+ sprite.rect.left=0
+ elif sprite.direction[0] < 0:
+ sprite.rect.left=game.screensize[0]-sprite.rect.size[0]
+ if sprite.direction[1] > 0:
+ sprite.rect.top=0
+ elif sprite.direction[1] < 0:
+ sprite.rect.top=game.screensize[1]-sprite.rect.size[1]
+ sprite.lastmove=0
+
+def pullWithIt(sprite, partner, game):
+ """ The partner sprite adds its movement to the sprite's. """
+ if hasattr(sprite, 'lastpull'):
+ # pull only once per timestep, even if there are multiple collisions
+ if sprite.lastpull == game.time:
+ return
+ sprite.lastpull = game.time
+ tmp = sprite.lastrect
+ sprite._updatePos(partner.lastdirection, speedup=1)
+ sprite.lastrect = tmp
+
+def teleportToExit(sprite, partner, game):
+ e = choice(game.sprite_groups[partner.stype])
+ sprite.rect=e.rect
+ sprite.lastmove=0
View
61 vgdl/tools.py
@@ -0,0 +1,61 @@
+'''
+Video game description language -- utility functions.
+
+@author: Tom Schaul
+'''
+
+
+class Node(object):
+ """ Lightweight indented tree structure, with automatic insertion at the right spot. """
+
+ parent = None
+ def __init__(self, content, indent, parent=None):
+ self.children = []
+ self.content = content
+ self.indent = indent
+ if parent:
+ parent.insert(self)
+ else:
+ self.parent = None
+
+ def insert(self, node):
+ if self.indent < node.indent:
+ if len(self.children) > 0:
+ assert self.children[0].indent == node.indent, 'children indentations must match'
+ self.children.append(node)
+ node.parent = self
+ else:
+ assert self.parent, 'Root node too indented?'
+ self.parent.insert(node)
+
+ def __repr__(self):
+ if len(self.children) == 0:
+ return self.content
+ else:
+ return self.content+str(self.children)
+
+ def getRoot(self):
+ if self.parent: return self.parent.getRoot()
+ else: return self
+
+
+def indentTreeParser(s, tabsize=8):
+ """ Produce an unordered tree from an indented string. """
+ # insensitive to tabs, parentheses, commas
+ s = s.expandtabs(tabsize)
+ s.replace('(', ' ')
+ s.replace(')', ' ')
+ s.replace(',', ' ')
+ lines = s.split("\n")
+
+ last = Node("",-1)
+ for l in lines:
+ # remove comments starting with "#"
+ if '#' in l:
+ l = l.split('#')[0]
+ # handle whitespace and indentation
+ content = l.strip()
+ if len(content) > 0:
+ indent = len(l)-len(l.lstrip())
+ last = Node(content, indent, last)
+ return last.getRoot()

0 comments on commit 7d60d3a

Please sign in to comment.
Something went wrong with that request. Please try again.