com.google.android.apps.santatracker.games.matching.MemoryMatchFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.apps.santatracker.games.matching.MemoryMatchFragment.java

Source

/*
 * Copyright (C) 2015 Google Inc. All Rights Reserved.
 *
 * 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.google.android.apps.santatracker.games.matching;

import com.google.android.apps.santatracker.R;
import com.google.android.apps.santatracker.games.common.ImmersiveModeHelper;
import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
import com.google.android.apps.santatracker.games.gumball.Utils;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.Callback;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Fragment that contains the memory match game.
 */
public class MemoryMatchFragment extends Fragment implements OnClickListener, AnimationListener, Callback {

    /**
     * Drawables for all card faces.
     */
    private static final Integer[] CARD_FACE_DRAWABLES = new Integer[] { R.drawable.mmg_card_ball,
            R.drawable.mmg_card_balloon, R.drawable.mmg_card_beachball, R.drawable.mmg_card_candle,
            R.drawable.mmg_card_globe, R.drawable.mmg_card_gumball, R.drawable.mmg_card_penguin,
            R.drawable.mmg_card_rabbit, R.drawable.mmg_card_reindeer, R.drawable.mmg_card_snowman,
            R.drawable.mmg_card_tree, R.drawable.mmg_card_trophy };
    /**
     * Drawables for all card face cloaks (background color).
     */
    private static final Integer[] CARD_CLOAK_DRAWABLES = new Integer[] { R.drawable.mmg_card_cloak_blue_dark,
            R.drawable.mmg_card_cloak_blue_light, R.drawable.mmg_card_cloak_orange,
            R.drawable.mmg_card_cloak_purple, R.drawable.mmg_card_cloak_red, R.drawable.mmg_card_cloak_orange };

    /**
     * Current game level.
     */
    private int mLevelNumber = 1;

    /**
     * Number of correct moves required for this level.
     */
    private int mCorrectMovesRequired = 0;

    /**
     * Count of correct moves in this level so far.
     */
    private int mCurrentCorrectMoves = 0;

    /**
     * Total score of the game so far.
     */
    private int mMatchScore = 0;

    /**
     * Count of the number of wrong selections in the level so far.
     */
    private int mWrongAnswers = 0;

    /**
     * First card that has been selected and is visible.
     */
    private View mVisibleCard1 = null;

    /**
     * Second card that has been selected and is visible.
     */
    private View mVisibleCard2 = null;

    /**
     * First card that was visible and is being animated to become hidden again.
     */
    private View mHiddenCard1;

    /**
     * Second card that was visible and is being animated to become hidden again.
     */
    private View mHiddenCard2;

    /**
     * Views that represent the cards (doors) on screen.
     */
    private View[] mViewCard = new View[12];

    /**
     * List of card faces.
     * This list is shuffled before each level.
     */
    private List<Integer> mCardFaceIds = Arrays.asList(CARD_FACE_DRAWABLES);

    /**
     * List of card cloaks (backgrounds).
     * This list is shuffled before each level.
     */
    private List<Integer> mCardCloakIds = Arrays.asList(CARD_CLOAK_DRAWABLES);

    /**
     * Time left in the game in milliseconds.
     */
    private long mTimeLeftInMillis = MatchingGameConstants.MATCH_INIT_TIME;
    /**
     * Countdown timer refresh interval in milliseconds.
     */
    private long mCountDownInterval = 1000;
    /**
     * Countdown timer that drives the game logic.
     */
    private GameCountdown mCountDownTimer = null;
    /**
     * Flag to indicate the state of this Fragment and stop the game correctly when the countdown
     * expires.
     */
    private boolean wasPaused = false;

    private Animation mAnimationRightPaneSlideOut;
    private Animation mAnimationLeftPaneSlideOut;
    private Animation mAnimationLeftPaneSlideIn;
    private Animation mAnimationRightPaneSlideIn;
    private Animation mAnimationScaleLevelDown;
    private Animation mAnimationLevelFadeOut;
    private Animation mAnimationLevelScaleUp;
    private Animation mAnimationPlayAgainBackground;
    private Animation mAnimationPlayAgainMain;
    private Animation mAnimationCardCover;
    private TranslateAnimation mAnimationSnowman;
    private Animation mAnimationTimerAlpha;
    private TranslateAnimation mAnimationSnowmanBack;
    private AnimationSet mAnimationSetSnowman;

    private CircleView mEndLevelCircle;
    private TextView mScoreText;
    private LevelTextView mLevelNumberText;

