0
\$\begingroup\$

I am using a 2 dimensional array to set up the game board of an arkanoid-like game I'm making. I haven't really used 2d arrays before, but someone suggested this would work better for me than the list I was originally using. My problem is that when a brick spawns, I need to remove that element from the list of options for the random brick spawn location. My brick spawns are random, but with conditions. Other than the first one, they need to spawn touching another brick. For now, I am just trying to remove the locations that have already been spawned at. I thought maybe I could put an if statement around the brick spawning code that checks if the location is null. My problem with this method is that it has to choose a location, check if it is null, if it is then it has to check if it is touching another brick, and then finally place a brick if it passes both parameters. Once we get down to only a couple of locations left to pick though, it could go around for a very long time checking non-null locations before it finally finds the right locations, potentially bogging down the game significantly. I have posted my code below. I'm hoping someone has a good suggestion on how to do this because I can't seem to come up with anything solid. I'm open to completely revamping if need be, but I really feel like the 2 dimensional method is the best path. Thank you!

private int columns = 20;                //Number of columns in our game board.
    private int rows = 30;                  //Number of rows in our game board.

    public int brickCount;               //Number of bricks to spawn.

    //Define brick types to assign sprites to in editor
    public GameObject oneHitBrick;                                
    public GameObject twoHitBrick;                                  
    public GameObject threeHitBrick;                                 
    public GameObject explodingBarrel;  //temporarily represented by the unbreakable brick

    private Transform boardHolder;                                  //A variable to store a reference to the transform of our Board object.
    //private List <Vector3> gridPositions = new List <Vector3> ();   //A list of possible locations to place tiles.
    private Vector3[,] gridPositions;
    //Clears our list gridPositions and prepares it to generate a new board.
    void InitialiseList ()
    {
        //Clear our list gridPositions.
        Array.Clear(gridPositions, 0, gridPositions.Length);
        //gridPositions.Clear ();

        //Loop through x axis (columns).
        for(int x = 1; x < columns-1; x++)
        {
            //Within each column, loop through y axis (rows).
            for(int y = 22; y < rows-1; y++) //start at 22 so no bricks can be spawned below this line.
            {
                //At each index add a new Vector3 to our list with the x and y coordinates of that position.
                gridPositions[x,y] = new Vector3();
            }
        }
    }

    //RandomPosition returns a random position from our list gridPositions.
    Vector3 RandomPosition ()
{
    //Create 2 random ints to choose the grid position for a brick
    int randomIndex1 = Random.Range (1, gridPositions.Length);
    int randomIndex2 = Random.Range (1, gridPositions.Length);

    //Declare a variable of type Vector3 called randomPosition, set it's value to the entry at randomIndex from our List gridPositions.
    Vector3 randomPosition = gridPositions [randomIndex1,randomIndex2];

        //Remove the entry at randomIndex from the array so that it can't be re-used.
        gridPositions[randomIndex1,randomIndex2] = null; //Error: Cannot convert null to 'UnityEngine.Vector3' because it is a non-nullable type

        //Return the randomly selected Vector3 position.
        return randomPosition;
    }


    //LayoutObjectAtRandom accepts an array of game objects to choose from along with a minimum and maximum range for the number of objects to create.
    void LayoutObjectAtRandom (GameObject brick, int brickCount)
    {

        //Instantiate objects until the randomly chosen limit objectCount is reached
        for(int i = 0; i < brickCount; i++)
        {
            //Choose a position for randomPosition by getting a random position from our list of available Vector3s stored in gridPosition
            Vector3 randomPosition = RandomPosition();

            //Instantiate tileChoice at the position returned by RandomPosition with no change in rotation
            Instantiate(brick, randomPosition, Quaternion.identity);
        }
    }


    //SetupScene initializes our level and calls the previous functions to lay out the game board
    public void SetupScene (int level)
    {
        //Reset our list of gridpositions.
        InitialiseList ();

        brickCount = (int)Mathf.Log(level, 2f); //set the number of bricks based on the current level

        //Instantiate a random number of wall tiles based on minimum and maximum, at randomized positions.
        LayoutObjectAtRandom (oneHitBrick, brickCount);

        //Instantiate a random number of food tiles based on minimum and maximum, at randomized positions.
        LayoutObjectAtRandom (twoHitBrick, brickCount);

        LayoutObjectAtRandom (threeHitBrick, brickCount);
    }
