Skip to content

Instantly share code, notes, and snippets.

@GuyCarver
Created November 20, 2012 05:07
Show Gist options
  • Save GuyCarver/4116156 to your computer and use it in GitHub Desktop.
Save GuyCarver/4116156 to your computer and use it in GitHub Desktop.
Pythonista Frogger style game
#----------------------------------------------------------------------
# Copyright (c) 2012, Guy Carver
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * The name of Guy Carver may not be used to endorse or promote products # derived#
# from # this software without specific prior written permission.#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# FILE monkey.py
# BY Guy Carver
# DATE 11/19/2012 10:48 PM
#----------------------------------------------------------------------
from scene import *
from random import *
from sound import *
from math import sin
IPad = True
streeth = 44
divw = 32
sidewalkw = 64
sidewalkh = 44
numcars = 8
numfloaters = 10
offset = Point(0, 50)
waterspeed = Point(80, 25)
streetspeed = Point(100, 50)
badguyspeed = Point(2, 1)
cardelay = (0.25, 0.75, 0.025)
floatdelay = (1.0, 2.0, 0.025)
fontsize = 20 #Size of the font for display of score and tries..
models = [('Ambulance', 50), ('Bus', 64),('Car_1', 48), ('Car_2', 48),('Delivery_Truck', 64),
('Fire_Engine', 64), ('Police_Car', 50), ('Taxi', 50), ('Bus', 80), ('Railway_Car', 80)]
rivermodels = [('Sailboat', 64), ('Sailboat', 96), ('Sailboat', 160),
('Speedboat', 192), ('Speedboat', 96), ('Speedboat', 128),
('Whale', 64), ('Whale', 96), ('Whale', 192)]
def getrect(pos):
###return a rect at given cell location with size of cell.
return Rect(pos.x * divw + offset.x,
pos.y * streeth + offset.y,
divw, streeth)
###-1 if < 0 otherwise 1
def sgn(val): return 1 if val >= 0 else -1
class badguy(object):
###Object to represent the badguy running at the top of the screen.
def __init__(self, y, scn):
self.scene = scn
w = divw
self.right = scn.size.w - w
x = uniform(w, self.right) #Pick random start position.
self.dir = uniform(-w, w) #Pick random direction and speed.
self.pos = Point(x, y)
self.turntime = 3 #Time interval in seconds between decisions to turn around.
self.timer = self.turntime
self.setspeed()
def setspeed(self): self.speed = uniform(1, 3)
def changedir(self): self.dir = -self.dir
def update(self, dt):
###Update.
self.pos.x -= self.dir * dt * self.speed
if self.pos.x < 0:
self.pos.x = 0
self.changedir()
elif self.pos.x > self.right:
self.pos.x = self.right
self.changedir()
self.timer -= dt
if self.timer <= 0: #If decision timer is up then randomly check to turn around.
self.timer = self.turntime
if random() < 0.2: #20% chance of turning around.
self.changedir()
def draw(self):
###Draw the badguy.
tint(1,1,1)
x = self.pos.x
if self.dir < 0:
x -= self.dir
r = Rect(x, self.pos.y, self.dir, streeth)
image('Runner', *r)
def collide(self, rect):
###Check collision of runner with given rectangle.
r = Rect(self.pos.x, self.pos.y, divw, streeth)
return r.intersects(rect)
class target(object):
###Object representing the target (family) at the top of the screen.
###Set the position.
def setpos(self, pos): self.frame = Rect(pos.x, pos.y, divw, streeth)
def draw(self):
###Draw the target.
tint(1,1,1)
image('Man_And_Woman', *self.frame)
###Check collision with given rect.
def collide(self, rct): return self.frame.intersects(rct)
class splash(object):
###Water splash consists of a blue ripple and white splash.
def __init__(self): self.on = False
def set(self, pos):
###Set the splash at the given position.
d = divw / 2
self.frame = Rect(pos.x + d, pos.y + d, 1, 1)
self.on = True
self.alpha = 0.8 #Ripple alpha.
self.alpha2 = 1.0 #Splash alpha.
set_volume(0.3)
play_effect('Shot')
def update(self, dt):
###Update the splash.
if self.on:
d = divw * dt #Expand by divw/second.
#Keep the center of the expanding splash at the same point.
w = self.frame.w + d * 2 #Adjust the width by 2xd
self.frame.x -= d #Subtrace d from x,y
self.frame.y -= d
self.frame.w = w #Set width/height to w.
self.frame.h = w
self.alpha = max(0.0, self.alpha - 1.5 * dt) #Adjust ripple alpha.
self.alpha2 = max(0.0, self.alpha2 - 3 * dt) #Adjust splash alpha.
self.on = self.alpha > 0 #Turn off when ripple alpha is 0.
def draw(self):
if self.on:
a = min(1.0, self.alpha)
tint(0,0,1,a)
image('Red_Ring', *self.frame) #Draw blue ripple.
tint(0.5,1,1,self.alpha2)
image('Snowflake', *self.frame) #Draw white splash.
class splat(object):
###Blood splat from getting run over.
def __init__(self): self.on = False
def set(self, pos):
###Set the position of the splat.
w = 24 if IPad else 12
self.frame = Rect(pos.x, pos.y, w, w)
self.on = True
self.alpha = 2.0 #Alpha > 1 to keep it visible longer.
set_volume(1)
play_effect('Footstep' if randint(0, 1) else 'Jump')
def update(self, dt):
###Update splat.
if self.on:
self.alpha = max(0.0, self.alpha - 0.5 * dt) #Reduce alpha by .5/second.
self.on = self.alpha > 0 #Turn off when alpha is 0.
def draw(self):
###If on draw the splat.
if self.on:
a = min(1.0, self.alpha) #Keep alpha between 0 and 1.
tint(1,0,0, a) #Tint it red.
image('Cloud', *self.frame)
class sidewalk(object):
###Sidewalk object.
def __init__(self, y, scn):
x = scn.bounds.x
w = scn.bounds.w
self.frame = Rect(x, y, w, streeth)
self.framesegment = Rect(x, y, sidewalkw, sidewalkh)
self.count = int(w /sidewalkw) #Tile image for width of screen.
self.image = 'PC_Ramp_South'
###Set the image to draw on the sidewalk.
def setimage(self, img): self.image = img
###Update does nothing.
def update(self, dt): pass
def draw(self):
###Draw the sidewalk.
tint(1,1,1,1)
p = Rect(*self.framesegment)
p.y -= 24 if IPad else 12
p.h += 50 if IPad else 25
for i in range(self.count): #Loop for # of tiles.
image(self.image, *p)
p.x += sidewalkw #Adjust x to next tile position.
class car(object):
###Object representing a car on the road.
def __init__(self, pos, scn, left):
self.start = 0 if left else scn.size.w #Start on left or right of screen.
self.otherside = scn.size.w - self.start #Set destination to other side.
self.scale_x = 1 if left else -1
x = self.start + pos.x * self.scale_x
self.frame = Rect(x, pos.y, 0, streeth)
self.on = random() < 0.35 #Randomly turn the car on.
self.setmodel()
pos.x += abs(self.frame.w) * 2 * self.scale_x #Adjust position by 2 x width of our frame so next car isn't on top of us.
self.prevx = x #Set previous x (in case we can ride the car).
###Set vehicle speed in the direction indicated by scale_x.
def setspeed(self, speed): self.vel = speed * self.scale_x
def setmodel(self):
###Randomly pick the model and set the frame size.
div = -1 if IPad else -.5 #Negative because we go opposite direction of image.
m = models[randint(0, len(models)-1)]
self.image = m[0]
self.frame.w = (m[1] * div) * self.scale_x
self.end = self.otherside - self.frame.w
###Check collision.
def collide(self, rct): return self.on and self.frame.intersects(rct)
def startpos(self):
###Reset the start position.
self.frame.x = self.start
self.prevx = self.start
def reset(self):
###Reset the vehicle and turn on.
self.setmodel()
self.startpos()
self.on = True
def isfree(self):
###Return true if the car is fully on screen indicating the
### lane is free for another car to start.
d = (self.frame.x + self.frame.w)
if self.start: #if start is not 0 (left side) negate the point.
d = -d
return (self.start + d) > 0
###Return the distance from last position to current position.
### Used to move the player if "riding the vehicle".
def deltax(self): return self.frame.x - self.prevx
def update(self, dt):
###Update the vehicle.
if self.on:
x = self.frame.x
self.prevx = x
x += self.vel * dt #Adjust x by velocity.
self.frame.x = x
#Set off if off end of screen.
self.on = (x > self.end) if self.end < 0 else (x < self.end)
def draw(self):
###Draw the vehicle.
if self.on:
tint(1, 1, 1)
image(self.image, *self.frame)
class floater(car):
###Extend car to represent a vehicle on the water.
def setmodel(self):
###Set the model.
div = -1 if IPad else -.5 #Negative because images are backwards.
i = randint(0, len(rivermodels)-1)
m = rivermodels[i]
self.image = m[0]
self.frame.w = m[1] * div * self.scale_x
self.end = self.otherside - self.frame.w
self.sink = i > 5 #If index > 5 then it's a sinking vehicle.
if self.sink: #Pick random sinking phase.
self.phase = random() * (self.otherside - self.start)
###Return true if submerged. -2 <= sine <= 0 and submerged if < -1.9
def submerged(self): return self.sink and self.sine < -1.9
def update(self, dt):
###Update the vehicle.
car.update(self, dt)
if self.sink: #If sinkable then adjust the sine wave.
self.sine = sin((self.frame.x + self.phase) * 0.01) - 1.0
def draw(self):
###Draw the vehicle.
if self.on:
if self.sink: #If sinkable then adjust y by sine wabe.
r = Rect(*self.frame)
r.y += (r.h * 0.5) * self.sine #Since sine is -2 - 0 we divide h by 2.
tint(1, 1, 1)
image(self.image, *r)
else:
car.draw(self)
class player(Layer):
###Player.
def __init__(self, pos, scn) :
Layer.__init__(self)
self.image = 'Monkey'
self.background = Color(0,0,0,0)
self.tint = Color(1,1,1,1)
self.setframe(pos)
self.start = pos
self.rotation = 0
self.score = 0
self.tries = 1
self.scene = scn
scn.add_layer(self)
#Return a new Point object representing the position.
def getpos(self): return Point(self.frame.x, self.frame.y)
def setframe(self, pos) :
###set position rect.
self.frame = Rect(pos.x, pos.y, divw, divw)
self.moving = False
self.supported = None
def sink(self):
###The vehicle we were riding sank.
self.scene.splash.set(self.getpos())
self.tries += 1
self.reset()
def hit(self):
###We were run over.
self.tries += 1
self.reset()
def finished(self):
###We reached the target.
self.score += 1
set_volume(0.4)
play_effect('Pulley')
self.reset()
def reset(self):
###Put back at the beginning.
self.setframe(self.start)
self.remove_all_animations()
###Set movement done.
def movedone(self): self.moving = False
def update(self, dt):
###Update the player.
Layer.update(self, dt)
###If supported by a vehicle.
if self.supported:
if self.supported.submerged(): #If the vehicle is submerged.
self.sink()
else:
d = self.supported.deltax() #Get amound to move to say on supporting vehicle.
self.frame.x += d
#Keep on screen.
self.frame.x = max(self.scene.bounds.left(), min(self.scene.bounds.right() - self.frame.w + 1, self.frame.x))
def moveit(self, dir) :
###if not moving then start movement anims in the given direction.
if not self.moving :
torect = Rect(*self.frame)
torect.x += dir.x * divw
torect.x = min(max(self.scene.min.x, torect.x), self.scene.max.x - divw)
torect.y += dir.y * streeth
torect.y = min(max(self.scene.min.y, torect.y), self.scene.max.y - streeth)
self.moving = True
set_volume(0.4)
play_effect('Jump_5')
crv = curve_ease_back_out #if dir.y else curve_linear
#Animate frame and call movedone when finished.
self.animate('frame', torect, 0.5, curve=crv, completion = self.movedone)
class stream(sidewalk):
###Extend sidewalk object to represent a river stream.
def __init__(self, y, scn, left):
sidewalk.__init__(self, y, scn)
pos = Point(scn.bounds.x, y)
#Create the floaters.
self.floaters = [floater(pos, scn, left) for i in xrange(numfloaters)]
self.setspeed()
self.lastfloater = None
def setspeed(self):
###Set random speed of the stream.
self.speed = random() * waterspeed.x + waterspeed.y
for f in self.floaters: #Adjust speed of floaters in this stream.
f.setspeed(self.speed)
def collide(self, rct):
###Check collision on this stream and return stream, floater of collision.
s = self.frame.intersects(rct)
if s: #If colliding with the stream check the floaters.
for f in self.floaters:
if f.collide(rct):
return (s, f) #Return stream, floater.
return (s, None)
def startfloater(self):
###Start a floater.
if self.lastfloater: #Make sure last floater is free 1st.
if not self.lastfloater.isfree():
return False
for f in self.floaters:
if not f.on: #Find a floater that is off.
f.reset() #Start floater.
self.lastfloater = f
break
return True
def update(self, dt):
###Update floaters in the stream.
for f in self.floaters:
f.update(dt)
def draw(self):
###Draw sidewalk and it's floaters.
sidewalk.draw(self)
for f in self.floaters:
f.draw()
class street(object):
###Object to represent a lane on the street.
def __init__(self, y, scn, left):
x = scn.bounds.x
w = scn.bounds.w
self.left = left
self.lastcar = None
self.frame = Rect(x, y, w, streeth)
self.divider = Rect(x, self.frame.top() - 2, w, 2)
if left:
self.count = int(w / divw)
self.divider.w = divw - 4 #Leave 4 pixels between tiles to make dash.
else:
self.count = 1 #Solid line.
pos = Point(x, y)
#Add cars, pos will be adjusted by the car constructor.
self.cars = [car(pos, scn, left) for i in xrange(numcars)]
self.setspeed()
def setspeed(self):
###Set speed to random range and adjust lane speed.
self.speed = random() * streetspeed.x + streetspeed.y
for c in self.cars:
c.setspeed(self.speed)
def collide(self, rct):
###Check collision of lane and cars in lane. Return true if collide.
if self.frame.intersects(rct):
for c in self.cars:
if c.collide(rct):
return True
return False
def startcar(self):
###Start a car.
if self.lastcar:
if not self.lastcar.isfree(): #Wait til last car is on screen.
return False
#Find a free car and start it.
for c in self.cars:
if not c.on:
c.reset()
self.lastcar = c
break
return True
def update(self, dt):
###Update cars in lane.
for c in self.cars:
c.update(dt)
def draw(self):
###Draw lane.
fill(0.40, 0.40, 0.40)
rect(*self.frame)
fill(1.00, 1.00, 0.00)
p = Rect(*self.divider)
for i in xrange(self.count):
rect(*p)
p.x += divw
def drawcars(self):
###Draw cars in lane.
for c in self.cars:
c.draw()
class MyScene(Scene):
###Scene.
def setup(self):
# This will be called before the first frame is drawn.
global IPad
global streeth
global divw
global sidewalkw
global sidewalkh
global offset
IPad = self.size.w > 700
if not IPad: #On IPhone all of these values are half.
streetspeed.x /= 2
streetspeed.y /= 2
waterspeed.x /= 2
waterspeed.y /= 2
badguyspeed.x /= 2
badguyspeed.y /= 2
streeth /= 2
divw /= 2
sidewalkw /= 2
sidewalkh /= 2
offset.y /= 2
self.root_layer = Layer(self.bounds)
self.org = Rect(self.bounds.x + 8, self.bounds.y + 8, self.bounds.w - 16, self.bounds.h - 16)
self.splat = splat()
self.splash = splash()
pos = Point(*offset)
self.min = Point(*pos)
self.start = sidewalk(pos.y, self)
px = divw * 16 #Middle tile.
self.player = player(Point(px, pos.y), self)
pos.y += streeth
def makelane(i):
###Local function to create a lane.
l = street(pos.y, self, (i & 1) == 0)
pos.y += streeth #Adjust y by lane height.
return l
def makestream(i):
###Local function to create a stream.
s = stream(pos.y, self, (i & 1))
s.setimage('PC_Water_Block')
pos.y += streeth #Adjust y by stream height.
return s
self.lanes = [makelane(i) for i in xrange(6)]
self.middle = sidewalk(pos.y, self)
pos.y += streeth
self.middle.setimage('PC_Grass_Block')
self.streams = [makestream(i) for i in xrange(6)]
self.streams.reverse()
self.end = sidewalk(pos.y, self)
self.badguy = badguy(pos.y, self)
self.target = target()
pos.y += streeth
pos.x = self.bounds.w
self.max = Point(*pos)
pos.y += 5 #Score is up 5 pixels.
pos.x = self.bounds.left()
self.scorepos = Point(*pos)
pos.x = self.bounds.right() - 100 #Tries are in 100 pixels from right.
self.trypos = Point(*pos)
self.reset()
###Return false.
def should_rotate(self, rot): return False
def settarget(self):
###Set target to random position on sidewalk at top of screen.
self.target.setpos(Point(random() * (self.size.w - sidewalkw * 2) + sidewalkw, self.end.frame.y))
def reset(self):
###Reset the game.
self.finished = False
self.cardelay = cardelay[1]
self.floatdelay = floatdelay[0]
self.startcar()
self.startfloater()
self.settarget()
def newvelocities(self):
###Set new velicities for street and streams.
for s in self.streams:
s.setspeed()
for l in self.lanes:
l.setspeed()
self.badguy.setspeed()
def startfloater(self):
###Start a random floater.
inds = range(0, len(self.streams))
shuffle(inds)
for i in inds: #Try each stream until a floater started.
if self.streams[i].startfloater():
break
#Restart the delay timer.
self.delay(self.floatdelay, self.startfloater)
def startcar(self):
###Start a random car.
inds = range(0, len(self.lanes))
shuffle(inds)
for i in inds: #Try each lane until a car is started.
if self.lanes[i].startcar():
break
#Restart the delay timer.
self.delay(self.cardelay, self.startcar)
def movedir(self, pos):
###calculate movement direction.
pos.x -= self.tstart.x
pos.y -= self.tstart.y
ax = abs(pos.x)
ay = abs(pos.y)
if ax > ay :
return Point(sgn(pos.x), 0)
return Point(0, sgn(pos.y))
def success(self):
###Player reached target.
self.player.finished()
self.newvelocities()
self.settarget()
self.cardelay = max(cardelay[0], self.cardelay - cardelay[2])
self.floatdelay = min(floatdelay[1], self.floatdelay + floatdelay[2])
def update(self):
###Update scene.
rct = Rect(*self.player.frame)
rct.x += 8 #Adjust player rectangle used for collision detection.
rct.w -= 16
rct.y += 8
rct.h -= 16
b = False
#Update cars.
for l in self.lanes:
l.update(self.dt)
#Update streams.
for s in self.streams:
s.update(self.dt)
#Check collision with cars.
for l in self.lanes:
if l.collide(rct): #If collision draw splat and reset player.
b = True
self.splat.set(self.player.getpos())
self.player.hit()
break
#If no collision check streams.
if not self.player.moving: #Only check support if not jumping.
if not b:
self.player.supported = None #Clear support.
#Check floaters in streams for support.
for s in self.streams:
b, cl = s.collide(rct)
if b: #If on a stream.
if cl: #If a support object.
self.player.supported = cl
else: #No support so we sink.
self.player.sink()
break
#If didn't collide.
if not b:
if self.target.collide(rct):
self.success() #At target.
elif self.badguy.collide(rct):
self.player.hit() #Caught by badguy.
self.badguy.update(self.dt)
self.splat.update(self.dt)
self.splash.update(self.dt)
def drawscore(self):
###Draw the score strings.
tint(1,1,0,1)
text('score: ' + str(self.player.score), 'Copperplate-Bold', fontsize, *self.scorepos.as_tuple(), alignment=9)
text('tries: ' + str(self.player.tries), 'Copperplate-Bold', fontsize, *self.trypos.as_tuple(), alignment=7)
def draw(self):
# This will be called for every frame (typically 60 times per second).
self.root_layer.update(self.dt)
self.update()
background(0,0,0)
self.end.draw()
for l in self.streams: l.draw()
self.middle.draw()
for l in self.lanes: l.draw()
self.splash.draw()
self.badguy.draw()
self.target.draw()
self.splat.draw()
#Draw cars after splat
for l in self.lanes: l.drawcars()
self.start.draw()
self.root_layer.draw()
self.drawscore()
###Handle touch begin event.
def touch_began(self, touch): self.tstart = touch.location
def touch_ended(self, touch):
#Handle touch end event.
if self.finished :
self.reset()
else: #Calculate swipe direction and move player.
d = self.movedir(touch.location)
self.player.moveit(d)
run(MyScene(), LANDSCAPE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment