Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports System.Linq
Imports System.ComponentModel
Imports System.Reflection
Imports System.ComponentModel.Design

Public Class FrmDesigner
Inherits Form

#Region " Variables "
Private selectionStartedOnWall As Boolean
Private isCursorOnPicGame As Boolean = False
Private SelectionType As String
Private Const MaxRecentFiles As Integer = 5
Private recentFiles As New List(Of String)()
Private recentFilesFilePath As String = Path.Combine(GetProjectDirectory(), "recent_files.txt")

Private lastMouseMoveTime As DateTime = DateTime.MinValue
Private Const DEBOUNCE_INTERVAL As Integer = 50 ' milliseconds

Public gameTriggers As New List(Of GameTrigger)
Public Event ObjectSelected(selectedObject As SelectedObject)
Public Property SelectionMode As Boolean
Public Property SelectionRequestedByForm As Form
Public Property SelectionRequestedFor As String

Private isSelectionReadyToDrag As Boolean = False
Private isDraggingSelection As Boolean = False
Private selectionOffset As Point ' The offset between mouse position and selection rectangle origin
Private selectionData As SelectionData
Private selectionStartCell As Point ' The grid cell coordinates corresponding to selectionRectangle's original position

Private Const INDENT As Integer = 20
Friend SQCOUNTX As Integer = 16
Friend SQCOUNTY As Integer = 11
Private Const WALL_THICKNESS As Integer = 10

Friend wallsHatchStyle As HatchStyle = HatchStyle.Cross
Friend wallsColor1 As Color = Color.Gray
Friend wallsColor2 As Color = Color.DarkGray
Friend floorsHatchStyle As HatchStyle = HatchStyle.SmallCheckerBoard
Friend floorsColor1 As Color = Color.LightGray
Friend floorsColor2 As Color = Color.White

Friend floorsUseEdgeStyle()() As Boolean ' New array to track edge style usage

Private SQSizeX As Integer
Private SQSizeY As Integer

Private isRightMouseDown As Boolean = False
Private isLeftMouseDown As Boolean = False
Private isMouseInsideGrid As Boolean = False

Private highlightedX As Integer
Private highlightedY As Integer
Private highlightedWallX As Boolean = False
Private highlightedWallY As Boolean = False
Private highlightColor As Color = Color.Black
Private highlightTimer As Timer

Private isRestoringState As Boolean = False
Private isActionInProgress As Boolean = False
Private undoStack As New Stack(Of RoomState)
Private redoStack As New Stack(Of RoomState)

Private ReadOnly projectDir As String = GetProjectDirectory()
Private ReadOnly premadesFolder As String = Path.Combine(projectDir, "Data", "Premades")
Private ReadOnly savesFolder As String = Path.Combine(projectDir, "Data", "Saves")
Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Sound/Music")
Private currentFilePath As String = ""
Friend SelectedMusic As String = ""

Friend verticalWalls()() As Boolean
Friend horizontalWalls()() As Boolean

Friend placedItems()() As PlacedItem
Friend verticalWallItems()() As PlacedItem
Friend horizontalWallItems()() As PlacedItem

Private animatedImages As New List(Of Image)
Private animationTimer As Timer

Private isDraggingItem As Boolean = False
Private draggedItem As PlacedItem = Nothing
Private draggedItemIsWall As Boolean = False
Private draggedItemIsVerticalWall As Boolean = False
Private draggedItemOriginalX As Integer
Private draggedItemOriginalY As Integer

Private mouseX As Integer
Private mouseY As Integer

Private rand As New Random()

Private rightMouseDownPosition As Point
Private rightMouseDownTime As DateTime
Private Const CLICK_DRAG_THRESHOLD As Integer = 5 ' pixels
Private Const CLICK_DRAG_THRESHOLD_SQUARED As Integer = CLICK_DRAG_THRESHOLD * CLICK_DRAG_THRESHOLD

Private isSelecting As Boolean = False
Private isInSelectionMode As Boolean = False
Private selectionStartPoint As Point
Private selectionEndPoint As Point
Private selectionRectangle As Rectangle

Public useGradient As Boolean = False
Private copiedSelectionData As SelectionData
Private copiedSelectionType As String
Private isCopying As Boolean = False

#End Region

#Region " Constructor and Initialization "
Public Sub New()
InitializeComponent()
InitializeWalls()
InitializeTimers()
InitializeEventHandlers()
LoadPremades()
LoadMusic()
InitializeAnimationForImages()
If Not isRestoringState Then SaveStateForUndo()
Me.KeyPreview = True

    ' Enable double buffering for picGame
    EnableDoubleBuffering(picGame)

    ' Initialize the status bar
End Sub

Private Sub UpdateRecentFiles(filePath As String)
    ' Remove the file if it already exists in the recent files list
    recentFiles.Remove(filePath)

    ' Add the new file to the top of the list
    recentFiles.Insert(0, filePath)

    ' Ensure we do not exceed the maximum number of recent files
    If recentFiles.Count > MaxRecentFiles Then
        recentFiles.RemoveAt(MaxRecentFiles) ' Remove the oldest entry
    End If

    ' Update the menu with the latest recent files
    UpdateRecentFilesMenu()

    ' Save the recent files to a text file
    SaveRecentFilesToFile()
End Sub

Private Sub UpdateRecentFilesMenu()
    ' Remove any existing recent file items and separators
    For i As Integer = mnu.DropDownItems.Count - 1 To 0 Step -1
        Dim item As ToolStripItem = mnu.DropDownItems(i)
        If TypeOf item Is ToolStripMenuItem AndAlso (CType(item, ToolStripMenuItem).Tag IsNot Nothing AndAlso CType(item, ToolStripMenuItem).Tag.ToString() = "RecentFile") OrElse

TypeOf item Is ToolStripSeparator Then
mnu.DropDownItems.RemoveAt(i)
End If
Next

    ' Find the "New" menu item
    Dim newMenuItem As ToolStripMenuItem = Nothing
    For Each item As ToolStripItem In mnu.DropDownItems
        If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "New" Then
            newMenuItem = CType(item, ToolStripMenuItem)
            Exit For
        End If
    Next

    If newMenuItem Is Nothing Then
        MessageBox.Show("New menu item not found.")
        Return
    End If

    ' Add the separator below the "New" menu item
    Dim separatorBelowNew As New ToolStripSeparator()
    mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(newMenuItem) + 1, separatorBelowNew)

    ' Find the "Load" menu item
    Dim loadMenuItem As ToolStripMenuItem = Nothing
    For Each item As ToolStripItem In mnu.DropDownItems
        If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "Load" Then
            loadMenuItem = CType(item, ToolStripMenuItem)
            Exit For
        End If
    Next

    If loadMenuItem Is Nothing Then
        MessageBox.Show("Load menu item not found.")
        Return
    End If

    ' Add the separator above the "Load" menu item
    Dim separatorAboveLoad As New ToolStripSeparator()
    mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(loadMenuItem), separatorAboveLoad)

    ' Add the separator above recent files
    Dim separatorAboveRecents As New ToolStripSeparator()
    mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(loadMenuItem) + 1, separatorAboveRecents)

    ' Populate the recent files list in the menu in reverse order
    For i As Integer = recentFiles.Count - 1 To 0 Step -1
        Dim file As String = recentFiles(i)
        Dim menuItem As New ToolStripMenuItem(file) ' Create a new menu item for each file
        menuItem.Tag = "RecentFile" ' Tag the item to identify it as a recent file item
        AddHandler menuItem.Click, AddressOf RecentFileMenuItem_Click ' Attach event handler
        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorAboveRecents) + 1, menuItem) ' Add item to the dropdown after the separator
    Next

    ' Add the separator below recent files
    Dim separatorBelowRecents As New ToolStripSeparator()
    mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorAboveRecents) + recentFiles.Count + 1, separatorBelowRecents)

    ' Ensure the Exit menu item is added only once
    Dim exitMenuItem As ToolStripMenuItem = Nothing
    For Each item As ToolStripItem In mnu.DropDownItems
        If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "Exit" Then
            exitMenuItem = CType(item, ToolStripMenuItem)
            Exit For
        End If
    Next

    If exitMenuItem Is Nothing Then
        exitMenuItem = New ToolStripMenuItem("Exit")
        AddHandler exitMenuItem.Click, AddressOf ExitMenuItem_Click

        ' Load the icon
        Dim iconPath As String = Path.Combine(projectDir, "Data", "Icons", "close.png")
        If File.Exists(iconPath) Then
            exitMenuItem.Image = Image.FromFile(iconPath)
        End If

        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorBelowRecents) + 1, exitMenuItem)
    End If

    ' Hide the separators if there are no recent files
    separatorAboveRecents.Visible = recentFiles.Count > 0
    separatorBelowRecents.Visible = recentFiles.Count > 0
End Sub

Private Sub ExitMenuItem_Click(sender As Object, e As EventArgs)
    Me.Close()
End Sub

Private Sub SaveRecentFilesToFile()
    Try
        Dim lines As New List(Of String)(recentFiles)
        File.WriteAllLines(recentFilesFilePath, lines)
    Catch ex As Exception
        MessageBox.Show("Error saving recent files: " & ex.Message)
    End Try
End Sub
Private Sub LoadRecentFiles()
    If File.Exists(recentFilesFilePath) Then
        Try
            Dim lines As String() = File.ReadAllLines(recentFilesFilePath)
            recentFiles.AddRange(lines.Where(Function(line) Not String.IsNullOrWhiteSpace(line)).Take(MaxRecentFiles))
        Catch ex As Exception
            MessageBox.Show("Error loading recent files: " & ex.Message)
        End Try
    End If
    UpdateRecentFilesMenu()
End Sub
Private Sub RecentFileMenuItem_Click(sender As Object, e As EventArgs)
    Dim selectedItem As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
    Dim filePath As String = selectedItem.Text

    ' Load the selected recent file
    LoadRoomFromFile(filePath)

    ' Update the current file path
    currentFilePath = filePath

    ' Update the recent files list with the selected file
    UpdateRecentFiles(filePath)
End Sub

Private Sub CboStyleChoice_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboStyleChoice.SelectedIndexChanged
    useGradient = cboStyleChoice.SelectedItem.ToString() = "Gradient"
    lstHatchstyles.Enabled = Not useGradient
    picGame.Invalidate() ' Redraw the picGame control
End Sub

Private Sub FrmDesigner_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    ' Check for Ctrl+Z (Undo)
    If e.Control AndAlso e.KeyCode = Keys.Z Then
        BtnUndo_Click(sender, e)
        e.SuppressKeyPress = True ' Prevent further processing
    End If

    ' Check for Ctrl+Y (Redo)
    If e.Control AndAlso e.KeyCode = Keys.Y Then
        BtnRedo_Click(sender, e)
        e.SuppressKeyPress = True
    End If
End Sub

Friend Sub InitializeWalls()
    verticalWalls = New Boolean(SQCOUNTX)() {}
    verticalWallItems = New PlacedItem(SQCOUNTX)() {}
    For x As Integer = 0 To SQCOUNTX
        verticalWalls(x) = New Boolean(SQCOUNTY - 1) {}
        verticalWallItems(x) = New PlacedItem(SQCOUNTY - 1) {}
        For y As Integer = 0 To SQCOUNTY - 1
            verticalWalls(x)(y) = True
            verticalWallItems(x)(y) = Nothing
        Next
    Next

    horizontalWalls = New Boolean(SQCOUNTX - 1)() {}
    horizontalWallItems = New PlacedItem(SQCOUNTX - 1)() {}
    For x As Integer = 0 To SQCOUNTX - 1
        horizontalWalls(x) = New Boolean(SQCOUNTY) {}
        horizontalWallItems(x) = New PlacedItem(SQCOUNTY) {}
        For y As Integer = 0 To SQCOUNTY
            horizontalWalls(x)(y) = True
            horizontalWallItems(x)(y) = Nothing
        Next
    Next

    placedItems = New PlacedItem(SQCOUNTX - 1)() {}
    For x As Integer = 0 To SQCOUNTX - 1
        placedItems(x) = New PlacedItem(SQCOUNTY - 1) {}
        For y As Integer = 0 To SQCOUNTY - 1
            placedItems(x)(y) = Nothing
        Next
    Next

    floorsUseEdgeStyle = New Boolean(SQCOUNTX - 1)() {}
    For x As Integer = 0 To SQCOUNTX - 1
        floorsUseEdgeStyle(x) = New Boolean(SQCOUNTY - 1) {}
        For y As Integer = 0 To SQCOUNTY - 1
            floorsUseEdgeStyle(x)(y) = False
        Next
    Next
End Sub

Private Sub InitializeTimers()
    highlightTimer = New Timer() With {.Interval = 500}
    AddHandler highlightTimer.Tick, AddressOf HighlightTimer_Tick
    highlightTimer.Start()

    animationTimer = New Timer() With {.Interval = 100} ' Update every 100 milliseconds (10 times per second)
    AddHandler animationTimer.Tick, AddressOf OnAnimationTick
    animationTimer.Start()
End Sub

Private Sub InitializeEventHandlers()
    ' Add event handlers for ColorPickers
    AddHandler colPattern.ColorChanged, AddressOf colPattern_ColorChanged
    AddHandler colBackground.ColorChanged, AddressOf colBackground_ColorChanged

    ' Add handlers for item picture boxes
    AddHandler picBlock1.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBaddy1.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBaddy2.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picLazer1.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picLazer2.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picFlipper1.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picFlipper2.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picFlipper3.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picFlipper4.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBaddy3.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBaddy4.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picCoin.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picGate.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picKey.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picStart.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picFinish.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBlockStopper1.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBall.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBlockSwitch.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picBomb.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picWarp.MouseDown, AddressOf PicBlock_MouseDown
    AddHandler picWarp2.MouseDown, AddressOf PicBlock_MouseDown

    ' Add handlers for form-level mouse events
    AddHandler Me.MouseMove, AddressOf Form_MouseMove
    AddHandler Me.MouseUp, AddressOf Form_MouseUp

    ' Add handlers for picGame
    picGame.AllowDrop = True
    AddHandler picGame.MouseDown, AddressOf PicGame_MouseDown
    AddHandler picGame.MouseUp, AddressOf PicGame_MouseUp
    AddHandler picGame.MouseMove, AddressOf PicGame_MouseMove
    AddHandler picGame.Paint, AddressOf picGame_Paint

    ' Add handlers for buttons
    AddHandler btnUndo.Click, AddressOf BtnUndo_Click
    AddHandler btnRedo.Click, AddressOf BtnRedo_Click
    AddHandler BtnAll.Click, AddressOf BtnAll_Click
    AddHandler BtnWall.Click, AddressOf BtnWalls_Click
    AddHandler BtnFloor.Click, AddressOf BtnFloors_Click
    AddHandler btnSavePremade.Click, AddressOf BtnSavePremade_Click

    ' Add handler for premades list
    AddHandler lstPremades.SelectedIndexChanged, AddressOf LstPremades_SelectedIndexChanged

    ' Add handler for hatch styles list
    AddHandler lstHatchstyles.SelectedIndexChanged, AddressOf LstHatchstyles_SelectedIndexChanged

    For Each ctl As Control In tabFloor1.Controls
        If TypeOf ctl Is PictureBox Then
            AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
            AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
        End If
    Next

    For Each ctl As Control In tabFloor2.Controls
        If TypeOf ctl Is PictureBox Then
            AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
            AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
        End If
    Next

    For Each ctl As Control In tabWalls.Controls
        If TypeOf ctl Is PictureBox Then
            AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
            AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
        End If
    Next

