Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: Rubik's Cube

0.00/5 (No votes)
24 Sep 2014 4  
A WPF 3D Rubik's Cube application

Introduction

I'm a huge fan of the Rubik's Cube and I solve it several times, almost daily. Ever since I got my first Rubik's Cube it has captivated me, and frustrated me in my attempts at getting faster at solving - I currently average 17 sec. It was only natural then that I took a shot at making a WPF version.

Background

The 3D APIs in WPF are not designed to provide full-featured game development capabilities but it is possible to develop something as 'simple' as a Rubik's Cube app. In this article I'll not delve into the finer details of WPF's 3D features so I will expect that you are conversant with that part of WPF. Another thing that you should be familiar with is basic cubing terms. Just in case, this is how you denote the faces of a cube,

322872/Cube_Faces.png

Using the App

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.

Rotating the U layer anti-clockwise

The same principle also applies to whole cube rotations but instead of using the left-mouse button you use the right-mouse button.

Note that to make rotations you only interact with the F and R faces of the cube. To scramble the cube click the Scramble button.

Design & Layout

Designing the 3D model of the cube by hand-coding XAML would have been a tedious affair so I instead opted to model it in Blender.

322872/Blender_Screenshot_1.png

Modelling of the cube in progress

322872/Blender_Screenshot_2.png

The final result

After modeling the cube in Blender I exported the 3D model as a Wavefront (.obj) file which I then added to my WPF project in Expression Blend, together with the corresponding material (.mtl) file.

The material and object files

With the material and object file in the project, adding the 3D model to a WPF container is just a matter of right-clicking the object file and selecting Insert from the context-menu.

For this project I added more lights for adequate illumination of the cube so the Viewport3D contains several PointLights, a DirectionalLight and an AmbientLight.

The Viewport3D is masked by several Paths that are used to deal with mouse events. I could have opted to use ModelUIElement3D - that support input, focus, and events - but opted to stick with ModelVisual3D objects.

Each ModelVisual3D object that represents a cube piece is named according to the colors of its 'stickers' e.g. WGR_Cubie is the White-Green-Red corner piece.

Code

The project contains three enumerations. The PieceLocations enumerations represent the location in 3D space of a cube piece.

Public Enum PieceLocations
    ' U layer locations
    FUL
    FU
    FUR
    RU
    BUR
    BU
    BUL
    LU
    UC
    ' E layer locations
    FL
    FC
    FR
    RC
    BR
    BC
    BL
    LC
    ' D layer locations
    FDL
    FD
    FDR
    RD
    BDR
    BD
    BDL
    LD
    DC
End Enum

The Notations enumerations represent the notations of a Rubik's Cube.

Public Enum Notations
    F
    F_prime
    F2
    B
    B_prime
    B2
    U
    U_prime
    U2
    D
    D_prime
    D2
    R
    R_prime
    R2
    L
    L_prime
    L2
End Enum

The FaceNotations enumerations represent the location of a Path on the faces of the cube. Remember that only two faces of the cube are masked by Paths.

Public Enum FaceLocations
    FUL
    FU
    FUR
    RUF
    RU
    RUB
    FL
    FC
    FR
    RF
    RC
    RB
    FDL
    FD
    FDR
    RDF
    RD
    RDB
End Enum

Class FacePath defines an attached property that is set on the masking Paths.

Public Class FacePath
    Public Shared ReadOnly LocationProperty As DependencyProperty =
        DependencyProperty.RegisterAttached("Location", GetType(FaceLocations), GetType(FacePath),
                                            New FrameworkPropertyMetadata(FaceLocations.FC,
                                                                          FrameworkPropertyMetadataOptions.None))

    Public Shared Sub SetLocation(ByVal element As Path, ByVal value As FaceLocations)
        element.SetValue(LocationProperty, value)
    End Sub

    Public Shared Function GetLocation(ByVal element As Path) As FaceLocations
        Return CType(element.GetValue(LocationProperty), FaceLocations)
    End Function
End Class

Each cube piece in the Viewport3D is associated with an object of class CubePiece which contains methods for rotating the associated ModelVisual3D around a particular axis.

Public Class CubePiece
    Public piece As ModelVisual3D

    Private axisPoint As New Point3D(0, 0, 0)
    Private axisAngleRtn3D As New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)
    Private dblAnimation As DoubleAnimation
    Private rotateTx3D As RotateTransform3D
    Private tx3dGroup As New Transform3DGroup

    Private Const ROTATION_TIME As Double = 200
    Friend Property PieceLocation() As PieceLocations

    Private Sub RotateAroundAxis(ByVal angle As Double)
        rotateTx3D = New RotateTransform3D(axisAngleRtn3D, axisPoint)
        dblAnimation = New DoubleAnimation(CDbl(angle), TimeSpan.FromMilliseconds(ROTATION_TIME),
                                           FillBehavior.HoldEnd)
        axisAngleRtn3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, dblAnimation)
        tx3dGroup.Children.Add(rotateTx3D)
        piece.Transform = tx3dGroup
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the X-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90&deg; or 90&deg;</param>
    Public Sub RotateAround_X_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(1, 0, 0), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnXaxisRotation(angle)
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the Y-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90&deg; or 90&deg;</param>
    Public Sub RotateAround_Y_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 1, 0), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnYaxisRotation(angle)
    End Sub

    ''' <summary>
    ''' Rotate cube piece around the Z-axis
    ''' </summary>
    ''' <param name="angle">The angle of rotation; -90&deg; or 90&deg;</param>
    Public Sub RotateAround_Z_axis(ByVal angle As Double)
        axisAngleRtn3D = New AxisAngleRotation3D(New Vector3D(0, 0, 1), 0)
        RotateAroundAxis(angle)
        ChangeLocationOnZaxisRotation(angle)
    End Sub
...
End Class

Class Scrambler contains methods for scramble generation and scrambling of the cube.

Public Class Scrambler
    Private index As Integer
    Private isScrambling As Boolean

    Private Const CLOCKWISE As Double = -90
    Private Const ANTI_CLOCKWISE As Double = 90

    Private mainWin As MainWindow
    Private rnd As Random
    Private scrambleTimer As DispatcherTimer
    Private scramble As List(Of Notations)

    Public Sub New(ByRef win As MainWindow)
        mainWin = win
        rnd = New Random
        scramble = New List(Of Notations)
        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
            scramble.Clear()
            ' Create a scramble with 20 values
            For i As Integer = 0 To 19
                AddToScramble()
            Next
            isScrambling = True
            scrambleTimer.Start()
            mainWin.CubeGroupGrid.IsEnabled = False
        Else
            MessageBox.Show("Scrambling of cube already in progress", _
                            "WPF Rubiks", MessageBoxButton.OK, MessageBoxImage.Exclamation)
        End If
    End Sub

    ''' <summary>
    ''' Create scramble with dissimilar values following each other, and the new value 
    ''' in the List is not an inverse or double of the preceding value e.g.
    ''' F' does not come after F, or F2 after F.
    ''' </summary>
    Private Sub AddToScramble()
        Dim randomNum As Integer = rnd.Next(0, 18)
        Dim newNotation As String = [Enum].GetName(GetType(Notations), randomNum)
        Dim notation As Notations

        If (scramble.Count > 0) Then
            Dim lastItem As String = scramble(scramble.Count - 1).ToString

            If newNotation.Contains("F") AndAlso lastItem.Contains("F") Then
                AddToScramble()
            ElseIf newNotation.Contains("R") AndAlso lastItem.Contains("R") Then
                AddToScramble()
            ElseIf newNotation.Contains("B") AndAlso lastItem.Contains("B") Then
                AddToScramble()
            ElseIf newNotation.Contains("L") AndAlso lastItem.Contains("L") Then
                AddToScramble()
            ElseIf newNotation.Contains("U") AndAlso lastItem.Contains("U") Then
                AddToScramble()
            ElseIf newNotation.Contains("D") AndAlso lastItem.Contains("D") Then
                AddToScramble()
            Else
                notation = [Enum].Parse(GetType(Notations), newNotation)
                scramble.Add(notation)
            End If
        Else
            notation = [Enum].Parse(GetType(Notations), newNotation)
            scramble.Add(notation)
        End If
    End Sub

    Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs)
        If (index < 19) Then
            RotateCubePieces(index)
            index += 1
        Else
            isScrambling = False
            scrambleTimer.Stop()
            index = 0
            mainWin.CubeGroupGrid.IsEnabled = True
        End If
    End Sub
...
End Class

The project contains a few more classes that deal mainly with determining which direction a layer or the cube should be rotated. You can take a look at these classes by downloading the project source from the download link at the top of the article page.

Conclusion

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,
  • 25th Sep, 2014: Updated article and source

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here