Android Open Source - candy-drop Arena Screen






From Project

Back to project page candy-drop.

License

The source code is released under:

Copyright (c) 2014, Gregory Martin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: ...

If you think the Android project candy-drop listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.gregfmartin.facetapper.screens;
// w w w . j a  va 2s.  co m
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.gregfmartin.facetapper.GameCore;
import com.gregfmartin.facetapper.entities.CandyBase;
import com.gregfmartin.facetapper.entities.CandyDrop;
import com.gregfmartin.facetapper.entities.Screen;
import com.gregfmartin.facetapper.utils.MathEngine;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * The screen where the game is actually played at.
 * <p/>
 * Each level has one objective: tap as many candies as you can before they run out. That being said, the rules are
 * as follows:
 * <p/>
 * 1. Each level has a finite number of candies that will be dropped. These candies will be dropped at random
 * intervals throughout the course of the level.
 * 2. Because there are a finite number of candies per level, there is a threshold of candies that the player will
 * need to tap in order to proceed to the next level. This threshold is determined based on the current level/
 * [difficulty setting (not yet implemented)]/total number of candies that will be dropped.
 * 3. Everything about the creation of the candies is random but also governed by the current level/[difficult
 * setting (not yet implemented)]. This includes the falling speed and point value.
 * 4. The player will have a finite number of continues. Here, continues will be treated as "lives" so-to-speak.
 * The player will start with three continues. A single continue will be used if all of the candies have been
 * dropped and the player has failed to tap the required number of candies to proceed to the next level. When this
 * happens, the level will restart which will reset all of the counters for the level save the static level
 * requirements (total number of candies/tap requirement). The score will be reset to the value that it was at
 * before the start of the level.
 * 5. If the player manages to tap enough candies to proceed to the next level, a victory animation will play, wait
 * for them to tap the screen to proceed to the next level, and will continue on from there.
 * 6. Level progression will gradually increase the difficulty. Because the values for the candies are capped, the
 * difficulty can be compensated for in different ways.
 *
 * @author Gregory Martin
 */
public class ArenaScreen extends Screen {
    /**
     * Do we update the information in the labels?
     */
    static public boolean STATUS_TEXT_DIRTY = true;

    // The current drop count
    private int mDropCounter = 0;

    // The number of "dead" candies
    private short mDeadCounter = 0;

    /*
     * Pool of candies for the level. The size of this pool is unknown since the size would be determined
     * by the current level.
     */
    private List<CandyBase>     mCandyQueue;
    private Iterator<CandyBase> mCandyQueueIterator;

    // The current state of the game
    private ArenaState mGamePlayState = ArenaState.PREP;

    // The root container for the UI components that will be used here
    private Table mPlayUiTable;

    // The root container for the UI components that will be used in the Lose portion
    private Table mLoseUiTable;

    // The root container for the UI components that will be used in the Win portion
    private Table mWinUiTable;

    // Label showing the player's current score (top-left) and its style
    private Label            mPlayerScoreLabel;
    private Label.LabelStyle mPlayerScoreLabelStyle;

    // Label showing the candies goal for the level (threshold/total) (top-right) and its style
    private Label            mLevelCandiesGoalLabel;
    private Label.LabelStyle mLevelCandiesGoalLabelStyle;

    // Label showing the current tapped candies total (bottom-right) and its style
    private Label            mCurrentTappedCandiesLabel;
    private Label.LabelStyle mCurrentTappedCandiesLabelStyle;

    // UI components that will be used in the Win table
    private Image                      mWinLogo;
    private TextButton                 mButtonNextLevel;
    private TextButton.TextButtonStyle mButtonNextLevelStyle;

    // UI components that will be used in the Lose table
    private Image                      mLoseLogo;
    private TextButton                 mButtonContinue;
    private TextButton.TextButtonStyle mButtonContinueStyle;
    private Label                      mContinuesLabel;
    private Label.LabelStyle           mContinuesLabelStyle;

    // Used to generate BitmapFonts
    private FreeTypeFontGenerator mFontGen;

    public ArenaScreen(GameCore gameCore) {
        super(gameCore);
    }

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

        // The font generator is needed by the UI - instantiate it first
        mFontGen = new FreeTypeFontGenerator(Gdx.files.internal("fonts/sawasdee/sawasdee.ttf"));

