Link to home
Create AccountLog in
Avatar of José Perez
José PerezFlag for Chile

asked on

Memory Game algorithm in VB.Net 2010, help

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.


Avatar of Kyle Abrahams, PMP
Kyle Abrahams, PMP
Flag of United States of America image

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

        ' 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

        ' 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
                    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)
    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
            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

            End If

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

Open in new window

I should have attached a screenshot of the code in action  :)
Avatar of José Perez


ok, it is working, but what changes do i have to do to support 6x6 (36 images)?
Here's a quick Star Wars memory game I made for my son:

*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.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
    End Sub

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

    Private Sub Shuffle()
        If Not BackgroundWorker1.IsBusy Then
            My.Computer.Audio.Play(My.Resources.R2D2, AudioPlayMode.BackgroundLoop)
        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
            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
    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
    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

                    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
                        If GameOver Then
                            My.Computer.Audio.Play(My.Resources.Force, AudioPlayMode.WaitToComplete)
                        End If
                        ' 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

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  :)
I hope George Lucas doesn't come after you for that one  ;) 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.
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 :)

Avatar of kaufmed
Flag of United States of America image

Link to home
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Avatar of nffvrxqgrcfqvvc

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.
kaufmed, we're very closer!!! just 2 minor thing:

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

what kind of controls are this?
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".
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
' Save the pallete
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)

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)
    End Sub
    Public Shared Sub AddImage(ByVal imagePaths() As String)
    End Sub
    Public Shared Sub 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)
    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)
        Next i
        Return imgItems
    End Function

End Class

Open in new window

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

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.
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"
                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
    End Sub

Open in new window

in fact, the 'bin' folder. thanks.
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.
LOL... Sometimes you need to talk to the wall
I agree with you bud...but as he's learning still it's probably a bit much to wrap his head around.  ;)
>>  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  ;)