// ------------------------- // Autostep methods // ------------------------- // flash the good and the bad tiles public void FlashBeforeAutostep() { // restore the puzzle state to the last good state SaveAll(); RestoreAll(); Vector2Int lastGood, nextGood; CheckPuzzleStateComplete(false, out lastGood, out nextGood); List <TileFlashArgs> flashList = new List <TileFlashArgs>(); // flash a tile to from to with the bad color TileStatus badTile = tileNeighbours[nextGood.x, nextGood.y]; flashList.Add(new TileFlashArgs { id = new Vector2Int(badTile.id.x, badTile.id.y), type = FlashType.Bad }); // flash a tile to go to with the good color TileStatus goodTile = tileNeighbours[badTile.id.x, badTile.id.y]; flashList.Add(new TileFlashArgs { id = new Vector2Int(goodTile.id.x, goodTile.id.y), type = FlashType.Good }); GlobalManager.MInstantMessage.DeliverMessage(InstantMessageType.PuzzleFlashTile, this, flashList); }
// --------------------- // logic checking // --------------------- // this method performs several calculations at once: // 1. it checks if the puzzle is assembled, and returns true/false according to the state of completeness // 2. it looks for the latest tile which is already in its place, and returns its position in lastGood // 3. it looks for the tile which should be placed after the lastGood one, and returns its position in nextGood // 4. it alse flashes the already assembled tiles if needed bool CheckPuzzleStateComplete(bool flashTiles, out Vector2Int lastGood, out Vector2Int nextGood) { Vector2Int current = new Vector2Int(0, 0); Vector2Int last = new Vector2Int(-1, -1); Vector2Int next = new Vector2Int(0, 0); nextGood = next; bool isComplete = true; List <TileFlashArgs> flashList = new List <TileFlashArgs>(); for (int y = 0; y < descriptor.init.height; y++) { for (int x = 0; x < descriptor.init.width; x++) { TileStatus tileStatus = tileNeighbours[x, y]; if (tileStatus.id != current || tileStatus.angle != 0) { isComplete = false; // set the nextGood position if (tileStatus.id.x == next.x && tileStatus.id.y == next.y) { nextGood = new Vector2Int { x = x, y = y }; } } else { if (isComplete) { if (flashTiles) { // this tile is in the place, so it should flash flashList.Add(new TileFlashArgs { id = new Vector2Int(x, y), type = FlashType.Good }); } last = current; // calculate the nextGood parameters if (++next.x >= descriptor.init.width) { next.x = 0; next.y++; } } } current.x++; } current.x = 0; current.y++; } lastGood = last; if (flashTiles) { GlobalManager.MInstantMessage.DeliverMessage(InstantMessageType.PuzzleFlashTile, this, flashList); } return(isComplete); }
// this method checks if the current puzzle state is "better" than the one in parameters // this means that the current puzzle state has more first "inplace" tiles than the other // if current puzzle state is better than aState, then return 1 // if current puzzle state is worse than aState, then return -1 // otherwise, return 0 int IsPuzzleStateBetter(string aState, out Vector2Int thisGoodTile, out Vector2Int otherGoodTile) { thisGoodTile = new Vector2Int(-1, -1); otherGoodTile = new Vector2Int(-1, -1); if (string.IsNullOrEmpty(aState)) { return(1); } TileStatus[,] otherTiles = new TileStatus[descriptor.init.width, descriptor.init.height]; ParseStatusString(aState, ref otherTiles); bool checkThis = true; bool checkOther = true; for (int y = 0; y < descriptor.init.height && (checkThis || checkOther); y++) { for (int x = 0; x < descriptor.init.width && (checkThis || checkOther); x++) { if (checkThis) { TileStatus thisTile = tileNeighbours[x, y]; if (thisTile.id.x == x && thisTile.id.y == y && thisTile.angle == 0) { thisGoodTile.x = x; thisGoodTile.y = y; } else { checkThis = false; } } if (checkOther) { TileStatus otherTile = otherTiles[x, y]; if (otherTile.id.x == x && otherTile.id.y == y && otherTile.angle == 0) { otherGoodTile.x = x; otherGoodTile.y = y; } else { checkOther = false; } } } } int thisId = thisGoodTile.y * descriptor.init.width + thisGoodTile.x; int otherId = otherGoodTile.y * descriptor.init.width + otherGoodTile.x; return(thisId > otherId ? 1 : (thisId > otherId ? -1 : 0)); }
IEnumerator PerformAutoStep() { // wait for a frame to ensure a clean steady puzzle state yield return(null); //Debug.Log("Starting autostep"); Vector2Int lastGood, nextGood; CheckPuzzleStateComplete(false, out lastGood, out nextGood); TileStatus tileStatus = tileNeighbours[nextGood.x, nextGood.y]; byte[] solution = AutoStepSolutions.GetSolution( descriptor.init.height, descriptor.init.width, tileStatus.id.y * descriptor.init.width + tileStatus.id.x, nextGood.y, nextGood.x, tileStatus.angle ); if (solution != null) { //Debug.Log("Running autostep for " + nextGood.ToString() + " to " + lastGood.ToString()); descriptor.state.AutocompleteUsed = true; PuzzleButtonController.PuzzleButtonArgs buttonArgs = new PuzzleButtonController.PuzzleButtonArgs { id = new Vector2Int(0, 0), fast = 0.5f }; for (int i = 0; i < solution.Length && !puzzleComplete; i++) { buttonArgs.id.y = (solution[i] >> 4) & 0xf; buttonArgs.id.x = solution[i] & 0xf; //Debug.Log(">> Step " + i.ToString() + ": rotating button " + buttonArgs.id.ToString()); RotateButton(buttonArgs, false); while (!buttonRotated) { yield return(null); } } //Debug.Log("Saving state"); SaveAll(); // tight vibe sound autostepJingle.pitchFactor = autostepPitchRange.Random; GlobalManager.MAudio.PlaySFX(autostepJingle); //Debug.Log("Notifying of autostep"); } GlobalManager.MInstantMessage.DeliverMessage(InstantMessageType.PuzzleAutostepUsed, this); }
// this method creates a status string out from the current status of tiles string BuildStatusString() { string statusString = ""; for (int y = 0; y < descriptor.init.height; y++) { for (int x = 0; x < descriptor.init.width; x++) { if (statusString != "") { statusString += "."; } TileStatus tileStatus = tileNeighbours[x, y]; statusString += tileStatus.id.x.ToString() + "," + tileStatus.id.y.ToString() + "," + tileStatus.angle.ToString(); } } return(statusString); }
// button rotation void RotateTilesWith(Vector2Int buttonId, bool saveAfterRotation = true) { // update tiles TileStatus temp = new TileStatus { id = new Vector2Int { x = tileNeighbours[buttonId.x, buttonId.y].id.x, y = tileNeighbours[buttonId.x, buttonId.y].id.y }, angle = tileNeighbours[buttonId.x, buttonId.y].angle }; tileNeighbours[buttonId.x, buttonId.y].id.x = tileNeighbours[buttonId.x, buttonId.y + 1].id.x; tileNeighbours[buttonId.x, buttonId.y].id.y = tileNeighbours[buttonId.x, buttonId.y + 1].id.y; tileNeighbours[buttonId.x, buttonId.y].angle = (tileNeighbours[buttonId.x, buttonId.y + 1].angle + 1) % 4; tileNeighbours[buttonId.x, buttonId.y + 1].id.x = tileNeighbours[buttonId.x + 1, buttonId.y + 1].id.x; tileNeighbours[buttonId.x, buttonId.y + 1].id.y = tileNeighbours[buttonId.x + 1, buttonId.y + 1].id.y; tileNeighbours[buttonId.x, buttonId.y + 1].angle = (tileNeighbours[buttonId.x + 1, buttonId.y + 1].angle + 1) % 4; tileNeighbours[buttonId.x + 1, buttonId.y + 1].id.x = tileNeighbours[buttonId.x + 1, buttonId.y].id.x; tileNeighbours[buttonId.x + 1, buttonId.y + 1].id.y = tileNeighbours[buttonId.x + 1, buttonId.y].id.y; tileNeighbours[buttonId.x + 1, buttonId.y + 1].angle = (tileNeighbours[buttonId.x + 1, buttonId.y].angle + 1) % 4; tileNeighbours[buttonId.x + 1, buttonId.y].id.x = temp.id.x; tileNeighbours[buttonId.x + 1, buttonId.y].id.y = temp.id.y; tileNeighbours[buttonId.x + 1, buttonId.y].angle = (temp.angle + 1) % 4; // update buttons buttonAngles[buttonId.x, buttonId.y] = (buttonAngles[buttonId.x, buttonId.y] + 1) % 4; // check if the puzzle is complete after this rotation CheckPuzzleComplete(); if (saveAfterRotation) { // some modes do not need immediate saving (e. g. shuffle or autosteps) SaveAll(); } }
// this method parses a status string and sets a TileStatus array according to it // it also initializes the TileStatus array if it is not yet initialized void ParseStatusString(string statusString, ref TileStatus[,] tiles) { string[] parts = null; if (!string.IsNullOrEmpty(statusString)) { parts = statusString.Split('.'); } int i = 0; for (int y = 0; y < descriptor.init.height; y++) { for (int x = 0; x < descriptor.init.width; x++, i++) { TileStatus tileStatus = tiles[x, y]; if (tileStatus == null) { tiles[x, y] = tileStatus = new TileStatus { id = new Vector2Int(x, y), angle = 0 }; } if (parts != null && i < parts.Length) { string[] statusparts = parts[i].Split(','); if (statusparts.Length == 3) { int tx, ty, angle; if (int.TryParse(statusparts[0], out tx) && int.TryParse(statusparts[1], out ty) && int.TryParse(statusparts[2], out angle)) { tileStatus.id.x = tx; tileStatus.id.y = ty; tileStatus.angle = angle; } } } } } }
// restore tiles' positions and angles public void RestoreTileStatuses(TileStatus[,] tileNeighbours) { int width = descriptor.init.width; int height = descriptor.init.height; float tileStartX = -(width - 1) * TileSize / 2; Vector3 tilePosition = new Vector3(tileStartX, (height - 1) * TileSize / 2, neutralTileZ); for (int y = 0; y < tileNeighbours.GetUpperBound(1) + 1; y++) { for (int x = 0; x < tileNeighbours.GetUpperBound(0) + 1; x++) { TileStatus tileStatus = tileNeighbours[x, y]; GameObject tile = tiles[tileStatus.id.x, tileStatus.id.y]; tile.transform.position = tilePosition; tile.transform.localRotation = initialTileRotation; tile.transform.Rotate(Vector3.forward, 90f * tileStatus.angle); tilePosition.x += TileSize; } tilePosition.y -= TileSize; tilePosition.x = tileStartX; } }