In part 1 of Programming a Memory Game we explained how to create the form and drawgrid at design time. Now we will move onto writing the actual code.
Since we have 20 cells on our grid, we need 10 distinct images.
As an application developer, it's up to you to consider this trade off and to make the best choice. But for simplicity, I will use the second one. That is, I will automatically load the images at the beginning and keep all of the images in memory till the end of the application. This is reasonable when your application uses a small number of images with reasonable sizes.
For this purpose, we will need two global arrays:
var ImagePaths : array [0..9] of string; Images : array [0..9] of TImage;
The following procedure will help us load the images:
procedure LoadImages; var i : integer; begin for i := 0 to 9 do begin Images[i] := TImage.Create(nil); Images[i].Picture.LoadFromFile(ImagePaths[i]) end end;
We will call this procedure only once, just on the creation of the main form.
NOTE: You have to assign the image paths before calling this procedure. And you'd better handle exceptions such as non-existent files, etc. Furthermore, your images should have one of the supported image extensions, such as .bmp. But for the sake of simplicity, I skip such kind of details and assume that all of the images will exist in the same directory with the application and all of them will be bitmap images.
Thus, I will change my ImagePaths array variable declaration as follows:
ImagePaths : array [0..9] of string = ('img0.bmp', 'img1.bmp', 'img2.bmp', 'img3.bmp', 'img4.bmp', 'img5.bmp', 'img6.bmp', 'img7.bmp', 'img8.bmp', 'img9.bmp');
Make sure that these images exist under your application directory.
Once we ensure that the images exist and have appropriate extensions, we can call the LoadImages procedure on creation:
procedure TfrmMain.FormCreate(Sender: TObject); begin LoadImages end;
The easiest way to randomize the couples is to randomize a permutation array.
Since we have 20 cells, we will have an array of integers, where each integer value keeps the index of a cell. But wait a minute... Our cells are indexed by 2 integers, namely one row and one column value. How can we represent them with a single index? It is easy. We can think of our double-indexed cell array as a single-indexed linear array. For this purpose, we will use the following function:
function LinearIndexOf (Row, Column : integer) : integer; begin Result := 5 * Row + Column // Returns the index of the given cell end;
Here, we multiply by 5 because there are 5 cells on each row.
Now, we will declare our random permutation array (globally):
var RandomPermutationArray : array [0..19] of integer;
We have to initialize this array once, in the creation of the application. Thus, OnCreate event of our main form will be as follows:
procedure TfrmMain.FormCreate(Sender: TObject); var i : integer; begin LoadImages; for i := 0 to 19 do RandomPermutationArray[i] := i end;
And the following procedure will randomize this array at each call to this procedure:
procedure RandomizeThePermutationArray; var i, RandomPosition, Temp : integer; begin for i := 0 to 18 do begin RandomPosition := i + Random(19 - i) + 1; Temp := RandomPermutationArray[i]; RandomPermutationArray[i] := RandomPermutationArray[RandomPosition]; RandomPermutationArray[RandomPosition] := Temp end; end;
This procedure is an easy-to-understand and a good way of randomizing a permutation array.
It just does the following:
For each element in the array except the last one, it selects randomly one of the elements to the right of this element and swaps them.
NOTE: You can use this nice method in your other applications whenever you need randomization.
How will we make use of this array in our application?
Suppose that after a call for randomization, our array became as follows:
19 - 6 - 7 -10 - 0 - 12 - 5 - 1 - 11 - 8 - 2 - 3 - 16 - 15 - 17 - 18 - 4 - 9 - 14 - 13
This will mean in our application that the cells with linear indexes 19 & 6 are couples and they share the first image, 7 & 10 are couples and they share the second image, 0 & 12 are couples and they share the third image, etc.
Now, we also need to keep the information of which cell constitutes a couple with which cell. For this purpose, we will use the following array:
var PartnerOf : array [0..19] of integer;
And the following procedure will assign the partnerships:
procedure AssignPartnerships; var i : integer; begin for i := 0 to 19 do if i mod 2 = 0 then PartnerOf[RandomPermutationArray[i]] := RandomPermutationArray[i + 1] else PartnerOf[RandomPermutationArray[i]] := RandomPermutationArray[i - 1] end;
This procedure couples two elements next to each other at each time.
We also need to keep the information of which cell will display which image. Therefore, we need to declare the following variable:
var ImageOfCell : array [0..19] of integer;
And the following procedure assigns the images to the cells, using the random permutation array:
procedure AssignImagesToCells; var i : integer; begin for i := 0 to 19 do ImageOfCell[RandomPermutationArray[i]] := i div 2 end;
This procedure assigns the 0th image to the first couple, 1st image to the second couple, and so on.
We have to call the followings at start of each new game:
NOTE: We used the built-in function Random() in the RandomizeThePermutationArray procedure. But before using it, we have to call the built-in Randomize procedure once, in order to assign the randomization seed. We can call it in the OnCreate event of the main form:
procedure TfrmMain.FormCreate(Sender: TObject); var i : integer; begin LoadImages; for i := 0 to 19 do RandomPermutationArray[i] := i; Randomize end;
Let's add a main menu to our application and add an item with a caption "New Game" to it. I will name this item as itemNewGame. When clicked, this item will start a new game.
We should call the three procedures mentioned above in the OnClick event of this item in order to make the necessary initializations
procedure TfrmMain.itemNewGameClick(Sender: TObject); begin RandomizeThePermutationArray; AssignPartnerships; AssignImagesToCells end;
In the third part of this tutorial we will move onto drawing the playing board using the DrawGrid component, if you wish to be informed when part 3 is available please send us your email address via the update section on the left of this page.
Delphi Tutorial - Programming a Memory Game - Part 1
Programming a Memory Game in Delphi - Part 2 - Starting to Code
Programming a Memory Game in Delphi - Part 3 - Drawing the Grid
Programming a Memory Game in Delphi - Part 4 - Coding the Algorithm