Screen Shuffling with Delphi (first step to a Sliding Puzzle Game)

I’m sure you’ve seen it already. When the program starts it takes a picture of the current desktop, the picture is then cut into a number of rectangular pieces (all of the same size). A piece at some random position is “removed”. The main code randomly swaps that piece with the one next to it. Only pieces adjacent to the removed one can be moved into it.

Implementation

Start a new Delphi (VCL) project with a blank form. Set the Name property to be ‘ShufflerForm’. Add an Image (Image1) and a Timer (Timer1) component to the form. Image will hold the (scrambled) picture of the Desktop and Timer will fire the drawing procedure. The Interval property of the Timer component determines how often the exchange takes place (the value of 1000 is equal to one second, 2000 is two seconds).
Several variables are required by the project. Place the next code above the implementation part of the form’s unit:

var
  ShufflerForm: TShufflerForm;

// add this
  desktopBitmap   : TBitmap;
  gx, gy          : Integer;
  redRect         : TBitmap;

  rW, rH          : Integer; //witdh and height of the shuffling rectangle

const
  DELTA = 8; //shoul be 2^n -> makes the Screen to be matrix DELTA x DELTA

• The integer constant DELTA determines in how many pieces (precisely: rows and columns) the screen will be split into. The DELTA number should be 2^n where n is a signed integer number. Bigger DELTA results in smaller pieces.
• desktopBitmap bitmap holds the captured image of the current Desktop screen – we get this picture by taking the screen shoot.
• redRect is a bitmap picture that replaces the “removed” piece of the picture. The redRect is created in the form’s OnCreate event.
• gx, gy hold the current x and y position (Left, Top) of the redRect inside the scrambled picture.
• rW, rH are width and Height of a rectangular part of the picture. For the 1024×768 and DELTA=16 the rW is 64 and rH is 48.

The project starts in the form’s OnCreate event handler:

procedure TShufflerForm.FormCreate(Sender: TObject);
begin
  rW := Screen.Width div DELTA;
  rH := Screen.Height div DELTA;

  redRect := TBitmap.Create;
  with redRect do
  begin
    Width := rW;
    Height := rH;
    Canvas.Brush.Color := clFuchsia;
    Canvas.Brush.Style := bssolid;
    Canvas.Pen.Style := psClear;
    Canvas.Rectangle(0,0,rW,rH);
    Canvas.Font.Size := 14;
    Canvas.Font.Color := clNavy;
    Canvas.Font.Style := Canvas.Font.Style + [fsBold];
    Canvas.TextOut(2,5,'Žarko Gajić');
    Canvas.Font.Style := Canvas.Font.Style - [fsBold];
    Canvas.TextOut(2,35,'On Delphi Programming');
    Canvas.TextOut(2,65,'http://zarko-gajic.iz.hr');
  end;

  Timer1.Enabled := False;
  Image1.Align := alClient;
  Visible := False;
  BorderStyle := bsNone;
  Top := 0;
  Left := 0;
  Width := Screen.Width;
  Height := Screen.Height;
  InitScreen;
//  SetWindowPos(Handle,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE + SWP_NOMOVE);
  Visible := True;

  Timer1.Interval := 750; // smaller := faster
  Timer1.Enabled  := True; // start calling DrawScreen
end;

First, the rW and rH values are determined by the DELTA value.
Next, the redRect bitmap is created that will be placed inside the picture so that it replaces the “removed” slide. The redRect is simply a red rectangle with a sample (blue) text inside it. You could use some ready made graphics, some logo or something.
Finally, the code sets the form to be wide and tall as the Screen. The (commented) call to the API SetWindowPos could be used to set the form to be always OnTop, non movable and non resizable. The InitScreen procedure is called. Timer Interval is set and the OnTimer event starts executing the DrawScreen procedure.

To close the program, hit the ESC key:

procedure TShufflerForm.FormKeyDown(Sender: TObject; var Key: Word;  Shift: TShiftState);
begin
  if Key = 27 then Close; //ESC
end;

InitScreen – Screen shot

The InitScreen procedure, called from the form’s OnCreate event handler, is used to take the screen shot of the current Desktop screen, set the starting position of the redRect and to draw the grid. The code that draws the grid is optional.

To take a screen shoot of the Desktop the GetDC along GetDesktopWindow are used. The BitBt API function is used to transfer the picture of the Desktop to the desktopBitmap. GetDC(GetDesktopWindow) retrieves a handle of a display device context for the specified window – the window returned by the GetDesktopWindow call. Finally desktopBitmap is then assigned to Image1 component.

Initial position of the redRect piece is randomly selected. Trunc(Random * DELTA) returns an integer number between 0 and DELTA. Next, the redRect is drawn on the gx, gy position using the CopyRect function of the Canvas object.

Finally, the grid is drawn by using the MoveTo and LineTo. Grid is optional and is used to better differentiate each piece of the picture.

procedure InitScreen;
var i,j:integer;
begin
  DesktopBitmap := TBitmap.Create;
  with DesktopBitmap do begin
    Width := Screen.Width;
    Height := Screen.Height;
  end;
  BitBlt(DesktopBitmap.Canvas.Handle,0,0,Screen.Width,Screen.Height,  GetDC(GetDesktopWindow),0,0,SrcCopy);
  ShufflerForm.Image1.Picture.Bitmap := DesktopBitmap;

  Randomize;

  gx := Trunc(Random * DELTA);
  gy := Trunc(Random * DELTA);

  ShufflerForm.Image1.Canvas.CopyRect(Rect(rW * gx, rH * gy, rW * gx + rW, rH * gy + rH),redRect.Canvas,Rect(0,0,rW,rH));

  //draw matrix
  for i:=0 to DELTA-1 do begin
    ShufflerForm.Image1.Canvas.MoveTo(rW * i,0);
    ShufflerForm.Image1.Canvas.LineTo(rW * i,Screen.Height);

    ShufflerForm.Image1.Canvas.MoveTo(0, rH * i);
    ShufflerForm.Image1.Canvas.LineTo(Screen.Width, rH * i);
  end;


  //**add cell numbers (row; column)**
  ShufflerForm.Image1.Canvas.Font.Size:=15;
  for i := 0 to DELTA-1 do
    for j := 0 to DELTA-1 do
      ShufflerForm.Image1.Canvas.TextOut(rw * i, rh * j, IntToStr(i+1)+';'+ IntToStr(j+1));
end;

Draw Screen

The main code is in the DrawScreen procedure. This procedure is called inside the OnTimer event of the Timer component.

procedure DrawScreen;
var
  r1,r2:TRect;
  direction:integer;
begin
  r1:=Rect(rW * gx , rH * gy,  rW * gx + rW  , rH * gy + rH);

  direction := Trunc(Random*4);
  case direction of
   0: gx := Abs((gx + 1) MOD DELTA);    //right
   1: gx := Abs((gx - 1) MOD DELTA);    //left
   2: gy := Abs((gy + 1) MOD DELTA);    //down
   3: gy := Abs((gy - 1) MOD DELTA);    //up
  end; //case

  r2 := Rect(rW * gx , rH * gy,  rW * gx + rW  , rH * gy + rH);

  with ShufflerForm.Image1.Canvas do
  begin
    CopyRect(r1, ShufflerForm.Image1.Canvas, r2);
    CopyRect(r2, redRect.Canvas, redRect.Canvas.ClipRect);
  end;
end;

The code may seem strange but is really simple. Since only pieces adjacent to the redRect one can be swapped with it, there are just 4 possible directions. The r1 rectangle holds the current position of the redRect, r2 points to the rectangle with the peace that will be replaced. The CopyRect is used to transfer the “selected” part on the redRect position and again to draw the redRect on its new position – thus swapping those two pieces.

It would be much eye-friendly if the swapping is animated – I’ll leave this for you to implement.

In Action

Do More Out Of it

For a bit of entertainment – the code is the starting point for the popular slide type puzzle. Those little hand puzzles with tiles numbered from 1 to 15 that you must slide into numeric order. All you have to do is to stop the DrawScreen routine after some time to get the puzzled image. The idea of the game is to move the pieces back to reform the picture. In general, add the code that receives user clicks on the shuffled picture. Clicking on a piece next to the redRect should swap that piece with the redRect. I leave this up to you 🙂

p.s.
This is an old one, refurbished. Yes, I’m using “with” above (quilty as charged).

One thought on “Screen Shuffling with Delphi (first step to a Sliding Puzzle Game)

  1. JPelttari

    I remember this one. I used it to learn and play with delphi some 17 years ago.
    I also made a screensaver of it with settings from windows screen saver dialog.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *