예제 #1
0
  /**
   * Generate new colors. This method is executed by the callback action when the player selects a
   * valid rectangle. The purpose of this method is to regenerate the board and apply any required
   * checks to make sure that the game doesn't enter in an infinite loop.
   *
   * @param bounds the bounds that have to be regenerated.
   */
  private void generate(Bounds bounds) {
    // Generate new balls;
    game.getState()
        .getBoard()
        .randomize(
            new Coordinate(bounds.minX, bounds.minY), new Coordinate(bounds.maxX, bounds.maxY));

    // Check the new board for valid combinations.
    CombinationFinder newFinder = CombinationFinder.create(game.getState().getBoard());
    if (newFinder.getPossibleBounds().size() == 1) {
      // Only one combination? This is trouble.
      Bounds newCombinationBounds = newFinder.getCombination();
      if (newCombinationBounds.equals(bounds)) {
        // Oh, oh, in the same spot! So, they must be of the same color.
        // Therefore, we need to randomize some balls to avoid enter
        // an infinite loop.
        timer.setRunning(false);
        board.setColoured(false);
        game.getState().resetBoard();
        board.addAction(
            Actions.sequence(
                board.shake(10, 5, 0.05f),
                Actions.run(
                    new Runnable() {
                      @Override
                      public void run() {
                        board.setColoured(true);
                        timer.setRunning(true);
                      }
                    })));
      }
    }
    board.addAction(board.showRegion(bounds));
  }
예제 #2
0
  @Override
  public void onTimeOut() {
    // Disable any further interactions.
    board.clearSelection();
    board.setColoured(true);
    board.setTouchable(Touchable.disabled);
    timer.setRunning(false);
    game.player.playSound(SoundCode.GAME_OVER);

    // Update the score... and the record.
    int score = game.getState().getScore();
    int time = Math.round(game.getState().getElapsedTime());
    game.getPlatform().score().registerScore(score, time);
    game.getPlatform().score().flushData();

    // Save information about this game in the statistics.
    game.statistics.getTotalData().incrementValue("score", game.getState().getScore());
    game.statistics.getTotalData().incrementValue("games");
    game.statistics
        .getTotalData()
        .incrementValue("time", Math.round(game.getState().getElapsedTime()));
    game.getPlatform().statistics().saveStatistics(game.statistics);

    // Mark a combination that the user could do if he had enough time.
    if (game.getState().getWiggledBounds() == null) {
      CombinationFinder combo = CombinationFinder.create(game.getState().getBoard());
      game.getState().setWiggledBounds(combo.getCombination());
    }
    for (int y = 0; y < game.getState().getBoard().getSize(); y++) {
      for (int x = 0; x < game.getState().getBoard().getSize(); x++) {
        if (game.getState().getWiggledBounds() != null
            && !game.getState().getWiggledBounds().inBounds(x, y)) {
          board.getBall(x, y).addAction(Actions.color(Color.DARK_GRAY, 0.15f));
        }
      }
    }

    // Animate the transition to game over.
    board.addAction(Actions.delay(2f, board.hideBoard()));
    hud.addAction(Actions.delay(2f, Actions.fadeOut(0.25f)));

    // Head to the game over after all these animations have finished.
    getStage()
        .addAction(
            Actions.delay(
                2.5f,
                Actions.run(
                    new Runnable() {
                      @Override
                      public void run() {
                        getStage().getRoot().clearActions();
                        game.pushScreen(Screens.GAME_OVER);
                      }
                    })));

    // Mark the game as finished.
    game.getState().setTimeout(true);
  }
예제 #3
0
  @Override
  public void render(float delta) {
    super.render(delta);

    // If the timer is running, keep incrementing the timer.
    if (timer.isRunning()) {
      game.getState().addTime(delta);
      game.getState().setRemainingTime(timer.getSeconds());
    }

    // The user should be able to leave during the game.
    if (Gdx.input.isKeyJustPressed(Input.Keys.BACK)
        || Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
      if (!paused && !game.getState().isTimeout()) {
        pauseGame();
      }
    }
  }
예제 #4
0
  /**
   * This method pauses the game. It is executed in one of the following situations: first, the user
   * presses PAUSE button. Second, the user presses BACK button. Third, the user pauses the game
   * (when the game is restored the game is still paused).
   */
  private void pauseGame() {
    paused = true;

    // Show the pause dialog unless you have already stop the game.
    if (!game.getState().isTimeout()) {
      showPauseDialog();
    }

    // If the game has started, pause it.
    if (running && !game.getState().isTimeout()) {
      board.setColoured(false);
      board.setTouchable(Touchable.disabled);
      timer.setRunning(false);
    }
  }
