//Pengupop-JS
//Version: 20251219
//Author: DarkRanger <darkranger.no-ip.org>
//License: GPL v3
const version = "20251219";
const hasTouchscreen = "ontouchend" in document;
const fps = 60;
const timeStepMs = 8;
const timeStep = 0.008;
const fieldHeight = 14;
const maxFieldWidth = 8;
const bubbleSpeed = 500;
const maxX = 224;
const backgroundImg = new Image();
const baseImg = new Image();
const spbgImg = new Image();
const destroyGroupSnd = new Audio("snd/destroy_group.wav");
const launchSnd = new Audio("snd/launch.wav");
const reboundSnd = new Audio("snd/rebound.wav");
const stickSnd = new Audio("snd/stick.wav");
const defaultWidth = 640;
const defaultHeight = 480;
const bottomLine = 380;
const splitLine = 320;
backgroundImg.src = "img/backgrnd.png"
baseImg.src = "img/base.png";
spbgImg.src = "img/spbg.png";

var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = defaultWidth
canvas.height = defaultHeight;

var gameWidth = canvas.width;
var gameHeight = canvas.height;
var rect = canvas.getBoundingClientRect();
var bubbles = [];
var players = [];
var gameMode = 1;
var mobileMode = 0;
var inputLocked = 0;
var hasShot = 0;
var lastTick = Date.now();
var mouseClicked = false;
var gamepadEnabled = false;

const MODE_PRACTICE = 1;
const MODE_RESTART_PRACTICE = 2;
const MODE_VERSUS = 3;
const MODE_RESTART_VERSUS = 4;

class MovingBubble {
    constructor () {
        this.x = 0;
        this.y = 0;
        this.velx = 0;
        this.vely = 0;
        this.lastpaintx = 0;
        this.lastpainty = 0;
        this.falling = 0;
        this.evil = 0;
        this.color = 0;
    }
}

class PlayerState {
    constructor (xoff, yoff, id) {
        this.id = id;
        this.xoff = xoff;
        this.yoff = yoff;
        this.field = [...Array(14)].map(element => Array(16));
        this.bubble = 0;
        this.nextBubble = 0;
        this.angle = 0;
        this.right = 0;
        this.dirtyMinx = 0;
        this.dirtyMiny = 0;
        this.dirtyMaxx = 0;
        this.dirtyMaxy = 0;
        this.lastAngle = -1;
        this.mbubbles = [];
        this.evilBubbles = Array(128);
        this.evilBubbleCount = 0;
        this.maxEvil = 0;
        this.evilBubbleSeed = 0;
        this.won = 0;
        this.score = 0;

        for (let i = 0; i < 256; i ++) {
            this.mbubbles.push(new MovingBubble());
        }
    }

    shoot() {
        shoot(this, -1, -1);
    }

    center() {
        this.angle = 0;
        this.right = 0;
    }

    moveLeft() {
        this.right = -1;
    }

    moveRight() {
        this.right = 1;
    }

    stop() {
        this.right = 0;
    }
}

class MatchState {
    constructor () {
        this.matches = new Array(maxFieldWidth * fieldHeight);
        this.matchCount = 0;
    }
}

class SimpleElem {
    constructor() {
        this.body = "";
    }

    empty() {
        this.body = "";
    }

    append(content) {
        this.body = this.body + content;
    }

    build() {
        return this.body;
    }
}

function keyDown(event) {
    let code = event.code;
    let player = players[0];
    let player2 = players[0];

    if (gameMode == MODE_VERSUS) {
        player2 = players[1];
    }

     if (code == "KeyW") {
        event.preventDefault();
        player.shoot();
     }
     if (code == "ArrowUp") {
        event.preventDefault();
        player2.shoot();
     }
     if (code == "KeyS") {
        event.preventDefault();
        player.center();
     }
     if (code == "ArrowDown") {
        event.preventDefault();
        player2.center();
     }
     if (code == "KeyA") {
        event.preventDefault();
        player.moveLeft();
     }
     if (code == "ArrowLeft") {
        event.preventDefault();
        player2.moveLeft();
     }
     if (code == "KeyD") {
        event.preventDefault();
        player.moveRight();
     }
     if (code == "ArrowRight") {
        event.preventDefault();
        player2.moveRight();
     }

}

function keyUp(event) {
    let code = event.code;
    let player = players[0];
    let player2 = players[0];

    if (gameMode == MODE_VERSUS) {
        player2 = players[1];
    }

    if (code == "KeyA" && player.right == -1) {
        event.preventDefault();
        player.stop();
    }
    if (code == "ArrowLeft" && player2.right == -1) {
        event.preventDefault();
        player2.stop();
    }
    if (code == "KeyD" && player.right == 1) {
        event.preventDefault();
        player.stop();
    }
    if (code == "ArrowRight" && player2.right == 1) {
        event.preventDefault();
        player2.stop();
    }
    if (code == "Escape" && confirm("Restart?")) {
        if (gameMode == MODE_PRACTICE) {
            newPractice();
        }
        else if (gameMode == MODE_VERSUS) {
            newVersus();
        }
    }
    if (code == "F11") {
        alert("You can press 'M' to toggle fullscreen mode instead.")
    }
    if (code == "KeyM") {
        event.preventDefault();

        if (!document.fullscreenElement) {
            let fullscreen = document.getElementsByTagName("canvas")[0]; 
            fullscreen.requestFullscreen();
        }
        else {
            document.exitFullscreen?.();
        }
    }
}

function pointerDown(x, y) {
    let player = players[0];
    let player2 = players[0];

    if (gameMode == MODE_VERSUS) {
        player2 = players[1];
    }

    if (y >= bottomLine) {
        if (x < splitLine) {
            player.shoot();
        }
        else {
            player2.shoot();
        }
    }
    else {
        if (x < (splitLine / 2)) {
            player.moveLeft();
        }
        else if (x > (splitLine / 2) && x < splitLine) {
            player.moveRight();
        }
        else if (x > splitLine && x < (splitLine + (splitLine / 2))) {
            player2.moveLeft();
        }
        else if (x > (splitLine + (splitLine / 2))) {
            player2.moveRight();
        }
    }
}

function pointerUp(x, y) {
    let player = players[0];
    let player2 = players[0];

    if (gameMode == MODE_VERSUS) {
        player2 = players[1];
    }

    if (y < bottomLine) {
        if (x < splitLine && player.right != 0) {
            player.stop();
        }
        else if (player2.right != 0){
            player2.stop();
        }
    }
}

function mouseDown(event) {
    let x = event.clientX - rect.left;
    let y = event.clientY - rect.top;
    event.preventDefault();
    pointerDown(x, y);
}

function mouseUp(event) {
    let x = event.clientX - rect.left;
    let y = event.clientY - rect.top;
    event.preventDefault();
    pointerUp(x, y);
}

function touchStart(event) {
    let x = event.touches[0].clientX - rect.left;
    let y = event.touches[0].clientY - rect.top;
    event.preventDefault();
    pointerDown(x, y);
}

function touchEnd(event) {
    let x = event.changedTouches[0].clientX - rect.left;
    let y = event.changedTouches[0].clientY - rect.top;
    event.preventDefault();
    pointerUp(x, y);
}

function buttonDown(event) {
    let player = players[0];

    if (event == "left") {
        player.moveLeft();
    }
    if (event == "right") {
        player.moveRight();
    }
    if (event == "up") {
        player.shoot();
    }
}

function buttonUp(event) {
    let player = players[0];

    if ((event == "left" || event == "right") && player.right != 0) {
        player.stop()
    }
}

function gamepadMovement() {
    if (!gamepadEnabled) {
        return;
    }

    let gamepads = navigator.getGamepads();

    for (let i = 0; i < gamepads.length; i++) {
        if (!gamepads[i] || i >= 2) {
            continue;
        }

        let axisValue = Math.round(gamepads[i].axes[0]);

        if (axisValue == 0) {
            players[i].stop();
        }
        else if (axisValue < 1) {
            players[i].moveLeft();
        }
        else if (axisValue >= 1) {
            players[i].moveRight();
        }
    }
}

function gamepadShoot() {
    if (!gamepadEnabled) {
        return;
    }

    let gamepads = navigator.getGamepads();

    for (let i = 0; i < gamepads.length; i++) {
        if (!gamepads[i] || i >= 2) {
            continue;
        }

        for (let j = 0; j < gamepads[i].buttons.length; j++) {
            if (gamepads[i].buttons[j].pressed) {
                players[i].shoot();
            }
        }
    }
}

function getWidth(y) {
    if (y % 2 == 0) {
        return 8;
    }
    else {
        return 7;
    }
}

function popupDialog(msg) {
    console.log(msg);
    alert(msg);
}

function shoot(player, color, velocity) {
    let mbubble = new MovingBubble;
    let speed = (velocity == -1) ? bubbleSpeed : velocity;
    hasShot = 1;

    mbubble.falling = 0;
    mbubble.evil = 0;
    mbubble.velx = Math.sin(player.angle / 180 * Math.PI) * speed;
    mbubble.vely = -Math.cos(player.angle / 180 * Math.PI) * speed;
    mbubble.x = 112;
    mbubble.y = 350;
    mbubble.color = player.bubble + 1;

    for (let i = 0; i < player.mbubbles.length; i++) {
        if (player.mbubbles[i].color == 0) {
            mbubble.lastpaintx = player.mbubbles[i].lastpaintx;
            mbubble.lastpainty = player.mbubbles[i].lastpainty;
            player.mbubbles[i] = mbubble;
            break;
        }
    }

    player.bubble = player.nextBubble;

    if (color == -1) {
        player.nextBubble = Math.floor(Math.random() * 8);
    }
    else {
        player.nextBubble = color;
    }

    launchSnd.play();
}

function randomBubble(player) {
    let tried = 0;

    while (tried != 0xFF) {
        let result = Math.floor(Math.random() * 8);
        tried |= (1 << result);

        for (let i = 0; i < fieldHeight; i++) {
            for (let j = 0; j < getWidth(i); j++) {
                if (player.field[i][j] == result + 1) {
                    return result;
                }
            }
        }
    }
    
    return Math.floor(Math.random() * 8);
}

function removeBubble(player, x, y, evil) {
    let mbubble = new MovingBubble();

    mbubble.falling = 1;
    mbubble.evil = evil;
    mbubble.x = x * 32 + ((y & 1) ? 16 : 0);
    mbubble.y = y * 28;
    mbubble.velx = Math.sin(Math.pow(x, 1.4) + Math.pow(y, 2.8)) * 0.5 * 200;
    mbubble.vely = (Math.sin(Math.pow(x, 3.4) + Math.pow(y, 0.6)) - 1) * 25  - 300;
    mbubble.color = player.field[y][x];
    mbubble.lastpaintx = mbubble.x;
    mbubble.lastpainty = mbubble.y;

    for (let i = 0; i < player.mbubbles.length; i++) {
        if (player.mbubbles[i].color == 0 && player.mbubbles[i].lastpaintx == Number.MIN_VALUE) {
            player.mbubbles[i] = mbubble;
            break;
        }
    }

    player.field[y][x] = 0;
}

function markDirty(player, x, y, width, height) {
    if (x < player.dirtyMinx) {
        player.dirtyMinx = x;
    }
    if (y < player.dirtyMiny) {
        player.dirtyMiny = y;
    }
    if (x + width > player.dirtyMaxx) {
        player.dirtyMaxx = x + width;
    }
    if (y + height > player.dirtyMaxy) {
        player.dirtyMaxy = y + height;
    }
}

function scan(player, x, y, color, match) {
     if (x < 0 || x >= getWidth(y) || y < 0 || y >= fieldHeight) {
        return;
     }

    if (!color) {
        if (!player.field[y][x]) {
            return;
        }
    }
    else {
        if (player.field[y][x] != 9 && player.field[y][x] != color) {
            return;
        }
    }

    let idx = y * maxFieldWidth + x;

    for (let i = 0; i < match.matchCount; i++) {
        if (match.matches[i] == idx) {
            return;
        }
    }
    
    match.matches[match.matchCount] = idx;
    match.matchCount = match.matchCount + 1;

    scan(player, x -1, y, color, match);
    scan(player, x +1, y, color, match);

    if (y & 1) {
        scan(player, x, y -1, color, match);
        scan(player, x +1, y -1, color, match);
        scan(player, x, y +1, color, match);
        scan(player, x +1, y +1, color, match);
    }
    else {
        scan(player, x -1, y -1, color, match);
        scan(player, x, y -1, color, match);
        scan(player, x -1, y +1, color, match);
        scan(player, x, y +1, color, match);
    }
}

function testGroup(player, bx, by, color) {
    var match = new MatchState();

    scan(player, bx, by, color, match);

    if (match.matchCount >= 3) {
        destroyGroupSnd.play();

        for (let i = 0; i < match.matchCount; i++) {
            removeBubble(player, match.matches[i] % maxFieldWidth, Math.floor(match.matches[i] / maxFieldWidth), 0);
        }

        return 1;
    }

    return 0;
}

function stick(player, bx, by, color) {
    var match = new MatchState();

    if (by >= fieldHeight) {
        return 0;
    }

    stickSnd.play();
    player.field[by][bx] = color;
    markDirty(player, bx * 32 + ((by & 1) ? 16 : 0), by * 28, 32, 32);
    let broken = 0;

    if (color <= 8) {
        if (testGroup(player, bx, by, color)) {
            broken = 1;
        }
    }
    else {
        for (let i = 1; i <= 8; i++) {
            player.field[by][bx] = i;

            if (testGroup(p, bx, by, i)) {
                broken = 1;
            }
        }

        if (broken) {
            player.field[by][bx] = 0;
        }
        else {
            player.field[by][bx] = color;
        }
    }

    if (broken) {
        match.matchCount = 0;

        for (let i = 0; i < getWidth(0); i++) {
            scan(player, i, 0, 0, match);
        }

        if (!match.matchCount) {
            for (let i = 0; i < fieldHeight; i++) {
                for (let j = 0; j < getWidth(i); ++j) {
                    if (!player.field[i][j]) {
                        continue;
                    }

                    removeBubble(player, j, i, 0);
                }
            }

            return 1;
        }

        for (let i = 0; i < fieldHeight; i++) {
            for (let j = 0; j < getWidth(i); j++) {
                if (!player.field[i][j]) {
                    continue;
                }

                for (var k = 0; k < match.matchCount; k++) {
                    if (match.matches[k] == i * maxFieldWidth + j) {
                        break;
                    }
                }

                if (k == match.matchCount) {
                    if (gameMode == MODE_VERSUS) {
                        if (player.id == players[0].id) {
                            if (players[1].maxEvil < 4) {
                                players[1].maxEvil++;
                            }
                        }
                        else {
                            if (players[0].maxEvil < 4) {
                                players[0].maxEvil++;
                            }
                        }
                    }

                    removeBubble(player, j, i, 1);
                }
            }
        }
    }

    return 0;
}

function initPlayer(player) {
    markDirty(player, 0, 0, maxFieldWidth * 32, 440);

    for (let i = 0; i < player.mbubbles.length; i++) {
        player.mbubbles[i].color = 0;
        player.mbubbles[i].lastpaintx = Number.MIN_VALUE;
    }
}