\$\endgroup\$
  • \$\begingroup\$ This is a little difficult for me to follow - is your goal to randomly select 2D locations that touch other bricks, but aren't filled by bricks themselves? \$\endgroup\$ – Pikalek Apr 4 '18 at 14:51
  • \$\begingroup\$ I'm sorry, yes I want to spawn a series of bricks, all touching, but I want to make sure that when it randomly selects a location to place a brick that there isn't already a brick there. My problem is that if I select the location, then check for a brick, it can get pretty inefficient. I'd like it to remove the location from the choices after placing a brick there. \$\endgroup\$ – ethan codes Apr 4 '18 at 14:54
  • \$\begingroup\$ It's okay - getting a concise & accurate problem description is half the battle. \$\endgroup\$ – Pikalek Apr 4 '18 at 21:34
1
\$\begingroup\$

The challenge here is that you actually want to track a couple of related but different things: occupied positions & candidate positions. One solution is to use one to derive the other as needed. That would work & might save some memory, but it comes at the expense of speed, and in my opinion, solution clarity.

Instead, what I would suggest is to track both separately. Furthermore, I would use a combination of both arrays & lists as I think it makes for a bit more solution clarity. The arrays are nice because you can easily check & update any position in the grid of used & candidate positions. However, the dynamically sized lists are handy for things like randomly selecting a valid candidate & reporting all the spawn locations without iterating over the whole works.

Finally, the Point class is useful because it bundles up the notion of an X,Y coord in a single object. So instead of having to track the coordinate parts separately, we can just keep a list of points.

The solution overview is:

  • Place your first brick, either manually or randomly.
  • Each time a brick is placed check its neighboring positions.
  • If a given neighboring position hasn't already been spotted before, add it to a list of candidates.
  • There after, you can easily select a random candidate position from the list built in the previous step.

C# isn't my primary language, but I took a shot at some demo code (also available here):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Drawing;

namespace Rextester {
    public class Program {
        bool[,] hasBrick;
        bool[,] alreadyOpen;
        List<Point> openPositions;
        List<Point> brickPositions;
        int width;
        int height;

        public void Init() {
            width = 20;
            height = 10;            
            hasBrick = new bool[width,height];
            alreadyOpen = new bool[width,height];
            openPositions = new List<Point>();
            brickPositions = new List<Point>();
        }

        public void AddBrick(int x, int y) {
            // if there's already a brick here, bail out
            if (hasBrick[x,y]) {
                return;
            }
            // add the brick
            hasBrick[x,y] = true;
            brickPositions.Add(new Point(x,y));
            // add legal neighboring candidate locations
            if (x+1 < width && !alreadyOpen[x+1,y]) {
                openPositions.Add(new Point(x+1,y));
                alreadyOpen[x+1,y] = true;
            }
            if (x-1 >= 0 && !alreadyOpen[x-1,y]) {
                openPositions.Add(new Point(x-1,y));
                alreadyOpen[x-1,y] = true;
            }
            if (y+1 < height && !alreadyOpen[x,y+1]) {
                openPositions.Add(new Point(x,y+1));
                alreadyOpen[x,y+1] = true;
            }
            if (y-1 >=0 && !alreadyOpen[x,y-1]) {
                openPositions.Add(new Point(x,y-1));
                alreadyOpen[x,y-1] = true;
            }
        }
        public Point GetRandomOpenPosition(Random rng){
            int index = rng.Next(openPositions.Count());
            Point result = openPositions[index];
            openPositions.RemoveAt(index);
            return result;
        }
        public void Display() {
            for(int a=0; a<height; a++) {
                for(int b=0; b<width; b++) {
                    if(hasBrick[b,a]){
                        Console.Write("#");
                    }
                    else if(alreadyOpen[b,a]) {
                        Console.Write("O");
                    }
                    else {
                        Console.Write(".");
                    }                    
                }
                Console.WriteLine();
            }
        }
        public static void Main(string[] args){

            Random rng = new Random();
            Program app = new Program();
            app.Init();
            app.AddBrick(4,7);

            for(int a=0; a<25; a++){
                Point p = app.GetRandomOpenPosition(rng);
                app.AddBrick(p.X, p.Y);
            }

            app.Display();
        }
    }
}

Note: I wasn't able to infer exactly how you are defining neighbors, so I assumed north, south, east, west. If you want diagonals, you'll need to add the corresponding four additional candidate position checks (NE, NW, SE, SW) in the AddBrick(..) method.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.