예제 #5
0
  @Override
  public void show() {
    super.show();

    // Reset data
    if (!game.isRestoredState()) {
      game.getState().reset();
    } else {
      game.setRestoredState(false);
      if (game.getState().isTimeout()) {
        game.pushScreen(Screens.GAME_OVER);
      }
    }

    // The player is playing
    game.getState().setPlaying(true);
    score.setValue(game.getState().getScore());
    timer.setSeconds(game.getState().getRemainingTime());

    paused = running = askingLeave = false;

    if (!game.getState().isCountdownFinished()) {
      countdown(
          2,
          new Runnable() {
            @Override
            public void run() {
              game.getState().setCountdownFinished(true);

              // Start the game unless the user is leaving or is paused.
              if (!paused && !askingLeave) {
                running = true;
                board.setColoured(true);
                timer.setRunning(true);
                board.setTouchable(Touchable.enabled);
                game.player.playSound(SoundCode.SUCCESS);
              }
            }
          });
    } else {
      pauseGame();
    }
  }
예제 #6
0
  /**
   * This method resumes the game. It is executed in one of the following situations: first, the
   * user presses CONTINUE button on pause screen. Second, the user presses BACK on the pause
   * screen.
   */
  private void resumeGame() {
    paused = false;

    // If the countdown has finished but the game is not running is a
    // condition that might be triggered in one of the following cases.
    // 1) The user has paused the game during the countdown. When the
    //    countdown finishes, because the game is paused, the game does
    //    not start.
    // 2) The user was leaving the game during the countdown. Same.
    if (!running && game.getState().isCountdownFinished()) {
      running = true;
      game.player.playSound(SoundCode.SUCCESS);
    }

    if (running && !game.getState().isTimeout()) {
      board.setColoured(true);
      board.setTouchable(Touchable.enabled);
      timer.setRunning(true);
    }
  }
예제 #7
0
  @Override
  public void onSelectionSucceeded(final List<BallActor> selection) {
    // Extract the data from the selection.
    List<Ball> balls = new ArrayList<>();
    for (BallActor selectedBall : selection) balls.add(selectedBall.getBall());
    final Bounds bounds = Bounds.fromBallList(balls);

    // Change the colors of the selected region.
    board.addAction(
        Actions.sequence(
            board.hideRegion(bounds),
            Actions.run(
                new Runnable() {
                  @Override
                  public void run() {
                    for (BallActor selectedBall : selection) {
                      selectedBall.setColor(Color.WHITE);
                    }
                    generate(bounds);
                    // Reset the cheat
                    game.getState().setCheatSeen(false);
                    game.getState().setWiggledBounds(null);
                  }
                })));

    // Give some score to the user.
    ScoreCalculator calculator = new ScoreCalculator(game.getState().getBoard(), bounds);
    int givenScore = calculator.calculate();
    game.getState().addScore(givenScore);
    score.giveScore(givenScore);

    // Put information about this combination in the stats.
    int rows = bounds.maxY - bounds.minY + 1;
    int cols = bounds.maxX - bounds.minX + 1;
    String size = Math.max(rows, cols) + "x" + Math.min(rows, cols);
    game.statistics.getSizesData().incrementValue(size);

    BallColor color = board.getBall(bounds.minX, bounds.minY).getBall().getColor();
    game.statistics.getColorData().incrementValue(color.toString().toLowerCase());
    game.statistics.getTotalData().incrementValue("balls", rows * cols);
    game.statistics.getTotalData().incrementValue("combinations");

    // Now, display the score to the user. If the combination is a
    // PERFECT combination, just display PERFECT.
    int boardSize = game.getState().getBoard().getSize() - 1;
    if (bounds.equals(new Bounds(0, 0, boardSize, boardSize))) {
      // Give score
      Label label = new Label("PERFECT", game.getSkin(), "monospace");
      label.setX((getStage().getViewport().getWorldWidth() - label.getWidth()) / 2);
      label.setY((getStage().getViewport().getWorldHeight() - label.getHeight()) / 2);
      label.setFontScale(3);
      label.setAlignment(Align.center);
      label.addAction(Actions.sequence(Actions.moveBy(0, 80, 0.5f), Actions.removeActor()));
      getStage().addActor(label);
      game.player.playSound(SoundCode.PERFECT);

      // Give time
      float givenTime = Constants.SECONDS - timer.getSeconds();
      timer.giveTime(givenTime, 4f);
    } else {
      // Was special?
      boolean special = givenScore != rows * cols;
      // Give score
      showPartialScore(givenScore, bounds, special);
      game.player.playSound(SoundCode.SUCCESS);

      // Give time
      float givenTime = 4f;
      timer.giveTime(givenTime, 0.25f);
    }
  }
