Source Code for iHTML ASKBLiG Demo


# ASKBliG
# ~~~~~~~
#
# Aka, "Breakout."  Kind-of.

# Input arguments:
# ~~~~~~~~~~~~~~~
#
# Width  -- (standard) width of board
# Height -- (standard) height of board
# Rows   -- number of rows of bricks

# Game play
# ~~~~~~~~~
# The user may move the mouse to move the paddle.  Pressing any mouse
# button will create a new ball; up to four balls may be active at
# one time.  Press 'n' to start a new game.

# Game scoring
# ~~~~~~~~~~~~
#
# Good things--
#
#   Hit wall:    +10  pts
#   Hit paddle:  +20  pts
#   Hit brick:   +100 pts
#   Hit ball:    +200 pts
#
# Bad things--
#
#   Loose ball:  -400 pts
#   Being alive: -1 pt/frame
#
# The player starts a game with 1000 points; the game is over when
# the score reaches zero.

# Known bugs:
# ~~~~~~~~~~
# 
# - Balls will sometimes go through a wall and get lost.
# - When the system slows down too much, mouse movement events are
#   seen sporadically; it should really be using MousePosition() to
#   place the paddle, put for some reason this routine it tremendously
#   slow when the paddle moves.  Strange.

import ihEvent   # For interpreting event messages
import ihApp     # Very basic application framework on top of
               # the iHTML core.  Veeerry basic.

#      Class: HitMask
# Superclass: none
#
# This class is used to make checking for object collisions more efficient.
# It divides the play area into horizontal strips, and keeps track of which
# objects are in each strip.  This way an object only needs to check those
# in its own strip, rather than the entire playfield.
#
# The class current divides the playfield into strips of 20 pixels; it
# should be more intelligent about this, as the overall efficiency of the
# system is extremely dependent on this constant.  Too large, and objects
# have to check more hits than necessary; too large, and they have to check
# many individual strips to find all the collisions.
#
# This class can also probably stand to be improved a bit -- it might
# be more effecient to have the strips contain dictionaries rather than
# arrays.

class HitMask:

    # Create class.  'height' is the total height, in pixels, of the
    # playfield.

    def __init__(self,height):
	self.height = height
	self.last = (height-1)/20
	if( self.last < 1 ): self.last = 1
	#self.masks = [[]] * (self.last+1)
	self.masks = []
	for i in range(0,self.last+1):
	    self.masks.append([])

    # Add a new piece to the HitMask, who's top is located at 'y' and
    # which is 'height' pixels tall.

    def AddPiece(self,piece,y,height):
	#print 'Add range for %s, y=%s, height=%s' % (piece,y,height)
	#print self.make_range(y,height)
	for i in self.make_range(y,height):
	    self.masks[i].append(piece)

    # Remove a piece from the HitMask, who's top is located at 'y' and
    # which is 'height' pixels tall.

    def RemPiece(self,piece,y,height):
	#print 'Rem range for %s, y=%s, height=%s' % (piece,y,height)
	#print self.make_range(y,height)
	try:
	    for i in self.make_range(y,height):
		self.masks[i].remove(piece)
	except ValueError:
	    pass

    # Update the position of a piece -- it was located at 'oldy' and
    # was 'oldheight' pixels tall; it is now located at 'newy' and is
    # 'newheight' pixels tall.
    def MovePiece(self,piece,oldy,oldheight,newy,newheight):
	oldy1, oldy2 = self.get_range(oldy,oldheight)
	newy1, newy2 = self.get_range(newy,newheight)
	if( oldy1 != newy1 or oldy2 != newy2 ):
	    #print 'Moving:',piece
	    if( oldy1 < newy1 ):
		for i in range(oldy1,newy1+1):
		    try:
			self.masks[i].remove(piece)
		    except:
			pass
	    if( newy1 < oldy1 ):
		for i in range(oldy1,newy1+1):
		    self.masks[i].append(piece)
	    if( oldy2 > newy2 ):
		for i in range(newy2-1,oldy2):
		    try:
			self.masks[i].remove(piece)
		    except:
			pass
	    if( newy2 > oldy2 ):
		for i in range(oldy2-1,newy2):
		    self.masks[i].append(piece)

    # Return an array of the strips which an object with the given
    # position and height will need to check for hits.

    def PieceMask(self,y,height):
	y1,y2 = self.get_range(y,height)
	#print 'Get range: from %s to %s' % (y1,y2)
	return self.masks[y1:y2]

    # **
    # ** Private functions
    # **

    # Return the range of strips covered by the given dimensions.
    def get_range(self,y,height):
	y1 = y/20
	if( y1 < 0 ): y1 = 0
	elif( y1 > self.last ): y1 = self.last
	y2 = ((y+height-1)/20)+1
	if( y2 < 1 ): y2 = 1
	elif( y2 > (self.last+1) ): y2 = (self.last+1)
	return y1,y2

    # Create an array range for the given dimensions.
    def make_range(self,y,height):
	y1 = y/20
	if( y1 < 0 ): y1 = 0
	elif( y1 > self.last ): y1 = self.last
	y2 = ((y+height-1)/20)+1
	if( y2 < 1 ): y2 = 1
	elif( y2 > (self.last+1) ): y2 = (self.last+1)
	#print 'y1=%s, y2=%s, last=%s' % (y1,y2,self.last)
	return range(y1,y2)

