Click here to Skip to main content
Click here to Skip to main content

WPF Rubik's Cube

By , 31 Jan 2012
 
Prize winner in Competition "Best VB.NET article of February 2012"

Screenshot_1.png

Introduction

I find the Rubik's Cube to be a really captivating and fascinating puzzle and since WPF has 3-D capabilities, I decided to try making a Rubik's cube application that would closely replicate the real thing, both visually and interactively.

Background

3-D support in WPF is not designed to provide full-featured game development capabilities but it is possible to emulate some simple games/toys, like the Rubik's cube. Please note that this article is not an introduction to 3-D graphics support in WPF, so I hope that you are at least conversant with the relevant details in this area. (I'm not exactly familiar with all the intricate details of 3-D support in WPF but I know enough to have made this application). If you are unfamiliar with the details of WPF 3-D support, or want to know more, then you can take a look at the various resources that are available online, like here on CodeProject.

Another thing that I hope you are also familiar with is some of the cubing terms. Just in case, this is how you denote the faces of a cube,

Cube_Faces.png

Using the Application

To turn a layer of the cube hold down the left mouse button and swipe in the direction you intend to rotate the layer. Once you let go of the left-mouse button the layer will rotate as intended.

Layer_Rotation_1.png

Rotating the U layer anti-clockwise

Layer_Rotation_2.png

You can also rotate the whole cube using the right-mouse button. Just hold down the right-mouse button and swipe across the intended axis of rotation. The cube will rotate once you let go off the right-mouse button.

Cube_Rotation_1.png

Rotating the cube around the Z-axis

Cube_Rotation_2.png

Note that to rotate a layer, or the whole cube; you only interact with the F and R faces of the cube. Also note that rotations will only occur if the swipe is along a particular layer, horizontally or vertically, and crosses two or more cubies/cubelets.

To scramble the cube, just click on the Scramble button.

Design & Layout

Designing the cube by hand-coding XAML would have been torture. Instead I opted to model the cube in Blender. After studying a few important Blender details; like moving, rotating, scaling, and applying materials to objects, I played around with the application for a while and ended up with a model that looks like a Rubik's cube.

Blender_Screenshot_1.png

Modelling of the cube in progress

Blender_Screenshot_2.png

The final result

After modeling of the cube was complete I exported the model as a Wavefront (.obj) file and then added the object (.obj) file and the material (.mtl) file that was generated to the WPF project, in Expression Blend. The object file and the material file can be found in the 3D_Cube folder.

Solution_Explorer.png

The material and object files

Once the material and object file were added to the project it was just a matter of right-clicking the object file and selecting Insert, from the context-menu, to add the 3-D content to the artboard, in Expression Blend. The Viewport3D object that was created contained quite a number of ModelVisual3D objects, including a PerspectiveCamera and several lights.

In order for the cube not to appear like just a black mass with coloured stickers, I added a good number of lights for adequate illumination.

Lights_In_Blend.png

"All of the lights, all of the lights..."

The Viewport3D object is overlaid with 18 Path objects that are used to respond to mouse events. I could have opted to use ModelUIElement3D objects; that support input, focus, and events, but opted to stick with ModelVisual3D objects.

Paths.png

Each of the ModelVisual3D objects that represent cubies is named according to the colors of its 'stickers' e.g. WGR_Cubie is the White-Green-Red corner piece.

The Code

The Location enumeration contains members representing the location of a cubie in 3-D space.

Enum Location
    ' Layers viewed from U-to-D
    ' =========================
    '
    ' First layer locations.
    FUL
    FU
    FUR
    RU
    BUR
    BU
    BUL
    LU
    UC

    ' Second layer locations.
    FL
    FC
    FR
    RC
    BR
    BC
    BL
    LC

    ' Last layer locations.
    FDL
    FD
    FDR
    RD
    BDR
    BD
    BDL
    LD
    DC
End Enum

Each cubie in the Viewport3D is associated with an object of class Cubie, which contains methods for rotating the cubelet around a particular axis,

Imports System.Windows.Media.Media3D
Imports System.Windows.Media.Animation

