//This method performs the actual "hold" action. The argument is the piece entering hold. The method sends the piece leaving hold (if any) to Orchestrator.SpawnPiece(). public void HoldPieceAction(BagPiece incomingPiece) { if (cooldown <= 0) { PersistantVars.pVars.PlaySound(SoundEffects.PIECE_HOLD); //Check for spiked hold. Note: This must be done before the actual hold action. Were it the other way around, it would be possible to spawn a piece then push a mino up inside that spawned piece with the garbage. if (ref_Orchestrator.ref_CurseManager.IsCurseActive(Curse.SPIKED_HOLD)) { spikes++; if (spikes >= SPIKED_HOLD_THRESHOLD) { spikes = 0; Stats.stats.IncStat("Garbage added from Spiked Hold"); ref_Orchestrator.ref_Board.AddCleanGarbage(); } UpdateSpikeDisplay(); } //Now handle the actual hold action. if (piecePrototype.HasProperty(BagPiece.EMPTY_HOLD)) //If there is no hold piece... { ref_Orchestrator.SpawnNextPiece(); //...just spawn the next piece. } else //If there is a hold piece... { ref_Orchestrator.SpawnPiece(piecePrototype, true); //...Spawn that hold piece. //true argument means that the piece to be spawned DID come from hold. } piecePrototype = incomingPiece; //Place the incoming piece into hold. cooldown = 1 + (ref_Orchestrator.ref_CurseManager.IsCurseActive(Curse.SLOW_HOLD) ? 1 : 0); //Sets cooldown to 1 normally, or 2 if slow hold curse is active. UpdateDisplay(); } }
/* * // Update is called once per frame * void Update() * { * * } */ public void UpdateDisplay(BagPiece piece, int numInQueue, bool isFogActive) { Vector2Int displayOffset = new Vector2Int(0, 0); //As the piece is drawn relative to the center-of-rotation mino, it needs to be shifted right and down so it doesn't collide with other pieces or the board. This indicates how much. //^The above was a class field, I don't think it needs to be a class field. //Set base properties minoes = PIECE_DATA[piece.pieceID].minoes; texture = PIECE_DATA[piece.pieceID].DetermineQueueTexture(piece, numInQueue, isFogActive); //Calculate displayOffset and displayHeight int minX = 0; //We don't need maxX because we don't actually care how far right the piece stretches. int minY = 0; int maxY = 0; foreach (Vector2Int mino in minoes) //Find the leftmost, topmost, and bottommost minoes. { minX = Mathf.Min(minX, mino.x); minY = Mathf.Min(minY, mino.y); maxY = Mathf.Max(maxY, mino.y); } //Set display height float displayHeight = (maxY - minY + 2) * MINO_SIZE; //Add two; one for an off-by-one error, one for padding. ownTransform.sizeDelta = new Vector2(QUEUE_DISPLAY_WIDTH, displayHeight); //Set the display offset accordingly. displayOffset.x = minX * -1; displayOffset.y = maxY * -1; //Set displayMinoes. displayMinoes = new Vector2Int[minoes.Length]; for (int i = 0; i < minoes.Length; i++) { displayMinoes[i] = minoes[i] + displayOffset; } }
//ResetObject is a method that appears in several object scripts that resets it to start-of-game values. public void ResetObject() { piecePrototype = new BagPiece(0, BagPiece.EMPTY_HOLD); //Initialize hold with a "hold is empty" piece. cooldown = 0; spikes = 0; ephemeral = 0; UpdateDisplay(); }
//BuildPseudoBag generates a bag for use in the Pseudo extra modes. A pseudo bag consists of all seven tetrominoes, two "very nice" pseudos, two "nice" pseudos, and one "mean" pseudo. public void BuildPseudoBag() { BagPiece[] bag = new BagPiece[12]; //First, add the seven tetrominoes. for (int i = 0; i < TETRA_BAG.Length; i++) { bag[i] = new BagPiece(TETRA_BAG[i]); } //Add two "very nice" pseudos. int firstPiece = VERY_NICE_PSEUDOS[Random.Range(0, VERY_NICE_PSEUDOS.Length)]; int secondPiece; while (true) { secondPiece = VERY_NICE_PSEUDOS[Random.Range(0, VERY_NICE_PSEUDOS.Length)]; if (firstPiece != secondPiece) { break; } } bag[7] = new BagPiece(firstPiece); bag[8] = new BagPiece(secondPiece); //Add two "nice" pseudos. firstPiece = NICE_PSEUDOS[Random.Range(0, NICE_PSEUDOS.Length)]; while (true) { secondPiece = NICE_PSEUDOS[Random.Range(0, NICE_PSEUDOS.Length)]; if (firstPiece != secondPiece) { break; } } bag[9] = new BagPiece(firstPiece); bag[10] = new BagPiece(secondPiece); //Add a "mean" pseudo. bag[11] = new BagPiece(MEAN_PSEUDOS[Random.Range(0, MEAN_PSEUDOS.Length)]); //Shuffle using the Fisher-Yates method, a shuffle proven to be the most efficient in both time (runs in O(n)) and memory (runs in-place with only one extra "swap" variable). for (int i = 0; i < bag.Length; i++) { int selectedElement = Random.Range(i, bag.Length); BagPiece swap = bag[selectedElement]; bag[selectedElement] = bag[i]; bag[i] = swap; } AddBag(bag); }
// BuildSetBag() generates a bag with a given set of pieces. Used in extra modes. public void BuildSetBag(int[] pieces) { BagPiece[] bag = new BagPiece[pieces.Length]; for (int i = 0; i < pieces.Length; i++) { bag[i] = new BagPiece(pieces[i]); } //Shuffle using the Fisher-Yates method, a shuffle proven to be the most efficient in both time (runs in O(n)) and memory (runs in-place with only one extra "swap" variable). for (int i = 0; i < pieces.Length; i++) { int selectedElement = Random.Range(i, pieces.Length); BagPiece swap = bag[selectedElement]; bag[selectedElement] = bag[i]; bag[i] = swap; } AddBag(bag); }
// BuildCursedBag() generates a bag of pieces to be added to the queue, in the main "cursed" game mode. public void BuildCursedBag() { //First, we need a new list of curses for this bag. Ask CurseManager to generate that. int[] bagCurses = ref_Orchestrator.ref_CurseManager.CreateNewCurses(); List <BagPiece> bag = new List <BagPiece>(INITIAL_BAG_LIST_SIZE); //Add the basic tetrominoes to the list. for (int i = 0; i < 7; i++) { bag.Add(new BagPiece(i)); } //If serenity is active, add two monominoes. if (bagCurses[0] < 0) { int monoIndex = ref_Orchestrator.mirrorMonominoRotation ? MIRROR_MONOMINO_PIECE_INDEX : MONOMINO_PIECE_INDEX; //This takes care of the "mirror monomino teleportation" player setting. for (int i = 0; i < MONOMINOES_PER_SERENE_BAG; i++) { bag.Add(new BagPiece(monoIndex)); } } //If pentomino curse is active, add pentominoes. Adds one "nice" pentomino then any other pentomino, and never the same one twice. if (IsCurseActiveThisBag(bagCurses, Curse.PENTA)) { int nicePentomino = NICE_PENTOMINOES[Random.Range(0, NICE_PENTOMINOES.Length)]; int otherPentomino; while (true) { otherPentomino = Random.Range(PENTOMINOES_START_INDEX, PENTOMINOES_END_INDEX + 1); if (otherPentomino != nicePentomino) { break; } } bag.Add(new BagPiece(nicePentomino)); bag.Add(new BagPiece(otherPentomino)); } //If h curse is active, add h piece. if (IsCurseActiveThisBag(bagCurses, Curse.SMALL_H)) { bag.Add(new BagPiece(H_PIECE_INDEX)); } //If drought is active, add three more tetrominoes (three unique pieces, none of which may be I or T) if (IsCurseActiveThisBag(bagCurses, Curse.DROUGHT)) { int[] pieceList = (int[])DROUGHT_FLOOD_PIECES.Clone(); //Sorta hacky but effective way to pick three unique pieces; just shuffle the list and use the first three. Because we only care about the first three, we can stop the shuffle after three. The shuffle itself is the ever-famous Fisher-Yates shuffle. for (int i = 0; i < 3; i++) { int randomIndex = Random.Range(i, 5); int temp = pieceList[i]; pieceList[i] = pieceList[randomIndex]; pieceList[randomIndex] = temp; } //Add the pieces to the bag. (Note: Technically, this loop can be merged into the one above, and also a couple lines from the above loop are unnecessary, but in this case I'm choosing clean and easy-to-read-later code over fast code. for (int i = 0; i < 3; i++) { bag.Add(new BagPiece(pieceList[i])); } } //If pseudo is active, add two pseudo pieces (which can't be two of the same piece) if (IsCurseActiveThisBag(bagCurses, Curse.PSEUDO)) { int firstPiece = Random.Range(PSEUDO_START_INDEX, PSEUDO_END_INDEX + 1); int secondPiece; while (true) { secondPiece = Random.Range(PSEUDO_START_INDEX, PSEUDO_END_INDEX + 1); if (firstPiece != secondPiece) { break; } } bag.Add(new BagPiece(firstPiece)); bag.Add(new BagPiece(secondPiece)); } //If twin is active, add a twinned piece. if (IsCurseActiveThisBag(bagCurses, Curse.TWIN)) { int piece = Random.Range(TWIN_START_INDEX, TWIN_END_INDEX + 1); bag.Add(new BagPiece(piece)); } //If big is active, add a big piece. if (IsCurseActiveThisBag(bagCurses, Curse.BIG)) { int piece = Random.Range(BIG_START_INDEX, BIG_END_INDEX + 1); bag.Add(new BagPiece(piece)); } //If big O is active, add a big O piece. if (IsCurseActiveThisBag(bagCurses, Curse.BIG_O)) { bag.Add(new BagPiece(BIG_O_PIECE_INDEX)); } //If big H is active, add a big H piece. if (IsCurseActiveThisBag(bagCurses, Curse.BIG_H)) { bag.Add(new BagPiece(BIG_H_PIECE_INDEX)); } //Shuffle the bag. This is done by creating a new array, picking a random value from the list, putting it in the array, removing it from the list, and repeating. BagPiece[] shuffledBag; if (IsCurseActiveThisBag(bagCurses, Curse.FLOOD)) //If Flood is active, make space for the extra Flood pieces generated after shuffle. { shuffledBag = new BagPiece[bag.Count + 3]; } else { shuffledBag = new BagPiece[bag.Count]; } int length = bag.Count; //The actual shuffle for (int i = 0; i < length; i++) { int index = Random.Range(0, bag.Count); shuffledBag[i] = bag[index]; bag.RemoveAt(index); } //If flood is active, add three of the same (non-I, non-T) piece. Must be done AFTER shuffle so they stay together in the queue. if (IsCurseActiveThisBag(bagCurses, Curse.FLOOD)) { //Hoo boy, you know the off-by-one error potential is bad when you have to break out pencil and paper, but I'm about 80% sure this is all correct. int randomPiece = DROUGHT_FLOOD_PIECES[Random.Range(0, DROUGHT_FLOOD_PIECES.Length)]; int randomLocation = Random.Range(0, shuffledBag.Length - 2); // Where Length-2 comes from: Start with Length becasue the flood can be inserted before any piece. Now add 1 because the flood can also be inserted at the end of the list as well. Now subtract 3 because the last three elements aren't pieces. Add 1 because Range() is exclusive on the upper bound. And subtract 1 because arrays index from 0. Len+1-3+1-1. Len-2. aaaaaaaaaaaaaaa. //Move everything in that randomLocation and onwards forward 3 places to make room for the flood pieces. for (int i = shuffledBag.Length - 1; i > randomLocation + 2; i--) // Where randomLocation+2 comes from: You need to clear out indices from randomLocation to randomLocation+2. If randomLocation+2 is clear, you're done. { shuffledBag[i] = shuffledBag[i - 3]; } //Now add the flood pieces shuffledBag[randomLocation] = new BagPiece(randomPiece); shuffledBag[randomLocation + 1] = new BagPiece(randomPiece); shuffledBag[randomLocation + 2] = new BagPiece(randomPiece); } //Handle disguise curse: If active, three random pieces are disguised and assigned the wrong color. if (IsCurseActiveThisBag(bagCurses, Curse.DISGUISE)) { int i = 0; while (i < DISGUISES_PER_BAG) { int rand = Random.Range(0, shuffledBag.Length); if (!shuffledBag[rand].HasAnyCursedProperty()) { shuffledBag[rand].AddProperty(BagPiece.DISGUISED); i++; } } } //Handle hard curse: If active, one random piece is made hard. Hard pieces must be cleared twice. The first time they are cleared, lines above them don't fall. if (IsCurseActiveThisBag(bagCurses, Curse.HARD)) { int i = 0; while (i < HARDS_PER_BAG) { int rand = Random.Range(0, shuffledBag.Length); if (!shuffledBag[rand].HasAnyCursedProperty()) { shuffledBag[rand].AddProperty(BagPiece.HARD); i++; } } } //Handle floating curse: If active, one random pieces is made floaty. Floaty pieces lock in one mino above where they would otherwise lock in, and cannot be soft-dropped and aren't affected by gravity. if (IsCurseActiveThisBag(bagCurses, Curse.FLOATING)) { int i = 0; while (i < FLOATINGS_PER_BAG) { int rand = Random.Range(0, shuffledBag.Length); if (!shuffledBag[rand].HasAnyCursedProperty()) { shuffledBag[rand].AddProperty(BagPiece.FLOATING); i++; } } } //Commit the bag to queue AddBag(shuffledBag); }