Кликать только на верхний, нижний, соседний элемент в 2d массиве

Есть такой код, 2d массив:

let app = document.getElementById('app');
let table = document.createElement('div');
    table.classList.add('table');
    app.appendChild(table);

const BASE = 9;
let arrNum = [];

for(let iNum = 1; iNum < 20; iNum ++ ) {
  if(iNum%10 === 0) continue;
  
  let strNum = iNum.toString();
  arrNum.push(...strNum);
}

let n = arrNum.length;
let x = BASE;
let y = Math.floor(n / BASE);

for(let iy = 0; iy < y; iy++){
  let tr = document.createElement('div');
      tr.classList.add('tr');
      table.appendChild(tr);
  
  for(let ix = 0; ix < x; ix++){
    let td = document.createElement('div');
      td.classList.add('td');
      td.dataset.x = ix;
      td.dataset.y = iy;
      tr.appendChild(td); 
  }
}



const nums = document.querySelectorAll('.td');
arrNum.forEach((num, i) => {
  nums[i].innerHTML = num;
});


let isNum = false;

let firstNum, secondNum;

function flipNum(){    
  
  if(this === firstNum) return;
  
  this.classList.add('select');
  
  if(!isNum) {
    isNum = true;
    firstNum = this;
    
    return;
  }
  
  secondNum = this;  
  
  getIndex();
  
  checkForMatch();
}


function getIndex(){
  //console.log(firstNum.dataset.x + ' ' + firstNum.dataset.y);
}

function checkForMatch() {
  let firstNumVal = +firstNum.innerHTML;
  let secondNumVal = +secondNum.innerHTML;
  
  let isMatch = firstNumVal + secondNumVal === BASE + 1 || firstNumVal === secondNumVal;   
  console.log(isMatch);
  
  isMatch ? disableNum() : resetMatch();
}


function disableNum(){
  firstNum.removeEventListener('click', flipNum);
  secondNum.removeEventListener('click', flipNum);
  
  firstNum.classList.add('match');
  secondNum.classList.add('match');
  
  resetNum();
}

function resetMatch(){
  firstNum.classList.remove('select');
  secondNum.classList.remove('select');
  
  resetNum();
}

function resetNum(){
  isNum = false;
  firstNum = null;
  secondNum = null; 
}



nums.forEach(num => num.addEventListener('click', flipNum));
* {
  box-sizing: border-box;
}

html,
body {
  padding: 0;
  margin: 0;
}


.table {
  max-width: 500px;
  margin: 2rem auto;
}

.tr {
  display: flex;
}

.td {
  width: calc(100% / 9);
  height: 2.5rem;
  line-height: 2.5rem;
  text-align: center;
  cursor: pointer;
  border: 1px solid;
}


.select {
  background: #8BC34A;
}

.match {
  background: #bdbdbd;
}
<div id="app"></div>

Вопрос: как можно реализовать клик только по соседней ячейке (ввеху, внизу, слева, справа), как запретить клик по диагональной ячейке?


Ответы (2 шт):

Автор решения: OPTIMUS PRIME

Суть задачи: Из блоков создан некий прямоугольник A x B, надо научиться находить соседние клетки каждого блока. Количество клеток в каждой строке заранее известно, остается немного поработоть с номером кликнутой кнопки:

let html = new Array(27).fill(0).map((_,i) => `<div class="box">${ i }</div>`).join("");
document.body.insertAdjacentHTML("beforeEnd", html);
// Создает блоки, не важно.

/***/

let boxes = document.querySelectorAll(".box");

const COLS = 9;

document.addEventListener("click", function(e) {
  if (e.target.classList.contains("box")) box_onclick(e.target);
});

function box_onclick(box) {
  console.clear();
  
  if (box.classList.contains("available")) {
    console.log("Доступно");
  } else {
    console.log("Не доступно");  
  }
  
  /***/
  boxes.forEach(e => e.classList.remove("active", "available"));
  
  box.classList.add("active");
  highlight_surrounding_boxes(box);  
}

function highlight_surrounding_boxes(box) {  
  let index = Array.prototype.indexOf.call(boxes, box);
  // Номер кликнутой клетки среди всех 27 boxes
  
  let surrounding_indexes = [
    /* top    */ index - COLS >= 0           &&  index - COLS ,
    /* right  */ (index + 1) % COLS != 0     &&  index + 1    ,   
    /* bottom */ index + COLS < boxes.length &&  index + COLS , 
    /* left   */ index % COLS != 0           &&  index - 1    ,
  ];

  /* Особенность логического "И" в JS:
       false && 17 === false
       true  && 17 === 17
      
     Если поставленное условие выдаст false, результатом всего
     выражения будет false. Иначе - число, нужный индекс.
  */
  
  console.log( JSON.stringify(surrounding_indexes) );
  
  surrounding_indexes.forEach(
    index => typeof index == "number" && boxes[index].classList.add("available")
  ); // Если index не число (сравнение выдаст false), вторая часть не выполнится.
}
.box {
  display: inline-block;
  
  width: 10vw;
  margin: 1px;
  
  padding: 5px;
  border: 3px solid orange;  
  box-sizing: border-box;
  
  cursor: pointer;
}

.box.available {
  background-color: #ddd;
}

.box.active {
  background-color: orange;
}

→ Ссылка
Автор решения: Grundy

Соседние клетки определяются тем, что их координаты отличаются на единицу.

Если брать только горизонтальное и вертикальное направление, то отличаться должна только одна координата, либо x либо y, друга должна совпадать.

Отсюда возникает следующая проверка

(first.x === second.x && Math.abs(first.y - second.y) === 1) // либо совпадают `x`, а `y` отличаются на 1 (верх либо низ)
(first.y === second.y && Math.abs(first.x - second.x) === 1) // либо совпадают `y`, а `x` отличаются на 1 (лево либо право)

В итоге код может выглядеть так:

let app = document.getElementById('app');
let table = document.createElement('div');
table.classList.add('table');
app.appendChild(table);

const BASE = 9;
let arrNum = [];

for (let iNum = 1; iNum < 20; iNum++) {
  if (iNum % 10 === 0) continue;

  let strNum = iNum.toString();
  arrNum.push(...strNum);
}

let n = arrNum.length;
let x = BASE;
let y = Math.floor(n / BASE);

for (let iy = 0; iy < y; iy++) {
  let tr = document.createElement('div');
  tr.classList.add('tr');
  table.appendChild(tr);

  for (let ix = 0; ix < x; ix++) {
    let td = document.createElement('div');
    td.classList.add('td');
    td.dataset.x = ix;
    td.dataset.y = iy;
    tr.appendChild(td);
  }
}



const nums = document.querySelectorAll('.td');
arrNum.forEach((num, i) => {
  nums[i].innerHTML = num;
});


let isNum = false;

let firstNum, secondNum;

function flipNum() {

  if (this === firstNum) return;

  this.classList.add('select');

  if (!isNum) {
    isNum = true;
    firstNum = this;

    return;
  }

  secondNum = this;

  var isMatch = checkIndex() && checkForMatch();

  if (isMatch) {
    disableNum();
  } else {
    resetMatch();
  }
}


function checkIndex() {
  let first = {
    x: +firstNum.dataset.x,
    y: +firstNum.dataset.y
  };
  let second = {
    x: +secondNum.dataset.x,
    y: +secondNum.dataset.y
  };

  return (first.x === second.x && Math.abs(first.y - second.y) === 1) ||
    (first.y === second.y && Math.abs(first.x - second.x) === 1);
}

function checkForMatch() {
  let firstNumVal = +firstNum.innerHTML;
  let secondNumVal = +secondNum.innerHTML;

  return firstNumVal + secondNumVal === BASE + 1 || firstNumVal === secondNumVal;
}


function disableNum() {
  firstNum.removeEventListener('click', flipNum);
  secondNum.removeEventListener('click', flipNum);

  firstNum.classList.add('match');
  secondNum.classList.add('match');

  resetNum();
}

function resetMatch() {
  firstNum.classList.remove('select');
  secondNum.classList.remove('select');

  resetNum();
}

function resetNum() {
  isNum = false;
  firstNum = null;
  secondNum = null;
}

nums.forEach(num => num.addEventListener('click', flipNum));
* {
  box-sizing: border-box;
}

html,
body {
  padding: 0;
  margin: 0;
}

.table {
  max-width: 500px;
  margin: 2rem auto;
}

.tr {
  display: flex;
}

.td {
  width: calc(100% / 9);
  height: 2.5rem;
  line-height: 2.5rem;
  text-align: center;
  cursor: pointer;
  border: 1px solid;
}

.select {
  background: #8BC34A;
}

.match {
  background: #bdbdbd;
}
<div id="app"></div>

→ Ссылка