public class PositionComponent implements Component {
    public Vector2 position = new Vector2();
} public class SizeComponent implements Component {
    public float width = 1.f;
    public float height = 1.f;
} public class GameStateEntity extends Entity implements PhysicsSystem.BallPointContactListener {
    // game mechanics
    private final static int GAME_STATE_GAME_OVER = 0;
    private final static int GAME_STATE_PLAYING = 1;
    private final static int GAME_STATE_STARTING = 2;

    private int mGameState = GAME_STATE_STARTING;

    private PointEntity mPointLeft;
    private PointEntity mPointRight;

    private int mPointTotal = 0;
    private int mRound = 0;

    public GameStateEntity(PointEntity left, PointEntity right) {
        mPointLeft = left;
        mPointRight = right;
    }

    public int getPointsTotal() {
        return mPointTotal;
    }

    public int getRound() {
        return mRound;
    }

    @Override
    public void update() {
        if (mGameState == GAME_STATE_STARTING) {
            startGame();
        }
    }

    private void startGame() {
        if (mGameState == GAME_STATE_PLAYING) {
            return;
        }

        mEngine.getFirstEntityOfType(BallEntity.class).onGameStart();

        mPointLeft.setIsAvailable(true);
        mPointRight.setIsAvailable(true);

        mPointTotal = 0;
        mRound++;

        mGameState = GAME_STATE_PLAYING;

        mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                .activateGui(IngameGuiEntity.class);

        mEngine.getFirstEntityOfType(RollInputEntity.class)
                .resetInput();
    }

    public void onStartGame() {
        if (mGameState == GAME_STATE_GAME_OVER) {
            startGame();
        }
    }

    public void onGameOver() {
        if (mGameState != GAME_STATE_PLAYING) {
            return;
        }

        mGameState = GAME_STATE_GAME_OVER;

        mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                .activateGui(HighScoreGuiEntity.class);

        HighScoreController.addHighScore(mPointTotal);
    }

    @Override
    public void onPointContact(int pointType) {
        if (pointType == PhysicsSystem.BODY_USER_DATA_TYPE_POINT_LEFT) {
            if (!mPointLeft.getIsAvailable()) {
                return; // don't increase points if this point is not available
            }

            mPointLeft.setIsAvailable(false);
            mPointRight.setIsAvailable(true);
        } else {
            if (!mPointRight.getIsAvailable()) {
                return; // don't increase points if this point is not available
            }

            mPointLeft.setIsAvailable(true);
            mPointRight.setIsAvailable(false);
        }

        mPointTotal++;

        mEngine.getFirstEntityOfType(BallEntity.class).onScoreChanged(mPointTotal);

        mEngine.getFirstEntityOfType(IngameGuiEntity.class).onScoreChanged(mPointTotal);
    }
} public class GameWorldEntity extends Entity {
    private int mWorldWidth;
    private int mWorldHeight;

    public GameWorldEntity(int worldWidth, int worldHeight) {
        mWorldWidth = worldWidth;
        mWorldHeight = worldHeight;
    }

    public int getWorldWidth() {
        return mWorldWidth;
    }

    public int getWorldHeight() {
        return mWorldHeight;
    }
} public class GravityEntity extends Entity {

    public GravityEntity() {
        addComponent(new GravityComponent());
    }
} public class BallEntity extends Entity {

    private final float RADIUS = 26.f;
    private final float HEIGHT_START_OFFSET = 80.f;

    private final float BALL_FRICTION_BASE = 0.4f;
    private final float BALL_FRICTION_DECREASE = 0.02f;
    private final float BALL_FRICTION_MIN = 0.05f;

    private Vector2 mBallStartPosition;

    public BallEntity(int worldWidth, int stickHandleHeight) {
        mBallStartPosition = new Vector2(worldWidth * 0.5f, stickHandleHeight + HEIGHT_START_OFFSET);

        addComponent(new PositionComponent());
        addComponent(new SizeComponent());
        addComponent(new RotationComponent());
        addComponent(new TextureComponent());
        addComponent(new PhysicsComponent());
    }

    @Override
    public void create() {
        getComponentByType(TextureComponent.class).textureRegion = new TextureRegion(
                new Texture(Gdx.files.internal("ball.png")));

        SizeComponent sizeComponent = getComponentByType(SizeComponent.class);
        sizeComponent.width = RADIUS * 2.f;
        sizeComponent.height = RADIUS * 2.f;

        // create physics body
        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.DynamicBody;
        bodyDef.position.set(mBallStartPosition.x, mBallStartPosition.y);

        CircleShape ballShape = new CircleShape();
        ballShape.setRadius(RADIUS - 2.f);

        PhysicsSystem physicsSystem = mEngine.getFirstSystemOfType(PhysicsSystem.class);
        physicsComponent.body = physicsSystem.getWorld().createBody(bodyDef);
        physicsComponent.body.setUserData(new PhysicsSystem.BodyUserDate(PhysicsSystem.BODY_USER_DATA_TYPE_BALL));
        Fixture fixture = physicsComponent.body.createFixture(ballShape, 0.5f);
        fixture.setFriction(BALL_FRICTION_BASE);
        fixture.setRestitution(0.4f);

        ballShape.dispose();
    }

    @Override
    public void update() {
        if (checkForGameOver()) {
            mEngine.getFirstEntityOfType(GameStateEntity.class).onGameOver();
        }
    }

    @Override
    public void dispose() {

    }

    public void onGameStart() {
        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);
        physicsComponent.body.setTransform(mBallStartPosition.x, mBallStartPosition.y, 0);
        physicsComponent.body.setLinearVelocity(MathUtils.randomBoolean() ? -10.f : 10.f, 0.f);
        physicsComponent.body.setAngularVelocity(0.f);

        PositionComponent positionComponent = getComponentByType(PositionComponent.class);
        positionComponent.position.set(mBallStartPosition.x, mBallStartPosition.y);
    }

    public void onScoreChanged(int score) {
        final float friction = MathUtils.clamp(
                BALL_FRICTION_BASE - (BALL_FRICTION_DECREASE * score), BALL_FRICTION_MIN, 1.f);

        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);
        physicsComponent.body.getFixtureList().get(0).setFriction(friction);
    }

    public boolean checkForGameOver() {
        GameWorldEntity gameWorldEntity = mEngine.getFirstEntityOfType(GameWorldEntity.class);
        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);
        Vector2 position = physicsComponent == null ? null : physicsComponent.body.getPosition();

        if (position != null && gameWorldEntity != null) {
            return position.x < 0 ||
                    position.x > gameWorldEntity.getWorldWidth() ||
                    position.y < 0 ||
                    position.y > gameWorldEntity.getWorldHeight();
        }
        return true;
    }
} public class CameraEntity extends Entity {
    private Camera mCamera;

    public CameraEntity() {
        addComponent(new PositionComponent());
    }

    @Override
    public void create() {
        GameWorldEntity gameWorldEntity = mEngine.getFirstEntityOfType(GameWorldEntity.class);
        if (gameWorldEntity == null) {
            // cannot create a camera without a game world
            return;
        }

        mCamera = new OrthographicCamera(gameWorldEntity.getWorldWidth(), gameWorldEntity.getWorldHeight());
        mCamera.position.set(mCamera.viewportWidth / 2f, mCamera.viewportHeight / 2f, 0.f);

        PositionComponent positionComponent = getComponentByType(PositionComponent.class);
        positionComponent.position.set(mCamera.viewportWidth / 2f, mCamera.viewportHeight / 2f);
    }

    public Camera getCamera() {
        return mCamera;
    }

    public void update() {
        mCamera.position.set(getComponentByType(PositionComponent.class).position, 0.f);
        mCamera.update();
    }
} public class HighScoreGuiEntity extends Entity {

    private static final Color COLOR_FONT = new Color(0xffffffff);

    private BitmapFont mFont;

    private Skin mSkin;

    private Array<Label> mHighScoreLabel;

    public HighScoreGuiEntity(Skin skin, BitmapFont font) {
        mSkin = skin;
        mFont = font;

        mHighScoreLabel = new Array<Label>();

        addComponent(new GuiComponent());
    }

    public void create() {
        Stage stage = new Stage(new ScreenViewport());

        getComponentByType(GuiComponent.class).stage = stage;

        Table rootTable = new Table();

        // add margin on the bottom for iOS
        final float dialogBottomOffset = (Gdx.app.getType() == Application.ApplicationType.iOS) ? 220 : 0;

        final float dialogMargin = 50.f;
        final float dialogWidth = Gdx.graphics.getWidth() - (dialogMargin * 2.f);
        final float dialogHeight = Gdx.graphics.getHeight() - (dialogMargin * 2.f) - dialogBottomOffset;

        final float margin = dialogWidth * 0.03f;

        rootTable.setWidth(dialogWidth);
        rootTable.setHeight(dialogHeight);
        rootTable.setPosition(dialogMargin, dialogMargin + dialogBottomOffset);

        rootTable.background(
                new NinePatchDrawable(
                        new NinePatch(new Texture(Gdx.files.internal("blue_panel.png")), 24, 24, 24, 24)));

        Label label = new Label("GAME OVER", mSkin);
        label.setFontScale(1.6f);
        label.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        rootTable.add(label)
                .expand(true, false)
                .padTop(dialogHeight * 0.05f)
                .padBottom(dialogHeight * 0.02f);

        rootTable.row();

        Table scrollPaneTable = new Table(mSkin);
        ScrollPane scrollPane = new ScrollPane(scrollPaneTable);

        IntArray highScores = HighScoreController.fetchHighScores();
        final int maxHighScores = 32;

        for (int i = 0; i < maxHighScores; ++i) {
            boolean hasScore = i < highScores.size;

            Label scoreTextLabel = new Label("high score:", mSkin);
            scoreTextLabel.setFontScale(0.7f);
            scoreTextLabel.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
            scrollPaneTable.add(scoreTextLabel)
                    .expand()
                    .padRight(64.f)
                    .padBottom(24.f)
                    .padRight(16.f)
                    .center();

            Label scoreLabel = new Label("" + (hasScore ? highScores.get(i) : "-"), mSkin);
            scoreLabel.setFontScale(0.7f);
            scoreLabel.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
            scrollPaneTable.add(scoreLabel)
                    .expand()
                    .padBottom(24.f)
                    .padRight(16.f)
                    .center();

            scrollPaneTable.row();

            mHighScoreLabel.add(scoreLabel);
        }

        rootTable.add(scrollPane)
                .fill()
                .expand()
                .padTop(16.f)
                .padBottom(16.f);

        rootTable.row();

        // restart button
        TextButton restartButton = new TextButton("RESTART", mSkin);
        restartButton.getLabel().setFontScale(1.f);
        restartButton.getLabel().setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        restartButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                mEngine.getFirstEntityOfType(GameStateEntity.class).onStartGame();
            }
        });

        rootTable.add(restartButton)
                .expand(true, false)
                .width(dialogWidth - (dialogMargin * 2.f))
                .height(dialogHeight * 0.08f)
                .padTop(margin)
                .padBottom(margin * 0.5f)
                .padLeft(margin)
                .padRight(margin)
                .center();

        rootTable.row();

        Table leaderBoardTable = new Table();

        final float settingsWidth = mFont.getLineHeight() * 1.4f;

        TextButton leaderBoardButton = new TextButton("LEADERBOARD", mSkin);
        leaderBoardButton.getLabel().setFontScale(1.f);
        leaderBoardButton.getLabel().setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        leaderBoardButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                if (HighScoreController.getUserName() == null) {
                    mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                            .activateGui(UserNameGuiEntity.class);
                } else {
                    mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                            .activateGui(LeaderBoardGuiEntity.class);
                }
            }
        });

        leaderBoardTable.add(leaderBoardButton)
                .expand(true, false)
                .width(dialogWidth - (dialogMargin * 2.f) - (settingsWidth + margin))
                .height(dialogHeight * 0.08f)
                .padLeft(margin)
                .center();

        Drawable drawable = new TextureRegionDrawable(
                new TextureRegion(new Texture(Gdx.files.internal("ic_settings.png"))));

        ImageButton imageButton = new ImageButton(drawable);
        imageButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                        .activateGui(UserNameGuiEntity.class);
            }
        });

        leaderBoardTable.add(imageButton)
                .expand(false, false)
                .right()
                .width(settingsWidth)
                .height(settingsWidth)
                .pad(margin);

        rootTable.add(leaderBoardTable)
                .expand(true, false)
                .padTop(margin * 0.5f)
                .padBottom(margin)
                .padLeft(margin)
                .padRight(margin);

        stage.addActor(rootTable);
    }

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

        updateHighScore();
    }

    private void updateHighScore() {
        IntArray highScores = HighScoreController.fetchHighScores();

        for (int i = 0; i < Math.min(mHighScoreLabel.size, highScores.size); i++) {
            mHighScoreLabel.get(i).setText("" + highScores.get(i));
        }
    }

    public void dispose() {
        GuiComponent guiComponent = getComponentByType(GuiComponent.class);
        if (guiComponent != null) {
            guiComponent.stage.dispose();
        }
    }
} public class IngameGuiEntity extends Entity {

    private static final Color COLOR_FONT = new Color(0xffffffff);

    private BitmapFont mFont;

    private Label mScoreLabel;

    public IngameGuiEntity(BitmapFont font) {
        mFont = font;

        addComponent(new GuiComponent());
    }

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

        GameStateEntity gameStateEntity = mEngine.getFirstEntityOfType(GameStateEntity.class);

        onScoreChanged(gameStateEntity.getPointsTotal());
    }

    public void create() {
        Stage stage = new Stage(new ScreenViewport());
        getComponentByType(GuiComponent.class).stage = stage;

        Table gameScore = new Table();

        float scorePanelWidth = Gdx.graphics.getWidth() * 0.8f; // 80 % of screen
        float scorePanelHeight = Gdx.graphics.getHeight() * 0.2f; // 20 % of screen

        gameScore.setWidth(scorePanelWidth);
        gameScore.setHeight(scorePanelHeight);
        gameScore.setY(Gdx.graphics.getHeight() - (scorePanelHeight + 30.f));
        gameScore.setX((Gdx.graphics.getWidth() - scorePanelWidth) * 0.5f);

        gameScore.setBackground(new NinePatchDrawable(
                new NinePatch(new Texture(Gdx.files.internal("red_button13.png")), 24, 24, 24, 24)));
        gameScore.pad(32.f);

        Label scoreLabel = new Label("Score", new Label.LabelStyle(
                mFont, COLOR_FONT));
        scoreLabel.setFontScale(1.2f);

        gameScore.add(scoreLabel).expand();

        gameScore.row();

        mScoreLabel = new Label("" + 0, new Label.LabelStyle(mFont, COLOR_FONT));
        mScoreLabel.setFontScale(2.0f);

        gameScore.add(mScoreLabel).expand();

        stage.addActor(gameScore);
    }

    public void dispose() {
        GuiComponent guiComponent = getComponentByType(GuiComponent.class);
        if (guiComponent != null) {
            guiComponent.stage.dispose();
        }
    }

    // this will one day be an event
    public void onScoreChanged(int points) {
        if (mScoreLabel != null) {
            mScoreLabel.setText("" + points);
        }
    }
} public class LeaderBoardGuiEntity extends Entity {

    private static final Color COLOR_FONT = new Color(0xffffffff);

    private Skin mSkin;

    private BitmapFont mFont;

    private float mDefaultMargin;

    private Table mScrollPaneTable;

    private LeaderBoardController mController;

    private IntArray mRoundPosted;

    private int mLoadingRetries;

    private Array<GuiLeaderBoardEntry> mLeaderBoardEntries;

    private boolean mLeaderBoardDirty = false;

    private class GuiLeaderBoardEntry {
        int rank;
        String name;
        int score;
    }

    public LeaderBoardGuiEntity(Skin skin, BitmapFont font) {
        mSkin = skin;
        mFont = font;

        mLeaderBoardEntries = new Array<GuiLeaderBoardEntry>();

        mRoundPosted = new IntArray();

        mController = new LeaderBoardController();

        addComponent(new GuiComponent());
    }

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

        mLeaderBoardEntries.clear();
        mLeaderBoardDirty = true;

        mLoadingRetries = 3;

        submitLatestScore();
    }

    @Override
    public void create() {
        Stage stage = new Stage(new ScreenViewport());

        getComponentByType(GuiComponent.class).stage = stage;

        Table rootTable = new Table();
        // add margin on the bottom for iOS
        final float dialogBottomOffset = (Gdx.app.getType() == Application.ApplicationType.iOS) ? 220 : 0;

        final float dialogMargin = 50.f;
        final float dialogWidth = Gdx.graphics.getWidth() - (dialogMargin * 2.f);
        final float dialogHeight = Gdx.graphics.getHeight() - (dialogMargin * 2.f) - dialogBottomOffset;

        mDefaultMargin = dialogWidth * 0.03f;

        rootTable.setWidth(dialogWidth);
        rootTable.setHeight(dialogHeight);
        rootTable.setPosition(dialogMargin, dialogMargin + dialogBottomOffset);

        rootTable.background(
                new NinePatchDrawable(
                        new NinePatch(new Texture(Gdx.files.internal("blue_panel.png")), 24, 24, 24, 24)));

        // HEADER
        Label label = new Label("LEADERBOARD", mSkin);
        label.setFontScale(1.6f);
        label.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        rootTable.add(label)
                .expand(true, false)
                .padTop(dialogHeight * 0.05f)
                .padBottom(mDefaultMargin * 0.5f);

        rootTable.row();

        mScrollPaneTable = new Table(mSkin);
        ScrollPane scrollPane = new ScrollPane(mScrollPaneTable);

        rootTable.add(scrollPane)
                .fill()
                .expand()
                .padTop(mDefaultMargin * 0.5f)
                .padBottom(mDefaultMargin * 0.5f);
        rootTable.row();

        // RESTART button
        TextButton restartButton = new TextButton("RESTART", mSkin);
        restartButton.getLabel().setFontScale(1.f);
        restartButton.getLabel().setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        restartButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                mEngine.getFirstEntityOfType(GameStateEntity.class).onStartGame();
            }
        });

        // try to correctly position the bottom restart button
        rootTable.add(restartButton)
                .expand(true, false)
                .width(dialogWidth - (dialogMargin * 4.f))
                .height(dialogHeight * 0.08f)
                .padTop(mDefaultMargin * 0.5f)
                .padBottom(mDefaultMargin)
                .padLeft(mDefaultMargin)
                .padRight(mDefaultMargin)
                .center();

        stage.addActor(rootTable);
    }

    private void loadLeaderBoard() {
        LeaderBoardController controller = new LeaderBoardController();
        controller.fetchLeaderBoard(new LeaderBoardController.FetchLeaderBoardListener() {
            @Override
            public void onSuccess(Array<LeaderBoardEntry> leaderBoardEntries) {
                mLeaderBoardEntries.clear();

                for (LeaderBoardEntry entry : leaderBoardEntries) {
                    GuiLeaderBoardEntry guiEntry = new GuiLeaderBoardEntry();
                    guiEntry.rank = entry.rank;
                    guiEntry.name = entry.name;
                    guiEntry.score = entry.score;
                    mLeaderBoardEntries.add(guiEntry);
                }

                mLeaderBoardDirty = true;
            }

            @Override
            public void onFailed() {
                if (mLoadingRetries > 0) {
                    loadLeaderBoard();
                    mLoadingRetries--;
                }
            }
        });
    }

    private void addLeaderBoardEntries() {
        mScrollPaneTable.clear();

        for (int i = -1; i < mLeaderBoardEntries.size; i++) {
            String rank;
            String name;
            String score;

            if (i == -1) {
                rank = "RANK";
                name = "NAME";
                score = "SCORE";
            } else {
                rank = "" + mLeaderBoardEntries.get(i).rank;
                name = mLeaderBoardEntries.get(i).name;
                score = "" + mLeaderBoardEntries.get(i).score;
            }

            addLeadBoardRow(rank, name, score);
        }
    }

    private void addLeadBoardRow(String rank, String name, String score) {
        Label indexLabel = new Label(rank, mSkin);
        indexLabel.setFontScale(0.7f);
        indexLabel.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        mScrollPaneTable.add(indexLabel)
                .expand(true, false)
                .center()
                .padTop(mDefaultMargin * 0.5f)
                .padBottom(mDefaultMargin * 0.5f)
                .padRight(mDefaultMargin * 0.5f);

        Label scoreNameLabel = new Label(name, mSkin);
        scoreNameLabel.setFontScale(0.7f);
        scoreNameLabel.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        mScrollPaneTable.add(scoreNameLabel)
                .expand(true, false)
                .center()
                .padTop(mDefaultMargin * 0.5f)
                .padBottom(mDefaultMargin * 0.5f)
                .padRight(mDefaultMargin * 0.5f);

        Label scoreLabel = new Label(score, mSkin);
        scoreLabel.setFontScale(0.7f);
        scoreLabel.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        mScrollPaneTable.add(scoreLabel)
                .expand(true, false)
                .center()
                .padTop(mDefaultMargin * 0.5f)
                .padBottom(mDefaultMargin * 0.5f);

        mScrollPaneTable.row();
    }

    private void submitLatestScore() {
        GameStateEntity gameStateEntity = mEngine.getFirstEntityOfType(GameStateEntity.class);

        int pointTotal = gameStateEntity.getPointsTotal();
        int round = gameStateEntity.getRound();

        if (mRoundPosted.contains(round)) {
            loadLeaderBoard();
        } else {
            mRoundPosted.add(round);

            mController.addScore(new LeaderBoardController.AddScoreListener() {
                @Override
                public void onSuccess() {
                    loadLeaderBoard();
                }

                @Override
                public void onFailed() {
                    loadLeaderBoard();
                }
            }, pointTotal, HighScoreController.getUserName());
        }
    }

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

        if (mLeaderBoardDirty) {
            addLeaderBoardEntries();
            mLeaderBoardDirty = false;
        }
    }

    @Override
    public void dispose() {
        GuiComponent guiComponent = getComponentByType(GuiComponent.class);
        if (guiComponent != null && guiComponent.stage != null) {
            guiComponent.stage.dispose();
        }
    }
} public class PhysicsDebugEntity extends Entity {

    private Box2DDebugRenderer mDebugRenderer = null;

    public PhysicsDebugEntity() {
        addComponent(new DebugComponent());
    }

    @Override
    public void update() {
        CameraEntity cameraEntity = mEngine.getFirstEntityOfType(CameraEntity.class);
        PhysicsSystem physicsSystem = mEngine.getFirstSystemOfType(PhysicsSystem.class);

        if (cameraEntity != null && physicsSystem != null) {
            mDebugRenderer.render(physicsSystem.getWorld(), cameraEntity.getCamera().combined);
        }
    }

    @Override
    public void create() {
        mDebugRenderer = new Box2DDebugRenderer();
    }

    @Override
    public void dispose() {
        mDebugRenderer.dispose();
    }
} public class PointEntity extends Entity {

    final static float RADIUS = 12.f;
    private float mAnimationTime;

    private boolean mIsPointLeft = true;
    private boolean mIsAvailable = true;

    public PointEntity(boolean isPointLeft, Vector2 leafHandlePosition, float leafHandleWidth,
                       float leafHandleHeight) {
        mIsPointLeft = isPointLeft;
        mAnimationTime = mIsPointLeft ? 0.f : 1.f;

        final float pointOffsetX = 22.f;
        final float pointOffsetY = 32.f;

        addComponent(new PhysicsComponent());

        float pointOffsetXTotal = (leafHandleWidth * 0.5f) + pointOffsetX;
        if (isPointLeft) {
            pointOffsetXTotal = -pointOffsetXTotal;
        }

        PositionComponent positionComponent = new PositionComponent();
        positionComponent.position.set(
                leafHandlePosition.x + pointOffsetXTotal,
                leafHandlePosition.y + (leafHandleHeight * 0.5f) + pointOffsetY);
        addComponent(positionComponent);

        SizeComponent sizeComponent = new SizeComponent();
        sizeComponent.width = RADIUS * 2.f;
        sizeComponent.height = RADIUS * 2.f;
        addComponent(sizeComponent);

        RotationComponent rotationComponent = new RotationComponent();
        addComponent(rotationComponent);

        addComponent(new TextureComponent());

        addComponent(new VisibleComponent());
    }

    @Override
    public void create() {
        TextureComponent textureComponent = getComponentByType(TextureComponent.class);
        if (textureComponent != null) {
            textureComponent.textureRegion = new TextureRegion(
                    new Texture(Gdx.files.internal("element_yellow_polygon_glossy.png")));
        }

        // create physics body
        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);
        PositionComponent positionComponent = getComponentByType(PositionComponent.class);
        SizeComponent sizeComponent = getComponentByType(SizeComponent.class);

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.StaticBody;
        bodyDef.position.set(positionComponent.position);
        bodyDef.angle = (mIsPointLeft ? 10.f : -10.f) * MathUtils.degRad;

        CircleShape pointShape = new CircleShape();
        pointShape.setRadius(sizeComponent.width * 0.5f);

        PhysicsSystem physicsSystem = mEngine.getFirstSystemOfType(PhysicsSystem.class);

        physicsComponent.body = physicsSystem.getWorld().createBody(bodyDef);
        physicsComponent.body.setUserData(
                new PhysicsSystem.BodyUserDate(
                        mIsPointLeft ? PhysicsSystem.BODY_USER_DATA_TYPE_POINT_LEFT : PhysicsSystem.BODY_USER_DATA_TYPE_POINT_RIGHT));
        Fixture fixtureLeftPoint = physicsComponent.body.createFixture(pointShape, 0.f);
        fixtureLeftPoint.setSensor(true);

        pointShape.dispose();
    }

    public void update() {
        mAnimationTime += Gdx.graphics.getDeltaTime();

        SizeComponent sizeComponent = getComponentByType(SizeComponent.class);
        float size = (RADIUS * 2.f) + (MathUtils.sin(mAnimationTime * (mIsPointLeft ? 4.0f : 4.1f)) * 2.f);
        sizeComponent.width = size;
        sizeComponent.height = size;
    }

    public void setIsAvailable(boolean isAvailable) {
        mIsAvailable = isAvailable;

        VisibleComponent visibleComponent = getComponentByType(VisibleComponent.class);
        if (visibleComponent != null) {
            visibleComponent.visible = isAvailable;
        }
    }

    public boolean getIsAvailable() {
        return mIsAvailable;
    }
} public class RollInputEntity extends Entity {

    // SMOOTH THE INPUT SAMPLING, HIGHER VALUE LESS RESPONSIVE GAME SO HARDER
    private static final int INPUT_SAMPLE_SIZE = 50;

    private FloatArray mInputRollList;
    private float mInputRollAverage;
    private int mInsertIndex;

    public RollInputEntity() {
        mInputRollList = new FloatArray(INPUT_SAMPLE_SIZE);
        mInputRollAverage = 0.f;
        mInsertIndex = 0;
    }

    public void update() {
        // prevent user from manipulating input to much
        addValue(MathUtils.clamp(Gdx.input.getRoll(), -25.f, 25.f));

        // normalised normalisedGravity
        Vector2 gravity = new Vector2(0, -1).rotate(mInputRollAverage).nor();

        // find the gravity entity
        GravityComponent gravityComponent = mEngine.getComponentFromFirstEntity(
                GravityEntity.class, GravityComponent.class);
        if (gravityComponent != null) {
            gravityComponent.normalisedGravity.set(gravity);
        }
    }

    public void resetInput() {
        mInputRollList.clear();
        mInsertIndex = 0;
    }

    private void addValue(float value) {
        mInsertIndex = (mInsertIndex + 1) % INPUT_SAMPLE_SIZE;

        if (mInsertIndex >= mInputRollList.size) {
            mInputRollList.add(value);
        } else {
            mInputRollList.set(mInsertIndex, value);
        }

        float total = 0.f;
        for (int i = 0; i < mInputRollList.size; ++i) {
            total += mInputRollList.get(i);
        }

        mInputRollAverage = total / mInputRollList.size;
    }
} public class SpriteEntity extends Entity {

    private String mTexturePath;

    public SpriteEntity(String texturePath, Vector2 position, float width) {
        init(texturePath, position, width, -1.f, 0.f);
    }

    public SpriteEntity(String texturePath, Vector2 position, float width, float height) {
        init(texturePath, position, width, height, 0.f);
    }

    public SpriteEntity(String texturePath, Vector2 position, float width, float height, float rotationDegree) {
        init(texturePath, position,  width, height, rotationDegree);
    }

    protected void init(String texturePath, Vector2 position, float width, float height, float rotationDegree) {
        mTexturePath = texturePath;

        PositionComponent positionComponent = new PositionComponent();
        positionComponent.position = new Vector2(position);
        addComponent(positionComponent);

        SizeComponent sizeComponent = new SizeComponent();
        sizeComponent.width = width;
        sizeComponent.height = height;
        addComponent(sizeComponent);

        RotationComponent rotationComponent = new RotationComponent();
        rotationComponent.degree = rotationDegree;
        addComponent(rotationComponent);

        addComponent(new TextureComponent());
    }

    @Override
    public void create() {
        TextureComponent textureComponent = getComponentByType(TextureComponent.class);
        if (textureComponent != null) {
            Texture texture = new Texture(Gdx.files.internal(mTexturePath));
            textureComponent.textureRegion = new TextureRegion(texture);

            SizeComponent sizeComponent = getComponentByType(SizeComponent.class);
            if (sizeComponent != null && sizeComponent.height == -1.f) {
                float aspect = (float)texture.getWidth() / (float)texture.getHeight();
                sizeComponent.height = sizeComponent.width / aspect;
            }
        }
    }

    @Override
    public void dispose() {
        TextureComponent textureComponent = getComponentByType(TextureComponent.class);
        if (textureComponent != null && textureComponent.textureRegion != null) {
            textureComponent.textureRegion.getTexture().dispose();
        }
    }
} public class StickLeafEntity extends Entity {

    public StickLeafEntity(int worldWidth, int handleHeight) {
        final float leafWidth = worldWidth * 0.666f; // 2/3 of screen width
        final float leafHeight = 20.f;

        addComponent(new TextureComponent());
        addComponent(new PhysicsComponent());

        SizeComponent sizeComponent = new SizeComponent();
        sizeComponent.width = leafWidth;
        sizeComponent.height = leafHeight;
        addComponent(sizeComponent);

        PositionComponent positionComponent = new PositionComponent();
        positionComponent.position.set(worldWidth * 0.5f, handleHeight + (leafHeight * 0.5f));
        addComponent(positionComponent);

        addComponent(new TextureComponent());
    }

    @Override
    public void create() {
        getComponentByType(TextureComponent.class).textureRegion = new TextureRegion(
                new Texture(Gdx.files.internal("stick_leaf.png")));

        PositionComponent positionComponent = getComponentByType(PositionComponent.class);
        SizeComponent sizeComponent = getComponentByType(SizeComponent.class);

        // create physics body
        PhysicsComponent physicsComponent = getComponentByType(PhysicsComponent.class);

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.StaticBody;
        bodyDef.position.set(
                positionComponent.position.x,
                positionComponent.position.y);

        // Create a polygon shape
        PolygonShape leafPhysicsBox = new PolygonShape();
        leafPhysicsBox.setAsBox(sizeComponent.width * 0.5f, sizeComponent.height * 0.5f);

        PhysicsSystem physicsSystem = mEngine.getFirstSystemOfType(PhysicsSystem.class);
        physicsComponent.body = physicsSystem.getWorld().createBody(bodyDef);
        physicsComponent.body.setUserData(
                new PhysicsSystem.BodyUserDate(PhysicsSystem.BODY_USER_DATA_TYPE_OTHER));
        Fixture fixture = physicsComponent.body.createFixture(leafPhysicsBox, 0.f);
        fixture.setFriction(0.4f);
        fixture.setRestitution(0.4f);

        leafPhysicsBox.dispose();
    }

    @Override
    public void dispose() {
        getComponentByType(TextureComponent.class).textureRegion.getTexture().dispose();
    }
} public class UserNameGuiEntity extends Entity {

    private static final Color COLOR_FONT = new Color(0xffffffff);

    private Skin mSkin;

    private BitmapFont mFont;

    private TextField mNameInput;

    private Image mErrorFeedbackImage;

    public UserNameGuiEntity(Skin skin, BitmapFont font) {
        mSkin = skin;
        mFont = font;

        addComponent(new GuiComponent());
    }

    @Override
    public void create() {
        final Stage stage = new Stage(new ScreenViewport());
        getComponentByType(GuiComponent.class).stage = stage;

        Table rootTable = new Table();
        // add margin on the bottom for iOS
        final float dialogBottomOffset = (Gdx.app.getType() == Application.ApplicationType.iOS) ? 220 : 0;

        final float dialogMargin = 50.f;
        final float dialogWidth = Gdx.graphics.getWidth() - (dialogMargin * 2.f);
        final float dialogHeight = Gdx.graphics.getHeight() - (dialogMargin * 2.f) - dialogBottomOffset;

        rootTable.setWidth(dialogWidth);
        rootTable.setHeight(dialogHeight);
        rootTable.setPosition(dialogMargin, dialogMargin + dialogBottomOffset);

        rootTable.background(
                new NinePatchDrawable(
                        new NinePatch(new Texture(Gdx.files.internal("blue_panel.png")), 24, 24, 24, 24)));

        Label label = new Label("name", mSkin);
        label.setFontScale(1.6f);
        label.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        rootTable.add(label)
                .expand(true, false)
                .padTop(dialogHeight * 0.1f)
                .padBottom(dialogHeight * 0.025f);
        rootTable.row();

        Label label2 = new Label("What is your name?", mSkin);
        label2.setFontScale(1.f);
        label2.setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        rootTable.add(label2)
                .expand(true, false)
                .padBottom(32.f)
                .padTop(32.f);
        rootTable.row();

        Table inputTable = new Table();

        TextField.TextFieldStyle textFieldStyle = mSkin.get(TextField.TextFieldStyle.class);
        textFieldStyle.font = mFont;
        textFieldStyle.background.setLeftWidth(16.f);
        textFieldStyle.background.setRightWidth(16.f);
        textFieldStyle.background.setTopHeight(16.f);
        textFieldStyle.background.setBottomHeight(16.f);

        mNameInput = new TextField("", mSkin);
        mNameInput.setMaxLength(HighScoreController.MAX_NAME_LENGTH);
        mNameInput.setAlignment(Align.center);

        final float errorFeedbackWidth = mFont.getLineHeight();
        final float padTextField = 16.f;

        inputTable.add(mNameInput)
                .width(dialogWidth - (dialogMargin * 5.f))
                .padLeft(errorFeedbackWidth + padTextField)
                .padRight(padTextField)
                .padBottom(dialogHeight * 0.2f);

        mErrorFeedbackImage = new Image(new Texture(Gdx.files.internal("red_cross.png")));
        inputTable.add(mErrorFeedbackImage)
                .width(errorFeedbackWidth)
                .height(errorFeedbackWidth)
                .padBottom(dialogHeight * 0.2f);

        mErrorFeedbackImage.setVisible(false);

        rootTable.add(inputTable)
                .expand()
                .width(dialogWidth - (dialogMargin * 4.f));

        rootTable.row();

        // continue button
        TextButton okButton = new TextButton("Continue", mSkin);
        okButton.getLabel().setFontScale(1.f);
        okButton.getLabel().setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        okButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                String username = mNameInput.getText();
                if (username != null
                        && username.length() >= HighScoreController.MIN_NAME_LENGTH
                        && username.length() <= HighScoreController.MAX_NAME_LENGTH) {
                    HighScoreController.setUserName(username);
                    mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                            .activateGui(LeaderBoardGuiEntity.class);
                } else {
                    mErrorFeedbackImage.setVisible(true);
                }
            }
        });

        rootTable.add(okButton)
                .expand(true, false)
                .width(dialogWidth - (dialogMargin * 4.f))
                .height(dialogHeight * 0.08f)
                .padTop(16.f)
                .padBottom(16.f)
                .padLeft(32.f)
                .padRight(32.f)
                .center();
        rootTable.row();

        // cancel button
        TextButton restartButton = new TextButton("CANCEL", mSkin);
        restartButton.getLabel().setFontScale(1.f);
        restartButton.getLabel().setStyle(new Label.LabelStyle(mFont, COLOR_FONT));
        restartButton.addCaptureListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                mEngine.getFirstSystemOfType(GuiRendererSystem.class)
                        .activateGui(HighScoreGuiEntity.class);
            }
        });

        // try to correctly position the bottom restart button
        rootTable.add(restartButton)
                .expand(true, false)
                .width(dialogWidth - (dialogMargin * 4.f))
                .height(dialogHeight * 0.08f)
                .padTop(16.f)
                .padBottom(32.f)
                .padLeft(32.f)
                .padRight(32.f)
                .center();

        stage.addActor(rootTable);
    }

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

        String userName = HighScoreController.getUserName();
        if (mNameInput != null && userName != null) {
            mNameInput.setText(userName);
        }

        mErrorFeedbackImage.setVisible(false);
    }

    @Override
    public void dispose() {
        GuiComponent guiComponent = getComponentByType(GuiComponent.class);
        if (guiComponent != null && guiComponent.stage != null) {
            guiComponent.stage.dispose();
        }
    }
} public class GuiRendererSystem extends System {

    private Class<? extends Entity> mActiveGui;

    @Override
    public void resume() {
        Array<Entity> entities = mEngine.getAllEntitiesWithComponentOfType(GuiComponent.class);
        for (Entity entity : entities) {
            if (mActiveGui != null && mActiveGui.isInstance(entity)) {
                continue;
            }

            entity.becomePaused();
        }
    }

    public GuiRendererSystem() {
        addComponentType(GuiComponent.class);
    }

    public void activateGui(Class<? extends Entity> guiEntityClass) {
        if (mActiveGui != null) {
            Entity activeGui = mEngine.getFirstEntityOfType(mActiveGui);
            activeGui.becomePaused();
        }

        mActiveGui = guiEntityClass;

        if (mActiveGui != null) {
            Entity toBecomeActiveEntity = mEngine.getFirstEntityOfType(mActiveGui);
            toBecomeActiveEntity.becomeActive();

            GuiComponent guiComponent = toBecomeActiveEntity.getComponentByType(GuiComponent.class);
            if (guiComponent != null) {
                Gdx.input.setInputProcessor(guiComponent.stage);
            }
        }
    }

    @Override
    public void updateFromEntities(Array<Entity> entities) {
        for (Entity entity : entities) {
            VisibleComponent visibleComponent = entity.getComponentByType(VisibleComponent.class);
            if (visibleComponent != null && !visibleComponent.visible) {
                continue;
            }

            GuiComponent guiComponent = entity.getComponentByType(GuiComponent.class);
            if (guiComponent != null && guiComponent.stage != null) {
                guiComponent.stage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
                guiComponent.stage.draw();
            }
        }
    }
} public class PhysicsSystem extends System {

    public interface BallPointContactListener {
        void onPointContact(int type);
    }

    public final static int BODY_USER_DATA_TYPE_BALL = 0;
    public final static int BODY_USER_DATA_TYPE_POINT_LEFT = 1;
    public final static int BODY_USER_DATA_TYPE_POINT_RIGHT = 2;
    public final static int BODY_USER_DATA_TYPE_OTHER = 3;

    public static class BodyUserDate {
        public BodyUserDate(int type) {
            this.type = type;
        }

        int type = BODY_USER_DATA_TYPE_OTHER;
    }

    private final float BASE_GRAVITY = 150.f;

    private float mAccumulator = 0.f;
    private World mWorld;
    private boolean mIsPaused = false;

    private BallPointContactListener mBallPointContactListener = null;

    public PhysicsSystem(BallPointContactListener ballPointContactListener) {
        addComponentType(PhysicsComponent.class);

        mBallPointContactListener = ballPointContactListener;
    }

    public void setIsPaused(boolean isPaused) {
        mIsPaused = isPaused;
    }

    public World getWorld() {
        return mWorld;
    }

    @Override
    public void create() {
        Box2D.init();

        mWorld = new World(new Vector2(0, BASE_GRAVITY), true);
        mWorld.setContactListener(new ContactListener() {
            @Override
            public void beginContact(Contact contact) {
                BodyUserDate userDateA = (BodyUserDate) contact.getFixtureA().getBody().getUserData();
                BodyUserDate userDateB = (BodyUserDate) contact.getFixtureB().getBody().getUserData();

                // check if there was a contact between the left or right point
                if (userDateA != null && userDateB != null) {
                    if ((userDateA.type == BODY_USER_DATA_TYPE_POINT_LEFT && userDateB.type == BODY_USER_DATA_TYPE_BALL) ||
                            (userDateA.type == BODY_USER_DATA_TYPE_BALL && userDateB.type == BODY_USER_DATA_TYPE_POINT_LEFT)) {
                        // touched left ball
                        mBallPointContactListener.onPointContact(BODY_USER_DATA_TYPE_POINT_LEFT);

                    } else if ((userDateA.type == BODY_USER_DATA_TYPE_POINT_RIGHT && userDateB.type == BODY_USER_DATA_TYPE_BALL) ||
                            (userDateA.type == BODY_USER_DATA_TYPE_BALL && userDateB.type == BODY_USER_DATA_TYPE_POINT_RIGHT)) {
                        // touched right ball
                        mBallPointContactListener.onPointContact(BODY_USER_DATA_TYPE_POINT_RIGHT);
                    }
                }
            }

            @Override
            public void endContact(Contact contact) {

            }

            @Override
            public void preSolve(Contact contact, Manifold oldManifold) {

            }

            @Override
            public void postSolve(Contact contact, ContactImpulse impulse) {

            }
        });
    }

    public void updateFromEntities(Array<Entity> entities) {
        // if the game is not running don't update the physics
        if (mIsPaused) {
            return;
        }

        GravityComponent gravityComponent = mEngine.getComponentFromFirstEntity(
                GravityEntity.class, GravityComponent.class);
        if (gravityComponent != null) {
            Vector2 gravity = new Vector2(gravityComponent.normalisedGravity);
            mWorld.setGravity(gravity.scl(BASE_GRAVITY));
        }

        // update object position
        for (Entity entity : entities) {
            PhysicsComponent physicsComp = entity.getComponentByType(PhysicsComponent.class);
            PositionComponent positionComp = entity.getComponentByType(PositionComponent.class);
            RotationComponent rotationComp = entity.getComponentByType(RotationComponent.class);

            if (physicsComp == null) {
                continue;
            }

            if (positionComp != null) {
                positionComp.position.set(physicsComp.body.getPosition());
            }

            if (rotationComp != null) {
                rotationComp.degree = physicsComp.body.getAngle() * MathUtils.radDeg;
            }
        }

        // some constants taken from documentation, could be tweaked
        final float timeStep = 1.f / 45.f;
        final int velocityIterations = 6;
        final int positionIterations = 2;

        float deltaTime = Gdx.graphics.getDeltaTime();

        // fixed time step
        // max frame time to avoid spiral of death (on slow devices)
        float frameTime = Math.min(deltaTime, 0.25f);
        mAccumulator += frameTime;
        while (mAccumulator >= timeStep) {
            mWorld.step(timeStep, velocityIterations, positionIterations);
            mAccumulator -= timeStep;
        }
    }

    @Override
    public void dispose() {
        mWorld.dispose();
    }
} public class SpriteRenderSystem extends System {
    private SpriteBatch mSpriteBatch;
    private Array<Entity> mZOrderedEntities;

    public SpriteRenderSystem() {
        addComponentType(TextureComponent.class);
        addComponentType(PositionComponent.class);
        addComponentType(SizeComponent.class);
    }

    @Override
    public void create() {
        mSpriteBatch = new SpriteBatch();
        mZOrderedEntities = new Array<Entity>();
    }

    public void refreshZOrder() {
        mZOrderedEntities.clear();
    }

    @Override
    public void updateFromEntities(Array<Entity> entities) {
        // update the (first) camera
        CameraEntity cameraEntity = mEngine.getFirstEntityOfType(CameraEntity.class);
        if (cameraEntity != null) {
            cameraEntity.update();

            mSpriteBatch.setProjectionMatrix(cameraEntity.getCamera().combined);
        }

        // only sort if this is required
        if (mZOrderedEntities.size == 0 && entities.size != 0) {
            mZOrderedEntities.addAll(entities);

            // this could be a performance killer, should do this once and only recalculate on change
            mZOrderedEntities.sort(new Comparator<Entity>() {
                @Override
                public int compare(Entity entity1, Entity entity2) {
                    ZIndexComponent zIndexComponent1 = entity1.getComponentByType(ZIndexComponent.class);
                    ZIndexComponent zIndexComponent2 = entity2.getComponentByType(ZIndexComponent.class);
                    int zIndex1 = zIndexComponent1 == null ? 0 : zIndexComponent1.index;
                    int zIndex2 = zIndexComponent2 == null ? 0 : zIndexComponent2.index;

                    return zIndex1 == zIndex2 ? 0 : zIndex1 > zIndex2 ? 1 : 0;
                }
            });
        }

        mSpriteBatch.begin();

        for (Entity entity : mZOrderedEntities) {
            VisibleComponent visibleComponent = entity.getComponentByType(VisibleComponent.class);
            if (visibleComponent != null && !visibleComponent.visible) {
                continue;
            }

            TextureComponent textureComponent = entity.getComponentByType(
                    TextureComponent.class);
            PositionComponent positionComponent = entity.getComponentByType(
                    PositionComponent.class);
            SizeComponent sizeComponent = entity.getComponentByType(
                    SizeComponent.class);
            RotationComponent rotationComponent = entity.getComponentByType(
                    RotationComponent.class);

            // need a texture, position and size, implicit by the componentsType
            if (textureComponent == null || positionComponent == null || sizeComponent == null) {
                continue;
            }

            float rotation = rotationComponent == null ? 0.f : rotationComponent.degree;

            mSpriteBatch.draw(
                    textureComponent.textureRegion,
                    positionComponent.position.x - (sizeComponent.width * 0.5f),
                    positionComponent.position.y - (sizeComponent.height * 0.5f),
                    sizeComponent.width * 0.5f,
                    sizeComponent.height * 0.5f,
                    sizeComponent.width,
                    sizeComponent.height,
                    1.f,
                    1.f,
                    rotation);
        }

        mSpriteBatch.end();
    }

    @Override
    public void dispose() {
        if (mSpriteBatch != null) {
            mSpriteBatch.dispose();
        }
    }
} public class Engine implements GameLifecycleListener {
    private Array<Entity> mEntities;
    private ObjectMap<String, Entity> mFirstEntities;
    private Array<System> mSystems;

    public Engine() {
        mEntities = new Array<Entity>();
        mFirstEntities = new ObjectMap<String, Entity>();
        mSystems = new Array<System>();
    }

    public Engine registerSystem(System system) {
        mSystems.add(system);
        system.setEngine(this);
        return this;
    }

    public Engine registerEntity(Entity entity) {
        entity.setEngine(this);

        String entityName = entity.getClass().getName();
        if (!mFirstEntities.containsKey(entityName)) {
            mFirstEntities.put(entityName, entity);
        }

        mEntities.add(entity);

        return this;
    }

    @Override
    public void create() {
        for (System system : mSystems) {
            system.create();
        }

        for (Entity entity : mEntities) {
            entity.create();
        }
    }

    @Override
    public void resume() {
        for (System system : mSystems) {
            system.resume();
        }
    }

    @Override
    public void update() {
        // first update the entity states
        for (int i = 0; i < mEntities.size; ++i) {
            Entity entity = mEntities.get(i);
            if (entity.getState() == Entity.STATE_BECOMING_ACTIVE) {
                entity.resume();
            } else if (entity.getState() == Entity.STATE_BECOMING_PAUSED) {
                entity.pause();
            }
        }

        for (int i = 0; i < mEntities.size; ++i) {
            Entity entity = mEntities.get(i);
            if (entity.getState() == Entity.STATE_ACTIVE) {
                entity.update();
            }
        }

        for (System system : mSystems) {
            Array<Class<? extends Component>> componentsTypes = system.getComponentsType();
            Array<Entity> matchingEntities = new Array<Entity>();

            for (int i = 0; i < mEntities.size; ++i) {
                Entity entity = mEntities.get(i);
                if (entity.getState() == Entity.STATE_ACTIVE &&
                        entity.matchesComponentTypes(componentsTypes)) {
                    matchingEntities.add(entity);
                }
            }

            system.updateFromEntities(matchingEntities);
        }
    }

    @Override
    public void pause() {
        for (System system : mSystems) {
            system.pause();
        }
    }

    @Override
    public void dispose() {
        for (System system : mSystems) {
            system.dispose();
        }

        for (Entity entity : mEntities) {
            entity.dispose();
        }
    }

    /**
     * Get the first entity of the E
     * @param type
     * @param <E>
     * @return
     */
    public <E extends Entity> E getFirstEntityOfType(Class<E> type) {
        Entity entity = mFirstEntities.get(type.getName());
        return entity == null ? null : (E) entity;
    }

    /**
     * get the component from a certain type C of the first entity from type E
     * @param entityType
     * @param componentType
     * @param <E>
     * @param <C>
     * @return
     */
    public <E extends Entity, C extends Component> C getComponentFromFirstEntity(
            Class<E> entityType, Class<C> componentType) {
        E entity = getFirstEntityOfType(entityType);
        return (entity == null) ? null : entity.getComponentByType(componentType);
    }

    /**
     * get the first system of a type
     * @param type
     * @param <S>
     * @return
     */
    public <S extends System> S getFirstSystemOfType(Class<S> type) {
        for (System system : mSystems) {
            if (type.isInstance(system)) {
                return (S) system;
            }
        }
        return null;
    }

    /**
     * get all entities of type E
     * @param type
     * @param <E>
     * @return
     */
    public <E extends Entity> Array<E> getAllEntitiesOfType(Class<E> type) {
        Array<E> entities = new Array<E>();
        for (Entity entity : mEntities) {
            if (type.isInstance(entity)) {
                entities.add((E) entity);
            }
        }
        return entities;
    }

    public <C extends Component> Array<Entity> getAllEntitiesWithComponentOfType(Class<C> componentType) {
        Array<Entity> entities = new Array<Entity>();
        for (Entity entity : mEntities) {
            if (entity.getComponentByType(componentType) != null) {
                entities.add(entity);
            }
        }
        return entities;
    }
}
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.balanceball.component.PositionComponent;
import com.balanceball.component.SizeComponent;
import com.balanceball.enity.GameStateEntity;
import com.balanceball.enity.GameWorldEntity;
import com.balanceball.enity.GravityEntity;
import com.balanceball.enity.BallEntity;
import com.balanceball.enity.CameraEntity;
import com.balanceball.enity.HighScoreGuiEntity;
import com.balanceball.enity.IngameGuiEntity;
import com.balanceball.enity.LeaderBoardGuiEntity;
import com.balanceball.enity.PhysicsDebugEntity;
import com.balanceball.enity.PointEntity;
import com.balanceball.enity.RollInputEntity;
import com.balanceball.enity.SpriteEntity;
import com.balanceball.enity.StickLeafEntity;
import com.balanceball.enity.UserNameGuiEntity;
import com.balanceball.system.GuiRendererSystem;
import com.balanceball.system.PhysicsSystem;
import com.balanceball.system.SpriteRenderSystem;
import com.sec.Engine;
package com.balanceball;


