I’m developing a game in React for study purposes. It was inspired in the game called Trigon: Triangle Block Puzzle for mobile. It has a hexagonal board and the goal is to survive as long as you can, placing the pieces that the game gives you in the board as you try to fill the rows and cols, similar to Tetris.
The current step that I’m in is the piece interaction with the board. The board was made with 8 horizontal rows of triangles (from 0 to 7). When the piece is moved above the board, the corresponding triangles should be highlighted based on the shape of the piece.
The pieces are made with triangles as well. The pieces that are made with just 1 row (lozenges, parallelograms…) are working just fine. But I’m having trouble with the ones made with 2 rows, like the hexagon (6 triangles, 3 in each row). The issue occurs when I position the piece in the center of the board (rows 3 and 4): only half of the triangles (either the upper or lower ones) are highlighted correctly. This behavior seems to be caused by a misalignment between the upper and lower sections of the board.
Here’s a video showing the issue: video here; and also the App’s repository: repository here.
Relevant Code:
Here is the code responsible for calculating the highlighted triangles:
This is the way I’m making the board:
const Board = () => {
const [highlightedTriangles, setHighlightedTriangles] = useState([]); // Destaques
const boardRef = useRef(null);
const scale = 0.8; // Escala do tabuleiro
const size = 50 * scale; // Tamanho do triângulo
const height = (Math.sqrt(3) / 2) * size; // Altura do triângulo equilátero
const rows = 8; // Número total de fileiras
// Define o número de triângulos por fileira
const triangleCounts = [9, 11, 13, 15, 15, 13, 11, 9]; // Cresce até o meio e diminui
// Gera os triângulos
const generateTriangles = () => {
const triangles = [];
let yOffset = 0; // Deslocamento vertical para cada fileira
let xOffset = 0; // Deslocamento horizontal, será ajustado conforme a fileira
triangleCounts.forEach((count, rowIndex) => {
const rowWidth = count * (size / 2); // Largura total da fileira
const rowCenterOffset = (triangleCounts[4] * size) / 2 - rowWidth / 2; // Centraliza a fileira baseada na maior fileira
xOffset = rowCenterOffset; // Ajusta o xOffset de acordo com a centralização
for (let colIndex = 0; colIndex < count; colIndex++) {
const shouldInvert = count === 11 || count === 15;
const isUp = shouldInvert
? (rowIndex + colIndex) % 2 !== 0 // Inverte o padrão
: (rowIndex + colIndex) % 2 === 0; // Padrão original
const x1 = xOffset + colIndex * (size / 2); // Posição x1 do triângulo
const x2 = x1 + size; // Posição x2 do triângulo
const yBase = yOffset; // Posição y da fileira
const uniqueIndex = `${rowIndex}-${colIndex}`; // Identificador único do triângulo
const isHighlighted = highlightedTriangles.includes(uniqueIndex); // Verifica se o triângulo deve ser destacado
triangles.push(
<polygon
key={uniqueIndex}
points={
isUp
? `${x1},${yBase + height} ${x2},${yBase + height} ${x1 + size / 2},${yBase}`
: `${x1},${yBase} ${x2},${yBase} ${x1 + size / 2},${yBase + height}`
}
stroke="#222"
strokeWidth="1"
className={`triangles ${isUp ? "up" : "down"} ${isHighlighted ? "highlighted" : ""
}`}
data-unique-index={uniqueIndex}
/>
);
}
yOffset += height; // Ajuste para a próxima fileira (aumenta a altura a cada iteração)
});
return triangles;
};
// Função de colisão atualizada para considerar apenas o ponto central
const handlePieceHover = ({ center, shape }) => {
const boardTriangles = Array.from(
boardRef.current.querySelectorAll(".triangles")
).map((triangle) => {
const rect = triangle.getBoundingClientRect();
const uniqueIndex = triangle.getAttribute("data-unique-index");
const isUp = triangle.classList.contains("up"); // Verifica a classe "up" ou "down"
return { rect, uniqueIndex, isUp };
});
const centralTriangle = boardTriangles.find(({ rect }) => {
return (
center.x > rect.left &&
center.x < rect.right &&
center.y > rect.top &&
center.y < rect.bottom
);
});
if (!centralTriangle) {
setHighlightedTriangles([]);
return;
}
const [centralRow, centralCol] = centralTriangle.uniqueIndex
.split("-")
.map(Number);
const trianglesToHighlight = shape.map(({ x, y, orientation }) => {
const targetRow = centralRow + y;
const shouldApplyRowOffset = targetRow >= 4;
// Calcula o deslocamento horizontal somente se necessário
let rowOffset = shouldApplyRowOffset
? (triangleCounts[centralRow] - triangleCounts[targetRow]) / 1 || 0
: 0;
if (centralRow === 3) {
rowOffset = 0; // Não aplica deslocamento horizontal
}
// Aplica o deslocamento ao calcular o targetCol
const targetCol = centralCol + x - rowOffset;
// Busca o triângulo correspondente
const targetTriangle = boardTriangles.find(
({ uniqueIndex }) => uniqueIndex === `${targetRow}-${targetCol}`
);
// Verifica a orientação (up/down)
if (targetTriangle && targetTriangle.isUp === (orientation === "up")) {
return `${targetRow}-${targetCol}`;
}
return null;
});
// Filtra nulos e atualiza os destaques
setHighlightedTriangles(trianglesToHighlight.filter(Boolean));
};
return (
<>
<div ref={boardRef} className="container">
<svg
width={triangleCounts[3] * size + size / 2} // Calcula a largura para incluir a maior fileira e centralizar
height={(rows + 1) * height} // Adiciona espaço para as fileiras
>
{generateTriangles()}
</svg>
</div>
{/* Adiciona uma peça de exemplo */}
<Piece
shape="hexagon"
size={size}
scaleFactor={1}
onHover={(triangles) => handlePieceHover(triangles)}
/>
</>
);
};
export default Board;
This is the function responsible for the highlighting and in the future It’ll be the responsible for giving the pieces the coordinates to be inserted in the board:
const handlePieceHover = ({ center, shape }) => {
const boardTriangles = Array.from(
boardRef.current.querySelectorAll(".triangles")
).map((triangle) => {
const rect = triangle.getBoundingClientRect();
const uniqueIndex = triangle.getAttribute("data-unique-index");
const isUp = triangle.classList.contains("up"); // Verifica a classe "up" ou "down"
return { rect, uniqueIndex, isUp };
});
const centralTriangle = boardTriangles.find(({ rect }) => {
return (
center.x > rect.left &&
center.x < rect.right &&
center.y > rect.top &&
center.y < rect.bottom
);
});
if (!centralTriangle) {
setHighlightedTriangles([]);
return;
}
const [centralRow, centralCol] = centralTriangle.uniqueIndex
.split("-")
.map(Number);
const trianglesToHighlight = shape.map(({ x, y, orientation }) => {
const targetRow = centralRow + y;
const shouldApplyRowOffset = targetRow >= 4;
// Calcula o deslocamento horizontal somente se necessário
let rowOffset = shouldApplyRowOffset
? (triangleCounts[centralRow] - triangleCounts[targetRow]) / 1 || 0
: 0;
if (centralRow === 3) {
rowOffset = 0; // Não aplica deslocamento horizontal
}
// Aplica o deslocamento ao calcular o targetCol
const targetCol = centralCol + x - rowOffset;
// Busca o triângulo correspondente
const targetTriangle = boardTriangles.find(
({ uniqueIndex }) => uniqueIndex === `${targetRow}-${targetCol}`
);
// Verifica a orientação (up/down)
if (targetTriangle && targetTriangle.isUp === (orientation === "up")) {
return `${targetRow}-${targetCol}`;
}
return null;
});
// Filtra nulos e atualiza os destaques
setHighlightedTriangles(trianglesToHighlight.filter(Boolean));
};
The highlight that the board receives comes from the .highlighted class in globals.css. This class is applied by this coordinates given by the components Piece.jsx (the one responsible for making the pieces):
const shapes = {
parallelogram: [
{ x: 0, y: 0, orientation: "up" },
{ x: 1, y: 0, orientation: "down" },
{ x: 2, y: 0, orientation: "up" },
{ x: -1, y: 0, orientation: "down" },
],
lozenge: [
{ x: -1, y: 0, orientation: "down" },
{ x: 0, y: 0, orientation: "up" },
],
hexagon: [
{ x: -1, y: 0, orientation: "up" },
{ x: -3, y: 0, orientation: "up" },
{ x: -2, y: 0, orientation: "down" },
{ x: 0, y: 1, orientation: "down" },
{ x: -2, y: 1, orientation: "down" },
{ x: -1, y: 1, orientation: "up" },
],
};
This is the way I’m making the pieces:
const shapesSVG = {
hexagon: (
<g>
<path
d={`M0,0 L${scaledSize},0 L${scaledSize / 2},${height} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
<path
d={`M${scaledSize},0 L${scaledSize * 1.5},${height} L${scaledSize / 2},${height} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
<path
d={`M${scaledSize * 1.5},${height} L${scaledSize},${height * 2} L${scaledSize / 2},${height} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
<path
d={`M0,${height * 2} L${scaledSize / 2},${height} L${scaledSize},${height * 2} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
<path
d={`M0,${height * 2} L${-scaledSize / 2},${height} L${scaledSize / 2},${height} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
<path
d={`M0,0 L${-scaledSize / 2},${height} L${scaledSize / 2},${height} Z`}
fill="#98b68a"
stroke="#222"
strokeWidth="1"
/>
</g>
)
};
How can I adjust the calculation so the highlighting works correctly when placing the piece in the central rows (4 and 5) of the board?
What I’ve Tried:
- Calculating horizontal offsets (rowOffset) to align rows with different triangle counts.
- Adding specific checks to handle the central rows (4 and 5), but without success.
- Adjusting the offset calculations based on the width of adjacent rows.
The triangles are correctly highlighted when:
- The piece is in the upper rows (0-3).
- The piece is in the lower rows (4-8).
- However, the issue persists when handling the central rows (3 and 4), where the piece spans both the “upper hemisphere” and the “lower hemisphere” of the board.
CANDINIZ is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.