function initField() {
    let i = 0;

    for (; i < fieldHeight / 2; i++) {
        for (let j = 0; j < getWidth(i); j++) {
            let color = Math.floor(Math.random() * 8) + 1;

            for (let playerCount = 0; playerCount < players.length; playerCount++) {
                let player = players[playerCount];
                player.field[i][j] = color;
            }
        }
    }

    for (; i < fieldHeight; i++) {
        for (let j = 0; j < getWidth(i); j++) {
            for (let playerCount = 0; playerCount < players.length; playerCount++) {
                let player = players[playerCount];
                player.field[i][j] = 0;
            } 
        }
    }

    for (let playerCount = 0; playerCount < players.length; playerCount++) {
        let bubble = Math.floor(Math.random() * 8);
        let nextBubble = Math.floor(Math.random() * 8);
        let player = players[playerCount];
        player.bubble = bubble;
        player.nextBubble = nextBubble;
        initPlayer(player);
    }
}

function paint(player) {
    for (let i = 0; i < player.mbubbles.length; i++) {
        if (player.mbubbles[i].lastpaintx == Number.MIN_VALUE) {
            continue;
        }
        
        markDirty(player, player.mbubbles[i].lastpaintx, player.mbubbles[i].lastpainty, 32, 32);
        player.mbubbles[i].lastpaintx = Number.MIN_VALUE;
    }

    for (let i = 0; i < fieldHeight; i++) {
        for (let j = 0; j < getWidth(i); j++) {
            if (!player.field[i][j]) {
                continue;
            }

            let x = player.xoff + j * 32 + ((i & 1) ? 16 : 0); 
            let y = player.yoff + i * 28;

            ctx.drawImage(bubbles[player.field[i][j] - 1], x, y);
        }
    }

    ctx.drawImage(bubbles[player.bubble], player.xoff + 112, player.yoff + 350);
    ctx.save();
    ctx.translate((player.xoff + 78) + 100 / 2, (player.yoff + 316) + 100 / 2);
    ctx.rotate(player.angle * Math.PI / 180);
    ctx.drawImage(baseImg, -100/2, -100/2);
    ctx.restore();
    ctx.drawImage(bubbles[player.nextBubble], player.xoff + 112, player.yoff + 400);

    for (let i = 0; i < player.mbubbles.length; i++) {
        let mbubble = player.mbubbles[i];
    
        if (mbubble.color == 0) {
            continue;
        }

        ctx.drawImage(bubbles[mbubble.color -1], mbubble.x + player.xoff, mbubble.y + player.yoff);
        mbubble.lastpaintx = mbubble.x - player.xoff;
        mbubble.lastpainty = mbubble.y - player.yoff;
    }
}

