Investigating nearby table cells

I am in the process of creating a game called Dots and Boxes.

The grid is filled with numerous dots:

<table>
    <tr>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
    </tr>
    <tr>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
    </tr>
</table>

On clicking one side of the box, it changes to black:

function addLine(obj) {
            console.log("Function Triggered")

            if (obj.style.backgroundColor != "black") {
                obj.style.backgroundColor = "black";
                changeTurn()
            }

After all four sides have turned black, indicating closure, the box adopts the color of the player who completed it:

Currently, players need to manually click on the box to update its color. Ideally, I want the box to automatically fill with the color corresponding to the player once all four sides are painted black around it.

How can I incorporate a JavaScript function to verify whether the lines above, below, left, and right of a box have been colored black?

var currentPlayer = "Blue";
            changePlayer();
            var counter = 0;

            function addLine(obj) {
                console.log("Function Triggered")

                if (obj.style.backgroundColor != "black") {
                    obj.style.backgroundColor = "black";
                    changePlayer()
                }
            }
            
            function fillBox(obj) {
                if (currentPlayer == "Blue") {
                    obj.style.backgroundColor = "red";
                }
                else if ( currentPlayer == "Red") {
                    obj.style.backgroundColor = "blue";
                }
            }

            function changePlayer() {
                if (currentPlayer == "Red") {
                    currentPlayer = "Blue";
                    document.getElementById('result').style.color = "blue";

                }
                else if (currentPlayer == "Blue") {
                    currentPlayer = "Red";
                    document.getElementById('result').style.color = "red";
                };
                console.log(currentPlayer);
                document.getElementById('result').innerHTML = currentPlayer + "'s Turn";
            }
h3 {
    font-family: Arial;
}
table {
    border-collapse: collapse;
}
.vLine {
    width: 10px;
    height: 60px;
}
.box {
    width: 60px;
    height: 60px;
}
.hLine {
    width: 60px;
    height: 10px;
}
.gap {
    width: 10px;
    height: 12px;
    background-color: black;
}
.vLine:hover, .hLine:hover {
    background-color: black;
}
<h3 id="result"></h3>

<table>
    <tr>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
        <td class="hLine" onclick="addLine(this)"></td>
        <td class="gap"></td>
    </tr>
    <tr>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
        <td class="box" onclick="fillBox(this)"></td>
        <td class="vLine" onclick="addLine(this)"></td>
    </tr>
    (⋭ continue pattern for remaining rows ⋮)
</table>

Answer №1

Mastering this task involves utilizing effective DOM traversal techniques.

When selecting a line, only two boxes can potentially be filled in: the ones directly above and below for horizontal lines, and the ones to the left and right for vertical lines. This requires navigating through rows in the DOM structure.

Each of these possible boxes is associated with four lines that need to be validated. Again, row traversal is essential here for handling the lines above and below.

To understand the core concepts better, I decided to recreate the game from scratch as it offers great learning opportunities. Let's focus on key aspects while avoiding coding practices like inline handlers and direct styling on elements; instead, leveraging CSS classes to handle controls, though using data attributes could provide a more robust solution.

Let's start by defining two crucial helper functions.

The first function converts array-like objects into actual arrays, enabling the use of Array prototype methods on them. This is particularly useful for NodeList objects.

function toArray(o) {
  return Array.prototype.slice.call(o);
}

The second function utilizes the first one and determines the position of an element relative to its parentNode.

function nodeIndex(node) {
  return toArray(node.parentNode.children).indexOf(node);
}

Moving on to our event handlers, both functions share similar structures. When a line is clicked, a fill function adds a new class called '.filled' to mark the line as filled. We then proceed to check adjacent boxes that can be filled - vertical boxes for horizontal lines (above and below) and horizontal boxes for vertical lines (left and right). Finally, the event listener is removed after a line is clicked to prevent multiple clicks.

function addHLine(e) {
  fill(this);
  this.removeEventListener(e.type, addHLine);
  checkVerticalBoxes(this);
  updateGame();
}

function addVLine(e) {
  fill(this);
  this.removeEventListener(e.type, addVLine);
  checkHorizontalBoxes(this);
  updateGame();
}

Now, let's delve into the 'checkHorizontalBoxes' and 'checkVerticalBoxes' functions. The horizontal boxes are located within the same row, indexed at +/- 1 from the starting line. On the other hand, vertical boxes are found in rows indexed at +/- 1 from the starting row, sharing the same index as the line. Conditional statements are necessary here to handle cases where a row or box may be out-of-bounds.

function checkHorizontalBoxes(line) {
  var left = line.previousElementSibling,
      right = line.nextElementSibling;

  if (left) checkSurroundingLines(left);
  if (right) checkSurroundingLines(right);
}

function checkVerticalBoxes(line) {
  var index = nodeIndex(line),
      up = line.parentNode.previousElementSibling,
      down = line.parentNode.nextElementSibling;

  if (up) checkSurroundingLines(up.children[index]);
  if (down) checkSurroundingLines(down.children[index]);
}

Both these functions call the 'checkSurroundingLines' function on potential boxes. Internally, this function invokes two additional functions - 'checkVerticalLines' and 'checkHorizontalLines'. Notice the repetitive pattern? The 'isFilled' function mirrors 'fill' but is used for testing whether a line has been marked as filled with the '.filled' class.

function checkHorizontalLines(node, idx) {
  var left = node.previousElementSibling,
      right = node.nextElementSibling;

  return isFilled(left) && isFilled(right);
}

function checkVerticalLines(node, idx) {
  var row = node.parentNode,
      up = row.previousElementSibling.children[idx],
      down = row.nextElementSibling.children[idx];

  return isFilled(up) && isFilled(down);
}

If both of these functions return true, we proceed to fill in the corresponding box.


This encapsulates the fundamental logic behind traversing the DOM efficiently for this particular task. Additional details can be found in the provided code snippets.

Here's the link to experience the functional game:

DEMO

Answer №2

To access the next and previous elements, you can utilize previousElementSibling and nextElementSibling. By incorporating the technique suggested in this post: Is it possible to get element's numerical index in its parent node without looping?, you can also target elements above and below. Here's how:

    var indexPos = Array.prototype.indexOf.call(obj.parentNode.children, obj);
    above = obj.parentNode.previousElementSibling.children[indexPos];
    below = obj.parentNode.nextElementSibling.children[indexPos]
    next = obj.nextElementSibling;
    previous = obj.previousElementSibling;

If you click on an element, you will notice the surrounding elements turning green.

For a demonstration, visit: https://jsfiddle.net/bvc0ta55/

This script does not account for potential errors like missing surrounding elements, however, given that your square elements are all surrounded, you should not encounter any issues.

Answer №3

In most cases, it is recommended to keep game logic separate from the DOM and handle all operations within game objects such as arrays. However, in your specific scenario, using data attributes can be a convenient way to assign values to elements of interest, create element arrays, and then utilize those data attributes for checking if a box is surrounded or not. I found this problem-solving process enjoyable, so I have enhanced your code by introducing arrays, assigning data attributes, and implementing horizontal/vertical checks. You can view the updated code on Jsfiddle: https://jsfiddle.net/pisamce/1ws3oyfe/

Furthermore, I made modifications to your addLine function to invoke check functions:

// Check if the box is enclosed
var row = +obj.dataset.row;
var col = +obj.dataset.col;
var type = obj.dataset.type;
if (type === 'h') {
    checkHorizontal(row, col);
} else if (type === 'v') {
    checkVertical(row, col);
}
... (additional content remains unchanged)...

Answer №4

Still a Work in Progress

Initially, my intention was simply to experiment with separating logic from the view, but it ended up resulting in a significant amount of code creation. Despite its imperfections, I believe that beginners might find some aspects of this project interesting.

In terms of the code itself, the logic resides within the classes defined in the first half of the JavaScript section, while the view consists of a collection of functions located towards the bottom (the second half of the JavaScript section). The Cell class and its subclasses, EdgeCell and CoreCell, serve as representations for the rectangular elements within the game. Edge cells are the clickable units, while core cells are the larger rectangles.

var GameError = extend(Error, {
    ctor: function () {
        Error.apply(this, arguments);
    }
});

var Game = extend(Object, {
    ctor: function () {
        var size = prompt('Board size: ', 3) || 3;
        this.board = new Board(size);
        this.currentPlayer = 1;
        this.players = [
            new Player(1, '#05A'),
            new Player(2, '#A11')
        ];
    },
    getBoard: function () {
        return this.board;
    },
    getCurrentPlayer: function () {
        return this.players[this.currentPlayer - 1];
    },
    nextTurn: function () {
        this.currentPlayer = this.currentPlayer ^ 3;
    },
    select: function (edge) {
        var coreCells, player;
        edge.switchOn();
        player = this.getCurrentPlayer();
        coreCells = this.board.getCoreCells(edge);
        each(coreCells, function (i, cell) {
            if (cell) {
                cell.incr();
                if (cell.isDone()) {
                    player.incrScore();
                }
            }
        });
    }
});

var Board = extend(Object, {
    ctor: function (width) {
        this.width = width;
        this.cells = this.makeCells();
    },
    getWidth: function () {
        return this.width;
    },
    getCell: function (i, j) {
        try { return this.cells[i][j]; }
        catch (e) { return null; }
    },
    makeCells: function () {
        var l = this.width * 2 + 1;
        var rows = new Array(l);
        loop.call(this, l, function (i) {
            var min = (i % 2) * 2 + 1;
            rows[i] = new Array(l);
            loop.call(this, l, function (j) {
                rows[i][j] = this.createCell(
                    min + j % 2, [i, j]
             ...
      
...
.row div {
          float: left;
      }

      .row::after {
          content: " ";
          display: block;
          clear: both;
      }

      .corner {
          width: 20px;
          height: 20px;
          background: #333;
      }

      .h-edge {
          width: 50px;
          height: 20px;
      }

      .v-edge {
          width: 20px;
          height: 50px;
      }

      .v-edge:hover,
      .h-edge:hover {
          cursor: pointer;
          background: #999;
      }

      .v-edge.on,
      .h-edge.on {
          cursor: pointer;
          background: #333;
      }

      .core {
          width: 50px;
          height: 50px;
      }

      #turn, #scores {
          font: normal 16px Courier;
          margin-bottom: .5em;
      }
<div id="scores"></div>
        <div id="turn"></div>

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

Two events can be triggered with just one button press

Is there a way to trigger two functions with just one button click? I want the button to execute the check() function and open a popup window with a PHP script. I've tried different approaches but none have been successful. Can anyone assist me? < ...

What are the steps to manipulate the data within an EJS file after passing an array through Node.js as an object?

I'm currently developing a simple calendar application using Node.js and ejs. I am working on passing an array of months through express to my ejs file, with the goal of being able to cycle through each month by clicking the next button. How can I imp ...

Is there a way to eliminate the 'All Files' option from an HTML file input field?

I have implemented a feature to allow users to upload custom files using . Currently, I am restricting the allowed file types to only ".Txt, .SVG, .BMP, .JPEG" by using accept=".Txt,.SVG,.BMP,.JPEG". This setting causes the browser's file-select dial ...

Automate website button clicks using Python and Selenium

I could really use some assistance: Currently, I have a Python/Selenium code snippet that carries out basic actions on a website to retrieve names, and it works perfectly fine. The objective: However, what I am aiming for is to automate these actions to ...

Learn how to implement a feature in your chat application that allows users to reply to specific messages, similar to Skype or WhatsApp, using

I am currently working on creating a chatbox for both mobile and desktop websites. However, I have encountered an obstacle in implementing a specific message reply feature similar to Skype and WhatsApp. In this feature, the user can click on the reply butt ...

JavaScript generated form fails to submit

I am currently facing an issue with submitting form data to a PHP file when it is generated using JavaScript. I am unsure of how to resolve this problem. The form submission works fine on a test .html file by itself, but not when it is generated by JavaScr ...

Retrieve the current language from a non-page component on the server side with the help of Next.js and the next-i18next

I am working on a nextjs application that utilizes the next-i18next library for internationalization. I'm struggling to figure out how to access the current language on the server-side within a component that is not a page. On the server side, you ca ...

Leveraging AJAX for database variable checks and PHP and JavaScript to redirect to specific pages based on the results

I am currently exploring the use of AJAX to validate variables in a database. The goal is to redirect to a specific page if the validation is successful. However, during this testing phase, I am not actually checking any variables. My focus is on verifying ...

What is the best way to obtain an attribute name that is reminiscent of Function.name?

My objective is to securely type an attribute. For example, I have an object: const identity = { address: "Jackie" }; At some point, I will rename the address key to something else, like street_address. This is how it is currently implemented ...

Convert this text into HTML code: "Textarea to

I have a basic <textarea> element that I want to transform links (www.google.com, msn.com, etc) and line breaks (\r\n) into HTML code. I found one library for converting links into <a hrefs>. Another library can convert line breaks i ...

Initial React render fails to retrieve API data

I've encountered an issue during development where the page loads before the API data is sent. I attempted to use asynchronous functions, but it didn't resolve the problem as expected. I suspect that my approach may be incorrect. Here's a sn ...

Using async/await in an Express route handler

Currently, I am facing an issue with my node/express route. The route collects information through url parameters and then uses these parameters to trigger a separate function that takes some time to execute. Despite using async/await to pause the executio ...

CSS: Placing a Column below a Float Positioned Column

While performing some maintenance on a website, I noticed that one page is not displaying correctly. There is a container div for a column on the left and a container div for content on the right, both within an overall page content container. Below these, ...

Using multiple <img> tags with identical src attributes

Imagine having a large number of <img> tags all with the same src value, like this: <img src="https://something.com/images/myimg.png" alt="" /> <img src="https://something.com/images/myimg.png" alt="" /> <img src="https://something.co ...

Concealing a column in ngx-datatable Ionic

I am just starting to work with NGX-datatable and I need to populate my data table in my Ionic app with data from Firebase. Here is the format of the JSON returned from Firebase: If I choose not to use the User_id as a column, how can I access the User_id ...

Using the `forEach` method nested within another `forEach

I'm facing an issue with the useEffect hook where it seems to not be rendering correctly. My cardArticle is only concatenating the last array and that's it. Below is the code snippet: const [articles, setArticles] = useState([]); const [cardA ...

Placement of Buttons Inside a Division Tag

Is there a better way to align button 3 & 4 in a vertical column next to button 1 & 2 without using tables in CSS? See the working example below: https://jsfiddle.net/Jaron787/0te3cs66/1/ Code: HTML <div id="lse" class="display"> <di ...

When attempting to post data using jQuery, an object is encountered that does not have a parameterless constructor

Here is the code snippet I am using with jQuery to post data and register a band: var formData = new FormData(); var file = document.getElementById("CoverPicture").files[0]; formData.append("file", file); var Name = $('#Name').val(); var Genre = ...

Encountering a CORS blockage: The request header authorization is restricted by Access-Control-Allow-Headers in the preflight response

I encountered an error message that says: Access to XMLHttpRequest at 'http://localhost:4000/api/investments' from origin 'http://localhost:5000' has been blocked by CORS policy: Request header field authorization is not allowed by Acce ...

tips for dynamically highlighting search bar after choosing a different dropdown option - vuejs

I need to filter products in my application based on name and category. To achieve this, I have included a search text box and a dropdown select box. The dropdown select box offers two options: 1. Search products by name 2. Search products by category name ...