[Closed] Problem add/remove elements from a list in C# while generating extra hex tiles on the map.

I have a few problems here though I think if some are fixed then others will be as well.

I am making an expanding hex based tile game that has no max size. It always starts with a specific tile that I have added to the scene. I also have a gameObject that is a “PossibleTile”. This basically gets replaced on a mouse click with the next actual tile, it is then supposed to add more possible tiles around it if none exist.

I have added the 6 original posible tiles to to the scene and to the list in the inspector. I think one of the problems is that the posible tile isn’t being removed from the surrounding tiles list correctly and is leaving a “missing” entry in the list in the inspector. I get an “ArgumentOutOfRange” error usually when trying to change one of the original tiles after the first one of the original possible tiles. Though it isn’t always related to that.

The second error seems to be a logic or processing error in that not all surrounding tiles are added everytime.

I’m new to the forums sorry if this post is long with the scripts. I added all of the three scripts as the logic errors could appear anywhere. This was still in prototype stage so I would have restructured it later to be more clean and modular.

I have a TileManager Script on a GameManager Object in the scene. This is that script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TileManager : MonoBehaviour {

    public Tile[] TileList = new Tile[24]; //Stores every possible tile
    public List<Tile> TileDeck; //This is the shuffled deck of the above collection
    public Tile CurrentTile; //This is the current tile incase any other code needs quick access to it
    public int CurrentTileIndex = 0; //This was anotherway to try to access the Tile in the Deck


    public List<Tile> PlacedTiles; //This store all the proper tiles on the map
    public List<Tile> SurroundingTiles; //This a list of all possible tile locations

    public int ShuffleTimes = 4; //This is the number of times to shuffle the deck

  
    // Use this for initialization
    void Start () {
        populateDeck(); //Adds all the tiles into the deck
        shuffleTileDeck(); //Shuffles the deck
        drawTile(); //draws the first tile
    }
  
    public void drawTile()
    {
        CurrentTile = TileDeck[0]; //Draws the top tile of the deck of tiles
    }

    //This was the old version in case this script was going to place the new tile.
    public void placeTile()
    {
        PlacedTiles.Add(CurrentTile); //Add current Tile to placed tiles
        TileDeck.RemoveAt(0); //Remove it from the deck
    }

    //This function is called from the possible tile when it replaces the tile
    public void placeTile(Tile newTile)
    {
        PlacedTiles.Add(newTile);
        TileDeck.RemoveAt(0);
    }

    //Add actual tiles to a deck of tiles
    public void populateDeck()
    {
        for (int i = 1; i < TileList.Length; i++)
        {
            TileDeck.Add(TileList[i]);
        }
    }

    //Shuffle Tile Deck
    public void shuffleTileDeck()
    {      
        int DeckSize = TileDeck.Count;
        for (int i = 0; i < ShuffleTimes; i++)
        {
            for (int j = 0; j < DeckSize; j++)
            {
            int k = Random.Range(0, (DeckSize - j));
                TileDeck.Add(TileDeck[k]);
                TileDeck.RemoveAt(k);
            }
        }
    }

    //This was an attempt to remove a possible tile from the list from within this script instead of the possible tile script
    public void RemoveTileFromPossibleList(Tile OldTile)
    {
        SurroundingTiles.Remove(OldTile);
    }

    //This is a placeholder for later when I need to rotate those tiles.
    #region Rotate Tiles
    public void RotateTileLeft()
    {
        CurrentTile.RotationClicks--;
        if (CurrentTile.RotationClicks<0)
        {
            CurrentTile.RotationClicks = 5;
        }
    }

    public void RotateTileRight()
    {
        CurrentTile.RotationClicks++;
        if (CurrentTile.RotationClicks>5)
        {
            CurrentTile.RotationClicks = 0;
        }
    }
    #endregion

}

Here is the Tile Script:

using UnityEngine;
using System.Collections;

public class Tile : MonoBehaviour{

    //Posible edge types
    public enum EdgeType
    {
        Water,
        Land,
    }

    //Each tile has 6 sides and each side is has two sides.
    public EdgeType[,] sides = new EdgeType[6,2];
    public int Id; //Stores an id for the tile to refer to it in a list

    //Simply stores the edges
    [Header("Edge Variables")]
    [Tooltip("Edge Variables with ship top corner facing left")]
    public EdgeType[] TopLeft = new EdgeType[2];
    public EdgeType[] Top = new EdgeType[2];
    public EdgeType[] TopRight = new EdgeType[2];
    public EdgeType[] BottomRight = new EdgeType[2];
    public EdgeType[] Bottom = new EdgeType[2];
    public EdgeType[] BottomLeft= new EdgeType[2];
  
    //Infor on where the tile is and how many times it rotated from normal.
    [Header("Placement Details")]
    public int RotationClicks = 0;
    public bool Placed = false;
    public int GridPos_x;
    public int GridPos_y;

    //The counter for each type of object on this tile
    [Header("Number of Units on Tile")]
    public int redUnitCount =0;
    public int redShipCount = 0;
    public int whiteUnitCount =0;
    public int whiteShipCount = 0;

}

Lastly the Possible Tile Script

using UnityEngine;
using System.Collections;

public class PossibleTile : Tile {

    //Reference to Tile Manager
    public TileManager Tm;

    void Start () {
        Tm = FindObjectOfType<TileManager>(); //Find TileManager on start
    }

    void OnMouseDown()
    {
        //When you click on a posible tile replace it with the curent tile from the tile deck
        Tile NewTileSpace = Instantiate(Tm.CurrentTile, transform.position, Quaternion.Euler(90, 0, 0)) as Tile;
        NewTileSpace.GridPos_x = GridPos_x;
        NewTileSpace.GridPos_y = GridPos_y;

        //Call the function from the Tile Manager that adds the new tile to the placed tile list
        Tm.placeTile(NewTileSpace);
        //Check to see if you can draw a tile
        if (Tm.TileDeck.Count != 0)
        {
            Tm.drawTile(); //Draw a new tile
            Tm.SurroundingTiles.Remove(this); //Remove this tile from the surrounding tiles
            AddExtraPossibleTiles(NewTileSpace); //Call function to add extra tiles
            Destroy(gameObject); //Destroy this possible tile
        }
    }

    public void AddExtraPossibleTiles(Tile NewPlacedTile)
    {
        //Booleans to check if a tile exists on each side of the possible/newtile
        bool TileAboveExists = false;
        bool TileBelowExists = false;

        bool TileAboveRightExists = false;
        bool TileBelowRightExists = false;

        bool TileAboveLeftExists = false;
        bool TileBelowLeftExists = false;

        //Loop though the actual tiles
        foreach (Tile Placed in Tm.PlacedTiles)
        {
            //These tiles share the same x
            if (Placed.GridPos_x == NewPlacedTile.GridPos_x)
            {
                //Check if this tile is 1 higher
                if (Placed.GridPos_y == NewPlacedTile.GridPos_y + 1)
                {
                    TileAboveExists = true;
                }
                //Check if this tile is 1 lower
                if (Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                {
                    TileBelowExists = true;
                }
            }

            //Check if the new tile is on an even x coordinate. Odd x coordinates are offset and reversed for some calculations.
            if (!IsOdd(NewPlacedTile.GridPos_x))
            {
                //Check Above Right
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x +1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileAboveRightExists = true;
                }
                //Check Below Right
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x+1) && Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                {
                    TileBelowRightExists = true;
                }
                //Check Above Left
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x -1)&& Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileAboveLeftExists = true;
                }
                //Check Below Left
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                {
                    TileBelowLeftExists = true;
                }
            }
            else //These do the same thing though are offset based off x
            {
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y+1)
                {
                    TileAboveRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileBelowRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y+1)
                {
                    TileAboveLeftExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileBelowLeftExists = true;
                }
            }
        }

        //Loop through possible tiles to see if they surround the new tile
        //This does the same as above but for the possible tiles list
        #region Loop Posible Tiles
        foreach (Tile Placed in Tm.SurroundingTiles)
        {
            if (Placed.GridPos_y == NewPlacedTile.GridPos_y + 1)
            {
                if (!TileAboveExists)
                {
                    if (Placed.GridPos_x == GridPos_x)
                    {
                        TileAboveExists = true;
                    }
                }
                if (!TileBelowExists)
                {
                    if (Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                    {
                        TileAboveExists = true;
                    }

                }
            }
            if (!IsOdd(NewPlacedTile.GridPos_x))
            {
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileAboveRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                {
                    TileBelowRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileAboveLeftExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y - 1)
                {
                    TileBelowLeftExists = true;
                }
            }
            else
            {
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y + 1)
                {
                    TileAboveRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x + 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileBelowRightExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y + 1)
                {
                    TileAboveLeftExists = true;
                }
                if (Placed.GridPos_x == (NewPlacedTile.GridPos_x - 1) && Placed.GridPos_y == NewPlacedTile.GridPos_y)
                {
                    TileBelowLeftExists = true;
                }
            }
        }
        #endregion

        //Generate a new tile in the spaces where a tile doesn't exist
        if (!TileAboveExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3(transform.position.x, transform.position.y, transform.position.z + 173.2051f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = GridPos_x;
            NewPTileSpace.GridPos_y = GridPos_y+1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
        if (!TileBelowExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3(transform.position.x, transform.position.y, transform.position.z - 173.2051f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = NewPlacedTile.GridPos_x;
            NewPTileSpace.GridPos_y = GridPos_y - 1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
        if (!TileAboveRightExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3((transform.position.x + 150), transform.position.y, transform.position.z + 86.6f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = GridPos_x+1;
            NewPTileSpace.GridPos_y = GridPos_y + 1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
        if (!TileBelowRightExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3((transform.position.x + 150), transform.position.y, transform.position.z - 86.6f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = GridPos_x +1;
            NewPTileSpace.GridPos_y = GridPos_y - 1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
        if (!TileAboveLeftExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3((transform.position.x - 150), transform.position.y, transform.position.z + 86.6f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = GridPos_x-1;
            NewPTileSpace.GridPos_y = GridPos_y + 1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
        if (!TileBelowLeftExists)
        {
            Tile NewPTileSpace = Instantiate(this, new Vector3((transform.position.x - 150), transform.position.y, transform.position.z - 86.6f), Quaternion.Euler(90, 0, 0)) as Tile;
            NewPTileSpace.GridPos_x = GridPos_x -1;
            NewPTileSpace.GridPos_y = GridPos_y - 1;
            NewPTileSpace.Id = (Tm.SurroundingTiles.Count + 1);
            Tm.SurroundingTiles.Add(NewPTileSpace);
        }
    }

    //Calculate if the x position is odd
    public static bool IsOdd(int value)
    {
        return value % 2 != 0;
    }
}

After your game has been running for a while, try iterating over your lists and checking for null tiles. Also check the list count vs what you expect. If your argument is out of range, you’re trying to access something you have removed.

It’s most likely the one where you use RemoveAt(k). When you do this, you change the count.

It’ll be easier to understand on a small scale. After this runs once, i is 1 and index 0 was removed, so index 0 is now 2 and index 1 is now 3. So the second iteration will work by removing 3 (index 1), but on the following run i will be 2 but the list will only have data at index 0. This will produce that same error.

List<int> a = new List<int>();
a.add(1);
a.add(2);
a.add(3);

for(int i = 0;i < a.Count;i++)
{
    a.removeAt(i);
}

The solution is to move your iterator back after you do a RemoveAt.

List<int> a = new List<int>();
a.add(1);
a.add(2);
a.add(3);

for(int i = 0;i < a.Count;i++)
{
    a.removeAt(i);
    i--;
}

Then your iterator will be at the correct place when you run the loop again. I debugged this on my project real quick just to confirm it. The starting values if you run the loop are 1,2,3 and after the loop, all that’s left is 2 at index 0. It didn’t throw an error in this case, but you can see how the behavior is unintended and can cause an error if you try to access one of these indices after code like that runs a few times :stuck_out_tongue:

I don’t think it is. That part of the code works. I get the size of the List before looping through, and adding and removing tiles.

That code is just to shuffle/randomise the deck of tiles.
It basically copies the tile from a random position in the deck to the end and then removes the original. The List is then automatically moving the others up. This is the problem with my actual problem. Either I remove it as a gameObject or remove it at the id (with RemoveAt() ), I still get the missing entry that confuses the code. I may be able to loop through the code to find “missing” entries though this seems like the wrong solution as well as pointless extra loops.

I think the error is somewhere in the possible tile script between lines 26-220.

Oops, it’s such a common mistake that my eyes ignored the definition of K.

If you double click on the error in your console, it’ll tell you which line threw the error. It should say the line right in the full error message, but double clicking it will open the exact line in monodevelop so you can also see surrounding code.

OK I fixed that error it seems.

I changed in the TileManager:

    public void placeTile(Tile newTile)
    {
        PlacedTiles.Add(newTile);
         TileDeck.RemoveAt(0);
    }

To:

    public void placeTile(Tile newTile)
    {
        PlacedTiles.Add(newTile);
        if (TileDeck.Count !=0)
        {
            TileDeck.RemoveAt(0);
        }
       
    }

I’m still getting the missing entries though it doesn’t seem to cause the OutOfRange Error anymore. I am still getting a logic or processing error though that it doesn’t always replace the the tile with the actual (current tile) tile though it can still create the other possible tiles around it. It sometimes creates the possible tile over an existing tile. It also on occasion doesn’t add a possible tile on one or two of the edges.

Unfortunately this isn’t an error that shows up in the logs.

You’re referencing CurrentTile in your add function, so if it’s ‘wrong’ then it’s being changed somewhere between where you select it and where you place it. If you debug some kind of value associated with the current tile every frame / every time it changes so you can see when it changes, you’ll see after you do something that either the initial value chosen is wrong or that it changes multiple times.

I’m not sure I understand what you are saying though I think that would be more work then necessary.

I’ve replaced this entire system with a tile specific raycast loop system. Basically when the tile is replaced it does 6 raycasts in all directions to see if a tile exists. It all works with that probably faster later aswell.

Thank you anyway.

If there is a fundamental flaw in a complex system, it’s often necessary to rewrite the whole system. It’s a good skill t figure out what went wrong, but either solution works in a 1 man project.

Good luck with stuff