/**
 * Created by tijs on 14/07/2017.
 */

public class BalanceBallSec extends ApplicationAdapter {

    private static final String TAG = Balanceball.class.getSimpleName();

    private static final Color COLOR_BACKGROUND = new Color(0xb8eaf9ff);

    private ShowAdmobListener mAdMobListener = null;

    private Engine mEngine;

    private static final int WORLD_WIDTH = 300;

    private static boolean DEBUG_RENDERER = false;

    private BitmapFont mFont;
    private Skin mSkin;

    private boolean mHasShownAdd = false;

    public BalanceBallSec() {

    }

    public BalanceBallSec(ShowAdmobListener listener) {
        mAdMobListener = listener;
    }

    @Override
    public void create () {
        init();

        mEngine.create();
    }

    private void initGuiResources() {
        FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("kenvector_future.ttf"));
        FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
        parameter.size = (int) (16.f * Gdx.graphics.getDensity());
        mFont = generator.generateFont(parameter);
        generator.dispose();

        mSkin = new Skin(Gdx.files.internal("skin/flat-earth-ui.json"));
    }

    private void init() {
        initGuiResources();

        // debug system
        PhysicsDebugEntity physicsDebugEntity = DEBUG_RENDERER ? new PhysicsDebugEntity() : null;

        // renderer
        final float aspectRatio = (float) Gdx.graphics.getHeight() / (float)Gdx.graphics.getWidth();
        int worldHeight = (int) ((float)WORLD_WIDTH * aspectRatio);

        GameWorldEntity gameWorldEntity = new GameWorldEntity(WORLD_WIDTH, worldHeight);

        // gravity
        GravityEntity gravityEntity = new GravityEntity();

        // render system
        SpriteRenderSystem spriteRenderSystem = new SpriteRenderSystem();

        // camera
        CameraEntity cameraEntity = new CameraEntity();