-
-
Save brikp/e181c17577a922340442ebe0419e2c3a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Godot; | |
using Jigsaw.Entities; | |
using System.Collections.Generic; | |
using System.Linq; | |
public enum PuzzlePosition { CENTER, TOP, RIGHT, BOTTOM, LEFT, TOP_LEFT, TOP_RIGHT, BOT_LEFT, BOT_RIGHT }; | |
public class PuzzleGen | |
{ | |
private const int MASK_REFERENCE_SIZE = 100; | |
private const int PUZZLE_REFERENCE_SIZE = 100; | |
public int PieceSize { get; set; } | |
public bool HasFinishedGenerating = false; | |
private Image fullPuzzleImage; | |
private Texture fullPuzzleTexture; | |
private Image adjustedPuzzleImage; | |
private ImageTexture adjustedPuzzleTexture; | |
private ShaderMaterial puzzleShader; | |
//private Dictionary<PuzzleMasks, (Texture top, Texture bottom, Texture right, Texture left)> maskTextures = | |
// new Dictionary<PuzzleMasks, (Texture top, Texture bottom, Texture right, Texture left)>(); | |
private Dictionary<PuzzleMasks, (Image top, Image bottom, Image right, Image left)> maskImages = | |
new Dictionary<PuzzleMasks, (Image top, Image bottom, Image right, Image left)>(); | |
private Dictionary<Vector2, PuzzlePosition> puzzlePositionLookup; | |
private Dictionary<Vector2, PuzzleSideData> tabsAndBlanks; | |
private int maskAdjustedSize; | |
private PackedScene puzzlePieceScene; | |
private int adjustedTextureWidth; | |
private int adjustedTextureHeight; | |
private int generatorX; | |
private int generatorY; | |
public int PuzzleCount; | |
private string saveFilePath; | |
File saveFile = new File(); | |
private Queue<string> sideDataSaveQueue = new Queue<string>(); | |
private bool isLoadingSaveData = false; | |
public PuzzleGen(string puzzleTexturePath, int pieceSize = 100) | |
{ | |
saveFilePath = GameState.currentSaveDirPath + "/side_data.txt"; | |
if (saveFile.FileExists(saveFilePath)) | |
isLoadingSaveData = true; | |
GD.Print("Side data filepath: ", saveFilePath); | |
PieceSize = pieceSize; | |
puzzlePieceScene = GD.Load("res://Entities/PuzzlePiece.tscn") as PackedScene; | |
fullPuzzleTexture = GD.Load(puzzleTexturePath) as Texture; | |
fullPuzzleImage = fullPuzzleTexture.GetData(); | |
fullPuzzleImage.Convert(Image.Format.Rgba8); | |
puzzleShader = GD.Load<ShaderMaterial>("res://Scenes/test.material"); | |
// puzzleShader = GD.Load<ShaderMaterial>("res://Scenes/puzzle_bevel_shader.material"); | |
// TODO: This should be moved to the main menu and passed to puzzle manager. Also image size should be saved in puzzle manager when saving data | |
// Adjust size to the puzzle size | |
int puzzleWidth = fullPuzzleTexture.GetWidth(); | |
int puzzleHeight = fullPuzzleTexture.GetHeight(); | |
// 1. Check how many pieces fit and get adjusted width and height | |
int pieceXCount = puzzleWidth / PieceSize; | |
int pieceYCount = puzzleHeight / PieceSize; | |
int adjustedWidth = pieceXCount * pieceSize; | |
int adjustedHeight = pieceYCount * pieceSize; | |
// 2. Cut off new size from the full image from middle out | |
int startingX = (puzzleWidth - adjustedWidth) / 2; | |
int startingY = (puzzleHeight - adjustedHeight) / 2; | |
Rect2 usedImagePartRect = new Rect2(startingX, startingY, adjustedWidth, adjustedHeight); | |
adjustedPuzzleImage = new Image(); | |
adjustedPuzzleTexture = new ImageTexture(); | |
adjustedPuzzleImage = fullPuzzleImage.GetRect(usedImagePartRect); | |
adjustedPuzzleTexture.CreateFromImage(adjustedPuzzleImage); | |
float puzzleSizeAdjustment = (float)PieceSize / PUZZLE_REFERENCE_SIZE; | |
maskAdjustedSize = (int)(MASK_REFERENCE_SIZE * puzzleSizeAdjustment); | |
foreach (PuzzleMasks key in PuzzleMask.PickedMasks) | |
{ | |
string maskPath = PuzzleMask.MaskInfo[key].maskPath; | |
Texture maskTexture = GD.Load(maskPath) as Texture; | |
(Image top, Image bottom, Image right, Image left) imageTuple = ( | |
getRotatedImage(maskTexture.GetData(), 270), | |
getRotatedImage(maskTexture.GetData(), 90), | |
maskTexture.GetData(), | |
getRotatedImage(maskTexture.GetData(), 180) | |
); | |
imageTuple.top.Resize(maskAdjustedSize, maskAdjustedSize); | |
imageTuple.right.Resize(maskAdjustedSize, maskAdjustedSize); | |
imageTuple.bottom.Resize(maskAdjustedSize, maskAdjustedSize); | |
imageTuple.left.Resize(maskAdjustedSize, maskAdjustedSize); | |
maskImages.Add(key, imageTuple); | |
} | |
} | |
public void StartPuzzleGeneration(Node2D puzzleBox) | |
{ | |
GD.Print("Starting puzzle generation, ", OS.GetTicksMsec()); | |
adjustedTextureWidth = adjustedPuzzleTexture.GetWidth(); | |
adjustedTextureHeight = adjustedPuzzleTexture.GetHeight(); | |
if (isLoadingSaveData) | |
LoadPuzzleLookupsFromSaveFile(); | |
else | |
BuildPuzzleLookups(adjustedTextureWidth, adjustedTextureHeight); | |
generatorX = 0; | |
generatorY = 0; | |
} | |
private void LoadPuzzleLookupsFromSaveFile() | |
{ | |
GD.Print("Loading puzzle side data from file: ", saveFilePath, ", ", OS.GetTicksMsec()); | |
tabsAndBlanks = new Dictionary<Vector2, PuzzleSideData>(); | |
saveFile.Open(saveFilePath, File.ModeFlags.Read); | |
int pieceCount = Int32.Parse(saveFile.GetLine()); | |
GD.Print("Loading ", pieceCount, " pieces ", OS.GetTicksMsec()); | |
for (int i = 0; i < pieceCount; i++) | |
{ | |
Vector2 index = Vector2.Zero; | |
PuzzleSideData sideData = new PuzzleSideData(); | |
string[] data = saveFile.GetLine().Split(","); | |
index.x = float.Parse(data[0]); | |
index.y = float.Parse(data[1]); | |
sideData.Top.mask = (PuzzleMasks)Int32.Parse(data[2]); | |
sideData.Top.isTab = data[3] == "1"; | |
sideData.Top.isEdge = data[4] == "1"; | |
sideData.Right.mask = (PuzzleMasks)Int32.Parse(data[5]); | |
sideData.Right.isTab = data[6] == "1"; | |
sideData.Right.isEdge = data[7] == "1"; | |
sideData.Bottom.mask = (PuzzleMasks)Int32.Parse(data[8]); | |
sideData.Bottom.isTab = data[9] == "1"; | |
sideData.Bottom.isEdge = data[10] == "1"; | |
sideData.Left.mask = (PuzzleMasks)Int32.Parse(data[11]); | |
sideData.Left.isTab = data[12] == "1"; | |
sideData.Left.isEdge = data[13] == "1"; | |
tabsAndBlanks.Add(index, sideData); | |
} | |
saveFile.Close(); | |
} | |
public PuzzlePiece GetNextPuzzlePiece() | |
{ | |
if (generatorY >= adjustedTextureHeight) | |
{ | |
generatorY = 0; | |
generatorX += PieceSize; | |
} | |
if (generatorX >= adjustedTextureWidth) | |
{ | |
return null; | |
} | |
if (PuzzleCount % 50 == 0) | |
{ | |
GD.Print("Generating... puzzle ", PuzzleCount); | |
} | |
//GD.Print("Generating puzzle (", generatorX, ",", generatorY, "), ", OS.GetTicksMsec()); | |
Vector2 puzzlePieceIndex = new Vector2(generatorX / PieceSize, generatorY / PieceSize); | |
PuzzlePiece puzzlePiece = GetPuzzlePieceWithNew(puzzlePieceIndex, generatorX, generatorY, PieceSize); | |
puzzlePiece.Index = puzzlePieceIndex; | |
puzzlePiece.BoundingBoxTopLeftIndex = puzzlePieceIndex; | |
puzzlePiece.BoundingBoxBotRightIndex = puzzlePieceIndex; | |
puzzlePiece.state = PuzzlePiece.PuzzleStates.IN_BOX; | |
puzzlePiece.PuzzleSize = PieceSize; | |
puzzlePiece.IndexSet.Add(puzzlePieceIndex); | |
Label puzzleLabel = new Label(); | |
puzzleLabel.Name = "Label"; | |
puzzleLabel.Visible = GameState.debugModeOn; | |
puzzleLabel.Text = "(" + generatorX / PieceSize + "," + generatorY / PieceSize + ")"; | |
puzzlePiece.AddChild(puzzleLabel); | |
generatorY += PieceSize; | |
PuzzleCount += 1; | |
return puzzlePiece; | |
} | |
private PuzzlePiece GetPuzzlePieceWithNew(Vector2 puzzleIndex, int x, int y, int step) | |
{ | |
PuzzlePiece node = puzzlePieceScene.Instance() as PuzzlePiece; | |
Rect2 imageRect = new Rect2(x, y, step, step); | |
Sprite sprite = new Sprite(); | |
sprite.Name = "Sprite"; | |
sprite.Texture = GetPuzzleTextureFromRect(puzzleIndex, imageRect); | |
float shaderOutline = Mathf.Max((float)PieceSize / 100, 0.5f); | |
puzzleShader.SetShaderParam("line_thickness", shaderOutline); | |
sprite.Material = puzzleShader; | |
CollisionShape2D shape = new CollisionShape2D(); | |
RectangleShape2D rect = new RectangleShape2D(); | |
rect.Extents = new Vector2(sprite.GetRect().Size.x / 2.0f, sprite.GetRect().Size.y / 2.0f); | |
shape.Shape = rect; | |
shape.Name = "CollisionShape"; | |
Area2D area2d = new Area2D(); | |
area2d.Name = "Area2D"; | |
area2d.CollisionLayer = 2; | |
area2d.CollisionMask = 2; | |
area2d.Monitorable = false; | |
area2d.Monitoring = false; | |
area2d.AddChild(shape); | |
node.AddChild(sprite); | |
node.AddChild(area2d); | |
return node; | |
} | |
private ImageTexture GetPuzzleTextureFromRect(Vector2 puzzleIndex, Rect2 rect) | |
{ | |
Image image = adjustedPuzzleImage.GetRect(rect); | |
PuzzleSideData puzzleSideData = tabsAndBlanks[puzzleIndex]; | |
//ColorImageBorder(image, new Color(0, 0, 0, 0.35f)); | |
AddBlanksToImage(image, puzzleSideData); | |
image = AddTabsToImage(image, rect, puzzleSideData); | |
ImageTexture texture = new ImageTexture(); | |
texture.CreateFromImage(image); | |
return texture; | |
} | |
private void AddBlanksToImage(Image image, PuzzleSideData puzzleSideData) | |
{ | |
if (puzzleSideData.Top.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Top.isTab == false) | |
{ | |
ApplyBlankMask(puzzleSideData, image, maskImages[puzzleSideData.Top.mask].bottom, PuzzlePosition.TOP); | |
} | |
if (puzzleSideData.Right.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Right.isTab == false) | |
{ | |
ApplyBlankMask(puzzleSideData, image, maskImages[puzzleSideData.Right.mask].left, PuzzlePosition.RIGHT); ; | |
} | |
if (puzzleSideData.Bottom.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Bottom.isTab == false) | |
{ | |
ApplyBlankMask(puzzleSideData, image, maskImages[puzzleSideData.Bottom.mask].top, PuzzlePosition.BOTTOM); | |
} | |
if (puzzleSideData.Left.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Left.isTab == false) | |
{ | |
ApplyBlankMask(puzzleSideData, image, maskImages[puzzleSideData.Left.mask].right, PuzzlePosition.LEFT); | |
} | |
} | |
private Image AddTabsToImage(Image image, Rect2 rect, PuzzleSideData puzzleSideData) | |
{ | |
Image mask, sideImage; | |
Vector2 topLeft = new Vector2(maskAdjustedSize / 2, maskAdjustedSize / 2); | |
Rect2 maskRect; | |
// 1. Create new image with target size | |
Vector2 targetSize = image.GetSize() + topLeft * 2; | |
Image targetImage = new Image(); | |
targetImage.Create((int)targetSize.x, (int)targetSize.y, false, image.GetFormat()); | |
// 2. Set pixels for base image (starting from top+left resize vector position) | |
targetImage.BlitRect(image, image.GetUsedRect(), topLeft); | |
// 3. Get pixels for the tabs from the full puzzle | |
// 4. Blit mask with image on each side if needed | |
if (puzzleSideData.Top.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Top.isTab) | |
{ | |
mask = maskImages[puzzleSideData.Top.mask].top; | |
maskRect = mask.GetUsedRect(); | |
maskRect.Position = rect.Position; | |
maskRect.Position -= new Vector2(0, maskRect.Size.y); | |
sideImage = adjustedPuzzleImage.GetRect(maskRect); | |
ApplyImageMask(sideImage, mask.GetRect(mask.GetUsedRect())); | |
targetImage.BlitRect( | |
sideImage, | |
new Rect2(0, 0, maskRect.Size.x, maskRect.Size.y), | |
new Vector2(topLeft.x, topLeft.y - maskRect.Size.y)); | |
} | |
if (puzzleSideData.Right.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Right.isTab == true) | |
{ | |
mask = maskImages[puzzleSideData.Right.mask].right; | |
maskRect = mask.GetUsedRect(); | |
maskRect.Position = rect.Position + new Vector2(image.GetWidth(), 0); | |
sideImage = adjustedPuzzleImage.GetRect(maskRect); | |
ApplyImageMask(sideImage, mask.GetRect(mask.GetUsedRect())); | |
targetImage.BlitRect( | |
sideImage, | |
new Rect2(0, 0, maskRect.Size.x, maskRect.Size.y), | |
new Vector2(targetSize.y - topLeft.x, topLeft.y)); | |
} | |
if (puzzleSideData.Bottom.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Bottom.isTab == true) | |
{ | |
mask = maskImages[puzzleSideData.Bottom.mask].bottom; | |
maskRect = mask.GetUsedRect(); | |
maskRect.Position = rect.Position + new Vector2(0, image.GetHeight()); | |
sideImage = adjustedPuzzleImage.GetRect(maskRect); | |
ApplyImageMask(sideImage, mask.GetRect(mask.GetUsedRect())); | |
targetImage.BlitRect( | |
sideImage, | |
new Rect2(0, 0, maskRect.Size.x, maskRect.Size.y), | |
new Vector2(topLeft.x, targetSize.y - topLeft.y)); | |
} | |
if (puzzleSideData.Left.mask != PuzzleMasks.NOT_SET && | |
puzzleSideData.Left.isTab == true) | |
{ | |
mask = maskImages[puzzleSideData.Left.mask].left; | |
maskRect = mask.GetUsedRect(); | |
maskRect.Position = rect.Position; | |
maskRect.Position -= new Vector2(maskRect.Size.x, 0); | |
sideImage = adjustedPuzzleImage.GetRect(maskRect); | |
ApplyImageMask(sideImage, mask.GetRect(mask.GetUsedRect())); | |
targetImage.BlitRect( | |
sideImage, | |
new Rect2(0, 0, maskRect.Size.x, maskRect.Size.y), | |
new Vector2(topLeft.x - maskRect.Size.x, topLeft.y)); | |
} | |
return targetImage; | |
} | |
private void ApplyImageMask(Image image, Image mask) | |
{ | |
image.Lock(); | |
mask.Lock(); | |
for (int x = 0; x < image.GetWidth(); x += 1) | |
{ | |
for (int y = 0; y < image.GetHeight(); y += 1) | |
{ | |
Color maskColor = mask.GetPixel(x, y); | |
if (maskColor.a == 0) | |
{ | |
image.SetPixel(x, y, new Color(0, 0, 0, 0)); | |
} | |
if (maskColor.a > 0 && maskColor.a < 1) | |
{ | |
Color imageColor = image.GetPixel(x, y); | |
imageColor.a = maskColor.a; | |
image.SetPixel(x, y, imageColor); | |
} | |
} | |
} | |
image.Unlock(); | |
mask.Unlock(); | |
} | |
private void ApplyBlankMask(PuzzleSideData puzzleSideData, Image image, Image blankMask, PuzzlePosition side) | |
{ | |
Rect2 maskRect = blankMask.GetUsedRect(); | |
Vector2 point = Vector2.Zero; | |
int maskX = (int)maskRect.Position.x; | |
int maskY; | |
image.Lock(); | |
blankMask.Lock(); | |
// 1. Get starting point - middle of side being masked | |
// 2. Move starting point to align with top left corner of the maskRect | |
switch (side) | |
{ | |
case PuzzlePosition.RIGHT: | |
point.x = image.GetWidth(); | |
point.y = 0; | |
point.x -= maskRect.Size.x; | |
//point.y -= maskRect.Size.y / 2; | |
break; | |
case PuzzlePosition.BOTTOM: | |
point.x = 0; | |
point.y = image.GetHeight(); | |
//point.x -= maskRect.Size.x / 2; | |
point.y -= maskRect.Size.y; | |
break; | |
case PuzzlePosition.LEFT: | |
point.x = 0; | |
point.y = 0; | |
//point.y -= maskRect.Size.y / 2; | |
break; | |
case PuzzlePosition.TOP: | |
point.x = 0; | |
point.y = 0; | |
//point.x -= maskRect.Size.x / 2; | |
break; | |
} | |
// 3. Apply the mask by iterating over the pixels | |
for (int x = (int)point.x; x < point.x + maskRect.Size.x; x += 1) | |
{ | |
maskY = (int)maskRect.Position.y; | |
for (int y = (int)point.y; y < point.y + maskRect.Size.y; y += 1) | |
{ | |
Color color = blankMask.GetPixel(maskX, maskY); | |
Color imageColor = image.GetPixel(x, y); | |
if (color.a > 0.1f) | |
{ | |
imageColor.a = 1 - color.a; | |
image.SetPixel(x, y, imageColor); | |
} | |
maskY += 1; | |
} | |
maskX += 1; | |
} | |
image.Unlock(); | |
blankMask.Unlock(); | |
} | |
private void BuildPuzzleLookups(int fullPuzzleWidth, int fullPuzzleHeight) | |
{ | |
GD.Print("No sidedata saved - Starting BuildPuzzleLookups, ", OS.GetTicksMsec()); | |
puzzlePositionLookup = new Dictionary<Vector2, PuzzlePosition>(); | |
tabsAndBlanks = new Dictionary<Vector2, PuzzleSideData>(); | |
int xIndexMax = fullPuzzleWidth / PieceSize - 1; | |
int yIndexMax = fullPuzzleHeight / PieceSize - 1; | |
for (int x = 0; x <= xIndexMax; x += 1) | |
{ | |
for (int y = 0; y <= yIndexMax; y += 1) | |
{ | |
var puzzleIndex = new Vector2(x, y); | |
if (y == 0) | |
{ | |
if (x == 0) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.TOP_LEFT); | |
} | |
else if (x == xIndexMax) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.TOP_RIGHT); | |
} | |
else | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.TOP); | |
} | |
} | |
else if (y == yIndexMax) | |
{ | |
if (x == 0) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.BOT_LEFT); | |
} | |
else if (x == xIndexMax) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.BOT_RIGHT); | |
} | |
else puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.BOTTOM); | |
} | |
else if (x == 0) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.LEFT); | |
} | |
else if (x == xIndexMax) | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.RIGHT); | |
} | |
else | |
{ | |
puzzlePositionLookup.Add(puzzleIndex, PuzzlePosition.CENTER); | |
} | |
var sideData = GeneratePuzzleSideData(puzzleIndex, puzzlePositionLookup[puzzleIndex]); | |
tabsAndBlanks.Add(puzzleIndex, sideData); | |
var serializedSideData = "" + | |
puzzleIndex.x + "," + | |
puzzleIndex.y + "," + | |
(int)sideData.Top.mask + "," + | |
Convert.ToInt32(sideData.Top.isTab) + "," + | |
Convert.ToInt32(sideData.Top.isEdge) + "," + | |
(int)sideData.Right.mask + "," + | |
Convert.ToInt32(sideData.Right.isTab) + "," + | |
Convert.ToInt32(sideData.Right.isEdge) + "," + | |
(int)sideData.Bottom.mask + "," + | |
Convert.ToInt32(sideData.Bottom.isTab) + "," + | |
Convert.ToInt32(sideData.Bottom.isEdge) + "," + | |
(int)sideData.Left.mask + "," + | |
Convert.ToInt32(sideData.Left.isTab) + "," + | |
Convert.ToInt32(sideData.Left.isEdge); | |
sideDataSaveQueue.Enqueue(serializedSideData); | |
} | |
} | |
GD.Print("Finishing BuildPuzzleLookups, ", OS.GetTicksMsec()); | |
GD.Print("Saving tabsAndBlanks, ", OS.GetTicksMsec()); | |
var fullSideData = "" + sideDataSaveQueue.Count + '\n'; | |
while (sideDataSaveQueue.Count > 0) | |
{ | |
fullSideData += sideDataSaveQueue.Dequeue() + '\n'; | |
} | |
Error error = saveFile.Open(saveFilePath, File.ModeFlags.WriteRead); | |
GD.Print(error); | |
saveFile.StoreString(fullSideData); | |
saveFile.Close(); | |
GD.Print("Finished saving tabsAndBlanks, ", OS.GetTicksMsec()); | |
} | |
private PuzzleSideData GeneratePuzzleSideData(Vector2 puzzleIndex, PuzzlePosition puzzlePosition) | |
{ | |
// We start from top to bottom, column by column, left to right, so (0,0), (0,1), (0,2), (1,0), (1,1) etc. | |
// This is why we can always assume right and bottom sides need to be generated. Left and top will be an edge (so skipped) or matched with neighbour. | |
GD.Randomize(); | |
var result = new PuzzleSideData(); | |
PuzzleMasks[] availableMasksKeys = maskImages.Keys.ToArray(); | |
PuzzleMasks maskKey; | |
bool isTab; | |
// Top side of the puzzle | |
if (puzzlePosition != PuzzlePosition.TOP_LEFT && | |
puzzlePosition != PuzzlePosition.TOP && | |
puzzlePosition != PuzzlePosition.TOP_RIGHT) | |
{ | |
Vector2 neighbourPuzzleIndex = puzzleIndex; | |
neighbourPuzzleIndex.y -= 1; | |
maskKey = tabsAndBlanks[neighbourPuzzleIndex].Bottom.mask; | |
isTab = !tabsAndBlanks[neighbourPuzzleIndex].Bottom.isTab; | |
result.Top = (false, maskKey, isTab); | |
} | |
// Left side of the puzzle | |
if (puzzlePosition != PuzzlePosition.TOP_LEFT && | |
puzzlePosition != PuzzlePosition.LEFT && | |
puzzlePosition != PuzzlePosition.BOT_LEFT) | |
{ | |
Vector2 neighbourPuzzleIndex = puzzleIndex; | |
neighbourPuzzleIndex.x -= 1; | |
maskKey = tabsAndBlanks[neighbourPuzzleIndex].Right.mask; | |
isTab = !tabsAndBlanks[neighbourPuzzleIndex].Right.isTab; | |
result.Left = (false, maskKey, isTab); | |
} | |
// Right side of the puzzle | |
if (puzzlePosition != PuzzlePosition.TOP_RIGHT && | |
puzzlePosition != PuzzlePosition.RIGHT && | |
puzzlePosition != PuzzlePosition.BOT_RIGHT) | |
{ | |
int randomKey = (int)GD.RandRange(0, availableMasksKeys.Length - 0.001); | |
maskKey = availableMasksKeys[randomKey]; | |
isTab = GD.Randf() <= 0.5f; | |
result.Right = (false, maskKey, isTab); | |
} | |
// Bottom side of the puzzle | |
if (puzzlePosition != PuzzlePosition.BOT_LEFT && | |
puzzlePosition != PuzzlePosition.BOTTOM && | |
puzzlePosition != PuzzlePosition.BOT_RIGHT) | |
{ | |
int randomKey = (int)GD.RandRange(0, availableMasksKeys.Length - 0.001); | |
maskKey = availableMasksKeys[randomKey]; | |
isTab = GD.Randf() <= 0.5f; | |
result.Bottom = (false, maskKey, isTab); | |
} | |
// Skew random generation of tabs/blanks to greatly favor 2/2 puzzles (2 tabs and 2 blanks) | |
// Only 4 possible top/left combinations: true/true, true/false, false/true, false/false | |
// Check each possible combination and set other sides accordingly with a X% chance | |
float twoTabFactor = 0.8f; | |
if (result.Left.isTab && result.Top.isTab) | |
{ | |
if (GD.Randf() < twoTabFactor) | |
{ | |
result.Bottom.isTab = false; | |
result.Right.isTab = false; | |
} | |
} | |
else if (result.Left.isTab == true && result.Top.isTab == false) | |
{ | |
if (GD.Randf() < twoTabFactor) | |
{ | |
result.Bottom.isTab = false; | |
result.Right.isTab = true; | |
} | |
} | |
else if (result.Left.isTab == false && result.Top.isTab == true) | |
{ | |
if (GD.Randf() < twoTabFactor) | |
{ | |
result.Bottom.isTab = true; | |
result.Right.isTab = false; | |
} | |
} | |
else if (result.Left.isTab == false && result.Top.isTab == false) | |
{ | |
if (GD.Randf() < twoTabFactor) | |
{ | |
result.Bottom.isTab = true; | |
result.Right.isTab = true; | |
} | |
} | |
return result; | |
} | |
private Image getRotatedImage(Image image, int degree) | |
{ | |
Image result = new Image(); | |
result.Create(image.GetWidth(), image.GetHeight(), false, image.GetFormat()); | |
image.Lock(); | |
result.Lock(); | |
if (degree == 90) | |
{ | |
for (int x = 0; x < image.GetWidth(); x += 1) | |
{ | |
for (int y = 0; y < image.GetHeight(); y += 1) | |
{ | |
Color color = image.GetPixel(x, y); | |
result.SetPixel(image.GetWidth() - y - 1, x, color); | |
} | |
} | |
} | |
else if (degree == 180) | |
{ | |
for (int x = 0; x < image.GetWidth(); x += 1) | |
{ | |
for (int y = 0; y < image.GetHeight(); y += 1) | |
{ | |
Color color = image.GetPixel(x, y); | |
result.SetPixel(image.GetWidth() - x - 1, y, color); | |
} | |
} | |
} | |
else if (degree == 270) | |
{ | |
for (int x = 0; x < image.GetWidth(); x += 1) | |
{ | |
for (int y = 0; y < image.GetHeight(); y += 1) | |
{ | |
Color color = image.GetPixel(x, y); | |
result.SetPixel(image.GetWidth() - y - 1, image.GetHeight() - x - 1, color); | |
} | |
} | |
} | |
image.Unlock(); | |
result.Unlock(); | |
return result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment