Evolution of the Click Game ~Sequel: Explaining New Features & Improvements~

In our previous article, we introduced the basic design of a simple click game and how the score-up mechanism works. As a sequel, this article explains the following improvements and new features implemented to deliver a more authentic gaming experience.
You can play the updated game here.

[click_game]

You can play the previous version of the game here.

[my_game]

目次

Adding a Time Limit and a Start Button

In the first part, there was no time limit, and players simply accumulated scores through straightforward clicks.

Improvements Made

  • Directly rendered the start button on the Canvas.
  • When the game has not started, a blue “START" button is displayed in the center of the screen, allowing the user to simply click it to begin.
  • During gameplay, the button display is removed to prevent operational confusion.
  • Added a mechanism to limit the game to a certain number of seconds (e.g., 10 seconds), starting a countdown when the start button is pressed.

Today’s High Score and All-Time High Score

In the first part, a simple mechanism for saving high scores was implemented.

Improvements Made

  • With the addition of a time limit, it is now possible to record high scores achieved within a single game session.
  • “Today’s High Score" and “All-Time High Score" are now managed separately.
  • Using local storage to record scores by date, a mechanism has been implemented to allow fresh challenges every day.

Enhanced Gameplay: Difficulty Levels and Enemy Health

In the first part, the focus was primarily on simple score increases, and the concept of difficulty did not exist.

Improvements Made

  • Introduced a system where the player levels up every time a certain number of clicks are cleared, gradually increasing the difficulty.
  • Additionally, added a progress bar representing the enemy’s health.
    • The progress bar decreases with each click, and its color changes (Green → Yellow → Red) depending on the remaining health.
    • Text such as “Enemy Health" is also displayed to make the remaining health visually intuitive.

Improved Target Visuals

In the first part, the click target was represented by a simple red circle.

Improvements Made

  • Changed the target representation to images.
  • Prepared multiple image URLs that change every time a level is cleared, enriching the game’s visuals.
  • Adjusted image loading timing to ensure they are rendered reliably.

Other Improvements

  • Double-Activation Prevention Function
    When integrating the game start button onto the Canvas, processing was added to prevent duplicate timer activations caused by rapid clicking.
  • Refining User Experience Details
    Along with the addition of various features, debugging logs and reset processing were also improved in pursuit of comfortable operability.

Code