        // All of the styles that get applied to elements
        mPlayerScoreLabelStyle = new Label.LabelStyle(mFontGen.generateFont(30), Color.YELLOW);
        mLevelCandiesGoalLabelStyle = new Label.LabelStyle(mFontGen.generateFont(30), Color.BLUE);
        mCurrentTappedCandiesLabelStyle = new Label.LabelStyle(mFontGen.generateFont(30), Color.GREEN);
        mButtonNextLevelStyle = new TextButton.TextButtonStyle(null, null, null, mFontGen.generateFont(40));
        mButtonNextLevelStyle.fontColor = Color.BLACK;
        mButtonContinueStyle = new TextButton.TextButtonStyle(null, null, null, mFontGen.generateFont(40));
        mButtonContinueStyle.fontColor = Color.BLACK;
        mContinuesLabelStyle = new Label.LabelStyle(mFontGen.generateFont(30), Color.RED);

        // All of the UI elements
        mPlayUiTable = new Table();
        mWinUiTable = new Table();
        mLoseUiTable = new Table();
        mPlayerScoreLabel = new Label("Score: ", mPlayerScoreLabelStyle);
        mLevelCandiesGoalLabel = new Label(String.format("%s / %s", String.valueOf(MathEngine.getInstance().getPlayerTapThreshold()), String.valueOf(MathEngine.getInstance().getTotalCandies())), mLevelCandiesGoalLabelStyle);
        mCurrentTappedCandiesLabel = new Label("Tapped: ", mCurrentTappedCandiesLabelStyle);
        mWinLogo = new Image(new Texture(Gdx.files.internal("images/ui/logos/win.png")));
        mButtonNextLevel = new TextButton("Next Level", mButtonNextLevelStyle);
        mLoseLogo = new Image(new Texture(Gdx.files.internal("images/ui/logos/lose.png")));
        mButtonContinue = new TextButton("Continue?", mButtonContinueStyle);
        mContinuesLabel = new Label("Continues: ", mContinuesLabelStyle);

