Source: sketch.js

/*******************************************************************************
 *
 *	@file sketch.js A quick brick breaker game
 *
 *	@author Omar Essilfie-Quaye <omareq08+githubio@gmail.com>
 *	@version 1.0
 *	@date 18-June-2022
 *	@link https://omareq.github.io/brick-breaker/
 *	@link https://omareq.github.io/brick-breaker/docs/
 *
 *******************************************************************************
 *
 *                   GNU General Public License V3.0
 *                   --------------------------------
 *
 *   Copyright (C) 2022 Omar Essilfie-Quaye
 *
 *   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
 *   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 this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *****************************************************************************/

/**
 * Enum of the possible game states
 *
 * @type       {Enum}
 *
 * @example
 * let gameMode = {
 *    start: 1,
 *    newLevel: 2,
 *    play: 3,
 *    end: 4
 *};
 */
let gameMode = {
    start: 1,
    newLevel: 2,
    play: 3,
    end: 4
};

/**
 * The current game mode
 *
 * @type       {gameMode}
 *
 * @see gameMode
 *
 * @example
 * let currentGameMode = gameMode.start;
 */
let currentGameMode = gameMode.start;

/**
 * Saves the start of a new level in milliseconds to provide debouncing of key
 * presses
 *
 * @type       {number}
 */
let newLevelStartTime;

/**
 * Stores the current level object
 *
 * @type       {Level}
 *
 * @see level
 */
let level;

/**
 * The current level number.  Used to select the correct layout at the start of
 * each new level.
 *
 * @type       {number}
 *
 * @see levelLayouts
 * @see Level
 * @see loadNextLevel
 */
let selectedLevel = 0;

/**
 * The ball object
 *
 * @type       {Ball}
 *
 * @see Ball
 */
let ball;

/**
 * The paddle object
 *
 * @type       {Paddle}
 *
 * @see Paddle
 */
let paddle;

/**
 * The number of lives remaining
 *
 * @type       {number}
 *
 * @see showLives
 */
let lives = 3;

/**
 * Flag to see if cheat mode is on.  When true the paddle will automatically
 * position itself under the ball so that the user doesn't have to.  This is a
 * useful debugging tool when the game needs to be played for long periods.
 *
 * @type       {boolean}
 */
let cheatMode = false;

/**
 * Saves the start time of cheat mode in milliseconds to provide debouncing to
 * key presses.
 *
 * @type       {number}
 */
let cheatModeToggleTime = 0;

/**
 * Show the aim point that the AI for the cheat mode wants to hit
 *
 * @type       {boolean}
 */
let showAimPoint = true;

/**
 * The position vector for the AI target location
 *
 * @type       {p5.Vector}
 */
let goalAimPoint;

/**
 * Noise offset for paddle position when it is cheat mode.  This stops the
 * paddle from always sending the ball straight up.
 *
 * @type       {number}
 *
 * @see Paddle
 */
let noiseOffset = 0;

/**
 * Shows the number of lives remaining on the canvas.
 *
 * @see lives
 */
function showLives() {
    push();
    fill(255);
    textSize(0.03 * height);
    textAlign(LEFT, BOTTOM);
    text("Lives: " + lives, 10, height);
    pop();
}

/**
 * Loads the next level from the levelLayouts array and resets the ball and
 * paddle to the centre.
 *
 * @see levelLayouts
 * @see Level
 */
function loadNextLevel() {
    if(selectedLevel >= levelLayouts.length) {
        currentGameMode = gameMode.end;
        return;
    }
    level = new Level(cols1, rows1, levelLayouts[selectedLevel]);
    let ballR = 7;
    ball = new Ball(width/2, 0.94 * height - ballR / 2,
        random(-2, 0), random(-3, -2),
        ballR);

    paddle = new Paddle();
}

/**
 * p5.js setup function, creates canvas.
 */
function setup() {
	let cnvSize;
	if(windowWidth > windowHeight) {
		cnvSize = 0.9 * windowHeight;
	} else {
		cnvSize = windowWidth;
	}
	let cnv = createCanvas(cnvSize, 0.7 * cnvSize);
	cnv.parent('sketch');
    loadNextLevel();
}

/**
 * p5.js draw function, is run every frame to create the desired animation
 */
