Table of Contents
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.
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#.
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.
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.
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.
Here is the main method for finding and turning the keys.
private void ToggleKeyStates(object sender, EventArgs e)
{
char[] XY = ((Key)sender).Name.ToCharArray(1, 2);
char X = XY[0];
char Y = XY[1];
foreach (Key l in this.panel1.Controls)
{
char[] xy = l.Name.ToCharArray(1, 2);
char x = xy[0];
char y = xy[1];
if (X == x || Y == y)
{
if(l.IsTurning == false)
l.TurnKey();
}
}
}
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.
public bool IsLocked
{
get{return m_isLocked;}
set {m_isLocked = value;}
}
public bool IsTurning
{
get{return m_isTurning;}
set{m_isTurning = value;}
}
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.
private void DefinePositions()
{
if (Degrees == 360)
{
Degrees = 0;
this.timer1.Enabled = false;
m_isTurning = !m_isTurning;
m_isLocked = false;
}
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
{
Degrees++;
m_isTurning = true;
}
this.Refresh();
}
The origin and surface of the circular path are defined in the DefineSurfacePath
method.
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;
x_path = (GetCos(360 + Degrees + x_center + degreeOffset) * Radius) + x_center;
y_path = (GetSin(360 + Degrees + y_center + degreeOffset) * Radius) + y_center;
x_path2 = (GetCos(360 + Degrees + 180 + x_center + degreeOffset) * Radius) + x_center;
y_path2 = (GetSin(360 + Degrees + 180 + y_center + degreeOffset) * Radius) + y_center;
}
The OnPaint(PaintEventArgs e)
override continuously calls DrawKey(Graphics g)
.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
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.
public void DrawKey(Graphics g)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
Brush brshREC = new SolidBrush(Color.White);
Pen p = new Pen(brshREC, 10);
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);
g.DrawLine(p, x_center, y_center, x_path, y_path);
g.DrawLine(p, x_center, y_center, x_path2, y_path2);
}
- 06-22-2010 - First version