Solved

Memory Game algorithm in VB.Net 2010, help

Posted on 2010-11-08
22
1,793 Views
Last Modified: 2012-05-10
I'm starting with a memory match game, it is a 6x6 blocks (images). the thing is i need to populate the matrix but i dont know how:

I was trying to figure out this way:

first randomize from 1 to 36
assign an image to each number
assign each number a cell in the 6x6 matrix
compare clicked image1 with clicked image 2

That is how i can picture it by now. What i would need from you guys is a help to convert this poor algorithm into VB.Net 2010 code.

Thanks,

Oscar
0
Comment
Question by:José Perez
  • 7
  • 5
  • 5
  • +2
22 Comments
 
LVL 39

Expert Comment

by:Kyle Abrahams
Comment Utility
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
I think it would be even simpler than that. Just create your grid, randomly each image to two different cells, then, during the game, compare the references of each image to see if they point to the same image. This should be a start. Currently, it displays all the images--you'll need to develop the logic to only display them when they are selected. I left it this way so you can get a feel for what the code is doing.
Public Class Form1
    Private firstSelection As PictureBox

    Private Sub generateButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles generateButton.Click
        Dim pb As PictureBox
        Dim cellCount As Integer = 12

        ' Clear existing
        Me.FlowLayoutPanel1.Controls.Clear()

        ' Create the cells
        For i As Integer = 0 To cellCount - 1
            pb = New PictureBox()

            pb.Margin = New Padding(5)
            pb.BackColor = Color.AliceBlue
            AddHandler pb.Click, AddressOf PictureBox_Click
            Me.FlowLayoutPanel1.Controls.Add(pb)
        Next

        ' Load the cells
        Dim rand As New Random(Now.Millisecond)
        Dim images() As Image = {New Bitmap("img1.bmp"), New Bitmap("img2.bmp"), New Bitmap("img3.bmp"), _
                                  New Bitmap("img4.bmp"), New Bitmap("img5.bmp"), New Bitmap("img6.bmp")}
        Dim populatedCount As Integer = 0
        Dim imgIndex As Integer

        ' Loop through available images
        For imgIndex = 0 To images.Length - 1
            ' Fill one image into two cells
            For loopTwice As Integer = 0 To 1
                Do
                    Dim randomInt As Integer = rand.Next(0, cellCount)

                    pb = DirectCast(Me.FlowLayoutPanel1.Controls(randomInt), PictureBox)
                Loop While pb.Image IsNot Nothing

                ' Set the image
                pb.Image = images(imgIndex)
            Next
        Next
    End Sub

    Private Sub PictureBox_Click(ByVal sender As Object, ByVal e As EventArgs)
        Dim clickedBox As PictureBox = DirectCast(sender, PictureBox)

        If Me.firstSelection Is Nothing Then
            clickedBox.BorderStyle = BorderStyle.Fixed3D
            Me.firstSelection = clickedBox
        Else
            If Me.firstSelection IsNot clickedBox AndAlso Me.firstSelection.Image Is clickedBox.Image Then
                Me.firstSelection.Image = Nothing
                clickedBox.Image = Nothing
                RemoveHandler Me.firstSelection.Click, AddressOf PictureBox_Click
                RemoveHandler clickedBox.Click, AddressOf PictureBox_Click

                MessageBox.Show("Match")
            End If

            Me.firstSelection.BorderStyle = BorderStyle.None
            Me.firstSelection = Nothing
        End If
    End Sub
End Class

Open in new window

0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
I should have attached a screenshot of the code in action  :)
untitled.PNG
0
 
LVL 2

Author Comment

by:José Perez
Comment Utility
ok, it is working, but what changes do i have to do to support 6x6 (36 images)?
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
Here's a quick Star Wars memory game I made for my son:
http://dl.dropbox.com/u/5131616/StarWarsMemory.zip

*The visual shuffle runs much smoother than in the screencast...the recorder isn't fast enough to capture it properly.
Public Class Form1



    Private Class Card

        Public Img As Image

        Public Name As String

        Public Sub New(ByVal img As Image, ByVal name As String)

            Me.Img = img

            Me.Name = name

        End Sub

    End Class



    Private R As New Random

    Private Cards As New List(Of Card)

    Private ClickCount As Integer = 0



    Private FirstCard, SecondCard As Card

    Private FirstPB, SecondPB As PictureBox



    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Cards.Clear()



        Cards.Add(New Card(My.Resources.Clone_Trooper_256x256, "CloneTrooper"))

        Cards.Add(New Card(My.Resources.Clone_Trooper_256x256, "CloneTrooper"))

        Cards.Add(New Card(My.Resources.Darth_Vader_256x256, "DarthVader"))

        Cards.Add(New Card(My.Resources.Darth_Vader_256x256, "DarthVader"))

        Cards.Add(New Card(My.Resources.Death_Star_256x256, "DeathStar"))

        Cards.Add(New Card(My.Resources.Death_Star_256x256, "DeathStar"))

        Cards.Add(New Card(My.Resources.Master_Joda_256x256, "Yoda"))

        Cards.Add(New Card(My.Resources.Master_Joda_256x256, "Yoda"))

        Cards.Add(New Card(My.Resources.R2D2_256x256, "R2D2"))

        Cards.Add(New Card(My.Resources.R2D2_256x256, "R2D2"))



        Cards.Add(New Card(My.Resources.Bicycle, "Bicycle"))

        Cards.Add(New Card(My.Resources.Bicycle, "Bicycle"))

        Cards.Add(New Card(My.Resources.BobbaFett, "BobbaFett"))

        Cards.Add(New Card(My.Resources.BobbaFett, "BobbaFett"))

        Cards.Add(New Card(My.Resources.Emblem1, "Emblem1"))

        Cards.Add(New Card(My.Resources.Emblem1, "Emblem1"))

        Cards.Add(New Card(My.Resources.Emblem2, "Emblem2"))

        Cards.Add(New Card(My.Resources.Emblem2, "Emblem2"))

        Cards.Add(New Card(My.Resources.RebelHelmet, "RebelHelmet"))

        Cards.Add(New Card(My.Resources.RebelHelmet, "RebelHelmet"))



        For Each pb As PictureBox In Me.Controls.OfType(Of PictureBox)()

            AddHandler pb.Click, AddressOf pb_Click

        Next

    End Sub



    Private Sub Form1_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown

        Shuffle()

    End Sub



    Private Sub Shuffle()

        If Not BackgroundWorker1.IsBusy Then

            My.Computer.Audio.Play(My.Resources.R2D2, AudioPlayMode.BackgroundLoop)

            BackgroundWorker1.RunWorkerAsync()

        End If

    End Sub



    Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

        Dim C As Card

        Dim index As Integer



        ' shuffle them for awhile while R2D2 sounds play in the background

        Dim targetDT As DateTime = DateTime.Now.AddSeconds(3)

        While targetDT > Now

            For i As Integer = 0 To Cards.Count - 1

                index = R.Next(0, Cards.Count)

                C = Cards(i)

                Cards(i) = Cards(index)

                Cards(index) = C

            Next

            BackgroundWorker1.ReportProgress(0)

            System.Threading.Thread.Sleep(50) ' let them be visible for a split sec

        End While

    End Sub



    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged

        ' show shuffled cards

        For i As Integer = 0 To Cards.Count - 1

            CType(Me.Controls("PictureBox" & (i + 1)), PictureBox).Tag = True

            CType(Me.Controls("PictureBox" & (i + 1)), PictureBox).Visible = True

            CType(Me.Controls("PictureBox" & (i + 1)), PictureBox).Image = Cards(i).Img

        Next

    End Sub



    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

        ' cover them up

        For i As Integer = 1 To Cards.Count

            CType(Me.Controls("PictureBox" & i), PictureBox).Image = My.Resources.StarWarsLogo

            Me.Controls("PictureBox" & i).Tag = True

        Next

        My.Computer.Audio.Stop()

    End Sub



    Private Sub pb_Click(ByVal sender As Object, ByVal e As System.EventArgs)

        Dim index As Integer

        Dim prefix As String = "PictureBox"

        Dim pb As PictureBox = CType(sender, PictureBox)

        If pb.Tag Then

            pb.Tag = False

            If Integer.TryParse(pb.Name.Substring(prefix.Length), index) Then

                pb.Image = Cards(index - 1).Img

                ClickCount = ClickCount + 1

                If ClickCount = 1 Then

                    FirstCard = Cards(index - 1)

                    FirstPB = pb

                ElseIf ClickCount = 2 Then

                    Application.DoEvents()

                    System.Threading.Thread.Sleep(750)



                    SecondCard = Cards(index - 1)

                    SecondPB = pb

                    If FirstCard.Name = SecondCard.Name Then

                        ' a match!

                        My.Computer.Audio.Play(My.Resources.LAZER_WAV, AudioPlayMode.Background)

                        FirstPB.Visible = False

                        FirstPB.Tag = True

                        SecondPB.Visible = False

                        SecondPB.Tag = True



                        Dim GameOver As Boolean = True

                        For Each ctl As Control In Me.Controls.OfType(Of PictureBox)()

                            If ctl.Visible Then

                                GameOver = False

                                Exit For

                            End If

                        Next

                        If GameOver Then

                            My.Computer.Audio.Play(My.Resources.Force, AudioPlayMode.WaitToComplete)

                            Shuffle()

                        End If

                    Else

                        ' not a match...

                        My.Computer.Audio.Play(My.Resources.chewy1, AudioPlayMode.Background)

                        FirstPB.Image = My.Resources.StarWarsLogo

                        FirstPB.Tag = True

                        SecondPB.Image = My.Resources.StarWarsLogo

                        SecondPB.Tag = True

                    End If



                    ClickCount = 0





                End If

            End If

        End If

    End Sub



End Class

Open in new window

Idle-Mind-367313.flv
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
I'm glad you're able to get the screencasts to upload Idle. I have yet  to have one not error out during upload  :\

As far as my example and sizing it for 6 x 6, you would need to add some math to appropriately size the FlowLayoutPanel. As long as you set the width to max out at six cards, then the rest should "flow" into place automatically. You would want to fix the size of the panel (or maybe even the form) because any resizing of the panel will cause the PictureBoxes to re-flow, thereby messing up the layout.

"cellCount" and the image source ("images" in my example) would obviously change as well  :)
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
I hope George Lucas doesn't come after you for that one  ;)
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
Hehe...no money is being made from the game so I'd think it'd be ok.  =D

*My son picked out all the images to use from a Google image search.
0
 
LVL 2

Author Comment

by:José Perez
Comment Utility
idle_mind, your code is very good but too advance for me to understand it. the backgroundworker really confuses me so i cant understand your code (very integrated to the other staff in your code). do you have something more simplistic?

kaufmed, your code is very close to what i am looking for, if you could update to work with 6x6 i would really appreciate it :)

OscarG
0
 
LVL 74

Accepted Solution

by:
käµfm³d   👽 earned 150 total points
Comment Utility
Here is an updated version. The blanks you see are because I only have 6 images--if the full 18 were provided, then these would have had images in them.
Public Class Form1
    Private firstSelection As PictureBox

    Private Sub generateButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles generateButton.Click
        Dim pb As PictureBox
        Dim cellCount As Integer
        Dim width As Integer
        Dim height As Integer
        Dim dummy As PictureBox = New PictureBox()

        If Not Integer.TryParse(Me.widthBox.Text, width) OrElse Not Integer.TryParse(Me.heightBox.Text, height) Then
            MessageBox.Show("Please enter a valid cell width and cell height!")
            Return
        End If

        cellCount = width * height

        ' Size the FlowLayoutPanel
        dummy.Margin = New Padding(5)
        Me.FlowLayoutPanel1.Width = (dummy.Width + dummy.Margin.Left + dummy.Margin.Right) * width
        dummy = Nothing

        ' Clear existing
        Me.FlowLayoutPanel1.Controls.Clear()

        ' Create the cells
        For i As Integer = 0 To cellCount - 1
            pb = New PictureBox()

            pb.Margin = New Padding(5)
            pb.BackColor = Color.AliceBlue
            AddHandler pb.Click, AddressOf PictureBox_Click
            Me.FlowLayoutPanel1.Controls.Add(pb)
        Next

        ' Load the cells
        Dim rand As New Random(Now.Millisecond)
        Dim images() As Image = {New Bitmap("img1.bmp"), New Bitmap("img2.bmp"), New Bitmap("img3.bmp"), _
                                  New Bitmap("img4.bmp"), New Bitmap("img5.bmp"), New Bitmap("img6.bmp")}
        Dim populatedCount As Integer = 0
        Dim imgIndex As Integer

        ' Loop through available images
        For imgIndex = 0 To images.Length - 1
            ' Fill one image into two cells
            For loopTwice As Integer = 0 To 1
                Do
                    Dim randomInt As Integer = rand.Next(0, cellCount)

                    pb = DirectCast(Me.FlowLayoutPanel1.Controls(randomInt), PictureBox)
                Loop While pb.Image IsNot Nothing

                ' Set the image
                pb.Image = images(imgIndex)
            Next
        Next
    End Sub

    Private Sub PictureBox_Click(ByVal sender As Object, ByVal e As EventArgs)
        Dim clickedBox As PictureBox = DirectCast(sender, PictureBox)

        If Me.firstSelection Is Nothing Then
            clickedBox.BorderStyle = BorderStyle.Fixed3D
            Me.firstSelection = clickedBox
        Else
            If Me.firstSelection IsNot clickedBox AndAlso Me.firstSelection.Image Is clickedBox.Image Then
                Me.firstSelection.Image = Nothing
                clickedBox.Image = Nothing
                RemoveHandler Me.firstSelection.Click, AddressOf PictureBox_Click
                RemoveHandler clickedBox.Click, AddressOf PictureBox_Click

                MessageBox.Show("Match")
            End If

            Me.firstSelection.BorderStyle = BorderStyle.None
            Me.firstSelection = Nothing
        End If
    End Sub
End Class

Open in new window

untitled.PNG
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
Hey these examples are pretty cool, ! It would be perfect to have a designer for these examples. If you think of it as pallete of images you can have one add images to the designer and create a single image pallete that you later could break into the dimensions. Would be flexible and only require maintaining one image the pallete per puzzle but allow the user to generate there own images for a puzzle by loading the pallete.
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 2

Author Comment

by:José Perez
Comment Utility
kaufmed, we're very closer!!! just 2 minor thing:

i have an error "not memeber of Form1" in the following:
'widthBox'
'hightBox'

what kind of controls are this?
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
Those are the two textboxes next to the button you see in my last screenshot. The left box is "widthBox" and the right box is "heightBox".
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
Here is a start for the basic idea for a designer (no GUI) using a single .TIFF image you have a basis for a easy to use pallete.
// usage:

Dim dlg As New OpenFileDialog
dlg.Multiselect = True
dlg.ShowDialog()
' Save the pallete
PuzzleDesigner.AddImage(dlg.FileNames)
PuzzleDesigner.Save("c:\users\medieval\documents\pallete.tiff", 64, 64)
 
' Load the saved pallete.
'Dim pallete As List(Of Image) = PuzzleDesigner.GetImages("c:\users\medieval\documents\pallete.tiff")
'For Each img As Image In pallete
' PictureBox1.Image = img
' Application.DoEvents()
' System.Threading.Thread.Sleep(200)
'Next

Imports System.IO

Imports System.Drawing.Imaging



Public Class PuzzleDesigner

    Private Sub New()

    End Sub



    Private Shared imageItems As New List(Of String)



    Public Shared Sub AddImage(ByVal imagePath As String)

        imageItems.Add(imagePath)

    End Sub

    Public Shared Sub AddImage(ByVal imagePaths() As String)

        imageItems.AddRange(imagePaths)

    End Sub

    Public Shared Sub Clear()

        imageItems.Clear()

    End Sub

    Public Shared Sub Save(ByVal fileName As String, _

                           ByVal w As Integer, _

                           ByVal h As Integer)

        Dim i As Integer

        Dim imageEncoder As Encoder = Encoder.SaveFlag

        Dim imageInfo As ImageCodecInfo = Nothing

        Dim imageCodec As ImageCodecInfo

        Dim ep As New EncoderParameters(1)

        For Each imageCodec In ImageCodecInfo.GetImageEncoders()

            If imageCodec.MimeType = "image/tiff" Then

                imageInfo = imageCodec

            End If

        Next imageCodec

        ep.Param(0) = New EncoderParameter(imageEncoder, EncoderValue.MultiFrame)

        Dim master As New Bitmap(imageItems(0))

        master = master.GetThumbnailImage(w, h, Nothing, IntPtr.Zero)

        master.Save(fileName, imageInfo, ep)

        ep.Param(0) = New EncoderParameter(imageEncoder, EncoderValue.FrameDimensionPage)

        For i = 1 To imageItems.Count - 1

            master.SaveAdd(New Bitmap(imageItems(i)).GetThumbnailImage(w, h, Nothing, IntPtr.Zero), ep)

        Next i

        ep.Param(0) = New EncoderParameter(imageEncoder, EncoderValue.Flush)

        master.SaveAdd(ep)

        master.Dispose()

    End Sub



    Public Shared Function GetImages(ByVal fileName As String) As List(Of Image)

        Dim i As Integer

        Dim imgItems As New List(Of Image)

        Dim fs As FileStream = File.Open(fileName, FileMode.Open, FileAccess.Read)

        Dim bm As Bitmap = Bitmap.FromStream(fs)

        For i = 0 To bm.GetFrameCount(FrameDimension.Page) - 1

            bm.SelectActiveFrame(FrameDimension.Page, i)

            Dim bmItem As New Bitmap(bm.Width, bm.Height)

            Dim g As Graphics = Graphics.FromImage(bmItem)

            g.DrawImageUnscaled(bm, 0, 0)

            g.Dispose()

            imgItems.Add(bmItem)

        Next i

        Return imgItems

    End Function



End Class

Open in new window

0
 
LVL 2

Author Comment

by:José Perez
Comment Utility
received error "invalid parameter" on this part of the code:

Dim images() As Image = {New Bitmap("img1.bmp"), New Bitmap("img2.bmp"), New Bitmap("img3.bmp"), New Bitmap("img4.bmp"), New Bitmap("img5.bmp"), New Bitmap("img6.bmp"), _

                                 New Bitmap("img7.bmp"), New Bitmap("img8.bmp"), New Bitmap("img9.bmp"), New Bitmap("img10.bmp"), New Bitmap("img11.bmp"), New Bitmap("img12.bmp"), _

                                 New Bitmap("img13.bmp"), New Bitmap("img14.bmp"), New Bitmap("img15.bmp"), New Bitmap("img16.bmp"), New Bitmap("img17.bmp"), New Bitmap("img18.bmp"), _

                                 New Bitmap("img19.bmp"), New Bitmap("img20.bmp"), New Bitmap("img21.bmp"), New Bitmap("img22.bmp"), New Bitmap("img23.bmp"), New Bitmap("img24.bmp"), _

                                 New Bitmap("img25.bmp"), New Bitmap("img26.bmp"), New Bitmap("img27.bmp"), New Bitmap("img28.bmp"), New Bitmap("img29.bmp"), New Bitmap("img30.bmp"), _

                                 New Bitmap("img31.bmp"), New Bitmap("img32.bmp"), New Bitmap("img33.bmp"), New Bitmap("img34.bmp"), New Bitmap("img35.bmp"), New Bitmap("img36.bmp")}

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
It probably can't find the files you're trying to use...

If you're running from the IDE they should be in the \bin folder.
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
You could load them this way as well:
    Dim images As New List(Of Image)



    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim imageName As String

        For i As Integer = 1 To 36

            imageName = "img" & i & ".bmp"

            Try

                images.Add(New Bitmap(imageName))

            Catch ex As Exception

                MessageBox.Show("File: " & imageName & vbCrLf & vbCrLf & ex.ToString, "Error Loading Image", MessageBoxButtons.OK, MessageBoxIcon.Error)

            End Try

        Next

    End Sub

Open in new window

0
 
LVL 2

Author Comment

by:José Perez
Comment Utility
in fact, the 'bin' folder. thanks.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
IMHO It's a pain to deal with so many images individually this is why a pallete scheme could be very useful to implement you have 1 image per set of images. It will make it easier to to manage them and even be more user friendly if you wanted to add a designer for the user to add there own set of images. *The game may not get old so quick. Keep your kids busy while they make there own puzzle with images. This is the type of features users generally look for in applications it gives them a sense of being part of the game. You could even have a small website or people who build them and upload them as a single file expanding the game giving that extra edge.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
LOL... Sometimes you need to talk to the wall
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
Comment Utility
I agree with you bud...but as he's learning still it's probably a bit much to wrap his head around.  ;)
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
>>  in fact, the 'bin' folder. thanks.

The solution was intended as a quick-and-dirty and I didn't make clear where the images were coming from; my apologies. Glad you got the answer.

I would suggest taking some time to study Idle_Mind's solution as well. His solution has a bit more coder-friendly re-usability in it (class Card and use of Resources for example). Don't be discouraged by his use of BackgroundWorker. It is there (to make the GUI more responsive during the shuffle). If you didn't spawn a separate thread from you GUI (which is what BW does internally), then you would get the infamous "[foo] (Not Responding)" until your shuffle logic completed. Besides, it's freakin' Star Wars  ;)
0

Featured Post

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

Microsoft Reports are based on a report definition, which is an XML file that describes data and layout for the report, with a different extension. You can create a client-side report definition language (*.rdlc) file with Visual Studio, and build g…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now