com.nappking.movietimesup.GameFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.nappking.movietimesup.GameFragment.java

Source

/**
 * Copyright 2012 Facebook
 *
 * 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.
 */

package com.nappking.movietimesup;

import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import org.json.JSONException;
import org.json.JSONObject;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.facebook.FacebookRequestError;
import com.facebook.Request;
import com.facebook.Response;
import com.facebook.Session;
import com.facebook.android.friendsmash.R;
import com.facebook.model.GraphObject;
import com.facebook.model.GraphUser;

/**
 *  Fragment shown once a user starts playing a game
 */
public class GameFragment extends Fragment {

    private static final Pair[] CELEBS = { Pair.create("Einstein", "drawable/nonfriend_1"),
            Pair.create("Xzibit", "drawable/nonfriend_2"), Pair.create("Goldsmith", "drawable/nonfriend_3"),
            Pair.create("Sinatra", "drawable/nonfriend_4"), Pair.create("George", "drawable/nonfriend_5"),
            Pair.create("Jacko", "drawable/nonfriend_6"), Pair.create("Rick", "drawable/nonfriend_7"),
            Pair.create("Keanu", "drawable/nonfriend_8"), Pair.create("Arnie", "drawable/nonfriend_9"),
            Pair.create("Jean-Luc", "drawable/nonfriend_10"), };

    // Tag used when logging messages
    private static final String TAG = GameFragment.class.getSimpleName();

    // FrameLayout as the container for the game
    private FrameLayout gameFrame;

    // FrameLayout of the progress container to show the spinner
    private FrameLayout progressContainer;

    // TextView for the Smash Player title
    private TextView smashPlayerNameTextView;

    // TextView for the score
    private TextView scoreTextView;

    // LinearyLayout containing the lives images
    private LinearLayout livesContainer;

    // Icon width for the friend images to smash
    private int iconWidth;

    // Screen Dimensions
    private int screenWidth;
    private int screenHeight;

    // Handler for putting messages on Main UI thread from background threads periodically
    private Handler timerHandler;

    // Handler for putting messages on Main UI thread from background thread after fetching images
    private Handler uiHandler;

    // Runnable task used to produce images to fly across the screen
    private Runnable fireImageTask = null;

    // Boolean indicating whether images have started firing
    private boolean imagesStartedFiring = false;

    // Index of the friend to smash (in the social game)
    private int friendToSmashIndex = -1;

    // Index of the celeb to smash (in the non-social game)
    private int celebToSmashIndex = -1;

    // ID of the friend to smash (if passed in as an attribute)
    private String friendToSmashIDProvided = null;

    // Name of the friend to smash
    private String friendToSmashFirstName = null;

    // Bitmap of the friend to smash
    private Bitmap friendToSmashBitmap;

    // Score for the user
    private int score = 0;

    // Lives the user has remaining
    private int lives = 3;

    // Boolean set to true if first image has been fired
    private boolean firstImageFired = false;

    // Boolean indicating that the first image to be fired is pending (i.e. a Request is
    // in the process of executing in a background thread to fetch the images / information)
    private boolean firstImagePendingFiring = false;

    // List of UserImageView objects created and visible
    private ArrayList<UserImageView> userImageViews = new ArrayList<UserImageView>();

    @SuppressWarnings("unused")
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setRetainInstance(true);

        // Instantiate the handlers
        timerHandler = new Handler();
        uiHandler = new Handler();

        // Get the friend to smash bitmap and name
        if (FriendSmashApplication.IS_SOCIAL) {
            // User is logged into FB, so choose a random FB friend to smash
            friendToSmashIndex = getRandomFriendIndex();
        } else {
            // User is not logged into FB, so choose a random celebrity to smash
            celebToSmashIndex = getRandomCelebIndex();
        }
    }

    @SuppressWarnings({ "deprecation" })
    @TargetApi(13)
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_game, parent, false);

        gameFrame = (FrameLayout) v.findViewById(R.id.gameFrame);
        progressContainer = (FrameLayout) v.findViewById(R.id.progressContainer);
        smashPlayerNameTextView = (TextView) v.findViewById(R.id.smashPlayerNameTextView);
        scoreTextView = (TextView) v.findViewById(R.id.scoreTextView);
        livesContainer = (LinearLayout) v.findViewById(R.id.livesContainer);

        // Set the progressContainer as invisible by default
        progressContainer.setVisibility(View.INVISIBLE);

        // Set the icon width (for the images to be smashed)
        setIconWidth(getResources().getDimensionPixelSize(R.dimen.icon_width));

        // Set the screen dimensions
        WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        if (Build.VERSION.SDK_INT >= 13) {
            Point size = new Point();
            display.getSize(size);
            setScreenWidth(size.x);
            setScreenHeight(size.y);
        } else {
            setScreenWidth(display.getWidth());
            setScreenHeight(display.getHeight());
        }

        // Always keep the Action Bar hidden
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            getActivity().getActionBar().hide();
        }

        // Instantiate the fireImageTask for future fired images
        fireImageTask = new Runnable() {
            public void run() {
                spawnImage(false);
            }
        };

        // Refresh the score board
        setScore(getScore());

        // Refresh the lives
        setLives(getLives());

        // Note: Images will start firing in the onResume method below

        return v;
    }

    // Sets the name of the player to smash in the top left TextView
    @SuppressWarnings("unused")
    private void setSmashPlayerNameTextView() {
        // Set the Smash Player Name title
        if (FriendSmashApplication.IS_SOCIAL) {
            // User is logged into FB ...
            if (friendToSmashFirstName == null) {
                // A name hasn't been set yet (i.e. it hasn't been fetched through a passed in id, so
                // a random friend needs to be used instead, so fetch this name
                friendToSmashFirstName = ((FriendSmashApplication) getActivity().getApplication())
                        .getFriend(friendToSmashIndex).getFirstName();
            }
            smashPlayerNameTextView.setText("Smash " + friendToSmashFirstName + " !");
        } else {
            // User is not logged into FB ...
            smashPlayerNameTextView.setText("Smash " + CELEBS[celebToSmashIndex].first + " !");
        }
    }

    // Select a random friend to smash
    private int getRandomFriendIndex() {
        Random randomGenerator = new Random(System.currentTimeMillis());
        int friendIndex = randomGenerator
                .nextInt(((FriendSmashApplication) getActivity().getApplication()).getFriends().size());
        return friendIndex;
    }

    // Select a random celebrity to smash (in the non-social game) or avoid smashing (in the social game)
    private int getRandomCelebIndex() {
        Random randomGenerator = new Random(System.currentTimeMillis());
        int celebIndex = randomGenerator.nextInt(CELEBS.length);
        return celebIndex;
    }

    // Set the image on the UserImageView to the specified bitmap of the user's friend and fire it
    private void setFriendImageAndFire(UserImageView imageView, Bitmap friendBitmap, boolean extraImage) {
        imageView.setImageBitmap(friendBitmap);

        if (extraImage) {
            // If this is an extra image, give it an extra point when smashed
            imageView.setExtraPoints(1);
        }

        fireImage(imageView, extraImage);
    }

    // Set the image on the UserImageView to the celebrity and fire it
    private void setCelebImageAndFire(UserImageView imageView, int celebIndex, boolean extraImage) {
        int imageResource = getResources().getIdentifier((String) CELEBS[celebIndex].second, null,
                getActivity().getPackageName());

        Drawable image = getResources().getDrawable(imageResource);
        imageView.setImageDrawable(image);

        fireImage(imageView, extraImage);
    }

    // Fire the UserImageView and setup the timer to start another image shortly (as long as the image that
    // is fired isn't an extra image)
    private void fireImage(final UserImageView imageView, boolean extraImage) {
        // Fire image
        imageView.setupAndStartAnimations(iconWidth, iconWidth, screenWidth, screenHeight, new AnimatorListener() {
            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!imageView.isWrongImageSmashed()) {
                    if (imageView.getVisibility() == View.VISIBLE && imageView.shouldSmash()
                            && !imageView.isVoid()) {
                        // Image is still visible, so user didn't smash it and they should have done (and it isn't void), so decrement the lives by one
                        setLives(getLives() - 1);
                    }

                    // Only hide this if the wrong image has not been smashed (otherwise, other logic will be run and image still needs to be shown)
                    hideAndRemove(imageView);
                }
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationStart(Animator animation) {
            }
        });

        if (!extraImage) {
            // If this isn't an extra image spawned, fire another image shortly
            fireAnotherImage();
        }

        // By this point, all network calls would have executed and the first image has fired with the next lined up
        // , so set firstImagePendingFiring to false
        firstImagePendingFiring = false;
    }

    // If this UserImageView is currently visible, hide it and remove it from the GameFragment view
    // and the list storing all UserImageViews
    private void hideAndRemove(UserImageView userImageView) {
        if (userImageView.getVisibility() == View.VISIBLE) {
            // Ensure it is hidden
            userImageView.setVisibility(View.GONE);
        }

        // Remove the userImageView from the gameFrame
        getGameFrame().removeView(userImageView);

        // Remove it from the userImageViews List in the gameFragment
        getUserImageViews().remove(userImageView);
    }

    // Fire another image shortly
    private void fireAnotherImage() {
        // Fire another image shortly ...
        if (fireImageTask != null) {
            timerHandler.postDelayed(fireImageTask, 700);
        }
    }

    // Called when the first image should be fired (only called during onResume)
    // If the game has been deep linked into (i.e. a user has clicked on a feed post or request in
    // Facebook), then fetch the specific user that should be smashed
    @SuppressWarnings("unused")
    private void fireFirstImage() {
        if (FriendSmashApplication.IS_SOCIAL) {
            // Get any bundle parameters there are
            Bundle bundle = getActivity().getIntent().getExtras();

            String requestID = null;
            String userID = null;
            if (bundle != null) {
                requestID = bundle.getString("request_id");
                userID = bundle.getString("user_id");
            }

            if (requestID != null && friendToSmashIDProvided == null) {
                // Deep linked from request
                // Make a request to get a specific user to smash if they haven't been fetched already

                // Show the spinner for this part
                progressContainer.setVisibility(View.VISIBLE);

                // Get and set the id of the friend to smash and start firing the image
                fireFirstImageWithRequestID(requestID);
            } else if (userID != null && friendToSmashIDProvided == null) {
                // Deep linked from feed post
                // Make a request to get a specific user to smash if they haven't been fetched already

                // Show the spinner for this part
                progressContainer.setVisibility(View.VISIBLE);

                // Get and set the id of the friend to smash and start firing the image
                fireFirstImageWithUserID(userID);
            } else {
                // requestID is null, userID is null or friendToSmashIDProvided is already set,
                // so use the randomly generated friend of the user or the already set friendToSmashIDProvided
                // So set the smashPlayerNameTextView text and hide the progress spinner as there is nothing to fetch
                progressContainer.setVisibility(View.INVISIBLE);
                setSmashPlayerNameTextView();

                // Now you're ready to fire the first image
                spawnImage(false);
            }
        } else {
            // Non-social, so set the smashPlayerNameTextView text and hide the progress spinner as there is nothing to fetch
            progressContainer.setVisibility(View.INVISIBLE);
            setSmashPlayerNameTextView();

            // Now you're ready to fire the first image
            spawnImage(false);
        }
    }

    // Fires the first image in a game with a given request id  (from a user deep linking by clicking
    // on a request from a specific user)
    private void fireFirstImageWithRequestID(String requestID) {
        final Session session = Session.getActiveSession();
        Request requestIDGraphPathRequest = Request.newGraphPathRequest(session, requestID, new Request.Callback() {

            @Override
            public void onCompleted(Response response) {
                FacebookRequestError error = response.getError();
                if (error != null) {
                    Log.e(FriendSmashApplication.TAG, error.toString());
                    closeAndHandleError(error);
                } else if (session == Session.getActiveSession()) {
                    if (response != null) {
                        // Extract the user id from the response
                        GraphObject graphObject = response.getGraphObject();
                        JSONObject fromObject = (JSONObject) graphObject.getProperty("from");
                        try {
                            friendToSmashIDProvided = fromObject.getString("id");
                        } catch (JSONException e) {
                            Log.e(FriendSmashApplication.TAG, e.toString());
                            closeAndShowError(getResources().getString(R.string.network_error));
                        }

                        // With the user id, fetch and set their name
                        Request userGraphPathRequest = Request.newGraphPathRequest(session, friendToSmashIDProvided,
                                new Request.Callback() {

                                    @Override
                                    public void onCompleted(Response response) {
                                        FacebookRequestError error = response.getError();
                                        if (error != null) {
                                            Log.e(FriendSmashApplication.TAG, error.toString());
                                            closeAndHandleError(error);
                                        } else if (session == Session.getActiveSession()) {
                                            if (response != null) {
                                                // Extract the user name from the response
                                                friendToSmashFirstName = response.getGraphObjectAs(GraphUser.class)
                                                        .getFirstName();
                                            }
                                            if (friendToSmashFirstName != null) {
                                                // If the first name of the friend to smash has been set, set the text in the smashPlayerNameTextView
                                                // and hide the progress spinner now that the user's details have been fetched
                                                progressContainer.setVisibility(View.INVISIBLE);
                                                setSmashPlayerNameTextView();

                                                // Now you're ready to fire the first image
                                                spawnImage(false);
                                            }
                                        }
                                    }
                                });
                        Request.executeBatchAsync(userGraphPathRequest);
                    }
                }
            }
        });
        Request.executeBatchAsync(requestIDGraphPathRequest);
    }

    // Fires the first image in a game with a given user id  (from a user deep linking by clicking
    // on a feed post from a specific user)
    private void fireFirstImageWithUserID(String userID) {
        final Session session = Session.getActiveSession();

        // With the user id, fetch and set their name, then start the firing of images
        friendToSmashIDProvided = userID;
        Request userGraphPathRequest = Request.newGraphPathRequest(session, friendToSmashIDProvided,
                new Request.Callback() {

                    @Override
                    public void onCompleted(Response response) {
                        FacebookRequestError error = response.getError();
                        if (error != null) {
                            Log.e(FriendSmashApplication.TAG, error.toString());
                            closeAndHandleError(error);
                        } else if (session == Session.getActiveSession()) {
                            if (response != null) {
                                // Extract the user name from the response
                                friendToSmashFirstName = response.getGraphObjectAs(GraphUser.class).getFirstName();
                            }
                            if (friendToSmashFirstName != null) {
                                // If the first name of the friend to smash has been set, set the text in the smashPlayerNameTextView
                                // and hide the progress spinner now that the user's details have been fetched
                                progressContainer.setVisibility(View.INVISIBLE);
                                setSmashPlayerNameTextView();

                                // Now you're ready to fire the first image
                                spawnImage(false);
                            }
                        }
                    }
                });
        Request.executeBatchAsync(userGraphPathRequest);
    }

    // Spawn a new UserImageView, set its bitmap (fetch it from Facebook if it hasn't already been fetched)
    // and fire it once the image has been set (and fetched if appropriate)
    @SuppressWarnings("unused")
    private void spawnImage(final boolean extraImage) {
        // Instantiate Random Generator
        Random randomGenerator = new Random(System.currentTimeMillis());

        // 1 in every 5 images should be a celebrity the user should not smash - calculate that here
        // Unless it is the first image fired, in which case it should always be the smashable image
        boolean shouldSmash = true;
        if (firstImageFired) {
            if (randomGenerator.nextInt(5) == 4 && firstImageFired) {
                shouldSmash = false;
            }
        } else if (!firstImageFired) {
            shouldSmash = true;
            firstImageFired = true;
        }

        // Create a new ImageView with a user to smash
        final UserImageView userImageView = (new UserImageView(getActivity(), shouldSmash));
        userImageView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (userImageView.shouldSmash()) {
                    // Smashed the right image ...

                    // Increment the score
                    setScore(getScore() + 1 + userImageView.getExtraPoints());

                    // Hide the userImageView
                    v.setVisibility(View.GONE);

                    // Remove it from the userImageViews List in this GameFragment
                    getUserImageViews().remove(v);
                } else {
                    // Smashed the wrong image ...
                    wrongImageSmashed(userImageView);
                }
                return false;
            }
        });
        userImageView.setLayoutParams(new LinearLayout.LayoutParams(iconWidth, iconWidth));
        gameFrame.addView(userImageView);
        userImageViews.add(userImageView);

        // Set the bitmap of the userImageView ...
        if (userImageView.shouldSmash()) {
            // The user should smash this image, so set the correct image
            if (FriendSmashApplication.IS_SOCIAL) {
                // User is logged into FB ...
                if (friendToSmashBitmap != null) {
                    // Bitmap for the friend to smash has already been retrieved, so use this
                    setFriendImageAndFire(userImageView, friendToSmashBitmap, extraImage);
                } else {
                    // Otherwise, the Bitmap for the friend to smash hasn't been retrieved, so retrieve it and set it

                    // Show the spinner while retrieving
                    progressContainer.setVisibility(View.VISIBLE);

                    // If a friend has been passed in, use that attribute, otherwise use the random friend that has been selected
                    final String friendToSmashID = friendToSmashIDProvided != null ? friendToSmashIDProvided
                            : ((FriendSmashApplication) getActivity().getApplication())
                                    .getFriend(friendToSmashIndex).getId();

                    // Fetch the bitmap and fire the image
                    fetchFriendBitmapAndFireImages(userImageView, friendToSmashID, extraImage);
                }
            } else {
                // User is not logged into FB ...
                setCelebImageAndFire(userImageView, celebToSmashIndex, extraImage);
            }
        } else {
            // The user should not smash this image, so set it to a random celebrity (but not the one being shown if it's the non-social game)
            int randomCelebToSmashIndex;
            do {
                randomCelebToSmashIndex = randomGenerator.nextInt(CELEBS.length);
            } while (randomCelebToSmashIndex == celebToSmashIndex);
            setCelebImageAndFire(userImageView, randomCelebToSmashIndex, extraImage);
        }
    }

    // Logic when the user smashes this image, but it turns out to be the wrong image - i.e.
    // it's shouldSmash boolean is false
    private void wrongImageSmashed(final UserImageView userImageView) {
        // Set this flag for checking in the animation ended logic
        userImageView.setWrongImageSmashed(true);

        // Stop all movement (not rotation) animations for this UserImageView
        userImageView.stopMovementAnimations();

        // Stop all animations for all other visible UserImageViews (and therefore hide them)
        hideAllUserImageViewsExcept(userImageView);

        // Scale the image up
        userImageView.scaleUp(new AnimatorListener() {
            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // Cancel rotation and exit to home screen
                userImageView.stopRotationAnimation();
                setLives(0);
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationStart(Animator animation) {
            }
        });

        // Ensure this UserImageView is in front
        getGameFrame().bringChildToFront(userImageView);
    }

    private void fetchFriendBitmapAndFireImages(final UserImageView userImageView, final String friendToSmashID,
            final boolean extraImage) {
        AsyncTask.execute(new Runnable() {
            public void run() {
                URL bitmapURL;
                try {
                    bitmapURL = new URL("http://graph.facebook.com/" + friendToSmashID + "/picture?width="
                            + iconWidth + "&height=" + iconWidth);
                    friendToSmashBitmap = BitmapFactory.decodeStream(bitmapURL.openConnection().getInputStream());
                } catch (Exception e) {
                    // Unknown error
                    Log.e(FriendSmashApplication.TAG, e.toString());
                }

                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // Hide the spinner after retrieving
                        progressContainer.setVisibility(View.INVISIBLE);

                        if (friendToSmashBitmap != null) {
                            setFriendImageAndFire(userImageView, friendToSmashBitmap, extraImage);

                            // Also set the lastFriendSmashedID in the application
                            ((FriendSmashApplication) getActivity().getApplication())
                                    .setLastFriendSmashedID(friendToSmashID);
                        } else {
                            closeAndShowError(getResources().getString(R.string.error_fetching_friend_bitmap));
                        }
                    }
                });
            }
        });
    }

    // Close the game and show the specified error to the user
    private void closeAndShowError(String error) {
        Bundle bundle = new Bundle();
        bundle.putString("error", error);

        Intent i = new Intent();
        i.putExtras(bundle);

        getActivity().setResult(Activity.RESULT_CANCELED, i);
        getActivity().finish();
    }

    // Close the game and show the specified FacebookRequestException to the user
    private void closeAndHandleError(FacebookRequestError error) {
        // Store the FacebookRequestError in the FacebookApplication before closing out this GameFragment so that
        // it is shown to the user once exited
        ((FriendSmashApplication) getActivity().getApplication()).setGameFragmentFBRequestError(error);

        getActivity().setResult(Activity.RESULT_CANCELED);
        getActivity().finish();
    }

    // Hide all the UserImageViews currently on display except the one specified
    // Called when the user has smashed the wrong image so that this is displayed large
    void hideAllUserImageViewsExcept(UserImageView userImageView) {
        // Stop new animations
        timerHandler.removeCallbacks(fireImageTask);

        // Stop animations on all existing visible UserImageViews (which will hide them automatically)
        Iterator<UserImageView> userImageViewsIterator = userImageViews.iterator();
        while (userImageViewsIterator.hasNext()) {
            UserImageView currentUserImageView = (UserImageView) userImageViewsIterator.next();
            if (!currentUserImageView.equals(userImageView)) {
                currentUserImageView.setVisibility(View.GONE);
            }
        }
    }

    // Mark all the existing visible UserImageViews as void (called when the game is paused)
    private void markAllUserImageViewsAsVoid() {
        Iterator<UserImageView> userImageViewsIterator = userImageViews.iterator();
        while (userImageViewsIterator.hasNext()) {
            UserImageView currentUserImageView = (UserImageView) userImageViewsIterator.next();
            currentUserImageView.setVoid(true);
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        // Stop the firing images
        stopTheFiringImages();
    }

    @SuppressWarnings("unused")
    @Override
    public void onResume() {
        super.onResume();

        // Stop any firing images (even though this is called in onPause, there might be new firing images
        // if they were pending while onPause was called
        stopTheFiringImages();

        if (!imagesStartedFiring) {
            // Fire first image
            if (FriendSmashApplication.IS_SOCIAL) {
                // Only fire for the social game if there isn't a first image pending firing
                if (!firstImagePendingFiring) {
                    // ... and also set the firstImagePendingFiring to true - will be set back
                    // to false later once the images have actually started firing (i.e. all network
                    // calls have executed) - note, this is only relevant for the social version
                    firstImagePendingFiring = true;
                    imagesStartedFiring = true;
                    fireFirstImage();
                }
            } else {
                imagesStartedFiring = true;
                fireFirstImage();
            }
        }
    }

    // Stop the firing of all images (and mark the existing ones as void) - called when the game is paused
    private void stopTheFiringImages() {
        // Mark all existing in flight UserImageViews as void (so they don't affect the user's lives once landed)
        markAllUserImageViewsAsVoid();

        // Stop new animations and indicate that images have not started firing
        timerHandler.removeCallbacks(fireImageTask);
        imagesStartedFiring = false;
    }

    // Get the current score
    int getScore() {
        return score;
    }

    // Set the score and if the score is divisible by 10, spawn more images ...
    // ... the higher the score, the more images that will be spawned
    void setScore(int score) {
        this.score = score;

        // Update the scoreTextView
        scoreTextView.setText("Score: " + score);

        // If they start scoring well, spawn more images
        if (score > 0 && score % 10 == 0) {
            // Every multiple of 10, spawn extra images ...
            for (int i = 0; i < score / 20; i++) {
                spawnImage(true);
            }
        }
    }

    // Get the user's number of lives they have remaining
    int getLives() {
        return lives;
    }

    // Set the number of lives that the user has, update the display appropriately and
    // end the game if they have run out of lives
    void setLives(int lives) {
        this.lives = lives;

        if (getActivity() != null) {
            // Update the livesContainer
            livesContainer.removeAllViews();
            for (int i = 0; i < lives; i++) {
                ImageView heartImageView = new ImageView(getActivity());
                heartImageView.setImageResource(R.drawable.heart_red);
                livesContainer.addView(heartImageView);
            }

            if (lives <= 0) {
                // User has no lives left, so end the game, passing back the score
                Bundle bundle = new Bundle();
                bundle.putInt("score", getScore());

                Intent i = new Intent();
                i.putExtras(bundle);

                getActivity().setResult(Activity.RESULT_OK, i);
                getActivity().finish();
            }
        }
    }

    /* Standard Getters & Setters */

    public int getIconWidth() {
        return iconWidth;
    }

    public void setIconWidth(int iconWidth) {
        this.iconWidth = iconWidth;
    }

    public int getScreenWidth() {
        return screenWidth;
    }

    public void setScreenWidth(int screenWidth) {
        this.screenWidth = screenWidth;
    }

    public int getScreenHeight() {
        return screenHeight;
    }

    public void setScreenHeight(int screenHeight) {
        this.screenHeight = screenHeight;
    }

    public FrameLayout getGameFrame() {
        return gameFrame;
    }

    public ArrayList<UserImageView> getUserImageViews() {
        return userImageViews;
    }
}