edu.bsu.issgame.core.sequence.SequenceMatchScreen.java Source code

Java tutorial

Introduction

Here is the source code for edu.bsu.issgame.core.sequence.SequenceMatchScreen.java

Source

/*
  Copyright 2015 Ball State University
    
  This file is part of Collaboration Station.
    
  Collaboration Station is free software: you can redistribute it
  and/or modify it under the terms of the GNU General Public
  License as published by the Free Software Foundation, either
  version 3 of the License, or (at your option) any later version.
    
  Collaboration Station is distributed in the hope that it will
  be useful, but WITHOUT ANY WARRANTY; without even the implied
  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.  See the GNU General Public License for more details.
    
  You should have received a copy of the GNU General Public
  License along with Collaboration Station.  If not, see
  <http://www.gnu.org/licenses/>.
*/
package edu.bsu.issgame.core.sequence;

import static com.google.common.base.Preconditions.checkState;
import static playn.core.PlayN.graphics;
import static playn.core.PlayN.log;
import static playn.core.PlayN.pointer;

import java.util.Map;

import playn.core.Layer;
import playn.core.Pointer;
import playn.core.Pointer.Event;
import pythagoras.f.IPoint;
import pythagoras.f.Point;
import tripleplay.anim.AnimBuilder;
import tripleplay.sound.MultiClip;
import tripleplay.sound.SoundBoard;
import tripleplay.ui.Label;
import tripleplay.ui.Root;
import tripleplay.ui.Shim;
import tripleplay.ui.layout.AxisLayout;
import tripleplay.util.PointerInput;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import edu.bsu.issgame.core.AbstractGameScreen;
import edu.bsu.issgame.core.CommonGameScreenUI;
import edu.bsu.issgame.core.CustomStyleSheet;
import edu.bsu.issgame.core.GameImage;
import edu.bsu.issgame.core.GameSound;
import edu.bsu.issgame.core.MinigameType;
import edu.bsu.issgame.core.Score;

public class SequenceMatchScreen extends CommonGameScreenUI {
    private static final IPoint percentOfScreen(float w, float h) {
        return new Point(graphics().width() * w, graphics().height() * h);
    }

    private static final ImmutableMap<SequenceItemType, SequenceButton> makeMap(SequenceItemType... types) {
        ImmutableMap.Builder<SequenceItemType, SequenceButton> builder = ImmutableMap.builder();
        for (SequenceItemType type : types) {
            SequenceButton button = new SequenceButton(type);
            builder.put(type, button);
        }
        return builder.build();
    }

    private static final float DURATION_OF_PRESSED_IMAGE = 600f;
    private static final float SCALE_TWEEN_TIME = 100f;
    private static final float SCALE_MAX = 1.05f;
    private static final float TOP_ROW = 0.30f;
    private static final float BOTTOM_ROW = 0.75f;
    private static final float LEFT = 0.20f;
    private static final float BETWEEN = 0.20f;
    private ImmutableMap<SequenceItemType, IPoint> buttonLocationMap = ImmutableMap.of(SequenceItemType.BIOLOGY,
            percentOfScreen(LEFT, TOP_ROW), SequenceItemType.EXERCISE, percentOfScreen(LEFT + BETWEEN, BOTTOM_ROW),
            SequenceItemType.SLEEP, percentOfScreen(LEFT + 2 * BETWEEN, TOP_ROW), //
            SequenceItemType.PHYSICS, percentOfScreen(LEFT + 3 * BETWEEN, BOTTOM_ROW));

    private final PointerInput input = new PointerInput();
    private final ImmutableMap<SequenceItemType, SequenceButton> buttonMap = makeMap(SequenceItemType.BIOLOGY, //
            SequenceItemType.EXERCISE, //
            SequenceItemType.SLEEP, //
            SequenceItemType.PHYSICS);
    private Sequence sequence;
    private Root root;
    private SoundBoard soundBoard = new SoundBoard();
    private Label watchCloselyLabel = new Label("Watch closely...");
    private Map<SequenceItemType, MultiClip> clipMap = Maps.newHashMap();

