Title: Make a jigsaw puzzle in C#
This example is a game sort of like a jigsaw puzzle. You load a picture, which the program breaks into tiles. You need to put the tiles in their proper positions to rebuild the image.
The program draws a grid on the background showing where the tiles belong. It outlines unpositioned tiles in black. Once you place a tile in its correct position, it is locked in that position, moved to the back of the stacking order, and outlined in white.
The program is simpler than you might imagine. It uses a Piece class to represent the current and "home" location of each piece. The program keeps the pieces in a List<Piece> named Pieces.
To draw the board, the program draws the background grid and then just loops through the pieces drawing them.
Unfortunately drawing images seems to be relatively slow so the first version of the program didn't redraw fast enough when the user was moving a piece and there were a large number of pieces. Each time the mouse moved, the program needed to redraw every piece and it was taking too long.
To avoid this problem, the new design uses a background image. When you start moving a piece, the program makes an image of the background without the piece that you are moving. Then instead of redrawing every piece, it only needs to redisplay this background and then draw the piece you are moving in its current position.
The following code shows the MakeBackground method that draws the background image.
// Make the background image without MovingPiece.
private void MakeBackground()
{
using (Graphics gr = Graphics.FromImage(Background))
{
gr.Clear(picPuzzle.BackColor);
// Draw a grid on the background.
DrawGrid(gr);
// Draw the pieces.
DrawPieces(gr);
}
picPuzzle.Visible = true;
picPuzzle.Refresh();
}
This method creates a Graphics object to draw on the Background bitmap and clears it. If then calls the DrawGrid and DrawPieces methods to draw the grid and pieces. It finishes by refreshing the picPuzzle PictureBox.
The following code shows the DrawGrid method.
// Draw a grid on the background.
private void DrawGrid(Graphics gr)
{
using (Pen thick_pen = new Pen(Color.DarkGray, 4))
{
for (int y = 0; y <= FullPicture.Height; y += RowHgt)
{
gr.DrawLine(thick_pen, 0, y, FullPicture.Width, y);
}
gr.DrawLine(thick_pen, 0, FullPicture.Height,
FullPicture.Width, FullPicture.Height);
for (int x = 0; x <= FullPicture.Width; x += ColWid)
{
gr.DrawLine(thick_pen, x, 0, x, FullPicture.Height);
}
gr.DrawLine(thick_pen, FullPicture.Width, 0,
FullPicture.Width, FullPicture.Height);
}
}
This code loops over the picture drawing grid lines.
The following code shows the DrawPieces method.
// Draw the pieces.
private void DrawPieces(Graphics gr)
{
using (Pen white_pen = new Pen(Color.White, 3))
{
using (Pen black_pen = new Pen(Color.Black, 3))
{
foreach (Piece piece in Pieces)
{
// Don't draw the piece we are moving.
if (piece != MovingPiece)
{
gr.DrawImage(FullPicture,
piece.CurrentLocation,
piece.HomeLocation,
GraphicsUnit.Pixel);
if (!GameOver)
{
if (piece.IsHome())
{
// Draw locked pieces with
// a white border.
gr.DrawRectangle(white_pen,
piece.CurrentLocation);
}
else
{
// Draw locked pieces with
// a black border.
gr.DrawRectangle(black_pen,
piece.CurrentLocation);
}
}
}
}
}
}
}
This method loops through the pieces. To draw a piece, the code copies the part of the original picture given by the rectangle HomeLocation to the board position given by the rectangle CurrentLocation. If the game isn't over, the method then outlines the piece with a black or white rectangle, depending on whether the piece is in its correct position.
The following code shows the DrawBoard method that redraws the board after the Background is ready.
// Draw the board.
private void DrawBoard()
{
using (Graphics gr = Graphics.FromImage(Board))
{
// Restore the background.
gr.DrawImage(Background, 0, 0,
Background.Width, Background.Height);
// Draw MovingPiece.
if (MovingPiece != null)
{
gr.DrawImage(FullPicture,
MovingPiece.CurrentLocation,
MovingPiece.HomeLocation,
GraphicsUnit.Pixel);
using (Pen blue_pen = new Pen(Color.Blue, 4))
{
gr.DrawRectangle(blue_pen,
MovingPiece.CurrentLocation);
}
}
}
picPuzzle.Visible = true;
picPuzzle.Refresh();
}
This code draws the Background image onto the Bitmap named Board. The picPuzzle PictureBox displays Board as its image.
Next the code draws the piece that the user is currently moving. It finishes by refreshing picBoard.
See the code for additional details.
There's still room for many improvements.
- Pieces with jigsaw-style edges rather than rectangles. That would let you fit pieces by shape and find the picture's edges.
- Rotating pieces. That would be very advanced.
- Pieces that stick together. When you join two pieces that belong together, it would be nice if they stuck so you could move them as a unit.
- Faster building of the background image.
I have an idea about the last one. Instead of rebuilding the whole Background image, you could find the piece under the mouse and just redraw any other pieces that overlap with that one. I think that would be much faster. I'll leave it to you (email me if you try it) or when I have more time.
Download the example to experiment with it and to see additional details.
|