        // Ensure that the Stage acts as the input processor
        Gdx.input.setInputProcessor(mStage);
    }

    @Override public void rehearsal() {
        super.rehearsal();
        resetPlayUi();
    }

    @Override public void logic(float delta) {
        super.logic(delta);

        // Check the current state of the level
        if(mGamePlayState == ArenaState.PREP) {
            mDropCounter = 0;

            resetPlayUi();

            // Get the MathEngine to prepare the level
            MathEngine.getInstance().prepLevel();

            // Allocate the candy pool
            prepareCandyPool();

            // Prep work should be complete, transition to the next phase
            mGamePlayState = ArenaState.PLAY;
        } else if(mGamePlayState == ArenaState.PLAY) {
            // Update the contents of the UI
            if(STATUS_TEXT_DIRTY) {
                updatePlayStateUi();
            }

            // Update necessary things in the MathEngine
            MathEngine.getInstance().update();

            // Check to see if we can drop a candy
            if(mDropCounter != MathEngine.getInstance().getTotalCandies()) {
                if(MathEngine.getInstance().canDropCandy()) {
                    if(mDropCounter < MathEngine.getInstance().getTotalCandies()) {
                        mStage.addActor(mCandyQueue.get(mDropCounter));
                        mDropCounter++;
                    }
                }
            }

            boolean areCandiesDead = areAllCandiesDead();

            // Check for potential cases where the game state would change
            if(areCandiesDead && MathEngine.getInstance().hasPlayerCrossedTapThreshold()) {
                mGamePlayState = ArenaState.WIN;
            }
            if(areCandiesDead && !MathEngine.getInstance().hasPlayerCrossedTapThreshold()) {
                mGamePlayState = ArenaState.LOSE;
            }
        } else if(mGamePlayState == ArenaState.LOSE) {
            resetLoseUi();
        } else if(mGamePlayState == ArenaState.WIN) {
            resetWinUi();
        } else if(mGamePlayState == ArenaState.RESET) {
            mDropCounter = 0;

            resetPlayUi();

            // Get the MathEngine to reset the level
            MathEngine.getInstance().resetLevel();

            // Allocate the candy pool
            prepareCandyPool();

            // Prep work should be complete, transition to the next phase
            mGamePlayState = ArenaState.PLAY;
        }
    }

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

        mFontGen.dispose();
        mCandyQueue.clear();
    }

    /**
     * Change the UI for the Play state
     */
    private void resetPlayUi() {
        // Clear all of the Actors from the Stage
        mStage.clear(); // This bubbles down to the Group

        // Reconfigure the table
        mPlayUiTable.clear();
        mPlayUiTable.setFillParent(true);
        mPlayUiTable.add(mPlayerScoreLabel).top().left();
        mPlayUiTable.add(mLevelCandiesGoalLabel).top().right();
        mPlayUiTable.row().expand().colspan(2);
        mPlayUiTable.add(mCurrentTappedCandiesLabel).bottom().right();

        mStage.addActor(mPlayUiTable);
    }

    /**
     * Change the UI for the Win state
     */
    private void resetWinUi() {
        // Clear all of the Actors from the stage
        mStage.clear();

        // Reconfigure the table
        mWinUiTable.clear();
        mWinUiTable.setFillParent(true);
        mWinUiTable.add(mWinLogo).top().center();
        mWinUiTable.row().expand();
        mWinUiTable.add(mButtonNextLevel).center();

        mButtonNextLevel.addListener(new InputListener() {
            @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
                // Move to the next level
                mGamePlayState = ArenaState.PREP;

                return false;
            }
        });

        mStage.addActor(mWinUiTable);
    }

    /**
     * Change the UI for the Lose state
     */
    private void resetLoseUi() {
        // Clear all of the Actors from the Stage
        mStage.clear();

        // Reconfigure the table
        mLoseUiTable.clear();
        mLoseUiTable.setFillParent(true);
        mLoseUiTable.add(mLoseLogo).top().center().colspan(2);
        mLoseUiTable.row().expand();
        mLoseUiTable.add(mContinuesLabel).bottom().left();
        mLoseUiTable.add(mButtonContinue).bottom().right();

        // TEST - Allow the user to continue regardless of their continue count
        mButtonContinue.addListener(new InputListener() {
            @Override public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
                // Reset the level
                mGamePlayState = ArenaState.RESET;

                return false;
            }
        });

        mStage.addActor(mLoseUiTable);
    }

    /**
     * Allocates (or re-allocates) the pool that's used for the Candies for this level. This function will be called
     * when the Game State is PREP so it will only be called once before moving onto the PLAY state. It will also
     * be called if the player loses the level and the level needs reset.
     */
    private void prepareCandyPool() {
        mCandyQueue = new ArrayList<>(MathEngine.getInstance().getTotalCandies());

        /*
         * Ideally, this block of code will be further broken down to insert candies and other entities based on
         * mathematical calculations generated from the MathEngine.
         */
        for(int i = 0; i < MathEngine.getInstance().getTotalCandies(); i++) {
            mCandyQueue.add(CandyDrop.obtain(MathEngine.getInstance().getLevel()));
        }
    }

    /**
     * Updates the information in the labels that are displayed on the screen during the play state.
     */
    private void updatePlayStateUi() {
        mPlayerScoreLabel.setText(String.format("Score: %s", String.valueOf(MathEngine.getInstance().getScore())));
        mLevelCandiesGoalLabel.setText(String.format("%s / %s", String.valueOf(MathEngine.getInstance().getPlayerTapThreshold()), String.valueOf(MathEngine.getInstance().getTotalCandies())));
        mCurrentTappedCandiesLabel.setText(String.format("Tapped: %s", String.valueOf(MathEngine.getInstance().getNumTappedCandies())));
        STATUS_TEXT_DIRTY = false;
    }

    /**
     * Run a quick loop through the candy pool to determine if all of them are marked as dead
     *
     * @return True if all candies have been marked as dead; False otherwise
     */
    private boolean areAllCandiesDead() {
        /*
        for(int i = 0; i < mCandyQueue.size(); i++) {
            if(!mCandyQueue.get(i).isAlive()) {
                mDeadCounter++;
            }
        }
        */

        mDeadCounter = 0;
        mCandyQueueIterator = mCandyQueue.iterator();
        while(mCandyQueueIterator.hasNext()) {
            if(!mCandyQueueIterator.next().isAlive()) {
                // mCandyQueueIterator.remove();
                mDeadCounter++;
            }
        }

        if(MathEngine.getInstance().getTotalCandies() == mDeadCounter) {
            return true;
        } else {
            return false;
        }
    }

    // The current state of the game play
    private enum ArenaState {
        PREP,
        PLAY,
        WIN,
        LOSE,
        RESET
    }
}




Java Source Code List

com.gregfmartin.facetapper.GameCore.java
com.gregfmartin.facetapper.Main.java
com.gregfmartin.facetapper.entities.CandyBase.java
com.gregfmartin.facetapper.entities.CandyDrop.java
com.gregfmartin.facetapper.entities.Pulse.java
com.gregfmartin.facetapper.entities.Screen.java
com.gregfmartin.facetapper.entities.SplashImage.java
com.gregfmartin.facetapper.entities.Sucker.java
com.gregfmartin.facetapper.screens.ArenaScreen.java
com.gregfmartin.facetapper.screens.AssociationScreen.java
com.gregfmartin.facetapper.screens.SplashScreen.java
com.gregfmartin.facetapper.screens.TitleScreen.java
com.gregfmartin.facetapper.utils.Colour.java
com.gregfmartin.facetapper.utils.CoreUtil.java
com.gregfmartin.facetapper.utils.DevSettings.java
com.gregfmartin.facetapper.utils.Gl20Utils.java
com.gregfmartin.facetapper.utils.MathEngine.java
com.gregfmartin.facetapper.utils.StagePrep.java