#      Class: GamePiece
# Superclass: none
#
# This class defines the components common to all pieces used by the
# game.  This includes the bricks, balls, paddle, etc.  It also may
# optionally make use of a HitMask object which the piece is to be
# put in.

# Universal game piece -- defines all common functionality and structure.
class GamePiece:

    # Class initialization.  Arguments are:
    #
    # app:      (required positional) the main application class.  Must be
    #           a subclass of a widget, and define the functions LogScore(),
    #           RemoveBrick(), RemoveBall(), and have member variables
    #           background, foreground, shine_color, shadow_color.
    #           [Yes, this isn't the best. :p]
    # hit_mask: (required positional) a HitMask object in which to place
    #           this piece.  If 'None', it is not used.
    # x:        (optional keyword) the initial x position
    # y:        (optional keyword) the initial y position
    # width:    (optional keyword) the initial width in pixels
    # height:   (optional keyword) the initial height in pixels
    # xvel:     (optional keyword) the initial x velocity
    # yvel:     (optional keyword) the initial y velocity
    # movable:  (optional keyword) boolean -- can this piece be moved?
    # hitable:  (optional keyword) hitable -- can this piece be touched?
    # color:    (optional keyword) color to draw piece in
    # pixmap:   (optional keyword) pixmap to draw piece with; overrides color

    def __init__(self,app,hit_mask,x=0,y=0,width=0,height=0,xvel=0,yvel=0,
		 movable=0,hitable=1,color=None,pixmap=None,**leftovers):
	self.app = app
	self.hit_mask = hit_mask
	self.x = x
	self.y = y
	self.width = width
	self.height = height
	self.xvel = xvel
	self.yvel = yvel
	if hit_mask: hit_mask.AddPiece(self,y,height)
	self.movable = movable
	self.hitable = hitable
	self.color = color or self.app.foreground
	self.pixmap = pixmap
	if pixmap:
	    self.width = pixmap.Dimensions()[2]
	    self.height = pixmap.Dimensions()[3]

    # Change the position of the piece.  Keeps the HitMask up-to-date.

    def SetPosition(self,x,y):
	if( self.hit_mask ): self.hit_mask.MovePiece(self,self.y,self.height,
						     y,self.height)
	self.x = x
	self.y = y

    # Change the size of the piece.  Keeps the HitMask up-to-date.

    def SetSize(self,width,height):
	if( self.hit_mask ): self.hit_mask.RemPiece(self,self.y,self.height)
	self.width = width
	self.height = height
	if( self.hit_mask ): self.hit_mask.AddPiece(self,self.y,self.height)

    # Check for collisions.  'pieces' is an array of pieces which should
    # be checked; for any collision found between this piece and one in
    # in the array, both piece's 'do_hit()' method is called.
    # This should probably be changed to take better advantage of the
    # HitMask.

    def Hit(self,pieces):
	for other in pieces:
	    if not self is other:
# 		print 'Did %s hit %s?' % (self.__class__,other.__class__)
# 		print '(%s,%s)/(%sx%s) to (%s,%s)/(%sx%s)' \
# 		      % ( self.x,self.y,self.width,self.height,
# 			  other.x,other.y,other.width,other.height )
		
		if ( self.x < (other.x+other.width)
		     and (self.x+self.width) > other.x
		     and self.y < (other.y+other.height)
		     and (self.y+self.height) > other.y
		     and self.hitable and other.hitable ):
