0

I am trying to write a simple web-page embedded program in javascript. Right now, I am attempting to write a linked list of all active buttons on the screen at any given time. However, when I attempt to iterate through a linked list that isn't empty, the program freezes.

This is the relevant block of code:

document.addEventListener("click", (event) => {

    if (seekButtons) {

        if (!(activeButtons.isEmpty())) {
            var runner = activeButtons.head;

            /*
             * 
             * This while loop seems to run indefinitely if the activeButtons list has anything in it.
             * 
             */ 
            while (runner) {
                //this list should be populated only by buttons
                console.log("a button! \n");

                if (runner.element.isInside(mouseX, mouseY)) {
                    document.getElementById("fiddleText").innerHTML = ('clicked');
                    console.log("We are in the button! \n");

                    //This line doesn't seem to work correctly. It's meant to move on to the next item in the list (and to exit the while loop if it's the last item), but the loop runs forever.
                    runner = runner.next;
                }
            }
            

        }
        
    }
});

I think I need another set of eyes to look over this and help me figure out why this isn't working.

The linked list code (not written by me) looks like this:

//this code shamelessly stolen from https://www.geeksforgeeks.org/implementation-linkedlist-javascript/

// User defined class node
class Node {
    // constructor
    constructor(element) {
        this.element = element;
        this.next = null
    }
}

// linkedlist class
class LinkedList {
    constructor() {
        this.head = null;
        this.size = 0;
    }

    // adds an element at the end
    // of list
    add(element) {
        // creates a new node
        var node = new Node(element);

        // to store current node
        var current;

        // if list is Empty add the
        // element and make it head
        if (this.head == null)
            this.head = node;
        else {
            current = this.head;

            // iterate to the end of the
            // list
            while (current.next) {
                current = current.next;
            }

            // add node
            current.next = node;
        }
        this.size++;
    }

    // insert element at the position index
    // of the list
    insertAt(element, index) {
        if (index < 0 || index > this.size)
            return console.log("Please enter a valid index.");
        else {
            // creates a new node
            var node = new Node(element);
            var curr, prev;

            curr = this.head;

            // add the element to the
            // first index
            if (index == 0) {
                node.next = this.head;
                this.head = node;
            } else {
                curr = this.head;
                var it = 0;

                // iterate over the list to find
                // the position to insert
                while (it < index) {
                    it++;
                    prev = curr;
                    curr = curr.next;
                }

                // adding an element
                node.next = curr;
                prev.next = node;
            }
            this.size++;
        }
    }

    // removes an element from the
    // specified location
    removeFrom(index) {
        if (index < 0 || index >= this.size)
            return console.log("Please Enter a valid index");
        else {
            var curr, prev, it = 0;
            curr = this.head;
            prev = curr;

            // deleting first element
            if (index === 0) {
                this.head = curr.next;
            } else {
                // iterate over the list to the
                // position to removce an element
                while (it < index) {
                    it++;
                    prev = curr;
                    curr = curr.next;
                }

                // remove the element
                prev.next = curr.next;
            }
            this.size--;

            // return the remove element
            return curr.element;
        }
    }

    // removes a given element from the
    // list
    removeElement(element) {
        var current = this.head;
        var prev = null;

        // iterate over the list
        while (current != null) {
            // comparing element with current
            // element if found then remove the
            // and return true
            if (current.element === element) {
                if (prev == null) {
                    this.head = current.next;
                } else {
                    prev.next = current.next;
                }
                this.size--;
                return current.element;
            }
            prev = current;
            current = current.next;
        }
        return -1;
    }

    // finds the index of element
    indexOf(element) {
        var count = 0;
        var current = this.head;

        // iterate over the list
        while (current != null) {
            // compare each element of the list
            // with given element
            if (current.element === element)
                return count;
            count++;
            current = current.next;
        }

        // not found
        return -1;
    }

    // Helper Methods

    // checks the list for empty
    isEmpty() {
        return this.size == 0;
    }

