- Home /
The question is answered, right answer was accepted
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.
Answer by Bunny83 · Apr 26, 2019 at 01:59 AM
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 work with strings because when I instantiate the Tetro$$anonymous$$oes, I do so by using Resources.Load. The returned string always has Prefab/ in front because it looks for that folder in the Resources folder. The generator specifies a Tetro$$anonymous$$o and instantiates it as a GameObject.
nextTetro$$anonymous$$o = (GameObject)Instantiate(Resources.Load(GetRandomTetro$$anonymous$$o(), typeof(GameObject)), new Vector2(5.0f, 20.0f), Quaternion.identity);
But I'm about to try out this method so if it works, then thanks a ton.
This works absolutely beautiful. All pieces are randomised, the prefabs no longer have to be in a Resources folder, and the code is a lot easier to read. Cheers mate.
Answer by surfuay · Apr 25, 2019 at 04:56 PM
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.
}
Why would you use an array with 8 elements if you only have 7 pieces? Also why rolling a random number between 0-8 (9 different numbers) if you only have 8 or 7 elements ?
The whold boolean thing won't work. How do you actually exclude elements you have already picked? Using bools is just a mess here ;)
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Is there a way to connect scripts in a way where they can be assembled like building blocks? 0 Answers
Play two Animations 1 Answer
Changing gravity direction... 5 Answers