Lab5 CannonGame



Create a mobile game: CannonGame App











CannonGame>res>layout-port

activity_cannon_controller.xml
<?xml version="1.0" encoding="utf-8"?>
<mdad.lab5.CannonView 
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:id="@+id/cannonView"
   android:background="@android:color/transparent"/>


CannonGame>res>values>strings

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">CannonGame</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
   <string name="results_format">Shots fired: %1$d\nTotal time: %2$.1f</string>
   <string name="reset_game">Reset Game</string>
   <string name="win">You win!</string>
   <string name="lose">You lose!</string>
   <string name="time_remaining_format">Time remaining: %.1f seconds</string>

    
</resources>

CannonView .java
package mdad.lab5;

import java.util.HashMap;
import java.util.Map;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.SoundPool;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class CannonView extends SurfaceView implements SurfaceHolder.Callback {

    private CannonThread cannonThread; // controls the game loop
    private Activity activity; // to display Game Over dialog in GUI thread
    private boolean dialogIsDisplayed = false;   
                
    // constants for game play
    public static final int TARGET_PIECES = 7; // sections in the target
    public static final int MISS_PENALTY = 2; // seconds deducted on a miss
    public static final int HIT_REWARD = 3; // seconds added on a hit

    // variables for the game loop and tracking statistics
    private boolean gameOver; // is the game over?
    private double timeLeft; // the amount of time left in seconds
    private int shotsFired; // the number of shots the user has fired
    private double totalElapsedTime; // the number of seconds elapsed

    // variables for the blocker and target
    private CannonModel blocker; // start and end points of the blocker
    private int blockerDistance; // blocker distance from left
    private int blockerBeginning; // blocker distance from top
    private int blockerEnd; // blocker bottom edge distance from top
    private int initialBlockerVelocity; // initial blocker speed multiplier
    private float blockerVelocity; // blocker speed multiplier during game

    private CannonModel target; // start and end points of the target
    private int targetDistance; // target distance from left
    private int targetBeginning; // target distance from top
    private double pieceLength; // length of a target piece
    private int targetEnd; // target bottom's distance from top
    private int initialTargetVelocity; // initial target speed multiplier
    private float targetVelocity; // target speed multiplier during game

    private int lineWidth; // width of the target and blocker
    private boolean[] hitStates; // is each target piece hit?
    private int targetPiecesHit; // number of target pieces hit (out of 7)

    // variables for the cannon and cannonball
    private Point cannonball; // cannonball image's upper-left corner
    private int cannonballVelocityX; // cannonball's x velocity
    private int cannonballVelocityY; // cannonball's y velocity
    private boolean cannonballOnScreen; // is the cannonball on the screen
    private int cannonballRadius; // cannonball radius
    private int cannonballSpeed; // cannonball speed
    private int cannonBaseRadius; // cannon base radius
    private int cannonLength; // cannon barrel length
    private Point barrelEnd; // the endpoint of the cannon's barrel
    private int screenWidth; // width of the screen
    private int screenHeight; // height of the screen

    // constants and variables for managing sounds
    private static final int TARGET_SOUND_ID = 0;
    private static final int CANNON_SOUND_ID = 1;
    private static final int BLOCKER_SOUND_ID = 2;
    private SoundPool soundPool; // plays sound effects
    private Map<Integer, Integer> soundMap; // maps IDs to SoundPool

    // Paint variables used when drawing each item on the screen
    private Paint textPaint; // Paint used to draw text
    private Paint cannonballPaint; // Paint used to draw the cannonball
    private Paint cannonPaint; // Paint used to draw the cannon
    private Paint blockerPaint; // Paint used to draw the blocker
    private Paint targetPaint; // Paint used to draw the target
    private Paint backgroundPaint; // Paint used to clear the drawing area

    // public constructor
    public CannonView(Context context, AttributeSet attrs)
    {
       super(context, attrs); // call super's constructor
       activity = (Activity) context; 
       
       // register SurfaceHolder.Callback listener
       getHolder().addCallback(this); 

       // initialize Lines and points representing game items
       blocker = new CannonModel(); // create the blocker as a Line
       target = new CannonModel(); // create the target as a Line
       cannonball = new Point(); // create the cannonball as a point

       // initialize hitStates as a boolean array
       hitStates = new boolean[TARGET_PIECES];

       // initialize SoundPool to play the app's three sound effects
       soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);

       // create Map of sounds and pre-load sounds
       soundMap = new HashMap<Integer, Integer>(); // create new HashMap
       soundMap.put(TARGET_SOUND_ID,soundPool.load(context, R.raw.target_hit, 1));
         soundMap.put(CANNON_SOUND_ID,soundPool.load(context, R.raw.cannon_fire, 1));
       soundMap.put(BLOCKER_SOUND_ID, soundPool.load(context, R.raw.blocker_hit, 1));

       // construct Paints for drawing text, cannonball, cannon,
       // blocker and target; these are configured in method onSizeChanged
       textPaint = new Paint(); // Paint for drawing text
       cannonPaint = new Paint(); // Paint for drawing the cannon
       cannonballPaint = new Paint(); // Paint for drawing a cannonball
       blockerPaint = new Paint(); // Paint for drawing the blocker
       targetPaint = new Paint(); // Paint for drawing the target
       backgroundPaint = new Paint(); // Paint for drawing the target
    } // end CannonView constructor

    // called by surfaceChanged when the size of the SurfaceView changes,
    // such as when it's first added to the View hierarchy
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
       super.onSizeChanged(w, h, oldw, oldh);

       screenWidth = w; // store the width
       screenHeight = h; // store the height
       cannonBaseRadius = h / 18; // cannon base radius 1/18 screen height
       cannonLength = w / 8; // cannon length 1/8 screen width

       cannonballRadius = w / 36; // cannonball radius 1/36 screen width
       cannonballSpeed = w * 3 / 2; // cannonball speed multiplier

       lineWidth = w / 24; // target and blocker 1/24 screen width

       // configure instance variables related to the blocker
       blockerDistance = w * 5 / 8; // blocker 5/8 screen width from left
       blockerBeginning = h / 8; // distance from top 1/8 screen height
       blockerEnd = h * 3 / 8; // distance from top 3/8 screen height
       initialBlockerVelocity = h / 2; // initial blocker speed multiplier
       blocker.start = new Point(blockerDistance, blockerBeginning);
       blocker.end = new Point(blockerDistance, blockerEnd);

       // configure instance variables related to the target
       targetDistance = w * 7 / 8; // target 7/8 screen width from left
       targetBeginning = h / 8; // distance from top 1/8 screen height
       targetEnd = h * 7 / 8; // distance from top 7/8 screen height
       pieceLength = (targetEnd - targetBeginning) / TARGET_PIECES;
       initialTargetVelocity = -h / 4; // initial target speed multiplier
       target.start = new Point(targetDistance, targetBeginning);
       target.end = new Point(targetDistance, targetEnd);

       // endpoint of the cannon's barrel initially points horizontally
       barrelEnd = new Point(cannonLength, h / 2);

       // configure Paint objects for drawing game elements
       textPaint.setTextSize(w / 20); // text size 1/20 of screen width
       textPaint.setAntiAlias(true); // smoothes the text
       cannonPaint.setStrokeWidth(lineWidth * 1.5f); // set line thickness
       blockerPaint.setStrokeWidth(lineWidth); // set line thickness      
       targetPaint.setStrokeWidth(lineWidth); // set line thickness       
       backgroundPaint.setColor(Color.WHITE); // set background color

       newGame(); // set up and start a new game
    } // end method onSizeChanged

    // reset all the screen elements and start a new game
    public void newGame()
    {
       // set every element of hitStates to false--restores target pieces
       for (int i = 0; i < TARGET_PIECES; ++i)
          hitStates[i] = false;

       targetPiecesHit = 0; // no target pieces have been hit
       blockerVelocity = initialBlockerVelocity; // set initial velocity
       targetVelocity = initialTargetVelocity; // set initial velocity
       timeLeft = 10; // start the countdown at 10 seconds
       cannonballOnScreen = false; // the cannonball is not on the screen
       shotsFired = 0; // set the initial number of shots fired
       totalElapsedTime = 0.0; // set the time elapsed to zero
       blocker.start.set(blockerDistance, blockerBeginning);
       blocker.end.set(blockerDistance, blockerEnd);
       target.start.set(targetDistance, targetBeginning);
       target.end.set(targetDistance, targetEnd);
       
       if (gameOver)
       {
          gameOver = false; // the game is not over
          cannonThread = new CannonThread(getHolder());
          cannonThread.start();
       } // end if
    } // end method newGame

    // called repeatedly by the CannonThread to update game elements
    private void updatePositions(double elapsedTimeMS)
    {
       double interval = elapsedTimeMS / 1000.0; // convert to seconds

       if (cannonballOnScreen) // if there is currently a shot fired
       {
          // update cannonball position
          cannonball.x += interval * cannonballVelocityX;
          cannonball.y += interval * cannonballVelocityY;

          // check for collision with blocker
          if (cannonball.x + cannonballRadius > blockerDistance && 
             cannonball.x - cannonballRadius < blockerDistance &&
             cannonball.y + cannonballRadius > blocker.start.y &&
             cannonball.y - cannonballRadius < blocker.end.y)
          {
             cannonballVelocityX *= -1; // reverse cannonball's direction
             timeLeft -= MISS_PENALTY; // penalize the user

             // play blocker sound
             soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
          } // end if

          // check for collisions with left and right walls
          else if (cannonball.x + cannonballRadius > screenWidth || 
             cannonball.x - cannonballRadius < 0)
             cannonballOnScreen = false; // remove cannonball from screen

          // check for collisions with top and bottom walls
          else if (cannonball.y + cannonballRadius > screenHeight || 
             cannonball.y - cannonballRadius < 0)
             cannonballOnScreen = false; // make the cannonball disappear

          // check for cannonball collision with target
          else if (cannonball.x + cannonballRadius > targetDistance && 
             cannonball.x - cannonballRadius < targetDistance && 
             cannonball.y + cannonballRadius > target.start.y &&
             cannonball.y - cannonballRadius < target.end.y)
          {
             // determine target section number (0 is the top)
             int section = 
                (int) ((cannonball.y - target.start.y) / pieceLength);
             
             // check if the piece hasn't been hit yet
             if ((section >= 0 && section < TARGET_PIECES) && 
                !hitStates[section])
             {
                hitStates[section] = true; // section was hit
                cannonballOnScreen = false; // remove cannonball
                timeLeft += HIT_REWARD; // add reward to remaining time

                // play target hit sound
                soundPool.play(soundMap.get(TARGET_SOUND_ID), 1, 1, 1, 0, 1f);

                // if all pieces have been hit
                if (++targetPiecesHit == TARGET_PIECES)
                {
                   cannonThread.setRunning(false);
                   showGameOverDialog(R.string.win); // show winning dialog
                   gameOver = true; // the game is over
                } // end if
             } // end if
          } // end else if
       } // end if

       // update the blocker's position
       double blockerUpdate = interval * blockerVelocity;
       blocker.start.y += blockerUpdate;
       blocker.end.y += blockerUpdate;

       // update the target's position
       double targetUpdate = interval * targetVelocity;
       target.start.y += targetUpdate;
       target.end.y += targetUpdate;

       // if the blocker hit the top or bottom, reverse direction
       if (blocker.start.y < 0 || blocker.end.y > screenHeight)
          blockerVelocity *= -1;

       // if the target hit the top or bottom, reverse direction
       if (target.start.y < 0 || target.end.y > screenHeight)
          targetVelocity *= -1;

       timeLeft -= interval; // subtract from time left

       // if the timer reached zero
       if (timeLeft <= 0.0)
       {
          timeLeft = 0.0;
          gameOver = true; // the game is over
          cannonThread.setRunning(false);
          showGameOverDialog(R.string.lose); // show the losing dialog
       } // end if
    } // end method updatePositions

    // fires a cannonball
    public void fireCannonball(MotionEvent event)
    {
       if (cannonballOnScreen) // if a cannonball is already on the screen
          return; // do nothing

       double angle = alignCannon(event); // get the cannon barrel's angle

       // move the cannonball to be inside the cannon
       cannonball.x = cannonballRadius; // align x-coordinate with cannon
       cannonball.y = screenHeight / 2; // centers ball vertically

       // get the x component of the total velocity
       cannonballVelocityX = (int) (cannonballSpeed * Math.sin(angle));

       // get the y component of the total velocity
       cannonballVelocityY = (int) (-cannonballSpeed * Math.cos(angle));
       cannonballOnScreen = true; // the cannonball is on the screen
       ++shotsFired; // increment shotsFired

       // play cannon fired sound
       soundPool.play(soundMap.get(CANNON_SOUND_ID), 1, 1, 1, 0, 1f);
    } // end method fireCannonball

    // aligns the cannon in response to a user touch
    public double alignCannon(MotionEvent event)
    {
       // get the location of the touch in this view
       Point touchPoint = new Point((int) event.getX(), (int) event.getY());

       // compute the touch's distance from center of the screen
       // on the y-axis
       double centerMinusY = (screenHeight / 2 - touchPoint.y);

       double angle = 0; // initialize angle to 0

       // calculate the angle the barrel makes with the horizontal
       if (centerMinusY != 0) // prevent division by 0
          angle = Math.atan((double) touchPoint.x / centerMinusY);

       // if the touch is on the lower half of the screen
       if (touchPoint.y > screenHeight / 2)
          angle += Math.PI; // adjust the angle

       // calculate the endpoint of the cannon barrel
       barrelEnd.x = (int) (cannonLength * Math.sin(angle));
       barrelEnd.y = 
          (int) (-cannonLength * Math.cos(angle) + screenHeight / 2);

       return angle; // return the computed angle
    } // end method alignCannon

    // draws the game to the given Canvas
    public void drawGameElements(Canvas canvas)
    {
       // clear the background
       canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), 
          backgroundPaint);
       
       // display time remaining
       canvas.drawText(getResources().getString(
          R.string.time_remaining_format, timeLeft), 30, 50, textPaint);

       // if a cannonball is currently on the screen, draw it
       if (cannonballOnScreen)
          canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius,
             cannonballPaint);

       // draw the cannon barrel
       canvas.drawLine(0, screenHeight / 2, barrelEnd.x, barrelEnd.y,
          cannonPaint);

       // draw the cannon base
       canvas.drawCircle(0, (int) screenHeight / 2,
          (int) cannonBaseRadius, cannonPaint);

       // draw the blocker
       canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x,
          blocker.end.y, blockerPaint);

       Point currentPoint = new Point(); // start of current target section

       // initialize curPoint to the starting point of the target
       currentPoint.x = target.start.x;
       currentPoint.y = target.start.y;

       // draw the target
       for (int i = 1; i <= TARGET_PIECES; ++i)
       {
          // if this target piece is not hit, draw it
          if (!hitStates[i - 1])
          {
             // alternate coloring the pieces yellow and blue
             if (i % 2 == 0)
                targetPaint.setColor(Color.YELLOW);
             else
                targetPaint.setColor(Color.BLUE);
             
             canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,
                (int) (currentPoint.y + pieceLength), targetPaint);
          } // end if
          
          // move curPoint to the start of the next piece
          currentPoint.y += pieceLength;
       } // end for
    } // end method drawGameElements

    // display an AlertDialog when the game ends
    private void showGameOverDialog(int messageId)
    {
       // create a dialog displaying the given String
       final AlertDialog.Builder dialogBuilder = 
          new AlertDialog.Builder(getContext());
       dialogBuilder.setTitle(getResources().getString(messageId));
       dialogBuilder.setCancelable(false);

       // display number of shots fired and total time elapsed
       dialogBuilder.setMessage(getResources().getString(
          R.string.results_format, shotsFired, totalElapsedTime));

       //Part 2
       // add the additional targetPiecesHit variable



       // =============================================

       dialogBuilder.setPositiveButton(R.string.reset_game,
          new DialogInterface.OnClickListener()
          {
             // called when "Reset Game" Button is pressed
             @Override
             public void onClick(DialogInterface dialog, int which)
             {
                dialogIsDisplayed = false;
                newGame(); // set up and start a new game
             } // end method onClick
          } // end anonymous inner class
       ); // end call to setPositiveButton

       activity.runOnUiThread(
          new Runnable() {
             public void run()
             {
                dialogIsDisplayed = true;
                dialogBuilder.show(); // display the dialog
             } // end method run
          } // end Runnable
       ); // end call to runOnUiThread
    } // end method showGameOverDialog

    // stops the game
    public void stopGame()
    {
       if (cannonThread != null)
          cannonThread.setRunning(false);
    } // end method stopGame

    // releases resources; called by CannonGame's onDestroy method 
    public void releaseResources()
    {
       soundPool.release(); // release all resources used by the SoundPool
       soundPool = null; 
    } // end method releaseResources

    // called when surface changes size
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format,
       int width, int height)
    {
    } // end method surfaceChanged

    // called when surface is first created
    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
       if (!dialogIsDisplayed)
       {
          cannonThread = new CannonThread(holder);
          cannonThread.setRunning(true);
          cannonThread.start(); // start the game loop thread
       } // end if
    } // end method surfaceCreated

    // called when the surface is destroyed
    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
       // ensure that thread terminates properly
       boolean retry = true;
       cannonThread.setRunning(false);
       
       while (retry)
       {
          try
          {
             cannonThread.join();
             retry = false;
          } // end try
          catch (InterruptedException e)
          {
          } // end catch
       } // end while
    } // end method surfaceDestroyed
    
    // Thread subclass to control the game loop
    private class CannonThread extends Thread
    {
       private SurfaceHolder surfaceHolder; // for manipulating canvas
       private boolean threadIsRunning = true; // running by default
       
       // initializes the surface holder
       public CannonThread(SurfaceHolder holder)
       {
          surfaceHolder = holder;
          setName("CannonThread");
       } // end constructor
       
       // changes running state
       public void setRunning(boolean running)
       {
          threadIsRunning = running;
       } // end method setRunning
       
       // controls the game loop
       @Override
       public void run()
       {
          Canvas canvas = null; // used for drawing
          long previousFrameTime = System.currentTimeMillis(); 
         
          while (threadIsRunning)
          {
             try
             {
                canvas = surfaceHolder.lockCanvas(null);               
                
                // lock the surfaceHolder for drawing
                synchronized(surfaceHolder)
                {
                   long currentTime = System.currentTimeMillis();
                   double elapsedTimeMS = currentTime - previousFrameTime;
                   totalElapsedTime += elapsedTimeMS / 1000.00; 
                   updatePositions(elapsedTimeMS); // update game state
                   drawGameElements(canvas); // draw 
                   previousFrameTime = currentTime; // update previous time
                } // end synchronized block
             } // end try
             finally
             {
                if (canvas != null) 
                   surfaceHolder.unlockCanvasAndPost(canvas);
             } // end finally
          } // end while
       } // end method run
    } // end nested class CannonThread
 } // end class CannonView
Part 2



(1) To increase the playtime to 30 sec:    
In CannonView class:
newGame() method:
timeLeft = 30; // change the countdown to 30 seconds instead of 10

(2) To add a hits value that will show the number
of targets destroyed at the end of the game:

In CannonView class:
showGameOverDialog() method:
// add the additional targetPiecesHit variable
dialogBuilder.setMessage(getResources().getString(R.string.results_format, shotsFired, totalElapsedTime, targetPiecesHit));
In the strings.xml file: 
Add the additional highlighted format string
<string name="results_format">Shots fired:%1$d\nTotal time: %2$.1f\nHits: %3$d \nDeveloped by Your Name </string>
CannonController.java

package mdad.lab5;

import android.app.Activity;
import android.media.AudioManager;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;

public class CannonController extends Activity {

 
    private GestureDetector gestureDetector; // listens for double taps
    private CannonView cannonView;
    SimpleOnGestureListener gestureListener = new SimpleOnGestureListener()
        {
           // called when the user double taps the screen
           public boolean onDoubleTap(MotionEvent e)
           {
              cannonView.fireCannonball(e); 
              return true; 
           } // 
        }; // end gestureListener

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_cannon_controller);
  
  cannonView = (CannonView) findViewById(R.id.cannonView); // get the CannonView
  gestureDetector = new GestureDetector(this, gestureListener);
  setVolumeControlStream(AudioManager.STREAM_MUSIC);

 }

 public void onPause()    {
        super.onPause(); 
        cannonView.stopGame(); 
     }
 protected void onDestroy() {
        super.onDestroy();
        cannonView.releaseResources();
     }
 public boolean onTouchEvent(MotionEvent event)  {
        int action = event.getAction();
        // the user user touched the screen or dragged along the screen
        if (action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_MOVE)
        {
           cannonView.alignCannon(event); 
        } 
        return gestureDetector.onTouchEvent(event);
 }
 
 
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.cannon_controller, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }
}


CannonModel.java

package mdad.lab5;
import android.graphics.Point;
public class CannonModel {
    public Point start; // starting Point
    public Point end; // ending Point

    // default constructor initializes Points to (0, 0)
    public CannonModel() {
       start = new Point(0, 0); // start Point
       end = new Point(0, 0); // end Point
    } // end constructor
}// end class




DEPLOY to Android Phones

Go to Bin folder as shown below
Copy and paste the apk file e.g. CanonGame.apk into your Android Phone Via USB Cable

OR email the apk file to your email account and install it to your phone




Enabling 'Unknown Sources' on Android

The exact names of the settings entries listed below may vary slightly from device to device, but the overall process should be very similar. To begin, head to your phone's main settings menu, then look for an entry titled either "Security" or "Lock screen and security."




(1) Security menu on stock Android, (2) Lock screen and security menu on Samsung devices

From this menu, simply tick the box or toggle the switch next to the "Unknown sources" entry, then press "OK" on the popup.