how to make a horizontal axis symmetry of an array

Hi everyone,

I manage to place 1’s in the 8x8 grid with the following code:

private void OnMouseUp()
    {
        float gridSize = 8;
        int row = Mathf.FloorToInt((transform.position.z + gridSize / 2f) / 1.6f);
        int col = Mathf.FloorToInt((transform.position.x + gridSize / 2f) / 1.6f);
        UpdateGridWithPentaminoX(row, col);
     }

    void UpdateGridWithPentaminoX(int row, int col)
    {
        for (int i = 0; i < 5; i++)
           for (int j = 0; j < 5; j++)
             if (pentaminoX[i, j] == 1)
               if (row + i >= 0 & row + i < 8 & col + j >= 0 & col + j < 8)
                    {
                        grid[row + i, col + j] = 1;
                    }
   }

I calculate the position of the 1s using the following formulas:

int row = Mathf.FloorToInt((transform.position.z + gridSize / 2f) / 1.6f);
int col = Mathf.FloorToInt((transform.position.x + gridSize / 2f) / 1.6f);

This works well, but the problem is that the 1’s are symmetrically positioned along the horizontal axis where the pentamino X is placed.
How do you make the horizontal axis symmetrical in the middle of the height?
I’ve tried this, but it doesn’t work (grid[row + i, col + (8 - j)] = 1;)

 void UpdateGridWithPentaminoX(int row, int col)
    {
        for (int i = 0; i < 5; i++)
          for (int j = 0; j < 5; j++)
            if (pentaminoX[i, j] == 1)
                if (row + i >= 0 & row + i < 8 & col + (8 - j) >= 0 & col + (8 - j) < 8)
                   {
                       grid[row + i, col + (8 - j)] = 1;
                   }
     }

Here is a visual sample (You can see the axis of symmetry in red in the image below.):

My question is the following :
how to make a horizontal axis symerty of an array?

If you can review my formulas,

thank you.

When working with two-dimensional arrays, it’s very easy to confuse in the code where the columns are and where the rows are. Moreover, you need to be very careful when dealing with nested for loops when iterating over such arrays, to clearly understand how you are “traversing” this matrix (from left to right, top to bottom, or vice versa).

Of course, when you see such an array in the code editor (as text), everything is obvious - the column goes from top to bottom, the row goes from left to right, and the origin is at the top left. BUT, as soon as you need to transfer this data to the console screen or to 3D/2D space, confusion arises because somewhere the zero coordinates of the screen start from the top left, somewhere from the bottom left. And in 3D, it starts from the middle of the world towards the positive direction along any of the axes. Moreover, the axes can also be different, somewhere the positive Y-axis goes up (as in Unity), and somewhere the positive Z-axis goes up, and so on.

For example, a very common situation is when such an array looks flipped in any direction when output, just like in your case. In such a situation, you need to analyze again in what sequence you output information to the screen or to another space.

I don’t think the code below will help you, but it’s like an example of your idea. There’s a grid, and different shapes above it, and you need to determine which cells these shapes occupy. Right now, it works without launching the game, directly in the editor. You can adjust the number of cells on the grid:

And when moving, the occupied cells turn green, and a two-dimensional array of this grid is additionally displayed in the Game window as zeros and ones.

Here is a lot of unnecessary code aimed solely at visualization. The main logic is located within the Update function.

Code

using System.Linq;
using UnityEngine;

[ExecuteAlways]
public class ArrayGridField : MonoBehaviour
{
    [Min(2)]
    public int GridSizeX = 10;

    [Min(2)]
    public int GridSizeZ = 8;

    public Transform[] Figures;

    public int[,] _grid;
    private void OnValidate()
    {
        _grid = new int[GridSizeZ, GridSizeX];
    }

    private void OnDrawGizmos()
    {
        float halfCell = 0.5f;
        Vector3 cellSize = new Vector3(0.9f, 0.1f, 0.9f);


        for (int z = 0; z < GridSizeZ; z++)
        {
            for (int x = 0; x < GridSizeX; x++)
            {
                Vector3 cellCelnter = new Vector3(x + halfCell, 0, z + halfCell);

                if (_grid[z, x] == 1)
                {
                    Gizmos.color = Color.green;
                    Gizmos.DrawCube(cellCelnter, cellSize);
                }
                else
                {
                    Gizmos.color = Color.white;
                    Gizmos.DrawCube(cellCelnter, cellSize);
                }

                //UnityEditor.Handles.Label(cellCelnter, _grid[z, x].ToString());
            }
        }
    }