# 		    print 'HIT: (%s,%s)/(%sx%s) to (%s,%s)/(%sx%s)' \
# 			  % ( self.x,self.y,self.width,self.height,
# 			      other.x,other.y,other.width,other.height )
		    self.do_hit(other)
		    other.do_hit(self)

    # Draw the piece's graphic in the drawable 'where'.  If a pixmap is
    # available, that is used; otherwise, it is by default drawn as a
    # rectangle in the piece's color.

    def Draw(self,where):
	if self.pixmap:
	    where.Paste(self.x,self.y,self.pixmap)
	else:
	    where.ForePen(self.color)
	    where.FillRectangle(self.x,self.y,self.width,self.height)
	
    # Erase the piece's graphic in the drawable 'where'.  Draws a
    # rectangle in the background color.

    def Erase(self,where):
	if self.pixmap:
	    where.ForePen(self.app.background)
	    where.FillRectangle(self.x,self.y,self.width,self.height)
	else:
	    where.ForePen(self.app.background)
	    where.FillRectangle(self.x,self.y,self.width,self.height)

    # Change state for next frame.  By default does no action.

    def Step(self):
	pass  # nothing

    # **
    # ** The rest are private method.
    # **

    # Do action when hit by another object.  Must be defined by sub-class.

    def do_hit(self,other):
	raise AccessError, 'undefined method do_hit() called.'

#      Class: Wall
# Superclass: GamePiece
#
# Implements the functionality for a wall piece.  This basically involves
# incrementing the player's score by 10 when hit by a ball.

