Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Lock Puzzle: A Perplexing Puzzle

4.68/5 (26 votes)
22 Jun 2010CPOL3 min read 35.2K   840  
A brain melting puzzle in which you must turn several interconnected keys to unlock the solution

fig1.jpgfig2.jpg

Table of Contents

Introduction

This project is a dangerously addictive and somewhat hypnotic puzzle game in which the player tries to situate all 16 keys to the vertical position. The rules are as follows:

  • The lock has 16 keys arranged in a 4x4 array; each key oriented either horizontally or vertically.
  • In order to open the lock, all the keys must be vertically oriented.
  • When you turn a key to another position, all the other keys in the same row and column automatically switch their positions too.

Background

In this article, I wish to show how to create a game derived from the mathematical problem named "The Lock Problem." Professor Paul Zeitz presented this problem in his recent lecture series titled "The Art and Craft of Mathematical Problem Solving" and after seeing this problem, I became inspired to transform it into a simple puzzle game using C#.

Points of Interest

Well, let us organize our pieces first. The board has 16 elements in four rows and four columns. How would we arrange everything so that clicking on one key also turns the other six respective keys? There may be several methods of achieving this but this is what I decided to try. I first assign each element of the 4x4 array with a numerical ID based on its position in the array. The first number represents the row and the second number represents the column.

fig3.jpg

The element in the first row of the first column is named L11, whereas, the second element in the third row is named L32. Let us click on key L32.

fig4.jpg

Our code needs to step through a list of elements from L11-L44 and find which ones have an ID that correspond with the clicked element. In the case of L32, we want to find any ID that begins with 3 OR ends with 2.

fig5.jpg

Here is the main method for finding and turning the keys.

C#
private void ToggleKeyStates(object sender, EventArgs e)
{
    // Get Sender cell coordinates and assign them to X and X char arrays
    // This is the Key that was clicked
    char[] XY = ((Key)sender).Name.ToCharArray(1, 2);
    char X = XY[0];
    char Y = XY[1];
    
    // Using the clicked object as a reference, 
    // Cycle through controls and find the corresponding key objects
    foreach (Key l in this.panel1.Controls)
    {
        // split the names of the key objects into char array
        char[] xy = l.Name.ToCharArray(1, 2);
        char x = xy[0];
        char y = xy[1];
        
        // Change state of rows and columns only
        if (X == x || Y == y)
        {
            if(l.IsTurning == false)
                l.TurnKey();
        }
    }
}

Using the Code

A few words about the Key control: It has two public Boolean properties named isTurning and isLocked. The isTurning property tells the calling method that the key is in the process of rotating. The isLocked property, if true, tells the calling method that the key is in the horizontal locked position.

C#
public bool IsLocked
{
    get{return m_isLocked;}
    set {m_isLocked = value;}
}
public bool IsTurning
{
    get{return m_isTurning;}
    set{m_isTurning = value;}
}

Lock and Unlock

When we click the key object, the isTurning and isLocked properties are toggled, the timer starts, and the key rotation is set into motion. The timer, when enabled, continuously calls DefinePositions()which sets the limits for the outermost points on the lines to situate in either the 12, 3, 6, or 9 o'clock positions. The key rotates to either a horizontal or a vertical position, the timer is then disabled and the rotation stops.

C#
private void DefinePositions()
{
    // Rotate and stop at the 3, 6, 9, and 12 o'clock positions.
    if (Degrees == 360)
    {
        Degrees = 0;                    // reset
        this.timer1.Enabled = false;    // stop timer
        m_isTurning = !m_isTurning;     // toggle turning flag
        m_isLocked = false;             // toggle locked flag
    }
    else if (Degrees == 90)
    {
        this.timer1.Enabled = false;
        m_isTurning = !m_isTurning;
        m_isLocked = true;
        Degrees++;
    }
    else if (Degrees == 180)
    {
        this.timer1.Enabled = false;
        m_isTurning = !m_isTurning;
        m_isLocked = false;
        Degrees++;
    }
    else if (Degrees == 270)
    {
        this.timer1.Enabled = false;
        m_isTurning = !m_isTurning;
        m_isLocked = true;
        Degrees++;
    }
    else  // keep moving and let us know that you are moving.
    {
        Degrees++;
        m_isTurning = true;
    }
    
    // Update the form and controls.
    this.Refresh();
}

Following the Path

The origin and surface of the circular path are defined in the DefineSurfacePath method.

C#
private void DefineSurfacePath(out int x_center, out int y_center, 
	out float x_path, out float y_path, out float x_path2, out float y_path2)
{
    int Radius = 30;
    x_center = this.Width / 2;
    y_center = this.Height / 2;
    
    // define outer rotation path for first line
    x_path = (GetCos(360 + Degrees + x_center + degreeOffset) * Radius) + x_center;
    y_path = (GetSin(360 + Degrees + y_center + degreeOffset) * Radius) + y_center;
    
    // define outer rotation path for second line
    x_path2 = (GetCos(360 + Degrees + 180 + x_center + degreeOffset) * Radius) + x_center;
    y_path2 = (GetSin(360 + Degrees + 180 + y_center + degreeOffset) * Radius) + y_center;
}

Rendering

The OnPaint(PaintEventArgs e) override continuously calls DrawKey(Graphics g).

C#
protected override void OnPaint(PaintEventArgs e)
{
    // give .net the first try
    base.OnPaint(e);

    // then take over
    Graphics g = e.Graphics;
    DrawKey(g);
}

After all the Drawing Points and Positions are defined, the DrawKey(Graphics g) method simply draws the given values to the screen.

C#
public void DrawKey(Graphics g)
{
    // ake sure all graphics are smooth, not blocky.
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.CompositingQuality = CompositingQuality.HighQuality;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    
    // Declare Brush and pen objects
    Brush brshREC = new SolidBrush(Color.White);
    Pen p = new Pen(brshREC, 10);
    
    // Declare radius and origin of circle
    int x_center;
    int y_center;
    float x_path;
    float y_path;
    float x_path2;
    float y_path2;
    DefineSurfacePath(out x_center, out y_center, out x_path, out y_path, 
	out x_path2, out y_path2);
   
    // draw previously defined points
    g.DrawLine(p, x_center, y_center, x_path, y_path);
    g.DrawLine(p, x_center, y_center, x_path2, y_path2);
}

History

  • 06-22-2010 - First version

License

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