Android Open Source - candy-drop Math Engine






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.utils;
//w  w w  . ja v  a  2s  .co  m
import com.gregfmartin.facetapper.entities.Pulse;

import java.util.LinkedList;

/**
 * A helper utility that will perform necessary calculations for certain parts in the game.
 *
 * @author Gregory Martin
 */
public final class MathEngine {
    static final private MathEngine instance = new MathEngine();

    /**
     * The total number of levels in the game.
     */
    static final public int MAX_LEVELS = 100;

    /**
     * The default number of continues the player starts off with
     */
    static final public short BASE_PLAYER_CONTINUES = 3;

    /**
     * The current level the player is on. The default value for this is zero even though the player
     * will never be on level "zero" as it will be incremented before it is ever used.
     */
    private byte mCurrentLevel;

    /**
     * The total number of candies to drop in this level. This value will change depending upon several
     * factors including, but not limited to, the difficulty.
     */
    private short mTotalCandiesToDrop;

    /**
     * The number of candies that were dropped in the previous level.
     */
    private short mPreviousLevelCandyTotal;

    /**
     * The total number of candies the player is required to tap on in order to complete the level. This
     * value is automatically generated as well.
     */
    private short mPlayerTapThreshold;

    /**
     * The current number of candies the player has tapped on.
     */
    private short mCurrentTappedCandies;

    /**
     * The player's current score.
     */
    private int mScore;

    /**
     * The number of times a player can continue if they lose the round.
     */
    private byte mContinues;

    /**
     * The Pulse that helps determine how to drop Candies
     */
    private Pulse mCandyDropPulse;

    /**
     * A list of difficulties that will be used per level. These are all populated before they're ever used.
     */
    private LinkedList<Float> mCanonicalDifficultyHistory;

    private MathEngine() {
        populateDifficultyScalars();
        mScore = 0;
        mContinues = BASE_PLAYER_CONTINUES;
    }

    static final public MathEngine getInstance() { return instance; }

    public byte getLevel() { return mCurrentLevel; }

    public short getTotalCandies() { return mTotalCandiesToDrop; }

    public short getPlayerTapThreshold() { return mPlayerTapThreshold; }

    public short getNumTappedCandies() { return mCurrentTappedCandies; }

    public int getScore() { return mScore; }

    public byte getContinues() { return mContinues; }

    public void addToScore(int appendScore) { mScore += appendScore; }

    public void addToTotalTapped(int appendTap) { mCurrentTappedCandies += appendTap; }

    public void minusLevel() { mCurrentLevel--; }

    /**
     * This method will populate all of the possible difficulty scalars that will be used per level.
     */
    private void populateDifficultyScalars() {
        mCanonicalDifficultyHistory = new LinkedList<>();

        /*
         * ln(0) = Infinity/Undefined and ln(1) = 1. These are accounted for in the method that
         * actually calculates the scalar.
         */
        for(int cur = 0; cur < MAX_LEVELS; cur++) {
            mCanonicalDifficultyHistory.add(generateLevelDifficultyScalar(cur));
        }
    }

    public void update() {
        // Update the Pulses
        mCandyDropPulse.update();
    }

    /**
     * Generates the difficulty scalar for the level that the player is currently on. The difficulty is
     * determined by a function of the natural logarithm (loge) against the current level along with some
     * tweaking to ensure sane values are returned. For example, ln(0) is Infinity or Undefined so we can't
     * use it and ln(1) = 1 so using that in calculations would result in a plateau effect. The only problem
     * with using a logarithmic curve for the difficulty is that the plateau effect arises again very quickly
     * in later levels as the differences in yield are of a magnitude of 0.001 or less in some cases. This needs
     * to be tested throughout the system to see if the reflected changes are usable or not.
     *
     * @param level The current level the player is on
     * @return A float representing the current degree of difficulty that will be applied to all entity-based
     * calculations in the system. The zero-index is accounted for automatically.
     */
    private float generateLevelDifficultyScalar(int level) {
        return (float)Math.log(Math.pow(((level + 1) * 2), 2));
    }

    /**
     * Calculate the total number of candies that will be distributed for this level. The total amount will
     * use the current difficulty as base factor so that the amount will scale logarithmically, giving the impression
     * that more candies will be assigned as the levels increase. If the player is on a level past the first one,
     * the previous total will also be used as a base factor in the calculation to help mitigate the plateau effect
     * on the logarithmic curve.
     *
     * @return The total number of candies that will be distributed on the current level
     */
    private short calculateTotalCandies() {
        // Check first to see if we have a total value from a previous level
        if(0 == mPreviousLevelCandyTotal) {
            // Generate a fresh batch
            return (short)Math.floor(getDifficultyForLevel(mCurrentLevel));
        } else {
            return (short)(Math.floor(getDifficultyForLevel(mCurrentLevel) + mPreviousLevelCandyTotal));
        }
    }

    /**
     * @param level The current level the player is on
     * @return The difficulty scalar for that level
     */
    public float getDifficultyForLevel(int level) {
        return mCanonicalDifficultyHistory.get(level);
    }

    public void prepLevel() {
        // Increment the level counter
        mCurrentLevel++;

        // Calculate the total number of candies that are going to be dispersed for this level
        // The still glaring problem here is the level 1
        // The floored variant on the current difficulty is used quite often for certain calculations
        short flooredDifficulty = (short)Math.floor(getDifficultyForLevel(mCurrentLevel));

        // Grab the previous candy total before modifying the value
        mPreviousLevelCandyTotal = mTotalCandiesToDrop;

        // Adjust the new total of candies for this level
        mTotalCandiesToDrop = calculateTotalCandies();

        /*
         * Determine the player's tap threshold. This is the number of candies the player must tap on
         * in order to successfully advance to the next level.
         *
         * The equation to calculate this is as follows:
         *
         * c * (0.8 + (floor(d) / 100))
         *
         * where
         *
         * c = The current number of total candies to drop
         * d = The difficulty (flooredDifficulty already contains the value to use here)
         *
         * Logic
         *
         * The threshold needs to be a fraction of the total number of candies that are going to be dropped.
         * Theoretically, it is possible and logically permissible for the threshold to equal the total to be
         * dropped but this equation, as a function the confines of the number set defined by the number of playable
         * levels, doesn't ever cross 100% so yields are never greater than or equal to the current number
         * of dropped candies. Originally the equation was nothing more than 80% of the total drop count. However,
         * despite the total drop count being abstractly derived from the logarithmic difficulty curve, the yield
         * was exponential. In this way, yields appear to be exponential but are more tightly bound to the total
         * drop count and vary in subtle ways as the series continues on. I have a feeling that the subtle hints
         * of the exponential curve come about from using the constant of 80% in the calculation.
         */
        mPlayerTapThreshold = (short)(mTotalCandiesToDrop * (0.8F + (flooredDifficulty / 100)));

        // The player couldn't have tapped any candies at this point, so set the counter to zero
        mCurrentTappedCandies = 0;

        // TEST - Instantiate the Candy Drop Pulse
        mCandyDropPulse = new Pulse(getDifficultyForLevel(mCurrentLevel), 20F, 1.0F, 1000F);
    }

    public void resetLevel() {
        mCurrentLevel--;

        // Increment the level counter
        mCurrentLevel++;

        // Calculate the total number of candies that are going to be dispersed for this level
        // The still glaring problem here is the level 1
        // The floored variant on the current difficulty is used quite often for certain calculations
        short flooredDifficulty = (short)Math.floor(getDifficultyForLevel(mCurrentLevel));

        // Grab the previous candy total before modifying the value
        // mPreviousLevelCandyTotal = mTotalCandiesToDrop;

        // Adjust the new total of candies for this level
        mTotalCandiesToDrop = calculateTotalCandies();

        /*
         * Determine the player's tap threshold. This is the number of candies the player must tap on
         * in order to successfully advance to the next level.
         *
         * The equation to calculate this is as follows:
         *
         * c * (0.8 + (floor(d) / 100))
         *
         * where
         *
         * c = The current number of total candies to drop
         * d = The difficulty (flooredDifficulty already contains the value to use here)
         *
         * Logic
         *
         * The threshold needs to be a fraction of the total number of candies that are going to be dropped.
         * Theoretically, it is possible and logically permissible for the threshold to equal the total to be
         * dropped but this equation, as a function the confines of the number set defined by the number of playable
         * levels, doesn't ever cross 100% so yields are never greater than or equal to the current number
         * of dropped candies. Originally the equation was nothing more than 80% of the total drop count. However,
         * despite the total drop count being abstractly derived from the logarithmic difficulty curve, the yield
         * was exponential. In this way, yields appear to be exponential but are more tightly bound to the total
         * drop count and vary in subtle ways as the series continues on. I have a feeling that the subtle hints
         * of the exponential curve come about from using the constant of 80% in the calculation.
         */
        mPlayerTapThreshold = (short)(mTotalCandiesToDrop * (0.8F + (flooredDifficulty / 100)));

        // The player couldn't have tapped any candies at this point, so set the counter to zero
        mCurrentTappedCandies = 0;

        // TEST - Instantiate the Candy Drop Pulse
        mCandyDropPulse = new Pulse(getDifficultyForLevel(mCurrentLevel), 1.0F, 1.0F, 1000F);
    }

    /**
     * @return True if the player has Continues to use, False otherwise
     */
    public boolean canResetLevel() {
        if(mContinues > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Determine, pseudo-randomly, if the game can drop a candy or not. Ideally, lower levels will drop candies
     * at a slower rate than later ones. However, a logarithmic curve for drop rate wouldn't alone be sufficient.
     *
     * @return If we can drop a candy now or not
     */
    public boolean canDropCandy() {
        return mCandyDropPulse.isCurrentPointWithinThreshold();
    }

    /**
     * @return True if the player has tapped the required number of candies to proceed to the next level
     */
    public boolean hasPlayerCrossedTapThreshold() {
        if(mCurrentTappedCandies > mPlayerTapThreshold) {
            return true;
        } else {
            return false;
        }
    }
}




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