PDA

View Full Version : Chapter 10 page 1


_I_Play_Chess
11-01-2003, 07:21 AM
Your First Delphi Game: Tic Tac Toe
--------------------------------------------

Page 1: Preparing the Game GUI
=======================
Welcome to the tenth chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Designing and developing a real game using Delphi: Tic Tac Toe.

Ok, stop playing with Delphi. After nine chapters of this course you are ready for some real action. It's time to create a full working Delphi project, and what type of project would you be more interested than creating a game?
Your goal in this chapter is to create a simple game using Delphi. Here are the topics you'll practically try and learn here:


Building a program GUI
Building a program Logic
Working with arrays
Building Functions, Procedures using Parameter Passing
Casting one Delphi object to another
Tic Tac Toe, the Rules
Tic Tac Toe is a traditional game, played by two opponents that alternately place X's and O's into 3x3 playing field.

(See Pic below)


Before the game starts, players have to decide who will go first and who will mark his moves with an X. After the first move, game proceeds with the opponents alternately placing their marks at any unoccupied cell. The goal of this game is to be the first one with three marks in a row, where the row can be horizontal, diagonal or vertical. If all cells are occupied and both players don't have a winning combination, the game is a draw.

Back to the "drawing board" ...

(to Be Continued)

_I_Play_Chess
11-01-2003, 07:43 AM
Preparing the Tic Tac Toe Game

At this moment you should have your copy of Delphi up and running. By default, as you know by now, when you start Delphi, a new (standard Windows) project is created hosting one Form object (and the associated unit).

Our game will require only one form where all the action is taking place, naturally we'll use the form object Delphi "prepared" for us.
Saving for the first time

Before we move on, I suggest you to rename that default form, by assigning the string 'frMain' to the Name property (using the Object Inspector). Next, save the project, by pointing to File | Save All (Delphi IDE main menu). The Save As dialog box will be displayed, providing you with the option to first save the form's unit file, the default name will be Unit1.pas. The offered location will be inside the Projects folder under the Delphi install folder. It is advisable to save every project in a separate folder, therefore, use the 'Create New Folder' toolbar button to create a new folder, and name it 'TicTacToe'. Now, change the name of the Unit1.pas to Main.pas and click Save. You will then be prompted to save the project, by default Delphi will name this project 'Project1' (DPR), of course we'll change it to, let's say, 'TicTacToe'. That's it. Later on, when you want to save the current status of your project, just use Ctrl+Shift+S (keyboard shortcut for Save All).

GUI
===
Ok, first we build the game GUI (graphical user interface). For the moment we only have a form object named 'frMain', use Object Inspector to change the Caption property to ' Tic Tac Toe'

Preparing the Tic Tac Toe Game
At this moment you should have your copy of Delphi up and running. By default, as you know by now, when you start Delphi, a new (standard Windows) project is created hosting one Form object (and the associated unit).
Our game will require only one form where all the action is taking place, naturally we'll use the form object Delphi "prepared" for us.
Saving for the first time
Before we move on, I suggest you to rename that default form, by assigning the string 'frMain' to the Name property (using the Object Inspector). Next, save the project, by pointing to File | Save All (Delphi IDE main menu). The Save As dialog box will be displayed, providing you with the option to first save the form's unit file, the default name will be Unit1.pas. The offered location will be inside the Projects folder under the Delphi install folder. It is advisable to save every project in a separate folder, therefore, use the 'Create New Folder' toolbar button to create a new folder, and name it 'TicTacToe'. Now, change the name of the Unit1.pas to Main.pas and click Save. You will then be prompted to save the project, by default Delphi will name this project 'Project1' (DPR), of course we'll change it to, let's say, 'TicTacToe'. That's it. Later on, when you want to save the current status of your project, just use Ctrl+Shift+S (keyboard shortcut for Save All).

GUI
===
Ok, first we build the game GUI (graphical user interface). For the moment we only have a form object named 'frMain', use Object Inspector to change the Caption property to ' Delphi Programing:Tic Tac Toe'

The playfield.

Your goal is to create a "grid" consisting of 9 cells, where each cell will display either nothing, X or O. The simplest is to use the Label component from the Standard palette. Drop one on the form. Note, we'll need 9 labels looking similar. The easiest way to do that is to create first label, name it, choose the appropriate font, color, and set every other property. For this purpose we will name the label 'lblCell0'.
Use the Object Inspector to set the following properties:

object lblCell0: TLabel // note Name = lblCell0
Left = 8
Top = 8
Width = 81
Height = 81
Alignment = taCenter
AutoSize = False
Caption = 'lblCell0'
Color = clCream
Font.Size = 16
Font.Name = 'Tahoma'
Font.Style = [fsBold]
Layout = tlCenter
end

-------------------

Note: If you right click anywhere on your form, and choose 'View As Text' command from the context pop up menu, you are presented with text description of the form’s attributes, inside the Code Editor window. To switch back to 'For View', just right click this "code" window and choose 'View As Form'.

After we shape up the first (cell) label, we can create other eight elements of the grid. Select the label object, and press CTRL+C. Now, select the form (click anywhere on the form) and press CTRL+V to create another label object. The second object will inherit all properties from the first one, only the Name property will be re-set to Label1. Alter it manually to lblCell1, note that you must also manually change the Caption property to lblCell1. We do not really need the Caption property at this moment, but if you delete it (making it an empty string) you will not be able to differentiate one cell from another (without selecting one, of course).

Go on and create all nine labels. Make sure that the Name properties are sequential in the grid, we will build the game logic on the cell position / name. Label object with Name lblCell0 zero should be in the top left corner, and the bottom right label should be lblCell8.
When you are finished your form should be looking something like:

_I_Play_Chess
11-01-2003, 07:50 AM
Next, we add some more objects to the form:

Two TButton objects (Standard Palette), name them 'btnNewGame' and 'btnResetScore',
One TRadioGroup object (Standard Palette), name it 'rgPlayFirst', set the Caption property to 'First to play'. Now, select this RadioGroup and point to the Items property, click the ellipsis button. Add two radio buttons, each string in Items makes a radio button appear in the radio group box with the string as its caption. The value of the ItemIndex property determines which radio button is currently selected.
One TGroupBox object (Name = 'gbScoreBoard'; Caption = 'Score Board') with six labels (names : lblX, lblXScore, lblO, lblOScore, lblMinus, lblColon).

Once we have the game GUI, we can proceed to game logic and initialization.

Next page > Game Initialization

_I_Play_Chess
11-01-2003, 08:04 AM
Your First Delphi Game: Tic Tac Toe
--------------------------------------------

Page 2: Game Initialization
===================
The GUI design part of the Tic Tac Toe game is complete, we move on to game logic and initialization...

Initializing the Tic Tac Toe Game

Before we start coding we have to decide how to represent the playfield (label/cell grid) in the computer memory.

Each cell can be treated individually, we can place all cells in one-dimensional array, or we can make two-dimensional array. Since we want to automate our program as much as possible we will represent cells as a two dimensional array. The top-left "corner" will have an index of [1, 1], the top-right corner will be [1, 3], the bottom-right [3, 3], etc. Two playfield variables will be created for playfield data, as the code will be more readable if we store each player moves separately.

Beside playfield variables we need to declare variables that will tell us how many moves are played (iMove), who is on the move (sPlaySign), is the game in progress or is the game over (bGameOver), and two variables that will hold the number of victories for each player (iXScore and iOScore). Place the following code above the implementation keyword inside the unit Main.pas, just below the frMain variable:
----------------------------
...
var
frMain: TfrMain; //added by Delphi - do NOT delete

iXPos : TXOPosArray;
iOPos : TXOPosArray;

sPlaySign : String;
bGameOver : Boolean;
iMove : Integer;
iXScore : Integer;
iOScore : Integer;
implementation
...
-----------------------------

Note: the TXOPosArray (two-dimensional integer array - holding 9 elements) type is declared above the form type declaration. Since we'll need to send an array as a parameter to a procedure, we have to define our "array type", like:

type TXOPosArray = array [1..3, 1..3] of Integer;


Now, inside the FormCreate event handler (double click a form to create one) we add the following code:
-------------------------
procedure TfrMain.FormCreate(Sender: TObject);
begin
iXScore := 0;
iOScore := 0;