function draw() {
	background(225);
    switch(currentGameMode) {
        case gameMode.start: {
            push();
            fill(135, 81, 153);
            textAlign(CENTER, CENTER);
            textSize(0.1 * height);
            text("Press Enter to Start", width / 2, height / 2);
            textSize(0.05 * height);
            text("Press Left Arrow and Right Arrow to move paddle",
                width / 2, 3 * height / 4);
            pop();
            if(keyIsPressed && (keyCode == ENTER)) {
                currentGameMode = gameMode.newLevel;
                newLevelStartTime = millis();
            }
        }
        break;
        case gameMode.newLevel: {
            level.draw();
            paddle.draw();
            ball.draw();
            showLives();
            if(keyIsPressed && (keyCode == ENTER)) {
                if(millis() - newLevelStartTime > 1000) {
                    currentGameMode = gameMode.play;
                }
            }
        }
        break;
        case gameMode.play: {
            showLives();
            level.checkBricksHitBy(ball);
            level.draw();

            if(keyIsDown(LEFT_ARROW)) {
                paddle.moveLeft();
            } else if(keyIsDown(RIGHT_ARROW)) {
                paddle.moveRight();
            }

            if(keyIsPressed && key.toLowerCase() == "c") {
                if(millis() - cheatModeToggleTime > 1000) {
                    cheatMode = !cheatMode;
                }
            }

            if(cheatMode) {
                let noiseAmp = 0.3;
                let n = noise(noiseOffset) * noiseAmp * paddle.w;
                noiseOffset += 0.01;

                let goalBrickPos = level.posHighestBrick();

                if(goalAimPoint == undefined) {
                    goalAimPoint = goalBrickPos;
                }

                let aimPointError = goalBrickPos.sub(goalAimPoint);
                let aimPointKp = 0.05;
                let deltaAimpoint = aimPointError.mult(aimPointKp);
                goalAimPoint = goalAimPoint.add(deltaAimpoint);

                let goalAngle = atan2(ball.x - goalAimPoint.x,
                    ball.y - goalAimPoint.y);

                if(showAimPoint) {
                    push();
                    noFill();
                    stroke(225, 20, 20);
                    strokeWeight(0.5 * ball.r);
                    ellipse(goalAimPoint.x, goalAimPoint.y,
                        4 * ball.r, 4 * ball.r);
                    line(goalAimPoint.x - 4 * ball.r, goalAimPoint.y,
                        goalAimPoint.x + 4 * ball.r, goalAimPoint.y);
                    line(goalAimPoint.x, goalAimPoint.y - 4 * ball.r,
                        goalAimPoint.x, goalAimPoint.y + 4 * ball.r);
                    pop();
                }

                goalAngle = constrain(goalAngle, -QUARTER_PI, QUARTER_PI);

                let goalPaddleOffset = map(goalAngle,
                    QUARTER_PI, -QUARTER_PI,
                    0, paddle.w);

                let goalPaddleX = ball.x - goalPaddleOffset - n;
                goalPaddleX = constrain(goalPaddleX,
                    ball.x - paddle.w,
                    ball.x + paddle.w);

                let errorPaddleX = paddle.x - goalPaddleX;
                let kp = 0.2;
                let deltaPaddleX = -kp * errorPaddleX;

                paddle.setX(paddle.x + deltaPaddleX);
                paddle.checkEdges();
            }
            paddle.isHitBy(ball);
            paddle.draw();

            ball.update();
            ball.draw();

            if(ball.y > paddle.y) {
                lives--;

                if(lives == 0) {
                    currentGameMode = gameMode.end;
                    return;
                }

                currentGameMode = gameMode.newLevel;
                newLevelStartTime = millis();
                paddle = new Paddle();
                let ballR = 7;
                ball = new Ball(width/2, 0.94 * height - ballR / 2,
                    random(-2, 0), random(-3, -2),
                    ballR);
            }

            if(level.win()) {
                currentGameMode = gameMode.newLevel;
                selectedLevel++;
                loadNextLevel();
            }
        }
        break;
        case gameMode.end: {
            push();
            fill(135, 81, 153);
            textAlign(CENTER, CENTER);
            textSize(0.1 * height);
            text("Game Over", width / 2, height / 2);
            pop();
        }
        break;
    }
}


Documentation generated by JSDoc 3.6.3 on Sat Jul 02 2022 09:28:00 GMT+0100 (BST)