Why does my pieces misalign when placed in the center of my React-based board game?

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.

New contributor

CANDINIZ is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật