Option Explicit On
Option Strict On

Imports System.Windows.Forms
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

Public Class frmDesigner
    Inherits Form

#Region "Variables"
    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", "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
#End Region

#Region "Constructor and Initialization"

    Public Sub New()
        InitializeComponent()
        InitializeWalls()
        InitializeTimers()
        InitializeEventHandlers()
        LoadPremades()
        LoadMusic()
        InitializeAnimationForImages()
        If Not isRestoringState Then SaveStateForUndo()
        Me.KeyPreview = True

    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
            ' Call your existing Undo method
            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
            ' Call your existing Redo method
            btnRedo_Click(sender, e)
            e.SuppressKeyPress = True
        End If
    End Sub
    Private Function GetProjectDirectory() As String
        Dim dir As DirectoryInfo = New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4 ' Adjust this number based on your directory structure
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function

    Friend Sub InitializeWalls()
        ' Initialize verticalWalls and verticalWallItems
        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

        ' Initialize horizontalWalls and horizontalWallItems
        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

        ' Initialize placedItems
        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

        ' Initialize floorsUseEdgeStyle
        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}
        AddHandler animationTimer.Tick, AddressOf OnAnimationTick
        animationTimer.Start()
    End Sub

    Private Sub InitializeEventHandlers()
        AddColorPictureBoxHandlers(Me)

        ' 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()
        ' Get new grid sizes from combo boxes
        Dim newSQCOUNTX As Integer
        Dim newSQCOUNTY As Integer
        If Integer.TryParse(cboX.SelectedItem?.ToString(), newSQCOUNTX) AndAlso Integer.TryParse(cboY.SelectedItem?.ToString(), newSQCOUNTY) Then
            ' Save current state for undo before changing the grid size
            SaveState(True)

            ' Store old grid sizes
            Dim oldSQCOUNTX As Integer = SQCOUNTX
            Dim oldSQCOUNTY As Integer = SQCOUNTY

            ' Update grid size variables
            SQCOUNTX = newSQCOUNTX
            SQCOUNTY = newSQCOUNTY

            ' Resize the arrays while keeping existing data
            ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, SQCOUNTX, SQCOUNTY)

            ' Recalculate square sizes based on new grid dimensions
            CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

            ' Invalidate the drawing area to redraw the grid
            picGame.Invalidate()

            ' Reset highlighting and selection states
            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)
        ' Resize verticalWalls and verticalWallItems
        verticalWalls = ResizeJaggedArray(verticalWalls, oldX + 1, oldY - 1, newX + 1, newY - 1, True)
        verticalWallItems = ResizeJaggedArray(verticalWallItems, oldX + 1, oldY - 1, newX + 1, newY - 1, False)

        ' Resize horizontalWalls and horizontalWallItems
        horizontalWalls = ResizeJaggedArray(horizontalWalls, oldX - 1, oldY + 1, newX - 1, newY + 1, True)
        horizontalWallItems = ResizeJaggedArray(horizontalWallItems, oldX - 1, oldY + 1, newX - 1, newY + 1, False)

        ' Resize placedItems
        placedItems = ResizeJaggedArray(placedItems, oldX - 1, oldY - 1, newX - 1, newY - 1, False)

        ' Resize floorsUseEdgeStyle
        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
                    ' Copy existing data if within old array bounds
                    newArray(x)(y) = sourceArray(x)(y)
                Else
                    ' Initialize with default values
                    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  ' Change the background color on hover
        Debug.WriteLine("Mouse entered: " & pb.Name)  ' Debug message
    End Sub

    Public Sub PictureBox_MouseLeave(sender As Object, e As EventArgs)
        Dim pb As PictureBox = DirectCast(sender, PictureBox)
        pb.BackColor = Color.Transparent  ' Reset the background color
        Debug.WriteLine("Mouse left: " & pb.Name)  ' Debug message
    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

#End Region

#Region "Event Handlers"

    Private Sub frmDesigner_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Remove event handlers temporarily
        RemoveHandler cboX.SelectedIndexChanged, AddressOf cboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf cboY_SelectedIndexChanged

        ' Set default selections
        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

        ' Reattach event handlers
        AddHandler cboX.SelectedIndexChanged, AddressOf cboX_SelectedIndexChanged
        AddHandler cboY.SelectedIndexChanged, AddressOf cboY_SelectedIndexChanged

        ' Other initialization code...
        LoadMusic()
        LoadPremades()
        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()
        ' Ensure the music folder exists
        If Not Directory.Exists(musicFolder) Then
            Directory.CreateDirectory(musicFolder)
        End If

        ' Load available music files into the SelectedMusic string based on the desired file
        Dim allowedExtensions As String() = {".mp3", ".wav"}
        Dim musicFileFound As Boolean = False

        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    ' Set default value or use your slider's value
                Dim gain As Integer = 10       ' Set default value or use your slider's value

                ' Update SelectedMusic with the chosen music file
                SelectedMusic = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, volume, gain)
                lblAudio.Text = SelectedMusic

                musicFileFound = True
                Exit For ' Assuming you want to take just one file, leave the loop
            End If
        Next

        If Not musicFileFound Then
            lblAudio.Text = "No music files found."
            SelectedMusic = String.Empty ' Clear if no files are found
        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)
        ' Required but can be left empty
    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"))

            ' Update lstHatchstyles selection after loading settings
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub

    Private Sub mnuClearWalls_Click(sender As Object, e As EventArgs) Handles mnuClearWalls.Click
        If Not selectionRectangle.IsEmpty Then
            If Not isRestoringState Then SaveStateForUndo()

            For Each pt As Point In GetVerticalWallsInRectangle(selectionRectangle)
                If Not IsVerticalWallOuterBorder(pt.X) AndAlso Not IsVerticalWallConnectedToEdge(pt) Then
                    verticalWalls(pt.X)(pt.Y) = False
                    verticalWallItems(pt.X)(pt.Y) = Nothing
                End If
            Next

            For Each pt As Point In GetHorizontalWallsInRectangle(selectionRectangle)
                If Not IsHorizontalWallOuterBorder(pt.Y) AndAlso Not IsHorizontalWallConnectedToEdge(pt) Then
                    horizontalWalls(pt.X)(pt.Y) = False
                    horizontalWallItems(pt.X)(pt.Y) = Nothing
                End If
            Next

            selectionRectangle = Rectangle.Empty
            isInSelectionMode = False
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    End Sub

    Private Sub mnuFillWalls_Click(sender As Object, e As EventArgs) Handles mnuFillWalls.Click
        If Not selectionRectangle.IsEmpty Then
            If Not isRestoringState Then SaveStateForUndo()

            For Each pt As Point In GetVerticalWallsInRectangle(selectionRectangle)
                If pt.X > 0 AndAlso pt.X < SQCOUNTX Then
                    verticalWalls(pt.X)(pt.Y) = True
                End If
            Next

            For Each pt As Point In GetHorizontalWallsInRectangle(selectionRectangle)
                If pt.Y > 0 AndAlso pt.Y < SQCOUNTY Then
                    horizontalWalls(pt.X)(pt.Y) = True
                End If
            Next

            selectionRectangle = Rectangle.Empty
            isInSelectionMode = False
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    End Sub

    Private Sub mnuClearObjects_Click(sender As Object, e As EventArgs) Handles mnuClearObjects.Click
        If Not selectionRectangle.IsEmpty Then
            If Not isRestoringState Then SaveStateForUndo()

            For Each pt As Point In GetCellsInRectangle(selectionRectangle)
                placedItems(pt.X)(pt.Y) = Nothing
            Next

            For Each pt As Point In GetVerticalWallsInRectangle(selectionRectangle)
                verticalWallItems(pt.X)(pt.Y) = Nothing
            Next

            For Each pt As Point In GetHorizontalWallsInRectangle(selectionRectangle)
                horizontalWallItems(pt.X)(pt.Y) = Nothing
            Next

            selectionRectangle = Rectangle.Empty
            isInSelectionMode = False
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    End Sub
    Private Sub mnuClearEdge_Click(sender As Object, e As EventArgs) Handles mnuClearEdge.Click
        If Not selectionRectangle.IsEmpty Then
            If Not isRestoringState Then SaveStateForUndo()

            For Each pt As Point In GetCellsInRectangle(selectionRectangle)
                floorsUseEdgeStyle(pt.X)(pt.Y) = False
            Next

            selectionRectangle = Rectangle.Empty
            isInSelectionMode = False
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    End Sub

    Private Sub mnuAddEdge_Click(sender As Object, e As EventArgs) Handles mnuAddEdge.Click
        If Not selectionRectangle.IsEmpty Then
            If Not isRestoringState Then SaveStateForUndo()

            ' Loop through each cell within the selection rectangle
            For Each pt As Point In GetCellsInRectangle(selectionRectangle)
                ' Remove any floor items in the cell
                placedItems(pt.X)(pt.Y) = Nothing

                ' Remove edge style if previously applied
                floorsUseEdgeStyle(pt.X)(pt.Y) = True

                ' Remove wall items around the cell and place walls

                ' Left vertical wall at (pt.X, pt.Y)
                If pt.X >= 0 AndAlso pt.X < verticalWalls.Length Then
                    ' Remove wall item on this wall
                    verticalWallItems(pt.X)(pt.Y) = Nothing
                    ' Place the wall
                    verticalWalls(pt.X)(pt.Y) = True
                End If

                ' Right vertical wall at (pt.X + 1, pt.Y)
                If pt.X + 1 >= 0 AndAlso pt.X + 1 < verticalWalls.Length Then
                    verticalWallItems(pt.X + 1)(pt.Y) = Nothing
                    verticalWalls(pt.X + 1)(pt.Y) = True
                End If

                ' Top horizontal wall at (pt.X, pt.Y)
                If pt.X >= 0 AndAlso pt.X < horizontalWalls.Length AndAlso pt.Y >= 0 AndAlso pt.Y < horizontalWalls(0).Length Then
                    horizontalWallItems(pt.X)(pt.Y) = Nothing
                    horizontalWalls(pt.X)(pt.Y) = True
                End If

                ' Bottom horizontal wall at (pt.X, pt.Y + 1)
                If pt.X >= 0 AndAlso pt.X < horizontalWalls.Length AndAlso pt.Y + 1 >= 0 AndAlso pt.Y + 1 < horizontalWalls(0).Length Then
                    horizontalWallItems(pt.X)(pt.Y + 1) = Nothing
                    horizontalWalls(pt.X)(pt.Y + 1) = True
                End If
            Next

            ' Clear the selection and update the UI
            selectionRectangle = Rectangle.Empty
            isInSelectionMode = False
            Cursor = Cursors.Default
            picGame.Invalidate()
        End If
    End Sub

    Private Sub mnuMove_Click(sender As Object, e As EventArgs) Handles mnuMove.Click
        isSelectionReadyToDrag = True
        Cursor = Cursors.Hand
        ' Capture the data within the selection
        CaptureSelectionData()
    End Sub

    Private Sub btnSelect_Click(sender As Object, e As EventArgs) Handles btnSelect.Click
        If isInSelectionMode Then
            isInSelectionMode = False
            Cursor = Cursors.Default
            selectionRectangle = Rectangle.Empty
            picGame.Invalidate()
        Else
            isInSelectionMode = True
            Cursor = Cursors.Cross
        End If
    End Sub

    Private Sub mnuCancel_Click(sender As Object, e As EventArgs) Handles mnuCancel.Click
        selectionRectangle = Rectangle.Empty
        isInSelectionMode = False
        isSelectionReadyToDrag = False
        Cursor = Cursors.Default
        picGame.Invalidate()
    End Sub

    Private Sub mnuNew_Click(sender As Object, e As EventArgs) Handles mnuNew.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 = ""

        ' Clear level name and music selection
        txtName.Text = ""
        SelectedMusic = ""
        'cboMusic.SelectedIndex = -1

        ' Update selections
        UpdateSelections()

        picGame.Invalidate()
    End Sub

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

    Private Sub mnuLoad_Click(sender As Object, e As EventArgs) Handles mnuLoad.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
            ' Before loading, save the current state
            If Not isRestoringState Then SaveStateForUndo()
            LoadRoomFromFile(openDialog.FileName)
            currentFilePath = openDialog.FileName

            ' Update the UI elements related to grid size
            UpdateGridSizeUI()

            ' Update selections
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub
    Private Sub mnuSaveAs_Click(sender As Object, e As EventArgs) Handles mnuSaveAs.Click
        Dim saveDialog As New SaveFileDialog() With {
            .InitialDirectory = savesFolder,
            .Filter = "Room Files (*.room)|*.room",
            .DefaultExt = "room",
            .AddExtension = True,
            .Title = "Save Room As"
        }
        If saveDialog.ShowDialog() = DialogResult.OK Then
            SaveRoomToFile(saveDialog.FileName)
            currentFilePath = saveDialog.FileName
        End If
    End Sub
    Private Sub SaveState(isNewAction As Boolean)
        If isRestoringState Then Return
        If isNewAction Then
            ' Clear redo stack when a new action is performed
            redoStack.Clear()
        End If
        ' Save current state to undo stack
        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 SaveStateForUndo()
        If isRestoringState Then Return
        ' Do not clear redoStack here
        ' Save current state to undo stack
        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 SaveStateForRedo()
        If isRestoringState Then Return
        ' Save current state to redo stack
        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 btnUndo_Click(sender As Object, e As EventArgs) Handles btnUndo.Click
        If undoStack.Count > 1 Then
            ' Save current state to redo stack before undo
            SaveStateForRedo()
            ' Pop the last 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 grid size-related UI
            UpdateGridSizeUI()

            ' Update selections
            UpdateSelections()

            picGame.Invalidate()
        End If
    End Sub
    Private Sub btnRedo_Click(sender As Object, e As EventArgs) Handles btnRedo.Click
        If redoStack.Count > 0 Then
            ' Save current state to undo stack before redo
            SaveStateForUndo()
            ' Restore the state from redo stack
            isRestoringState = True
            Dim redoState As RoomState = redoStack.Pop()
            redoState.RestoreState(Me)
            isRestoringState = False

            ' Update grid size-related UI
            UpdateGridSizeUI()

            ' Update selections
            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 cboMusic_SelectedIndexChanged(sender As Object, e As EventArgs)
        ' SelectedMusic = If(cboMusic.SelectedItem?.ToString(), "")
        If Not isRestoringState Then SaveStateForUndo()
    End Sub

    Private Sub btnAll_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeWalls()
        RandomizeFloors()

        ' Update the HatchStyle list selection
        UpdateSelections()
        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 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 PicGame_Paint(sender As Object, e As PaintEventArgs)
        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)
        e.Graphics.SmoothingMode = SmoothingMode.None
        DrawGrid(e.Graphics)
        DrawHighlight(e.Graphics)

        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
                        ' Draw vertical wall item under cursor
                        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

                        ' Keep original image width, stretch height
                        Dim newWidth As Integer = img.Width
                        Dim newHeight As Integer = wallHeight

                        ' Center horizontally
                        Dim centeredX As Integer = xPos + (wallWidth - newWidth) \ 2
                        drawRect = New Rectangle(centeredX, yPos, newWidth, newHeight)
                    Else
                        ' Draw horizontal wall item under cursor
                        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

                        ' Stretch width, keep original image height
                        Dim newWidth As Integer = wallWidth
                        Dim newHeight As Integer = img.Height

                        ' Center vertically
                        Dim centeredY As Integer = yPos + (wallHeight - newHeight) \ 2
                        drawRect = New Rectangle(xPos, centeredY, newWidth, newHeight)
                    End If
                Else
                    ' Draw floor item under cursor
                    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

        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

        If isDraggingSelection Then
            ' Draw the selectionData at the selection rectangle position with transparency
            Dim drawCellX As Integer = (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim drawCellY As Integer = (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)
            DrawSelectionData(e.Graphics, drawCellX, drawCellY)
        End If
    End Sub

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

            mouseX = picGameClientPoint.X
            mouseY = picGameClientPoint.Y

            If picGame.ClientRectangle.Contains(picGameClientPoint) Then
                UpdateHighlight(mouseX, mouseY)
                isMouseInsideGrid = True
            Else
                isMouseInsideGrid = False
            End If

            picGame.Invalidate()
        End If
    End Sub
    Private Function IsVerticalWallOuterBorder(x As Integer) As Boolean
        ' Vertical walls on the outer borders are at x = 0 and x = SQCOUNTX
        Return x = 0 OrElse x = SQCOUNTX
    End Function
    Private Function IsHorizontalWallOuterBorder(y As Integer) As Boolean
        ' Horizontal walls on the outer borders are at y = 0 and y = SQCOUNTY
        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
            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)

                ' Place the item if the drop location is valid
                If isMouseInsideGrid Then
                    If highlightedWallX AndAlso draggedItemIsWall AndAlso draggedItemIsVerticalWall Then
                        ' Ensure indices are within bounds
                        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                       verticalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < verticalWallItems(highlightedX).Length Then

                            If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                ' Place vertical wall item
                                verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                verticalWalls(highlightedX)(highlightedY) = False
                            Else
                                ' Invalid drop location, return item to original location
                                ReturnItemToOriginalLocation()
                            End If
                        End If
                    ElseIf highlightedWallY AndAlso draggedItemIsWall AndAlso Not draggedItemIsVerticalWall Then
                        ' Ensure indices are within bounds
                        If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                       horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(highlightedX).Length Then

                            If Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                ' Place horizontal wall item
                                horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                horizontalWalls(highlightedX)(highlightedY) = False
                            Else
                                ' Invalid drop location, return item to original location
                                ReturnItemToOriginalLocation()
                            End If
                        End If
                    ElseIf Not highlightedWallX AndAlso Not highlightedWallY AndAlso Not draggedItemIsWall Then
                        ' Ensure indices are within bounds
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                       placedItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < placedItems(highlightedX).Length Then

                            ' Place floor item
                            placedItems(highlightedX)(highlightedY) = draggedItem
                        End If
                    Else
                        ' Invalid drop location, return item to original location
                        ReturnItemToOriginalLocation()
                    End If
                Else
                    ' Not inside grid, return item to original location
                    ReturnItemToOriginalLocation()
                End If
            Else
                ' Not over picGame, return item to original location
                ReturnItemToOriginalLocation()
            End If

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

            picGame.Invalidate()
        End If
    End Sub
    Private Sub PicGame_MouseDown(sender As Object, e As MouseEventArgs) Handles picGame.MouseDown
        If SelectionMode Then
            ' Determine what was clicked
            Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

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

            ' Raise the event
            RaiseEvent ObjectSelected(selected)

            ' Reset selection mode
            SelectionMode = False
            SelectionRequestedByForm = Nothing
            SelectionRequestedFor = Nothing

            ' Exit the method to prevent other interactions
            Return
        End If
        If isSelectionReadyToDrag Then
            If selectionRectangle.Contains(e.Location) Then
                If e.Button = MouseButtons.Left Then
                    ' Start dragging the selection immediately
                    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
                    ' Cancel the move operation
                    isSelectionReadyToDrag = False
                    selectionRectangle = Rectangle.Empty
                    Cursor = Cursors.Default
                    picGame.Invalidate()
                End If
            Else
                ' User clicked outside the selection, cancel the selection
                isSelectionReadyToDrag = False
                selectionRectangle = Rectangle.Empty
                Cursor = Cursors.Default
                picGame.Invalidate()
            End If
        ElseIf isInSelectionMode Then
            If e.Button = MouseButtons.Left Then
                isSelecting = True
                selectionStartPoint = e.Location
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
                picGame.Invalidate()
            End If
        Else
            If e.Button = MouseButtons.Right Then
                isRightMouseDown = True
                rightMouseDownPosition = e.Location
                rightMouseDownTime = DateTime.Now
                If Not isActionInProgress Then
                    isActionInProgress = True
                    If Not isRestoringState Then SaveStateForUndo()
                End If
            ElseIf e.Button = MouseButtons.Left Then
                UpdateHighlight(e.X, e.Y)
                Dim itemPickedUp As Boolean = False
                If isMouseInsideGrid Then
                    If Not isActionInProgress Then
                        isActionInProgress = True
                        If Not isRestoringState Then SaveStateForUndo()
                    End If

                    ' Try to pick up an item first
                    If highlightedWallX Then
                        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                       verticalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < verticalWallItems(highlightedX).Length Then

                            If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                                ' Pick up vertical wall item
                                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(highlightedX).Length Then

                            If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                                ' Pick up horizontal wall item
                                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(highlightedX).Length Then

                            If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                                ' Pick up floor item
                                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 no item was picked up, proceed to set edge style or add walls
                        If rdoEdge.Checked Then
                            isLeftMouseDown = True
                            SetEdgeStyleAtMousePosition(e.X, e.Y)
                        Else
                            ' Add wall at mouse position
                            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
        If isDraggingSelection Then
            ' Update the selection rectangle position as the mouse moves
            Dim newX As Integer = e.X - selectionOffset.X
            Dim newY As Integer = e.Y - selectionOffset.Y

            ' Calculate cell positions
            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)

            ' Constrain to grid boundaries
            cellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, cellX))
            cellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, cellY))

            ' Update selection rectangle position
            selectionRectangle.X = INDENT + cellX * (SQSizeX + WALL_THICKNESS)
            selectionRectangle.Y = INDENT + cellY * (SQSizeY + WALL_THICKNESS)
            selectionRectangle.Width = selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS
            selectionRectangle.Height = selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS

            picGame.Invalidate()
        ElseIf isInSelectionMode Then
            If isSelecting Then
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
                picGame.Invalidate()
            End If
        Else
            If isDraggingItem Then
                mouseX = e.X
                mouseY = e.Y
                UpdateHighlight(e.X, e.Y)
                picGame.Invalidate()
            Else
                mouseX = e.X
                mouseY = e.Y
                UpdateHighlight(e.X, e.Y)
                picGame.Invalidate()
                If isRightMouseDown Then
                    If rdoEdge.Checked Then
                        UpdateHighlight(e.X, e.Y)
                        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
                ElseIf isLeftMouseDown Then
                    If rdoEdge.Checked Then
                        UpdateHighlight(e.X, e.Y)
                        If isMouseInsideGrid Then
                            SetEdgeStyleAtMousePosition(e.X, e.Y)
                        End If
                    Else
                        AddWallAtMousePosition(e.X, e.Y)
                    End If
                End If
            End If
        End If
    End Sub
    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 Sub PicGame_MouseUp(sender As Object, e As MouseEventArgs) Handles picGame.MouseUp
        If isDraggingSelection Then
            If e.Button = MouseButtons.Left Then
                ' Finish dragging
                isDraggingSelection = False
                picGame.Capture = False
                Cursor = Cursors.Default

                ' Get new cell positions
                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)

                ' Ensure indices are within bounds
                newCellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, newCellY))

                ' First, clear data from original location
                ClearSelectionDataAt(selectionStartCell.X, selectionStartCell.Y)

                ' Then, move data to new location
                MoveSelectionDataTo(newCellX, newCellY)

                ' Reset selection-related variables
                selectionRectangle = Rectangle.Empty
                isSelectionReadyToDrag = False
                isInSelectionMode = False ' Exit selection mode after moving
                Cursor = Cursors.Default
                picGame.Invalidate()

                If Not isRestoringState Then SaveStateForUndo()
            ElseIf e.Button = MouseButtons.Right Then
                ' Cancel the move operation
                isDraggingSelection = False
                picGame.Capture = False
                Cursor = Cursors.Default

                ' Reset selection rectangle to original position
                selectionRectangle = New Rectangle(
                INDENT + selectionStartCell.X * (SQSizeX + WALL_THICKNESS),
                INDENT + selectionStartCell.Y * (SQSizeY + WALL_THICKNESS),
                selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
                selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

                ' Reset selection-related variables
                isSelectionReadyToDrag = False
                isInSelectionMode = False ' Exit selection mode after canceling
                Cursor = Cursors.Default
                picGame.Invalidate()
            End If
        ElseIf isInSelectionMode Then
            If isSelecting AndAlso e.Button = MouseButtons.Left Then
                isSelecting = False
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
                picGame.Invalidate()

                If Not selectionRectangle.IsEmpty Then
                    ' Show context menu at mouse position
                    Dim menuPosition As Point = e.Location
                    ctmMenu.Show(picGame, menuPosition)
                Else
                    ' If selection is empty, exit selection mode
                    isInSelectionMode = False
                    Cursor = Cursors.Default
                End If
            End If
        Else
            If e.Button = MouseButtons.Left Then
                If isDraggingItem Then
                    isDraggingItem = False
                    picGame.Capture = False

                    If isMouseInsideGrid Then
                        If highlightedWallX AndAlso draggedItemIsWall AndAlso draggedItemIsVerticalWall Then
                            If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                           highlightedY >= 0 AndAlso highlightedY < verticalWallItems(highlightedX).Length Then
                                ' Place vertical wall item
                                verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                verticalWalls(highlightedX)(highlightedY) = False
                            End If
                        ElseIf highlightedWallY AndAlso draggedItemIsWall AndAlso Not draggedItemIsVerticalWall Then
                            If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                           highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(highlightedX).Length Then
                                ' Place horizontal wall item
                                horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                horizontalWalls(highlightedX)(highlightedY) = False
                            End If
                        ElseIf Not highlightedWallX AndAlso Not highlightedWallY AndAlso Not draggedItemIsWall Then
                            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                           highlightedY >= 0 AndAlso highlightedY < placedItems(highlightedX).Length Then
                                ' Place floor item
                                placedItems(highlightedX)(highlightedY) = draggedItem
                            End If
                        Else
                            ' Allow placement for wall items on the edge
                            If (highlightedX = 0 OrElse highlightedX = SQCOUNTX OrElse highlightedY = 0 OrElse highlightedY = SQCOUNTY) Then
                                If draggedItemIsWall Then
                                    If draggedItemIsVerticalWall Then
                                        verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                        verticalWalls(highlightedX)(highlightedY) = False
                                    Else
                                        horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                        horizontalWalls(highlightedX)(highlightedY) = False
                                    End If
                                End If
                            End If
                        End If
                    Else
                        ' Not inside grid, return item to original location
                        ReturnItemToOriginalLocation()
                    End If
                Else
                    isLeftMouseDown = False
                    If isActionInProgress Then
                        isActionInProgress = False
                        redoStack.Clear()
                    End If
                End If
            ElseIf e.Button = MouseButtons.Right Then
                isRightMouseDown = False
                If isActionInProgress Then
                    isActionInProgress = False
                    redoStack.Clear()
                End If

                If rdoEdge.Checked Then
                    ' Remove edge style at the mouse position
                    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
                    ' Existing logic for right-click when not in edge mode
                    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
            End If
        End If
    End Sub

    Private Sub CaptureSelectionData()
        ' Calculate start and end cell indices based on the 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)

        ' Ensure the indices are within the grid boundaries
        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

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

        ' Prepare arrays with the appropriate sizes
        ' Placed Items and Floors Edge Style
        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) {}
        Next

        ' Vertical Walls and Items
        Dim verticalWallsWidth As Integer = selectionWidth + 1
        Dim verticalWallsHeight As Integer = selectionHeight
        selectionData.verticalWalls = New Boolean(verticalWallsWidth - 1)() {}
        selectionData.verticalWallItems = New PlacedItem(verticalWallsWidth - 1)() {}
        For x As Integer = 0 To verticalWallsWidth - 1
            selectionData.verticalWalls(x) = New Boolean(verticalWallsHeight - 1) {}
            selectionData.verticalWallItems(x) = New PlacedItem(verticalWallsHeight - 1) {}
        Next

        ' Horizontal Walls and Items
        Dim horizontalWallsWidth As Integer = selectionWidth
        Dim horizontalWallsHeight As Integer = selectionHeight + 1
        selectionData.horizontalWalls = New Boolean(horizontalWallsWidth - 1)() {}
        selectionData.horizontalWallItems = New PlacedItem(horizontalWallsWidth - 1)() {}
        For x As Integer = 0 To horizontalWallsWidth - 1
            selectionData.horizontalWalls(x) = New Boolean(horizontalWallsHeight - 1) {}
            selectionData.horizontalWallItems(x) = New PlacedItem(horizontalWallsHeight - 1) {}
        Next

        ' Copy data from the main arrays into selectionData
        ' Placed Items and Floors Edge Style
        For x As Integer = 0 To selectionWidth - 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

        ' Vertical Walls and Items
        For x As Integer = 0 To verticalWallsWidth - 1
            For y As Integer = 0 To verticalWallsHeight - 1
                selectionData.verticalWalls(x)(y) = verticalWalls(startCellX + x)(startCellY + y)
                selectionData.verticalWallItems(x)(y) = verticalWallItems(startCellX + x)(startCellY + y)
            Next
        Next

        ' Horizontal Walls and Items
        For x As Integer = 0 To horizontalWallsWidth - 1
            For y As Integer = 0 To horizontalWallsHeight - 1
                selectionData.horizontalWalls(x)(y) = horizontalWalls(startCellX + x)(startCellY + y)
                selectionData.horizontalWallItems(x)(y) = horizontalWallItems(startCellX + x)(startCellY + y)
            Next
        Next

        ' Store the starting cell of the selection
        selectionStartCell = New Point(startCellX, startCellY)
    End Sub

    Private Sub MoveSelectionDataTo(newCellX As Integer, newCellY As Integer)
        ' Placed Items and Floors Edge Style
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If newCellX + x >= 0 AndAlso newCellX + x < SQCOUNTX AndAlso newCellY + y >= 0 AndAlso newCellY + y < SQCOUNTY Then
                    placedItems(newCellX + x)(newCellY + y) = selectionData.placedItems(x)(y)
                    floorsUseEdgeStyle(newCellX + x)(newCellY + y) = selectionData.floorsUseEdgeStyle(x)(y)
                End If
            Next
        Next

        ' Vertical Walls and Items
        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            For y As Integer = 0 To selectionData.verticalWalls(0).Length - 1
                If newCellX + x >= 0 AndAlso newCellX + x < verticalWalls.Length AndAlso newCellY + y >= 0 AndAlso newCellY + y < verticalWalls(0).Length Then
                    verticalWalls(newCellX + x)(newCellY + y) = selectionData.verticalWalls(x)(y)
                    verticalWallItems(newCellX + x)(newCellY + y) = selectionData.verticalWallItems(x)(y)
                End If
            Next
        Next

        ' Horizontal Walls and Items
        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            For y As Integer = 0 To selectionData.horizontalWalls(0).Length - 1
                If newCellX + x >= 0 AndAlso newCellX + x < horizontalWalls.Length AndAlso newCellY + y >= 0 AndAlso newCellY + y < horizontalWalls(0).Length Then
                    horizontalWalls(newCellX + x)(newCellY + y) = selectionData.horizontalWalls(x)(y)
                    horizontalWallItems(newCellX + x)(newCellY + y) = selectionData.horizontalWallItems(x)(y)
                End If
            Next
        Next
    End Sub

    Private Sub ClearSelectionDataAt(oldCellX As Integer, oldCellY As Integer)
        ' Placed Items and Floors Edge Style
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If oldCellX + x >= 0 AndAlso oldCellX + x < SQCOUNTX AndAlso oldCellY + y >= 0 AndAlso oldCellY + y < SQCOUNTY Then
                    placedItems(oldCellX + x)(oldCellY + y) = Nothing
                    floorsUseEdgeStyle(oldCellX + x)(oldCellY + y) = False
                End If
            Next
        Next

        ' Vertical Walls and Items
        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            For y As Integer = 0 To selectionData.verticalWalls(0).Length - 1
                If oldCellX + x >= 0 AndAlso oldCellX + x < verticalWalls.Length AndAlso oldCellY + y >= 0 AndAlso oldCellY + y < verticalWalls(0).Length Then
                    verticalWalls(oldCellX + x)(oldCellY + y) = True ' Default state
                    verticalWallItems(oldCellX + x)(oldCellY + y) = Nothing
                End If
            Next
        Next

        ' Horizontal Walls and Items
        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            For y As Integer = 0 To selectionData.horizontalWalls(0).Length - 1
                If oldCellX + x >= 0 AndAlso oldCellX + x < horizontalWalls.Length AndAlso oldCellY + y >= 0 AndAlso oldCellY + y < horizontalWalls(0).Length Then
                    horizontalWalls(oldCellX + x)(oldCellY + y) = True ' Default state
                    horizontalWallItems(oldCellX + x)(oldCellY + y) = Nothing
                End If
            Next
        Next
    End Sub

    Private Sub DrawSelectionData(g As Graphics, baseCellX As Integer, baseCellY As Integer)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Apply a semi-transparent color matrix
        Dim colorMatrix As New Imaging.ColorMatrix()
        colorMatrix.Matrix33 = 0.5F ' Set the alpha to 50%
        Dim imgAttributes As New Imaging.ImageAttributes()
        imgAttributes.SetColorMatrix(colorMatrix, Imaging.ColorMatrixFlag.Default, Imaging.ColorAdjustType.Bitmap)

        ' Draw Placed Items
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionData.placedItems(x)(y) IsNot Nothing Then
                    Dim img As Image = GetImageByKey(selectionData.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

    Public Sub UpdateGridSizeUI()
        ' Remove event handlers temporarily to prevent triggering events
        RemoveHandler cboX.SelectedIndexChanged, AddressOf cboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf cboY_SelectedIndexChanged

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

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

        ' Recalculate square sizes
        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

        ' Reattach event handlers
        AddHandler cboX.SelectedIndexChanged, AddressOf cboX_SelectedIndexChanged
        AddHandler cboY.SelectedIndexChanged, AddressOf cboY_SelectedIndexChanged

        ' Invalidate picGame to redraw the grid
        picGame.Invalidate()
    End Sub
#End Region

#Region "Utility and Helper Methods"
    Private Function IsHorizontalWallConnectedToEdge(pt As Point) As Boolean
        ' Horizontal walls are between cells (x, y-1) and (x, y)
        Dim x As Integer = pt.X
        Dim y As Integer = pt.Y

        ' Check if adjacent cells have edge style applied
        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
        ' Vertical walls are between cells (x-1, y) and (x, y)
        Dim x As Integer = pt.X
        Dim y As Integer = pt.Y

        ' Check if adjacent cells have edge style applied
        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)
        ' Ensure SQCOUNTX and SQCOUNTY are greater than zero
        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 Sub DrawGrid(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Draw floor squares
        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
                Else
                    Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                        g.FillRectangle(floorBrush, floorRect)
                    End Using
                End If
            Next
        Next

        ' Draw wall intersections
        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

        ' Draw vertical walls or floor
        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(x).Length Then

                    If verticalWalls(x)(y) Then
                        ' Draw wall
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    Else
                        ' Draw floor
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        ' Draw horizontal walls or floor
        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(x).Length Then

                    If horizontalWalls(x)(y) Then
                        ' Draw wall
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    Else
                        ' Draw floor
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        ' Draw items
        DrawItems(g)
        DrawWallItems(g)

        ' Draw grid numbers (X-axis and Y-axis)
        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)

        ' Adjustments
        Dim columnNumberOffsetY As Single = -20.0F  ' Move column numbers higher
        Dim rowNumberOffsetX As Single = -5.0F      ' Move row numbers to the right

        ' Drawing numbers along the X-axis (top)
        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  ' Adjusted position

            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

        ' Drawing numbers along the Y-axis (left)
        For y As Integer = 0 To SQCOUNTY - 1
            Dim xPos As Single = xOrigin + rowNumberOffsetX  ' Adjusted position
            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

        ' Clean up
        font.Dispose()
        brush.Dispose()
    End Sub
    Friend Sub FillBorderWalls()
        ' Fill vertical walls along the left and right borders
        For y As Integer = 0 To SQCOUNTY - 1
            verticalWalls(0)(y) = True
            verticalWalls(SQCOUNTX)(y) = True
        Next

        ' Fill horizontal walls along the top and bottom borders
        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)

                            ' Draw the item with preserved transparency
                            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)
        ' Draw vertical wall items
        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

                            ' Keep original image width, stretch height
                            Dim newWidth As Integer = img.Width
                            Dim newHeight As Integer = wallHeight

                            ' Center horizontally
                            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

        ' Draw horizontal wall items
        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

                            ' Stretch width, keep original image height
                            Dim newWidth As Integer = wallWidth
                            Dim newHeight As Integer = img.Height

                            ' Center vertically
                            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 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

        If mouseX >= 0 AndAlso mouseY >= 0 Then
            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

            ' Check if cellX and cellY are within grid bounds
            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
        End If
    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

                ' Remove wall items around the cell and place walls
                ' Left vertical wall
                If highlightedX >= 0 AndAlso highlightedX < verticalWalls.Length AndAlso
           verticalWalls(highlightedX) IsNot Nothing AndAlso
           highlightedY >= 0 AndAlso highlightedY < verticalWalls(highlightedX).Length Then
                    verticalWallItems(highlightedX)(highlightedY) = Nothing
                    verticalWalls(highlightedX)(highlightedY) = True
                End If

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

                ' Top horizontal wall
                If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
                    horizontalWallItems(highlightedX)(highlightedY) = Nothing
                    horizontalWalls(highlightedX)(highlightedY) = True
                End If

                ' Bottom horizontal wall
                If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length 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 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)

        ' Update the HatchStyle list selection if Walls radio button is checked
        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)

        ' Update the HatchStyle list selection if Floors radio button is checked
        UpdateSelections()
    End Sub

    Private Sub UpdateSelections()
        ' Update Hatch Style Selection
        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

        ' Update Music Selection
        'If Not String.IsNullOrEmpty(SelectedMusic) Then
        ''    If cboMusic.Items.Contains(SelectedMusic) Then
        '        cboMusic.SelectedItem = SelectedMusic
        '    Else
        '        MessageBox.Show("The music file '" & SelectedMusic & "' is not available.", "Music Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        '        cboMusic.SelectedIndex = -1
        '    End If
        'Else
        '    cboMusic.SelectedIndex = -1 ' No selection
        'End If
    End Sub

    Private Sub AddColorPictureBoxHandlers(parent As Control)
        For Each ctrl As Control In parent.Controls
            If TypeOf ctrl Is PictureBox AndAlso ctrl.Name.StartsWith("col") Then
                AddHandler ctrl.Click, AddressOf ColorPictureBox_Click
            End If
            If ctrl.HasChildren Then
                AddColorPictureBoxHandlers(ctrl)
            End If
        Next
    End Sub

    Private Sub ColorPictureBox_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        Dim pb As PictureBox = CType(sender, PictureBox)
        Dim selectedColor As Color = pb.BackColor

        If pb.Name.StartsWith("col") Then
            Dim numStr As String = pb.Name.Substring(3)
            Dim num As Integer
            If Integer.TryParse(numStr, num) Then
                If num >= 1 AndAlso num <= 48 Then
                    ' Update Color1
                    If rdoFloors.Checked Then
                        floorsColor1 = selectedColor
                    ElseIf rdoWalls.Checked Then
                        wallsColor1 = selectedColor
                    ElseIf rdoEdge.Checked Then
                        wallsColor1 = selectedColor ' For edge, use wall's color
                    End If
                ElseIf num >= 51 AndAlso num <= 98 Then
                    ' Update Color2
                    If rdoFloors.Checked Then
                        floorsColor2 = selectedColor
                    ElseIf rdoWalls.Checked Then
                        wallsColor2 = selectedColor
                    ElseIf rdoEdge.Checked Then
                        wallsColor2 = selectedColor ' For edge, use wall's color
                    End If
                End If
            End If
        End If

        picGame.Invalidate()
    End Sub

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

        isDraggingItem = True
        isActionInProgress = True
        If Not isRestoringState Then SaveStateForUndo()

        ' 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

        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 "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 "picBaddy3"
                Return picBaddy3.Image
            Case "picBaddy4"
                Return picBaddy4.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 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
                If verticalWallItems(highlightedX)(highlightedY) Is Nothing AndAlso
               highlightedX > 0 AndAlso highlightedX < SQCOUNTX Then
                    verticalWalls(highlightedX)(highlightedY) = True
                    picGame.Invalidate()
                End If
            End If
        ElseIf highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
                If horizontalWallItems(highlightedX)(highlightedY) Is Nothing AndAlso
               highlightedY > 0 AndAlso highlightedY < SQCOUNTY Then
                    horizontalWalls(highlightedX)(highlightedY) = True
                    picGame.Invalidate()
                End If
            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
            ' Mouse is outside of grid; do nothing
            Return
        End If

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

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

                    ' Check if the wall is connected to an edge and fill it in if necessary
                    If IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        verticalWalls(highlightedX)(highlightedY) = True
                    End If
                ElseIf removeWalls AndAlso verticalWalls(highlightedX)(highlightedY) Then
                    ' Check if it's an outer wall
                    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 < horizontalWalls.Length AndAlso
       horizontalWalls(highlightedX) IsNot Nothing AndAlso
       highlightedY >= 0 AndAlso highlightedY < horizontalWalls(highlightedX).Length Then

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

                    ' Check if the wall is connected to an edge and fill it in if necessary
                    If IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        horizontalWalls(highlightedX)(highlightedY) = True
                    End If
                ElseIf removeWalls AndAlso horizontalWalls(highlightedX)(highlightedY) Then
                    ' Check if it's an outer wall
                    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(highlightedX).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 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

            ' Ensure music is loaded before restoring state
            LoadMusic()

            ' Restore the state
            isRestoringState = True
            roomData.RestoreState(Me)
            isRestoringState = False

            ' Update selections
            UpdateSelections()
        Catch ex As Exception
            MessageBox.Show("Error loading room: " & ex.Message)
        End Try
    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(";") 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

            ' Update lstHatchstyles selection after loading settings
            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
        ' Save the current room data to a temporary file
        Dim tempFilePath As String = Path.Combine(Path.GetTempPath(), "temp.room")
        SaveRoomToFile(tempFilePath)

        ' Open frmGame and pass the temp file path
        Dim gameForm As New frmGame()
        gameForm.RoomFilePath = tempFilePath
        gameForm.Show()
    End Sub
    ' Helper method to clone jagged arrays
    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
            ' Create a new RoomData object capturing the current state
            Dim currentState As New RoomData(Me)

            ' Serialize the RoomData object to a JSON string using System.Text.Json
            Dim options As New JsonSerializerOptions With {
                .IncludeFields = True,
                .WriteIndented = True ' For readability; optional
            }
            Dim jsonString As String = JsonSerializer.Serialize(currentState, options)

            ' Write the JSON string to the specified file
            File.WriteAllText(filePath, jsonString)
        Catch ex As Exception
            MessageBox.Show("Error saving room: " & ex.Message)
        End Try
    End Sub

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

    Private Sub btnAudio_Click(sender As Object, e As EventArgs) Handles btnAudio.Click
        Using frm As New frmMusic
            If frm.ShowDialog() = DialogResult.OK Then
                ' Get returned values from the music form
                Dim returnedVolume As Integer = frm.VolumeLevel
                Dim returnedGain As Integer = frm.GainLevel
                Dim audioFile As String = frm.AudioFile

                ' Use only the filename without extension for SelectedMusic
                Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(audioFile)

                ' Update the SelectedMusic string with the new values
                SelectedMusic = String.Format("{0}, V:{1}, G:{2}", audioFile, returnedVolume, returnedGain)

                ' Update the label to display the selected music information
                lblAudio.Text = SelectedMusic
            End If
        End Using
    End Sub
#End Region
End Class

Option Explicit On
Option Strict On

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.IO
Imports NAudio.Wave
Imports NAudio.Wave.SampleProviders
Imports System.Threading.Tasks

Public Class frmMusic
    ' Define fields for audio processing
    Private ReadOnly projectDir As String = GetProjectDirectory()
    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Music")
    Private ReadOnly IconsFolder As String = Path.Combine(projectDir, "Data", "Icons")

    Private waveOut As WaveOutEvent
    Private audioFileReader As AudioFileReader
    Private sampleChannel As SampleChannel

    ' Properties to return volume and gain levels
    Public Property AudioFile As String
    Public Property VolumeLevel As Integer
    Public Property GainLevel As Integer

    ' Fields for the moving line
    Private playbackTimer As New Timer()
    Private currentPosition As Double = 0
    Private totalDuration As Double = 0
    Private ReadOnly darkGreenPen As New Pen(Color.DarkGreen, 2)

    Private isPlaying As Boolean = False
    Private isRepeating As Boolean = False
    Private Function GetProjectDirectory() As String
        Dim dir As DirectoryInfo = New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function
    Private Sub frmMusic_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))

        ' Disable the "new row" at the bottom and allow a vertical scrollbar
        dgSound.AllowUserToAddRows = False
        dgSound.ScrollBars = ScrollBars.Vertical

        ' Initialize DataGridView columns
        dgSound.Columns.Clear()
        dgSound.Columns.Add("Filename", "Filename")
        dgSound.Columns.Add("Length", "Length")
        dgSound.Columns.Add("Type", "Type")

        ' Calculate the total available width for columns (account for vertical scrollbar width)
        Dim scrollbarWidth As Integer = SystemInformation.VerticalScrollBarWidth
        Dim availableWidth As Integer = dgSound.ClientSize.Width - scrollbarWidth

        ' Set column widths based on desired percentages
        dgSound.Columns("Filename").Width = CInt(availableWidth * 0.6)
        dgSound.Columns("Length").Width = CInt(availableWidth * 0.2)
        dgSound.Columns("Type").Width = CInt(availableWidth * 0.2)
        dgSound.RowTemplate.Height = 20
        dgSound.AllowUserToResizeRows = False

        ' Load music files into DataGridView
        LoadMusicFiles()

        ' Set up other controls
        TBVolume.Minimum = 0
        TBVolume.Maximum = 100
        TBVolume.Value = 50

        TBGain.Minimum = 0
        TBGain.Maximum = 30
        TBGain.Value = 10

        UpdateVolumeLabel()
        UpdateGainLabel()

        DrawCenterLine() ' Initialize with a center line

        playbackTimer.Interval = 500 ' Update every 500ms
        AddHandler playbackTimer.Tick, AddressOf PlaybackTimer_Tick

    End Sub

    Private Sub LoadMusicFiles()
        Dim allowedExtensions As String() = {".mp3", ".wav"}
        dgSound.Rows.Clear()

        If Directory.Exists(musicFolder) Then
            Dim musicFiles = Directory.GetFiles(musicFolder)
            For Each file In musicFiles
                If allowedExtensions.Contains(Path.GetExtension(file).ToLower()) Then
                    Dim fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file)
                    Dim fileType = Path.GetExtension(file).Replace(".", "").ToUpper()

                    Using reader As New AudioFileReader(file)
                        Dim duration As String = $"{reader.TotalTime.Minutes:D2}:{reader.TotalTime.Seconds:D2}"
                        dgSound.Rows.Add(fileNameWithoutExtension, duration, fileType)
                    End Using
                End If
            Next
        Else
            MessageBox.Show("Music folder doesn't exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

    Private Sub dgSound_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgSound.CellClick
        If e.RowIndex >= 0 AndAlso e.RowIndex < dgSound.Rows.Count Then
            Dim selectedRow = dgSound.Rows(e.RowIndex)
            Dim fileNameWithoutExtension = selectedRow.Cells("Filename").Value.ToString()
            Dim fileType = selectedRow.Cells("Type").Value.ToString().ToLower()

            ' Call the method with the correct parameters
            LoadAndPlayAudio(fileNameWithoutExtension, fileType)

            ' Update buttons
            btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
            isPlaying = True
            btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            btnStop.BackColor = DefaultBackColor
            btnStop.BorderStyle = BorderStyle.None
            If isRepeating Then
                btnRepeat.BackColor = Color.LightGreen
                btnRepeat.BorderStyle = BorderStyle.Fixed3D
            Else
                btnRepeat.BackColor = DefaultBackColor
                btnRepeat.BorderStyle = BorderStyle.None
            End If
        End If
    End Sub

    Private Sub LoadAndPlayAudio(selectedFileName As String, fileType As String)
        playbackTimer.Stop()

        Try
            ' Ensure previous audio resources are disposed of safely
            If waveOut IsNot Nothing Then
                RemoveHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped
                waveOut.Stop()
                waveOut.Dispose()
                waveOut = Nothing
            End If

            If audioFileReader IsNot Nothing Then
                audioFileReader.Dispose()
                audioFileReader = Nothing
            End If

            ' Construct the complete file path
            Dim completeFilePath As String = Path.Combine(musicFolder, selectedFileName & "." & fileType)

            If File.Exists(completeFilePath) Then
                audioFileReader = New AudioFileReader(completeFilePath)
                sampleChannel = New SampleChannel(audioFileReader, True)

                ' Apply gain
                Dim gainMultiplier As Single = TBGain.Value / 10.0F
                sampleChannel.Volume = gainMultiplier

                ' Initialize and start playback
                waveOut = New WaveOutEvent()
                waveOut.Init(New SampleToWaveProvider(sampleChannel))
                waveOut.Volume = TBVolume.Value / 100.0F

                totalDuration = audioFileReader.TotalTime.TotalSeconds
                currentPosition = 0 ' Reset

                ' Draw initial waveform
                DrawWaveform(completeFilePath, gainMultiplier)
                AudioFile = selectedFileName & "." & fileType

                AddHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped

                waveOut.Play()
                playbackTimer.Start()
            Else
                MessageBox.Show("Selected audio file not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                DrawCenterLine()
            End If
        Catch ex As Exception
            MessageBox.Show($"Error loading audio: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub
    ' Ensure only one version of this method exists
    Private Sub WaveOut_PlaybackStopped(sender As Object, e As StoppedEventArgs)
        If Me.IsHandleCreated AndAlso Not Me.IsDisposed AndAlso audioFileReader IsNot Nothing Then
            ' Determine if track finished playing normally
            If Math.Abs(audioFileReader.CurrentTime.TotalSeconds - audioFileReader.TotalTime.TotalSeconds) < 0.1 Then
                currentPosition = totalDuration
                playbackTimer.Stop()
                UpdateTimerLabel()

                If isRepeating Then
                    Try
                        ' Reset and play again if repeating
                        audioFileReader.CurrentTime = TimeSpan.Zero
                        currentPosition = 0
                        waveOut.Play()
                        playbackTimer.Start()
                        btnPlayPause.BorderStyle = BorderStyle.Fixed3D
                    Catch ex As Exception
                        MessageBox.Show($"Error repeating audio: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    End Try
                Else
                    DrawCenterLine()
                    btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
                    isPlaying = False
                    btnPlayPause.BorderStyle = BorderStyle.FixedSingle
                End If
            End If
        End If
    End Sub

    Private Sub TBVolume_Scroll(sender As Object, e As EventArgs) Handles TBVolume.Scroll
        If waveOut IsNot Nothing Then
            waveOut.Volume = TBVolume.Value / 100.0F
        End If
        UpdateVolumeLabel()
    End Sub

    Private Sub TBGain_Scroll(sender As Object, e As EventArgs) Handles TBGain.Scroll
        If sampleChannel IsNot Nothing Then
            Dim gainMultiplier As Single = TBGain.Value / 10.0F
            sampleChannel.Volume = gainMultiplier
            DrawWaveform(Path.Combine(musicFolder, AudioFile), gainMultiplier)
        End If
        UpdateGainLabel()
    End Sub

    Private Sub UpdateVolumeLabel()
        lblVolume.Text = "Volume: " & TBVolume.Value
    End Sub

    Private Sub UpdateGainLabel()
        lblGain.Text = "Gain: " & TBGain.Value
    End Sub

    Private Async Sub DrawWaveform(filePath As String, gainMultiplier As Single)
        If Not Me.IsHandleCreated OrElse picWav.IsDisposed OrElse picWav.Width = 0 OrElse picWav.Height = 0 Then Exit Sub

        Await Task.Run(Sub()
                           Try
                               Using reader As New AudioFileReader(filePath)
                                   Dim buffer As Single() = New Single(16383) {}
                                   Dim allSamples As New List(Of Single)()
                                   Dim samplesRead As Integer

                                   Do
                                       samplesRead = reader.Read(buffer, 0, buffer.Length)
                                       allSamples.AddRange(buffer.Take(samplesRead))
                                   Loop While samplesRead > 0

                                   Dim bmp As New Bitmap(picWav.Width, picWav.Height)
                                   Using g As Graphics = Graphics.FromImage(bmp)
                                       g.Clear(Color.Black)
                                       Dim midY As Integer = picWav.Height \ 2
                                       Dim samplesPerPixel As Integer = Math.Max(1, allSamples.Count \ (picWav.Width * reader.WaveFormat.Channels))

                                       For x As Integer = 0 To picWav.Width - 1
                                           Dim startIndex As Integer = x * samplesPerPixel * reader.WaveFormat.Channels
                                           Dim maxAmplitude As Single = allSamples.Skip(startIndex).Take(samplesPerPixel).Max(Function(s) Math.Abs(s))
                                           maxAmplitude *= gainMultiplier
                                           maxAmplitude = Math.Min(maxAmplitude, 1.0F)

                                           Dim amplitudeHeight As Integer = CInt(maxAmplitude * picWav.Height / 2)
                                           g.DrawLine(Pens.White, x, midY - amplitudeHeight, x, midY + amplitudeHeight)
                                       Next

                                       ' Draw the green line
                                       Dim greenLineX As Integer = CInt((currentPosition / totalDuration) * picWav.Width)
                                       g.DrawLine(darkGreenPen, greenLineX, 0, greenLineX, picWav.Height)
                                   End Using

                                   If Me.IsHandleCreated Then
                                       Me.Invoke(Sub()
                                                     If Not picWav.IsDisposed Then
                                                         picWav.Image?.Dispose()
                                                         picWav.Image = bmp
                                                     End If
                                                 End Sub)
                                   End If
                               End Using
                           Catch ex As Exception
                               Console.WriteLine($"Drawing waveform error: {ex.Message}")
                           End Try
                       End Sub)
    End Sub

    Private Sub DrawCenterLine()
        If picWav.Width = 0 OrElse picWav.Height = 0 Then Exit Sub

        Dim bmp As New Bitmap(picWav.Width, picWav.Height)
        Using g As Graphics = Graphics.FromImage(bmp)
            g.Clear(Color.Black)
            Dim midY As Integer = CInt(picWav.Height / 2)
            g.DrawLine(Pens.White, 0, midY, picWav.Width, midY)
        End Using
        picWav.Image = bmp
    End Sub

    Private Sub frmMusic_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        ' Stop timer and clean up resources
        playbackTimer.Stop()
        RemoveHandler playbackTimer.Tick, AddressOf PlaybackTimer_Tick

        Try
            If waveOut IsNot Nothing Then
                RemoveHandler waveOut.PlaybackStopped, AddressOf WaveOut_PlaybackStopped
                waveOut.Stop()
                waveOut.Dispose()
                waveOut = Nothing
            End If

            If audioFileReader IsNot Nothing Then
                audioFileReader.Dispose()
                audioFileReader = Nothing
            End If
        Catch ex As Exception
            Console.WriteLine($"Error during form closing: {ex.Message}")
        End Try

        VolumeLevel = TBVolume.Value
        GainLevel = TBGain.Value
        Me.DialogResult = DialogResult.OK
    End Sub
    Private Sub UpdateTimerLabel()
        If audioFileReader IsNot Nothing Then
            Dim elapsed As TimeSpan = audioFileReader.CurrentTime
            Dim total As TimeSpan = audioFileReader.TotalTime
            lblTimer.Text = String.Format("{0:D2}:{1:D2} / {2:D2}:{3:D2}", elapsed.Minutes, elapsed.Seconds, total.Minutes, total.Seconds)
        Else
            lblTimer.Text = "00:00 / 00:00"
        End If
    End Sub

    Private Sub PlaybackTimer_Tick(sender As Object, e As EventArgs)
        If audioFileReader IsNot Nothing AndAlso waveOut IsNot Nothing AndAlso waveOut.PlaybackState = PlaybackState.Playing Then
            currentPosition = audioFileReader.CurrentTime.TotalSeconds
            UpdateTimerLabel()

            ' Ensure the form and controls are still valid
            If Me.IsHandleCreated Then
                Me.Invoke(Sub() DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F))
            End If
        End If
    End Sub
    Private Sub picWav_MouseDown(sender As Object, e As MouseEventArgs) Handles picWav.MouseDown
        ' Ensure audio is loaded and playing
        If audioFileReader Is Nothing OrElse waveOut Is Nothing Then Exit Sub

        ' Calculate the new position based on the click
        Dim newPositionRatio As Double = e.X / picWav.Width
        newPositionRatio = Math.Max(0, Math.Min(newPositionRatio, 1)) ' Clamp between 0 and 1
        currentPosition = totalDuration * newPositionRatio

        ' Set the audio playback position
        audioFileReader.CurrentTime = TimeSpan.FromSeconds(currentPosition)

        ' Redraw the waveform with the new position
        If Not String.IsNullOrEmpty(AudioFile) Then
            DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F)
        End If

        ' Ensure the playback timer continues
        playbackTimer.Start()

        ' Restart playback from the new position
        If waveOut.PlaybackState <> PlaybackState.Playing Then
            waveOut.Play()
        End If
    End Sub

    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        If waveOut IsNot Nothing Then
            waveOut.Stop()
            audioFileReader.CurrentTime = TimeSpan.Zero
            currentPosition = 0
            playbackTimer.Stop()
            UpdateTimerLabel()
            DrawWaveform(Path.Combine(musicFolder, AudioFile), TBGain.Value / 10.0F)
            btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
            isPlaying = False
            btnPlayPause.BorderStyle = BorderStyle.FixedSingle

            ' Dispose and reinitialize waveOut and audioFileReader
            waveOut.Dispose()
            waveOut = Nothing
            audioFileReader.Dispose()
            audioFileReader = Nothing
        End If

        ' Reset back colors
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.Fixed3D
        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If

        ' Start the timer to change the border style back to FixedSingle after a short delay
        borderStyleTimer.Interval = 100 ' 100 milliseconds (adjust as needed)
        borderStyleTimer.Start()
    End Sub

    Private Sub borderStyleTimer_Tick(sender As Object, e As EventArgs) Handles borderStyleTimer.Tick
        ' Change the border style back to FixedSingle
        btnStop.BorderStyle = BorderStyle.FixedSingle

        ' Stop the timer
        borderStyleTimer.Stop()
    End Sub

    Private Sub frmMusic_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None

        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If

        If isPlaying Then
            btnPlayPause.BackColor = DefaultBackColor
            btnPlayPause.BorderStyle = BorderStyle.Fixed3D
        Else
            btnPlayPause.BackColor = DefaultBackColor
            btnPlayPause.BorderStyle = BorderStyle.None
        End If
    End Sub

    Private Sub btnRepeat_Click(sender As Object, e As EventArgs) Handles btnRepeat.Click
        btnRepeat.BackColor = Color.LightGray
        If isRepeating Then
            isRepeating = False
            btnRepeat.BorderStyle = BorderStyle.FixedSingle
        Else
            isRepeating = True
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        End If

        ' Update back colors for other buttons
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None
        If isPlaying Then
            btnPlayPause.BorderStyle = BorderStyle.Fixed3D
        Else
            btnPlayPause.BorderStyle = BorderStyle.FixedSingle
        End If
    End Sub

    Private Sub btnPlayPause_Click(sender As Object, e As EventArgs) Handles btnPlayPause.Click
        If waveOut IsNot Nothing Then
            If waveOut.PlaybackState = PlaybackState.Playing Then
                waveOut.Pause()
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Play.png"))
                isPlaying = False
                btnPlayPause.BorderStyle = BorderStyle.FixedSingle
            ElseIf waveOut.PlaybackState = PlaybackState.Paused Then
                waveOut.Play()
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
                isPlaying = True
                btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            End If
        Else
            ' Start playing the selected track if audio is loaded for the first time
            Dim selectedRow As DataGridViewRow = dgSound.CurrentRow
            If selectedRow IsNot Nothing Then
                Dim fileNameWithoutExtension = selectedRow.Cells("Filename").Value.ToString()
                Dim fileType = selectedRow.Cells("Type").Value.ToString().ToLower()
                LoadAndPlayAudio(fileNameWithoutExtension, fileType)
                btnPlayPause.Image = Image.FromFile(Path.Combine(IconsFolder, "Pause.png"))
                isPlaying = True
                btnPlayPause.BorderStyle = BorderStyle.Fixed3D
            End If
        End If

        ' Update back colors for other buttons
        btnStop.BackColor = DefaultBackColor
        btnStop.BorderStyle = BorderStyle.None
        If isRepeating Then
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.Fixed3D
        Else
            btnRepeat.BackColor = DefaultBackColor
            btnRepeat.BorderStyle = BorderStyle.None
        End If
    End Sub

    Private Sub btnStop_MouseEnter(sender As Object, e As EventArgs) Handles btnStop.MouseEnter, btnPlayPause.MouseEnter, btnRepeat.MouseEnter
        Dim btn As PictureBox = CType(sender, PictureBox)
        btn.BackColor = Color.LightGray
        'If btn Is btnRepeat And isRepeating Then Exit Sub

        btn.BorderStyle = BorderStyle.FixedSingle
    End Sub

End Class
Option Explicit On
Option Strict On

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Text.Json
Imports System.Text.Json.Serialization

Public Class RoomData
    Public Property GridSizeX As Integer
    Public Property GridSizeY As Integer

    ' Properties to save colors and styles
    Public Property wallsHatchStyleValue As Integer
    Public Property wallsColor1Argb As Integer
    Public Property wallsColor2Argb As Integer
    Public Property floorsHatchStyleValue As Integer
    Public Property floorsColor1Argb As Integer
    Public Property floorsColor2Argb As Integer

    ' Arrays to save wall states
    Public Property verticalWalls As Boolean()()
    Public Property horizontalWalls As Boolean()()

    ' Arrays to save placed items
    Public Property placedItems As PlacedItemData()()
    Public Property verticalWallItems As PlacedItemData()()
    Public Property horizontalWallItems As PlacedItemData()()

    ' Arrays to save floor edge styles
    Public Property floorsUseEdgeStyle As Boolean()()
    ' Property to save the selected music
    Public Property SelectedMusic As String

    ' Property to save the level name
    Public Property LevelName As String

    ' Parameterless constructor for deserialization
    Public Sub New()
    End Sub

    ' Constructor: captures the current state of the frmDesigner
    Public Sub New(roomView As frmDesigner)
        ' Save colors and styles as integers
        wallsHatchStyleValue = CInt(roomView.wallsHatchStyle)
        wallsColor1Argb = roomView.wallsColor1.ToArgb()
        wallsColor2Argb = roomView.wallsColor2.ToArgb()
        floorsHatchStyleValue = CInt(roomView.floorsHatchStyle)
        floorsColor1Argb = roomView.floorsColor1.ToArgb()
        floorsColor2Argb = roomView.floorsColor2.ToArgb()

        ' Save grid size
        GridSizeX = roomView.SQCOUNTX
        GridSizeY = roomView.SQCOUNTY

        ' Save wall states
        verticalWalls = CloneJaggedArray(roomView.verticalWalls)
        horizontalWalls = CloneJaggedArray(roomView.horizontalWalls)

        ' Save placed items
        placedItems = CopyPlacedItems(roomView.placedItems)
        verticalWallItems = CopyPlacedItems(roomView.verticalWallItems)
        horizontalWallItems = CopyPlacedItems(roomView.horizontalWallItems)

        ' Save floorsUseEdgeStyle
        floorsUseEdgeStyle = CloneJaggedArray(roomView.floorsUseEdgeStyle)

        ' Save the selected music
        SelectedMusic = roomView.SelectedMusic

        ' Save the level name
        LevelName = roomView.txtName.Text


    End Sub

    ' Helper method to clone jagged arrays
    Private Function CloneJaggedArray(Of T)(sourceArray As T()()) As T()()
        If sourceArray Is Nothing Then Return Nothing
        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)
            Else
                newArray(i) = Nothing
            End If
        Next
        Return newArray
    End Function

    ' Helper method to copy placed items
    Private Function CopyPlacedItems(items As PlacedItem()()) As PlacedItemData()()
        If items Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = items.Length
        Dim copiedItems As PlacedItemData()() = New PlacedItemData(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If items(x) IsNot Nothing Then
                Dim lengthInner As Integer = items(x).Length
                copiedItems(x) = New PlacedItemData(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If items(x)(y) IsNot Nothing Then
                        copiedItems(x)(y) = New PlacedItemData With {.ImageKey = items(x)(y).ImageKey}
                    Else
                        copiedItems(x)(y) = Nothing
                    End If
                Next
            Else
                copiedItems(x) = Nothing
            End If
        Next
        Return copiedItems
    End Function

    ' Restore the saved state back into the frmDesigner
    Public Sub RestoreState(roomView As frmDesigner)
        ' Restore the grid size
        Dim oldSQCOUNTX As Integer = roomView.SQCOUNTX
        Dim oldSQCOUNTY As Integer = roomView.SQCOUNTY
        roomView.SQCOUNTX = GridSizeX
        roomView.SQCOUNTY = GridSizeY

        ' Resize arrays to match the saved grid size and preserve data
        roomView.ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, roomView.SQCOUNTX, roomView.SQCOUNTY)

        ' Restore colors and styles from stored values
        roomView.wallsHatchStyle = CType(wallsHatchStyleValue, HatchStyle)
        roomView.wallsColor1 = Color.FromArgb(wallsColor1Argb)
        roomView.wallsColor2 = Color.FromArgb(wallsColor2Argb)
        roomView.floorsHatchStyle = CType(floorsHatchStyleValue, HatchStyle)
        roomView.floorsColor1 = Color.FromArgb(floorsColor1Argb)
        roomView.floorsColor2 = Color.FromArgb(floorsColor2Argb)

        ' Restore wall states
        roomView.verticalWalls = CloneJaggedArray(verticalWalls)
        roomView.horizontalWalls = CloneJaggedArray(horizontalWalls)

        ' Restore placed items
        roomView.placedItems = RestorePlacedItems(placedItems)
        roomView.verticalWallItems = RestorePlacedItems(verticalWallItems)
        roomView.horizontalWallItems = RestorePlacedItems(horizontalWallItems)

        ' Restore floorsUseEdgeStyle
        roomView.floorsUseEdgeStyle = CloneJaggedArray(floorsUseEdgeStyle)

        ' Restore the selected music
        roomView.SelectedMusic = SelectedMusic
        roomView.lblAudio.Text = SelectedMusic
        ' Restore the level name
        roomView.txtName.Text = LevelName

        ' Update grid size-related UI elements
        roomView.UpdateGridSizeUI()

        ' Invalidate the drawing area to redraw the grid
        roomView.picGame.Invalidate()
    End Sub

    ' Helper method to restore placed items
    Private Function RestorePlacedItems(data As PlacedItemData()()) As PlacedItem()()
        If data Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = data.Length
        Dim restoredItems As PlacedItem()() = New PlacedItem(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If data(x) IsNot Nothing Then
                Dim lengthInner As Integer = data(x).Length
                restoredItems(x) = New PlacedItem(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If data(x)(y) IsNot Nothing Then
                        restoredItems(x)(y) = New PlacedItem With {.ImageKey = data(x)(y).ImageKey}
                    Else
                        restoredItems(x)(y) = Nothing
                    End If
                Next
            Else
                restoredItems(x) = Nothing
            End If
        Next
        Return restoredItems
    End Function
End Class

' PlacedItemData class used for serialization/deserialization
Public Class PlacedItemData
    Public Property ImageKey As String
End Class

' PlacedItem class represents an item placed on the grid
Public Class PlacedItem
    Public Property ImageKey As String
End Class

Public Class SimpleRoomData
    ' Existing properties...
    Public WallsHatchStyleValue As Integer
    Public WallsColor1Argb As Integer
    Public WallsColor2Argb As Integer
    Public FloorsHatchStyleValue As Integer
    Public FloorsColor1Argb As Integer
    Public FloorsColor2Argb As Integer
    Public VerticalWalls()() As Boolean
    Public HorizontalWalls()() As Boolean
    Public placedItems()() As PlacedItemData
    Public verticalWallItems()() As PlacedItemData
    Public horizontalWallItems()() As PlacedItemData
    Public FloorsUseEdgeStyle()() As Boolean

    ' Add these new properties:
    Public LevelName As String
    Public SelectedMusic As String
End Class
Imports NAudio.Wave
Imports NAudio.Wave.SampleProviders
Imports System.IO

Public Class MusicManager
    Private musicWaveOut As WaveOutEvent
    Private musicReader As AudioFileReader

    Public Sub PlaySelectedMusic(musicFilePath As String, volumeLevel As Single, gainLevel As Single)
        StopMusic() ' Stop any currently playing music

        If Not String.IsNullOrEmpty(musicFilePath) AndAlso File.Exists(musicFilePath) Then
            Try
                musicReader = New AudioFileReader(musicFilePath)
                Dim loopingProvider As New LoopStream(musicReader)

                musicWaveOut = New WaveOutEvent()
                musicWaveOut.Init(loopingProvider)

                musicWaveOut.Volume = volumeLevel ' Volume for music only
                musicReader.Volume = gainLevel     ' Gain for music only

                musicWaveOut.Play()
            Catch ex As Exception
                MessageBox.Show("Error playing music: " & ex.Message)
            End Try
        Else
            MessageBox.Show("Selected music file not found: " & musicFilePath)
        End If
    End Sub

    Public Sub StopMusic()
        If musicWaveOut IsNot Nothing Then
            musicWaveOut.Stop()
            musicWaveOut.Dispose()
            musicWaveOut = Nothing
        End If

        If musicReader IsNot Nothing Then
            musicReader.Dispose()
            musicReader = Nothing
        End If
    End Sub

    Private Class LoopStream
        Inherits WaveStream

        Private ReadOnly sourceStream As WaveStream

        Public Sub New(ByVal sourceStream As WaveStream)
            Me.sourceStream = sourceStream
        End Sub

        Public Overrides ReadOnly Property WaveFormat() As WaveFormat
            Get
                Return sourceStream.WaveFormat
            End Get
        End Property

        Public Overrides ReadOnly Property Length() As Long
            Get
                Return sourceStream.Length
            End Get
        End Property

        Public Overrides Property Position() As Long
            Get
                Return sourceStream.Position
            End Get
            Set(ByVal value As Long)
                sourceStream.Position = value
            End Set
        End Property

        Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer
            Dim totalBytesRead As Integer = 0

            While totalBytesRead < count
                Dim bytesRead As Integer = sourceStream.Read(buffer, offset + totalBytesRead, count - totalBytesRead)
                If bytesRead = 0 Then
                    ' Loop back to the beginning
                    sourceStream.Position = 0
                Else
                    totalBytesRead += bytesRead
                End If
            End While

            Return totalBytesRead
        End Function
    End Class
End Class
Imports NAudio.Wave
Imports NAudio.Wave.SampleProviders
Imports System.IO

Public Class SFXManager
    Private soundEffectFiles As New Dictionary(Of String, String)
    Private soundEffectMixer As MixingSampleProvider
    Private soundEffectWaveOut As WaveOutEvent

    Public Sub New(soundEffectsFolder As String)
        InitializeSoundEffects(soundEffectsFolder)
    End Sub

    Private Sub InitializeSoundEffects(soundEffectsFolder As String)
        Try
            soundEffectFiles("Break Lazer") = Path.Combine(soundEffectsFolder, "Break Lazer.wav")
            soundEffectFiles("Coin") = Path.Combine(soundEffectsFolder, "Coin.wav")
            soundEffectFiles("Finish") = Path.Combine(soundEffectsFolder, "Finish.wav")
            soundEffectFiles("Move") = Path.Combine(soundEffectsFolder, "Move.wav")
            soundEffectFiles("Push Block") = Path.Combine(soundEffectsFolder, "Push Block.wav")
            soundEffectFiles("Warp") = Path.Combine(soundEffectsFolder, "Warp.wav")
            soundEffectFiles("Key") = Path.Combine(soundEffectsFolder, "Key.wav")
            soundEffectFiles("Door Open") = Path.Combine(soundEffectsFolder, "Door Open.wav")
            soundEffectFiles("Push Ball") = Path.Combine(soundEffectsFolder, "Push Ball.wav")
            soundEffectFiles("Flipper") = Path.Combine(soundEffectsFolder, "Flipper.wav")
            soundEffectFiles("Block Stop") = Path.Combine(soundEffectsFolder, "BlockStop.wav")

            ' Initialize the MixingSampleProvider and WaveOutEvent for sound effects
            soundEffectMixer = New MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(44100, 2))
            soundEffectMixer.ReadFully = True
            soundEffectWaveOut = New WaveOutEvent()
            soundEffectWaveOut.Init(soundEffectMixer)
            soundEffectWaveOut.Play()
        Catch ex As Exception
            MessageBox.Show("Error initializing sound effects: " & ex.Message)
        End Try
    End Sub

    Public Sub PlaySoundEffect(soundKey As String)
        If soundEffectFiles.ContainsKey(soundKey) Then
            Dim soundFilePath As String = soundEffectFiles(soundKey)
            If File.Exists(soundFilePath) Then
                Try
                    Dim reader As New AudioFileReader(soundFilePath)
                    reader.Volume = 1.0F ' Set constant volume for sound effects
                    soundEffectMixer.AddMixerInput(DirectCast(reader, ISampleProvider))
                Catch ex As Exception
                    ' Handle any errors here (e.g., sound file not found, can't play)
                End Try
            End If
        End If
    End Sub
    Public Sub StopSoundEffects()
        ' Stop sound effects playback if necessary (implement specific logic if needed).
        soundEffectWaveOut.Stop()
    End Sub
End Class
Imports System.IO
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel

Public Class frmGame

#Region "Variables"
    Private movingObjects As New List(Of MovingObject)()
    Private movingObjectsTimer As Timer

    Private CollectedCoins As Integer = 0
    Private playerX As Integer
    Private playerY As Integer
    Private hasKey As Boolean = False
    Private justWarped As Boolean = False
    Public Property RoomFilePath As String
    Private SelectedMusic As String

    Private Const INDENT As Integer = 20
    Private Const WALL_THICKNESS As Integer = 10

    Private SQCOUNTX As Integer
    Private SQCOUNTY As Integer

    Private SQSizeX As Integer
    Private SQSizeY As Integer

    Private wallsHatchStyle As HatchStyle
    Private wallsColor1 As Color
    Private wallsColor2 As Color
    Private floorsHatchStyle As HatchStyle
    Private floorsColor1 As Color
    Private floorsColor2 As Color

    Private verticalWalls()() As Boolean
    Private horizontalWalls()() As Boolean
    Private placedItems()() As String
    Private verticalWallItems()() As String
    Private horizontalWallItems()() As String
    Private floorsUseEdgeStyle()() As Boolean

    Private imageDictionary As New Dictionary(Of String, Image)
    Private animatedImages As New List(Of Image)

    Private animationTimer As Timer
    Private isRoomDataLoaded As Boolean = False

    Private ReadOnly projectDir As String = GetProjectDirectory()
    Private ReadOnly ImagesFolder As String = Path.Combine(projectDir, "Data", "Images")
    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Music")
    Private ReadOnly SFXFolder As String = Path.Combine(projectDir, "Data", "Sound Effects")

    Private musicManager As New MusicManager()
    Private sfxManager As New SFXManager(SFXFolder)

    Private Class MovingObject
        Public Property X As Integer
        Public Property Y As Integer
        Public Property DeltaX As Integer
        Public Property DeltaY As Integer
        Public Property IsBall As Boolean
        Public Property IsBlock As Boolean
        Public Property MoveInterval As Integer
        Public Property NextMoveTime As DateTime
    End Class
#End Region

#Region "Movement"
    Private Sub FindPlayerStartPosition()
        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
                    Dim imageKey As String = placedItems(x)(y)
                    If imageKey = "picStart" Then
                        playerX = x
                        playerY = y
                        placedItems(x)(y) = Nothing
                        Exit Sub
                    End If
                Next
            End If
        Next
    End Sub

    Private Sub frmGame_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
        Dim moved As Boolean = False
        Select Case e.KeyCode
            Case Keys.Up, Keys.P
                moved = AttemptMove("Up")
            Case Keys.Down, Keys.L
                moved = AttemptMove("Down")
            Case Keys.Left, Keys.Z
                moved = AttemptMove("Left")
            Case Keys.Right, Keys.X
                moved = AttemptMove("Right")
        End Select
        If moved Then
            picTest.Invalidate()
        End If
    End Sub

    Private Function AttemptMove(direction As String) As Boolean
        Dim deltaX As Integer = 0
        Dim deltaY As Integer = 0
        Select Case direction
            Case "Up"
                deltaY = -1
            Case "Down"
                deltaY = 1
            Case "Left"
                deltaX = -1
            Case "Right"
                deltaX = 1
        End Select

        Dim newX As Integer = playerX + deltaX
        Dim newY As Integer = playerY + deltaY

        If newX < 0 OrElse newX >= SQCOUNTX OrElse newY < 0 OrElse newY >= SQCOUNTY Then
            Return False
        End If

        If Not CanMoveTo(newX, newY, deltaX, deltaY) Then
            Return False
        End If

        Dim targetItem As String = placedItems(newX)(newY)

        If String.IsNullOrEmpty(targetItem) Then
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Move")
            CheckForWarp()
            Return True
        ElseIf targetItem = "picBlock1" Then
            Dim blockMoved As Boolean = AttemptPushBlock(newX, newY, deltaX, deltaY)
            If blockMoved Then
                playerX = newX
                playerY = newY
                sfxManager.PlaySoundEffect("Move")
                CheckForWarp()
                Return True
            Else
                Return False
            End If
        ElseIf targetItem = "picBlockStopper1" Then
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Move")
            CheckForWarp()
            Return True
        ElseIf targetItem = "picBall" Then
            Dim ballMoved As Boolean = AttemptPushBall(newX, newY, deltaX, deltaY)
            If ballMoved Then
                playerX = newX
                playerY = newY
                sfxManager.PlaySoundEffect("Move")
                CheckForWarp()
                Return True
            Else
                Return False
            End If
        ElseIf targetItem = "picCoin" Then
            CollectedCoins += 1
            placedItems(newX)(newY) = Nothing
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Coin")
            sfxManager.PlaySoundEffect("Move")
            CheckForWarp()
            Return True
        ElseIf targetItem = "picKey" Then
            hasKey = True
            placedItems(newX)(newY) = Nothing
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Key")
            sfxManager.PlaySoundEffect("Move")
            CheckForWarp()
            Return True
        ElseIf targetItem = "picGate" Then
            If hasKey Then
                placedItems(newX)(newY) = Nothing
                playerX = newX
                playerY = newY
                sfxManager.PlaySoundEffect("Door Open")
                sfxManager.PlaySoundEffect("Move")
                CheckForWarp()
                Return True
            Else
                Return False
            End If
        ElseIf targetItem = "picFinish" Then
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Move")
            GameWon()
            Return True
        ElseIf targetItem = "picWarp" Then
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Move")
            HandleWarp("picWarp")
            Return True
        ElseIf targetItem = "picWarp2" Then
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Move")
            HandleWarp("picWarp2")
            Return True
        Else
            Return False
        End If
    End Function

    Private Sub CheckForWarp()
        Dim currentItem As String = placedItems(playerX)(playerY)
        If currentItem = "picWarp" AndAlso Not justWarped Then
            HandleWarp("picWarp")
        ElseIf currentItem = "picWarp2" AndAlso Not justWarped Then
            HandleWarp("picWarp2")
        ElseIf currentItem <> "picWarp" AndAlso currentItem <> "picWarp2" Then
            justWarped = False
        End If
    End Sub

    Private Sub HandleWarp(warpType As String)
        If Not justWarped Then
            Dim warpPositions As New List(Of Point)
            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) = warpType Then
                            warpPositions.Add(New Point(x, y))
                        End If
                    Next
                End If
            Next
            warpPositions.Remove(New Point(playerX, playerY))
            If warpPositions.Count = 1 Then
                justWarped = True
                sfxManager.PlaySoundEffect("Warp")
                playerX = warpPositions(0).X
                playerY = warpPositions(0).Y
                CheckForWarp()
            End If
        End If
    End Sub

    Private Function AttemptPushBall(ballX As Integer, ballY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim nextX As Integer = ballX + deltaX
        Dim nextY As Integer = ballY + deltaY

        If nextX < 0 OrElse nextX >= SQCOUNTX OrElse nextY < 0 OrElse nextY >= SQCOUNTY Then
            Return False
        End If

        If Not CanBallMoveTo(ballX, ballY, deltaX, deltaY) Then
            Return False
        End If

        Dim targetItem As String = placedItems(nextX)(nextY)
        Dim playerAtNextPos As Boolean = (nextX = playerX AndAlso nextY = playerY)

        If String.IsNullOrEmpty(targetItem) AndAlso Not playerAtNextPos Then
            placedItems(ballX)(ballY) = Nothing

            Dim newBall As New MovingObject With {
                .X = ballX,
                .Y = ballY,
                .DeltaX = deltaX,
                .DeltaY = deltaY,
                .IsBall = True,
                .IsBlock = False,
                .MoveInterval = 100,
                .NextMoveTime = DateTime.Now.AddMilliseconds(100)
            }

            movingObjects.Add(newBall)

            If movingObjectsTimer Is Nothing Then
                movingObjectsTimer = New Timer()
                AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
                movingObjectsTimer.Interval = 50
                movingObjectsTimer.Start()
            End If

            sfxManager.PlaySoundEffect("Push Ball")

            Return True
        Else
            Return False
        End If
    End Function

    Private Sub CheckForFlipper(movingObject As MovingObject)
        Dim x As Integer = movingObject.X
        Dim y As Integer = movingObject.Y
        Dim onFlipper As Boolean = False

        ' Check Right wall (Flipper1: Push left)
        If x < verticalWallItems.Length - 1 AndAlso verticalWallItems(x + 1)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x + 1)(y)
            If item = "picFlipper1" Then
                movingObject.DeltaX = -1
                movingObject.DeltaY = 0
                movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                sfxManager.PlaySoundEffect("Flipper")
                onFlipper = True
            End If
        End If

        ' Check Left wall (Flipper2: Push right)
        If x > 0 AndAlso verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            If item = "picFlipper2" Then
                movingObject.DeltaX = 1
                movingObject.DeltaY = 0
                movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                sfxManager.PlaySoundEffect("Flipper")
                onFlipper = True
            End If
        End If

        ' Check Bottom wall (Flipper3: Push up)
        If y < horizontalWallItems(x).Length - 1 AndAlso horizontalWallItems(x)(y + 1) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y + 1)
            If item = "picFlipper3" Then
                movingObject.DeltaX = 0
                movingObject.DeltaY = -1
                movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                sfxManager.PlaySoundEffect("Flipper")
                onFlipper = True
            End If
        End If

        ' Check Top wall (Flipper4: Push down)
        If y > 0 AndAlso horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            If item = "picFlipper4" Then
                movingObject.DeltaX = 0
                movingObject.DeltaY = 1
                movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                sfxManager.PlaySoundEffect("Flipper")
                onFlipper = True
            End If
        End If

        ' If the object is not on a flipper and doesn't have a direction yet, do nothing
        ' This allows the moving object to retain its current direction
    End Sub

    Private Sub OnMovingObjectsTimerTick(sender As Object, e As EventArgs)
        Dim currentTime As DateTime = DateTime.Now

        For i As Integer = movingObjects.Count - 1 To 0 Step -1
            Dim movingObject = movingObjects(i)

            If movingObject.NextMoveTime > currentTime Then
                Continue For
            End If

            Dim deltaX As Integer = movingObject.DeltaX
            Dim deltaY As Integer = movingObject.DeltaY

            Dim currentX As Integer = movingObject.X
            Dim currentY As Integer = movingObject.Y
            Dim objectStopped As Boolean = False

            Dim nextX As Integer = currentX + deltaX
            Dim nextY As Integer = currentY + deltaY

            If nextX < 0 OrElse nextX >= SQCOUNTX OrElse nextY < 0 OrElse nextY >= SQCOUNTY Then
                objectStopped = True
            Else
                If Not CanObjectMoveTo(movingObject, currentX, currentY, deltaX, deltaY) Then
                    objectStopped = True
                Else
                    Dim targetItem As String = placedItems(nextX)(nextY)
                    Dim playerAtNextPos As Boolean = (nextX = playerX AndAlso nextY = playerY)
                    Dim objectAtNextPos As Boolean = movingObjects.Any(Function(o) o.X = nextX AndAlso o.Y = nextY AndAlso o IsNot movingObject)

                    If String.IsNullOrEmpty(targetItem) AndAlso Not playerAtNextPos AndAlso Not objectAtNextPos Then
                        movingObject.X = nextX
                        movingObject.Y = nextY
                        movingObject.NextMoveTime = currentTime.AddMilliseconds(movingObject.MoveInterval)

                        ' After moving, check for flippers to possibly change direction
                        CheckForFlipper(movingObject)
                    Else
                        objectStopped = True
                    End If
                End If
            End If

            If objectStopped Then
                If IsObjectOnFlipper(movingObject) Then
                    ' The object is on a flipper, so we keep it in movingObjects
                    movingObject.NextMoveTime = currentTime.AddMilliseconds(movingObject.MoveInterval)
                    ' Update movement direction based on current flipper
                    CheckForFlipper(movingObject)
                Else
                    ' The object is not on a flipper, so we can stop tracking it
                    If movingObject.IsBall Then
                        placedItems(movingObject.X)(movingObject.Y) = "picBall"
                    ElseIf movingObject.IsBlock Then
                        placedItems(movingObject.X)(movingObject.Y) = "picBlock1"
                    End If
                    sfxManager.PlaySoundEffect("Block Stop")
                    movingObjects.RemoveAt(i)
                End If
            End If
        Next

        ' Do not stop the timer; keep checking for objects on flippers
        picTest.Invalidate()
    End Sub

    Private Function IsObjectOnFlipper(movingObject As MovingObject) As Boolean
        Dim x As Integer = movingObject.X
        Dim y As Integer = movingObject.Y

        ' Check Right wall (Flipper1)
        If x < verticalWallItems.Length - 1 AndAlso verticalWallItems(x + 1)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x + 1)(y)
            If item = "picFlipper1" Then
                Return True
            End If
        End If

        ' Check Left wall (Flipper2)
        If x > 0 AndAlso verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            If item = "picFlipper2" Then
                Return True
            End If
        End If

        ' Check Bottom wall (Flipper3)
        If y < horizontalWallItems(x).Length - 1 AndAlso horizontalWallItems(x)(y + 1) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y + 1)
            If item = "picFlipper3" Then
                Return True
            End If
        End If

        ' Check Top wall (Flipper4)
        If y > 0 AndAlso horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            If item = "picFlipper4" Then
                Return True
            End If
        End If

        Return False
    End Function
    Private Sub AddObjectsOnFlippersToMovingObjects()
        ' Loop through the grid to find blocks or balls that are on flippers
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                Dim item As String = placedItems(x)(y)
                If item = "picBlock1" OrElse item = "picBall" Then
                    ' Check if this item is adjacent to a flipper
                    Dim movingObject As New MovingObject With {
                    .X = x,
                    .Y = y,
                    .IsBlock = (item = "picBlock1"),
                    .IsBall = (item = "picBall"),
                    .DeltaX = 0,
                    .DeltaY = 0
                }

                    ' Use CheckForFlipper to determine if the object is on a flipper and set its movement direction
                    CheckForFlipper(movingObject)

                    If movingObject.DeltaX <> 0 OrElse movingObject.DeltaY <> 0 Then
                        ' The object is on a flipper and will start moving
                        ' Remove it from placedItems
                        placedItems(x)(y) = Nothing
                        movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                        movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                        movingObjects.Add(movingObject)
                    End If
                End If
            Next
        Next

        ' Start the movingObjectsTimer if not already running
        If movingObjects.Count > 0 AndAlso movingObjectsTimer Is Nothing Then
            movingObjectsTimer = New Timer()
            AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
            movingObjectsTimer.Interval = 50
            movingObjectsTimer.Start()
        End If
    End Sub
    Private Function CanObjectMoveTo(movingObject As MovingObject, currentX As Integer, currentY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim wallExists As Boolean = False
        Dim laserExists As Boolean = False
        Dim laserX As Integer = -1
        Dim laserY As Integer = -1
        Dim laserType As String = ""

        If deltaX = 1 Then
            wallExists = verticalWalls(currentX + 1)(currentY)
            If verticalWallItems(currentX + 1)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX + 1
                laserY = currentY
                laserType = verticalWallItems(laserX)(laserY)
            End If
        ElseIf deltaX = -1 Then
            wallExists = verticalWalls(currentX)(currentY)
            If verticalWallItems(currentX)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY
                laserType = verticalWallItems(laserX)(laserY)
            End If
        ElseIf deltaY = 1 Then
            wallExists = horizontalWalls(currentX)(currentY + 1)
            If horizontalWallItems(currentX)(currentY + 1) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY + 1
                laserType = horizontalWallItems(laserX)(laserY)
            End If
        ElseIf deltaY = -1 Then
            wallExists = horizontalWalls(currentX)(currentY)
            If horizontalWallItems(currentX)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY
                laserType = horizontalWallItems(laserX)(laserY)
            End If
        End If

        If wallExists Then
            Return False
        End If

        If laserExists Then
            Dim laserCanBeDestroyed As Boolean = False

            If (deltaX <> 0 AndAlso laserType = "picLazer1") Then
                laserCanBeDestroyed = True
            ElseIf (deltaY <> 0 AndAlso laserType = "picLazer2") Then
                laserCanBeDestroyed = True
            End If

            If laserCanBeDestroyed Then
                If deltaX <> 0 Then
                    verticalWallItems(laserX)(laserY) = Nothing
                ElseIf deltaY <> 0 Then
                    horizontalWallItems(laserX)(laserY) = Nothing
                End If
                sfxManager.PlaySoundEffect("Break Lazer")
            Else
                Return False
            End If
        End If

        Return True
    End Function

    Private Function CanBallMoveTo(currentX As Integer, currentY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Return CanObjectMoveTo(New MovingObject With {.IsBall = True}, currentX, currentY, deltaX, deltaY)
    End Function

    Private Function IsLaserOnVerticalWall(x As Integer, y As Integer) As Boolean
        If verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            Return item = "picLazer1" OrElse item = "picLazer2"
        End If
        Return False
    End Function

    Private Function IsLaserOnHorizontalWall(x As Integer, y As Integer) As Boolean
        If horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            Return item = "picLazer1" OrElse item = "picLazer2"
        End If
        Return False
    End Function

    Private Function AttemptPushBlock(blockX As Integer, blockY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim blockNewX As Integer = blockX + deltaX
        Dim blockNewY As Integer = blockY + deltaY

        If blockNewX < 0 OrElse blockNewX >= SQCOUNTX OrElse blockNewY < 0 OrElse blockNewY >= SQCOUNTY Then
            Return False
        End If

        If Not CanBlockMoveTo(blockX, blockY, deltaX, deltaY) Then
            Return False
        End If

        Dim blockTargetItem As String = placedItems(blockNewX)(blockNewY)
        Dim playerAtNextPos As Boolean = (blockNewX = playerX AndAlso blockNewY = playerY)

        If String.IsNullOrEmpty(blockTargetItem) AndAlso Not playerAtNextPos Then
            placedItems(blockX)(blockY) = Nothing
            placedItems(blockNewX)(blockNewY) = "picBlock1"

            sfxManager.PlaySoundEffect("Push Block")

            ' After moving the block, check if it should start moving due to a flipper
            Dim newBlock As New MovingObject With {
                .X = blockNewX,
                .Y = blockNewY,
                .IsBlock = True,
                .IsBall = False,
                .DeltaX = 0,
                .DeltaY = 0
            }

            CheckForFlipper(newBlock)
            If newBlock.DeltaX <> 0 OrElse newBlock.DeltaY <> 0 Then
                placedItems(blockNewX)(blockNewY) = Nothing
                newBlock.MoveInterval = 50
                newBlock.NextMoveTime = DateTime.Now.AddMilliseconds(50)
                movingObjects.Add(newBlock)

                If movingObjectsTimer Is Nothing Then
                    movingObjectsTimer = New Timer()
                    AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
                    movingObjectsTimer.Interval = 50
                    movingObjectsTimer.Start()
                End If
            End If

            Return True
        Else
            Return False
        End If
    End Function

    Private Function CanBlockMoveTo(blockX As Integer, blockY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Return CanObjectMoveTo(New MovingObject With {.IsBlock = True}, blockX, blockY, deltaX, deltaY)
    End Function

    Private Function IsFlipperOnVerticalWall(x As Integer, y As Integer) As Boolean
        If verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            Return item = "picFlipper1" OrElse item = "picFlipper2"
        End If
        Return False
    End Function

    Private Function IsFlipperOnHorizontalWall(x As Integer, y As Integer) As Boolean
        If horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            Return item = "picFlipper3" OrElse item = "picFlipper4"
        End If
        Return False
    End Function

    Private Function CanMoveTo(newX As Integer, newY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        ' Check walls, lasers, and flippers between current and new position
        If deltaX = 1 Then
            ' Moving right
            If verticalWalls(playerX + 1)(playerY) Then Return False
            If IsLaserOnVerticalWall(playerX + 1, playerY) Then Return False
            If IsFlipperOnVerticalWall(playerX + 1, playerY) Then Return False
        ElseIf deltaX = -1 Then
            ' Moving left
            If verticalWalls(playerX)(playerY) Then Return False
            If IsLaserOnVerticalWall(playerX, playerY) Then Return False
            If IsFlipperOnVerticalWall(playerX, playerY) Then Return False
        ElseIf deltaY = 1 Then
            ' Moving down
            If horizontalWalls(playerX)(playerY + 1) Then Return False
            If IsLaserOnHorizontalWall(playerX, playerY + 1) Then Return False
            If IsFlipperOnHorizontalWall(playerX, playerY + 1) Then Return False
        ElseIf deltaY = -1 Then
            ' Moving up
            If horizontalWalls(playerX)(playerY) Then Return False
            If IsLaserOnHorizontalWall(playerX, playerY) Then Return False
            If IsFlipperOnHorizontalWall(playerX, playerY) Then Return False
        End If

        ' Check if the new position is occupied by a moving object
        If movingObjects.Any(Function(b) b.X = newX AndAlso b.Y = newY) Then
            Return False
        End If

        Return True
    End Function

    Private Sub GameWon()
        sfxManager.PlaySoundEffect("Finish")
        MessageBox.Show($"Congratulations! You have completed the level.{Environment.NewLine}Coins Collected: {CollectedCoins}")
        Me.Close()
    End Sub
#End Region

#Region "Load"
    Private Sub frmGame_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Initialize sound effects before any potential calls to PlaySoundEffect
        InitializeImages()

        ' Load the room data from file
        If Not String.IsNullOrEmpty(RoomFilePath) AndAlso File.Exists(RoomFilePath) Then
            LoadRoomFromFile(RoomFilePath)
        Else
            MessageBox.Show("Room file not found.")
            Me.Close()
            Return
        End If

        ' Start the animation timer
        animationTimer = New Timer() With {.Interval = 100}
        AddHandler animationTimer.Tick, AddressOf OnAnimationTick
        animationTimer.Start()

        picTest.Invalidate()

        Me.KeyPreview = True
        Me.Focus()
    End Sub

    Private Sub LoadRoomFromFile(filePath As String)
        Try
            Dim options As New JsonSerializerOptions With {
                .IncludeFields = True,
                .PropertyNameCaseInsensitive = 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

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

    Private Sub RestoreRoomData(roomData As RoomData)
        Try
            SQCOUNTX = roomData.GridSizeX
            SQCOUNTY = roomData.GridSizeY

            wallsHatchStyle = CType(roomData.wallsHatchStyleValue, HatchStyle)
            wallsColor1 = Color.FromArgb(roomData.wallsColor1Argb)
            wallsColor2 = Color.FromArgb(roomData.wallsColor2Argb)
            floorsHatchStyle = CType(roomData.floorsHatchStyleValue, HatchStyle)
            floorsColor1 = Color.FromArgb(roomData.floorsColor1Argb)
            floorsColor2 = Color.FromArgb(roomData.floorsColor2Argb)

            verticalWalls = roomData.verticalWalls
            horizontalWalls = roomData.horizontalWalls
            floorsUseEdgeStyle = roomData.floorsUseEdgeStyle

            placedItems = RestorePlacedItems(roomData.placedItems)
            verticalWallItems = RestorePlacedItems(roomData.verticalWallItems)
            horizontalWallItems = RestorePlacedItems(roomData.horizontalWallItems)

            SelectedMusic = roomData.SelectedMusic
            lblName.Text = roomData.LevelName
            PlaySelectedMusic()

            isRoomDataLoaded = True
            picTest.Invalidate()
        Catch ex As Exception
            MessageBox.Show("Error restoring room data: " & ex.ToString())
        End Try

        FindPlayerStartPosition()
        AddObjectsOnFlippersToMovingObjects()

    End Sub

    Private Function RestorePlacedItems(data As PlacedItemData()()) As String()()
        If data Is Nothing Then
            Return Nothing
        End If

        Dim lengthOuter As Integer = data.Length
        Dim restoredItems As String()() = New String(lengthOuter - 1)() {}

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

    Private Sub InitializeImages()
        Try
            imageDictionary("picBlock1") = Image.FromFile(Path.Combine(ImagesFolder, "Block.png"))
            imageDictionary("picBall") = Image.FromFile(Path.Combine(ImagesFolder, "Ball.gif"))
            imageDictionary("picCoin") = Image.FromFile(Path.Combine(ImagesFolder, "Coin.gif"))
            imageDictionary("picFinish") = Image.FromFile(Path.Combine(ImagesFolder, "Finish.gif"))
            imageDictionary("picFlipper1") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper1.gif"))
            imageDictionary("picFlipper2") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper2.gif"))
            imageDictionary("picFlipper3") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper3.gif"))
            imageDictionary("picFlipper4") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper4.gif"))
            imageDictionary("picGate") = Image.FromFile(Path.Combine(ImagesFolder, "Gate.gif"))
            imageDictionary("picKey") = Image.FromFile(Path.Combine(ImagesFolder, "Key.gif"))
            imageDictionary("picLazer1") = Image.FromFile(Path.Combine(ImagesFolder, "Lazer1.gif"))
            imageDictionary("picLazer2") = Image.FromFile(Path.Combine(ImagesFolder, "Lazer2.gif"))
            imageDictionary("picWarp") = Image.FromFile(Path.Combine(ImagesFolder, "Warp.gif"))
            imageDictionary("picWarp2") = Image.FromFile(Path.Combine(ImagesFolder, "Warp2.gif"))
            imageDictionary("picStart") = Image.FromFile(Path.Combine(ImagesFolder, "Guy.gif"))
            imageDictionary("picBlockStopper1") = Image.FromFile(Path.Combine(ImagesFolder, "Debris1.png"))

            Dim animatedImageKeys As String() = {
                "picLazer1", "picLazer2", "picFlipper1", "picFlipper2",
                "picFlipper3", "picFlipper4", "picCoin", "picGate",
                "picKey", "picFinish", "picBall", "picWarp", "picWarp2", "picStart"
            }

            For Each imgKey In animatedImageKeys
                If imageDictionary.ContainsKey(imgKey) Then
                    Dim img As Image = imageDictionary(imgKey)
                    animatedImages.Add(img)
                    ImageAnimator.Animate(img, AddressOf OnFrameChanged)
                End If
            Next
        Catch ex As Exception
            MessageBox.Show("Error loading images: " & ex.Message)
        End Try
    End Sub

    Private Function GetProjectDirectory() As String
        Dim dir As DirectoryInfo = New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function
#End Region

#Region "Sound"
    Private Sub PlaySelectedMusic()
        If Not String.IsNullOrEmpty(SelectedMusic) Then
            Dim musicDetails As String() = SelectedMusic.Split(", ")
            Dim musicFileName As String = musicDetails(0)
            Dim volumeLevel As Single = Integer.Parse(musicDetails(1).Split(":")(1)) / 100.0F
            Dim gainLevel As Single = Integer.Parse(musicDetails(2).Split(":")(1)) / 10.0F

            Dim musicFilePath As String = Path.Combine(musicFolder, musicFileName)

            musicManager.PlaySelectedMusic(musicFilePath, volumeLevel, gainLevel)
        End If
    End Sub
#End Region

#Region "Draw"
    Private Sub OnAnimationTick(sender As Object, e As EventArgs)
        For Each img In animatedImages
            ImageAnimator.UpdateFrames(img)
        Next
        picTest.Invalidate()
    End Sub

    Private Sub OnFrameChanged(o As Object, e As EventArgs)
        ' Required but can be left empty
    End Sub

    Private Sub picTest_Paint(sender As Object, e As PaintEventArgs) Handles picTest.Paint
        If Not isRoomDataLoaded Then
            Return
        End If

        Using backgroundBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
            e.Graphics.FillRectangle(backgroundBrush, picTest.ClientRectangle)
        End Using

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

        e.Graphics.SmoothingMode = SmoothingMode.None

        DrawGrid(e.Graphics)
    End Sub

    Private Sub CalculateSquareSizes(clientWidth As Integer, clientHeight As Integer)
        SQSizeX = (clientWidth - 2 * INDENT - (SQCOUNTX + 1) * WALL_THICKNESS) \ SQCOUNTX
        SQSizeY = (clientHeight - 2 * INDENT - (SQCOUNTY + 1) * WALL_THICKNESS) \ SQCOUNTY
    End Sub

    Private Sub DrawGrid(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        ' Draw floor squares
        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 IsNot Nothing AndAlso floorsUseEdgeStyle.Length > x AndAlso
                   floorsUseEdgeStyle(x) IsNot Nothing AndAlso floorsUseEdgeStyle(x).Length > y AndAlso
                   floorsUseEdgeStyle(x)(y) Then

                    Using floorBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                        g.FillRectangle(floorBrush, floorRect)
                    End Using
                Else
                    Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                        g.FillRectangle(floorBrush, floorRect)
                    End Using
                End If
            Next
        Next

        ' Draw wall intersections
        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

        ' Draw vertical walls or floor
        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(x).Length Then

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

        ' Draw horizontal walls or floor
        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(x).Length Then

                    If horizontalWalls(x)(y) Then
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, 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)
    End Sub

    Private Sub DrawItems(g As Graphics)
        If placedItems Is Nothing Then Return

        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
                    Dim imageKey As String = placedItems(x)(y)
                    If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                        If imageKey = "picStart" Then
                            Continue For
                        End If
                        Dim img As Image = imageDictionary(imageKey)
                        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)

                        g.DrawImage(img, destRect)
                    End If
                Next
            End If
        Next

        ' Draw moving objects (balls and blocks)
        For Each movingObject In movingObjects
            Dim img As Image = Nothing
            If movingObject.IsBall Then
                img = imageDictionary("picBall")
            ElseIf movingObject.IsBlock Then
                img = imageDictionary("picBlock1")
            End If

            If img IsNot Nothing Then
                Dim xPos As Integer = INDENT + movingObject.X * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim yPos As Integer = INDENT + movingObject.Y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

                g.DrawImage(img, destRect)
            End If
        Next

        DrawPlayer(g)
    End Sub

    Private Sub DrawPlayer(g As Graphics)
        Dim img As Image = imageDictionary("picStart")
        Dim xPos As Integer = INDENT + playerX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
        Dim yPos As Integer = INDENT + playerY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
        Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

        g.DrawImage(img, destRect)
    End Sub

    Private Sub DrawWallItems(g As Graphics)
        ' Draw vertical wall items
        If verticalWallItems IsNot Nothing Then
            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
                        Dim imageKey As String = verticalWallItems(x)(y)
                        If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                            Dim img As Image = imageDictionary(imageKey)
                            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
                    Next
                End If
            Next
        End If

        ' Draw horizontal wall items
        If horizontalWallItems IsNot Nothing Then
            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
                        Dim imageKey As String = horizontalWallItems(x)(y)
                        If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                            Dim img As Image = imageDictionary(imageKey)
                            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
                    Next
                End If
            Next
        End If
    End Sub
#End Region

#Region "Closing"
    Private Sub frmGame_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        ' Stop and dispose of animation timer
        If animationTimer IsNot Nothing Then
            animationTimer.Stop()
            animationTimer.Dispose()
            animationTimer = Nothing
        End If

        ' Stop and dispose of moving objects timer
        If movingObjectsTimer IsNot Nothing Then
            movingObjectsTimer.Stop()
            movingObjectsTimer.Dispose()
            movingObjectsTimer = Nothing
        End If

        ' Clear moving objects list
        movingObjects.Clear()

        ' Stop music playback
        musicManager.StopMusic()

        ' Stop sound effects
        sfxManager.StopSoundEffects()
    End Sub

    Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click
        Me.Close()
    End Sub
#End Region
End Class

I've been working all day trying to get the sound effects volume to IGNORE the music volume. The sound effects should play at their defualt value. The music should play as it is set by the user.