InitPlayGround;
end;
-------------------------

The iXScore and iOScore variables are initialized when the form is created. This is because we don't want to change their values until user presses the btnResetScore button.
All other variables have to be initialized after the end of each game - inside the InitPlayGround procedure.

Playfield Initialization

During a playfield initialization we will clear the cells (that is, Captions of all Labels with lblCellX names) by setting the Caption property to '' (empty string). It is necessary to set iXPos and iOPos arrays to zero. Beside that, we have to check the value of the rgPlayFirst radio group ItemIndex property to determine which player will play first in the next game. Also, we have to reset the number of moves to zero (iMove), and set the bGameOver variable to False.

To create the InitPlayGround procedure, go to the Code Editor window and below the implementation keyword, add the next code:
--------------------------
procedure TfrMain.InitPlayGround;
var
i, j, k: integer;
begin
for i := 1 to 3 do
begin
for j := 1 To 3 do
begin
k:= (i - 1) * 3 + j - 1; // 0 .. 8
TLabel(FindComponent('lblCell' + IntToStr(k))).Caption := '';
iXPos[i, j] := 0;
iOPos[i][j] := 0;
end;
end;

if rgPlayFirst.ItemIndex = 0 then sPlaySign := 'X';
if rgPlayFirst.ItemIndex = 1 then sPlaySign := 'O';

bGameOver := False;
iMove := 0;
end;
--------------------------

Note that you will also need to add this procedure's header in the private section of the form declaration (interface part of the unit):

...
private
procedure InitPlayGround;
...

Stop for the moment, and consider the next two lines in the above code:
----------------------------
k:= (i - 1) * 3 + j - 1; // 0 .. 8
TLabel(FindComponent('lblCell' + IntToStr(k))).Caption := '';
----------------------------

What we want to do in the InitPlayGround procedure is to set the caption to all lblCellX label components to an empty string. One way to achieve this is to use 9 lines of code:

lblCell0.Caption:='';
lblCell1.Caption:='';
lblCell2.Caption:='';
...
lblCell7.Caption:='';
lblCell8.Caption:='';
-------------------------

Bad practice! The above code "tip", searches for a component on a form by its name (FindComponent), casts it to TLabel and assigns an empty string to the Caption Property. FindComponent returns the component in the forms Components property array with the name that matches the string in the only parameter required.

Next page > Your Turn!

_I_Play_Chess
11-01-2003, 08:16 AM
Your First Delphi Game: Tic Tac Toe
--------------------------------------------

Page 3: Player X, your turn!
====================
Until now we have created the Tic Tac Toe game GUI and we've initialized the variables. We are ready to make our first move.

X, your turn! (or O if you prefer)

Event OnClick for the particular lblCellX component will be called each time a player presses a (left) mouse button on the Label component making the playfield grid.

There is one more thing we have to check before we process the user input. It is very important to be sure that the current game is in progress. In the case that the game is over, we will simply exit this procedure.

After we verify that the game is in progress, we can process the user input - in a procedure GamePlay that expect an integer parameter named CellIndex. CellIndex would have the value set to 0 for the top-left corner cell (label) of the playfield and the value 8 for the bottom-right corner. To get the CellIndex we extract the cell number from the label name, for example, if the label clicked is named lblCell5, the CellIndex for that label (cell) is 5.

To create an OnClick event handler procedure for the first label (lblCell0), simply double click it, or select the lblCell0 component, go to Object Inspector, switch to Events and double click the column right to OnClick. Either way, the Code Editor receives the input focus, and you can enter the programming logic code for the OnClick handler:
-------------------------
procedure TfrMain.lblCell0Click(Sender: TObject);
var
iWin : integer;
CellIndex : 0..8;
begin
if bGameOver = True Then Exit;
if TLabel(Sender).Caption <> '' then
begin
ShowMessage('Cell occupied!');
Exit;
end;
CellIndex := StrToInt(RightStr(TLabel(Sender).Name,1));
iWin := GamePlay(CellIndex);
end;
--------------------------