    private int mSoundDoorOpen = -1;
    private int mSoundDoorClose = -1;
    private int mSoundMatchWrong = -1;
    private int mSoundMatchRight = -1;
    private int mSoundBeep = -1;
    private int mSoundGameOver = -1;
    private MediaPlayer mBackgroundMusic;
    private SoundPool mSoundPool;

    private TextView mTimerTextView;
    private View mViewPlayAgainBackground;
    private View mViewPlayAgainMain;
    private TextView mTextPlayAgainScore;
    private TextView mTextPlayAgainLevel;
    private ImageView mButtonPlay;
    private ImageView mButtonPause;
    private ImageButton mButtonBigPlay;
    private Button mPlayAgainBtn;
    private ImageButton mButtonCancelBar;
    private ImageButton mButtonMenu;
    private ImageView mViewInstructions;
    private View mViewPauseOverlay;
    private View mViewBonusSnowman;
    private AnimationDrawable mInstructionDrawable;
    private ImageView mViewGPlusSignIn;
    private View mLayoutGPlus;

    /**
     * Handler that dismisses the game instructions when the game is started for the first time.
     */
    private Handler mDelayHandler = new Handler();

    /**
     * Preferences that store whether the game instructions have been viewed.
     */
    private SharedPreferences mPreferences;

    /**
     * Indicates whether the screen is clickable.
     * It is disabled when a full screen animation is in progress at the end of the level or at the
     * end of the game.
     */
    private boolean isClickable = true;

    /**
     * Create a new instance of this fragment.
     */
    public static MemoryMatchFragment newInstance() {
        return new MemoryMatchFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_memory_match, container, false);
        rootView.setKeepScreenOn(true);