Public Class Cubie

    Public cubelet As ModelVisual3D

    Private _cubieLocation As Location

    Private axisPoint As New Point3D(0, 0, 0)
    Private axisAngleRtn3D As New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)

    Private dblAnim As DoubleAnimation
    Private rtnTrans3D As RotateTransform3D
    Private transGroup As New Transform3DGroup

    Private milliSec As Short = 280

    ''' <summary>
    ''' Location of the cubie in 3D space; in terms  
    ''' of an enum Location value.
    ''' </summary>    
    Friend Property CubieLocation() As Location
        Get
            Return _cubieLocation
        End Get
        Set(ByVal value As Location)
            _cubieLocation = value
        End Set
    End Property

    ''' <summary>
    ''' Rotate cubie around the X-axis.
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90 or 90.</param>
    Public Sub RotateAround_X_Axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(1, 0, 0), 0)
        rtnTrans3D = New RotateTransform3D(axisAngleRtn3D, axisPoint)
        dblAnim = New DoubleAnimation(CDbl(angle), TimeSpan.FromMilliseconds(milliSec), FillBehavior.HoldEnd)

        axisAngleRtn3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, dblAnim)
        transGroup.Children.Add(rtnTrans3D)
        cubelet.Transform = transGroup

        ' Change value of _cubeLocation accordingly.
        ChangeLocationOn_X_AxisRtn(angle)
    End Sub

    ''' <summary>
    ''' Rotate cubie around the Y-axis.
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90 or 90.</param>
    Public Sub RotateAround_Y_Axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 1, 0), 0)
        rtnTrans3D = New RotateTransform3D(axisAngleRtn3D, axisPoint)
        dblAnim = New DoubleAnimation(CDbl(angle), TimeSpan.FromMilliseconds(milliSec), FillBehavior.HoldEnd)

        axisAngleRtn3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, dblAnim)
        transGroup.Children.Add(rtnTrans3D)
        cubelet.Transform = transGroup

        ' Change value of _cubeLocation accordingly.
        ChangeLocationOn_Y_AxisRtn(angle)
    End Sub

    ''' <summary>
    ''' Rotate cubie around the Z-axis.
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90 or 90.</param>
    Public Sub RotateAround_Z_Axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)
        rtnTrans3D = New RotateTransform3D(axisAngleRtn3D, axisPoint)
        dblAnim = New DoubleAnimation(CDbl(angle), TimeSpan.FromMilliseconds(milliSec), FillBehavior.HoldEnd)

        axisAngleRtn3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, dblAnim)
        transGroup.Children.Add(rtnTrans3D)
        cubelet.Transform = transGroup

        ' Change value of _cubeLocation accordingly.
        ChangeLocationOn_Z_AxisRtn(angle)
    End Sub

    Private Sub ChangeLocationOn_X_AxisRtn(ByVal angle As Integer)
        ' Looking from R-to-L
        ' ===================
        ' 90 degree angle (anti-clockwise rotation).
        If angle > 0 Then
            Select Case _cubieLocation
                ' First layer.
                Case Location.FUR
                    _cubieLocation = Location.FDR
                Case Location.RU
                    _cubieLocation = Location.FR
                Case Location.BUR
                    _cubieLocation = Location.FUR
                Case Location.BR
                    _cubieLocation = Location.RU
                Case Location.BDR
                    _cubieLocation = Location.BUR
                Case Location.RD
                    _cubieLocation = Location.BR
                Case Location.FDR
                    _cubieLocation = Location.BDR
                Case Location.FR
                    _cubieLocation = Location.RD
                ...    
            End Select
        Else
            ' -90 degree angle (clockwise rotation).
            Select Case _cubieLocation
                ' First layer.
                Case Location.FUR
                    _cubieLocation = Location.BUR
                Case Location.RU
                    _cubieLocation = Location.BR
                Case Location.BUR
                    _cubieLocation = Location.BDR
                Case Location.BR
                    _cubieLocation = Location.RD
                Case Location.BDR
                    _cubieLocation = Location.FDR
                Case Location.RD
                    _cubieLocation = Location.FR
                Case Location.FDR
                    _cubieLocation = Location.FUR
                Case Location.FR
                    _cubieLocation = Location.RU
                ...   
            End Select
        End If
    End Sub

    Private Sub ChangeLocationOn_Y_AxisRtn(ByVal angle As Integer)
        ' Looking from U-to-D
        ' ===================
        ' 90 degree angle (anti-clockwise rotation).
        If angle > 0 Then
            Select Case _cubieLocation
                ...
                    ' Second layer.
                Case Location.FL
                    _cubieLocation = Location.FR
                Case Location.FC
                    _cubieLocation = Location.RC
                Case Location.FR
                    _cubieLocation = Location.BR
                Case Location.RC
                    _cubieLocation = Location.BC
                Case Location.BR
                    _cubieLocation = Location.BL
                Case Location.BC
                    _cubieLocation = Location.LC
                Case Location.BL
                    _cubieLocation = Location.FL
                Case Location.LC
                    _cubieLocation = Location.FC
                ...    
            End Select
        Else
            ' -90 degree angle (clockwise rotation).
            Select Case _cubieLocation
                ...
                    ' Second layer.
                Case Location.FL
                    _cubieLocation = Location.BL
                Case Location.FC
                    _cubieLocation = Location.LC
                Case Location.FR
                    _cubieLocation = Location.FL
                Case Location.RC
                    _cubieLocation = Location.FC
                Case Location.BR
                    _cubieLocation = Location.FR
                Case Location.BC
                    _cubieLocation = Location.RC
                Case Location.BL
                    _cubieLocation = Location.BR
                Case Location.LC
                    _cubieLocation = Location.BC
                ...    
            End Select
        End If
    End Sub

    Private Sub ChangeLocationOn_Z_AxisRtn(ByVal angle As Integer)
        ' Looking from F-to-B
        ' ===================
        ' 90 degree angle (anti-clockwise rotation).
        If angle > 0 Then
            Select Case _cubieLocation
                ...
                    ' Third layer.
                Case Location.BUL
                    _cubieLocation = Location.BDL
                Case Location.BU
                    _cubieLocation = Location.BL
                Case Location.BUR
                    _cubieLocation = Location.BUL
                Case Location.BR
                    _cubieLocation = Location.BU
                Case Location.BDR
                    _cubieLocation = Location.BUR
                Case Location.BD
                    _cubieLocation = Location.BR
                Case Location.BDL
                    _cubieLocation = Location.BDR
                Case Location.BL
                    _cubieLocation = Location.BD
            End Select
        Else
            ' -90 degree angle (clockwise rotation).
            Select Case _cubieLocation
                ...
                    ' Third layer.
                Case Location.BUL
                    _cubieLocation = Location.BUR
                Case Location.BU
                    _cubieLocation = Location.BR
                Case Location.BUR
                    _cubieLocation = Location.BDR
                Case Location.BR
                    _cubieLocation = Location.BD
                Case Location.BDR
                    _cubieLocation = Location.BDL
                Case Location.BD
                    _cubieLocation = Location.BL
                Case Location.BDL
                    _cubieLocation = Location.BUL
                Case Location.BL
                    _cubieLocation = Location.BU
            End Select
        End If
    End Sub
End Class

The ModelVisual3D objects that make up a particular cubie are associated with a Cubie object when the PopulateList() method in class Rotater is called by its constructor.

    Private Sub PopulateList()
        ' Layers viewed from U-to-D
        ' =========================
        ' CubieLocations are the initial locations of cubies 
        ' in 3D space with White-U, Green-F, & Red-R. 
        ' ==================================================
        '
        ' First layer.
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FUL, .cubelet = mainWin.WGO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FU, .cubelet = mainWin.WG_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FUR, .cubelet = mainWin.WGR_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.RU, .cubelet = mainWin.WR_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BUR, .cubelet = mainWin.WRB_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BU, .cubelet = mainWin.WB_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BUL, .cubelet = mainWin.WBO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.LU, .cubelet = mainWin.WO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.UC, .cubelet = mainWin.WC_Cubie})

        ' Second layer.
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FL, .cubelet = mainWin.GO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FC, .cubelet = mainWin.GC_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FR, .cubelet = mainWin.GR_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.RC, .cubelet = mainWin.RC_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BR, .cubelet = mainWin.RB_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BC, .cubelet = mainWin.BC_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BL, .cubelet = mainWin.BO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.LC, .cubelet = mainWin.OC_Cubie})

        ' Third layer.
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FDL, .cubelet = mainWin.YGO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FD, .cubelet = mainWin.YG_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.FDR, .cubelet = mainWin.YGR_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.RD, .cubelet = mainWin.YR_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BDR, .cubelet = mainWin.YRB_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BD, .cubelet = mainWin.YB_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.BDL, .cubelet = mainWin.YBO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.LD, .cubelet = mainWin.YO_Cubie})
        cubiesList.Add(New Cubie With {.CubieLocation = Location.DC, .cubelet = mainWin.YC_Cubie})
    End Sub