    public SequenceMatchScreen(AbstractGameScreen previous) {
        super(previous);
        createRoot();
        setBackground(GameImage.SEQUENCE_MATCHING_BACKGROUND.image);
        placeSequenceButtonsOnScreen();
        sequence = new Sequence().addItem();
        root.add(watchCloselyLabel);
        initializeSoundBoard();
        enterState(pauseBetweenRoundsState);
    }

    private void createRoot() {
        root = iface.createRoot(AxisLayout.vertical(), //
                CustomStyleSheet.instance(), layer)//
                .setSize(graphics().width(), graphics().height());
        root.add(new Shim(15, 15));
    }

    private void initializeSoundBoard() {
        for (SequenceItemType type : SequenceItemType.values()) {
            MultiClip clip = new MultiClip(soundBoard, type.sfx.path, 2, 0.4f);
            clipMap.put(type, clip);
        }
    }

    private void placeSequenceButtonsOnScreen() {
        for (SequenceButton button : buttonMap.values()) {
            IPoint ipoint = buttonLocationMap.get(button.getButtonType());
            layer.addAt(button.getImageLayer(), ipoint.x(), ipoint.y());
            registerInputOn(button);
        }
    }

    private void registerInputOn(final SequenceButton button) {
        input.register(button.getImageLayer(), new Pointer.Adapter() {
            @Override
            public void onPointerStart(Event event) {
                state.onPress(button);
            }

            @Override
            public void onPointerEnd(Event event) {
                state.onRelease(button);
            }
        });
    }

    private void playSfxFor(SequenceItemType buttonType) {
        clipMap.get(buttonType).reserve().play();
    }

    private void updateScore() {
        int delta = TriangularMathUtil.sum(sequence.getLength());
        Score scoreToAdd = Score.create().addScience(delta);
        score.update(score.get().add(scoreToAdd));
    }

    @Override
    protected void setMinigameType() {
        thisMinigame = MinigameType.PATTERN_REPEAT;
    }

    private void disableInput() {
        pointer().setListener(null);
    }

    private void enableInput() {
        pointer().setListener(input.plistener);
    }

    private State state;

    private void enterState(State newState) {
        if (this.state != null) {
            this.state.onExit();
        }
        this.state = newState;
        newState.onEnter();
    }

    private interface State {
        void onEnter();

        void onExit();

        void onPress(SequenceButton button);

        void onRelease(SequenceButton button);
    }

    private abstract class AbstractState implements State {

        private final String name;

        protected AbstractState(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }

        @Override
        public void onEnter() {
        }

        @Override
        public void onExit() {
        }

        @Override
        public void onPress(SequenceButton button) {
            logWarning(button);
        }

        @Override
        public void onRelease(SequenceButton button) {
            logWarning(button);
        }

        private void logWarning(SequenceButton button) {
            log().warn("Unexpected interaction with " + button + " from state " + toString());
        }
    }

    private final State showSequenceState = new AbstractState("showSequenceState") {

        private final int delayBetweenSequenceItems = 100;
        private final int delayBetweenWatchMessageAndSequenceStart = 500;
        private final Runnable showSequenceAction = new Runnable() {
            @Override
            public void run() {
                showSequence();
            }
        };
        private final Runnable enterYourTurnStateAction = new Runnable() {
            @Override
            public void run() {
                enterState(yourTurnState);
            }
        };
        private AnimBuilder builder;

        @Override
        public void onEnter() {
            AnimBuilder builder = anim.delay(0).then();
            if (sequence.getLength() == 1) {
                builder = builder.tweenAlpha(watchCloselyLabel.layer)//
                        .to(1f)//
                        .in(250f)//
                        .then()//
                        .delay(delayBetweenWatchMessageAndSequenceStart)//
                        .then()//
                        .tweenAlpha(watchCloselyLabel.layer)//
                        .to(0f)//
                        .in(250f)//
                        .then();
            }
            builder.action(showSequenceAction);
        }

        private void showSequence() {
            checkState(builder == null);
            builder = anim.delay(0).then();
            for (SequenceItemType buttonType : sequence.getSequence()) {
                animateButton(buttonType);
            }
            builder.action(enterYourTurnStateAction);
            builder = null;
        }

        private void animateButton(SequenceItemType buttonType) {
            final SequenceButton button = buttonMap.get(buttonType);
            builder = builder//
                    .action(new Runnable() {
                        @Override
                        public void run() {
                            button.setPressed();
                            playSfxFor(button.getButtonType());
                            pulseScale(button.getImageLayer());
                        }
                    })//
                    .then()//
                    .delay(DURATION_OF_PRESSED_IMAGE)//
                    .then()//
                    .action(new Runnable() {
                        @Override
                        public void run() {
                            button.setUnpressed();
                        }
                    })//
                    .then()//
                    .delay(delayBetweenSequenceItems)//
                    .then();
        }

        private void pulseScale(Layer buttonLayer) {
            final float holdDelay = Math.max(0, DURATION_OF_PRESSED_IMAGE - 2 * SCALE_TWEEN_TIME);
            anim.tweenScale(buttonLayer)//
                    .to(SCALE_MAX)//
                    .in(SCALE_TWEEN_TIME)//
                    .easeIn()//
                    .then()//
                    .delay(holdDelay)//
                    .then()//
                    .tweenScale(buttonLayer)//
                    .to(1f)//
                    .in(SCALE_TWEEN_TIME)//
                    .easeOut();
        }
    };

    private final State pauseBetweenRoundsState = new AbstractState("pauseBetweenRoundsState") {

        private final int DELAY_BETWEEN_ROUNDS = 500;

        private final Runnable enterShowSequenceState = new Runnable() {
            @Override
            public void run() {
                enterState(showSequenceState);
            }
        };

        @Override
        public void onEnter() {
            anim.delay(DELAY_BETWEEN_ROUNDS)//
                    .then()//
                    .action(enterShowSequenceState);
        }

    };

    private final State yourTurnState = new AbstractState("yourTurnState") {

        @Override
        public void onEnter() {
            activateAllButtons();
            enableInput();
        }

        private void activateAllButtons() {
            for (SequenceButton button : buttonMap.values()) {
                button.setActive();
            }
        }

        @Override
        public void onPress(SequenceButton button) {
            playSfxFor(button.getButtonType());
            button.setPressed();
            button.getImageLayer().setScale(SCALE_MAX);
        }

        @Override
        public void onRelease(SequenceButton button) {
            button.setUnpressed();
            if (sequence.isClickCorrect(button)) {
                handleCorrect();
            } else {
                handleIncorrect();
            }
            anim.tweenScale(button.getImageLayer())//
                    .to(1f)//
                    .easeOut()//
                    .in(SCALE_TWEEN_TIME);
        }

        private void handleCorrect() {
            sequence.incrementPointer();
            if (sequence.isComplete()) {
                updateScore();
                extendSequence();
                disableInput();
            }
        }

        private void extendSequence() {
            sequence.addItem();
            sequence.resetPointer();
            enterState(pauseBetweenRoundsState);
        }

        private void handleIncorrect() {
            enterState(incorrectState);
        }

        @Override
        public void onExit() {
            deactivateAllButtons();
        }

        private void deactivateAllButtons() {
            for (SequenceButton button : buttonMap.values()) {
                button.setInactive();
            }
        }
    };

    private final State incorrectState = new AbstractState("incorrectState") {

        private final Runnable enterPauseBetweenRoundsState = new Runnable() {
            @Override
            public void run() {
                enterState(pauseBetweenRoundsState);
            }
        };

        @Override
        public void onEnter() {
            GameSound.SEQUENCE_INCORRECT.sound.play();
            disableInput();
            sequence.reset();
            sequence.resetPointer();
            sequence.addItem();
            pauseAndShakeButtonsThenShowNewSequence();
        }

        private void pauseAndShakeButtonsThenShowNewSequence() {
            for (SequenceButton button : buttonMap.values()) {
                anim.shake(button.getImageLayer());
            }
            anim.shake(watchCloselyLabel.layer);
            anim.shake(layer)//
                    .then()//
                    .action(enterPauseBetweenRoundsState);
        }

    };

    @Override
    public void wasHidden() {
        super.wasHidden();
        disableInput();
    }

}