1010! Klooni, a free customizable puzzle game for Android and Desktop
    Copyright (C) 2017-2019  Lonami Exo @ lonami.dev

    This program 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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
package dev.lonami.klooni.game;

import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.TimeUtils;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import dev.lonami.klooni.Klooni;
import dev.lonami.klooni.serializer.BinSerializable;

public class TimeScorer extends BaseScorer implements BinSerializable {

    //region Members

    private final Label timeLeftLabel;

    private long startTime;
    private int highScore;

    // Indicates where we would die in time. Score adds to this, so we take
    // longer to die. To get the "score" we simply calculate `deadTime - startTime`
    private long deadTime;

    // We need to know when the game was paused to "stop" counting
    private long pauseTime;
    private int pausedTimeLeft;


    //region Static variables

    private static final long START_TIME = 30 * 1000000000L;

    // 2 seconds every 10 points: (2/10)*10^9 to get the nanoseconds
    private static final double SCORE_TO_NANOS = 0.2e+09d;
    private static final double NANOS_TO_SECONDS = 1e-09d;


    //region Constructor

    // The board size is required when calculating the score
    public TimeScorer(final Klooni game, GameLayout layout) {
        super(game, layout, Klooni.getMaxTimeScore());
        highScore = Klooni.getMaxTimeScore();

        Label.LabelStyle labelStyle = new Label.LabelStyle();
        labelStyle.font = game.skin.getFont("font");
        timeLeftLabel = new Label("", labelStyle);

        startTime = TimeUtils.nanoTime();
        deadTime = startTime + START_TIME;

        pausedTimeLeft = -1;


    //region Private methods

    private int nanosToSeconds(long nano) {
        return MathUtils.ceil((float) (nano * NANOS_TO_SECONDS));

    private long scoreToNanos(int score) {
        return (long) (score * SCORE_TO_NANOS);

    private int getTimeLeft() {
        return Math.max(nanosToSeconds(deadTime - TimeUtils.nanoTime()), 0);


    //region Public methods

    public int addBoardScore(int stripsCleared, int boardSize) {
        // Only clearing strips adds extra time
        long extraTime = scoreToNanos(calculateClearScore(stripsCleared, boardSize));
        deadTime += extraTime;
        super.addBoardScore(stripsCleared, boardSize);

        return nanosToSeconds(extraTime);

    public boolean isGameOver() {
        return TimeUtils.nanoTime() > deadTime;

    public String gameOverReason() {
        return "time is up";

    public void saveScore() {
        if (isNewRecord()) {

    protected boolean isNewRecord() {
        return getCurrentScore() > highScore;

    public void pause() {
        pauseTime = TimeUtils.nanoTime();
        pausedTimeLeft = getTimeLeft();

    public void resume() {
        if (pauseTime != 0L) {
            long difference = TimeUtils.nanoTime() - pauseTime;
            startTime += difference;
            deadTime += difference;

            pauseTime = 0L;
            pausedTimeLeft = -1;

    public void draw(SpriteBatch batch) {

        int timeLeft = pausedTimeLeft < 0 ? getTimeLeft() : pausedTimeLeft;
        timeLeftLabel.draw(batch, 1f);


    //region Serialization

    public void write(DataOutputStream out) throws IOException {
        // current/dead offset ("how long until we die"), highScore
        out.writeLong(TimeUtils.nanoTime() - startTime);

    public void read(DataInputStream in) throws IOException {
        // We need to use the offset, since the start time
        // is different and we couldn't save absolute values
        long deadOffset = in.readLong();
        deadTime = startTime + deadOffset;
        highScore = in.readInt();