예제 #8
0
  @Override
  public void setUpInterface(Table table) {
    // Create the actors for this screen.
    timer = new TimerActor(Constants.SECONDS, game.getSkin());
    score = new ScoreActor(game.getSkin());
    board = new BoardActor(game.getBallAtlas(), game.getState().getBoard());

    // Add the help button.
    ImageButton help = new ImageButton(game.getSkin(), "blueHelp");
    help.addListener(
        new ChangeListener() {
          @Override
          public void changed(ChangeEvent event, Actor actor) {
            // Don't act if the game hasn't started yet.
            if (!timer.isRunning() || game.getState().isTimeout()) {
              event.cancel();
              return;
            }

            // Don't do anything if there are less than 5 seconds.
            if (timer.getSeconds() <= 5 && game.getState().getWiggledBounds() == null) {
              game.player.playSound(SoundCode.FAIL);
              event.cancel();
              return;
            }

            // Wiggle a valid combination.
            if (game.getState().getWiggledBounds() == null) {
              CombinationFinder finder = CombinationFinder.create(game.getState().getBoard());
              game.getState()
                  .setWiggledBounds(
                      finder
                          .getPossibleBounds()
                          .get(MathUtils.random(finder.getPossibleBounds().size() - 1)));
            }
            board.addAction(board.shake(game.getState().getWiggledBounds(), 10, 5, 0.1f));

            if (!game.getState().isCheatSeen()) {
              // Subtract some time.
              float subtractedTime = 5f;
              final float step = subtractedTime / 10;
              getStage()
                  .addAction(
                      Actions.repeat(
                          10,
                          Actions.delay(
                              0.01f,
                              Actions.run(
                                  new Runnable() {
                                    @Override
                                    public void run() {
                                      timer.setSeconds(timer.getSeconds() - step);
                                    }
                                  }))));
              game.getState().setCheatSeen(true);
            }

            event.cancel();
          }
        });

    // Add the pause button.
    ImageButton pause = new ImageButton(game.getSkin(), "blueCross");
    pause.addListener(
        new ChangeListener() {
          @Override
          public void changed(ChangeEvent event, Actor actor) {
            game.player.playSound(SoundCode.FAIL);
            pauseGame();
            event.cancel();
          }
        });

    // Disable game until countdown ends.
    timer.setRunning(false);
    board.setTouchable(Touchable.disabled);

    // Add subscribers.
    timer.addSubscriber(this);
    board.addSubscriber(this);
    score.setScoreListener(this);

    /*
     * Fill the HUD, which is the display that appears over the board with
     * all the information. The HUD is generated differently depending on
     * the aspect ratio of the device. If the device is 4:3, the HUD is
     * compressed to avoid making the board small. Otherwise, it's expanded
     * to the usual size.
     */
    boolean landscape = Gdx.graphics.getWidth() > Gdx.graphics.getHeight();
    float aspectRatio =
        landscape
            ? (float) Gdx.graphics.getWidth() / Gdx.graphics.getHeight()
            : (float) Gdx.graphics.getHeight() / Gdx.graphics.getWidth();
    hud = new Table();

    if (aspectRatio < 1.5f) {
      // Compact layout: buttons and score in the same line.
      hud.add(new BorderedContainer(game.getSkin(), help)).size(50).padBottom(10);
      hud.add(new BorderedContainer(game.getSkin(), score))
          .height(50)
          .width(Constants.VIEWPORT_WIDTH / 2)
          .space(10)
          .expandX()
          .fillX();
      hud.add(new BorderedContainer(game.getSkin(), pause)).size(50).padBottom(10).row();
      hud.add(new BorderedContainer(game.getSkin(), timer))
          .colspan(3)
          .fillX()
          .height(40)
          .padBottom(20)
          .row();
    } else {
      // Large layout: buttons above timer, score below timer (classic).
      hud.add(new BorderedContainer(game.getSkin(), help))
          .size(50)
          .spaceLeft(10)
          .padBottom(10)
          .align(Align.left);
      hud.add(new BorderedContainer(game.getSkin(), pause))
          .size(50)
          .spaceRight(10)
          .padBottom(10)
          .align(Align.right)
          .row();
      hud.add(new BorderedContainer(game.getSkin(), timer))
          .colspan(2)
          .fillX()
          .expandX()
          .height(40)
          .padBottom(10)
          .row();
      hud.add(new BorderedContainer(game.getSkin(), score))
          .colspan(2)
          .height(60)
          .width(Constants.VIEWPORT_WIDTH / 2)
          .align(Align.center)
          .padBottom(10)
          .row();
    }

    if (aspectRatio > 1.6) {
      hud.padBottom(20);
    }

    table.add(hud).fillX().expandY().align(Align.top).row();
    table.add(board).fill().expand().row();
  }