    private void Update()
    {
        var changedFigures = Figures.Where(i => i.hasChanged);

        if (changedFigures.Count() != 0)
        {
            ResetGrid();

            foreach (var figure in changedFigures)
            {
                foreach (Transform box in figure)
                {
                    int x = Mathf.FloorToInt(box.position.x);
                    int z = Mathf.FloorToInt(box.position.z);

                    if (PositionIsValid(x, z))
                    {
                        _grid[z, x] = 1;
                    }
                }
            }
        }
    }

    private void ResetGrid()
    {
        for (int z = 0; z < GridSizeZ; z++)
        {
            for (int x = 0; x < GridSizeX; x++)
            {
                _grid[z, x] = 0;
            }
        }
    }



    private void OnGUI()
    {
        Rect rect = new Rect(10, (GridSizeZ * 20), 20, 20);

        for (int z = 0; z < GridSizeZ; z++)
        {
            for (int x = 0; x < GridSizeX; x++)
            {
                GUI.color = _grid[z, x] == 1 ? Color.yellow : Color.black;
                GUI.Label(rect, _grid[z, x].ToString());
                rect.x += 20;
            }
            rect.x = 10;
            rect.y -= 20;
        }
    }

    private bool PositionIsValid(float x, float z)
    {
        return x >= 0 && x < GridSizeX && z >= 0 && z < GridSizeZ;
    }
}

Just in case, I’ll provide the package so you can immediately “try out” the example.

9724675–1390363–GridExample.unitypackage (17.6 KB)

4 Likes

Hi everyone,

I imported your code but I encountered several errors:

Why in your GridExample file, we can’t move the pentaminoes.

In addition, the spoiler code highlights a number of code errors that I can’t resolve.

Notably in this line of code:

var changesFigures = Figures.Where(i => i.hasChanged);

more precisely the code ‘Where’.

Your new code may help me further,

Thank you,

A+

Strange, I double-checked the package just in case by importing it into the project, but no errors occurred. From the description of ‘several errors,’ it’s impossible to assume or advise anything to you. Each error always has its description and information about the possible issue, all of which are outputted to the console. Additionally, the line number in which the error occurred is always indicated.

There is no implementation of pentomino movement in the code. Currently, the figures can be moved simply using the ‘move’ tool, just like any object in the scene. This code is not a test assignment or a game. It’s just an example of how to associate cellular figures with a two-dimensional array.

I don’t understand what you’re talking about.

What do you think should be in the new code? The entire game?

Hi,

I’m not asking for the full set (unless you’d like me to),

but I would like to be able to move the pentominoes on the grid with the mouse.

Thanks in advance,

A+

“.Where” is a LINQ extension. Make sure you have imported the namespace System.Linq.

Tip: Some errors can be easily fixed by the IDE (like a missing namespace import). Just put your text cursor on the red line, and hit Alt+Enter (or click the yellow light bulb when it appears) to open a suggestion window with the possible fix.

I’m trying to find the code that creates the following result:

[[ /url]

I’d like to move the pentominoes with the mouse and update the grid.](24/13/rvhb.gif - Visionneuse Zupimages)

Or tell me how to make a horizontal axes symmetry in the middle of the grid height (see the red line in the image above) of a 2 dimensional array.
[
Thanks for your help,

A+](24/13/rvhb.gif - Visionneuse Zupimages)

I think there is no need to use a two-dimensional array in this task. After all, the field can be of any shape, such as rectangular or in the form of silhouettes of different objects. Therefore, it is much simpler to just place the cells of the field as needed, and to check if the cubes of the dragged figure are above the cells, it is enough to simply check the coincidence of their coordinates for each.