document.addEventListener("DOMContentLoaded", function() {
    const canvas = document.getElementById("click-game-canvas");
    const progressBar = document.getElementById("click-game-progress");
    if (!canvas || !progressBar) return;

    const ctx = canvas.getContext("2d");
    canvas.width = 300;
    canvas.height = 300;

    let level = 1;
    let defaultClicks = 2; // Initial click count
    let targetClicks = defaultClicks;
    let addClicks = 2; // Clicks to add per level
    let ClickGameTimeLeft = 10;
    let gameActive = false;
    let timer;
    let remainingClicks;
    let targetX, targetY;
    const targetRadius = 20;

    const today = new Date().toISOString().split('T')[0]; // 📅 Today's date (YYYY-MM-DD)
    const savedData = JSON.parse(localStorage.getItem("clickGameScores")) || {};

    // Retrieve high scores from local storage (0 if none)
    let todayHighScore = savedData[today] || 0;
    let highScore = savedData["highScore"] || 0;

    const targetImages = clickGameData.imageUrls; // Image URLs passed from PHP
    let targetImage = new Image(); // Defined as a global variable

    function loadRandomTargetImage() {
        targetImage.src = targetImages[Math.min(level - 1, targetImages.length - 1)];
        targetImage.style.left = `${Math.random() * (canvas.width - 48)}px`;
        targetImage.style.top = `${Math.random() * (canvas.height - 48)}px`;
    }

    function drawGame() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "black";
        ctx.font = "18px Arial";
        // Display current level
        ctx.fillText("Level: " + level, 200, 40);
        // Display remaining time
        ctx.fillText("Time: " + ClickGameTimeLeft, 200, 20);
        // Display high scores
        ctx.fillText("High Score: " + highScore, 10, 20);
        ctx.fillText("Today High Score: " + todayHighScore, 10, 40);

        if (gameActive) {
            ctx.drawImage(targetImage, targetX - targetRadius, targetY - targetRadius, targetRadius * 2, targetRadius * 2);
        } else {
            // Draw game start button
            ctx.fillStyle = "blue";
            ctx.fillRect(90, 120, 120, 40);

            ctx.fillStyle = "white";
            ctx.font = "20px Arial";
            ctx.fillText("START", 120, 147);
        }
    }

    function updateProgressBar() {
        const percentage = Math.max((remainingClicks / targetClicks) * 100, 0);
        progressBar.style.width = percentage + "%";

        // Change color
        if (percentage > 66) {
            progressBar.style.backgroundColor = "green"; // 2/3 or more → Green
        } else if (percentage > 33) {
            progressBar.style.backgroundColor = "yellow"; // 1/3 or more → Yellow
        } else {
            progressBar.style.backgroundColor = "red"; // Less than 1/3 → Red
        }
    }

    function moveTarget() {
        targetX = Math.random() * (canvas.width - 2 * targetRadius) + targetRadius;
        targetY = Math.random() * (canvas.height - 2 * targetRadius) + targetRadius;
        loadRandomTargetImage();
    }

    function checkClick(event) {
        if (!gameActive) return;

        const rect = canvas.getBoundingClientRect();
        const x = event.clientX - rect.left;
        const y = event.clientY - rect.top;
        const distance = Math.sqrt((x - targetX) ** 2 + (y - targetY) ** 2);

        if (distance < targetRadius) {
            remainingClicks--;
            updateProgressBar();

            if (remainingClicks <= 0) {
                levelUp();
                return;
            }

            moveTarget();
        }

        drawGame();
    }

    function levelUp() {
        clearInterval(timer);
        gameActive = false;
        ctx.fillStyle = "green";
        ctx.font = "24px Arial";
        ctx.fillText("Level Up!", 100, 150);

        setTimeout(() => {
            level++;
            targetClicks += addClicks;
            startGame();
        }, 2000);
    }

    function updateHighScore() {
        if (level > todayHighScore) {
            todayHighScore = level;
            savedData[today] = todayHighScore;
        }
        if (level > highScore) {
            highScore = level;
            savedData["highScore"] = highScore;
        }
        localStorage.setItem("clickGameScores", JSON.stringify(savedData));
    }

    function gameOver() {
        clearInterval(timer);
        gameActive = false;

        // Update high scores
        updateHighScore();

        ctx.fillStyle = "black";
        ctx.font = "24px Arial";
        ctx.fillText("Game Over!", 80, 150);
        ctx.fillText("High Score: " + highScore, 80, 180);
        ctx.fillText("Today High Score: " + todayHighScore, 80, 210);

        setTimeout(() => {
            level = 1; // Reset level
            targetClicks = defaultClicks;
            ClickGameTimeLeft = 10;
            drawGame();
        }, 3000);
    }

    function startGame() {
        if (gameActive) return; // Do not start during gameplay

        clearInterval(timer);
        ClickGameTimeLeft = 10;
        gameActive = true;
        remainingClicks = targetClicks;

        moveTarget();
        updateProgressBar();
        drawGame();

        timer = setInterval(() => {
            if (ClickGameTimeLeft > 0) {
                ClickGameTimeLeft--;
                drawGame();
            } else {
                gameOver();
            }
        }, 1000);
    }

    canvas.addEventListener("click", checkClick);
    // startButton.addEventListener("click", startGame);
    canvas.addEventListener("click", function(event) {
        const rect = canvas.getBoundingClientRect();
        const x = event.clientX - rect.left;
        const y = event.clientY - rect.top;

        if (!gameActive && x >= 90 && x <= 210 && y >= 120 && y <= 160) {
            startGame();
        }
    });


    drawGame();
});

Conclusion

With this update, the originally simple click game has evolved significantly in terms of strategy, visuals, and operability. By further expanding upon the foundations introduced in the first part, we are now able to provide users with fresh challenges and enjoyment.
Going forward, we will continue to pursue an even more engaging gaming experience through minor improvements and the addition of new features. Be sure to download the latest version and experience the evolved click game for yourself!