        // Below ICS, display a special, simplified background for the entire fragment
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            rootView.findViewById(R.id.match_score_layout)
                    .setBackgroundResource(R.drawable.score_background_gingerbread);
        }

        // Initialise the sound pool and all sound effects
        mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
        mSoundDoorOpen = mSoundPool.load(getActivity(), R.raw.mmg_open_door_3, 1);
        mSoundDoorClose = mSoundPool.load(getActivity(), R.raw.mmg_close_door, 1);
        mSoundMatchWrong = mSoundPool.load(getActivity(), R.raw.mmg_wrong, 1);
        mSoundMatchRight = mSoundPool.load(getActivity(), R.raw.mmg_right, 1);
        mSoundGameOver = mSoundPool.load(getActivity(), R.raw.gameover, 1);
        mSoundBeep = mSoundPool.load(getActivity(), R.raw.mmg_open_door_2, 1);

        // Set up all animations.
        loadAnimations();

        // G+ sign-in views
        mViewGPlusSignIn = (ImageView) rootView.findViewById(R.id.gplus_button);
        mViewGPlusSignIn.setOnClickListener(this);
        mLayoutGPlus = rootView.findViewById(R.id.play_again_gplus);
        mLayoutGPlus.setVisibility(View.GONE);

        // 'Play again' screen views
        mTextPlayAgainScore = (TextView) rootView.findViewById(R.id.play_again_score);
        mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
        mTextPlayAgainLevel = (TextView) rootView.findViewById(R.id.play_again_level);
        mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));
        mViewPlayAgainBackground = rootView.findViewById(R.id.play_again_bkgrd);
        mViewPlayAgainMain = rootView.findViewById(R.id.play_again_main);
        mPlayAgainBtn = (Button) rootView.findViewById(R.id.play_again_btn);
        mPlayAgainBtn.setOnClickListener(this);

        // Level, countdown and score views at the bottom of the screen
        mTimerTextView = (TextView) rootView.findViewById(R.id.match_timer);
        mLevelNumberText = (LevelTextView) rootView.findViewById(R.id.card_end_level_number);
        mLevelNumberText.setVisibility(View.GONE);
        mScoreText = (TextView) rootView.findViewById(R.id.match_score);
        mScoreText.setText(String.valueOf(mMatchScore));

        // End of level animated circle
        mEndLevelCircle = (CircleView) rootView.findViewById(R.id.card_end_level_circle);
        mEndLevelCircle.setVisibility(View.GONE);

        // The snowman that is animated as a bonus when the player is particularly awesome
        mViewBonusSnowman = rootView.findViewById(R.id.match_snowman);

        // 'Pause' screen views
        mButtonMenu = (ImageButton) rootView.findViewById(R.id.main_menu_button);
        mButtonMenu.setOnClickListener(this);
        mButtonMenu.setVisibility(View.GONE);
        mButtonPlay = (ImageView) rootView.findViewById(R.id.match_play_button);
        mButtonPlay.setOnClickListener(this);
        mButtonPlay.setVisibility(View.GONE);
        mButtonPause = (ImageView) rootView.findViewById(R.id.match_pause_button);
        mButtonPause.setOnClickListener(this);
        mButtonPause.setVisibility(View.VISIBLE);
        mViewPauseOverlay = rootView.findViewById(R.id.match_pause_overlay);
        mViewPauseOverlay.setVisibility(View.GONE);
        mButtonBigPlay = (ImageButton) rootView.findViewById(R.id.match_big_play_button);
        mButtonBigPlay.setOnClickListener(this);
        mButtonCancelBar = (ImageButton) rootView.findViewById(R.id.match_cancel_bar);
        mButtonCancelBar.setOnClickListener(this);
        mButtonCancelBar.setVisibility(View.GONE);

        // Playing cards (doors)
        mViewCard[0] = rootView.findViewById(R.id.card_position_1);
        mViewCard[1] = rootView.findViewById(R.id.card_position_2);
        mViewCard[2] = rootView.findViewById(R.id.card_position_3);
        mViewCard[3] = rootView.findViewById(R.id.card_position_4);
        mViewCard[4] = rootView.findViewById(R.id.card_position_5);
        mViewCard[5] = rootView.findViewById(R.id.card_position_6);
        mViewCard[6] = rootView.findViewById(R.id.card_position_7);
        mViewCard[7] = rootView.findViewById(R.id.card_position_8);
        mViewCard[8] = rootView.findViewById(R.id.card_position_9);
        mViewCard[9] = rootView.findViewById(R.id.card_position_10);
        mViewCard[10] = rootView.findViewById(R.id.card_position_11);
        mViewCard[11] = rootView.findViewById(R.id.card_position_12);

        // Display the instructions if they haven't been seen by the player yet.
        mPreferences = getActivity().getSharedPreferences(MatchingGameConstants.PREFERENCES_FILENAME,
                Context.MODE_PRIVATE);
        if (!mPreferences.getBoolean(MatchingGameConstants.MATCH_INSTRUCTIONS_VIEWED, false)) {
            // Instructions haven't been viewed yet. Construct an AnimationDrawable with instructions.
            mInstructionDrawable = new AnimationDrawable();
            mInstructionDrawable.addFrame(getResources().getDrawable(R.drawable.instructions_touch_1), 300);
            mInstructionDrawable.addFrame(getResources().getDrawable(R.drawable.instructions_touch_2), 300);
            mInstructionDrawable.setOneShot(false);
            mViewInstructions = (ImageView) rootView.findViewById(R.id.instructions);
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                mViewInstructions.setImageResource(R.drawable.instructions_touch_1);
            } else {
                mViewInstructions.setImageDrawable(mInstructionDrawable);
                mInstructionDrawable.start();
            }
            // Set a timer to hide the instructions after 2 seconds
            mViewInstructions.postDelayed(new StartGameDelay(), 2000);
        } else {
            //Instructions have already been viewed. Start the first level.
            setUpLevel();
        }

        return rootView;
    }

    /**
     * Load and initialise all animations required for the game.
     */
    private void loadAnimations() {
        mAnimationTimerAlpha = new AlphaAnimation(0.0f, 1.0f);
        mAnimationTimerAlpha.setDuration(1000);
        mAnimationTimerAlpha.setRepeatMode(Animation.REVERSE);
        mAnimationTimerAlpha.setRepeatCount(Animation.INFINITE);

        mAnimationPlayAgainBackground = AnimationUtils.loadAnimation(getActivity(), R.anim.play_again_bkgrd_anim);
        mAnimationPlayAgainBackground.setFillAfter(true);
        mAnimationPlayAgainBackground.setAnimationListener(this);
        mAnimationCardCover = AnimationUtils.loadAnimation(getActivity(), R.anim.card_answer_flash);
        mAnimationCardCover.setFillAfter(true);
        mAnimationPlayAgainMain = AnimationUtils.loadAnimation(getActivity(), R.anim.play_again_main_anim);
        mAnimationPlayAgainMain.setFillAfter(true);
        mAnimationPlayAgainMain.setAnimationListener(this);
        // Special bonus animation to play if the player is particularly awesome.
        mAnimationSetSnowman = new AnimationSet(true);
        mAnimationSnowman = new TranslateAnimation(150, 0, 150, 0);
        mAnimationSnowman.setDuration(1000);
        mAnimationSetSnowman.addAnimation(mAnimationSnowman);
        mAnimationSnowmanBack = new TranslateAnimation(0, 150, 0, 150);
        mAnimationSnowmanBack.setDuration(1000);
        mAnimationSnowmanBack.setStartOffset(1500);
        mAnimationSnowmanBack.setAnimationListener(this);
        mAnimationSetSnowman.addAnimation(mAnimationSnowmanBack);
        mAnimationSetSnowman.setAnimationListener(this);

        mAnimationRightPaneSlideOut = AnimationUtils.loadAnimation(getActivity(), android.R.anim.slide_out_right);
        mAnimationRightPaneSlideOut.setFillAfter(true);
        mAnimationLeftPaneSlideOut = AnimationUtils.loadAnimation(getActivity(), R.anim.left_pane_slide_out);
        mAnimationLeftPaneSlideOut.setFillAfter(true);
        mAnimationLeftPaneSlideIn = AnimationUtils.loadAnimation(getActivity(), android.R.anim.slide_in_left);
        mAnimationLeftPaneSlideIn.setFillAfter(true);
        mAnimationRightPaneSlideIn = AnimationUtils.loadAnimation(getActivity(), R.anim.right_pane_slide_in);
        mAnimationRightPaneSlideIn.setFillAfter(true);
        mAnimationScaleLevelDown = AnimationUtils.loadAnimation(getActivity(), R.anim.scale_level_anim_down);
        mAnimationScaleLevelDown.setAnimationListener(this);
        mAnimationLevelFadeOut = AnimationUtils.loadAnimation(getActivity(), R.anim.level_fade_out_anim);
        mAnimationLevelFadeOut.setAnimationListener(this);
        mAnimationLevelScaleUp = AnimationUtils.loadAnimation(getActivity(), R.anim.scale_up_level_anim);
        mAnimationLevelScaleUp.setAnimationListener(this);
        mAnimationRightPaneSlideOut.setAnimationListener(this);
        mAnimationLeftPaneSlideOut.setAnimationListener(this);
    }

    /**
     * Runnable that stars the game after the instructions have been viewed.
     * It hides the instructions, marks them as viewed and starts the game.
     */
    private class StartGameDelay implements Runnable {

        @Override
        public void run() {
            // Start the first level.
            setUpLevel();

            // Hide the instructions.
            mInstructionDrawable.stop();
            mViewInstructions.setVisibility(View.GONE);
            // Mark the instructions as 'viewed'.
            Editor edit = mPreferences.edit();
            edit.putBoolean(MatchingGameConstants.MATCH_INSTRUCTIONS_VIEWED, true);
            edit.commit();
        }

    }

    public void onSignInSucceeded() {
        setSignInButtonVisibility(false);
    }

    public void onSignInFailed() {

    }

    @Override
    public void onResume() {
        super.onResume();
        isClickable = true;
        if (wasPaused && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
            mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
            mCountDownTimer.start();
            wasPaused = false;
        }
        loadBackgroundMusic();
        updateSignInButtonVisibility();
    }

    /**
     * Toggles visibility of the G+ sign in layout if the user is not already signed in.
     */
    private void setSignInButtonVisibility(boolean show) {
        mLayoutGPlus.setVisibility(show && !Utils.isSignedIn(this) ? View.VISIBLE : View.GONE);
    }

    private void updateSignInButtonVisibility() {
        if (mLayoutGPlus.getVisibility() == View.VISIBLE && Utils.isSignedIn(this)) {
            setSignInButtonVisibility(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        pauseGame();
        stopBackgroundMusic();
        if (mCountDownTimer != null && mViewPauseOverlay.getVisibility() != View.VISIBLE) {
            mCountDownTimer.cancel();
            wasPaused = true;
        }
    }

    private void stopBackgroundMusic() {
        if (mBackgroundMusic != null) {
            mBackgroundMusic.stop();
            mBackgroundMusic.release();
            mBackgroundMusic = null;
        }
    }

    private void loadBackgroundMusic() {
        mBackgroundMusic = MediaPlayer.create(getActivity(), R.raw.santatracker_musicloop);
        mBackgroundMusic.setLooping(true);
        mBackgroundMusic.setVolume(.1f, .1f);
        mBackgroundMusic.start();
    }

    /**
     * Starts the next level.
     * Shuffles the cards, sets up the views and starts the countdown for the next level.
     */
    private void setUpLevel() {
        mCurrentCorrectMoves = 0;
        mWrongAnswers = 0;
        if (mCountDownTimer != null) {
            mCountDownTimer.cancel();
        }

        // Display the level number
        mTextPlayAgainLevel.setText(String.valueOf(mLevelNumber));

        // Lock all doors, unlock them individually per level later
        for (View card : mViewCard) {
            setUpLockedCard(card);
        }

        if (mLevelNumber > 1) {
            // Add the 'next level' bonus time
            mTimeLeftInMillis += MatchingGameConstants.MATCH_ADD_TIME_NEXT_LEVEL;
        }

        int pairsRequired = Math.min(mLevelNumber, 5) + 1;
        mCorrectMovesRequired = pairsRequired;
        ArrayList<MemoryCard> memoryCards = MemoryCard.getGameCards(pairsRequired * 2, mCardFaceIds, mCardCloakIds);
        int[] cardSlots;

        if (mLevelNumber == 1) {
            cardSlots = new int[] { 2, 3, 8, 9 };
        } else if (mLevelNumber == 2) {
            cardSlots = new int[] { 0, 1, 2, 3, 4, 5 };
        } else if (mLevelNumber == 3) {
            cardSlots = new int[] { 1, 2, 3, 4, 7, 8, 9, 10 };
        } else if (mLevelNumber == 4) {
            cardSlots = new int[] { 0, 1, 2, 3, 4, 5, 7, 8, 9, 10 };
        } else {
            cardSlots = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
        }
        for (int i = 0; i < cardSlots.length; i++) {
            setUpMemoryCard(mViewCard[cardSlots[i]], memoryCards.get(i));
        }

        // Start the countdown for the new level
        mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
        mCountDownTimer.start();
    }

    /**
     * Sets the card displayed by this view to the 'locked' state.
     */
    private void setUpLockedCard(View view) {
        view.setOnClickListener(null);
        view.findViewById(R.id.card_locked).setVisibility(View.VISIBLE);
        view.findViewById(R.id.card_cloak).setVisibility(View.GONE);
        view.findViewById(R.id.card_frame).setVisibility(View.GONE);
        view.findViewById(R.id.card_image).setVisibility(View.GONE);
        view.findViewById(R.id.card_pane_right).clearAnimation();
        view.findViewById(R.id.card_pane_left).clearAnimation();
        view.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
        view.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
        view.findViewById(R.id.card_cover).setVisibility(View.GONE);
    }

    /**
     * Sets the viewCard that displays a card to show the face and cloaking indicated by the
     * {@link com.google.android.apps.santatracker.games.matching.MemoryCard}.
     */
    private void setUpMemoryCard(View viewCard, MemoryCard card) {
        viewCard.setOnClickListener(this);
        card.mView = viewCard;
        viewCard.setTag(card);
        viewCard.findViewById(R.id.card_locked).setVisibility(View.GONE);
        viewCard.findViewById(R.id.card_frame).setVisibility(View.VISIBLE);
        ((ImageView) viewCard.findViewById(R.id.card_cloak)).setImageResource(card.mCardCloakId);
        viewCard.findViewById(R.id.card_cloak).setVisibility(View.VISIBLE);
        ((ImageView) viewCard.findViewById(R.id.card_image)).setImageResource(card.mCardImageId);
        viewCard.findViewById(R.id.card_image).setVisibility(View.VISIBLE);
        viewCard.findViewById(R.id.card_pane_right).clearAnimation();
        viewCard.findViewById(R.id.card_pane_left).clearAnimation();
        viewCard.findViewById(R.id.card_pane_left).setVisibility(View.VISIBLE);
        viewCard.findViewById(R.id.card_pane_right).setVisibility(View.VISIBLE);
        viewCard.findViewById(R.id.card_cover).setVisibility(View.INVISIBLE);
    }

    /**
     * Plays a sound unveils the given card.
     */
    private void showCard(View view) {
        mSoundPool.play(mSoundDoorOpen, 1, 1, 0, 0, 1.0f);
        view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideOut);

        view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideOut);
    }

    /**
     * Plays a sound and hides the given card.
     */
    private void hideCard(View view) {
        mSoundPool.play(mSoundDoorClose, 1, 1, 0, 0, 1.0f);
        view.findViewById(R.id.card_pane_left).startAnimation(mAnimationLeftPaneSlideIn);
        view.findViewById(R.id.card_pane_right).startAnimation(mAnimationRightPaneSlideIn);
    }

    @Override
    public void onClick(View view) {
        // Check if the cards are not currently clickable and skip if necessary.
        if (view.getTag() != null && isClickable) {
            // A card has been clicked.
            onCardClick(view);
        } else if (view.equals(mPlayAgainBtn)) {
            // The 'play again' button has been clicked. Stop the music and restart.
            stopBackgroundMusic();
            loadBackgroundMusic();

            // Reset the game state.
            resetGameState();

            // Reset the UI and clear all animations.
            mScoreText.setText(String.valueOf(mMatchScore));
            mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
            mViewPlayAgainBackground.clearAnimation();
            mViewPlayAgainMain.clearAnimation();
            mViewPlayAgainBackground.setVisibility(View.GONE);
            mViewPlayAgainMain.setVisibility(View.GONE);
            mButtonMenu.setVisibility(View.GONE);
            setSignInButtonVisibility(false);
        } else if (view.equals(mButtonPause)) {
            // Pause button.
            pauseGame();
        } else if (view.equals(mButtonPlay) || view.equals(mButtonBigPlay)) {
            // Play button, resume the game.
            resumeGame();
        } else if (view.equals(mButtonCancelBar) || view.equals(mButtonMenu)) {
            // Exit the game.
            exit();
        } else if (view.equals(mViewGPlusSignIn)) {
            // Start sign-in flow.
            PlayGamesActivity activity = Utils.getPlayGamesActivity(this);
            if (activity != null) {
                activity.startSignIn();
            }
        }
    }

    private void resetGameState() {
        mLevelNumber = 1;
        mTimeLeftInMillis = MatchingGameConstants.MATCH_INIT_TIME;
        setUpLevel();
        mMatchScore = 0;
    }

    /**
     * Handles onClick events for views that represent cards.
     * Unveils the card and checks for a match if another card has already been unveiled.
     */
    private void onCardClick(View view) {
        MemoryCard card1 = (MemoryCard) view.getTag();
        if (mVisibleCard1 != null && mVisibleCard2 != null) {
            // Two cards are already unveiled, hide them both
            hideCard(mVisibleCard1);
            hideCard(mVisibleCard2);
            mVisibleCard2.setOnClickListener(this);
            mVisibleCard1.setOnClickListener(this);
            mVisibleCard1 = view;
            mVisibleCard2 = null;
            mVisibleCard1.setOnClickListener(null);
            showCard(mVisibleCard1);
        } else if (mVisibleCard1 != null && mVisibleCard2 == null) {
            // One card is already unveiled and a second one has been selected
            MemoryCard card2 = (MemoryCard) mVisibleCard1.getTag();

            if (card1.mCardImageId == card2.mCardImageId) {
                // The second card matches the face of the first one - we have a winner!
                mVisibleCard2 = view;
                mVisibleCard2.setOnClickListener(null);
                mVisibleCard1.setOnClickListener(null);
                showCard(view);
                if (mVisibleCard1.findViewById(R.id.card_cover).getVisibility() != View.GONE) {
                    // Play an animation to flash the background of both cards in green
                    mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
                    mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.GREEN);
                    mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
                    mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
                    mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
                    mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
                    mAnimationCardCover.setAnimationListener(this);
                    mHiddenCard1 = mVisibleCard1;
                    mHiddenCard2 = mVisibleCard2;
                    mVisibleCard1.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
                    mVisibleCard2.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);

                    mVisibleCard2 = null;
                    mVisibleCard1 = null;

                    // Add the cards to the tally of correct cards
                    mCurrentCorrectMoves++;
                    increaseScoreMatch();

                    // Check if this level is finished
                    checkNextLevel();
                }
            } else {
                // The second card does not match the first one - this is not a match.
                mSoundPool.play(mSoundMatchWrong, 1, 1, 0, 0, 1.0f);
                mWrongAnswers++;
                mVisibleCard2 = view;
                mVisibleCard2.setOnClickListener(null);
                showCard(mVisibleCard2);
                // Play an animation to flash the background of both cards in red
                mVisibleCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
                mVisibleCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.RED);
                mVisibleCard1.findViewById(R.id.card_cover).clearAnimation();
                mVisibleCard2.findViewById(R.id.card_cover).clearAnimation();
                mVisibleCard1.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
                mVisibleCard2.findViewById(R.id.card_cover).setVisibility(View.VISIBLE);
                mAnimationCardCover.setAnimationListener(null);
                mVisibleCard1.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
                mVisibleCard2.findViewById(R.id.card_cover).startAnimation(mAnimationCardCover);
            }
        } else {
            // This is the first card that has been unveiled.
            mVisibleCard1 = view;
            mVisibleCard1.setOnClickListener(null);
            showCard(mVisibleCard1);
        }
    }

    /**
     * Advances the game to the next level if all matching pairs have been unveiled.
     */
    private void checkNextLevel() {
        if (mCurrentCorrectMoves >= mCorrectMovesRequired) {
            // Increment the level count
            increaseScoreLevel();
            mLevelNumber++;
            mLevelNumberText.setLevelNumber(mLevelNumber);
            // Start the 'next level' animation after a short delay.
            mDelayHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mLevelNumberText.startAnimation(mAnimationLevelScaleUp);
                    mEndLevelCircle.startAnimation(mAnimationScaleLevelDown);
                    setUpLevel();
                }
            }, 750);
        }
    }

    private void exit() {
        getActivity().finish();
    }

    private void resumeGame() {
        mButtonPause.setVisibility(View.VISIBLE);
        mButtonPlay.setVisibility(View.GONE);
        mCountDownTimer = new GameCountdown(mTimeLeftInMillis, mCountDownInterval);
        mCountDownTimer.start();
        mViewPauseOverlay.setVisibility(View.GONE);
        mButtonCancelBar.setVisibility(View.GONE);
        if (Utils.hasKitKat()) {
            ImmersiveModeHelper.setImmersiveSticky(getActivity().getWindow());
        }
    }

    /**
     * CountDownTimer that handles the game timing logic of the memory match game.
     */
    public class GameCountdown extends CountDownTimer {

        private Boolean animationStarted = false;

        public GameCountdown(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }

        @Override
        public void onFinish() {
            // When the countdown is over, end the game.
            mTimerTextView.clearAnimation();
            animationStarted = false;
            mTimerTextView.setTextColor(Color.WHITE);
            mTimerTextView.setTypeface(Typeface.DEFAULT);
            if (mViewPlayAgainBackground.getVisibility() != View.VISIBLE && !wasPaused) {
                submitScore(MatchingGameConstants.LEADERBOARDS_MATCH, mMatchScore);
                stopBackgroundMusic();

                // Show the 'play again' screen.
                mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
                mViewPlayAgainBackground.startAnimation(mAnimationPlayAgainBackground);
                mViewPlayAgainMain.startAnimation(mAnimationPlayAgainMain);
                mViewPlayAgainBackground.setVisibility(View.VISIBLE);
                mViewPlayAgainMain.setVisibility(View.VISIBLE);
                mButtonMenu.setVisibility(View.VISIBLE);
                setSignInButtonVisibility(true);

                mSoundPool.play(mSoundGameOver, .2f, .2f, 0, 0, 1.0f);
            }
        }

        @Override
        public void onTick(long millisUntilFinished) {

            long seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished);
            if (seconds >= 6) {
                animationStarted = false;
                mTimerTextView.clearAnimation();
                mTimerTextView.setTypeface(Typeface.DEFAULT);
                mTimerTextView.setTextColor(Color.WHITE);
            } else if (!animationStarted) {
                // Start flashing the countdown time
                animationStarted = true;
                mTimerTextView.setTypeface(Typeface.DEFAULT_BOLD);
                mTimerTextView.setTextColor(Color.RED);
                mTimerTextView.clearAnimation();
                mTimerTextView.startAnimation(mAnimationTimerAlpha);
            }

            // Update the displayed countdown time
            mTimeLeftInMillis = millisUntilFinished;
            mTimerTextView.setText(String.format("%d:%02d", TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished),
                    seconds - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished))));

        }

    }

    /**
     * Increases the current score when a match is found.
     * The score is based on the current level.
     */
    private void increaseScoreMatch() {
        mSoundPool.play(mSoundMatchRight, 1, 1, 0, 0, 1.0f);
        mMatchScore += (50 * (Math.pow(1.1, mLevelNumber - 1)));
        mScoreText.setText(String.valueOf(mMatchScore));
        mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
    }

    /**
     * Increases the current score when advancing to the next level.
     */
    private void increaseScoreLevel() {
        mMatchScore += (500 * (Math.pow(1.1, mLevelNumber - 1)));
        if (mLevelNumber > 2 && mWrongAnswers == 0) {
            // Show an amazing bonus animation if this the player is particularly skillful ;)
            mViewBonusSnowman.startAnimation(mAnimationSnowman);
        }
        mScoreText.setText(String.valueOf(mMatchScore));
        mTextPlayAgainScore.setText(String.valueOf(mMatchScore));
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        // The game is clickable again now that the animation has ended
        isClickable = true;

        if (animation == mAnimationScaleLevelDown) {
            // After the scale level down animation, fade out the end level circle
            mLevelNumberText.startAnimation(mAnimationLevelFadeOut);
            mEndLevelCircle.startAnimation(mAnimationLevelFadeOut);
        } else if (animation == mAnimationLevelFadeOut) {
            // Hide the end level circle after the animation has finished
            mEndLevelCircle.clearAnimation();
            mLevelNumberText.clearAnimation();
            mLevelNumberText.setVisibility(View.GONE);
            mEndLevelCircle.setVisibility(View.GONE);
        } else if (animation == mAnimationSetSnowman) {
            mViewBonusSnowman.clearAnimation();
            mViewBonusSnowman.setVisibility(View.GONE);
        } else if (animation == mAnimationCardCover) {
            // Reset the state and animations of both cards after they have been hidden again
            mHiddenCard1.clearAnimation();
            mHiddenCard2.clearAnimation();

            mHiddenCard1.findViewById(R.id.card_pane_right).clearAnimation();
            mHiddenCard1.findViewById(R.id.card_pane_left).clearAnimation();
            mHiddenCard2.findViewById(R.id.card_pane_right).clearAnimation();
            mHiddenCard2.findViewById(R.id.card_pane_left).clearAnimation();
            mHiddenCard1.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
            mHiddenCard1.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
            mHiddenCard2.findViewById(R.id.card_pane_right).setVisibility(View.GONE);
            mHiddenCard2.findViewById(R.id.card_pane_left).setVisibility(View.GONE);
            mHiddenCard1.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
            mHiddenCard2.findViewById(R.id.card_cover).setBackgroundColor(Color.TRANSPARENT);
            mHiddenCard1.findViewById(R.id.card_cover).setVisibility(View.GONE);
            mHiddenCard2.findViewById(R.id.card_cover).setVisibility(View.GONE);
        }
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    }

    @Override
    public void onAnimationStart(Animation animation) {
        // Mark the game as not clickable when an animation is in progress
        isClickable = false;

        // Mark the correct views as visible before the start of an animation.
        if (animation == mAnimationScaleLevelDown) {
            mEndLevelCircle.setVisibility(View.VISIBLE);
            mLevelNumberText.setVisibility(View.VISIBLE);
        } else if (animation == mAnimationPlayAgainBackground) {
            mViewPlayAgainBackground.setVisibility(View.VISIBLE);
        } else if (animation == mAnimationPlayAgainMain) {
            mViewPlayAgainMain.setVisibility(View.VISIBLE);
            setSignInButtonVisibility(true);
        } else if (animation == mAnimationSetSnowman) {
            mViewBonusSnowman.setVisibility(View.VISIBLE);
            mViewBonusSnowman.postDelayed(new Runnable() {

                @Override
                public void run() {
                    mSoundPool.play(mSoundBeep, .5f, .5f, 0, 0, 1.0f);
                }
            }, 800);
        }
    }

    @Override
    public void invalidateDrawable(Drawable who) {
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {
    }

    /**
     * Pauses the game when the back key is pressed.
     */
    public void onBackKeyPressed() {
        if (mViewPlayAgainMain.getVisibility() == View.VISIBLE) {
            exit();
        } else {
            if (mButtonPause.getVisibility() != View.GONE) {// check if already handled
                pauseGame();
            } else {
                resumeGame();
            }
        }
    }

    private void pauseGame() {
        mButtonPause.setVisibility(View.GONE);
        mButtonPlay.setVisibility(View.VISIBLE);
        if (mCountDownTimer != null) {
            mCountDownTimer.cancel();
        }
        mViewPauseOverlay.setVisibility(View.VISIBLE);
        mButtonCancelBar.setVisibility(View.VISIBLE);
        if (Utils.hasKitKat()) {
            ImmersiveModeHelper.setImmersiveStickyWithActionBar(getActivity().getWindow());
        }
    }

    /**
     * Submit score to Play Games services and the leader board.
     */
    private void submitScore(int resId, int score) {
        PlayGamesActivity act = Utils.getPlayGamesActivity(this);
        if (act != null) {
            act.postSubmitScore(resId, score);
        }
    }
}