function gameLogic() {
    for (let playerCount = 0; playerCount < players.length; playerCount++) {
        let player = players[playerCount];
        let bx = 0;
        let by = 0;
        player.angle = player.angle + (player.right * 90 * timeStep * 1.5);//Slightly modified.

        if (player.angle < -85) {
            player.angle = -85;
        }
        else if (player.angle > 85) {
            player.angle = 85;
        }

        for (let k = 0; k < player.mbubbles.length; k++) {
            let bubble = player.mbubbles[k];

            if (!bubble.color) {
                continue;
            }
        
            if (!bubble.falling) {
                bubble.x += bubble.velx * timeStep;
                bubble.y += bubble.vely * timeStep;

                if (bubble.x < 0) {
                    bubble.x = -bubble.x;
                    bubble.velx = -bubble.velx;
                    reboundSnd.play();
                }
                else if (bubble.x > maxX) {
                    bubble.x = 2 * maxX - bubble.x;
                    bubble.velx = -bubble.velx;
                    reboundSnd.play();
                }

                let collide = 0;

                for (let i = -1; i < fieldHeight; i++) {
                    if (collide == 1) {
                        break;
                    }

                    for (let j = 0; j < getWidth(i); j++) {
                        
                        if (i != -1 && !player.field[i][j]) {
                            continue;
                        }

                        let posx = j * 32 + ((i & 1) ? 32 : 16);
                        let posy = i * 28 + 16;
                        let dirx = posx - (bubble.x + 16);
                        let diry = posy - (bubble.y + 16);

                        if (dirx * dirx + diry * diry < 784) {
                            if (Math.abs(dirx) > Math.abs(diry)) {
                                by = i;

                                if (dirx > 0) {
                                    bx = j - 1;
                                }
                                else {
                                    bx = j + 1;
                                }
                            }
                            else {
                                if (diry > 0) {
                                    by = i - 1;
                                }
                                else {
                                    by = i + 1;
                                }

                                if (by & 1) {
                                    if(dirx > 0) {
                                        bx = j - 1;
                                    }
                                    else {
                                        bx = j;
                                    }
                                }
                                else {
                                    if(dirx > 0) {
                                        bx = j;
                                    }
                                    else {
                                        bx = j + 1;
                                    }
                                }
                            }

                            if (by > 11 && !inputLocked) {
                                inputLocked = 1;

                                if (gameMode == MODE_PRACTICE) {
                                    popupDialog("Game Over");
                                    newPractice();
                                }
                                else if (gameMode == MODE_VERSUS) {
                                    if (player.id == players[0].id) {
                                        players[1].score = players[1].score + hasShot;
                                    }
                                    else if (player.id == players[1].id) {
                                        players[0].score = players[0].score + hasShot;
                                    }

                                    hasShot = 0;
                                    popupDialog(`${players[0].score}:${players[1].score}`);
                                    newVersus(players[0].score, players[1].score);
                                }

                                break;
                            }

                            stick(player, bx, by, bubble.color);
                            bubble.color = 0;
                            collide = 1;
                            break;
                        }
                    }
                }
            }
            else {
                bubble.vely += 1500 * timeStep;
                bubble.x += bubble.velx * timeStep ;
                bubble.y += bubble.vely * timeStep;
                let k = 0;

                if (bubble.y > 480) {
                    if (bubble.evil && gameMode == MODE_VERSUS) {
                        if (player.id == players[0].id && players[1].maxEvil) {
                            k = players[1].evilBubbleCount++;
                            players[1].maxEvil--;
                            players[1].evilBubbles[k] = bubble.color;
                        }
                        else if (player.id == players[1].id && players[0].maxEvil) {
                            k = players[0].evilBubbleCount++;
                            players[0].maxEvil--;
                            players[0].evilBubbles[k] = bubble.color;
                        }
                    }

                    bubble.color = 0;
                }
            }
        }

        if (player.evilBubbleCount) {
            if (player.evilBubbleCount > 4) {
                player.evilBubbleCount = 4;
            }

            let i = 0;

            while (player.evilBubbleCount && i < player.mbubbles.length) {
                if (player.mbubbles[i].color != 0 || player.mbubbles[i].lastpaintx != Number.MIN_VALUE) {
                    i++;
                    continue;
                }

                let rand = Math.sin(Math.pow(player.evilBubbleSeed++, 4.5)) * 0.5 + 0.5;
                let angle = rand * 60 - 30;

                player.mbubbles[i].falling = 0;
                player.mbubbles[i].velx = Math.sin(angle / 180 * Math.PI) * bubbleSpeed;
                player.mbubbles[i].vely = -Math.cos(angle / 180 * Math.PI) * bubbleSpeed;
                player.mbubbles[i].x = 112;
                player.mbubbles[i].y = 400;
                player.mbubbles[i].color = player.evilBubbles[0];
                player.evilBubbleCount--;
                i++;
                break;
            }
        }
    }
}

function gameRender() {
    ctx.clearRect(0, 0, gameWidth, gameHeight);

    if (gameMode == MODE_PRACTICE) {
        ctx.drawImage(spbgImg, 0, 0);
    }
    else if (gameMode == MODE_VERSUS) {
        ctx.drawImage(backgroundImg, 0, 0);
    }

    let now = Date.now();

    if (now - lastTick >= timeStepMs) {
        for (let playerCount = 0; playerCount < players.length; playerCount++) {
            let player = players[playerCount];
            paint(player);
        }

        lastTick = Date.now();
    }
}

function screenReset() {
    let buttonsElem = document.getElementById("buttons");

    if (mobileMode) {
        canvas.width = 320;
        let elem = new SimpleElem();
        let pointerDown = "onmousedown";
        let pointerUp = "onmouseup";

        if (hasTouchscreen) {
            pointerDown = "ontouchstart";
            pointerUp = "ontouchend";
        }

        elem.append(`<br>`);
        elem.append(`<button id="left-button" type="button" ${pointerDown}="buttonDown('left')" ${pointerUp}="buttonUp('left')"><b>◁</b></button>`);
        elem.append(` <button id="up-button" type="button" ${pointerDown}="buttonDown('up')">▢</button> `);
        elem.append(`<button id="right-button" type="button" ${pointerDown}="buttonDown('right')" ${pointerUp}="buttonUp('right')"><b>▷</b></button>`);
        elem.append(`<br><br>`);
        buttonsElem.innerHTML = elem.build();
    }
    else {
        canvas.width = 640;
        buttonsElem.innerHTML = "";
    }

    rect = canvas.getBoundingClientRect();
}

function gameReset(mode) {
    gameMode = mode;
    inputLocked = 0;
    hasShot = 0;
}

function newPractice() {
    screenReset();
    gameMode = MODE_RESTART_PRACTICE;
    players = [];
    players.push(new PlayerState(30, 40, 0));
    initField();
    gameReset(MODE_PRACTICE);
}

function newVersus(score1, score2) {
    mobileMode = 0;
    screenReset();
    gameMode = MODE_RESTART_VERSUS;
    players = [];
    players.push(new PlayerState(30, 40, 0));
    players.push(new PlayerState(355, 40, 1));

    if (score1 !== undefined && Number.isInteger(score1)) {
        players[0].score = score1;
    }
    if (score2 !== undefined && Number.isInteger(score2)) {
        players[1].score = score2;
    }

    initField();
    gameReset(MODE_VERSUS);
}

function mainLoop() {
    if (gameMode == MODE_RESTART_PRACTICE || gameMode == MODE_RESTART_VERSUS || inputLocked) {
        return;
    }

    gamepadMovement();
    gameLogic();
    gameRender();
    
    setTimeout(function() {
        requestAnimationFrame(mainLoop);
    }, 1000 / fps);
};

function initialize() {
    document.addEventListener("keydown", function (event) {
        keyDown(event);
    }, false);
    document.addEventListener("keyup", function (event) {
        keyUp(event);
    }, false);

    //The mouse/touchpad support is roughly implemented.
    //Not recommend to use it.
    /*if (!hasTouchscreen) {
        canvas.addEventListener("mousedown", function (event) {
            mouseDown(event);
        }, false);
        canvas.addEventListener("mouseup", function (event) {
            mouseUp(event);
        }, false);
    }
    else {
        canvas.addEventListener("touchstart", function (event) {
            touchStart(event);
        }, false);
        canvas.addEventListener("touchend", function (event) {
            touchEnd(event);
        }, false);
    }*/

    document.getElementById("version").textContent = `Ver: ${version}`;

    for (let i = 0; i < 8; i++) {
        let bubbleImg = new Image();
        bubbleImg.src = `img/bubble-colourblind-${i+1}.png`;
        bubbles.push(bubbleImg);
    }

    setInterval(function() {
        gamepadShoot();
    }, 150);

    mainLoop();
}

document.addEventListener("DOMContentLoaded", function(event) {
    window.dispatchEvent(new Event("hashchange"));
    initialize();
});

window.addEventListener("gamepadconnected", function (event) {
    gamepadEnabled = true;
}, false);

window.addEventListener("gamepaddisconnected", function (event) {
    gamepadEnabled = false;
}, false);

window.addEventListener("hashchange", function (event) {
    if (this.window.location.hash == "#versus") {
        gameMode = MODE_VERSUS;
        newVersus();
    }
    else if (this.window.location.hash == "#mobile") {
        gameMode == MODE_PRACTICE;
        mobileMode = 1;
        newPractice();
    }
    else {
        gameMode == MODE_PRACTICE;
        newPractice();
    }
});
