Striving to code a JavaScript version of the classic Snake Game - Encountering an issue where the snake's

I'm currently troubleshooting an issue with my snake game where the tail array of the snake is only being drawn one block at a time after eating the food. My goal is to have the entire tail array drawn together and smoothly follow the snake head, creating a complete snake appearance. However, when the snake head moves over the food, only one rectangle is drawn in the spot where the food was located, and the head continues to be drawn in its new position.

It seems like the tail array of the snake is getting drawn throughout the map individually instead of as a connected segment.

Below you can find the code I am working with:

Thank you for any assistance!

Entire Code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">>
      <title>Document</title>
      <style>
        .new-div {
          display:flex;
          justify-content: center;
          align-items: center;
          width:400px;
          height:400px;
          position:absolute;
          top:0;
          z-index:4000;
        }

        .game-content {
          position:relative;
          top:35px;
          right:0px;
          border:none;
          border-radius:5px;
          padding:10px;
          background-color:teal;
          color:white;
        }
      </style>
    </head>
    <body>
      <canvas id="canvas" width="400" height="400" style="border:1px solid black"></canvas>
      <script type="text/javascript">

      window.onload = function() {
        fruit.generateNewFruit();
        window.requestAnimationFrame(main);
      }

      var tools = {
        canvas: document.getElementById('canvas'),
        ctx: canvas.getContext('2d'),
        drawRect: function(x, y, sizeX, sizeY, color, type, stroke, strokeColor) {
          if (type == 'fill') {
            this.ctx.fillStyle = color;
            this.ctx.fillRect(x, y, sizeX, sizeY);
            if (stroke == true) {
              this.ctx.strokeStyle = strokeColor;
              this.ctx.strokeRect(x, y, sizeX, sizeY);
            }
          } else {
            this.ctx.fillStyle = color;
            this.ctx.strokeRect(x, y, sizeX, sizeY);
          }
        },
        writeText: function(text, x, y, font, fontStyle, color) {
          if (font) {
            this.ctx.font = fontStyle;
          }
          this.ctx.fillStyle = color;
          this.ctx.fillText(text, x, y);
        }
      }

      let game = {
        x: tools.canvas.width / 2 - 40,
        y: tools.canvas.height / 2,
        gameOver: false,
        gameOverMsg: function(msg, font) {
          let newDiv = document.createElement('div');
          let button = document.createElement('button');
          let btnText = document.createTextNode('Play Again');

          button.className = "game-content";
          newDiv.className = "new-div";

          tools.writeText(msg, this.x, this.y, true, "16px bold sans-serif", "#fff");

          button.appendChild(btnText);
          newDiv.appendChild(button);
          document.body.appendChild(newDiv);

          document.getElementsByClassName('game-content')[0].addEventListener('click', function() {
            game.gameOver = true;
          });
        }
      }

      let map = {
        tileX: 0,
        tileY: 0,
        tileRow: 20,
        tileCol: 20,
        tileSize: 20,
        tileColor: 'gray',
        drawMap: function() {
          for (let col = 0; col < this.tileCol; col++) {
            for (let row = 0; row < this.tileRow; row++) {
              tools.drawRect(this.tileX, this.tileY, this.tileSize, this.tileSize, this.tileColor, 'fill');
              this.tileX += this.tileSize;
            }
            this.tileX = 0;
            this.tileY += this.tileSize;
          }
          this.tileY = 0;
        },
        outOfBounds: function() {
          if (snake.x < 0 || snake.x > tools.canvas.width || snake.y < 0 || snake.y > tools.canvas.height) {
            game.gameOver = true;
          }
        }
      }

      let fruit = {
        x: 0,
        y: 0,
        size: 20,
        fillColor: 'hotpink',
        strokeColor: 'black',
        drawFruit: function() {
          tools.drawRect(this.x, this.y, this.size, this.size, this.fillColor, 'fill', true, this.strokeColor);
        },
        generateNewFruit: function() {
          this.x = Math.floor(Math.random() * 20) * 20;
          this.y = Math.floor(Math.random() * 20) * 20;
        }
      }

      let snake = {
        x: 0,
        y: 0,
        size: 20,
        color: 'black',
        direction: '',
        bodySize: 0,
        init: false,
        tail: [],
        drawSnake: function() {

          for (let i=0; i < this.bodySize; i++) {
            tools.drawRect(this.tail[i].x, this.tail[i].y, this.size, this.size, this.color, 'fill', true, 'lime');
          }

          tools.drawRect(this.x, this.y, this.size, this.size, this.color, 'fill', true, 'lime');
        },
        move: function() {
          if (this.direction == 'left') {
            this.x-=this.size;
          }
          else if (this.direction == 'up') {
            this.y-=this.size;
          }
          else if (this.direction == 'right') {
            this.x+=this.size;
          }
          else if (this.direction == 'down') {
            this.y+=this.size;
          }
        }
      }

      let drawEverything = function() {
          if (game.gameOver) {
            window.cancelAnimationFrame(main);
          }



          if (snake.x === fruit.x && snake.y === fruit.y) {
            fruit.generateNewFruit();
            snake.bodySize++;


          if (snake.bodySize === snake.tail.length) {
            for (let i=0; i < snake.tail.length - 1; i++) {
              snake.tail[i] = snake.tail[i+1];
            }
          }

          snake.tail[snake.bodySize-1] = {x: snake.x, y: snake.y};

          }

          tools.ctx.clearRect(0, 0, tools.canvas.width, tools.canvas.height);
          map.drawMap();
          map.outOfBounds();
          snake.drawSnake();
          snake.move();
          fruit.drawFruit();
      }



      let main = function() {
          if (game.gameOver) {
            game.gameOverMsg("Game Over");
            cancelAnimationFrame(main);
            return;
          } else {
            drawEverything();

            setTimeout(function() {
              requestAnimationFrame(main);
            }, 1000/20);
          }
      }

      window.addEventListener('keydown', function(e) {
        let key = e.keyCode;

        switch(key) {
          case 65: snake.direction = 'left';
          break;
          case 87: snake.direction = 'up';
          break;
          case 68: snake.direction = 'right';
          break;
          case 83: snake.direction = 'down';
          break;
        }
      });

      </script>
    </body>
    </html>

This section handles shifting the snake downward:

  let drawEverything = function() {
          if (game.gameOver) {
            window.cancelAnimationFrame(main);
          }



          if (snake.x === fruit.x && snake.y === fruit.y) {
            fruit.generateNewFruit();
            snake.bodySize++;


          if (snake.bodySize === snake.tail.length) {
            for (let i=0; i < snake.tail.length - 1; i++) {
              snake.tail[i] = snake.tail[i+1];
            }
          }

          snake.tail[snake.bodySize-1] = {x: snake.x, y: snake.y};

          }

          tools.ctx.clearRect(0, 0, tools.canvas.width, tools.canvas.height);
          map.drawMap();
          map.outOfBounds();
          snake.drawSnake();
          snake.move();
          fruit.drawFruit();
      }

Here is the part responsible for drawing the updated shifted-down tail and snake head:

drawSnake: function() {

          for (let i=0; i < this.bodySize; i++) {
            tools.drawRect(this.tail[i].x, this.tail[i].y, this.size, this.size, this.color, 'fill', true, 'lime');
          }

          tools.drawRect(this.x, this.y, this.size, this.size, this.color, 'fill', true, 'lime');
}

If you have insights on why the tail array is not being drawn as a continuous set of blocks or if there's an issue with the drawSnake function, your help would be greatly appreciated.

Answer №1

It was necessary for me to move the for loop outside of the if statement related to food consumption.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Delay the execution using Javascript/jQuery, remove the last element from the DOM

Within a jQuery AJAX call, I am using a $.each() loop. Each element in the response data triggers the addition of a portion of HTML to a container class in my index.html file. To view the code and ensure proper formatting, visit the codepen here as the pa ...

What is the best way to show a spinner or progress bar while loading an image?

I'm currently developing a React app using Next.js to display various images. The user can click on an item from the list on the left, which then displays three images on the right. These images are rendered using the Next.js Image component: ...

Tips for transferring JSON data to a CodeIgniter view

I have a query regarding how to replace a specific section of my webpage with data returned from the controller. Currently, I have a page that displays movie details directly fetched from the database. My goal is to only display movies of a particular genr ...

PHP loop causing malfunction in jQuery Tooltip feature

I am trying to enhance my website with tool-tips, and I found this useful code snippet at In order to create table rows <tr>, I have implemented a while loop. The structure of my tool-tip <div> is as follows: <td class="maandgem">&e ...

Difficulty arises when applying hover effects to animations using callbacks

Currently facing an issue with the hover event in jQuery. There are two side-by-side containers with hover events on both. When hovering, a div containing additional information slides up into view and slides back down when the hover ends. The concept is ...

Refreshing javascript in order to crop images

I'm currently working on developing a function that allows users to select an image for their avatar, preview it on the page using JavaScript, crop it using the jQuery Guillotine plugin, and then upload it to the server with the necessary coordinates ...

How to delete a specific element from the DOM using its ID in JavaScript

I am facing a challenge in removing an element from the page dynamically without refreshing using JavaScript. The script I have written successfully deletes the record from the database, but I need assistance in removing the container element based on it ...

Beginning the process of setting up information windows for map markers with the help

As I work on dynamically adding info windows to Google Maps markers from a JSON array received from the server, I find myself grappling with Javascript's handling of variables and scope. One attempt involved this code snippet: $.getJSON("server", fu ...

Concealing the parent element within the DOM if its children have no content

<div> <h1>Birds</h1> <ul> <li ng-if="bird.type === 'bird'" ng-repeat="bird in creatures">{{bird.name}}</li> </ul> </div> I received data from the server and I need to display it i ...

The appearance of an unforeseen * symbol caused a

Having issues with this particular line of code, import * as posenet from '@tensorflow-models/posenet' The error 'Uncaught SyntaxError: Unexpected token *' keeps popping up, I have the latest version of Chrome installed and I've ...

Vue 2 - Error: The function is not defined at HTMLInputElement.invoker (vue.esm.js?65d7:1810)TypeError: cloned[i].apply

I encountered the following error: Uncaught TypeError: cloned[i].apply is not a function at HTMLInputElement.invoker (vue.esm.js?65d7:1810) I have set up my project using vue-cli (simple webpack), and below is the code for my component: <template ...

How can the useEffect Hook be utilized to perform a specific operation just one time?

My goal was to have a modal popup appear if the user moves their cursor outside of the window. I experimented with the following code: React.useEffect(() => { const popupWhenLeave = (event) => { if ( event.clientY <= 0 || ...

I am struggling to decide which attribute to use for implementing image swap on mouseover() and mouseout()

I have a problem using jQuery to switch between images when hovering on and off. Here's the code snippet I'm working with: HTML <img class="commentImg" src="images/comments.png" data-swap="images/comment_hover.png" alt=""> jQuery $(" ...

Guide to creating and importing a JSON file in Wordpress

I'm looking to save a list in a JSON file within the Wordpress functions.php file. Can you guide me on how to achieve this? function ScheduleDeletion(){ ?> <script type="text/javascript"> var day = (new Date).get ...

Tips for converting ISO 8601 timestamps to UTC in time tags and keeping the same classes, while also adapting to user locale and timezone using JavaScript

Is there a way to convert ISO 8601 timestamps in UTC into the user's local time zone using JavaScript? I have an HTML table with multiple timestamps in UTC, all in ISO 8601 format and enclosed in <time> tags with the same class. I want to refo ...

Tips for specifying minimum length in the v-autocomplete component in Vuetify

Within my code, I incorporate the v-autocomplete UI Component. Is there a way to configure it so that it only starts searching after a minimum length of 3 symbols? Typically, the default minimum length is set to 1 symbol, and it shows the no-data-text mes ...

I'm currently working with React and experiencing issues with my components being unrendered

I'm currently navigating a React tutorial and I'm having issues getting this code to display some buttons. I'm utilizing codepen.io to work on the code and can provide the tutorial link if needed. const clips = [ { keyCode: 81, key ...

Analyzing the contents of a JSON file and matching them with POST details in order to retrieve

When comparing an HTTP Post body in node.js to a JSON file, I am looking for a match and want the details from the JSON file. I've experimented with different functions but I'm unsure if my JSON file is not formatted correctly for my needs or if ...

"Exploring the Effects of Opacity Gradation in

Is there a way to create a face with an opacity gradient in three.js since rgba is not supported and the alpha is not used? I've heard it might be achievable with a ShaderMaterial and custom attributes, but being new to WebGL, I'm still trying t ...

Using jquery to remove textbox value when checkbox is unchecked

I have a single datatable created using jQuery. The first column of the table consists of checkboxes, and the last column contains textboxes. Because these elements are generated dynamically, they end up having identical IDs and names as shown below: $ ...