    // gives the size of the list
    size_of_list() {
        console.log(this.size);
    }

    // prints the list items
    printList() {
        var curr = this.head;
        var str = "";
        while (curr) {
            str += curr.element + " ";
            curr = curr.next;
        }
        console.log(str);
    }
}

My main code looks like this:

//canvas elements
var canvas = document.getElementById("SnekGamCanvas");
var ctx = canvas.getContext("2d");
canvas.addEventListener('click', function () { }, false);


//some important variables
var px = canvas.width / 2;
var py = canvas.height / 2;

var snekColor = "#EC942D";

var clock = 0;

var mouseX = 0.5;
var mouseY = 0.5;

var activeButtons = new LinkedList();

var seekButtons = true;

//classes

class clickButton {
    constructor(text, color, altColor, width, height, radius, xpos, ypos) {
        this.text = text;
        this.color = color;
        this.altColor = altColor;
        this.width = width;
        this.height = height;
        this.radius = radius;
        this.xpos = xpos;
        this.ypos = ypos;
    }

    isInside(datX, datY) {
        //usually, datX will be mouseX, and datY will be mouseY.
        if (datX > (this.xpos) && datX < (this.xpos + this.width)) {
            if ((datY > this.ypos) && datY < (this.ypos + this.height)) {
                return true;
            }
        }
        return false;
    }

    drawButton() {
        ctx.strokeStyle = "#000000"
        if (this.isInside(mouseX, mouseY)) {
            ctx.fillStyle = this.altColor;

            roundRect(this.xpos, this.ypos, this.width, this.height, this.radius, true, true, this.altColor);

            ctx.fillStyle = "#000000";
            ctx.strokeStyle = "#000000";
            ctx.font = '40px san-serif';

            ctx.strokeText(this.text, this.xpos + 10, this.ypos + 40);
            ctx.fillText(this.text, this.xpos + 10, this.ypos + 40);
        }
        else {
            ctx.fillStyle = this.color;

            roundRect(this.xpos, this.ypos, this.width, this.height, this.radius, true, true, this.color);

            ctx.fillStyle = "#000000";
            ctx.strokeStyle = "#000000";
            ctx.font = '40px san-serif';

            ctx.strokeText(this.text, this.xpos + 10, this.ypos + 40);
            ctx.fillText(this.text, this.xpos + 10, this.ypos + 40);
        }
        

        //draw_Ball(303, 500, 50, snekColor);
    }

    clickOnButton() {
        document.getElementById("fiddleText").innerHTML = ('clicked');
    }

}

//buttons

var startButton = new clickButton("Start Game", "#74B5ED", "#1824C7", 200, 50, 20, ((canvas.width / 2) - 100), (canvas.height * (4 / 5)));

//images
var seel = new Image();
seel.onload = function () {
    ctx.drawImage(seel, 0, 0, canvas.width, canvas.height);
}
seel.src = "https://assets.pokemon.com/assets/cms2/img/pokedex/full/086.png"

var snek_title = new Image();
snek_title.onload = function () {
    ctx.drawImage(snek_title, 0, 0, canvas.width, canvas.height);
}
snek_title.src = "https://globin347.com/images/Snake%20Title.png"

//stuff about mouse moving
//the relative mouse position code came from this stackoverflow page: https://stackoverflow.com/questions/17130395/real-mouse-position-in-canvas

function getMousePosX(canvas, evt) {
    var rect = canvas.getBoundingClientRect(), // abs. size of element
        scaleX = canvas.width / rect.width;    // relationship bitmap vs. element for X

    return (evt.clientX - rect.left) * scaleX;   // scale mouse coordinates after they have
}

function getMousePosY(canvas, evt) {
    var rect = canvas.getBoundingClientRect(), // abs. size of element
        scaleY = canvas.height / rect.height;  // relationship bitmap vs. element for Y

    return (evt.clientY - rect.top) * scaleY;
}

document.addEventListener('mousemove', (event) => {
    //document.getElementById("fiddleText").innerHTML = (`Mouse X: ${event.clientX}, Mouse Y: ${event.clientY}`);


    mouseX = getMousePosX(canvas, event);
    mouseY = getMousePosY(canvas, event);
    //document.getElementById("fiddleText").innerHTML = ('mouseX: ' + mouseX + ', mouseY: ' + mouseY);

    //now convert total position to canvas position
    //mouseX, mouseY = getMousePos(canvas, event);

    //document.getElementById("fiddleText").innerHTML = ('mouseX: ' + mouseX + ', mouseY: ' + mouseY);
});

//mouse clicking

document.addEventListener("click", (event) => {

    if (seekButtons) {

        if (!(activeButtons.isEmpty())) {
            var runner = activeButtons.head;

            /*
             * 
             * This while loop seems to run indefinately if the activeButtons list has anything in it.
             * 
             */ 
            while (runner) {
                //this list should be populated only by buttons
                console.log("a button! \n");

                if (runner.element.isInside(mouseX, mouseY)) {
                    document.getElementById("fiddleText").innerHTML = ('clicked');
                    console.log("We are in the button! \n");

                    //This line doesn't seem to work correctly. It's meant to move on to the next item in the list (and to exit the while loop if it's the last item), but the loop runs forever.
                    runner = runner.next;
                }
            }
            
        }
        
    }
});


//begin
var gameState = -1;

function draw() {

    clock += 1;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    //document.getElementById("fiddleText").innerHTML = ("Clock: " + clock);
    if (gameState == -1) {
        //startup
        setup();
    }
    else if (gameState == 0) {
        //this hasn't been implemented yet
        startMenu();
    }
    else if (gameState == 1) {
        //this hasn't been implemented yet either
        playGame();
    }
    else if (gameState == 2) {
        //ditto
        gameOver();
    }
    else {
        //something's wrong

        ctx.drawImage(seel, 0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "#b30000";
        ctx.strokeStyle = "#000000";
        ctx.font = '140px san-serif';

        ctx.fillText('OH NO', 120, 120);
        ctx.strokeText('OH NO', 120, 120);

        ctx.fillText('IT BLOKE', 200, 630);
        ctx.strokeText('IT BLOKE', 200, 630);
    }

}
setInterval(draw, 10);

function setup() {

    //this should be added to the buttons list before the program starts in earnest

    activeButtons.add(startButton);
    activeButtons.printList();

    //document.getElementById("fiddleText").innerHTML = ('Oh Boy Baby');
    //document.getElementById("fiddleText").innerHTML = ('large');

    gameState = 0;

}

function startMenu() {
    ctx.drawImage(snek_title, 0, 0, canvas.width, canvas.height);

    startButton.drawButton();

    //draw_Ball(mouseX, mouseY, 50, snekColor);
}

function playGame() {
    draw_Ball(200, 700, 50, snekColor);
    draw_Ball(400, 700, 50, snekColor);
    draw_Ball(300, 500, 50, snekColor);
}

function gameOver() {

}

//this function was stolen from stack overflow
function showImage(width, height, image_source, alt_text) {
    var img = document.createElement("img");
    img.src = image_source;
    img.width = width;
    img.height = height;
    img.alt = alt_text;

}

function draw_Ball(bx, by, size, ballColor) {
    ctx.beginPath();
    ctx.arc(bx, by, size, 0, (Math.PI * 2));
    ctx.fillStyle = ballColor;
    ctx.fill();
    ctx.strokeStyle = "#000000";
    ctx.stroke();
    ctx.closePath();
}

//This next function was taken from stack overflow

function roundRect(x, y, width, height, radius, stroke, fill, color) {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    if (stroke) {
        ctx.stroke();
    }
    if (fill) {
        ctx.fill();
    }
    ctx.closePath();
    return;
}

And, just in case, here are my css and html files:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Portfolio</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body class="background_gradient">
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark dark-bg border-bottom box_shadow mb-0">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Portfolio</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <!--
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                        -->
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Resume">Resume</a>
                        </li>
                        <!----
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Art3D">3D Art</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Art2D">2D Art</a>
                        </li>
                        <!---->
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Snake">Snake</a>
                        </li>
                        
                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="CodeExamples">Code Examples</a>
                        </li>

                        <li class="nav-item">
                            <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Ballad">Ballad of the Masked Bandits</a>
                        </li>
                        <!--
    <li class="nav-item">
        <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="DataBaseHub">Database Hub</a>
    </li>
    --->
                        <!--
    <li class="nav-item">
        <a class="nav-link text-light" asp-area="" asp-controller="Home" asp-action="Unavailable">???</a>
    </li>
        -->
                        <!--Temporary Links-->
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container-fluid" id="MainDiv">
        <main role="main" class="pb-0" style="width:100%">
            <!--Where the other code goes-->
            
                @{
                    ViewData["Title"] = "Snake Game";
                }
                
                <div class="container-fluid purple_gradient text-center">
                    <h1>Snake Game</h1>
                </div>
                <div class="buffer"></div>
                <div class="container">
                    <div class="fancy_text_box">
                        <div class="container buffer">
                            <div class="ghostly_text_box text-center">
                                <h1>By the power of Javascript, here is a playable snake game.</h1>
                                <div class="buffer"></div>
                                <h1 id="fiddleText">Give it a moment to load.</h1>
                            </div>
                
                            <div class="buffer"></div>
                
                            <div class="ghostly_text_box text-center">
                                <canvas onload="draw()" class="simple_text_box" id="SnekGamCanvas" width="1000" height="1000"></canvas>
                            </div>
                
                        </div>
                
                    </div>
                
                    <div class="text-center">
                        <div class="buffer"></div>
                
                        <a class="button glo_button big_r_button big_text" asp-area="" asp-controller="Home" asp-action="Index">Back to Home</a>
                
                        <div class="buffer"></div>
                    </div>
                
                    <!--The code be here but if you are reading this you probably already knew that-->
                    <script src="~/js/Snake.js"></script>
                
                </div>
                
        </main>
    </div>

    <footer class="border-top footer dark-bg text-light">
        <div class="container">
            &copy; 2021 - Portfolio - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    <script src="../jsc3d-master/jsc3d/jsc3d.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>

.

/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */

a.navbar-brand {
  white-space: normal;
  text-align: center;
  word-break: break-all;
}

/* Provide sufficient contrast against white background */
a {
  color: #0366d6;
}

.btn-primary {
  color: #fff;
  background-image: linear-gradient(30deg, #b6e2dd, #2a5efe);
  border-color: #1861ac;
}

/*Link colors*/
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
  color: #fff;
  background-color: #1b6ec2;
  border-color: #1861ac;
}

/* Sticky footer styles
-------------------------------------------------- */
html {
  font-size: 14px;
}
@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

.border-top {
  border-top: 1px solid #e5e5e5;
}
.border-bottom {
  border-bottom: 1px solid #e5e5e5;
}

.box-shadow {
  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}

button.accept-policy {
  font-size: 1rem;
  line-height: inherit;
}

/* Sticky footer styles
-------------------------------------------------- */
html {
  position: relative;
  min-height: 100%;
}

body {
  /* Margin bottom by footer height */
  margin-bottom: 60px;
}
.footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  white-space: nowrap;
  line-height: 60px; /* Vertically center the text there */
}

/* My Stuff
--------------------------------------------------------------------------
--------------------------------------------------------------------------
--------------------------------------------------------------------------
*/

/*This gives me more control over the exact dark background color*/
.dark-bg
{
    background-color: #161631;
}

.purple_gradient 
{
    /*The image used*/
    background-image: linear-gradient(#4b1ac4, #fff);

    height:100%;
    width:100%;

    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
}

.test_box_blue
{
    /* A container with a solid color and an outline */
    background-color: #2d1eb2;

    width: 100%;
    height: 100%;
    margin: 0px;

}

.test_box
{
    border:solid #000000;
}

#MainDiv
{
    padding:0;
    margin:0;

    left:0;
    top:0;

    width:100%;
    height:100%;
}

.tundra_backround
{
    background-image: url('../images/Tundra_Fixed.png');
    width:100%;
    height:100%;
}

.white_space_box
{
    height:50 px;
}

.background_gradient
{
    background-image:linear-gradient(320deg, #fff, #96cbde);
}

.glo_button
{
    min-width: 30%;
    height: 20%;
    border-radius: 25px;
    padding: 20px;
    margin: 10px;
    box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
    transition-duration: 0.4s;
    border: 4px solid #000;
}

.big_r_button {
    background-color: #a10000;
    color: #fff;
}

.big_r_button:hover {
    color: #fff;
    background-color: #4e0505;
}

.big_b_button {
    background-color: #080e9f;
    color: #fff;
}

.big_b_button:hover {
    color: #fff;
    background-color: #161631;
}

.big_g_button {
    background-color: #0a7727;
    color: #fff;
}

.big_g_button:hover {
    color: #fff;
    background-color: #07340e;
}

.big_p_button {
    background-color: #6f1cbf;
    color: #fff;
}

.big_p_button:hover {
   color: #fff;
   background-color: #2a073e;

}

.buffer
{
    padding: 20px;
}

.big_text
{
    font-size: 60px;
    font-family:'Times New Roman', Times, serif;
    text-shadow: 2px 2px rgb(12 14 39 / 0.67);
}

.fancy_text_box{
    background-image: linear-gradient(300deg, #ece1c4, #c99e69);
    border-radius: 25px;
    border: 4px solid #5d3c08;
}

.simple_text_box{
    background-color: #fff;

    border: 2px solid #000;
}

.ghostly_text_box{
    background-color: rgb(255 255 255 / 0.60);
    border-radius: 25px;
    padding: 10px;
    border: 3px solid #000;
}

.thick_border{
    border: 4px solid #000;
}

.black_and_white_gradient{
    background-image: linear-gradient(310deg, #fff, #000);
}

.red_border{
    padding: 0px;
    margin: 0px;
    border: 4px solid #8f0000;
}

.model_box{
    border: 4px solid #000;
    background-color: #fff;
    border-radius: 25px;
}

.image_box{
    border: 4px solid #000;
    background-color: #fff;
}

.chain_image_box {
    border-top: 4px solid #000;
    border-left: 4px solid #000;
    border-right: 4px solid #000;
    border-bottom: 0px;
    background-color: #fff;
}

.margin_setter {
    margin: 20px;
    padding: 20px;
}

#model_display_1{

}
Globin347
  • 177
  • 2
  • 9
  • I'm assuming you're just doing an exercise and you're not actually intending the app to have that list class. You don't need that list class and it's an awful implementation at that. – Aluan Haddad Dec 23 '21 at 23:41

1 Answers1

1

You're only moving to the next runner when the current runner is inside the button. So when your while loop gets to a runner that isn't in the button, it gets stuck on that element and loops infinitely.

Take the runner = runner.next; line out of the if.

            while (runner) {
                //this list should be populated only by buttons
                console.log("a button! \n");

                if (runner.element.isInside(mouseX, mouseY)) {
                    document.getElementById("fiddleText").innerHTML = ('clicked');
                    console.log("We are in the button! \n");
                }
                runner = runner.next;
            }

A for loop might make things simpler:

for (let runner = activeButtons.head; runner; runner = runner.next) {
    if (runner.element.isInside(mouseX, mouseY)) {
        document.getElementById("fiddleText").innerHTML = ('clicked');
        console.log("We are in the button! \n");
    }
}
Barmar
  • 741,623
  • 53
  • 500
  • 612