End Sub

Private Sub CboX_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboX.SelectedIndexChanged
    UpdateGridSize()
End Sub

Private Sub CboY_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboY.SelectedIndexChanged
    UpdateGridSize()
End Sub

Private Sub UpdateGridSize()
    Dim newSQCOUNTX As Integer
    Dim newSQCOUNTY As Integer
    If Integer.TryParse(cboX.SelectedItem?.ToString(), newSQCOUNTX) AndAlso Integer.TryParse(cboY.SelectedItem?.ToString(), newSQCOUNTY) Then
        If Not isRestoringState Then SaveStateForUndo()
        Dim oldSQCOUNTX As Integer = SQCOUNTX
        Dim oldSQCOUNTY As Integer = SQCOUNTY
        SQCOUNTX = newSQCOUNTX
        SQCOUNTY = newSQCOUNTY
        useGradient = (cboStyleChoice.SelectedItem?.ToString() = "Gradient")
        ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, SQCOUNTX, SQCOUNTY)
        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)
        picGame.Invalidate()
        highlightedX = -1
        highlightedY = -1
        isMouseInsideGrid = False
        selectionRectangle = Rectangle.Empty
    End If
End Sub

Friend Sub ResizeArrays(oldX As Integer, oldY As Integer, newX As Integer, newY As Integer)
    verticalWalls = ResizeJaggedArray(verticalWalls, oldX + 1, oldY - 1, newX + 1, newY - 1, True)
    verticalWallItems = ResizeJaggedArray(verticalWallItems, oldX + 1, oldY - 1, newX + 1, newY - 1, False)
    horizontalWalls = ResizeJaggedArray(horizontalWalls, oldX - 1, oldY + 1, newX - 1, newY + 1, True)
    horizontalWallItems = ResizeJaggedArray(horizontalWallItems, oldX - 1, oldY + 1, newX - 1, newY + 1, False)
    placedItems = ResizeJaggedArray(placedItems, oldX - 1, oldY - 1, newX - 1, newY - 1, False)
    floorsUseEdgeStyle = ResizeJaggedArray(floorsUseEdgeStyle, oldX - 1, oldY - 1, newX - 1, newY - 1, False)
    FillBorderWalls()
End Sub

Private Function ResizeJaggedArray(Of T)(sourceArray As T()(), oldSizeX As Integer, oldSizeY As Integer, newSizeX As Integer, newSizeY As Integer, initializeValue As Boolean) As T()()
    Dim newArray As T()() = New T(newSizeX)() {}
    For x As Integer = 0 To newSizeX
        newArray(x) = New T(newSizeY) {}
        For y As Integer = 0 To newSizeY
            If x < sourceArray.Length AndAlso y < sourceArray(x).Length Then
                newArray(x)(y) = sourceArray(x)(y)
            Else
                If GetType(T) Is GetType(Boolean) AndAlso initializeValue Then
                    newArray(x)(y) = CType(Convert.ChangeType(True, GetType(T)), T)
                Else
                    newArray(x)(y) = Nothing
                End If
            End If
        Next
    Next
    Return newArray
End Function

Public Sub PictureBox_MouseEnter(sender As Object, e As EventArgs)
    Dim pb As PictureBox = DirectCast(sender, PictureBox)
    pb.BackColor = Color.LightBlue
End Sub

Public Sub PictureBox_MouseLeave(sender As Object, e As EventArgs)
    Dim pb As PictureBox = DirectCast(sender, PictureBox)
    pb.BackColor = Color.Transparent
End Sub

Private Sub InitializeAnimationForImages()
    animatedImages.Add(picBaddy1.Image)
    animatedImages.Add(picBaddy2.Image)
    animatedImages.Add(picBaddy3.Image)
    animatedImages.Add(picBaddy4.Image)
    animatedImages.Add(picLazer1.Image)
    animatedImages.Add(picLazer2.Image)
    animatedImages.Add(picFlipper1.Image)
    animatedImages.Add(picFlipper2.Image)
    animatedImages.Add(picFlipper3.Image)
    animatedImages.Add(picFlipper4.Image)
    animatedImages.Add(picCoin.Image)
    animatedImages.Add(picGate.Image)
    animatedImages.Add(picKey.Image)
    animatedImages.Add(picStart.Image)
    animatedImages.Add(picFinish.Image)
    animatedImages.Add(picBall.Image)
    animatedImages.Add(picBomb.Image)
    animatedImages.Add(picWarp.Image)
    animatedImages.Add(picWarp2.Image)

    For Each img In animatedImages
        If img IsNot Nothing Then
            ImageAnimator.Animate(img, AddressOf OnFrameChanged)
        End If
    Next
End Sub
Private Function GetItemKeysForType(type As String) As String()
    Select Case type
        Case "Baddies"
            Return New String() {"picBaddy1", "picBaddy2", "picBaddy3", "picBaddy4"}
        Case "BlockStoppers"
            Return New String() {"picBlockStopper1"}
        Case "Blocks"
            Return New String() {"picBlock1"}
        Case "Coins"
            Return New String() {"picCoin"}
        Case "Keys"
            Return New String() {"picKey"}
        Case "Warps"
            Return New String() {"picWarp", "picWarp2"}
        Case "Walls"
            Return New String() {}
        Case "Edge"
            Return New String() {}
        Case Else
            Return New String() {}
    End Select
End Function

#End Region

#Region " Event Handlers "

Private Sub FrmDesigner_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
    RemoveHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

    If cboX.Items.Contains(SQCOUNTX.ToString()) Then
        cboX.SelectedItem = SQCOUNTX.ToString()
    End If
    If cboY.Items.Contains(SQCOUNTY.ToString()) Then
        cboY.SelectedItem = SQCOUNTY.ToString()
    End If

    AddHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
    AddHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

    ' Ensure one of the radio buttons is checked
    rdoWalls.Checked = True

    ' Initialize the color pickers with the current colors
    colPattern.SelectedColor = wallsColor1
    colBackground.SelectedColor = wallsColor2

    LoadMusic()
    LoadPremades()
    LoadRecentFiles()

    UpdateSelections()
End Sub
Private Sub LoadPremades()
    If Not Directory.Exists(premadesFolder) Then Directory.CreateDirectory(premadesFolder)
    lstPremades.Items.Clear()
    For Each file As String In Directory.GetFiles(premadesFolder, "*.Premade")
        lstPremades.Items.Add(Path.GetFileNameWithoutExtension(file))
    Next
End Sub

Private Sub LoadMusic()
    If Not Directory.Exists(musicFolder) Then
        Directory.CreateDirectory(musicFolder)
    End If

    Dim allowedExtensions As String() = {".mp3", ".wav"}
    Dim musicFileFound As Boolean = False
    Dim defaultMusicFile As String = Path.Combine(musicFolder, "The Jolly Drummer.wav")
    Dim defaultVolume As Integer = 100
    Dim defaultGain As Integer = 5

    For Each file As String In Directory.GetFiles(musicFolder)
        Dim extension As String = Path.GetExtension(file).ToLower()
        If allowedExtensions.Contains(extension) Then
            Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(file)
            Dim volume As Integer = 50
            Dim gain As Integer = 10

            SelectedMusic = String.Format("{0}, V:{1}, G:{2}", file, volume, gain)
            lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, volume, gain)

            musicFileFound = True
            Exit For
        End If
    Next

    If Not musicFileFound Then
        ' Set default music file, volume, and gain
        SelectedMusic = String.Format("{0}, V:{1}, G:{2}", defaultMusicFile, defaultVolume, defaultGain)
        lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", Path.GetFileNameWithoutExtension(defaultMusicFile), defaultVolume, defaultGain)
    End If
End Sub

Private Sub OnAnimationTick(sender As Object, e As EventArgs)
    For Each img In animatedImages
        ImageAnimator.UpdateFrames(img)
    Next
    picGame.Invalidate()
End Sub

Private Sub OnFrameChanged(o As Object, e As EventArgs)
End Sub

Private Sub HighlightTimer_Tick(sender As Object, e As EventArgs)
    highlightColor = If(highlightColor = Color.Black, Color.White, Color.Black)
    picGame.Invalidate()
End Sub

Private Sub LstPremades_SelectedIndexChanged(sender As Object, e As EventArgs)
    If lstPremades.SelectedIndex >= 0 Then
        If Not isRestoringState Then SaveStateForUndo()
        Dim selectedPremade As String = lstPremades.SelectedItem.ToString()
        LoadSettingsFromFile(Path.Combine(premadesFolder, selectedPremade & ".Premade"))
        UpdateSelections()
        picGame.Invalidate()
    End If
End Sub

Private Sub MnuNew_Click(sender As Object, e As EventArgs) Handles mnuNew.Click, btnNew.Click
    If Not isRestoringState Then SaveStateForUndo()
    wallsHatchStyle = HatchStyle.Cross
    wallsColor1 = Color.Gray
    wallsColor2 = Color.DarkGray
    floorsHatchStyle = HatchStyle.SmallCheckerBoard
    floorsColor1 = Color.LightGray
    floorsColor2 = Color.White
    InitializeWalls()
    currentFilePath = ""

    txtName.Text = ""
    SelectedMusic = ""

    UpdateSelections()

    picGame.Invalidate()
End Sub

Private Sub MnuSave_Click(sender As Object, e As EventArgs) Handles mnuSave.Click, btnSave.Click
    If String.IsNullOrEmpty(currentFilePath) Then
        MnuSaveAs_Click(sender, e)
    Else
        SaveRoomToFile(currentFilePath)
    End If
End Sub

Private Sub MnuSaveAs_Click(sender As Object, e As EventArgs) Handles mnuSaveAs.Click
    ' Create and configure the SaveFileDialog
    Dim saveDialog As New SaveFileDialog() With {
.InitialDirectory = savesFolder, ' Set initial directory to the project directory
.Filter = "Room Files (*.room)|*.room",
.DefaultExt = "room",
.AddExtension = True,
.Title = "Save Room As"
}

    ' Show the dialog and check if the user clicked OK
    If saveDialog.ShowDialog() = DialogResult.OK Then
        ' Save the current room data to the specified file
        SaveRoomToFile(saveDialog.FileName)

        ' Update the current file path
        currentFilePath = saveDialog.FileName

        ' Update recent files list
        UpdateRecentFiles(currentFilePath) ' Update recent files with the new save
    End If
End Sub

Private Sub MnuLoad_Click(sender As Object, e As EventArgs) Handles mnuLoad.Click, btnOpen.Click
    Dim openDialog As New OpenFileDialog() With {
.InitialDirectory = savesFolder,
.Filter = "Room Files (*.room)|*.room",
.DefaultExt = "room",
.AddExtension = True,
.Title = "Load Room"
}

    If openDialog.ShowDialog() = DialogResult.OK Then
        If Not isRestoringState Then SaveStateForUndo()
        LoadRoomFromFile(openDialog.FileName)

        UpdateRecentFiles(openDialog.FileName) ' Update recent files

        currentFilePath = openDialog.FileName
        UpdateGridSizeUI()
        UpdateSelections()
        picGame.Invalidate()
    End If
End Sub
Private Sub SaveStateForUndo()
    If isRestoringState Then Return
    undoStack.Push(New RoomState(Me))
    If undoStack.Count > 50 Then
        undoStack = New Stack(Of RoomState)(undoStack.Reverse().Take(50))
    End If
End Sub

Private Sub SaveState(isNewAction As Boolean)
    If isRestoringState Then Return
    If isNewAction Then
        redoStack.Clear()
    End If
    undoStack.Push(New RoomState(Me))
    If undoStack.Count > 50 Then
        undoStack = New Stack(Of RoomState)(undoStack.Reverse().Take(50))
    End If
End Sub
Private Sub BtnUndo_Click(sender As Object, e As EventArgs) Handles btnUndo.Click
    If undoStack.Count > 1 Then
        ' Save the current state for redo before performing undo
        SaveStateForRedo()

        ' Pop the current state from the undo stack
        undoStack.Pop()

        ' Restore the previous state
        isRestoringState = True
        Dim previousState As RoomState = undoStack.Peek()
        previousState.RestoreState(Me)
        isRestoringState = False

        ' Update the UI to reflect the restored state
        UpdateGridSizeUI()
        UpdateSelections()
        picGame.Invalidate()
    End If
End Sub

Private Sub SaveStateForRedo()
    If isRestoringState Then Return
    redoStack.Push(New RoomState(Me))
    If redoStack.Count > 50 Then
        redoStack = New Stack(Of RoomState)(redoStack.Reverse().Take(50))
    End If
End Sub

Private Sub BtnRedo_Click(sender As Object, e As EventArgs) Handles btnRedo.Click
    If redoStack.Count > 0 Then
        SaveStateForUndo()
        isRestoringState = True
        Dim redoState As RoomState = redoStack.Pop()
        redoState.RestoreState(Me)
        isRestoringState = False
        UpdateGridSizeUI()
        UpdateSelections()
        picGame.Invalidate()
    End If
End Sub

Private Sub BtnSavePremade_Click(sender As Object, e As EventArgs)
    Dim saveDialog As New SaveFileDialog() With {
        .InitialDirectory = premadesFolder,
        .Filter = "Premade Files (*.Premade)|*.Premade",
        .DefaultExt = "Premade",
        .AddExtension = True,
        .Title = "Save Premade"
    }
    If saveDialog.ShowDialog() = DialogResult.OK Then
        SaveSettingsToFile(saveDialog.FileName)
        LoadPremades()
    End If
End Sub

Private Sub LstHatchstyles_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstHatchstyles.SelectedIndexChanged
    If lstHatchstyles.SelectedItem IsNot Nothing Then
        Dim selectedHatchStyleName As String = lstHatchstyles.SelectedItem.ToString()
        Dim selectedHatchStyle As HatchStyle = CType([Enum].Parse(GetType(HatchStyle), selectedHatchStyleName), HatchStyle)
        If rdoFloors.Checked Then
            floorsHatchStyle = selectedHatchStyle
        ElseIf rdoWalls.Checked Then
            wallsHatchStyle = selectedHatchStyle
        End If
        If Not isRestoringState Then SaveStateForUndo()
        picGame.Invalidate()
    End If
End Sub

Private Sub BtnAll_Click(sender As Object, e As EventArgs)
    If Not isRestoringState Then SaveStateForUndo()
    RandomizeWalls()
    RandomizeFloors()
    picGame.Invalidate()
End Sub

Private Sub BtnWalls_Click(sender As Object, e As EventArgs)
    If Not isRestoringState Then SaveStateForUndo()
    RandomizeWalls()
    picGame.Invalidate()
End Sub

Private Sub BtnFloors_Click(sender As Object, e As EventArgs)
    If Not isRestoringState Then SaveStateForUndo()
    RandomizeFloors()
    picGame.Invalidate()
End Sub

Private Sub RandomizeWalls()
    wallsColor1 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
    wallsColor2 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
    Dim hatchStyles As Array = [Enum].GetValues(GetType(HatchStyle))
    wallsHatchStyle = CType(hatchStyles.GetValue(rand.Next(hatchStyles.Length)), HatchStyle)
    UpdateSelections()
End Sub

Private Sub RandomizeFloors()
    floorsColor1 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
    floorsColor2 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
    Dim hatchStyles As Array = [Enum].GetValues(GetType(HatchStyle))
    floorsHatchStyle = CType(hatchStyles.GetValue(rand.Next(hatchStyles.Length)), HatchStyle)
    UpdateSelections()
End Sub

Private Sub UpdateSelections()
    If rdoWalls.Checked Then
        lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
    ElseIf rdoFloors.Checked Then
        lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
    ElseIf rdoEdge.Checked Then
        lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
    End If

    cboStyleChoice.Enabled = rdoFloors.Checked
    If rdoFloors.Checked AndAlso useGradient Then
        lstHatchstyles.Enabled = False
    Else
        lstHatchstyles.Enabled = True
    End If

    If cboStyleChoice.Items.Count > 0 Then
        If useGradient Then
            cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("Gradient")
        Else
            cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("HatchStyle")
        End If
    End If

    ' Update the color pickers to reflect the current colors
    UpdateColorPickers()
End Sub
Private Sub RdoWalls_CheckedChanged(sender As Object, e As EventArgs) Handles rdoWalls.CheckedChanged
    If rdoWalls.Checked Then
        UpdateSelections()
    End If
End Sub

Private Sub RdoFloors_CheckedChanged(sender As Object, e As EventArgs) Handles rdoFloors.CheckedChanged
    If rdoFloors.Checked Then
        UpdateSelections()
    End If
End Sub

Private Sub RdoEdge_CheckedChanged(sender As Object, e As EventArgs) Handles rdoEdge.CheckedChanged
    If rdoEdge.Checked Then
        UpdateSelections()
    End If
End Sub

Private Sub Form_MouseMove(sender As Object, e As MouseEventArgs)
    Dim screenPos As Point = Me.PointToScreen(New Point(e.X, e.Y))
    Dim picGameClientPoint As Point = picGame.PointToClient(screenPos)

    isCursorOnPicGame = picGame.ClientRectangle.Contains(picGameClientPoint)

    If isDraggingItem Then
        mouseX = picGameClientPoint.X
        mouseY = picGameClientPoint.Y

        If isCursorOnPicGame Then
            UpdateHighlight(mouseX, mouseY)
            isMouseInsideGrid = True

            ' Snap the item to the nearest wall and center it
            If draggedItemIsWall Then
                If draggedItemIsVerticalWall Then
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 4
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                Else
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 4
                End If
            Else
                mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
            End If
        Else
            isMouseInsideGrid = False
        End If

        picGame.Invalidate() ' Invalidate only the picGame control
    End If

    UpdateStatusBar(e.X, e.Y)
End Sub
Private Function IsVerticalWallOuterBorder(x As Integer) As Boolean
    Return x = 0 OrElse x = SQCOUNTX
End Function

Private Function IsHorizontalWallOuterBorder(y As Integer) As Boolean
    Return y = 0 OrElse y = SQCOUNTY
End Function

Private Sub Form_MouseUp(sender As Object, e As MouseEventArgs)
    If isDraggingItem AndAlso e.Button = MouseButtons.Left Then
        ' Before modifying the data model, save the state
        If Not isRestoringState Then
            SaveStateForUndo()
        End If

        isDraggingItem = False
        Me.Capture = False
        Cursor.Current = Cursors.Default

        Dim screenPos As Point = Me.PointToScreen(New Point(e.X, e.Y))
        Dim picGameClientPoint As Point = picGame.PointToClient(screenPos)

        If picGame.ClientRectangle.Contains(picGameClientPoint) Then
            mouseX = picGameClientPoint.X
            mouseY = picGameClientPoint.Y
            UpdateHighlight(mouseX, mouseY)

            If isMouseInsideGrid Then
                If draggedItemIsWall Then
                    If draggedItemIsVerticalWall Then
                        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                       verticalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                            If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) OrElse IsFlipperItem(draggedItem.ImageKey) Then
                                verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                verticalWalls(highlightedX)(highlightedY) = False
                            Else
                                ReturnItemToOriginalLocation()
                            End If
                        End If
                    Else
                        If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                       horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                            If Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) OrElse IsFlipperItem(draggedItem.ImageKey) Then
                                horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                horizontalWalls(highlightedX)(highlightedY) = False
                            Else
                                ReturnItemToOriginalLocation()
                            End If
                        End If
                    End If
                Else
                    ' Existing code for placing non-wall items
                    If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                        placedItems(highlightedX)(highlightedY) = draggedItem
                    End If
                End If
            Else
                ReturnItemToOriginalLocation()
            End If
        Else
            ReturnItemToOriginalLocation()
        End If

        draggedItem = Nothing

        If isActionInProgress Then
            isActionInProgress = False
            redoStack.Clear()
        End If

        picGame.Invalidate()
        redoStack.Clear()
    End If
End Sub
Private Sub PicGame_MouseDown(sender As Object, e As MouseEventArgs) Handles picGame.MouseDown
    If SelectionMode Then
        ' Handle selecting a square in selection mode
        Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
        Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

        Dim selected As New SelectedObject With {
        .Type = "Square",
        .Coordinates = New Point(clickedX, clickedY)
    }

        RaiseEvent ObjectSelected(selected)

        SelectionMode = False
        SelectionRequestedByForm = Nothing
        SelectionRequestedFor = Nothing

        Return
    End If

    If isSelectionReadyToDrag Then
        ' Start dragging the selection
        If selectionRectangle.Contains(e.Location) Then
            If e.Button = MouseButtons.Left Then
                SaveStateForUndo()
                isDraggingSelection = True
                selectionOffset = New Point(e.X - selectionRectangle.X, e.Y - selectionRectangle.Y)
                selectionStartCell = New Point(
                (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS),
                (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS))
                picGame.Capture = True
                Cursor = Cursors.SizeAll
            ElseIf e.Button = MouseButtons.Right Then
                isSelectionReadyToDrag = False
                selectionRectangle = Rectangle.Empty
                Cursor = Cursors.Default
                picGame.Invalidate()
            End If
        Else
            isSelectionReadyToDrag = False
            selectionRectangle = Rectangle.Empty
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    ElseIf isInSelectionMode Then
        ' Start selecting a new area
        If e.Button = MouseButtons.Left Then
            isSelecting = True
            selectionStartPoint = e.Location
            selectionEndPoint = e.Location
            selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
            picGame.Invalidate()
        ElseIf e.Button = MouseButtons.Right Then
            If selectionRectangle.Contains(e.Location) Then
                Dim menuPosition As Point = e.Location
                ctmMenu.Show(picGame, menuPosition)
            End If
        End If
    Else
        ' Normal mouse down processing
        If e.Button = MouseButtons.Right Then
            isRightMouseDown = True
            rightMouseDownPosition = e.Location
            rightMouseDownTime = DateTime.Now
            If Not isActionInProgress Then
                isActionInProgress = True
                SaveStateForUndo()
            End If
        ElseIf e.Button = MouseButtons.Left Then
            isLeftMouseDown = True
            UpdateHighlight(e.X, e.Y)
            Dim itemPickedUp As Boolean = False
            If isMouseInsideGrid Then
                If Not isActionInProgress Then
                    isActionInProgress = True
                    SaveStateForUndo()
                End If

                If highlightedWallX Then
                    If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                   verticalWallItems(highlightedX) IsNot Nothing AndAlso
                   highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                        If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                            isDraggingItem = True
                            draggedItem = verticalWallItems(highlightedX)(highlightedY)
                            draggedItemIsWall = True
                            draggedItemIsVerticalWall = True
                            draggedItemOriginalX = highlightedX
                            draggedItemOriginalY = highlightedY
                            verticalWallItems(highlightedX)(highlightedY) = Nothing
                            verticalWalls(highlightedX)(highlightedY) = True
                            picGame.Capture = True
                            itemPickedUp = True
                        End If
                    End If
                ElseIf highlightedWallY Then
                    If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                   horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                   highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                        If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                            isDraggingItem = True
                            draggedItem = horizontalWallItems(highlightedX)(highlightedY)
                            draggedItemIsWall = True
                            draggedItemIsVerticalWall = False
                            draggedItemOriginalX = highlightedX
                            draggedItemOriginalY = highlightedY
                            horizontalWallItems(highlightedX)(highlightedY) = Nothing
                            horizontalWalls(highlightedX)(highlightedY) = True
                            picGame.Capture = True
                            itemPickedUp = True
                        End If
                    End If
                Else
                    If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                   placedItems(highlightedX) IsNot Nothing AndAlso
                   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                        If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                            isDraggingItem = True
                            draggedItem = placedItems(highlightedX)(highlightedY)
                            draggedItemIsWall = False
                            draggedItemIsVerticalWall = False
                            draggedItemOriginalX = highlightedX
                            draggedItemOriginalY = highlightedY
                            placedItems(highlightedX)(highlightedY) = Nothing
                            picGame.Capture = True
                            itemPickedUp = True
                        End If
                    End If
                End If

                If itemPickedUp Then
                    redoStack.Clear()
                    picGame.Invalidate()
                Else
                    If rdoEdge.Checked Then
                        isLeftMouseDown = True
                        SetEdgeStyleAtMousePosition(e.X, e.Y)
                    Else
                        isLeftMouseDown = True
                        AddWallAtMousePosition(e.X, e.Y)
                    End If
                End If
            End If
        End If
    End If
End Sub
Private Sub PicGame_MouseMove(sender As Object, e As MouseEventArgs) Handles picGame.MouseMove
    isCursorOnPicGame = True

    If isDraggingSelection Then
        ' Existing code for dragging selection
        If isCopying Then
            If copiedSelectionData Is Nothing Then
                Return
            End If
        Else
            If selectionData Is Nothing Then
                CaptureSelectionData()
            End If
        End If

        Dim newX As Integer = e.X - selectionOffset.X
        Dim newY As Integer = e.Y - selectionOffset.Y

        Dim cellX As Integer = (newX - INDENT + (SQSizeX + WALL_THICKNESS) \ 2) \ (SQSizeX + WALL_THICKNESS)
        Dim cellY As Integer = (newY - INDENT + (SQSizeY + WALL_THICKNESS) \ 2) \ (SQSizeY + WALL_THICKNESS)

        If isCopying Then
            cellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, cellX))
            cellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, cellY))
            selectionRectangle.Width = copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS
            selectionRectangle.Height = copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
        Else
            cellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, cellX))
            cellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, cellY))
            selectionRectangle.Width = selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS
            selectionRectangle.Height = selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
        End If

        selectionRectangle.X = INDENT + cellX * (SQSizeX + WALL_THICKNESS)
        selectionRectangle.Y = INDENT + cellY * (SQSizeY + WALL_THICKNESS)

        picGame.Invalidate()
    ElseIf isDraggingItem Then
        ' Update the mouse position to the current cursor position
        mouseX = e.X
        mouseY = e.Y

        ' Snap the item to the nearest wall and center it
        UpdateHighlight(e.X, e.Y)
        If draggedItemIsWall Then
            If draggedItemIsVerticalWall Then
                mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
            Else
                mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
            End If
        Else
            mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
            mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
        End If

        picGame.Invalidate()
    ElseIf isInSelectionMode Then
        If isSelecting Then
            selectionEndPoint = e.Location
            selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
            picGame.Invalidate()
        End If
    Else
        UpdateHighlight(e.X, e.Y)
        picGame.Invalidate()
        If isRightMouseDown Then
            If rdoEdge.Checked Then
                If isMouseInsideGrid Then
                    RemoveEdgeStyleAtMousePosition(e.X, e.Y)
                End If
            Else
                Dim deltaX As Integer = e.X - rightMouseDownPosition.X
                Dim deltaY As Integer = e.Y - rightMouseDownPosition.Y
                Dim distanceSquared As Integer = deltaX * deltaX + deltaY * deltaY
                If distanceSquared > CLICK_DRAG_THRESHOLD_SQUARED Then
                    RemoveObjectAtMousePosition(e.X, e.Y, removeWalls:=True, removeItems:=False)
                End If
            End If
        End If
        If isLeftMouseDown Then
            If rdoEdge.Checked Then
                If isMouseInsideGrid Then
                    SetEdgeStyleAtMousePosition(e.X, e.Y)
                End If
            Else
                AddWallAtMousePosition(e.X, e.Y)
            End If
        End If
    End If

    UpdateStatusBar(e.X, e.Y)
End Sub
Private Sub UpdateStatusBar(pixelX As Integer, pixelY As Integer)

    Dim squareX As Integer = (pixelX - INDENT + (SQSizeX + WALL_THICKNESS) \ 2) \ (SQSizeX + WALL_THICKNESS)
    Dim squareY As Integer = (pixelY - INDENT + (SQSizeY + WALL_THICKNESS) \ 2) \ (SQSizeY + WALL_THICKNESS)

    squareX = Math.Max(0, Math.Min(SQCOUNTX - 1, squareX))
    squareY = Math.Max(0, Math.Min(SQCOUNTY - 1, squareY))

    Dim isOverWall As Boolean = highlightedWallX OrElse highlightedWallY

    Dim content As String = "Empty"
    If isOverWall Then
        If highlightedWallX Then
            If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
       highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then
                If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                    content = GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)
                ElseIf verticalWalls(highlightedX)(highlightedY) Then
                    content = "Wall"
                End If
            End If
        ElseIf highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
       highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then
                If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                    content = GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)
                ElseIf horizontalWalls(highlightedX)(highlightedY) Then
                    content = "Wall"
                End If
            End If
        End If
    Else
        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then
            If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                content = GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)
            ElseIf floorsUseEdgeStyle(highlightedX)(highlightedY) Then
                content = "Edge"
            End If
        End If
    End If

    If isOverWall Then
        stb.Text = "Location: " & pixelX & ", " & pixelY & " | Wall: " & squareX & ", " & squareY & " | " & content
    Else
        stb.Text = "Location: " & pixelX & ", " & pixelY & " | Square: " & squareX & ", " & squareY & " | " & content
    End If
End Sub

Private Function GetItemName(imageKey As String) As String
    Select Case imageKey
        Case "picBlock1"
            Return "Block"
        Case "picBaddy1"
            Return "Baddy1"
        Case "picBaddy2"
            Return "Baddy2"
        Case "picLazer1"
            Return "Lazer1"
        Case "picLazer2"
            Return "Lazer2"
        Case "picFlipper1"
            Return "Flipper1"
        Case "picFlipper2"
            Return "Flipper2"
        Case "picFlipper3"
            Return "Flipper3"
        Case "picFlipper4"
            Return "Flipper4"
        Case "picBaddy3"
            Return "Baddy3"
        Case "picBaddy4"
            Return "Baddy4"
        Case "picCoin"
            Return "Coin"
        Case "picGate"
            Return "Gate"
        Case "picKey"
            Return "Key"
        Case "picStart"
            Return "Start"
        Case "picFinish"
            Return "Finish"
        Case "picBlockStopper1"
            Return "BlockStopper"
        Case "picBall"
            Return "Ball"
        Case "picBlockSwitch"
            Return "BlockSwitch"
        Case "picBomb"
            Return "Bomb"
        Case "picWarp"
            Return "Warp"
        Case "picWarp2"
            Return "Warp2"
        Case Else
            Return "Unknown"
    End Select
End Function

Private Sub RemoveEdgeStyleAtMousePosition(mouseX As Integer, mouseY As Integer)
    UpdateHighlight(mouseX, mouseY)
    If Not highlightedWallX AndAlso Not highlightedWallY Then
        If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
            floorsUseEdgeStyle(highlightedX)(highlightedY) = False
            picGame.Invalidate()
        End If
    End If
End Sub

Private Function IsLazerItem(imgKey As String) As Boolean
    Return imgKey = "picLazer1" OrElse imgKey = "picLazer2"
End Function

Private Sub PicGame_MouseUp(sender As Object, e As MouseEventArgs) Handles picGame.MouseUp
    If isDraggingSelection Then
        ' Existing code for completing selection dragging
        If e.Button = MouseButtons.Left Then
            ' Save the state before modifying the data model
            If Not isRestoringState Then
                SaveStateForUndo()
            End If

            isDraggingSelection = False
            picGame.Capture = False
            Cursor = Cursors.Default

            If selectionRectangle.IsEmpty Then
                Return
            End If

            Dim newCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
            Dim newCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)

            If isCopying Then
                newCellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, newCellY))
                PasteSelectionDataTo(newCellX, newCellY)
            Else
                newCellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, newCellY))
                MoveSelectionDataTo(newCellX, newCellY)
            End If

            isCopying = False
            isSelectionReadyToDrag = False
            isInSelectionMode = False
            selectionRectangle = Rectangle.Empty
            Cursor = Cursors.Default
            picGame.Invalidate()

            ' Clear the redo stack since we've made a new change
            redoStack.Clear()
        End If
    ElseIf isInSelectionMode Then
        If isSelecting AndAlso e.Button = MouseButtons.Left Then
            isSelecting = False
            ' Existing code for finalizing the selection rectangle
            selectionEndPoint = e.Location
            selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

            ' Snap the selection rectangle to the grid cells
            Dim startCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
            Dim startCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)
            Dim endCellX As Integer = ((selectionRectangle.Right - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeX + WALL_THICKNESS)
            Dim endCellY As Integer = ((selectionRectangle.Bottom - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeY + WALL_THICKNESS)

            startCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, startCellX))
            startCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, startCellY))
            endCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, endCellX))
            endCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, endCellY))

            Dim selectionWidth As Integer = endCellX - startCellX + 1
            Dim selectionHeight As Integer = endCellY - startCellY + 1

            ' Adjust the selection rectangle to match the grid and exclude walls
            selectionRectangle = New Rectangle(
            INDENT + startCellX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            INDENT + startCellY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
            selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
        )

            picGame.Invalidate()

            If selectionRectangle.IsEmpty Then
                isInSelectionMode = False
                Cursor = Cursors.Default
                selectionRectangle = Rectangle.Empty
            End If
        End If
    Else
        ' Existing code for normal mouse up processing
        If e.Button = MouseButtons.Left Then
            If isDraggingItem Then
                ' Save the state before modifying the data model
                If Not isRestoringState Then
                    SaveStateForUndo()
                End If

                isDraggingItem = False
                picGame.Capture = False
                Cursor.Current = Cursors.Default

                ' Snap the item to the grid position
                UpdateHighlight(e.X, e.Y)
                If isMouseInsideGrid Then
                    If draggedItemIsWall Then
                        If draggedItemIsVerticalWall Then
                            If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                           highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                                If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                    verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                    verticalWalls(highlightedX)(highlightedY) = False
                                Else
                                    ReturnItemToOriginalLocation()
                                End If
                            End If
                        Else
                            If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                           highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                                If Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                    horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                    horizontalWalls(highlightedX)(highlightedY) = False
                                Else
                                    ReturnItemToOriginalLocation()
                                End If
                            End If
                        End If
                    Else
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                       highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                            placedItems(highlightedX)(highlightedY) = draggedItem
                        End If
                    End If
                Else
                    ReturnItemToOriginalLocation()
                End If

                ' Clear redo stack since we've made a new change
                redoStack.Clear()

                ' Invalidate the picGame control to refresh the display
                picGame.Invalidate()
            ElseIf isLeftMouseDown Then
                ' For continuous actions like drawing walls, save the state when the action starts
                If Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                ' Since the action is complete, reset isActionInProgress
                isActionInProgress = False
            End If

            isLeftMouseDown = False

        ElseIf e.Button = MouseButtons.Right Then
            If isRightMouseDown Then
                ' For continuous actions like erasing, save the state when the action starts
                If Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                isRightMouseDown = False

                If rdoEdge.Checked Then
                    UpdateHighlight(e.X, e.Y)
                    If isMouseInsideGrid Then
                        If Not highlightedWallX AndAlso Not highlightedWallY Then
                            If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
                                floorsUseEdgeStyle(highlightedX)(highlightedY) = False
                                picGame.Invalidate()
                            End If
                        End If
                    End If
                Else
                    Dim deltaX As Integer = e.X - rightMouseDownPosition.X
                    Dim deltaY As Integer = e.Y - rightMouseDownPosition.Y
                    Dim distanceSquared As Integer = deltaX * deltaX + deltaY * deltaY
                    If distanceSquared < CLICK_DRAG_THRESHOLD_SQUARED Then
                        RemoveObjectAtMousePosition(e.X, e.Y, removeWalls:=False, removeItems:=True)
                    End If
                End If

                ' Since the action is complete, reset isActionInProgress
                isActionInProgress = False
            End If
        End If
    End If
End Sub
Public Sub UpdateGridSizeUI()
    RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
    RemoveHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

    If Not cboX.Items.Contains(SQCOUNTX.ToString()) Then
        cboX.Items.Add(SQCOUNTX.ToString())
    End If
    cboX.SelectedItem = SQCOUNTX.ToString()

    If Not cboY.Items.Contains(SQCOUNTY.ToString()) Then
        cboY.Items.Add(SQCOUNTY.ToString())
    End If
    cboY.SelectedItem = SQCOUNTY.ToString()

    AddHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
    AddHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

    CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

    picGame.Invalidate()
End Sub

#End Region

#Region " Draw "
Private Sub picGame_Paint(sender As Object, e As PaintEventArgs) Handles picGame.Paint
' Calculate the sizes of the squares based on the client size of picGame
CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

    ' Set the smoothing mode to None for pixel-perfect drawing
    e.Graphics.SmoothingMode = SmoothingMode.None

    ' Draw the gradient background if useGradient is true
    If useGradient Then
        DrawGradientBackground(e.Graphics)
    End If

    ' Draw the grid
    DrawGrid(e.Graphics)

    ' Draw any dragged item
    If isDraggingItem AndAlso draggedItem IsNot Nothing Then
        Dim img As Image = GetImageByKey(draggedItem.ImageKey)
        If img IsNot Nothing Then
            ImageAnimator.UpdateFrames(img)
            Dim drawRect As Rectangle

            If draggedItemIsWall Then
                If draggedItemIsVerticalWall Then
                    Dim wallWidth As Integer = WALL_THICKNESS
                    Dim wallHeight As Integer = SQSizeY
                    Dim xPos As Integer = mouseX - WALL_THICKNESS \ 2
                    Dim yPos As Integer = mouseY - SQSizeY \ 2
                    Dim newWidth As Integer = img.Width
                    Dim newHeight As Integer = wallHeight
                    Dim centeredX As Integer = xPos + (wallWidth - newWidth) \ 2
                    drawRect = New Rectangle(centeredX, yPos, newWidth, newHeight)
                Else
                    Dim wallWidth As Integer = SQSizeX
                    Dim wallHeight As Integer = WALL_THICKNESS
                    Dim xPos As Integer = mouseX - SQSizeX \ 2
                    Dim yPos As Integer = mouseY - WALL_THICKNESS \ 2
                    Dim newWidth As Integer = wallWidth
                    Dim newHeight As Integer = img.Height
                    Dim centeredY As Integer = yPos + (wallHeight - newHeight) \ 2
                    drawRect = New Rectangle(xPos, centeredY, newWidth, newHeight)
                End If
            Else
                Dim xPos As Integer = mouseX - SQSizeX \ 2
                Dim yPos As Integer = mouseY - SQSizeY \ 2
                drawRect = New Rectangle(xPos, yPos, SQSizeX, SQSizeY)
            End If
            e.Graphics.DrawImage(img, drawRect)
        End If
    End If

    ' Draw selection rectangle
    If (isInSelectionMode OrElse isSelectionReadyToDrag OrElse isDraggingSelection) AndAlso Not selectionRectangle.IsEmpty Then
        Using selectionBrush As New SolidBrush(Color.FromArgb(64, Color.Blue))
            e.Graphics.FillRectangle(selectionBrush, selectionRectangle)
        End Using
        Using selectionPen As New Pen(Color.Blue, 2) With {.DashStyle = DashStyle.Dash}
            e.Graphics.DrawRectangle(selectionPen, selectionRectangle)
        End Using
    End If

    ' Draw selection data during drag
    If isDraggingSelection Then
        Dim baseCellX As Integer = (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
        Dim baseCellY As Integer = (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)
        If isCopying Then
            DrawSelectionData(e.Graphics, baseCellX, baseCellY, copiedSelectionData)
        Else
            DrawSelectionData(e.Graphics, baseCellX, baseCellY, selectionData)
        End If
    End If

    ' Ensure DrawHighlight is called
    DrawHighlight(e.Graphics)
End Sub
Private Sub DrawGradientBackground(g As Graphics)
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT
    Dim gridWidth As Integer = SQCOUNTX * (SQSizeX + WALL_THICKNESS)
    Dim gridHeight As Integer = SQCOUNTY * (SQSizeY + WALL_THICKNESS)

    Dim gradientRect As New Rectangle(xOrigin, yOrigin, gridWidth, gridHeight)

    Using brush As New LinearGradientBrush(gradientRect, floorsColor1, floorsColor2, LinearGradientMode.Vertical)
        g.FillRectangle(brush, gradientRect)
    End Using
End Sub
Private Sub DrawGrid(g As Graphics)
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT

    DrawGradientBackground(g)

    For x As Integer = 0 To SQCOUNTX - 1
        For y As Integer = 0 To SQCOUNTY - 1
            Dim floorRect As New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    SQSizeX,
    SQSizeY
)

            If floorsUseEdgeStyle(x)(y) Then
                Using floorBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                    g.FillRectangle(floorBrush, floorRect)
                End Using
            ElseIf useGradient Then
                Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                    g.FillRectangle(hatchBrush, floorRect)
                End Using
            Else
                Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                    g.FillRectangle(floorBrush, floorRect)
                End Using
            End If
        Next
    Next

    For x As Integer = 0 To SQCOUNTX
        For y As Integer = 0 To SQCOUNTY
            Dim intersectionRect As New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    WALL_THICKNESS,
    WALL_THICKNESS
)

            Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                g.FillRectangle(wallBrush, intersectionRect)
            End Using
        Next
    Next

    For x As Integer = 0 To SQCOUNTX
        For y As Integer = 0 To SQCOUNTY - 1
            Dim wallRect As New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    WALL_THICKNESS, SQSizeY
)

            If x >= 0 AndAlso x < verticalWalls.Length AndAlso
   verticalWalls(x) IsNot Nothing AndAlso
   y >= 0 AndAlso y < verticalWalls(0).Length Then

                If verticalWalls(x)(y) Then
                    Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                        g.FillRectangle(wallBrush, wallRect)
                    End Using
                ElseIf useGradient Then
                    Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                        g.FillRectangle(hatchBrush, wallRect)
                    End Using
                Else
                    Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                        g.FillRectangle(floorBrush, wallRect)
                    End Using
                End If
            End If
        Next
    Next

    For x As Integer = 0 To SQCOUNTX - 1
        For y As Integer = 0 To SQCOUNTY
            Dim wallRect As New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    SQSizeX, WALL_THICKNESS
)

            If x >= 0 AndAlso x < horizontalWalls.Length AndAlso
   horizontalWalls(x) IsNot Nothing AndAlso
   y >= 0 AndAlso y < horizontalWalls(0).Length Then

                If horizontalWalls(x)(y) Then
                    Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                        g.FillRectangle(wallBrush, wallRect)
                    End Using
                ElseIf useGradient Then
                    Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                        g.FillRectangle(hatchBrush, wallRect)
                    End Using
                Else
                    Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                        g.FillRectangle(floorBrush, wallRect)
                    End Using
                End If
            End If
        Next
    Next

    DrawItems(g)
    DrawWallItems(g)

    DrawGridNumbers(g)
