Memory 3D – Full Source Code Delphi Game

Memory3D“If you want it to be playable and more interesting you need to jazz it up a bit!”

That’s what’s been cooking in my head from the time I’ve finished implementing the back end for the game of Memory (Match Up, Concentration, or whatever you are used to call it). As a proof of concept, I’ve coded a very simple application, aka the front end for the game – juts to have a workable user interface.

Now, the time has come to finish the series and introduce a more eye candy version of the game, one that’s not using dull buttons with numbers for field display values but that actually looks like card game type of Memory, with nice fancy images/icons for game fields. Why stop there? Why not go a step forward and introduce a new dimension for the game: make it 3D having fields appear on planes/layers, so players need to switch between planes to match a pair.

Memory 3D

Here’s the idea: in the standard Memory game, all fields containing game pairs, would be presented to the player at once. Meaning that if the game has 12 fields, that is 6 pairs, all 12 playable fields are visible and the player can pick from to find a match – so basically 2 dimensions: a number of rows and columns where fields are presented in a grid like fashion (say 4×3 for 12 fields).

What if I would to introduce one more, 3rd, dimension: a plane (a layer). So if the game has 8 pairs (16 fields) and 2 planes – only 8 fields are visible/presented to the player at one time. 8 fields on first plane, 8 more on the second plane. So one field of a pair can be on the first plane and the second field can be on the second plane. Player needs to switch between planes to find a match – remember the row and column but also the plane.

Sounds interesting, so let’s see the UI and some code …

For those living by the TL;DR: mantra: download full source code.

As stated I want to have some graphics displayed on each game field. For this purpose I’ve went to browse the https://www.freepik.com web site hosting some free to use icons and graphics. I was looking for some handy set of icons and have found one from @makyzz:

There are 10×10 icons/glyphs here – quite enough as that would result in game of Memory of maximum 200 fields (100 pairs). I’ll store the entire image in a hidden “ClipImage” TImage control and will cut out the piece I need for a game field. Since field values would go from 0 to 99 the icon/glyph in second row, third column would have the index of 12. Here’s a function to get the image graphics for a field with the value of “fieldIndex”:

function TMainForm.GetFieldGraphics(const fieldIndex: integer): TPicture;
var
  clipWidth, clipHeight : Integer;
  srcRect, destRect : TRect;
begin
  clipWidth := ClipImage.Picture.Width div ClipColumns;
  clipHeight := ClipImage.Picture.Height div ClipRows;

  destRect := Rect(0, 0, clipWidth, clipHeight) ;
  srcRect.Left := (fieldIndex mod ClipColumns) * clipWidth;
  srcRect.Top := (fieldIndex div ClipRows) * clipHeight;
  srcRect.Right := srcRect.Left + clipWidth;
  srcRect.Bottom := srcRect.Top + clipHeight;

  PictureClip.Bitmap.Width := clipWidth;
  PictureClip.Bitmap.Height := clipHeight;
  PictureClip.Bitmap.Canvas.CopyRect(destRect, ClipImage.Canvas, srcRect) ;

  result := PictureClip;
end;

The UI

The user interface looks pretty much the same as in the 2D version of the game. We need to have a way to switch between planes so 2 more buttons added to the UI. Also, I’ve added some Display options: to display (or not) some field info like what plane are we one, what player has claimed the pair and alike.

The New Game

Here’s how the game is created and started:

procedure TMainForm.btnNewGameClick(Sender: TObject);
var
  newGamePairs, newGamePlayers, newGamePlanes : integer;
  newGameFieldValueShift : integer;
begin
  Randomize;

  newGamePairs := StrToInt(ledPairs.Text);
  newGamePlayers := StrToInt(ledPlayers.Text);;
  newGamePlanes := StrToInt(ledPlanes.Text);;

  fClipImage := imgImagesGrid100;
  fClipColumns := 10;
  fClipRows := 10;
  fMaxPairs := fClipColumns * fClipRows;

  if newGamePairs > MaxPairs then
  begin
    newGamePairs := MaxPairs;
    ledPairs.Text := newGamePairs.ToString;
  end;

  newGameFieldValueShift := Random(MaxPairs - newGamePairs + 1);

  try
    MGame.NewGame(newGamePairs, newGamePlayers, newGamePlanes, newGameFieldValueShift);
  except on E:Exception do
    begin
      MessageDlg(E.Message, mtError, [mbOk], -1);
      Exit;
    end;
  end;
end;

Set the wanted number of fields, planes and players. The “newGameFieldValueShift” ensures we always pick random range of icons from the available set – so not to always use the same ones.

The MGame.NewGame would raise the OnGameCreate and OnGameStart events I’m using to setup the user interface. If the total number of fields (2 x number of pairs) is not dividable by the number of planes – the game would raise an exception and would not start.

The OnPlaneChaned even handler is the most interesting one as this is where the icons would be extracted:

procedure TMainForm.PlaneChanged(Sender : TObject);
var
  i : integer;
  mField : TMField;
  pnl : TPanel;
  img : TImage;
  lbl : TLabel;
begin
  lblCurrentPlane.Caption := Format('Current plane: %d of %d planes.', [MGame.CurrentPlane, MGame.PlanesCount]);

  //set field hosts for current plane
  for i := 0 to -1 + gameGrid.ControlCount do
  begin
    mField := MGame.Fields[i + MGame.FirstFieldIndexOnCurrentPlane];

    pnl := TPanel(gameGrid.Controls[i]);
    img := TImage(pnl.Controls[0]);
    lbl := TLabel(pnl.Controls[1]);

    if chkShowPlaneInfo.Checked then
    begin
      lbl.Caption := Format('%d / %d / %d', [i + 1, MGame.CurrentPlane, MGame.PlanesCount]);
      lbl.Visible := MGame.PlanesCount > 1;
    end
    else
      lbl.Visible := false;

    mField.Host := img;

    if mField = MGame.OpenedField then
    begin
      pnl.BevelKind := bkTile;
      TImage(mField.Host).Picture.Assign(GetFieldGraphics(mField.Value))
    end
    else if mField.Player <> nil then
    begin
      pnl.BevelKind := bkNone;
      TImage(mField.Host).Picture.Assign(GetFieldGraphics(mField.Value));

      if chkShowClaimedInfo.Checked then
      begin
        lbl.Caption := Format('%s', [mField.Player.Name]);
        lbl.Visible := true;
      end;
    end
    else
    begin
      pnl.BevelKind := bkTile;
      TImage(mField.Host).Picture.Assign(imgQuestionField.Picture);
      //TImage(mField.Host).Picture.Assign(GetFieldGraphics(mField.Value))
    end;
  end;

  pnlGameGrid.Refresh;
end;

The rest of the code is more or less the same as in the 2D version of the game.

Hope you like it and if you do a FireMonkey version for mobiles – do share 😉

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.