1. The Variable Declarations TList basically works like array but with some differences. We can declare array of Integer, Byte, TRect, etc. But TList always uses
Pointer and if we want use pointer to store data we have to request memory first before using it.
Pointer doesn't store the 'actual' data, it points to the location of data. That's why it calls pointer. You can think a pointer is like a shortcut or link in your computer.
If you use Integer (in an array), the variant for its pointer type is ^Integer (for use in TList). If it is Byte then the pointer type of it is ^Byte.
array of Integer ---> ^Integer
array of Byte ---> ^Byte
array of TRect ---> ^TRect (or PRect)To use TList we need to do the declaration twice: one for the list itself and one for the items. In the snake program, it should be:
var
SnakeBody: TList;
var
SnakeSegment: PRect; // or ^TRect
Because SnakeBody need to access all the time when the snake program is running, it must be declared on the global scope. But we only access SnakeSegment when needed so we can declare it on local scope.
So now we can transform part of the psedocode to Pascal code (brown is psedocode):
procedure MoveSnake(NewHead: TRect);
var
SnakeSegment: PRect;
begin Request memory for NewSegment
Set NewSegment to contain NewHead data
Add the NewSegment into SnakeBody at the first position
If not(SnakeIsGrowing) then begin
Remove LastSegment from SnakeBody
end
Disable snake growingend;
2. Adding New Item into The ListUsually we insert a new item into a list using this steps:
1. Request memory for the item
2. Set the item to contain the real data
3. Add the item into the listWe use
New to request memory. Read more about New:
https://www.freepascal.org/docs-html/rtl/system/new.htmlIn pointers, we use this symbol: "
^" to set the data. So in your snake game it will be:
SnakeSegment^ := NewHead;We now will insert the SnakeSegment into the list by calling
Insert. Read more about Insert:
https://www.freepascal.org/docs-html/rtl/classes/tlist.insert.htmlBecause we want the NewHead (SnakeSegment) tobe inserted
at the beginning of the list and TList uses
zero-based-index, the code will be:
SnakeBody.Insert(0, SnakeSegment);So now the psedocode becomes:
procedure MoveSnake(NewHead: TRect);
var
SnakeSegment: PRect;
begin
New(SnakeSegment);
SnakeSegment^ := NewHead;
SnakeBody.Insert(0, SnakeSegment); If not(SnakeIsGrowing) then begin
Remove LastSegment from SnakeBody
end
Disable snake growingend;
3. Check If The Snake Need Tail RemovalIt is easy, just use an
if-then-begin and an
end. Because we use
SnakeIsGrowing variable to indicate that tail removal is not needed, so the code will be:
if not(SnakeIsGrowing) then begin
...
end;4. Removing The TailFirst you need to get the
index of the last segment. Because TList uses zero-based-index, the length of the snake needs to
subtract with 1. We use TList.
Count to get the total number of the items in the list.
Read more:
https://www.freepascal.org/docs-html/rtl/classes/tlist.count.htmlSo the index of the tail will be:
SnakeBody.Count-1We now need to point the pointer to the tail before we can progress further. To retrieve the pointer in a list we can use
TList[index]. So the code become:
SnakeSegment := SnakeBody[SnakeBody.Count-1];Now the SnakeSegment is pointing to the tail. But before we remove it, you should remember to remove it first from the
GameWorld. Remember the GameWorld we talk previously, it can contain Snake, Fruit or Empty. We need to call
PutItem(x, y, Empty);Because SnakeSegment is a pointer, to get the data we need to use this symbol again: "
^". So the code becomes:
PutItem(SnakeSegment^.Left, SnakeSegment^.Top, Emtpy)Note: it uses
emtpy because I mistyped it earlier.
After removing from the GameWorld now we remove it from the list. Do you remember my previous explanation about
New? Because we requested memory for SnakeSegment, before we remove it from TList we have to free the memory. We use
Freemem to free the requested memory of the pointer.
Read more:
https://www.freepascal.org/docs-html/rtl/system/freemem.htmlThe code is simple:
Freemem(SnakeSegment);
And then we continue to remove it from the list using
Delete.
Read more:
https://www.freepascal.org/docs-html/rtl/classes/tlist.delete.htmlBecause it is zero-based-index, the code will be:
SnakeBody.Delete(SnakeBody.Count-1);Here is the pseudocode transformation to real code:
procedure TForm1.MoveSnake(NewHead: TRect);
var
SnakeSegment: PRect;
begin
New(SnakeSegment);
SnakeSegment^ := NewHead;
SnakeBody.Insert(0, SnakeSegment);
if not(SnakeIsGrowing) then begin
SnakeSegment := SnakeBody[SnakeBody.Count-1];
PutItem(SnakeSegment^.Left, SnakeSegment^.Top, Emtpy);
Freemem(SnakeSegment);
SnakeBody.Delete(SnakeBody.Count-1);
end; Disable snake growingend;
5. Make Sure The Snake Stop GrowingWhen growing, the snake will only grow 1 segment and stop growing. We need to make sure it won't grow continuously by adding a single line at the end of the procedure:
SnakeIsGrowing := False;Finally, the code now becomes:
procedure TForm1.MoveSnake(NewHead: TRect);
var
SnakeSegment: PRect;
begin
New(SnakeSegment);
SnakeSegment^ := NewHead;
SnakeBody.Insert(0, SnakeSegment);
if not(SnakeIsGrowing) then begin
SnakeSegment := SnakeBody[SnakeBody.Count-1];
PutItem(SnakeSegment^.Left, SnakeSegment^.Top, Emtpy);
Freemem(SnakeSegment);
SnakeBody.Delete(SnakeBody.Count-1);
end;
SnakeIsGrowing := False;
end;