com.maplescot.loggerbill.game.LoggerEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.maplescot.loggerbill.game.LoggerEngine.java

Source

/*
 *
 *  * (C) Copyright 2014 MapleScot Development
 *  * This file licensed under a Creative Commons 3.0 by attribution licence
 *  * https://creativecommons.org/licenses/by/3.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.
 *  *
 *  * https://github.com/duriej/LoggerBill
 *
 */

package com.maplescot.loggerbill.game;

import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.ParticleEffect;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.maplescot.loggerbill.game.world.*;
import com.maplescot.loggerbill.gpg.Ads;
import com.maplescot.loggerbill.misc.Assets;
import com.maplescot.loggerbill.misc.ProfileManager;
import com.maplescot.loggerbill.ui.PausedDialog;

import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;

import static com.maplescot.loggerbill.misc.Constants.*;

/**
 * This is where the guts of the game go.
 * <p/>
 * Created by troy on 20/09/14.
 */
public class LoggerEngine implements GameEngine, GameRenderer, InputProcessor {

    private static String TAG = LoggerEngine.class.toString();

    private ShaderProgram ghostShader;
    private ShaderProgram nightShader;
    private ParticleEffect chips;
    private PausedDialog pausedDialog;
    private FireFlies fireFlies_front;
    private FireFlies fireFlies_back;

    private static Random rnum = new Random();
    private static boolean isNight = false;
    private static int dayCounter = 0;

    private Bill bill = new Bill();
    private boolean showHelp = true;
    private boolean isPaused = false;
    private boolean playing = true;
    private float offset = 0; // add a bit of animation to or fingers...
    // A tree consists of.... chunkTypes.
    //  ---> How many chunks could a woodchuck chuck, if a woodchuck could chuck chunks?
    private LinkedList<Chunk> tree = new LinkedList<Chunk>();
    private LinkedList<EjectedChunk> ejectedChunks = new LinkedList<EjectedChunk>();
    private float fall_dist = 0; // To animate the tree falling down to replace a chunk.
    private boolean billAlive = true;
    private BillGhost billGhost = null;
    private float totalTime = 0f;
    private float idleTime = 0.5f; // Idle time is a percentage... 100%. This is decremented each frame... if it gets to zero bill dies.
    private float speed;
    private int chunk_counter = 0;

    private static float width;

    private float xMul, yMul; // Multipliers used to transform real screen coords to virtual

    @Override
    public void init() {
        BackgroundScenery.getInstance().init();

        Gdx.app.debug(TAG, "Compiling Shaders");

        ghostShader = new ShaderProgram(Gdx.files.internal(VERTEX_SHADER), Gdx.files.internal(FRAG_SHADER));
        if (!ghostShader.isCompiled()) {
            String msg = "Could not compile shader program: " + ghostShader.getLog();
            throw new GdxRuntimeException(msg);
        }

        nightShader = new ShaderProgram(Gdx.files.internal(NIGHT_VERTEX_SHADER),
                Gdx.files.internal(NIGHT_FRAG_SHADER));

        if (!nightShader.isCompiled()) {
            String msg = "Could not compile shader program: " + nightShader.getLog();
            throw new GdxRuntimeException(msg);
        }

        chips = new ParticleEffect();
        chips.load(Gdx.files.internal("particles/chips.pfx"), Gdx.files.internal("particles"));
        pausedDialog = new PausedDialog(this);

        // Calculate multiplier for relative screen coords.
        xMul = VIEWPORT_GUI_WIDTH / Gdx.graphics.getWidth();
        yMul = getViewportHeight() / Gdx.graphics.getHeight();
    }

    @Override
    public InputProcessor getInputProcessor() {
        return this;
    }

    @Override
    public void reset() {
        dayCounter++;
        if (dayCounter > 3) { // Every 3 plays change from day to night.
            isNight = !isNight;
            dayCounter = 0;
        }

        playing = true;
        if (isNight) {
            fireFlies_front = new FireFlies(10, 1);
            fireFlies_back = new FireFlies(10, 195);
        } else {
            fireFlies_front = null;
            fireFlies_back = null;
        }

        BackgroundScenery.getInstance().setNight(isNight);
        BackgroundScenery.getInstance().setNightShader(nightShader);
        billAlive = true;
        bill.setDead(false);
        showHelp = true;

        //billGhost = null;
        // Reset score
        idleTime = 0.5f;
        totalTime = 0f;
        chunk_counter = 0;
        speed = START_SPEED;
        bill.setSide(LEFT);
        bill.setChopping(false);

        tree.clear();
        buildTree();

        // trigger loading a new ad for those ad networks that need it
        Ads.getInstance().showBanner(true);

    }

    @Override
    public boolean run(float delta) {
        if (fall_dist > 0)
            fall_dist -= CHUNK_SIZE * (delta * 10);
        else
            fall_dist = 0;

        if (isPaused && pausedDialog != null)
            pausedDialog.render(delta);
        else {
            checkForDead();
            if (billAlive && !showHelp) {
                idleTime -= delta * speed;
                totalTime += delta;
            }
        }

        return playing;
    }

    @Override
    public void drawWorld(SpriteBatch batch, float delta) {
        if (isNight) {
            // Night sky.
            Gdx.gl.glClearColor(0.036f, 0.058f, 0.0988f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        } else {
            // Colour my world blue.
            Gdx.gl.glClearColor(0.36f, 0.58f, 0.988f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        }
        BackgroundScenery.getInstance().draw(delta);

        // The Grass...
        setShader(batch);
        batch.setColor(0f, 1f, 0f, 1f); // Green Grass
        batch.draw(Assets.getInstance().grassRegion, -(VIEW_WIDTH / 2) - 50, 0f, VIEW_WIDTH + 100,
                BILL_HEIGHT + 20);

        if (isNight) {
            batch.setShader(null);
            fireFlies_back.draw(batch, delta);
        }

        setShader(batch);
        // The stump...
        batch.setColor(1f, 1f, 1f, 1f);
        batch.draw(Assets.getInstance().stumpRegion, -(Assets.getInstance().stumpRegion.getRegionWidth() / 2) - 7,
                40f);

        drawTree(batch); // Draw the actual tree

        ListIterator<EjectedChunk> ejectIterator = ejectedChunks.listIterator();
        while (ejectIterator.hasNext()) {
            EjectedChunk myChunk = ejectIterator.next();
            if (!myChunk.draw(batch, delta))
                ejectIterator.remove(); // Clean up ejected chunks when off screen.
        }

        bill.draw(batch, delta);

        if (billGhost != null) {
            billGhost.draw(batch, ghostShader, delta);
        }

        chips.draw(batch);
        if (!chips.isComplete())
            chips.update(delta);

        if (isNight) {
            batch.setShader(null);
            fireFlies_front.draw(batch, delta);
        }

    }

    @Override
    public void drawHUD(SpriteBatch batch, float delta) {
        batch.setShader(null); // No darkening of the HUD at night
        String str = String.valueOf(chunk_counter);
        BitmapFont.TextBounds tb = Assets.getInstance().font.big.getBounds(str);
        Assets.getInstance().font.big.setColor(0f, 1f, 0f, 1f);
        Assets.getInstance().font.big.draw(batch, str, VIEWPORT_GUI_WIDTH / 2 - (tb.width / 2), 250);

        Assets.getInstance().pauseButtonDrawable.draw(batch, VIEWPORT_GUI_WIDTH - 150, 135, 100, 100);
        drawTimerGuage(batch);
        if (showHelp)
            drawHelp(batch, delta);
    }

    @Override
    public void setPause(boolean paused) {
        isPaused = paused;
        if (paused)
            showPause(false); // No animation.

    }

    @Override
    public boolean isPaused() {
        return isPaused;
    }

    public void setShader(SpriteBatch batch) {
        if (isNight) {
            batch.setShader(nightShader);
            nightShader.setUniformf("u_amount", 0.85f);
        } else
            batch.setShader(null);
    }

    public void endGame() {
        Gdx.app.debug(TAG, "Ending game.");
        playing = false;
    }

    /**
     * This method builds the initial tree.
     */
    private void buildTree() {

        // Start out with at least 2 CLEAR chunks...
        tree.add(new Chunk(Chunk.Type.CLEAR));
        tree.add(new Chunk(Chunk.Type.CLEAR));

        for (int i = 2; i < CHUNK_COUNT; i++) {
            addChunk();
        }

    }

    /**
     * Add a chunk to the tree. The rules are simple. We have three types of chunks - LEFT, RIGHT, or CLEAR. This
     * indicates the side that the branch is on (or no branch in the case of CLEAR). Every second chunk at minimum
     * must be clear - i.e. you cannot have two branches following each other. However after a clear section you can
     * have any of the three. Simples?
     */
    private void addChunk() {
        Chunk lastChunk = tree.getLast();
        if (lastChunk.getType() == Chunk.Type.CLEAR) {
            int r = rnum.nextInt(5);
            // give a slight bias to having a branch over not having a branch
            if (r == 0)
                tree.add(new Chunk(Chunk.Type.CLEAR));
            else if (r % 2 == 0)
                tree.add(new Chunk(Chunk.Type.RIGHT));
            else
                tree.add(new Chunk(Chunk.Type.LEFT));
        } else
            tree.add(new Chunk(Chunk.Type.CLEAR));

    }

    private void drawHelp(SpriteBatch batch, float delta) {

        offset += delta;
        if (offset >= 360)
            offset = 0;

        batch.draw(Assets.getInstance().tapIcon_left, ((VIEWPORT_GUI_WIDTH / 5) - 50),
                (float) ((getViewportHeight() - 300) + (Math.sin(offset) * 25)), 100, 100);
        batch.draw(Assets.getInstance().tapIcon_right, ((VIEWPORT_GUI_WIDTH / 5) * 4) - 50,
                (float) ((getViewportHeight() - 300) - (Math.sin(offset) * 25)), 100, 100);

        Assets.getInstance().font.normal.setColor(1f, 0f, 0f, 1f);

        String helpText;
        if (Gdx.app.getType() == Application.ApplicationType.Desktop
                || Gdx.app.getType() == Application.ApplicationType.WebGL
                || Gdx.app.getType() == Application.ApplicationType.Applet)
            helpText = "Press";
        else
            helpText = "Tap";

        Assets.getInstance().font.normal.draw(batch, helpText, ((VIEWPORT_GUI_WIDTH / 5) - 35),
                (getViewportHeight() - 200));
        Assets.getInstance().font.normal.draw(batch, helpText, (((VIEWPORT_GUI_WIDTH / 5) * 4) - 30),
                (getViewportHeight() - 200));

    }

    private void drawTimerGuage(SpriteBatch batch) {
        String str = "";
        batch.draw(Assets.getInstance().guage_empty,
                VIEWPORT_GUI_WIDTH / 2 - (Assets.getInstance().guage_empty.getRegionWidth() / 2), 150f);
        // the bezel around the fuel guage is 6 pixels... so I need to adjust some dimensions by 7 and 12
        float w = ((Assets.getInstance().guage_empty.getRegionWidth() - 12) * idleTime);
        batch.draw(Assets.getInstance().guage_full,
                VIEWPORT_GUI_WIDTH / 2 - (Assets.getInstance().guage_empty.getRegionWidth() / 2) + 6, 158f, w,
                Assets.getInstance().guage_full.getRegionHeight());

        if (idleTime < 0.24f && idleTime > 0) {
            str = "Chop Faster!";
            Assets.getInstance().font.normal.setColor(1f, 1f, 0f, 1f);
        } else if (idleTime <= 0.0) {
            str = "Too Slow!";
            Assets.getInstance().font.normal.setColor(1f, 0f, 0f, 1f);
        }
        BitmapFont.TextBounds tb = Assets.getInstance().font.normal.getBounds(str);
        Assets.getInstance().font.normal.draw(batch, str, (VIEWPORT_GUI_WIDTH / 2) - (tb.width / 2),
                156 + tb.height / 2);
    }

    /**
     * Helper function for drawing the tree.
     *
     * @param batch The sprite batch to use for improving performance
     */
    public void drawTree(SpriteBatch batch) {
        Sprite mySprite;
        float yPos = 192 - (CHUNK_SIZE / 2) + fall_dist;
        for (Chunk thisChunk : tree) {
            mySprite = thisChunk.getSprite();

            mySprite.setX(-CHUNK_SIZE / 2 - (thisChunk.getType() == Chunk.Type.LEFT ? CHUNK_SIZE : 0));
            mySprite.setY(yPos);
            mySprite.draw(batch);
            yPos += CHUNK_SIZE - 1;
        }
    }

    /**
     * Check conditions for bill's possible demise..
     */
    private void checkForDead() {
        if (!billAlive)
            return;
        if (fall_dist == 0 && tree.getFirst().getType() == Chunk.Type.LEFT && bill.getSide() == LEFT
                || tree.getFirst().getType() == Chunk.Type.RIGHT && bill.getSide() == RIGHT)
            killBill();

        if (billAlive && idleTime <= 0) {
            Gdx.app.log(TAG, "Too slow");
            killBill();
        }

    }

    /**
     * Game over...
     */
    private void killBill() {
        billAlive = false; // He's dead Jim
        Gdx.input.vibrate(500);
        bill.setDead(true);
        billGhost = new BillGhost(bill.getSide());
        if (ProfileManager.getProfile().getTotalPlays() % 5 == 0)
            Ads.getInstance().showInterstitial();
        showPause(true);
    }

    /**
     * Spew one the top chunk of the tree off to one side, and increment chunk counter.
     *
     * @param direction Direction to fly off -1 = left, 1 = right.
     */
    private void eject(int direction) {
        if (!billAlive)
            return; //Dead men chop no trees.
        if (showHelp)
            showHelp = false; // Turn off help
        bill.chop();
        Gdx.input.vibrate(50);
        if (ProfileManager.getProfile().isSoundOn())
            Assets.getInstance().thwack.play();
        ejectedChunks.add(new EjectedChunk(tree.getFirst().getSprite(), direction));
        tree.removeFirst();
        addChunk();
        chunk_counter++;
        if (chunk_counter % LEVEL_THRESHOLD == 0) {
            speed += SPEED_INCREASE;
        }
        idleTime += RECHARGE_RATE;
        if (idleTime > 1)
            idleTime = 1;
        fall_dist = CHUNK_SIZE;
        chips.findEmitter("Chips").getVelocity().setHigh(150 * bill.getSide(), 300 * bill.getSide());
        chips.setPosition(bill.getSide() * CHUNK_SIZE / 2, BILL_HEIGHT + 45 + CHUNK_SIZE / 2);
        chips.start();
    }

    private void moveBill(int side) {
        if (side == bill.getSide() || !billAlive)
            return; // Don't do anything if we're already on this side.. or if we're dead.... Jim
        bill.setSide(side);
        checkForDead();
    }

    private void showPause(boolean anim) {

        Gdx.app.debug(TAG, "showing pause dialog");
        isPaused = true;
        pausedDialog.show(chunk_counter, totalTime, anim);
        if (!billAlive)
            ProfileManager.getProfile().addPlay(chunk_counter, totalTime);
    }

    @Override
    public boolean isAlive() {
        return billAlive;
    }

    @Override
    public void dispose() {
        Gdx.app.debug(TAG, "Disposing resources");
        pausedDialog.dispose();
    }

    // ------ Input Processor stuff
    @Override
    public boolean keyDown(int keycode) {
        if (!billAlive)
            return false;
        if (keycode == Input.Keys.RIGHT) {
            moveBill(RIGHT);
            eject(LEFT);
        }
        if (keycode == Input.Keys.LEFT) {
            moveBill(LEFT);
            eject(RIGHT);
        }
        if (keycode == Input.Keys.BACK || keycode == Input.Keys.ESCAPE)
            showPause(true);
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        // Noy used
        return false;
    }

    /**
     * This is not American football... But we will celebrate touchdown events with some chopping action.
     *
     * @param screenX X location of touch in real pixels
     * @param screenY X location of touch in real pixels
     * @param pointer only relevant for multitouch
     * @param button  button pressed
     * @return false
     */
    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {

        // Check for hit of the pause button.

        float vx = screenX * xMul;
        float vy = screenY * yMul;
        Gdx.app.debug(TAG, "x: " + vx + "  Y: " + vy);
        //VIEWPORT_GUI_WIDTH - 150, 135, 100, 100
        if (vx > (VIEWPORT_GUI_WIDTH - 200) && vx < VIEWPORT_GUI_WIDTH && vy > 135 && vy < 235) {
            showPause(true);
            return false;
        }
        if (!billAlive)
            return false;
        if (screenX < Gdx.graphics.getWidth() / 2) {
            moveBill(LEFT);
            eject(RIGHT);
        } else {
            moveBill(RIGHT);
            eject(LEFT);
        }
        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        // Not used
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        // Not used
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        // Not used
        return false;
    }
}