class Wall(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

    def do_hit(self,other):
	if other.__class__ == Ball:
	    self.app.LogScore(10)

#      Class: Brick
# Superclass: GamePiece
#
# Implements the functionality for a wall piece.  This involves
# incrementing the player's score by 100 when hit by a ball, and asking
# the main class to remove this brick.

class Brick(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

    def do_hit(self,other):
	if other.__class__ == Ball:
	    self.app.LogScore(100)
	    self.app.RemoveBrick(self)

#      Class: InvisibleDeath
# Superclass: GamePiece
#
# Creates a piece which removes a ball whenever it is touched, and
# decrements the player's score by 400.  It does not draw on to the
# playfield.  This class is used to bound the bottom of the screen,
# below the paddle.

class InvisibleDeath(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

    def Draw(self,where):
	pass

    def Erase(self,where):
	pass

    def do_hit(self,other):
	if other.__class__ == Ball:
	    self.app.LogScore(-400)
	    self.app.RemoveBall(other)

#      Class: InvisibleCleaner
# Superclass: GamePiece
#
# Creates a piece which removes a ball whenever it is touched, but
# does not decrement the player's score.  It does not draw on to the
# playfield.  This class is used behind the walls, to catch any
# balls which somehow [through a bug in the program :p] make it through
# them.

class InvisibleCleaner(GamePiece):

    def __init__(self,app,hit_mask,**keys):

        # Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

    def Draw(self,where):
        pass

    def Erase(self,where):
        pass

    def do_hit(self,other):
        if other.__class__ == Ball:
            self.app.RemoveBall(other)

#      Class: Paddle
# Superclass: GamePiece
#
# Implement the game paddle.  This piece positions itself so that
# it is centered on its current (x,y) location, and adds 20 to the
# player's score when hit by a ball.  It also doesn't allow itself
# to be moved through walls.

class Paddle(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

	self.xvel = 0
	self.yvel = 0

    def do_mouse(self,ev,x,y):
	oldx = self.x
	oldy = self.y
	self.SetPosition(x - (self.width/2),y - (self.height/2))
	if( self.x != oldx):
	    self.xvel = self.x - oldx
	if( self.y != oldy):
	    self.yvel = self.y - oldy

    def do_hit(self,other):
	if other.__class__ == Ball:
	    self.app.LogScore(20)
	elif other.__class__ == Wall:
# 	    print 'myx=%s, myw=%s; othx=%s, othw=%s' % (self.x,self.width,
# 	    						other.x,other.width)
	    if( other.width < other.height ):
		if( (other.x+(other.width/2)) < (self.x+(self.width/2)) ):
		    self.x = other.x+other.width
		if( (other.x+(other.width/2)) > (self.x+(self.width/2)) ):
		    self.x = other.x-self.width
	    else:
		if( (other.y+(other.height/2)) < (self.y+(self.height/2)) ):
		    self.y = other.y+other.height
		if( (other.y+(other.height/2)) > (self.y+(self.height/2)) ):
		    self.y = other.y-self.height

#      Class: Ball
# Superclass: GamePiece
#
# Implement the game ball.  This piece's default draw style is a
# filled circle; it automatically moves itself when stepped, increments
# the player's score by 100 when hit by another ball [giving a total
# score of 200], and tries to bounce itself off other objects.  The
# latter is sometimes not too successful, however.  
# The class changes its default xvel and yvel to 1.

class Ball(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	# Change default velocities.
	if not keys.has_key('xvel'):
	    keys['xvel'] = 1
	if not keys.has_key('yvel'):
	    keys['yvel'] = 1

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

    def Draw(self,where):
	if self.pixmap:
	    where.Paste(self.x,self.y,self.pixmap)
	else:
	    where.ForePen(self.color)
	    where.FillArc(self.x,self.y,self.width,self.height)

    def Step(self):
	if( self.xvel < -7 ): self.xvel = -7
	if( self.xvel > 7 ): self.xvel = 7
	if( self.yvel < -7 ): self.yvel = -7
	if( self.yvel > 7 ): self.yvel = 7
	self.SetPosition(self.x + self.xvel,self.y + self.yvel)

    def do_hit(self,other):
	if other.__class__ == Ball:
	    self.app.LogScore(100)  # Note this is called once for each ball.
	if( other.width < other.height ):
	    self.xvel = -self.xvel
	    if other.__class__ != Wall:
		if( self.y < other.y ):
		    self.yvel = -abs(self.yvel)
		elif( (self.y+self.height) > (other.y+other.height) ):
		    self.yvel = abs(self.yvel)
		    self.yvel = self.yvel + other.yvel
	else:
	    self.yvel = -self.yvel
	    if other.__class__ != Wall:
		if( self.x < other.x ):
		    self.xvel = -abs(self.xvel)
		elif( (self.x+self.width) > (other.x+other.width) ):
		    self.xvel = abs(self.xvel)
		self.xvel = self.xvel + other.xvel
	self.Step()

#      Class: Points
# Superclass: GamePiece
#
# A game piece which keeps track of and displays the player's score.
# Defaults to 'hitable' being 0 and a width which will theoretically
# hold a "large" score.  Also supports a new argument 'points' which
# defines the initial score.

class Points(GamePiece):

    def __init__(self,app,hit_mask,**keys):

	if not keys.has_key('hitable'):
	    keys['hitable'] = 0

	if( not keys.has_key('width') ):
	    tw,th,asc,desc = app.TextExtent(' High: 88888 / Current: 88888 ')
	    print 'text width',tw
	    keys['width'] = tw

	# Do superclass initialization.
        apply(GamePiece.__init__,(self,app,hit_mask),keys)

	if( keys.has_key('points') ):
	    self.points = keys['points']
	else:
	    self.points = 1000
	self.high = self.points

    # Add a point of the given 'amount' [may be negative] to the running
    # total.  If this is a new maximum score, take it.

    def AddPoints(self,amount):
	self.points = self.points + amount
	if( self.points > self.high ):
	    self.high = self.points

    # Is the game over?

    def IsOver(self):
	return self.points < 0

    def Draw(self,where):
	view = 'High: %5d / Current: %5d' % (self.high,self.points)
	w,h,a,d = where.TextExtent(view)
	where.ForePen(self.app.shadow_color)
	where.DrawText(self.x+self.width-w+1,
		       self.y+(self.height/2)-(h/2)+a+1,
		       view, -1)
	where.ForePen(self.app.shine_color)
	where.DrawText(self.x+self.width-w-1,
		       self.y+(self.height/2)-(h/2)+a-1,
		       view, -1)
	where.ForePen(self.color)
	where.DrawText(self.x+self.width-w,
		       self.y+(self.height/2)-(h/2)+a,
		       view, -1)
	
    def do_hit(self,other):
	pass

#      Class: ihEmbed
# Superclass: iHTML Application
#
# Our top-level class.

class ihEmbed(ihApp.Application):

    # Log a change in the user's scored -- called by the pieces.

    def LogScore(self,points):
	if( not self.is_demo ):
	    self.points.AddPoints(points)

    # Remove the given 'ball' from the playfield.  Also removes it
    # from the HitMask, which isn't good; the ball should be doing this
    # itself, nie?

    def RemoveBall(self,ball):
	self.ball_count = self.ball_count - 1
	try:
	    self.move_pieces.remove(ball)
	except ValueError:
	    pass
	self.hit_mask.RemPiece(ball,ball.y,ball.height)

    # Remove the given 'brick' from the playfield.  Also removes it
    # from the HitMask, which isn't good; the brick should be doing this
    # itself, nie?

    def RemoveBrick(self,brick):
	self.brick_count = self.brick_count - 1
	brick.Erase(self.buffer)
	try:
	    self.fixed_pieces.remove(brick)
	except ValueError:
	    pass
	self.hit_mask.RemPiece(brick,brick.y,brick.height)
	if( self.brick_count == 0 ):
	    self.build_bricks()
	    self.draw_all()

    # Main application entry.

    def __init__(self,rows=6,**args):

	# Send all arguments up to parent initialization.
        apply(ihApp.Application.__init__,(self,),args)

	# Retrieve arguments
	self.rows = rows

	# Retrieve basic app colors
	self.foreground = self.ForePen()  
	self.background = self.BackPen()
	self.wall_color = self.foreground
	self.shine_color = self.MakeColor(1,1,1)
	self.shadow_color = self.MakeColor(0,0,0)
	self.ball_color = self.MakeColor(1,1,0)
	self.brick_color = self.MakeColor(.9,.3,.2)
	self.paddle_color = self.MakeColor(0,0,.4)

	# Create buffer to draw in.
	self.buffer = self.MakePixmap(self.Dimensions()[2],
				  self.Dimensions()[3])

	# Initialize mouse coordinates
	self.mouse_x = 0
	self.mouse_y = 0
	self.is_demo = 0

	# Find dimensions for playfield
	self.find_dimens()

	# Create initial game pieces.
	self.start_game()

	# Draw initial image into buffer and start game.
	self.draw_all()
	self.update()

    # **
    # ** All other methods are private
    # **

    # Find dimensions for screen.  This sets a ton of member variable
    # which I am not going to document, so there.  [Hey!  It's a
    # dynamically interpreted language!  You're -supposed- to be quick
    # and loose with your variables. ;)]

    def find_dimens(self):
	x, y, w, h = self.Dimensions()
	tw,th,asc,desc = self.TextExtent('88888888888')
	bor = 10
	self.width = w
	self.height = h
	self.inner_width = w-(bor*2)
	self.inner_height = h-(bor*2)
	self.text_height = th
	self.border_width = bor
	self.brick_top = bor*3 + th

	self.border_width = 10
	self.brick_width = (self.inner_width/10)-4
	self.paddle_width = w/15
	self.paddle_height = bor

	self.ball_width = 8

	# Create pictures for some game pieces
	self.brick_pic = self.MakePixmap(self.brick_width,bor)
	self.brick_pic.ForePen(self.brick_color)
	self.brick_pic.FillRectangle(0,0,self.brick_width,bor)
	self.brick_pic.ForePen(self.shadow_color)
	self.brick_pic.DrawLine(0,bor-1,self.brick_width-1,bor-1)
	self.brick_pic.DrawLine(self.brick_width-1,0,self.brick_width-1,bor-1)
	self.brick_pic.ForePen(self.shine_color)
	self.brick_pic.DrawLine(0,0,self.brick_width-1,0)
	self.brick_pic.DrawLine(0,0,0,bor-2)

	bw = self.ball_width
	self.ball_pic = self.MakePixmap(bw,bw)
	self.ball_pic.ForePen(self.background)
	self.ball_pic.FillRectangle(0,0,bw,bw)
	self.ball_pic.ForePen(self.ball_color)
	self.ball_pic.FillArc(0,0,bw,bw)
	self.ball_pic.ForePen(self.shine_color)
	self.ball_pic.DrawArc(0,0,bw,bw,90,270)
	self.ball_pic.ForePen(self.shadow_color)
	self.ball_pic.DrawArc(0,0,bw,bw,270,90)

	self.paddle_pic = self.MakePixmap(self.paddle_width,bor)
	self.paddle_pic.ForePen(self.paddle_color)
	self.paddle_pic.FillRectangle(0,0,self.paddle_width,bor)
	self.paddle_pic.ForePen(self.shadow_color)
	self.paddle_pic.DrawLine(0,bor-1,self.paddle_width-1,bor-1)
	self.paddle_pic.DrawLine(self.paddle_width-1,0,
				 self.paddle_width-1,bor-1)
	self.paddle_pic.ForePen(self.shine_color)
	self.paddle_pic.DrawLine(0,0,self.paddle_width-1,0)
	self.paddle_pic.DrawLine(0,0,0,bor-2)
    
    # Start a new game.  Initializes the arrays holding pieces, creates
    # a HitMask for the game, creates the pieces.

    def start_game(self):
	self.move_pieces = []   # All pieces which can move
	self.fixed_pieces = []  # All pieces which are fixed in place

	self.ball_count = 0
	self.brick_count = 0

	w, h = self.width, self.height
	th = self.text_height
	bor = self.border_width
	inw, inh = self.inner_width, self.inner_height

	self.hit_mask = HitMask(h)
	self.ball_count = 0
	self.brick_count = 0

	self.paddle = Paddle(self, self.hit_mask,
			     x=w/2, y=self.height-self.paddle_height,
			     width=w/15, height=bor,
			     color=self.paddle_color,
			     pixmap=self.paddle_pic)

	self.points = Points(self, None, 
			     x=bor*2, y=bor*2,
			     width = w-(bor*4), height=th+(th/2), 
			     color = self.foreground, points=1000)

	self.fixed_pieces.append(Wall(self, self.hit_mask,
				      x=0, y=0,
				      width=bor, height=h,
				      color=self.wall_color))
        self.fixed_pieces.append(InvisibleCleaner(self, self.hit_mask,
                                                x=-(bor*2), y=-(bor*2),
                                                width=(bor*2), height=h+(bor*2),
                                                color=self.wall_color))
	self.fixed_pieces.append(Wall(self, self.hit_mask,
				      x=w-bor, y=0,
				      width=bor, height=h,
				      color=self.wall_color))
        self.fixed_pieces.append(InvisibleCleaner(self, self.hit_mask,
                                                x=w, y=-(bor*2),
                                                width=(bor*2), height=h+(bor*2),
                                                color=self.wall_color))
	self.fixed_pieces.append(Wall(self, self.hit_mask,
				      x=bor, y=0,
				      width=w-(bor*2), height=bor,
				      color=self.wall_color))
        self.fixed_pieces.append(InvisibleCleaner(self, self.hit_mask,
                                                x=0, y=-(bor*2),
                                                width=w, height=(bor*2),
                                                color=self.wall_color))
	self.fixed_pieces.append(InvisibleDeath(self, self.hit_mask,
						x=0, y=h+bor,
						width=w, height=(bor*2),
						color=self.wall_color))

	self.move_pieces.append(self.paddle)

	self.build_bricks()

    # Build the columns of bricks.  Builds 'self.cols' number of columns.

    def build_bricks(self):
	top = self.brick_top
	for i in range(self.rows):
	    for j in range(10):
		self.brick_count = self.brick_count + 1
		self.fixed_pieces.append(Brick(self, self.hit_mask,
					       x = ((self.inner_width*j)
						    /10)+2+self.border_width,
					       y = top,
					       width = (self.inner_width/10)-4,
					       height = self.border_width,
					       color = self.brick_color,
					       pixmap = self.brick_pic))
	    top = top + self.border_width*2

	#self.move_pieces.append(Ball(self,x=w/2,y=h/2,width=8,height=8,
	#			     xvel=2,yvel=2,color=self.ball_color))

 	#print 'Mask array='
 	#print self.hit_mask.masks

    # Draw the entire playfield in the buffer.  This is only done when
    # something drastically changes.

    def draw_all(self):
	self.buffer.ForePen(self.background)
	self.buffer.FillRectangle(0,0,self._Widget_.Dimensions()[2],
			      self._Widget_.Dimensions()[3])
	for i in self.move_pieces:
	    i.Draw(self.buffer)
	for i in self.fixed_pieces:
	    i.Draw(self.buffer)
	self.points.Draw(self.buffer)
	self.redraw()

    # Redraw the main window.  This just involves copying the buffer in
    # to it.

    def redraw(self):
	self.Paste(0,0,self.buffer)
	self.Flush()
	pass

    # Erase all of the dynamic pieces.

    def erase_pieces(self):
	for i in self.move_pieces:
	    i.Erase(self.buffer)
	self.points.Erase(self.buffer)

    # Draw all of the dynamic pieces.

    def draw_pieces(self):
	for i in self.move_pieces:
	    i.Draw(self.buffer)
	self.points.Draw(self.buffer)

    # Update the game.  Erase all pieces, step all pieces, get mouse
    # position and move paddle, check for collisions, redraw display.

    def update(self):
	#print self.MouseState()
	self.FutureCall(.05,self.update)
	self.erase_pieces()
	self.LogScore(-(self.ball_count)/2 - 1)
	for i in self.move_pieces:
	    i.Step()
	# For some reason, MousePosition() seems to be deadly slow
	# when the mouse is moving in the window... HUH?  
	#self.mouse_x, self.mouse_y = self.MousePosition()
	#print self.MousePosition()
	if( self.is_demo ):
	    lowest = None
	    follow = None
	    for i in self.move_pieces:
		if( i.__class__ == Ball ):
		    if( not lowest or i.y > lowest.y ):
			lowest = i
	            if( i.yvel >= 0 and (not follow or i.y > follow.y) ):
			follow = i
	    if( not follow ):
		follow = lowest
	    if( follow ):
		if( follow.x > (self.mouse_x+10) ):
		    self.mouse_x = self.mouse_x+10
                elif( follow.x < (self.mouse_x-10) ):
                    self.mouse_x = self.mouse_x-10
		else:
		    self.mouse_x = follow.x
	    if( (not lowest) or lowest.y < (self.height/2) ):
		self.add_ball(self.mouse_x,self.mouse_y)
	if( self.mouse_x < self.border_width ):
	    self.mouse_x = self.border_width
	if( self.mouse_x > (self.width-self.border_width) ):
	    self.mouse_x = self.width-self.border_width
	if( self.mouse_y < self.border_width ):
	    self.mouse_y = self.border_width
	if( self.mouse_y > (self.height-self.border_width) ):
	    self.mouse_y = self.height-self.border_width
	self.paddle.do_mouse(None,self.mouse_x,self.height-self.paddle_height)
	for i in self.move_pieces:
 	    #print 'PieceMask:'
 	    #print self.hit_mask.PieceMask(i.y,i.height)
	    for j in self.hit_mask.PieceMask(i.y,i.height):
 		#print j
		# Problem -- if a piece occurs in more than one slice, we
		# may hit it twice.
		i.Hit(j)
	self.draw_pieces()

	if( self.points.IsOver() ):
	    self.start_game()
	    self.draw_all()
	else:
	    self.redraw()

    # Handle redraw events.

    def OnRedraw(self,ev,x,y,w,h):
	self.redraw()

    # Handle keypress events.

    def OnKeyPress(self,ev,key,state):
	keystr = chr(key)
	if( keystr == 'n' ):
	    self.is_demo = 0
	    self.points.AddPoints(-100000)
	if( keystr == 'd' ):
	    self.is_demo = 1

    # Handle mouse events.

    def add_ball(self,x,y):
            if( self.ball_count < 4 ):
                self.ball_count = self.ball_count + 1
                self.move_pieces.append(Ball(self, self.hit_mask,
                                             x=self.paddle.x
                                             +(self.paddle.width/2)-4,
                                             y=self.paddle.y-8,
                                             width=self.ball_width,
                                             height=self.ball_width,
                                             xvel=self.paddle.xvel,
                                             yvel=-5,
                                             color=self.ball_color,
                                             pixmap=self.ball_pic))

    def OnMouse(self,ev,x,y,state,hit):

	if( ev.Code == ihEvent.CodeNames['MousePress'] ):
	    self.add_ball(x,y)
	if( not self.is_demo ):
	    self.mouse_x = x
	    self.mouse_y = y

__export__ = ['ihEmbed']

_________.oo_Q_Q_oo.____________________________________________
Dianne Kyra Hackborn <hackbod@angryredplanet.com>
Last modified: Wed Aug 14 14:33:18 PDT 1996

This web page and all material contained herein is Copyright (c) 1997 Dianne Hackborn, unless otherwise noted. All rights reserved.