/*
* Copyright (C) 2016 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 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.graphics.drawable.VectorDrawableCompat;
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 com.google.android.apps.santatracker.R;
import com.google.android.apps.santatracker.util.ImmersiveModeHelper;
import com.google.android.apps.santatracker.games.common.PlayGamesActivity;
import com.google.android.apps.santatracker.games.gumball.Utils;
import com.google.android.apps.santatracker.invites.AppInvitesFragment;
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};
private static final int DOOR_CLOSE_DELAY_MILLIS = 1500;
/**
* 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 Handler mDoorCloseHandler;
private Runnable mDoorCloseRunnable;
private boolean mDoorCloseTimerTicking = false;
private long mDoorCloseTimerStart = 0;
private long mDoorCloseTimeRemaining = DOOR_CLOSE_DELAY_MILLIS;
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 mInviteButton;
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();
/**
* Handler and Runnable to fix the occasional "nothing is clickable" bug, The timeout
* is set as slightly longer than our max animation duration.
*/
private static final long ANIMATION_TIMEOUT = 1005;
private Handler mClickabilityHandler = new Handler();
private Runnable mMakeClickableRunnable = new Runnable() {
@Override
public void run() {
isClickable = true;
}
};
/**
* 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;
private AppInvitesFragment mInvitesFragment;
/**
* 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);
mDoorCloseHandler = new Handler();
mDoorCloseRunnable = new Runnable() {
@Override
public void run() {
hideBothPreviousCards();
mDoorCloseTimerTicking = false;
mDoorCloseTimerStart = 0;
mDoorCloseTimeRemaining = DOOR_CLOSE_DELAY_MILLIS;
}
};
// 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);
mInviteButton = (ImageView) rootView.findViewById(R.id.invite_button);
mInviteButton.setOnClickListener(this);
mInviteButton.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(VectorDrawableCompat.create(getResources(),
R.drawable.instructions_touch_1, null), 300);
mInstructionDrawable
.addFrame(VectorDrawableCompat.create(getResources(),
R.drawable.instructions_touch_2, null), 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 onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mInvitesFragment = AppInvitesFragment.getInstance(getActivity());
}
@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);
mInviteButton.setVisibility(View.GONE);
setSignInButtonVisibility(false);
} else if (view.equals(mButtonPause)) {
// Pause button.
pauseGame();
} else if (view.equals(mButtonPlay) || view.equals(mButtonBigPlay)) {
// Play button, unmute 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();
}
} else if (view.equals(mInviteButton)) {
// Send app invite
mInvitesFragment.sendGameInvite(
getString(R.string.memory),
getString(R.string.memory_game_id),
mMatchScore);
}
}
private void resetGameState() {
mLevelNumber = 1;
mTimeLeftInMillis = MatchingGameConstants.MATCH_INIT_TIME;
setUpLevel();
mMatchScore = 0;
}
/**
* Hides the cards of the previous turn.
* Should be called when the previous turn was an incorrect match.
*/
private void hideBothPreviousCards() {
if (mVisibleCard1 != null && mVisibleCard2 != null) {
hideCard(mVisibleCard1);
hideCard(mVisibleCard2);
mVisibleCard2.setOnClickListener(this);
mVisibleCard1.setOnClickListener(this);
mVisibleCard1 = null;
mVisibleCard2 = null;
}
}
/**
* 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
// This is also triggered by a timer but this occurs if the user
// plays the next turn before the timer is up.
hideBothPreviousCards();
mVisibleCard1 = view;
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);
// 1.5 seconds after this turn was deemed incorrect, hide both cards.
mDoorCloseHandler.postDelayed(mDoorCloseRunnable, DOOR_CLOSE_DELAY_MILLIS);
mDoorCloseTimerStart = System.currentTimeMillis();
mDoorCloseTimerTicking = true;
}
} else {
// This is the first card that has been unveiled.
mVisibleCard1 = view;
mVisibleCard1.setOnClickListener(null);
showCard(mVisibleCard1);
}
}
@Override
public void onStop() {
if(mDoorCloseHandler != null && mDoorCloseRunnable != null) {
mDoorCloseHandler.removeCallbacks(mDoorCloseRunnable);
}
super.onStop();
}
/**
* 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);
// If the user had gotten the previous turn incorrect and the CloseDoor timer was
// ticking before the mute, continue the timer.
if(mDoorCloseHandler != null && mDoorCloseRunnable != null && mDoorCloseTimerTicking) {
mDoorCloseHandler.postDelayed(mDoorCloseRunnable, mDoorCloseTimeRemaining);
mDoorCloseTimerStart = System.currentTimeMillis();
}
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);
mInviteButton.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;
mClickabilityHandler.removeCallbacksAndMessages(null);
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);
}
// Set a clickability timeout
mClickabilityHandler.postDelayed(mMakeClickableRunnable, ANIMATION_TIMEOUT);
}
@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 {
// Exit the game.
exit();
}
}
}
private void pauseGame() {
mButtonPause.setVisibility(View.GONE);
mButtonPlay.setVisibility(View.VISIBLE);
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
// If the user got the previous turn incorrect and the CloseDoor timer is ticking,
// stop the CloseDoor timer but remember how much time is left on it.
if (mDoorCloseHandler != null && mDoorCloseRunnable != null && mDoorCloseTimerTicking) {
mDoorCloseHandler.removeCallbacks(mDoorCloseRunnable);
mDoorCloseTimeRemaining =
DOOR_CLOSE_DELAY_MILLIS - (System.currentTimeMillis() - mDoorCloseTimerStart);
}
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);
}
}
}