End Sub
Private Sub DrawGridNumbers(g As Graphics)
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT
    Dim font As New Font("Arial", 10, FontStyle.Regular)
    Dim brush As New SolidBrush(Color.Black)

    Dim columnNumberOffsetY As Single = -20.0F
    Dim rowNumberOffsetX As Single = -5.0F

    For x As Integer = 0 To SQCOUNTX - 1
        Dim xPos As Single = xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + CSng(SQSizeX) / 2
        Dim yPos As Single = yOrigin + columnNumberOffsetY

        Dim label As String = x.ToString()
        Dim xLabelSize As SizeF = g.MeasureString(label, font)
        g.DrawString(label, font, brush, xPos - xLabelSize.Width / 2.0F, yPos)
    Next

    For y As Integer = 0 To SQCOUNTY - 1
        Dim xPos As Single = xOrigin + rowNumberOffsetX
        Dim yPos As Single = yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + CSng(SQSizeY) / 2

        Dim label As String = y.ToString()
        Dim yLabelSize As SizeF = g.MeasureString(label, font)
        g.DrawString(label, font, brush, xPos - yLabelSize.Width, yPos - yLabelSize.Height / 2.0F)
    Next

    font.Dispose()
    brush.Dispose()
End Sub

Friend Sub FillBorderWalls()
    For y As Integer = 0 To SQCOUNTY - 1
        verticalWalls(0)(y) = True
        verticalWalls(SQCOUNTX)(y) = True
    Next

    For x As Integer = 0 To SQCOUNTX - 1
        horizontalWalls(x)(0) = True
        horizontalWalls(x)(SQCOUNTY) = True
    Next
End Sub

Private Sub DrawItems(g As Graphics)
    For x As Integer = 0 To placedItems.Length - 1
        If placedItems(x) IsNot Nothing Then
            For y As Integer = 0 To placedItems(x).Length - 1
                If placedItems(x)(y) IsNot Nothing Then
                    Dim img As Image = GetImageByKey(placedItems(x)(y).ImageKey)
                    If img IsNot Nothing Then
                        ImageAnimator.UpdateFrames(img)
                        Dim xPos As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                        Dim yPos As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                        Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

                        Dim imgAttributes As New Imaging.ImageAttributes()
                        imgAttributes.SetWrapMode(WrapMode.TileFlipXY)
                        g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                    End If
                End If
            Next
        End If
    Next
End Sub

Private Sub DrawWallItems(g As Graphics)
    For x As Integer = 0 To verticalWallItems.Length - 1
        If verticalWallItems(x) IsNot Nothing Then
            For y As Integer = 0 To verticalWallItems(x).Length - 1
                If verticalWallItems(x)(y) IsNot Nothing Then
                    Dim img As Image = GetImageByKey(verticalWallItems(x)(y).ImageKey)
                    If img IsNot Nothing Then
                        ImageAnimator.UpdateFrames(img)
                        Dim wallX As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) - (WALL_THICKNESS \ 2)
                        Dim wallY As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) + (WALL_THICKNESS \ 2)
                        Dim wallWidth As Integer = WALL_THICKNESS
                        Dim wallHeight As Integer = SQSizeY

                        Dim newWidth As Integer = img.Width
                        Dim newHeight As Integer = wallHeight

                        Dim centeredX As Integer = wallX + (wallWidth - newWidth) \ 2
                        Dim drawRect As New Rectangle(centeredX, wallY, newWidth, newHeight)
                        g.DrawImage(img, drawRect)
                    End If
                End If
            Next
        End If
    Next

    For x As Integer = 0 To horizontalWallItems.Length - 1
        If horizontalWallItems(x) IsNot Nothing Then
            For y As Integer = 0 To horizontalWallItems(x).Length - 1
                If horizontalWallItems(x)(y) IsNot Nothing Then
                    Dim img As Image = GetImageByKey(horizontalWallItems(x)(y).ImageKey)
                    If img IsNot Nothing Then
                        ImageAnimator.UpdateFrames(img)
                        Dim wallX As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) + (WALL_THICKNESS \ 2)
                        Dim wallY As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) - (WALL_THICKNESS \ 2)
                        Dim wallWidth As Integer = SQSizeX
                        Dim wallHeight As Integer = WALL_THICKNESS

                        Dim newWidth As Integer = wallWidth
                        Dim newHeight As Integer = img.Height

                        Dim centeredY As Integer = wallY + (wallHeight - newHeight) \ 2
                        Dim drawRect As New Rectangle(wallX, centeredY, newWidth, newHeight)
                        g.DrawImage(img, drawRect)
                    End If
                End If
            Next
        End If
    Next
End Sub

Private Sub DrawHighlight(g As Graphics)
    If Not isMouseInsideGrid OrElse highlightedX < 0 OrElse highlightedY < 0 OrElse Not isCursorOnPicGame Then
        Return
    End If

    Using highlightPen As New Pen(highlightColor, 2) With {.DashStyle = DashStyle.Dash}
        If highlightedWallX Then
            Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2
            Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim rect As New Rectangle(x, y, WALL_THICKNESS, SQSizeY)
            g.DrawRectangle(highlightPen, rect)
        ElseIf highlightedWallY Then
            Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2
            Dim rect As New Rectangle(x, y, SQSizeX, WALL_THICKNESS)
            g.DrawRectangle(highlightPen, rect)
        Else
            Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim rect As New Rectangle(x, y, SQSizeX, SQSizeY)
            g.DrawRectangle(highlightPen, rect)
        End If
    End Using
End Sub
Private Sub UpdateHighlight(mouseX As Integer, mouseY As Integer)
    mouseX -= INDENT
    mouseY -= INDENT

    isMouseInsideGrid = False
    highlightedWallX = False
    highlightedWallY = False
    highlightedX = -1
    highlightedY = -1

    Dim cellSizeX As Integer = SQSizeX + WALL_THICKNESS
    Dim cellSizeY As Integer = SQSizeY + WALL_THICKNESS
    Dim cellX As Integer = mouseX \ cellSizeX
    Dim cellY As Integer = mouseY \ cellSizeY

    If cellX >= 0 AndAlso cellX <= SQCOUNTX AndAlso
   cellY >= 0 AndAlso cellY <= SQCOUNTY Then

        Dim offsetX As Integer = mouseX Mod cellSizeX
        Dim offsetY As Integer = mouseY Mod cellSizeY

        highlightedWallX = offsetX < WALL_THICKNESS
        highlightedWallY = offsetY < WALL_THICKNESS
        highlightedX = cellX
        highlightedY = cellY

        isMouseInsideGrid = True
    End If

    If highlightedX < 0 Then highlightedX = 0
    If highlightedX > SQCOUNTX Then highlightedX = SQCOUNTX
    If highlightedY < 0 Then highlightedY = 0
    If highlightedY > SQCOUNTY Then highlightedY = SQCOUNTY

    If highlightedWallX AndAlso (highlightedX = 0 OrElse highlightedX = SQCOUNTX) Then
        highlightedWallX = True
    End If

    If highlightedWallY AndAlso (highlightedY = 0 OrElse highlightedY = SQCOUNTY) Then
        highlightedWallY = True
    End If
End Sub

#End Region

#Region " Colours "
Private Sub UpdateColorPickers()
If rdoWalls.Checked Then
' Set color pickers to wall colors
colPattern.SelectedColor = wallsColor1
colBackground.SelectedColor = wallsColor2
ElseIf rdoFloors.Checked Then
' Set color pickers to floor colors
colPattern.SelectedColor = floorsColor1
colBackground.SelectedColor = floorsColor2
End If
End Sub

Public Sub UpdateGameColors(senderObject As Object, newColor As Color)
    If rdoWalls.Checked Then
        ' Update wall colors
        If senderObject Is colPattern Then
            wallsColor1 = newColor
        ElseIf senderObject Is colBackground Then
            wallsColor2 = newColor
        End If
    ElseIf rdoFloors.Checked Then
        ' Update floor colors
        If senderObject Is colPattern Then
            floorsColor1 = newColor
        ElseIf senderObject Is colBackground Then
            floorsColor2 = newColor
        End If
    End If

    ' Redraw the game area to reflect changes
    picGame.Invalidate()
End Sub

Private Sub colPattern_ColorChanged(sender As Object, e As EventArgs)
    UpdateGameColors(colPattern, colPattern.SelectedColor)
End Sub

Private Sub colBackground_ColorChanged(sender As Object, e As EventArgs)
    UpdateGameColors(colBackground, colBackground.SelectedColor)
End Sub

#End Region

#Region " Utility and Helper Methods "
Private Sub BtnFillDeadEnds_Click(sender As Object, e As EventArgs) Handles btnFillDeadEnds.Click
If Not isRestoringState Then SaveStateForUndo()
FillDeadEnds()
End Sub
Private Sub FillDeadEnds()
If Not isRestoringState Then SaveStateForUndo()
Dim wallCount As Integer = 0

    ' Loop through each square in the grid
    For y As Integer = 0 To SQCOUNTY - 1
        For x As Integer = 0 To SQCOUNTX - 1
            wallCount = 0
            Dim isFlipperPresent As Boolean = False ' To track if any flippers are present

            ' Count the walls around the square at (x, y)
            If x = 0 OrElse (x > 0 AndAlso (verticalWalls(x)(y) OrElse IsFlipperItem(verticalWallItems(x)(y)))) Then
                wallCount += 1  ' Check vertical wall on the left
                If IsFlipperItem(verticalWallItems(x)(y)) Then isFlipperPresent = True
            End If
            If x = SQCOUNTX - 1 OrElse (x < SQCOUNTX AndAlso (verticalWalls(x + 1)(y) OrElse IsFlipperItem(verticalWallItems(x + 1)(y)))) Then
                wallCount += 1  ' Check vertical wall on the right
                If IsFlipperItem(verticalWallItems(x + 1)(y)) Then isFlipperPresent = True
            End If
            If y = 0 OrElse (y > 0 AndAlso (horizontalWalls(x)(y) OrElse IsFlipperItem(horizontalWallItems(x)(y)))) Then
                wallCount += 1  ' Check horizontal wall on the top
                If IsFlipperItem(horizontalWallItems(x)(y)) Then isFlipperPresent = True
            End If
            If y = SQCOUNTY - 1 OrElse (y < SQCOUNTY AndAlso (horizontalWalls(x)(y + 1) OrElse IsFlipperItem(horizontalWallItems(x)(y + 1)))) Then
                wallCount += 1  ' Check horizontal wall on the bottom
                If IsFlipperItem(horizontalWallItems(x)(y + 1)) Then isFlipperPresent = True
            End If

            ' Check if the square is not occupied, has three walls, and does not contain a flipper
            If wallCount = 3 AndAlso Not isFlipperPresent AndAlso placedItems(x)(y) Is Nothing Then
                placedItems(x)(y) = New PlacedItem() With {.ImageKey = "picCoin"}
            End If
        Next
    Next

    ' Refresh the game panel to show changes
    picGame.Invalidate()
End Sub

Private Function IsFlipperItem(item As PlacedItem) As Boolean
    If item Is Nothing Then Return False
    Return item.ImageKey = "picFlipper1" OrElse item.ImageKey = "picFlipper2" OrElse item.ImageKey = "picFlipper3" OrElse item.ImageKey = "picFlipper4"
End Function

Private Function IsHorizontalWallConnectedToEdge(pt As Point) As Boolean
    Dim x As Integer = pt.X
    Dim y As Integer = pt.Y

    If y > 0 AndAlso floorsUseEdgeStyle(x)(y - 1) Then Return True
    If y < SQCOUNTY AndAlso floorsUseEdgeStyle(x)(y) Then Return True

    Return False
End Function

Private Function IsVerticalWallConnectedToEdge(pt As Point) As Boolean
    Dim x As Integer = pt.X
    Dim y As Integer = pt.Y

    If x > 0 AndAlso floorsUseEdgeStyle(x - 1)(y) Then Return True
    If x < SQCOUNTX AndAlso floorsUseEdgeStyle(x)(y) Then Return True

    Return False
End Function

Private Sub CalculateSquareSizes(clientWidth As Integer, clientHeight As Integer)
    If SQCOUNTX <= 0 OrElse SQCOUNTY <= 0 Then
        Throw New InvalidOperationException("Grid dimensions must be greater than zero.")
    End If

    SQSizeX = (clientWidth - 2 * INDENT - (SQCOUNTX + 1) * WALL_THICKNESS) \ SQCOUNTX
    SQSizeY = (clientHeight - 2 * INDENT - (SQCOUNTY + 1) * WALL_THICKNESS) \ SQCOUNTY
End Sub

Private Function GetRectangle(p1 As Point, p2 As Point) As Rectangle
    Dim x As Integer = Math.Min(p1.X, p2.X)
    Dim y As Integer = Math.Min(p1.Y, p2.Y)
    Dim width As Integer = Math.Abs(p1.X - p2.X)
    Dim height As Integer = Math.Abs(p1.Y - p2.Y)
    Return New Rectangle(x, y, width, height)
End Function

Private Sub SetEdgeStyleAtMousePosition(mouseX As Integer, mouseY As Integer)
    UpdateHighlight(mouseX, mouseY)
    If Not highlightedWallX AndAlso Not highlightedWallY Then
        If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
            floorsUseEdgeStyle(highlightedX)(highlightedY) = True

            If highlightedX >= 0 AndAlso highlightedX < verticalWalls.Length AndAlso
       verticalWalls(highlightedX) IsNot Nothing AndAlso
       highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
                verticalWallItems(highlightedX)(highlightedY) = Nothing
                verticalWalls(highlightedX)(highlightedY) = True
            End If

            If highlightedX + 1 >= 0 AndAlso highlightedX + 1 < verticalWalls.Length AndAlso
       verticalWalls(highlightedX + 1) IsNot Nothing AndAlso
       highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
                verticalWallItems(highlightedX + 1)(highlightedY) = Nothing
                verticalWalls(highlightedX + 1)(highlightedY) = True
            End If

            If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
       horizontalWalls(highlightedX) IsNot Nothing AndAlso
       highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
                horizontalWallItems(highlightedX)(highlightedY) = Nothing
                horizontalWalls(highlightedX)(highlightedY) = True
            End If

            If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
       horizontalWalls(highlightedX) IsNot Nothing AndAlso
       highlightedY + 1 >= 0 AndAlso highlightedY + 1 < horizontalWalls(0).Length Then
                horizontalWallItems(highlightedX)(highlightedY + 1) = Nothing
                horizontalWalls(highlightedX)(highlightedY + 1) = True
            End If

            picGame.Invalidate()
        End If
    End If
