Here you will find the source code for the Kitty Hero game, as described in the upcoming book Graphic Guide to Python

Copyright 2018 Antony Lees

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 def setup():
  size(600, 1000, P3D)
  
  # game board
  global standard_tile
  standard_tile = "standard"
  global hole_tile
  hole_tile = "hole"
  global hole_hit_tile
  hole_hit_tile = "hit"
  global target_tile
  target_tile = "target"
  global number_of_columns
  number_of_columns = 5
  global tiles
  tiles = []
  global tile_size
  tile_size = width/number_of_columns/2
  global number_of_rows
  number_of_rows = (height/tile_size) * 2 
  global x_offset
  x_offset = (width-tile_size * number_of_columns)/2
  grace_period = 1
  
  # initialise tiles
  for i in range(number_of_rows):
  # set y coordinates (-y so scrolls downwards)
      y = -i * tile_size
      for j in range(number_of_columns):
          # set x coordinate
          x = j * tile_size + x_offset
          if i > grace_period:
            tile_type = random_tile_type()
            tile = Tile(x, y, tile_type)
            tiles.append(tile)
          
  #cat
  global cat_size
  cat_size = tile_size
  global cat
  cat = Cat(cat_size)
  # initial position
  cat.x = width/2
  cat.y = height/2
  
  # display
  global score
  score = 0
  global lives
  lives = 3
  
  # speed
  global lowest_speed
  lowest_speed = 5
  global highest_speed
  highest_speed = 20
  global speed
  speed = lowest_speed

def draw():
   background(0)
   lights()
   
   global score 
   fill(255)
   textSize(tile_size/2.5)
   text("Score: {}".format(score), 10, tile_size/2)
   
   global lives
   text("Lives: {}".format(lives), width-(tile_size*1.5), tile_size/2)

   # set speed but keep within limits
   speed = constrain(score/2, lowest_speed, highest_speed)

   pushMatrix()
   rotateX(radians(50))
   cat.draw_cat()
           
   for tile in tiles:
     if lives >= 1:
        tile.y = tile.y + speed
        tile.draw_tile()
        # check each tile for hits
        if tile.target_hit(cat.x, cat.y):
            score += 1
        if tile.hole_hit(cat.x, cat.y):
            lives -=1
            
   popMatrix()
   recycle_tiles()
   
# recycle tiles off the screen
def recycle_tiles():
    # get y coord of last tile
    y = 0
    global tiles
    for tile in tiles:
        if tile.y < y:
            y = tile.y
        
    # moves tiles to the end (x coord can stay the same)
    new_y = y - tile_size
    for tile in tiles:
    # if off the screen
        if tile.y > height:
            tile.y = new_y
            tile.type = random_tile_type()
     
# generate a random type of tile
def random_tile_type():
    tile_type = standard_tile
    # randomised tile chances
    if random(0, 5) > 4:
        tile_type = hole_tile
    if random(0, 50) > 45:
        tile_type = target_tile
    return tile_type

class Cat:

  def __init__(self, cat_size):
    self.x = 0
    self.y = 0
    self.cat_colour = color(255, 200, 0)
    self.cat_size = cat_size
    self.tail_length = cat_size/4
    self.tail_thickness = cat_size/7
    self.tail = []
    # initialise tail parts using 2 dimensional list
    for i in range (self.tail_length):
        self.tail.append([0, 0])

  def draw_cat(self):
    # draw body in separate matrix so can rotate
    pushMatrix()
    noStroke()
    translate(self.x, self.y)
    rotateX(frameCount/20.0) # roll
    sphereDetail(7)
    fill(self.cat_colour)
    sphere(cat_size * 0.4) 
    popMatrix()

    pushMatrix()
    noStroke()
    # draw head
    translate(self.x, self.y - (cat_size/2))
    sphereDetail(8)
    sphere(self.cat_size * 0.2)
    # draw ears
    # left
    pushMatrix()
    #fill(0, 0, 255)
    translate(-cat_size*0.1, -cat_size*0.05, cat_size*0.15)
    rotateY(0.5)
    rotateX(4)
    sphereDetail(1)
    sphere(cat_size*0.1)
    popMatrix()
    # right
    pushMatrix()
    translate(cat_size*0.1, -cat_size*0.05, cat_size*0.15)
    rotateY(3)
    rotateX(2)
    sphereDetail(1)
    sphere(cat_size*0.1)
    popMatrix()
    #triangle(-size*0.1, -size*0.05, -size*0.05, -size*0.2, -size*0.25, -size*0.3)
    #triangle(size*0.1, -size*0.05, size*0.05, -size*0.20, size*0.25, -size*0.3)
    # draw tail
    sphereDetail(8)
    # tail end
    self.tail[self.tail_length-1][0] = (sin(frameCount * 0.2) * 10)
    self.tail[self.tail_length-1][1] = cat_size/2
    # rest of tail
    for i in range(self.tail_length-1):
        self.tail[i][0] = self.tail[i+1][0]
        self.tail[i][1] = self.tail[i+1][1] + (self.tail_thickness/2)
        pushMatrix()
        #ellipse(tail[i].x, tail[i].y, tailThickness, tailThickness)
        translate(self.tail[i][0], self.tail[i][1])
        sphere(self.tail_thickness)
        popMatrix()
    popMatrix()
    
# class for tiles
class Tile:
    
  global standard_tile_colour
  standard_tile_colour = color(255) # white
  global hole_tile_colour
  hole_tile_colour = color(0) # black
  global hole_hit_tile_colour
  hole_hit_tile_colour = color(255, 0, 0) #red
  global target_tile_colour
  target_tile_colour = color(255, 255, 0) # yellow
  
  def __init__(self, x, y, type):
      self.x = x
      self.y = y
      self.type = type

  def draw_tile(self):
      stroke(0)
      if self.type == standard_tile:
        tile_colour = fill(standard_tile_colour)
      elif self.type == hole_tile:
        tile_colour = fill(hole_tile_colour)
      elif self.type == hole_hit_tile:
        tile_colour = fill(hole_hit_tile_colour)
      elif self.type == target_tile:
        tile_colour = fill(target_tile_colour)
      square(self.x, self.y, tile_size)
  
  def target_hit(self, cat_x, cat_y):
    target_hit = self.hit(cat_x, cat_y) and self.type == target_tile
    if target_hit:
      self.type = standard_tile
    return target_hit

  def hole_hit(self, cat_x, cat_y):
    hole_hit = self.hit(cat_x, cat_y) and self.type == hole_tile
    if hole_hit:
      self.type = hole_hit_tile
    return hole_hit
  
  # check if we hit a tile
  def hit(self, cat_x, cat_y):
      hit = cat_y >= self.y and cat_y <= self.y + tile_size and cat_x >= self.x and cat_x <= self.x + tile_size
      return hit

    
def mouseMoved():
    cat.x = mouseX
    cat.y = mouseY