As I mentioned earlier, the Path objects that overlay the Viewport3D are used to detect mouse events in order to carry out the required rotation.

    Private Sub Paths_PreviewMouseLeftButtonDown(ByVal sender As Object, _
                                                 ByVal e As System.Windows.Input.MouseButtonEventArgs) _
    Handles FU_1.PreviewMouseLeftButtonDown, FU_2.PreviewMouseLeftButtonDown, FU_3.PreviewMouseLeftButtonDown, _
    FM_1.PreviewMouseLeftButtonDown, FM_2.PreviewMouseLeftButtonDown, FM_3.PreviewMouseLeftButtonDown, _
    FD_1.PreviewMouseLeftButtonDown, FD_2.PreviewMouseLeftButtonDown, FD_3.PreviewMouseLeftButtonDown, _
    RU_1.PreviewMouseLeftButtonDown, RU_2.PreviewMouseLeftButtonDown, RU_3.PreviewMouseLeftButtonDown, _
    RM_1.PreviewMouseLeftButtonDown, RM_2.PreviewMouseLeftButtonDown, RM_3.PreviewMouseLeftButtonDown, _
    RD_1.PreviewMouseLeftButtonDown, RD_2.PreviewMouseLeftButtonDown, RD_3.PreviewMouseLeftButtonDown

        cPath_1 = CType(sender, Path)

    End Sub

    Private Sub Paths_PreviewMouseLeftButtonUp(ByVal sender As Object, _
                                               ByVal e As System.Windows.Input.MouseButtonEventArgs) _
    Handles FU_1.PreviewMouseLeftButtonUp, FU_2.PreviewMouseLeftButtonUp, FU_3.PreviewMouseLeftButtonUp, _
    FM_1.PreviewMouseLeftButtonUp, FM_2.PreviewMouseLeftButtonUp, FM_3.PreviewMouseLeftButtonUp, _
    FD_1.PreviewMouseLeftButtonUp, FD_2.PreviewMouseLeftButtonUp, FD_3.PreviewMouseLeftButtonUp, _
    RU_1.PreviewMouseLeftButtonUp, RU_2.PreviewMouseLeftButtonUp, RU_3.PreviewMouseLeftButtonUp, _
    RM_1.PreviewMouseLeftButtonUp, RM_2.PreviewMouseLeftButtonUp, RM_3.PreviewMouseLeftButtonUp, _
    RD_1.PreviewMouseLeftButtonUp, RD_2.PreviewMouseLeftButtonUp, RD_3.PreviewMouseLeftButtonUp

        cPath_2 = CType(sender, Path)

        If (cPath_1 IsNot cPath_2) Then
            rt.RotateLayer(cPath_1, cPath_2)
        End If

    End Sub

    Private Sub Paths_PreviewMouseRightButtonDown(ByVal sender As Object, _
                                                  ByVal e As System.Windows.Input.MouseButtonEventArgs) _
    Handles FU_1.PreviewMouseRightButtonDown, FU_2.PreviewMouseRightButtonDown, FU_3.PreviewMouseRightButtonDown, _
    FM_1.PreviewMouseRightButtonDown, FM_2.PreviewMouseRightButtonDown, FM_3.PreviewMouseRightButtonDown, _
    FD_1.PreviewMouseRightButtonDown, FD_2.PreviewMouseRightButtonDown, FD_3.PreviewMouseRightButtonDown, _
    RU_1.PreviewMouseRightButtonDown, RU_2.PreviewMouseRightButtonDown, RU_3.PreviewMouseRightButtonDown, _
    RM_1.PreviewMouseRightButtonDown, RM_2.PreviewMouseRightButtonDown, RM_3.PreviewMouseRightButtonDown, _
    RD_1.PreviewMouseRightButtonDown, RD_2.PreviewMouseRightButtonDown, RD_3.PreviewMouseRightButtonDown

        cPath_1 = CType(sender, Path)

    End Sub

    Private Sub Paths_PreviewMouseRightButtonUp(ByVal sender As Object, _
                                                ByVal e As System.Windows.Input.MouseButtonEventArgs) _
    Handles FU_1.PreviewMouseRightButtonUp, FU_2.PreviewMouseRightButtonUp, FU_3.PreviewMouseRightButtonUp, _
    FM_1.PreviewMouseRightButtonUp, FM_2.PreviewMouseRightButtonUp, FM_3.PreviewMouseRightButtonUp, _
    FD_1.PreviewMouseRightButtonUp, FD_2.PreviewMouseRightButtonUp, FD_3.PreviewMouseRightButtonUp, _
    RU_1.PreviewMouseRightButtonUp, RU_2.PreviewMouseRightButtonUp, RU_3.PreviewMouseRightButtonUp, _
    RM_1.PreviewMouseRightButtonUp, RM_2.PreviewMouseRightButtonUp, RM_3.PreviewMouseRightButtonUp, _
    RD_1.PreviewMouseRightButtonUp, RD_2.PreviewMouseRightButtonUp, RD_3.PreviewMouseRightButtonUp

        cPath_2 = CType(sender, Path)

        If (cPath_1 IsNot cPath_2) Then
            rt.RotateCube(cPath_1, cPath_2)
        End If

    End Sub

The RotateLayer() method in class Rotater calls several methods which determine which Path objects the user interacted with so as to respond accordingly.

    Public Sub RotateLayer(ByVal path_1 As Path, ByVal path_2 As Path)
        HorizontalSwipeAcross_F_Paths(path_1, path_2)
        HorizontalSwipeAcross_R_Paths(path_1, path_2)
        HorizontalSwipeAcross_FandR_Paths(path_1, path_2)

        VerticalSwipeOn_F_Paths(path_1, path_2)
        VerticalSwipeOn_R_Paths(path_1, path_2)
    End Sub


    ''' <summary>
    ''' Check which horizontal layer the user intends to rotate when 
    ''' the user swipes horizontally across the front face of the
    ''' cube, and rotate the layer. 
    ''' </summary>   
    Private Sub HorizontalSwipeAcross_F_Paths(ByVal path_1 As Path, ByVal path_2 As Path)
        ' Horizontal swipe on FU... Paths; L-to-R mouse swipe.    
        If (path_1 Is mainWin.FU_1 AndAlso path_2 Is mainWin.FU_2) Then
            RotateFU_RU_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.FU_1 AndAlso path_2 Is mainWin.FU_3) Then
            RotateFU_RU_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.FU_2 AndAlso path_2 Is mainWin.FU_3) Then
            RotateFU_RU_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        End If
        ' Horizontal swipe on FU... Paths; R-to-L mouse swipe.
        If (path_1 Is mainWin.FU_3 AndAlso path_2 Is mainWin.FU_2) Then
            RotateFU_RU_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.FU_3 AndAlso path_2 Is mainWin.FU_1) Then
            RotateFU_RU_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.FU_2 AndAlso path_2 Is mainWin.FU_1) Then
            RotateFU_RU_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        End If
        ...        
    End Sub
    
    ''' <summary>
    ''' Check which horizontal layer the user intends to rotate when 
    ''' the user swipes horizontally across the right face of the
    ''' cube, and rotate the layer.  
    ''' </summary> 
    Private Sub HorizontalSwipeAcross_R_Paths(ByVal path_1 As Path, ByVal path_2 As Path)
        ...
        ' =======================================================
        ' Horizontal swipe on RM... Paths; L-to-R mouse swipe.
        If (path_1 Is mainWin.RM_1 AndAlso path_2 Is mainWin.RM_2) Then
            RotateFM_RM_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RM_1 AndAlso path_2 Is mainWin.RM_3) Then
            RotateFM_RM_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RM_2 AndAlso path_2 Is mainWin.RM_3) Then
            RotateFM_RM_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        End If
        ' Horizontal swipe on RM... Paths; R-to-L mouse swipe.
        If (path_1 Is mainWin.RM_3 AndAlso path_2 Is mainWin.RM_2) Then
            RotateFM_RM_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RM_3 AndAlso path_2 Is mainWin.RM_1) Then
            RotateFM_RM_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RM_2 AndAlso path_2 Is mainWin.RM_1) Then
            RotateFM_RM_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        End If
        ...
    End Sub

    ''' <summary>
    ''' Check for horizontal swipes that cross from the front face of
    ''' the cube to the right face, and vice-versa, and rotate the 
    ''' appopriate layer.
    ''' </summary>   
    Private Sub HorizontalSwipeAcross_FandR_Paths(ByVal path_1 As Path, ByVal path_2 As Path)
        ' Horizontal swipe crossing from FU Paths to RU Paths
        ' and vice-versa.
        If (path_1 Is mainWin.FU_2 AndAlso path_2 Is mainWin.RU_1) Then
            RotateFU_RU_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.FU_3 AndAlso path_2 Is mainWin.RU_1) Then
            RotateFU_RU_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RU_2 AndAlso path_2 Is mainWin.FU_3) Then
            RotateFU_RU_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        ElseIf (path_1 Is mainWin.RU_1 AndAlso path_2 Is mainWin.FU_3) Then
            RotateFU_RU_LayerAroundY_Axis(CLOCKWISE_ANGLE)
        End If
        ...
    End Sub    

The methods called by the various methods highlighted above determine which cubies to rotate to create the illusion of a layer rotation e.g. the following methods lead to layer rotation around the Y-axis,

    Public Sub RotateFU_RU_LayerAroundY_Axis(ByVal angle As Double)
        Dim cubiesToMove = cubiesList.Where _
           (Function(c) c.CubieLocation = Location.FUL Or c.CubieLocation = Location.FU Or _
                c.CubieLocation = Location.FUR Or c.CubieLocation = Location.RU Or _
                c.CubieLocation = Location.BUR Or c.CubieLocation = Location.BU Or _
                c.CubieLocation = Location.BUL Or c.CubieLocation = Location.LU Or _
                c.CubieLocation = Location.UC)

        For Each c In cubiesToMove
            c.RotateAround_Y_Axis(angle)
        Next
    End Sub

    Private Sub RotateFM_RM_LayerAroundY_Axis(ByVal angle As Double)
        Dim cubiesToMove = cubiesList.Where _
            (Function(c) c.CubieLocation = Location.FL Or c.CubieLocation = Location.FC Or _
                 c.CubieLocation = Location.FR Or c.CubieLocation = Location.RC Or _
                 c.CubieLocation = Location.BR Or c.CubieLocation = Location.BC Or _
                 c.CubieLocation = Location.BL Or c.CubieLocation = Location.LC)

        For Each c In cubiesToMove
            c.RotateAround_Y_Axis(angle)
        Next
    End Sub

    Public Sub RotateFD_RD_LayerAroundY_Axis(ByVal angle As Double)
        Dim cubiesToMove = cubiesList.Where _
           (Function(c) c.CubieLocation = Location.FDL Or c.CubieLocation = Location.FD Or _
                c.CubieLocation = Location.FDR Or c.CubieLocation = Location.RD Or _
                c.CubieLocation = Location.BDR Or c.CubieLocation = Location.BD Or _
                c.CubieLocation = Location.BDL Or c.CubieLocation = Location.LD Or _
                c.CubieLocation = Location.DC)

        For Each c In cubiesToMove
            c.RotateAround_Y_Axis(angle)
        Next
    End Sub

The class Scrambler deals with the scrambling of the cube.

Imports System.Windows.Threading

Public Class Scrambler

    Private rtr As Rotater
    Private mainWin As MainWindow

    Private rotations() As String = {"F", "F'", "F2", "R", "R'", "R2", _
                                     "B", "B'", "B2", "L", "L'", "L2", _
                                     "U", "U'", "U2", "D", "D'", "D2"}

    Private rndIndex As Integer = -1
    Private index As Integer
    Private listIndex As Integer
    Private isScrambling As Boolean

    Private Const CLOCKWISE_ANGLE As Double = -90
    Private Const ANTICLOCKWISE_ANGLE As Double = 90

    Private rnd As New Random
    Private scrambleTimer As DispatcherTimer
    Private rotationsList As New List(Of String)

    Public Sub New(ByRef rt As Rotater, ByRef win As MainWindow)
        rtr = rt
        mainWin = win

        scrambleTimer = New DispatcherTimer
        scrambleTimer.Interval = New TimeSpan(0, 0, 0, 0, 400)
        AddHandler scrambleTimer.Tick, AddressOf Timer_Tick
    End Sub

    Public Sub ScrambleCube()
        If (isScrambling = False) Then
            rotationsList.Clear()

            For i As Integer = 0 To 24
                PopulateRotationsList()
            Next
            ' Disable Grid containing the Viewport and Path
            ' elements for rotating layers.
            mainWin.ViewportAndCanvasGrid.IsEnabled = False

            isScrambling = True
            scrambleTimer.Start()
        Else
            MessageBox.Show("Scrambling of cube already in progress", _
                            "WPF Rubiks", MessageBoxButton.OK, MessageBoxImage.Exclamation)
        End If
    End Sub

    ''' <summary>
    ''' Populate rotationsList with disimillar Strings following
    ''' each other, and the next String in the List is not an
    ''' inverse or double of the preceding String e.g. F' does 
    ''' not come after F, or F2 after F.
    ''' </summary>
    Private Sub PopulateRotationsList()
        Dim n As Integer = rnd.Next(0, 18)
        Dim lastString As String = String.Empty

        If (rotationsList.Count > 0) Then
            lastString = rotationsList(rotationsList.Count - 1)
        End If

        If (n <> rndIndex) Then
            rndIndex = n
            If (lastString = "F" AndAlso rotations(rndIndex) = "F'") Then
                PopulateRotationsList()
            ElseIf (lastString = "F'" AndAlso rotations(rndIndex) = "F") Then
                PopulateRotationsList()
            ElseIf (lastString = "F" AndAlso rotations(rndIndex) = "F2") Then
                PopulateRotationsList()
            ElseIf (lastString = "F'" AndAlso rotations(rndIndex) = "F2") Then
                PopulateRotationsList()
            ElseIf (lastString = "F2" AndAlso rotations(rndIndex) = "F") Then
                PopulateRotationsList()
            ElseIf (lastString = "F2" AndAlso rotations(rndIndex) = "F'") Then
                PopulateRotationsList()
            ...
                '======================
            ElseIf (lastString = "L" AndAlso rotations(rndIndex) = "L'") Then
                PopulateRotationsList()
            ElseIf (lastString = "L'" AndAlso rotations(rndIndex) = "L") Then
                PopulateRotationsList()
            ElseIf (lastString = "L" AndAlso rotations(rndIndex) = "L2") Then
                PopulateRotationsList()
            ElseIf (lastString = "L'" AndAlso rotations(rndIndex) = "L2") Then
                PopulateRotationsList()
            ElseIf (lastString = "L2" AndAlso rotations(rndIndex) = "L") Then
                PopulateRotationsList()
            ElseIf (lastString = "L2" AndAlso rotations(rndIndex) = "L'") Then
                PopulateRotationsList()
            ...
                '======================
            ElseIf (lastString = "D" AndAlso rotations(rndIndex) = "D'") Then
                PopulateRotationsList()
            ElseIf (lastString = "D'" AndAlso rotations(rndIndex) = "D") Then
                PopulateRotationsList()
            ElseIf (lastString = "D" AndAlso rotations(rndIndex) = "D2") Then
                PopulateRotationsList()
            ElseIf (lastString = "D'" AndAlso rotations(rndIndex) = "D2") Then
                PopulateRotationsList()
            ElseIf (lastString = "D2" AndAlso rotations(rndIndex) = "D") Then
                PopulateRotationsList()
            ElseIf (lastString = "D2" AndAlso rotations(rndIndex) = "D'") Then
                PopulateRotationsList()
            Else
                rotationsList.Add(rotations(rndIndex))
            End If
        Else
            PopulateRotationsList()
        End If
    End Sub

    ''' <summary>
    ''' DispatcherTimer Tick event handler.
    ''' </summary>   
    Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
        If (index <= 24) Then
            index += 1
            RotateCubies()
        Else
            isScrambling = False
            scrambleTimer.Stop()

            index = 0
            listIndex = 0
            rndIndex = -1

            mainWin.ViewportAndCanvasGrid.IsEnabled = True
        End If
    End Sub

    Private Sub RotateCubies()

        Select Case rotationsList(listIndex)
            Case "F"
                rtr.RotateR_1PathsLayerAroundZ_Axis(CLOCKWISE_ANGLE)
            Case "F'"
                rtr.RotateR_1PathsLayerAroundZ_Axis(ANTICLOCKWISE_ANGLE)
            Case "F2"
                rtr.RotateR_1PathsLayerAroundZ_Axis(CLOCKWISE_ANGLE)
                rtr.RotateR_1PathsLayerAroundZ_Axis(CLOCKWISE_ANGLE)
            ...    
                '===================================================
            Case "L"
                rtr.RotateF_1PathsLayerAroundX_Axis(ANTICLOCKWISE_ANGLE)
            Case "L'"
                rtr.RotateF_1PathsLayerAroundX_Axis(CLOCKWISE_ANGLE)
            Case "L2"
                rtr.RotateF_1PathsLayerAroundX_Axis(ANTICLOCKWISE_ANGLE)
                rtr.RotateF_1PathsLayerAroundX_Axis(ANTICLOCKWISE_ANGLE)
            ...
                '=================================================
            Case "D"
                rtr.RotateFD_RD_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
            Case "D'"
                rtr.RotateFD_RD_LayerAroundY_Axis(CLOCKWISE_ANGLE)
            Case "D2"
                rtr.RotateFD_RD_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
                rtr.RotateFD_RD_LayerAroundY_Axis(ANTICLOCKWISE_ANGLE)
        End Select

        listIndex += 1
    End Sub