End Sub

Private Function GetCellsInRectangle(rect As Rectangle) As List(Of Point)
    Dim selectedCells As New List(Of Point)
    For x As Integer = 0 To SQCOUNTX - 1
        For y As Integer = 0 To SQCOUNTY - 1
            Dim cellRect As Rectangle = GetCellRectangle(x, y)
            If rect.IntersectsWith(cellRect) Then
                selectedCells.Add(New Point(x, y))
            End If
        Next
    Next
    Return selectedCells
End Function

Private Function GetCellRectangle(x As Integer, y As Integer) As Rectangle
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT
    Return New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    SQSizeX,
    SQSizeY)
End Function

Private Function GetVerticalWallsInRectangle(rect As Rectangle) As List(Of Point)
    Dim selectedWalls As New List(Of Point)
    For x As Integer = 0 To SQCOUNTX
        For y As Integer = 0 To SQCOUNTY - 1
            Dim wallRect As Rectangle = GetVerticalWallRectangle(x, y)
            If rect.IntersectsWith(wallRect) Then
                selectedWalls.Add(New Point(x, y))
            End If
        Next
    Next
    Return selectedWalls
End Function

Private Function GetVerticalWallRectangle(x As Integer, y As Integer) As Rectangle
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT
    Return New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    WALL_THICKNESS, SQSizeY)
End Function

Private Function GetHorizontalWallsInRectangle(rect As Rectangle) As List(Of Point)
    Dim selectedWalls As New List(Of Point)
    For x As Integer = 0 To SQCOUNTX - 1
        For y As Integer = 0 To SQCOUNTY
            Dim wallRect As Rectangle = GetHorizontalWallRectangle(x, y)
            If rect.IntersectsWith(wallRect) Then
                selectedWalls.Add(New Point(x, y))
            End If
        Next
    Next
    Return selectedWalls
End Function

Private Function GetHorizontalWallRectangle(x As Integer, y As Integer) As Rectangle
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT
    Return New Rectangle(
    xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
    SQSizeX, WALL_THICKNESS)
End Function

Private Sub PicBlock_MouseDown(sender As Object, e As MouseEventArgs)
    Dim pic As PictureBox = CType(sender, PictureBox)

    isDraggingItem = True

    ' Create a new PlacedItem with the ImageKey
    draggedItem = New PlacedItem() With {.ImageKey = pic.Name}

    ' Determine if it's a wall item
    draggedItemIsWall = IsVerticalItem(pic.Name) OrElse IsHorizontalItem(pic.Name)
    draggedItemIsVerticalWall = IsVerticalItem(pic.Name)

    ' Since it's being dragged from the toolbox, there is no original position
    draggedItemOriginalX = -1
    draggedItemOriginalY = -1

    ' Capture the mouse events at the form level
    Me.Capture = True

    Cursor.Current = Cursors.Hand

    ' Initialize the mouse position to the current cursor position
    Dim mousePos As Point = picGame.PointToClient(Cursor.Position)
    mouseX = mousePos.X
    mouseY = mousePos.Y
    picGame.Invalidate()
End Sub

Private Sub ReturnItemToOriginalLocation()
    If draggedItemIsWall Then
        If draggedItemOriginalX >= 0 AndAlso draggedItemOriginalY >= 0 Then
            If draggedItemIsVerticalWall Then
                verticalWallItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
                verticalWalls(draggedItemOriginalX)(draggedItemOriginalY) = False
            Else
                horizontalWallItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
                horizontalWalls(draggedItemOriginalX)(draggedItemOriginalY) = False
            End If
        End If
    Else
        If draggedItemOriginalX >= 0 AndAlso draggedItemOriginalY >= 0 Then
            placedItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
        End If
    End If
End Sub

Private Function GetImageByKey(imageKey As String) As Image
    Select Case imageKey
        Case "picBlock1"
            Return picBlock1.Image
        Case "picBaddy1"
            Return picBaddy1.Image
        Case "picBaddy2"
            Return picBaddy2.Image
        Case "picBaddy3"
            Return picBaddy3.Image
        Case "picBaddy4"
            Return picBaddy4.Image
        Case "picLazer1"
            Return picLazer1.Image
        Case "picLazer2"
            Return picLazer2.Image
        Case "picFlipper1"
            Return picFlipper1.Image
        Case "picFlipper2"
            Return picFlipper2.Image
        Case "picFlipper3"
            Return picFlipper3.Image
        Case "picFlipper4"
            Return picFlipper4.Image
        Case "picCoin"
            Return picCoin.Image
        Case "picGate"
            Return picGate.Image
        Case "picKey"
            Return picKey.Image
        Case "picStart"
            Return picStart.Image
        Case "picFinish"
            Return picFinish.Image
        Case "picBlockStopper1"
            Return picBlockStopper1.Image
        Case "picBall"
            Return picBall.Image
        Case "picBlockSwitch"
            Return picBlockSwitch.Image
        Case "picBomb"
            Return picBomb.Image
        Case "picWarp"
            Return picWarp.Image
        Case "picWarp2"
            Return picWarp2.Image
        Case Else
            Return Nothing
    End Select
End Function

Private Function IsVerticalItem(imgKey As String) As Boolean
    Return imgKey = "picLazer1" OrElse imgKey = "picFlipper1" OrElse imgKey = "picFlipper2"
End Function

Private Function IsHorizontalItem(imgKey As String) As Boolean
    Return imgKey = "picLazer2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
End Function

Private Function IsFlipperItem(imgKey As String) As Boolean
    Return imgKey = "picFlipper1" OrElse imgKey = "picFlipper2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
End Function

Private Sub AddWallAtMousePosition(mouseX As Integer, mouseY As Integer)
    UpdateHighlight(mouseX, mouseY)

    If highlightedWallX Then
        If highlightedX >= 0 AndAlso highlightedX < verticalWalls.Length AndAlso

highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
verticalWalls(highlightedX)(highlightedY) = True
picGame.Invalidate()
End If
ElseIf highlightedWallY Then
If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
horizontalWalls(highlightedX)(highlightedY) = True
picGame.Invalidate()
End If
End If
End Sub

Private Sub RemoveObjectAtMousePosition(mouseX As Integer, mouseY As Integer, removeWalls As Boolean, removeItems As Boolean)
    UpdateHighlight(mouseX, mouseY)

    If Not isMouseInsideGrid Then
        Return
    End If

    If highlightedWallX Then
        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso

verticalWallItems(highlightedX) IsNot Nothing AndAlso
highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

            If removeItems AndAlso verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                verticalWallItems(highlightedX)(highlightedY) = Nothing
                verticalWalls(highlightedX)(highlightedY) = True
                picGame.Invalidate()

                If IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                    verticalWalls(highlightedX)(highlightedY) = True
                End If
            ElseIf removeWalls AndAlso verticalWalls(highlightedX)(highlightedY) Then
                If Not IsVerticalWallOuterBorder(highlightedX) AndAlso Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                    verticalWalls(highlightedX)(highlightedY) = False
                    picGame.Invalidate()
                End If
            End If
        End If
    ElseIf highlightedWallY Then
        If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso

horizontalWallItems(highlightedX) IsNot Nothing AndAlso
highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

            If removeItems AndAlso horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                horizontalWallItems(highlightedX)(highlightedY) = Nothing
                horizontalWalls(highlightedX)(highlightedY) = True
                picGame.Invalidate()

                If IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                    horizontalWalls(highlightedX)(highlightedY) = True
                End If
            ElseIf removeWalls AndAlso horizontalWalls(highlightedX)(highlightedY) Then
                If Not IsHorizontalWallOuterBorder(highlightedY) AndAlso Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                    horizontalWalls(highlightedX)(highlightedY) = False
                    picGame.Invalidate()
                End If
            End If
        End If
    Else
        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso

placedItems(highlightedX) IsNot Nothing AndAlso
highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

            If removeItems AndAlso placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                placedItems(highlightedX)(highlightedY) = Nothing
                picGame.Invalidate()
            End If
        End If
    End If
End Sub

Private Sub LoadSettingsFromFile(filePath As String)
    Try
        If Not File.Exists(filePath) Then Return
        Dim lines As String() = File.ReadAllLines(filePath)
        Dim section As String = ""
        For Each line As String In lines
            line = line.Trim()
            If String.IsNullOrEmpty(line) OrElse line.StartsWith(";"c) Then Continue For
            If line.StartsWith("[") AndAlso line.EndsWith("]") Then
                section = line.Substring(1, line.Length - 2)
            Else
                Dim parts() As String = line.Split(New Char() {":"c}, 2)
                If parts.Length = 2 Then
                    LoadProperty(parts(0).Trim(), parts(1).Trim(), section)
                End If
            End If
        Next

        UpdateSelections()
    Catch ex As Exception
        MessageBox.Show("Error loading settings: " & ex.Message)
    End Try
End Sub
Private Sub LoadProperty(key As String, value As String, section As String)
    Dim isWall As Boolean = (section = "Walls")
    Select Case key
        Case "HatchStyle"
            Dim hatchStyle As HatchStyle
            If [Enum].TryParse(value, hatchStyle) Then
                If isWall Then wallsHatchStyle = hatchStyle Else floorsHatchStyle = hatchStyle
            End If
        Case "Color1"
            Dim color As Color = ParseColor(value)
            If isWall Then wallsColor1 = color Else floorsColor1 = color
        Case "Color2"
            Dim color As Color = ParseColor(value)
            If isWall Then wallsColor2 = color Else floorsColor2 = color
    End Select
End Sub
Private Function ParseColor(colorString As String) As Color
    Dim parts() As String = colorString.Split(","c)
    If parts.Length = 4 Then
        Dim a, r, g, b As Integer
        If Integer.TryParse(parts(0), a) AndAlso Integer.TryParse(parts(1), r) AndAlso

Integer.TryParse(parts(2), g) AndAlso Integer.TryParse(parts(3), b) Then
Return Color.FromArgb(a, r, g, b)
End If
End If
Return Color.Black
End Function
Private Sub SaveSettingsToFile(filePath As String)
Try
Using sw As New StreamWriter(filePath)
sw.WriteLine("[Walls]")
sw.WriteLine("HatchStyle: " & wallsHatchStyle.ToString())
sw.WriteLine("Color1: " & ColorToString(wallsColor1))
sw.WriteLine("Color2: " & ColorToString(wallsColor2))
sw.WriteLine()
sw.WriteLine("[Floors]")
sw.WriteLine("HatchStyle: " & floorsHatchStyle.ToString())
sw.WriteLine("Color1: " & ColorToString(floorsColor1))
sw.WriteLine("Color2: " & ColorToString(floorsColor2))
End Using
Catch ex As Exception
MessageBox.Show("Error saving settings: " & ex.Message)
End Try
End Sub

Private Function ColorToString(color As Color) As String
    Return $"{color.A},{color.R},{color.G},{color.B}"
End Function

Private Sub BtnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click
    Dim tempFilePath As String = Path.Combine(Path.GetTempPath(), "temp.room")
    SaveRoomToFile(tempFilePath)

    PauseBackgroundTasks()

    Dim gameForm As New frmGame()
    gameForm.RoomFilePath = tempFilePath
    AddHandler gameForm.FormClosed, AddressOf OnGameFormClosed
    gameForm.Show()
End Sub

Private Sub PauseBackgroundTasks()
    animationTimer.Stop()
    highlightTimer.Stop()
End Sub

Private Sub ResumeBackgroundTasks()
    animationTimer.Start()
    highlightTimer.Start()
End Sub

Private Sub OnGameFormClosed(sender As Object, e As FormClosedEventArgs)
    ResumeBackgroundTasks()
End Sub

Private Function CloneJaggedArray(Of T)(sourceArray As T()()) As T()()
    Dim lengthOuter As Integer = sourceArray.Length
    Dim newArray As T()() = New T(lengthOuter - 1)() {}
    For i As Integer = 0 To lengthOuter - 1
        If sourceArray(i) IsNot Nothing Then
            Dim lengthInner As Integer = sourceArray(i).Length
            newArray(i) = New T(lengthInner - 1) {}
            Array.Copy(sourceArray(i), newArray(i), lengthInner)
        End If
    Next
    Return newArray
End Function

Private Function ConvertPlacedItemsToStrings(items As PlacedItem()()) As String()()
    Dim lengthOuter As Integer = items.Length
    Dim stringItems As String()() = New String(lengthOuter - 1)() {}
    For x As Integer = 0 To lengthOuter - 1
        If items(x) IsNot Nothing Then
            Dim lengthInner As Integer = items(x).Length
            stringItems(x) = New String(lengthInner - 1) {}
            For y As Integer = 0 To lengthInner - 1
                If items(x)(y) IsNot Nothing Then
                    stringItems(x)(y) = items(x)(y).ImageKey
                Else
                    stringItems(x)(y) = Nothing
                End If
            Next
        End If
    Next
    Return stringItems
End Function

Public Sub SaveRoomToFile(filePath As String)
    Try
        Dim currentState As New RoomData(Me)

        Dim options As New JsonSerializerOptions With {
.IncludeFields = True,
.WriteIndented = True

}
Dim jsonString As String = JsonSerializer.Serialize(currentState, options)

        File.WriteAllText(filePath, jsonString)
    Catch ex As Exception
        MessageBox.Show("Error saving room: " & ex.Message)
    End Try
End Sub

Private Sub LoadRoomFromFile(filePath As String)
    Try
        If Not File.Exists(filePath) Then Return

        Dim options As New JsonSerializerOptions With {
.IncludeFields = True

}
Dim jsonString As String = File.ReadAllText(filePath)
Dim roomData As RoomData = JsonSerializer.Deserialize(Of RoomData)(jsonString, options)

        If roomData Is Nothing Then
            MessageBox.Show("Error: Failed to deserialize room data.")
            Return
        End If

        isRestoringState = True
        roomData.RestoreState(Me)
        isRestoringState = False

        UpdateSelections()

        If roomData.useGradient Then
            cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("Gradient")
        Else
            cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("HatchStyle")
        End If

        Me.Text = filePath

        ' Update the audio label with the selected music
        Dim selectedMusicParts As String() = SelectedMusic.Split(New String() {", V:", ", G:"}, StringSplitOptions.None)
        If selectedMusicParts.Length = 3 Then
            Dim audioFile As String = selectedMusicParts(0).Trim()
            Dim volume As Integer = Integer.Parse(selectedMusicParts(1).Trim())
            Dim gain As Integer = Integer.Parse(selectedMusicParts(2).Trim())
            Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(audioFile)
            lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, volume, gain)
        End If

    Catch ex As Exception
        MessageBox.Show("Error loading room: " & ex.Message)
    End Try