Note 0: If the cell is occupied (was clicked before during the game), we exit the procedure. In order to "see" whether the cell is occupied we use the Sender parameter, cast it to TLabel and test the Caption property. If the cell already hosts 'X' or 'O', we display a message using the ShowMessage Delphi function.

Note 1 : to assign a value to CellIndex we use the RightStr function defined in the StrUtils.pas unit. You'll need to manually add this unit to the uses list in the interface uses clause of the Main.pas unit. If you forget to do that, the next time you try to compile your project, the compiler will report an error: "Undeclared Identifier 'RightStr'"!!

Note 2: The above event handler was coded with the following in mind: there is no need to have a separate event handler for each and every of 9 cells making the playfield. What we are preparing for, with the above code, is to use this one procedure for all lblCellX label components and their OnClick events. To have the lblCellX (where X stands for 0 to 8) share this specific OnClick event handler, do the following:

a) select the "rest" of the eight lblCell label components (Shift + Click 'em);

b) Go the Events page of the Object Inspector

c) In the right column to the OnCLick, from the drop down list pick the lblCell0Click.


Processing the Move

Before we start with processing, let me refresh your memory. iXPos and iOPos arrays hold information about already played moves by the X and O player. iMove tells us how many moves are already made.

The user's choice must be the regular move, which means that user didn't click on the already occupied cell.
If the user makes a regular move, we have to increase the counter (iMove) by one. Then we have to translate the coordinates form one-dimensional to two-dimensional array. The xo_Move value of 0 will be translated to [1,1], 1 to [1,2] ... 3 to [2,1] ... 8 to [3,3].
-------------------------
function TfrMain.GamePlay(xo_Move : Integer):integer;
var
x, y : 1..3;
iWin : integer;
begin
Result := -1;

Inc(iMove);
x := (xo_Move Div 3) + 1;
y := (xo_Move Mod 3) + 1;
...
-------------------------

Also we have to check which player has made the move because this routine will process the input from both players.
-------------------------
if sPlaySign = 'O' then
begin
iOPos[x,y] := 1;
iWin := CheckWin(iOPos);
end
else
begin
iXPos[x,y] := 1;
iWin := CheckWin(iXPos);
end;

TLabel(FindComponent('lblCell' + IntToStr(xo_Move))).Caption := sPlaySign;
--------------------------

For example, when the player X places his mark in the top left corner of the playfield, the variables will have the following values:

_I_Play_Chess
11-01-2003, 08:18 AM
Take a look at the values when O player makes his move in the middle of the playfield:

_I_Play_Chess
11-01-2003, 08:21 AM
After every move we call the CheckWin function to look for the winning combination.

If the player succeeds to win, CheckWin will return the number of the winning combination. If there is no winner, CheckWin will return -1. In case that we have the winner the game will end, the current result will be placed on the scoreboard, and a congratulation message will pop up.
-----------------------------
Result := iWin;

if iWin >= 0 then
begin
bGameOver := True;
//mark victory

if sPlaySign = 'X' then
begin
iXScore := iXScore + 1;
lblXScore.Caption := IntToStr(iXScore);
end
else
begin
iOScore := iOScore + 1;
lblOScore.Caption := IntToStr(iOScore);
end;

ShowMessage(sPlaySign + ' - Wins!');
end;
-----------------------------

If players played nine moves and there is no winner, it is a draw; and the current game is over. On the other hand, if the game is still in progress, we have to allow the other player to make his move.
----------------------------
if (iMove = 9) AND (bGameOver = False) Then
begin
ShowMessage('It''s a Draw!');
bGameOver := True
end;

if sPlaySign = 'O' Then
sPlaySign := 'X'
else
sPlaySign := 'O';
end; //function GamePlay
--------------------------------

Ok, we now have the main part of the code logic done, what's left is the CheckWin function.

Do we have a winner?

After each move we have to check if the game is over. Game could end in three ways: X player wins the game, O player wins the game, or it is a draw. To look for a possible winner we count the number of X's and O's in each row, column and diagonal. If the player manages to put three signs in row, column or diagonal, we have the winner.

_I_Play_Chess
11-01-2003, 08:24 AM
Here's a portion of the CheckWin function, for the rest of the function look at the project code.
------------------------------
function TfrMain.CheckWin(iPos : TXOPosArray) : Integer;
var
iScore : Integer;
i : Integer;
j : Integer;
begin
Result := -1;

//in rows?
iScore := 0;
for i := 1 to 3 do
begin
iScore := 0;
Inc(Result);
for j := 1 To 3 do Inc(iScore, iPos[i,j]);
if iScore = 3 Then Exit
end;//for i
...
------------------------------

Note that you will have to add these procedure's headers in the private section of the form declaration, as you did with the InitPlayGround procedure. Now the private section is looking like:

private
procedure InitPlayGround;
function GamePlay(xo_Move : Integer) : integer;
function CheckWin(iPos : TXOPosArray) : integer;

We now move to the last stage of our Tic Tac Toe development, the creation of the event handlers for 'New Game' and 'Reset Score'...

Next page > New Game?

_I_Play_Chess
11-01-2003, 08:31 AM
Your First Delphi Game: Tic Tac Toe
--------------------------------------------

Page 4: New Game?
==============
There are two more handlers we have to code: create a procedure for a new game, and a procedure that will reset the score.

New Game

We already have the InitPlayGround procedure, therefore creating a new game would be a peace of cake. All we have to do is call the InitPlayGround.

Note: a player can click on the btnNewGame button after the game is finished (bGameOver = True) or during a game (bGameOver = False). Because of that it would be nice to ask the user to confirm his decision.

In this particular case there would be negligible damage or there would be no damage at all. Just remember that good programming practice implicate getting the user's confirmation.

Double click the btnNewGame button to create and add the code for the New Game option:
---------------------------
procedure TfrMain.btnNewGameClick(Sender: TObject);
begin
if bGameOver = False then
begin
if MessageDlg(
'End the current game?',
mtConfirmation,
mbOKCancel,0) = mrCancel then Exit;
end;

InitPlayGround;
end;
---------------------------

Reseting the Scoreboard

Recall that the iXScore and iOScore variables hold the number of wins for each player. Oponents read the score form the lblXScore and lblOScore labels.
The task is simple: set iXScore and iOScore values to zero, and update lblXScore.Caption and lblOScore.Caption.

Of course, get the user's confirmation first.
-------------------------
procedure TfrMain.btnResetScoreClick(Sender: TObject);
begin
if MessageDlg(
'Reset the scores?',
mtConfirmation,
mbOKCancel,0) = mrCancel then Exit;

iXScore := 0;
iOScore := 0;
lblXScore.Caption := IntToStr(iXScore);
lblOScore.Caption := IntToStr(iOScore);
end;
-------------------------
(See pic below)

_I_Play_Chess
11-01-2003, 08:39 AM
Some exercises for you...


Please, take a look at that CheckWin function again. Note that in case of a "we have a winner" it returns an integer number from 0 to 8. What is this number for? See the picture Below:


If we have a winner and the winner has occupied the middle row, the CheckWin will retun 2.

Your excersise (for this chapter) is to create a new function to mark (in any way you want) a victory on a playfied. For example, you could change the background color for the three labels making the wining combination. Or, draw an ellipse arround the cells, it's up to you... ;)


To the next chapter: A Beginner's Guide to Delphi Programming
This is the end of the tenth chapter, in the next chapter, we'll deal with creating your first MDI (multiple document interface) application.

_I_Play_Chess
11-01-2003, 08:45 AM
On demand if you wish having the complete source code:

write me a word at:

_i_play_chess@yahoo.com


It will be a pleasure to send it for you.

leblitzer
11-03-2003, 06:11 AM
brue can u post all cursez on your site?

_I_Play_Chess
11-03-2003, 07:55 PM
My site doesn't exist anymore!

My other business site is for business!

kroz
11-05-2003, 11:50 AM
Hey ipc looking good bud. Nice, did what i tell u help> Im still tryin to find that floppy of my progs/

leblitzer
11-06-2003, 04:24 AM
ipc mess me on yim pliz

_I_Play_Chess
11-06-2003, 07:24 PM
This week was a big working week.


Is that could wait till the weekend.

I am a bit busy all day long. Have one of my tech absent for all this week

Kr0z buddy thx for the good help I will wait bud! np