I'm making a 4x4 number puzzle where the user is only able to drag adjacent tiles from the empty tile and drop it in the empty type to swap position. I'm using the method isDraggable to figure out if the tile is adjacent to the empty tile and feeding the boolean value into the Tile object which then makes the tile draggable or not. Right now I'm not able to drag the adjacent tiles into the empty tile.
Board class
import React from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';
import {Tile} from './Tile'
import './Number_puzzle.css';
export class Number_puzzle extends React.Component {
constructor(props){
super(props);
this.state = {
numbers : [ [1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,-1] ] // -1 is empty
};
this.shuffle2darray(this.state.numbers)
console.log("shuffled!")
}
shuffle2darray(numbers){
let randomI = -1
let randomJ = -1
for ( let i = 0; i < numbers.length; i++){
for ( let j = 0; j < numbers[i].length; j++){
while(randomI !== i && randomJ !== j){ // prevent swapping with itself
randomI = Math.floor(Math.random()*4)
randomJ = Math.floor(Math.random()*4)
}
const holder = numbers[randomI][randomJ]
numbers[randomI][randomJ] = numbers[i][j]
numbers[i][j] = holder
randomI = -1
randomJ = -1
}
}
}
isDraggable(tileRow, tileCol){
for (let i = 0; i < this.state.numbers.length; i++) {
for (let j = 0; j < this.state.numbers[i].length; j++) {
if (this.state.numbers[i][j] === -1) {
if( tileRow === i && tileCol === (j-1))
return true;
if( tileRow === (i-1) && tileCol === j)
return true;
if( tileRow === (i+1) && tileCol === j)
return true;
if( tileRow === i && tileCol === (j+1))
return true;
else
return false
}
}
}
}
handleDrag(fromIndex, toIndex) {
if (this.isDraggable(fromIndex[0], toIndex[0])) {
const numbers = this.state.numbers.slice(); // make a copy of the 2D array by slice and not modifying numbers directly
numbers[toIndex[0]][toIndex[1]] = numbers[fromIndex[0]][fromIndex[1]];
numbers[fromIndex[0]][fromIndex[1]] = -1;
this.setState({ numbers: numbers }); // update the state with the new 2D array
}
}
render() {
return (
<DndProvider backend={HTML5Backend}>
<div className="grid-container">
{this.state.numbers.map((row, i) => (
<React.Fragment key={i}>
{row.map((number, j) => (
<div key={`${i}${j}`}>
{console.log("number: " + number)}
{console.log("draggable: " + this.isDraggable(i, j))}
<Tile
number={number}
position={[i, j]}
isAdjacent={this.isDraggable(i, j)}
onDrop={this.handleDrag.bind(this)}
/>
</div>
))}
</React.Fragment>
))}
</div>
</DndProvider>
);
}
}
Tile class
import React from 'react';
import { useDrag, useDrop } from 'react-dnd';
export function Tile({ number, position, isAdjacent, onDrop }) {
const [{ isDragging }, drag] = useDrag({
type: 'tile',
item: { position },
canDrag: isAdjacent,
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
});
const [{ isOver }, drop] = useDrop({
accept: 'tile',
canDrop: () => {
console.log("number", number)
if (number === -1) {
return true;
}
return isAdjacent;
},
drop: (item) => onDrop(item.position, position),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
});
const style = {
opacity: isDragging ? 0.5 : 1,
backgroundColor: isAdjacent ? 'lightgreen' : 'white',
border: isOver ? '2px dashed black' : 'none'
};
return (
<div
draggable={isAdjacent}
onDragStart={(e) => {
e.dataTransfer.setData('text/plain', ''); // required for Firefox
drag();
}}
ref={(node) => {
if (node !== null) {
drag(node.firstChild);
}
drop(node);
}}
style={style}
>
{number !== -1 && (
<div ref={drag} style={{ cursor: 'move' }}>
{number}
</div>
)}
</div>
);
}