“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.
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 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); lbl := TLabel(pnl.Controls); 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 😉
Zarko, you are a great resource for the Delphi Dev Community. We have just grown up reading Delphi About.com. I remember the small app I had once downloaded long back – The document navigation sidebar app. That has inspired me and kick-started the journey. Nice to see the attached source code in examples. Glad the sample files are not lost in time.