Here you will find the source code for the Kitty Hero game, as described in the book Beginning Graphics Programming with Processing 3

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.
 import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.view.WindowManager;
import android.os.Bundle;
import android.os.Vibrator;
 
static final int NUMBER_OF_COLUMNS = 5;
static final int GRACE_PERIOD = 1;
 
static final int STANDARD_TILE = 0;
static final int HOLE_TILE = 1;
static final int SEALED_HOLE_TILE = 2;
static final int SPECIAL_TILE = 3;
 
static final int INITIAL_SCORE = 0;
static final int INITIAL_LIVES = 9;
static final int LOWEST_SPEED = 5;
static final int GREATEST_SPEED = 20;
 
static final String SCORE_TEXT = "Score: ";
static final String LIVES_TEXT = "Lives: ";
static final String GAME_OVER_TEXT = "GAME OVER";
static final String END_SCORE_TEXT = "You scored: ";
static final String RESTART_TEXT = "Press To Restart";
static final String START_TEXT = "Press To Start";
static final String GAME_TITLE_1 = "Kitty Hero:";
static final String GAME_TITLE_2 = "The Adventures of";
static final String GAME_TITLE_3 = "Mr Kittenson";
 
 
Context context;
SensorManager manager;
Sensor sensor;
AccelerometerListener listener;
Vibrator vibe;
float ax;
float ay;
float az;
 
int numberOfRows;
int tileSize;
int offsetX;
 
Cat cat;
 
int lives;
int score;
int speed;
 
boolean started;
boolean gameOver;
 
ArrayList<Tile> tiles;
 
void setup() {
   //size(600, 600, P3D);
   fullScreen(P3D);
   orientation(PORTRAIT);
   tileSize = width/NUMBER_OF_COLUMNS/2;
   numberOfRows = (height/tileSize) * 2;
   // set gap at sides
   offsetX = (width-tileSize * NUMBER_OF_COLUMNS)/2;
   // initialise game
   initialise();
   // setup accelerometer
   context = getActivity();
   manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
   sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
   listener = new AccelerometerListener();
   // SENSOR_DELAY_FASTEST
   // SENSOR_DELAY_GAME
   manager.registerListener(listener,
      sensor, SensorManager.SENSOR_DELAY_FASTEST);
   // register vibrator
   vibe = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
}
 
void draw() {
 
   background(0);
 
   if (!started) {
      showStartScreen();
   } else if (gameOver) {
      showGameOver();
   } else {
      play();
   }
}
 
void initialise() {
   lives = INITIAL_LIVES;
   score = INITIAL_SCORE;
   speed = LOWEST_SPEED;
   cat = new Cat(tileSize);
   // initial position
   cat.x = width/2;
   cat.y = height - (height/4);
   // set bounds
   cat.minX = offsetX;
   cat.maxX = width - offsetX;
   cat.minY = tileSize;
   cat.maxY = height-(height/8);
   // initialise tiles
   tiles = new ArrayList<Tile>();
   for (int i = 0; i < numberOfRows; i++) {
      // set y coordinates (-y so scrolls downwards)
      int y = -i * tileSize;
      for (int j = 0; j < NUMBER_OF_COLUMNS; j++) {
         // set x coordinate
         int x = j * tileSize + offsetX;
         int tileType = STANDARD_TILE;
         // allow grace period of normal tiles
         if (i > GRACE_PERIOD) {
            // random tile types
            tileType = generateRandomTileType();
         }
         tiles.add(new Tile(x, y, tileType));
      }
   }
}
 
void startGame() {
   started = true;
   gameOver = false;
}
 
void play() {
   if (lives < 1) {
      gameOver = true;
      vibe.vibrate(500);
   }
 
   lights();
   // scoreboard
   textAlign(LEFT);
   fill(255);
   textSize(tileSize/2.5);
   text(SCORE_TEXT + score, 10, tileSize/2);
   text(LIVES_TEXT + lives, width-(tileSize*1.5), tileSize/2);
 
   // set speed but keep within limits
   speed = constrain(score/2, LOWEST_SPEED, GREATEST_SPEED);
 
   pushMatrix();
   // rotate for 3D
   rotateX(radians(45));
 
   // draw tiles
   stroke(0);
   for (Tile tile : tiles) {
      // move tiles
      tile.y = tile.y + speed; 
      if (tile.type == HOLE_TILE) {
         fill(0);
         // check location of kitty
         if (hitTile(tile.x, tile.y, cat.x, cat.y)) {
            // hit a hole, do something
            lives--;
            vibe.vibrate(100);
            tile.type = SEALED_HOLE_TILE;
         }
      } else if (tile.type == SPECIAL_TILE) {
         fill(255, 255, 0);
         // check location of kitty
            if (hitTile(tile.x, tile.y, cat.x, cat.y)) {
               // hit a special tile, do something
               score++;
               tile.type = STANDARD_TILE;
            }
      } else if (tile.type == SEALED_HOLE_TILE) {
         fill(255, 0, 0);
      } else {
         fill(255);
      }
      rect(tile.x, tile.y, tileSize, tileSize);
   }
   // draw kitty
   cat.draw();
   popMatrix();
   // recycle tiles
   recycleTiles();
}
 
// show game over screen
void showGameOver() {
   fill(167, 0, 174);
   int offset = width/10;
   rect(offset, offset, width-offset*2, height-offset*2);
   fill(0);
   textAlign(CENTER);
   int fontSize = textToFontSize(GAME_OVER_TEXT);
   textSize(fontSize);
   text(GAME_OVER_TEXT, width/2, offset + fontSize);
   fontSize = textToFontSize(END_SCORE_TEXT + score);
   textSize(fontSize);
   text(END_SCORE_TEXT + score, width/2, height/2);
   fontSize = textToFontSize(RESTART_TEXT);
   textSize(fontSize);
   text(RESTART_TEXT, width/2, height - offset - fontSize);
   cat.draw();
}
 
// show start screen
void showStartScreen() {
   fill(167, 0, 174);
   int offset = width/10;
   rect(offset, offset, width-offset*2, height-offset*2);
   fill(0);
   textAlign(CENTER);
   int fontSize = textToFontSize(GAME_TITLE_2);
   textSize(fontSize);
   text(GAME_TITLE_1, width/2, offset + fontSize);
   text(GAME_TITLE_2, width/2, offset + fontSize * 2);
   text(GAME_TITLE_3, width/2, offset + fontSize * 3);
   textSize(textToFontSize(START_TEXT));
   text(START_TEXT, width/2, height - offset - fontSize);
   cat.draw();
}
 
// calculates the font size for a piece of text
int textToFontSize(String text) {
   // work out space per character
   int chars = text.length();
   int availableSpace = width;
   int characterSpace = availableSpace / chars;
   // convert pixels to font size
   int size = (int) (characterSpace * 1.3);
   return size;
}
 
// generate a random type of tile
int generateRandomTileType() {
   int tileType = STANDARD_TILE;
   // randomised tile chances
   if (random(0, 5) > 4) {
      tileType = HOLE_TILE;
   }
   if (random(0, 50) > 45) {
      tileType = SPECIAL_TILE;
   }
   return tileType;
}
 
// recycle tiles off the screen
void recycleTiles() {
   // get y coord of last tile
   int y = 0;
   for (Tile tile : tiles) {
      if (tile.y < y) {
         y = tile.y;
      }
   }
   // moves tiles to the end (x coord can stay the same)
   int newY = y - tileSize;
   for (Tile tile : tiles) {
      // if off the screen
      if (tile.y > height) {
         tile.y = newY;
         tile.type = generateRandomTileType();
      }
   }
}
 
// check if we hit a tile
boolean hitTile(int tileX, int tileY, int locationX, int locationY) {
   return (locationY >= tileY && locationY <= tileY + tileSize
      && locationX >= tileX && locationX <= tileX + tileSize);
}
 
// class for tiles
class Tile {
   int x;
   int y;
   int type;
 
   Tile(int x, int y, int type) {
      this.x = x;
      this.y = y;
      this.type = type;
   }
}
 
// class for drawing kitty
class Cat {
 
   final int tailLength;
   final int tailThickness;
   final color CAT_COLOUR = color(255, 200, 0);
   final int size;
   // tail segments
   PVector[] tail;
   // location
   int x;
   int y;
   // bounds
   int minX;
   int maxX;
   int minY;
   int maxY;
 
   Cat(int size) {
      this.size = size;
      this.tailLength = size/4;
      this.tailThickness = size/7;
      tail = new PVector[tailLength];
      for (int i = 0; i < tailLength; i++) {
         tail[i] = new PVector(0, 0);
      }
   }
 
   void draw() {
      // draw body in separate matrix so can rotate
      pushMatrix();
      noStroke();
      translate(x, y);
      rotateX(frameCount/20.0); // roll
      sphereDetail(7);
      fill(CAT_COLOUR);
      sphere(size * 0.4);
      popMatrix();
 
      pushMatrix();
      noStroke();
      // draw head
      translate(x, y - (size/2));
      sphereDetail(8);
      sphere(size * 0.2);
      // draw ears
      // left
      pushMatrix();
      //fill(0, 0, 255);
      translate(-size*0.1, -size*0.05, size*0.15);
      rotateY(0.5);
      rotateX(4);
      sphereDetail(1);
      sphere(size*0.1);
      popMatrix();
      // right
      pushMatrix();
      translate(size*0.1, -size*0.05, size*0.15);
      rotateY(3);
      rotateX(2);
      sphereDetail(1);
      sphere(size*0.1);
      popMatrix();
      // draw tail
      sphereDetail(8);
      tail[tailLength-1].x = (sin(frameCount * 0.2) * 10);
      tail[tailLength-1].y = size/2;
      for (int i = 0; i < tailLength-1; i++) {
         tail[i].x = tail[i+1].x;
         tail[i].y = tail[i+1].y + (tailThickness/2);
         pushMatrix();
         translate(tail[i].x, tail[i].y);
         sphere(tailThickness);
         popMatrix();
      }
      popMatrix();
   }
}
 
void mousePressed() {
   // ignore if game is playing
   if (gameOver || !started) {
      initialise();
      startGame();
   }
}
 
public void onCreate(Bundle bundle) {
   super.onCreate(bundle); 
   // keep the screen on
   getActivity().getWindow().addFlags(
      WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
 
public void onResume() {
   super.onResume();
   if (manager != null) {
      manager.registerListener(listener,
         sensor, SensorManager.SENSOR_DELAY_FASTEST);
   }
}
 
public void onPause() {
   super.onPause();
   if (manager != null) {
      manager.unregisterListener(listener);
   }
}
 
class AccelerometerListener implements SensorEventListener {
   public void onSensorChanged(SensorEvent event) {
      ax = event.values[0];
      ay = event.values[1];
      az = event.values[2];
 
      if (cat.x - ax > cat.minX && cat.x - ax < cat.maxX) {
         cat.x -= ax;
      }
 
      if (cat.y + ay > cat.minY && cat.y + ay < cat.maxY) {
         cat.y += ay;
      }
   }
 
   public void onAccuracyChanged(Sensor sensor, int accuracy) {
      // do nothing
   }
}