End Class

Conclusion

That's it. I hope you'll have a fun time solving the WPF Rubik's Cube. I have scrambled and solved it several times and it is quite as thrilling as the real thing.

History

  • 1st Feb, 2012: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Meshack Musundi
Software Developer
Kenya Kenya
Member
Meshack is an avid programmer with a bias towards WPF and VB.NET. He currently resides in a small town in Kiambu county, Kenya.
 
Awards;
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • Best VB.NET article of February 2013
  • Best VB.NET article of October 2012
  • Best VB.NET article of July 2012
  • Best VB.NET article of February 2012
  • Best VB.NET article of January 2012
  • Best VB.NET article of November 2011
  • Best VB.NET article of June 2011
  • Best VB.NET article of May 2011
  • Best VB.NET article of March 2011
  • Best VB.NET article of February 2011
  • Best VB.NET article of January 2011
  • Best VB.NET article of December 2010
  • Best VB.NET article of November 2010

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5membermichael azzar1 Jun '12 - 5:20 
QuestionYou're very Distinctive [modified]membermichael azzar1 Jun '12 - 5:18 
GeneralRe: You're very DistinctivemvpMeshack Musundi1 Jun '12 - 21:18 
GeneralRe: You're very Distinctivemembermichael azzar2 Jun '12 - 10:49 
GeneralRubik's CubememberVishalvPatil17 Apr '12 - 5:30 
GeneralRe: Rubik's CubemvpMeshack Musundi17 Apr '12 - 20:33 
GeneralMy vote of 5memberG-Tek3 Apr '12 - 3:49 
GeneralRe: My vote of 5mvpMeshack Musundi12 Apr '12 - 4:25 
GeneralMy vote of 5memberBrian Pendleton2 Apr '12 - 7:39 
GeneralRe: My vote of 5mvpMeshack Musundi12 Apr '12 - 4:22 
GeneralMy vote of 5memberReza Ahmadi2 Apr '12 - 5:45 
GeneralRe: My vote of 5mvpMeshack Musundi12 Apr '12 - 4:21 
GeneralMy vote of 5membermanoj kumar choubey19 Mar '12 - 19:35 
GeneralRe: My vote of 5mvpMeshack Musundi20 Mar '12 - 23:09 
GeneralMy vote of 5memberPolinia13 Mar '12 - 1:56 
GeneralRe: My vote of 5mvpMeshack Musundi14 Mar '12 - 21:07 
GeneralMy vote of 5memberAnurag Gandhi12 Mar '12 - 9:36 
GeneralRe: My vote of 5mvpMeshack Musundi12 Mar '12 - 20:50 
GeneralMy vote of 5memberProEnggSoft7 Mar '12 - 20:49 
GeneralRe: My vote of 5mvpMeshack Musundi7 Mar '12 - 20:53 
GeneralMy vote of 5memberAnt210016 Feb '12 - 3:06 
GeneralRe: My vote of 5mvpMeshack Musundi17 Feb '12 - 5:22 
QuestionVery finememberCIDev14 Feb '12 - 4:28 
GeneralRe: Very finemvpMeshack Musundi14 Feb '12 - 20:16 
GeneralMy vote of 5memberPankaj Chamria10 Feb '12 - 20:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 1 Feb 2012
Article Copyright 2012 by Meshack Musundi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid