7-Bag Randomiser

TL;DR: I’ve created a randomiser for tetrominos with a switch statement that marks pieces chosen as “picked”, so that the switch statement can ignore it. However, the switch statement can still pick that case and results in returning the default Tetromino.


So, I’m working on a fan made Tetris game and I wanted to implement the 7 bag randomiser. (see this article for information)

So in order to generate a new Tetromino, I created a generate tetromino method which contains a switch statement that randomly picks out of 7 cases which Tetromino will be next.

string GetRandomTetromino ()
    {

        if (IUsed == true && LUsed == true && JUsed == true && OUsed == true && SUsed == true && ZUsed == true && TUsed == true) {

            IUsed = false;
            LUsed = false;
            JUsed = false;
            OUsed = false;
            SUsed = false;
            ZUsed = false;
            TUsed = false;

        }

        int randomTetromino = Random.Range(1, 8);

        // This string is used to define the location of the next Tetromino.
        string randomTetrominoName = "Prefabs/Tetromino_J";



        // This switch is used to get a random Tetromino out of 7.
        // NOTE: This needs to be modified to have a 7-bag randomiser later.
        switch (randomTetromino)
        {

            case 1:
                if (TUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_T";
                    TUsed = true;
                    
                }

                break;

            case 2:
                if (SUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_S";
                    SUsed = true;
                    
                }

                break;

            case 3:
                if (LUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_L";
                    LUsed = true;
                    
                }

                break;

            case 4:
                if (JUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_J";
                    JUsed = true;
                    
                }

                break;

            case 5:
                if (IUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_I";
                    IUsed = true;
                    
                }

                break;

            case 6:
                if (ZUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_Z";
                    ZUsed = true;
                    
                }

                break;

            case 7:
                if (OUsed == false) {
                    randomTetrominoName = "Prefabs/Tetromino_O";
                    OUsed = true;
                    
                }

                break;
            

        }

        // Return the location of the next Tetromino specified by the switch.
        return randomTetrominoName;

    }

Basically, I have 7 booleans called IUsed, LUsed, JUsed, etc. and they’re enabled if the randomiser picks a certain piece, signalling “Hey, this piece shouldn’t be called anymore”. If all of these booleans are enabled, the if statement at the start makes them all false so that the randomiser has options again.

There’s a problem though.

After 6 or so pieces pass by, a ton of J Pieces start appearing before the final piece comes along.
I found out that this is because the switch statement is ignoring pieces that have already been picked and the final piece only comes when it’s case is executed.

As a result, if the switch statement picks a case for a Tetromino already picked, it returns the default Tetromino: J.

Right here:

string randomTetrominoName = "Prefabs/Tetromino_J";

I’m struggling to find alternatives to or an exact way to re-execute/ing the switch statement so that it only returns once it gets a piece not already picked.

If anyone finds an alternative that’s better or a way to fix the issue with the current solution, all help would be appreciated.

Why do you work with strings here? Shouldn’t you have a list of gameobject references to those prefabs in order to instantiate them? To create a random bag drawing all you have to do is use a List. When you draw an element from that List, just remove that element from the list until the list is empty. If the list is empty just add all 7 elements again.

// assigned in the inspector or initialized inside Awake
public GameObject[] pieces;

private List<GameObject> bag = new List<GameObject>();

public GameObject DrawPiece()
{
    if (bag.Count == 0)
    {
        bag.AddRange(pieces);
    }
    int index = Random.Range(0, bag.Count);
    GameObject p = bag[index];
    bag.RemoveAt(index);
    return p;
}

That’s all you need. This is a self refilling bag of pieces. So if your pieces array contains 7 elements the “DrawPiece()” method will randomly draw one of those 7 pieces until the bag is empty. Then it automatically refills the bag.

Note instead of RemoveAt, one could actually use a slightly better approach since the order inside the bag doesn’t matter. You could move the last element to the removed index position and always remove the last element. This avoids the required element-down-shift operation that is required when removing an element in between. However since we only have 7 elements max in the bag it’s not really worth doing that. Such techniques are more important for larger lists (like 100+ elements). With 7 elements the average elements that need to be moved is lower than half the element count.

I like what you did there, I would use an array in conjuction with the bools

so in script you have at the top I would make

  public gameObject[] tetrominos;

then in the editor i would declare thesize of it (8) in your case and put a prefab in each of those.

Now when I go back to my script to see if I can do anything I believe I would need 1 method (this will be a skeleton of it instead of a full thing, expand as you need).

 public void AvailableTetrominos()
 {
      //your local int variable for the random range
      
       if (TetrominosAll == true)
      {
              //when you're doing an int range you always have to go 1 higher than the number you want.  It does not include the final number as a possibility.  It only does that in a float
              int randomTet = random.Range(0, 9)
              random tet is selected
      }
      else if  and on and on.  
 }