Today, I tried to make a demo of such a game, the package of which I will attach below. Only standard cubes are used there, with the center of rotation being in the middle. Therefore, when preparing the level, enable snap to grid in the editor (standard grid cell is 1x1), so that all field cells look even. The package also contains prefabs of different figures, although you can easily add your own. As well as a prefab for a field cell from which you need to build the level. Shapes are moved with the left mouse button, and you can rotate the shape with the Q and W key for flip it.

IMPORTANT CONDITION: since the field is located in the XZ plane, place the field cells and shapes in these same planes. There should be only the set of shapes on the level that completely covers the field, that is, the level cannot be passed if there are extra shapes left, or if any part of the shape goes beyond the field.

For example, you can create such a level:

Or something more complex:

You can find many interesting shapes for such a game on the internet.

Here’s the code for those who don’t want to download the package.
Cell.cs

using UnityEngine;

public class Cell : MonoBehaviour
{
    public Material EmptyMat;
    public Material BusyMat;

    private bool _isBusy;

    public bool IsBusy
    {
        get => _isBusy;
        set
        {
            _isBusy = value;
            GetComponent<Renderer>().material = _isBusy ? BusyMat : EmptyMat;
        }
    }
}

Shape.cs

using System.Collections;
using UnityEngine;

[SelectionBase]
public class Shape : MonoBehaviour
{
    private float _rotationTimeSec = 0.2f;

    private Vector3 _currentRotation;
    private Vector3 _targetRotation;

    private Coroutine _rotationCW;
    private Coroutine _flipZ;
    public void SetPivot(Vector3 wolrdPoint)
    {
        Vector3 offset = transform.position - wolrdPoint;
        transform.position -= offset;
        foreach (Transform child in transform)
        {
            child.position += offset;
        }
    }

    public void RotateCW()
    {
        if (_rotationCW != null)
            StopCoroutine(_rotationCW);

        _rotationCW = StartCoroutine(RotateCWCoroutine());
    }

    public void RotateFlipZ()
    {
        if (_flipZ != null)
            StopCoroutine(_flipZ);

        _flipZ = StartCoroutine(RotateFlipCoroutine());
    }

    private IEnumerator RotateCWCoroutine()
    {
        _targetRotation.y += 90f;

        float from = _currentRotation.y;
        float to = _targetRotation.y;

        float progress = 0;

        while (progress < 1f)
        {
            progress += 1f / _rotationTimeSec * Time.deltaTime;
            progress = Mathf.Clamp01(progress);

            float progressEaseOut = 1 - Mathf.Pow(1 - progress, 3);
            _currentRotation.y = Mathf.Lerp(from, to, progressEaseOut);
            transform.eulerAngles = _currentRotation;


            yield return null;
        }
    }
    private IEnumerator RotateFlipCoroutine()
    {
        _targetRotation.z += 180f;

        float from = _currentRotation.z;
        float to = _targetRotation.z;

        float progress = 0;

        while (progress < 1f)
        {
            progress += 1f / _rotationTimeSec * Time.deltaTime;
            progress = Mathf.Clamp01(progress);

            float progressEaseOut = 1 - Mathf.Pow(1 - progress, 3);
            _currentRotation.z = Mathf.Lerp(from, to, progressEaseOut);
            transform.eulerAngles = _currentRotation;

            yield return null;
        }

    }

}

PentaminoGame.cs

using UnityEngine;

public class PentaminoGame : MonoBehaviour
{
    private Camera _camera;

    // dragging stuf
    private Shape _draggedFigure;
    private Plane _plane;
    private Vector3 _draggingMouseOffset;

    private Cell[] _cells;
    private Shape[] _shapes;
    private int _shapesItemsCount;

    private bool _gameIsComplete;

    private void Start()
    {
        _camera = FindObjectOfType<Camera>();
        _cells = FindObjectsOfType<Cell>();
        _shapes = FindObjectsOfType<Shape>();

        foreach (Shape shape in _shapes)
        {
            _shapesItemsCount += shape.transform.childCount;
        }

        if (_shapesItemsCount != _cells.Length)
        {
            Debug.LogError("ERROR IN PENTOMINO SETUP: the number of cells on the board does not match the space occupied by the shapes. Make sure that the current pentomino consists ONLY of shapes present on the scene.");
        }
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray mouseRay = _camera.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(mouseRay, out RaycastHit hitInfo))
            {
                if (hitInfo.collider.transform.root.TryGetComponent<Shape>(out _draggedFigure))
                {
                    _draggedFigure.SetPivot(hitInfo.transform.position);
                    _plane = new Plane(Vector3.up, _draggedFigure.transform.position);
                    _plane.Raycast(mouseRay, out float enter);

                    _draggingMouseOffset = _draggedFigure.transform.position - mouseRay.GetPoint(enter);
                }
            }
        }


        if (_draggedFigure != null)
        {
            Ray mouseRay = _camera.ScreenPointToRay(Input.mousePosition);
            _plane.Raycast(mouseRay, out float enter);
            _draggedFigure.transform.position = mouseRay.GetPoint(enter) + _draggingMouseOffset;


            if (Input.GetKeyDown(KeyCode.Q))
            {
                _draggedFigure.RotateCW();
            }

            if (Input.GetKeyDown(KeyCode.W))
            {
                _draggedFigure.RotateFlipZ();
            }

            int busyCellsCount = DefineBusyCells();

            if (Input.GetMouseButtonUp(0))
            {
                Vector3 snapPos = _draggedFigure.transform.position;
                snapPos.x = Mathf.RoundToInt(snapPos.x);
                snapPos.z = Mathf.RoundToInt(snapPos.z);

                _draggedFigure.transform.position = snapPos;
                _draggedFigure = null;

                _gameIsComplete = busyCellsCount == _cells.Length && busyCellsCount == _shapesItemsCount;
            }
        }
    }

    private int DefineBusyCells()
    {
        int busyCount = 0;

        foreach (Cell cell in _cells)
        {
            cell.IsBusy = false;
            int cellX = Mathf.RoundToInt(cell.transform.position.x);
            int cellZ = Mathf.RoundToInt(cell.transform.position.z);

            foreach (Shape shape in _shapes)
            {
                foreach (Transform cubeChild in shape.transform)
                {
                    int cubeX = Mathf.RoundToInt(cubeChild.position.x);
                    int cubeZ = Mathf.RoundToInt(cubeChild.position.z);

                    if (cellX == cubeX && cellZ == cubeZ)
                    {
                        cell.IsBusy = true;
                        busyCount++;
                        break;
                    }
                }

                if (cell.IsBusy)
                {
                    break;
                }
            }
        }

        return busyCount;
    }


    private void OnGUI()
    {
        GUILayout.Label("LMB: dragging colored shapes");
        GUILayout.Label("Q: rotate cw");
        GUILayout.Label("W: flip");

        GUILayout.Space(15);

        if (_gameIsComplete)
        {
            if (Time.frameCount % 10 <= 5) // blink effect
                GUILayout.Label("GAME STATUS: COMPLETE!!!");
        }
        else
        {
            GUILayout.Label("GAME STATUS: not complete..");
        }
    }
}

9737131–1392622–PentaminoDemo.unitypackage (59.3 KB)

1 Like

In fact, I’m looking to update the grid with 0s and 1s (where the pentomino cubes are placed).

If you have any ideas.

A+

One of the options with a two-dimensional array was in the second post of this thread. If you want, I can provide the permalink to that answer here so you don’t have to scroll up the page yourself.

Hi,

I look forward to receiving the permanent link from you.

The magical step into the past #2

Hello,

I’d like to update the grid using the OnGUI() code to display the grid values on the screen.
To do this, I use the following code:

private void OnGUI()
    {
        Rect rect = new Rect(10, (GridSizeZ * 20), 20, 20);
        for (int z = 0; z < GridSizeZ; z++)
        {
            for (int x = 0; x < GridSizeX; x++)
            {
                GUI.color = _grid[z, x] == 1 ? Color.yellow : Color.black;
                GUI.Label(rect, _grid[z, x].ToString());
                rect.x += 20;
            }
            rect.x = 10;
            rect.y -= 20;
        }
    }

The problem is that no numbers appear on the screen.
I’ve tried to create a canvas, but it’s not configurable and the text still doesn’t appear.

If you have any help,

A+