End Sub

Private Sub BtnTriggerEditor_Click(sender As Object, e As EventArgs) Handles btnTriggerEditor.Click
    PauseBackgroundTasks()
    Dim frm As New frmTriggerEditor()
    frm.ShowDialog()
End Sub

Private Sub BtnAudio_Click(sender As Object, e As EventArgs) Handles btnAudio.Click, lblAudio.Click
    Dim selectedMusicParts As String() = SelectedMusic.Split(New String() {", V:", ", G:"}, StringSplitOptions.None)
    If selectedMusicParts.Length = 3 Then
        Dim audioFile As String = selectedMusicParts(0).Trim()
        Dim volume As Integer = Integer.Parse(selectedMusicParts(1).Trim())
        Dim gain As Integer = Integer.Parse(selectedMusicParts(2).Trim())
        PauseBackgroundTasks()
        Using frm As New frmMusic With {
.InitialVolume = volume,
.InitialGain = gain,
.InitialSelectedTrack = audioFile

}
If frm.ShowDialog() = DialogResult.OK Then
Dim returnedVolume As Integer = frm.VolumeLevel
Dim returnedGain As Integer = frm.GainLevel
Dim returnedAudioFile As String = frm.AudioFile

                SelectedMusic = String.Format("{0}, V:{1}, G:{2}", returnedAudioFile, returnedVolume, returnedGain)

                Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(returnedAudioFile)
                lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, returnedVolume, returnedGain)
            End If
        End Using
    Else
        MessageBox.Show("SelectedMusic string is not in the expected format.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End If
End Sub

#End Region

#Region " ctmMenu "
Private Sub BtnSelect_Click(sender As Object, e As EventArgs) Handles btnSelect.Click
If isInSelectionMode Then
' Exiting selection mode
isInSelectionMode = False
isSelecting = False
isSelectionReadyToDrag = False
selectionRectangle = Rectangle.Empty
picGame.Cursor = Cursors.Default
picGame.Invalidate()
Else
' Entering selection mode
isInSelectionMode = True
isSelecting = False
isSelectionReadyToDrag = False
selectionRectangle = Rectangle.Empty
picGame.Cursor = Cursors.Cross
End If
End Sub

Private Sub MnuSelectAll_Click(sender As Object, e As EventArgs) Handles mnuSelectAll.Click
    selectionRectangle = New Rectangle(INDENT, INDENT, SQCOUNTX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS, SQCOUNTY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)
    isInSelectionMode = True
    Cursor = Cursors.Cross
    picGame.Invalidate()
End Sub

Private Sub SelectAllItemsOfType(ParamArray itemKeys As String())
    Dim selectedPoints As New List(Of Point)

    For x As Integer = 0 To SQCOUNTX - 1
        For y As Integer = 0 To SQCOUNTY - 1
            Dim localX As Integer = x
            Dim localY As Integer = y
            If placedItems(localX)(localY) IsNot Nothing AndAlso Array.Exists(itemKeys, Function(key) placedItems(localX)(localY).ImageKey = key) Then
                selectedPoints.Add(New Point(localX, localY))
            End If
        Next
    Next

    If selectedPoints.Count > 0 Then
        Dim minX = selectedPoints.Min(Function(p) p.X)
        Dim minY = selectedPoints.Min(Function(p) p.Y)
        Dim maxX = selectedPoints.Max(Function(p) p.X)
        Dim maxY = selectedPoints.Max(Function(p) p.Y)

        selectionRectangle = New Rectangle(
    INDENT + minX * (SQSizeX + WALL_THICKNESS),
    INDENT + minY * (SQSizeY + WALL_THICKNESS),
    (maxX - minX + 1) * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
    (maxY - minY + 1) * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

        isInSelectionMode = True
        Cursor = Cursors.Cross
        picGame.Invalidate()
    End If
End Sub

Private Sub ctmMenu_Opened(sender As Object, e As EventArgs)
    AddHandler ctmMenu.Closed, AddressOf ctmMenu_Closed
End Sub

Private Sub ctmMenu_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs)
    If isInSelectionMode Then
        Dim menuPosition = picGame.PointToClient(MousePosition)
        ctmMenu.Show(picGame, menuPosition)
    End If
End Sub
Private Sub mnuCancel_Click(sender As Object, e As EventArgs)
    isInSelectionMode = False
    isSelectionReadyToDrag = False
    Cursor = Cursors.Default
    picGame.Invalidate()
End Sub

Private Sub mnuFill_Click(sender As Object, e As EventArgs) Handles mnuFillBlocks.Click, mnuFillBlockStoppers.Click, mnuFillCoins.Click, mnuFIllEdge.Click, mnuFillKeys.Click, mnuFillWalls.Click
    If Not isRestoringState Then SaveStateForUndo()
    If selectionRectangle.IsEmpty Then
        MessageBox.Show("Please select an area before using the fill operation.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Return
    End If

    ' Extract the selection type from the sender's name
    Dim menuItem = CType(sender, ToolStripMenuItem)
    Dim selectionType = menuItem.Name.Substring(7) ' Remove the "mnuFill" prefix

    ' Call the appropriate fill method based on the selection type
    Select Case selectionType
        Case "Edge"
            FillSelectedAreaWithEdge()
        Case "Walls"
            FillSelectedAreaWithWalls()
        Case "BlockStoppers"
            FillSelectedAreaWithItems("picBlockStopper1")
        Case "Blocks"
            FillSelectedAreaWithItems("picBlock1")
        Case "Coins"
            FillSelectedAreaWithItems("picCoin")
        Case "Keys"
            FillSelectedAreaWithItems("picKey")
        Case Else
            MessageBox.Show($"Invalid selection type for fill operation: {selectionType}.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Select
End Sub

Private Sub mnuClear_Click(sender As Object, e As EventArgs) Handles mnuClearAll.Click, mnuClearBaddies.Click, mnuClearBlocks.Click, mnuClearBlockStoppers.Click, mnuClearCoins.Click, mnuClearEdge.Click, mnuClearKeys.Click, mnuClearWalls.Click, mnuClearWarps.Click
    If Not isRestoringState Then SaveStateForUndo()
    If selectionRectangle.IsEmpty Then
        Return
    End If

    ' Extract the selection type from the sender's name
    Dim menuItem = CType(sender, ToolStripMenuItem)
    Dim selectionType = menuItem.Name.Substring(8) ' Remove the "mnuClear" prefix

    ' Call the appropriate clear method based on the selection type
    Select Case selectionType
        Case "All"
            ClearSelectedArea()
        Case "Edge"
            ClearSelectedAreaEdge()
        Case "Walls"
            ClearSelectedAreaWalls()
        Case Else
            ClearSelectedAreaItems(GetItemKeysForType(selectionType))
    End Select
End Sub

Private Sub mnuCopy_Click(sender As Object, e As EventArgs) Handles mnuCopyAll.Click, mnuCopyBaddies.Click, mnuCopyBlocks.Click, mnuCopyBlockStoppers.Click, mnuCopyCoins.Click, mnuCopyEdge.Click, mnuCopyKeys.Click, mnuCopyWalls.Click, mnuCopyWarps.Click
    If Not isRestoringState Then SaveStateForUndo()
    If selectionRectangle.IsEmpty Then
        Return
    End If

    CaptureSelectionData()
    copiedSelectionData = CloneSelectionData(selectionData)
    isCopying = True

    ' Extract the selection type from the sender's name
    Dim menuItem = CType(sender, ToolStripMenuItem)
    copiedSelectionType = menuItem.Name.Substring(6) ' Remove the "mnuCopy" prefix

    ' Automatically start dragging the selection
    isSelectionReadyToDrag = True
    isDraggingSelection = True
    selectionOffset = New Point(Cursor.Position.X - picGame.PointToScreen(selectionRectangle.Location).X, Cursor.Position.Y - picGame.PointToScreen(selectionRectangle.Location).Y)
    picGame.Capture = True
    Cursor = Cursors.SizeAll
    picGame.Invalidate()
End Sub
Private Function CloneSelectionData(original As SelectionData) As SelectionData
    Dim clone As New SelectionData()
    clone.selectionWidth = original.selectionWidth
    clone.selectionHeight = original.selectionHeight

    ' Clone placedItems
    If original.placedItems IsNot Nothing Then
        clone.placedItems = original.placedItems.Select(Function(row) row.ToArray()).ToArray()
    End If

    ' Clone floorsUseEdgeStyle
    If original.floorsUseEdgeStyle IsNot Nothing Then
        clone.floorsUseEdgeStyle = original.floorsUseEdgeStyle.Select(Function(row) row.ToArray()).ToArray()
    End If

    ' Clone verticalWalls and verticalWallItems
    If original.verticalWalls IsNot Nothing Then
        clone.verticalWalls = original.verticalWalls.Select(Function(row) CType(row.Clone(), Boolean())).ToArray()
    End If

    If original.verticalWallItems IsNot Nothing Then
        clone.verticalWallItems = original.verticalWallItems.Select(Function(row) row.ToArray()).ToArray()
    End If

    ' Clone horizontalWalls and horizontalWallItems
    If original.horizontalWalls IsNot Nothing Then
        clone.horizontalWalls = original.horizontalWalls.Select(Function(row) CType(row.Clone(), Boolean())).ToArray()
    End If

    If original.horizontalWallItems IsNot Nothing Then
        clone.horizontalWallItems = original.horizontalWallItems.Select(Function(row) row.ToArray()).ToArray()
    End If

    Return clone
End Function
Private Sub mnuMove_Click(sender As Object, e As EventArgs) Handles mnuMoveAll.Click, mnuMoveBaddies.Click, mnuMoveBlocks.Click, mnuMoveBlockStoppers.Click, mnuMoveCoins.Click, mnuMoveEdge.Click, mnuMoveKeys.Click, mnuMoveWalls.Click, mnuMoveWarps.Click
    If Not isRestoringState Then SaveStateForUndo()
    If selectionRectangle.IsEmpty Then
        Return
    End If

    CaptureSelectionData()
    isCopying = False

    ' Extract the selection type from the sender's name
    Dim menuItem = CType(sender, ToolStripMenuItem)
    copiedSelectionType = menuItem.Name.Substring(6) ' Remove the "mnuMove" prefix

    ' Automatically start dragging the selection
    isSelectionReadyToDrag = True
    isDraggingSelection = True
    selectionOffset = New Point(Cursor.Position.X - picGame.PointToScreen(selectionRectangle.Location).X, Cursor.Position.Y - picGame.PointToScreen(selectionRectangle.Location).Y)
    picGame.Capture = True
    Cursor = Cursors.SizeAll
    picGame.Invalidate()
End Sub

Private Sub CaptureSelectionData()
    If selectionRectangle.IsEmpty Then
        Return
    End If

    ' Get start and end cell indices from selection rectangle
    Dim startCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
    Dim startCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)
    Dim endCellX As Integer = ((selectionRectangle.Right - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeX + WALL_THICKNESS)
    Dim endCellY As Integer = ((selectionRectangle.Bottom - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeY + WALL_THICKNESS)

    startCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, startCellX))
    startCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, startCellY))
    endCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, endCellX))
    endCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, endCellY))

    Dim selectionWidth As Integer = endCellX - startCellX + 1
    Dim selectionHeight As Integer = endCellY - startCellY + 1

    ' Initialize selectionData
    selectionData = New SelectionData()
    selectionData.selectionWidth = selectionWidth
    selectionData.selectionHeight = selectionHeight

    ' Capture placed items and floor edge styles
    selectionData.placedItems = New PlacedItem(selectionWidth - 1)() {}
    selectionData.floorsUseEdgeStyle = New Boolean(selectionWidth - 1)() {}
    For x As Integer = 0 To selectionWidth - 1
        selectionData.placedItems(x) = New PlacedItem(selectionHeight - 1) {}
        selectionData.floorsUseEdgeStyle(x) = New Boolean(selectionHeight - 1) {}
        For y As Integer = 0 To selectionHeight - 1
            selectionData.placedItems(x)(y) = placedItems(startCellX + x)(startCellY + y)
            selectionData.floorsUseEdgeStyle(x)(y) = floorsUseEdgeStyle(startCellX + x)(startCellY + y)
        Next
    Next

    ' Capture internal vertical walls (excluding boundary walls)
    If selectionWidth > 1 Then
        Dim numVertWalls As Integer = selectionWidth - 2
        selectionData.verticalWalls = New Boolean(numVertWalls)() {}
        selectionData.verticalWallItems = New PlacedItem(numVertWalls)() {}
        For x As Integer = 0 To numVertWalls
            selectionData.verticalWalls(x) = New Boolean(selectionHeight - 1) {}
            selectionData.verticalWallItems(x) = New PlacedItem(selectionHeight - 1) {}
            For y As Integer = 0 To selectionHeight - 1
                selectionData.verticalWalls(x)(y) = verticalWalls(startCellX + x + 1)(startCellY + y)
                selectionData.verticalWallItems(x)(y) = verticalWallItems(startCellX + x + 1)(startCellY + y)
            Next
        Next
    Else
        ' No internal vertical walls to capture
        selectionData.verticalWalls = Nothing
        selectionData.verticalWallItems = Nothing
    End If

    ' Capture internal horizontal walls (excluding boundary walls)
    If selectionHeight > 1 Then
        Dim numHorizWalls As Integer = selectionHeight - 2
        selectionData.horizontalWalls = New Boolean(selectionWidth - 1)() {}
        selectionData.horizontalWallItems = New PlacedItem(selectionWidth - 1)() {}
        For x As Integer = 0 To selectionWidth - 1
            selectionData.horizontalWalls(x) = New Boolean(numHorizWalls) {}
            selectionData.horizontalWallItems(x) = New PlacedItem(numHorizWalls) {}
            For y As Integer = 0 To numHorizWalls
                selectionData.horizontalWalls(x)(y) = horizontalWalls(startCellX + x)(startCellY + y + 1)
                selectionData.horizontalWallItems(x)(y) = horizontalWallItems(startCellX + x)(startCellY + y + 1)
            Next
        Next
    Else
        ' No internal horizontal walls to capture
        selectionData.horizontalWalls = Nothing
        selectionData.horizontalWallItems = Nothing
    End If

    ' Store the selection start cell
    selectionStartCell = New Point(startCellX, startCellY)
End Sub
Private Sub PasteSelectionDataTo(newCellX As Integer, newCellY As Integer)
    If copiedSelectionData Is Nothing Then
        Return
    End If

    ' Ensure the new position is within bounds
    newCellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, newCellX))
    newCellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, newCellY))

    ' Paste placed items and floor edge styles
    For x As Integer = 0 To copiedSelectionData.selectionWidth - 1
        For y As Integer = 0 To copiedSelectionData.selectionHeight - 1
            If newCellX + x < SQCOUNTX AndAlso newCellY + y < SQCOUNTY Then
                placedItems(newCellX + x)(newCellY + y) = copiedSelectionData.placedItems(x)(y)
                floorsUseEdgeStyle(newCellX + x)(newCellY + y) = copiedSelectionData.floorsUseEdgeStyle(x)(y)
            End If
        Next
    Next

    ' Paste internal vertical walls, if any
    If copiedSelectionData.verticalWalls IsNot Nothing Then
        For x As Integer = 0 To copiedSelectionData.verticalWalls.Length - 1
            For y As Integer = 0 To copiedSelectionData.verticalWalls(x).Length - 1
                Dim targetX As Integer = newCellX + x + 1
                Dim targetY As Integer = newCellY + y

                If targetX >= 0 AndAlso targetX < verticalWalls.Length AndAlso targetY >= 0 AndAlso targetY < verticalWalls(0).Length Then
                    verticalWalls(targetX)(targetY) = copiedSelectionData.verticalWalls(x)(y)
                    verticalWallItems(targetX)(targetY) = copiedSelectionData.verticalWallItems(x)(y)
                End If
            Next
        Next
    End If

    ' Paste internal horizontal walls, if any
    If copiedSelectionData.horizontalWalls IsNot Nothing Then
        For x As Integer = 0 To copiedSelectionData.horizontalWalls.Length - 1
            For y As Integer = 0 To copiedSelectionData.horizontalWalls(x).Length - 1
                Dim targetX As Integer = newCellX + x
                Dim targetY As Integer = newCellY + y + 1

                If targetX >= 0 AndAlso targetX < horizontalWalls.Length AndAlso targetY >= 0 AndAlso targetY < horizontalWalls(0).Length Then
                    horizontalWalls(targetX)(targetY) = copiedSelectionData.horizontalWalls(x)(y)
                    horizontalWallItems(targetX)(targetY) = copiedSelectionData.horizontalWallItems(x)(y)
                End If
            Next
        Next
    End If

    ' Update the selection rectangle to the new position
    selectionRectangle = New Rectangle(
    INDENT + newCellX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    INDENT + newCellY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
    copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
)

    picGame.Invalidate()
End Sub
Private Sub MoveSelectionDataTo(newCellX As Integer, newCellY As Integer)
    ' Ensure the new position is within bounds
    newCellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, newCellX))
    newCellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, newCellY))

    ' Move placed items and floor edge styles
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            Dim targetX As Integer = newCellX + x
            Dim targetY As Integer = newCellY + y
            Dim sourceX As Integer = selectionStartCell.X + x
            Dim sourceY As Integer = selectionStartCell.Y + y

            If targetX >= 0 AndAlso targetX < SQCOUNTX AndAlso targetY >= 0 AndAlso targetY < SQCOUNTY Then
                placedItems(targetX)(targetY) = selectionData.placedItems(x)(y)
                floorsUseEdgeStyle(targetX)(targetY) = selectionData.floorsUseEdgeStyle(x)(y)
            End If
            ' Clear original location
            placedItems(sourceX)(sourceY) = Nothing
            floorsUseEdgeStyle(sourceX)(sourceY) = False
        Next
    Next

    ' Move internal vertical walls, if any
    If selectionData.verticalWalls IsNot Nothing Then
        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            For y As Integer = 0 To selectionData.verticalWalls(x).Length - 1
                Dim targetX As Integer = newCellX + x + 1
                Dim targetY As Integer = newCellY + y
                Dim sourceX As Integer = selectionStartCell.X + x + 1
                Dim sourceY As Integer = selectionStartCell.Y + y

                If targetX >= 0 AndAlso targetX < verticalWalls.Length AndAlso targetY >= 0 AndAlso targetY < verticalWalls(0).Length Then
                    verticalWalls(targetX)(targetY) = selectionData.verticalWalls(x)(y)
                    verticalWallItems(targetX)(targetY) = selectionData.verticalWallItems(x)(y)
                End If
                ' Clear original location
                verticalWalls(sourceX)(sourceY) = True
                verticalWallItems(sourceX)(sourceY) = Nothing
            Next
        Next
    End If

    ' Move internal horizontal walls, if any
    If selectionData.horizontalWalls IsNot Nothing Then
        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            For y As Integer = 0 To selectionData.horizontalWalls(x).Length - 1
                Dim targetX As Integer = newCellX + x
                Dim targetY As Integer = newCellY + y + 1
                Dim sourceX As Integer = selectionStartCell.X + x
                Dim sourceY As Integer = selectionStartCell.Y + y + 1

                If targetX >= 0 AndAlso targetX < horizontalWalls.Length AndAlso targetY >= 0 AndAlso targetY < horizontalWalls(0).Length Then
                    horizontalWalls(targetX)(targetY) = selectionData.horizontalWalls(x)(y)
                    horizontalWallItems(targetX)(targetY) = selectionData.horizontalWallItems(x)(y)
                End If
                ' Clear original location
                horizontalWalls(sourceX)(sourceY) = True
                horizontalWallItems(sourceX)(sourceY) = Nothing
            Next
        Next
    End If

    ' Update the selection rectangle to the new position
    selectionRectangle = New Rectangle(
    INDENT + newCellX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    INDENT + newCellY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
    selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
    selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
)

    ' Update the selection start cell to the new location
    selectionStartCell = New Point(newCellX, newCellY)

    ' Invalidate the picGame control to refresh the display
    picGame.Invalidate()
End Sub
Private Sub ClearSelectionDataAt(oldCellX As Integer, oldCellY As Integer)
    ' Clear placed items and floors
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            placedItems(oldCellX + x)(oldCellY + y) = Nothing
            floorsUseEdgeStyle(oldCellX + x)(oldCellY + y) = False
        Next
    Next

    ' Clear internal vertical walls, if any
    If selectionData.verticalWalls IsNot Nothing Then
        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            For y As Integer = 0 To selectionData.verticalWalls(x).Length - 1
                Dim wallX As Integer = oldCellX + x + 1
                Dim wallY As Integer = oldCellY + y
                verticalWalls(wallX)(wallY) = True ' Reset to default state (e.g., wall present or absent)
                verticalWallItems(wallX)(wallY) = Nothing
            Next
        Next
    End If

    ' Clear internal horizontal walls, if any
    If selectionData.horizontalWalls IsNot Nothing Then
        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            For y As Integer = 0 To selectionData.horizontalWalls(x).Length - 1
                Dim wallX As Integer = oldCellX + x
                Dim wallY As Integer = oldCellY + y + 1
                horizontalWalls(wallX)(wallY) = True ' Reset to default state
                horizontalWallItems(wallX)(wallY) = Nothing
            Next
        Next
    End If
End Sub
Private Sub DrawSelectionData(g As Graphics, baseCellX As Integer, baseCellY As Integer, data As SelectionData)
    Dim xOrigin As Integer = INDENT
    Dim yOrigin As Integer = INDENT

    Dim colorMatrix As New Imaging.ColorMatrix()
    colorMatrix.Matrix33 = 0.5F  ' Set opacity to 50%
    Dim imgAttributes As New Imaging.ImageAttributes()
    imgAttributes.SetColorMatrix(colorMatrix, Imaging.ColorMatrixFlag.Default, Imaging.ColorAdjustType.Bitmap)

    For x As Integer = 0 To data.selectionWidth - 1
        For y As Integer = 0 To data.selectionHeight - 1
            If data.placedItems(x)(y) IsNot Nothing Then
                Dim img As Image = GetImageByKey(data.placedItems(x)(y).ImageKey)
                If img IsNot Nothing Then
                    ImageAnimator.UpdateFrames(img)
                    Dim xPos As Integer = xOrigin + (baseCellX + x) * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                    Dim yPos As Integer = yOrigin + (baseCellY + y) * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                    Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)
                    g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                End If
            End If
        Next
    Next
End Sub
Private Sub FillSelectedAreaWithEdge()
    CaptureSelectionData()
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            If selectionStartCell.X + x < SQCOUNTX AndAlso selectionStartCell.Y + y < SQCOUNTY Then
                floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
            End If
        Next
    Next
    For x As Integer = 0 To selectionData.selectionWidth
        For y As Integer = 0 To selectionData.selectionHeight - 1
            If selectionStartCell.X + x < verticalWalls.Length AndAlso selectionStartCell.Y + y < verticalWalls(0).Length Then
                verticalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
            End If
        Next
    Next
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight
            If selectionStartCell.X + x < horizontalWalls.Length AndAlso selectionStartCell.Y + y < horizontalWalls(0).Length Then
                horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
            End If
        Next
    Next
    picGame.Invalidate()
End Sub
Private Sub FillSelectedAreaWithWalls()
    If selectionData Is Nothing Then
        CaptureSelectionData()
    End If

    ' Fill internal vertical walls
    For x As Integer = 0 To selectionData.selectionWidth - 2
        For y As Integer = 0 To selectionData.selectionHeight - 1
            Dim wallX As Integer = selectionStartCell.X + x + 1
            Dim wallY As Integer = selectionStartCell.Y + y

            If wallX >= 0 AndAlso wallX < verticalWalls.Length AndAlso wallY >= 0 AndAlso wallY < verticalWalls(0).Length Then
                verticalWalls(wallX)(wallY) = True
            End If
        Next
    Next

    ' Fill internal horizontal walls
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 2
            Dim wallX As Integer = selectionStartCell.X + x
            Dim wallY As Integer = selectionStartCell.Y + y + 1

            If wallX >= 0 AndAlso wallX < horizontalWalls.Length AndAlso wallY >= 0 AndAlso wallY < horizontalWalls(0).Length Then
                horizontalWalls(wallX)(wallY) = True
            End If
        Next
    Next

    picGame.Invalidate()
End Sub
Private Sub FillSelectedAreaWithItems(itemKey As String)
    CaptureSelectionData()
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            If selectionStartCell.X + x < placedItems.Length AndAlso selectionStartCell.Y + y < placedItems(0).Length Then
                placedItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = New PlacedItem() With {.ImageKey = itemKey}
            End If
        Next
    Next
    picGame.Invalidate()
End Sub

Private Sub ClearSelectedArea()
    If selectionData Is Nothing Then
        CaptureSelectionData()
    End If

    ' Clear placed items and floor edge styles
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            placedItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
            floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
        Next
    Next

    ' Clear internal vertical walls
    For x As Integer = 0 To selectionData.selectionWidth - 2
        For y As Integer = 0 To selectionData.selectionHeight - 1
            verticalWalls(selectionStartCell.X + x + 1)(selectionStartCell.Y + y) = False
            verticalWallItems(selectionStartCell.X + x + 1)(selectionStartCell.Y + y) = Nothing
        Next
    Next

    ' Clear internal horizontal walls
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 2
            horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y + 1) = False
            horizontalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y + 1) = Nothing
        Next
    Next

    picGame.Invalidate()
End Sub
Private Sub ClearSelectedAreaEdge()
    CaptureSelectionData()
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 1
            If selectionStartCell.X + x < SQCOUNTX AndAlso selectionStartCell.Y + y < SQCOUNTY Then
                floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
            End If
        Next
    Next
    picGame.Invalidate()
End Sub

Private Sub ClearSelectedAreaWalls()
    If selectionData Is Nothing Then
        CaptureSelectionData()
    End If

    ' Clear internal vertical walls
    For x As Integer = 0 To selectionData.selectionWidth - 2
        For y As Integer = 0 To selectionData.selectionHeight - 1
            Dim wallX As Integer = selectionStartCell.X + x + 1
            Dim wallY As Integer = selectionStartCell.Y + y

            ' Ensure indices are within bounds
            If wallX >= 0 AndAlso wallX < verticalWalls.Length AndAlso wallY >= 0 AndAlso wallY < verticalWalls(0).Length Then
                verticalWalls(wallX)(wallY) = False
                verticalWallItems(wallX)(wallY) = Nothing
            End If
        Next
    Next

    ' Clear internal horizontal walls
    For x As Integer = 0 To selectionData.selectionWidth - 1
        For y As Integer = 0 To selectionData.selectionHeight - 2
            Dim wallX As Integer = selectionStartCell.X + x
            Dim wallY As Integer = selectionStartCell.Y + y + 1

            ' Ensure indices are within bounds
            If wallX >= 0 AndAlso wallX < horizontalWalls.Length AndAlso wallY >= 0 AndAlso wallY < horizontalWalls(0).Length Then
                horizontalWalls(wallX)(wallY) = False
                horizontalWallItems(wallX)(wallY) = Nothing
            End If
        Next
    Next

    picGame.Invalidate()
End Sub
Private Sub ClearSelectedAreaItems(ParamArray itemKeys As String())
    CaptureSelectionData()
    For x As Integer = 0 To selectionData.selectionWidth - 1
        Dim localX As Integer = x
        For y As Integer = 0 To selectionData.selectionHeight - 1
            Dim localY As Integer = y
            If selectionStartCell.X + localX < placedItems.Length AndAlso selectionStartCell.Y + localY < placedItems(0).Length Then
                If placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY) IsNot Nothing AndAlso Array.Exists(itemKeys, Function(key) placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY).ImageKey = key) Then
                    placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY) = Nothing
                End If
            End If
        Next
    Next
    picGame.Invalidate()
End Sub

#End Region

End Class

I recently created a ColorPicker control. And it works perfectly! You helped me a lot with that. Thank you!

I did it to reduce the code in frmDesigner. I've recently just added a new Control called ItemToolBar. the control is already populated with tab pages and all the controls I use in my app. What I'm trying to accomplish here is basically for you. My programming skills are very poor. So I upload it to you all the time, hoping for solutions etc. But the code has gotten SO long now, that it's getting harder and harder to get solutions. This is my way of bringing down the massive amount of code in frmDesigner. To help you to help me. 

Can you go through frmDesigner. update all the code associated with picBlock. Move the vast majority into ItemToolBar.vb. All the variables addhandlers and what else you think is needed. (Anything starting with "pic" is usually items on the toolbar not including picGame)

Please list entire subs. As I'm not versed enough in coding to understand what you mean when you comment "'existing code" or similar. It confuses me, throws me off and I end up stressed.