PDA

View Full Version : Object Oriented programin (step one of many)



robert83
15-03-2014, 03:26 PM
Hi everyone,

So I've decided to get deeper into using Object Oriented programming, this is first of many Object Oriented programming tests I'll be making for the sake of learning it...

What I created now is a TMonster class which has a few parameters, procedures ... I create 10 Ogres from this which randomly attack each other, excluding themselves and dead ogres...

Please take a look at my code , and tell me what not to do , somehow I feel I'm using parameters incorrectly...or the naming is not correct , even though it is working....



unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Grids, Math,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
Memo1: TMemo;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMonster = class
private
myname : string; // monster name
health : smallint;
myarmor : byte; // 0-255
myattack : byte; // 0-255
mytargetID : byte; // targetID
isDead : boolean;
published
property name : string // monsters name
read myname;
property life : smallint // get's life for monster
read health;
property armor : byte // get's armor for monster
read myarmor;
property attack : byte // get's attack for monster
read myattack;
property target : byte // actual target (random)
read mytargetID;
property Dead : boolean // am I dead
read isDead;
constructor Create (name : string ; life : byte ; armor : byte ; attack : byte);
procedure TargetID( id : byte);
procedure OnHit( attack : byte );
procedure Died();
end;
var
Form1: TForm1;
Ogre : array of TMonster;

implementation
{$R *.dfm}
constructor TMonster.Create(name : string ; life: Byte ; armor : byte ; attack : byte);
begin
myname := name;
health := life;
myarmor := armor;
myattack :=attack;
isDead := false; // by default alive
end;
procedure TMonster.TargetID(id: Byte);
begin
myTargetID := id;
end;
procedure TMonster.OnHit(attack: Byte);
begin
if (attack - armor) > 0 then
health := health - ( attack - armor );
// check if monster went to Himmel (heaven in germany)
Died();
end;
procedure TMonster.Died;
begin
if health <= 0 then
isDead := true;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
procedure TForm1.FormCreate(Sender: TObject);
var i : integer;
begin
// we have 10 little ogre's
SetLength(Ogre,10);
// each of them has a name , and healtpoints , armor, attack
Ogre[0] := TMonster.Create('William',100,1,1);
Ogre[1] := TMonster.Create('William 2nd',120,1,1);
Ogre[2] := TMonster.Create('Robert the Grumpy',145,2,3);
Ogre[3] := TMonster.Create('Antal the SpaceOgre',150,4,3);
Ogre[4] := TMonster.Create('Sebastian the Unstopable',160,1,4);
Ogre[5] := TMonster.Create('Blorb the Blorb',99,5,6);
Ogre[6] := TMonster.Create('Shrek',49,2,4);
Ogre[7] := TMonster.Create('Devid the Serbian Ogre',180,4,6);
Ogre[8] := TMonster.Create('Keen the Wise Ogre',167,6,9);
Ogre[9] := TMonster.Create('Ogre the Ogre',98,6,11);
StringGrid1.RowCount:=11;
StringGrid1.Cells[0,0]:='Name';
StringGrid1.Cells[1,0]:='Health';
StringGrid1.Cells[2,0]:='Armor';
StringGrid1.Cells[3,0]:='Attack';
for I := 0 to 9 do
begin
StringGrid1.Cells[0,i+1]:=Ogre[i].name;
StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].life);
StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var i,j,target: integer;
AvailableTarget : array of integer;
begin
randomize();
Memo1.Lines.Clear;
// fill array with alive monsters
SetLength(AvailableTarget,0);
for i := 0 to 9 do
begin
if Ogre[i].Dead = false then
begin
SetLength(AvailableTarget, length(AvailableTarget)+1);
AvailableTarget[High(AvailableTarget)]:=i;
end;
end;

for I := 0 to 9 do
begin
if Ogre[i].Dead = false then // can only attack if alive
begin
target := AvailableTarget[random(length(AvailableTarget))]; // target is choosen from array of alive monsters
if i <> target then // will not attack self
begin
Ogre[i].mytargetID:=target;
Ogre[target].OnHit(Ogre[i].attack);
Memo1.Lines.Add(Ogre[i].name+' attacks '+inttostr(Ogre[i].target)+' '+Ogre[Ogre[i].target].name);
end;
end;
end;


// update ogre stats
for I := 0 to 9 do
begin
StringGrid1.Cells[0,i+1]:=Ogre[i].name;
StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].life);
StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
end;
// if only one target is available , means we have a winner, everyone else is dead
if length(AvailableTarget) = 1 then
begin
Timer1.Enabled:=false;
for i := 0 to 9 do
begin
if Ogre[i].isDead = false then
Memo1.Lines.Add('The Winner is : '+ Ogre[i].name);
end;
end;
end;
end.



Next step would be maybe to create some sorta very simple window app, where I would have multiple monster types :

Ogre = Red Box
Troll = Blue Box
Imp = Green Box
Human = Black Box

then Ogre's would be sworn enemies of Troll's and Imp's
Imp's would hate Humans
Troll's would be enemies of Humans
and Humans of each three, Ogre, Troll, Imp...

I would be able to add to window per button new monster type with specific starting position , it would move in one way...if hit's wall , or some sort of other obstruction would randomly
choose between available directions...
if enemies would go to close to each other less then one block (32x32) , they would fight till one dies...

Naturally I'm right now a bit far from this... but will start working on this immediately.... this way it's fun to learn...


Greetings
Robert

SilverWarior
15-03-2014, 06:13 PM
Here are several suggestions:
1. In pascal it is common that most private variables have a letter F as prefix. So instead of Health you would us FHealth. This way you can have verry similar names for private variables and prperties. This increases your codea readabiliy a lot.
2. Replace mytargetID varaible with pointer to monster class. This way you allow yourself to easily get certain information from that monster (Position, Health, MosterType, etc.) by simply accesing property of that class (no need to search through a list of all monsters to find which has apropriate ID).
3. In OnHit procedure you are substracting armor value from attack (damage) value and then checking to see if that is larger than 0 while you could easily just check to see if armor value is greater than attack value.
Or even better simpy substract armor value from attack value and if that is largen than 0 reduce the health by that result.

procedure OnHit(attack: Byte);
var Damage: Integer;
begin
damage := attack - armor; //Reduce damage by armor
armor := armor - attack; //Reduce armor durability
if armor < 0 then armor := 0; //to avoiud armor being negative mumber
if damage> 0 then
begin
Healt := Health - damage; //Reduce health
if Health <= 0 isDead := True;
end;
end;

This way you also get rid of Died procedure.
4. I also recomend learning about class inheritance: Why? By using class inheritance you make one ancestor class for all units which contains variables common to all of your ingame units (health, postition, unit type, name, description, etc.). And in inherited classes you can add aditional variables which are specific for certain unit types (range for ranged units, armor for armored units, etc.). And becouse Objective Pascal alows you to acces any inherited class by using it paretn class you can acces all the variables declared in parent class without the use of actuall clas of your object.

Man I realy need to finish my Tutorial on working with classes as it would be of great help to you and probably many others.

robert83
15-03-2014, 08:47 PM
Hi ,

I'm afraid I don't understand this part, to tell you the truth I "feel" like there has to be a way to be able to handle the "random" monster selection from within the class ..but I have no idea how.

For example I create monster... and it somehow check for available monsters to attack, but how would this newly created monster know if there are at all other monsters to attack, or how many monsters
are there (sorry for the confusion, I'm a bit confused myself to)

Right now it's really simple because everyone can attack everyone, but say if there would be groups of friendly units, and group of enemy units...I would have problems.

2. Replace mytargetID varaible with pointer to monster class. This way you allow yourself to easily get certain information from that monster (Position, Health, MosterType, etc.) by simply accesing property of that class (no need to search through a list of all monsters to find which has apropriate ID).


Also I've altered the code, seems I have to use strict private...otherwise private values are visible...and modifiable....outside of the class...from TForm for example....

So , private are all with F ,FArmor, FHealth, from within class on the OnHit procedure for example is it ok to use Health or FHealth ?



unit Unit1;

interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Grids, Math,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
Memo1: TMemo;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TMonster = class
strict private // othervise private is visible
FName : string;
FHealth : smallint;
FArmor : smallint;
FAttack : smallint;
FTargetID : byte;
FDead : boolean;
published
property Name : string
read FName write FName;
property Health : smallint
read FHealth write FHealth;
property Armor : smallint
read FArmor write FArmor;
property Attack : smallint
read FAttack write FAttack;
property TargetID : byte
read FTargetID write FTargetID;
property Dead : boolean
read FDead write FDead;
constructor Create (name : string ; health : byte ; armor : byte ; attack : byte);
procedure OnHit( attack : byte );

end;
var
Form1: TForm1;
Ogre : array of TMonster;

implementation
{$R *.dfm}
constructor TMonster.Create(name : string ; health: Byte ; armor : byte ; attack : byte);
begin
FName := name;
FHealth := health;
FArmor := armor;
FAttack :=attack;
FDead := false; // by default alive
end;

procedure TMonster.OnHit(attack: Byte);
var damage : smallint;
begin
damage := attack - armor; //Reduce damage by armor
if damage > 0 then
begin
Health := Health - damage; //Reduce health
if Health <= 0 then
begin
Dead := True;
Health :=0;
end;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
procedure TForm1.FormCreate(Sender: TObject);
var i : integer;
begin
// we have 10 little ogre's
SetLength(Ogre,10);
// each of them has a name , and healtpoints , armor, attack
Ogre[0] := TMonster.Create('William',100,1,1);
Ogre[1] := TMonster.Create('William 2nd',120,1,1);
Ogre[2] := TMonster.Create('Robert the Grumpy',145,2,3);
Ogre[3] := TMonster.Create('Antal the SpaceOgre',150,4,3);
Ogre[4] := TMonster.Create('Sebastian the Unstopable',160,1,4);
Ogre[5] := TMonster.Create('Blorb the Blorb',99,5,6);
Ogre[6] := TMonster.Create('Shrek',49,2,4);
Ogre[7] := TMonster.Create('Devid the Serbian Ogre',180,4,6);
Ogre[8] := TMonster.Create('Keen the Wise Ogre',167,6,9);
Ogre[9] := TMonster.Create('Ogre the Ogre',98,6,11);
StringGrid1.RowCount:=11;
StringGrid1.Cells[0,0]:='Name';
StringGrid1.Cells[1,0]:='Health';
StringGrid1.Cells[2,0]:='Armor';
StringGrid1.Cells[3,0]:='Attack';
for I := 0 to 9 do
begin
StringGrid1.Cells[0,i+1]:=Ogre[i].name;
StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].Health);
StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var i,target: integer;
AvailableTarget : array of integer;
begin
randomize();
Memo1.Lines.Clear;
// fill array with alive monsters
SetLength(AvailableTarget,0);
for i := 0 to 9 do
begin
if Ogre[i].Dead = false then
begin
SetLength(AvailableTarget, length(AvailableTarget)+1);
AvailableTarget[High(AvailableTarget)]:=i;
end;
end;

for I := 0 to 9 do
begin
if Ogre[i].Dead = false then // can only attack if alive
begin
target := AvailableTarget[random(length(AvailableTarget))]; // target is choosen from array of alive monsters
if i <> target then // will not attack self
begin
Ogre[i].TargetID:=target;
Ogre[target].OnHit(Ogre[i].attack);
Memo1.Lines.Add(Ogre[i].name+' attacks '+inttostr(Ogre[i].TargetID)+' '+Ogre[Ogre[i].TargetID].name);
end;
end;
end;


// update ogre stats
for I := 0 to 9 do
begin
StringGrid1.Cells[0,i+1]:=Ogre[i].name;
StringGrid1.Cells[1,i+1]:=inttostr(Ogre[i].Health);
StringGrid1.Cells[2,i+1]:=inttostr(Ogre[i].armor);
StringGrid1.Cells[3,i+1]:=inttostr(Ogre[i].attack);
end;
// if only one target is available , means we have a winner, everyone else is dead
if length(AvailableTarget) = 1 then
begin
Timer1.Enabled:=false;
for i := 0 to 9 do
begin
if Ogre[i].Dead = false then
Memo1.Lines.Add('The Winner is : '+ Ogre[i].name);
end;
end;
end;
end.

robert83
16-03-2014, 03:20 AM
Hi,

I've continued with my next step , which in the end turned out harder then I thought (atleast for me) ...
What I did is...define a map now, and draw it onscreen using shapes... then I've created my monster class which can navigate inside this maze...
Directions are choosen from available directions on random when I hit the wall...

I think it turned out quiet good...anyway I am dead tired , here is the code, I'm off to play one more goodnight SC2 , then off to sleep.



unit MainFrm;
// idea is
// I check available directions...
// then I set a direction
// when I would hit a wall...I check again available directions...
// then I set a direction...
// could be good
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TMain = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Button1: TButton;
Button2: TButton;
Timer1: TTimer;
Edit1: TEdit;
procedure DrawMap();
procedure DrawMonster();
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TDirection = ( dUp, dDown , dLeft, dRight );
TMonster = class
strict private
FName : string;
FStartX,FStartY : byte;
FSetDir : TDirection;
Direction : array of TDirection;
published
property Name : string
read FName write FName;
property StartX : byte
read FStartX write FStartX;
property StartY : byte
read FStartY write FStartY;
property SetDir : TDirection
read FSetDir write FSetDir;
constructor Create( Name : string ; StartX,StartY : byte ; SetDir : TDirection );
procedure GetPossibleDirections();
procedure OnMove;
end;



TMapLine = array [0..19] of Integer;
TMap = array [0..14] of TMapLine;
const
Map: Array [0..14,0..19] of Integer = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1),
(1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1),
(1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1),
(1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
(1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
(1,0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
(1,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1),
(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));

var
Main: TMain;
Monster : array of TMonster;
// hacky way later fix
AI : array of TShape;


implementation
{$R *.dfm}
constructor TMonster.Create(Name: string ; StartX,StartY : byte; SetDir : TDirection);
begin
FName := Name;
FStartX := StartX;
FStartY := StartY;
FSetDir := SetDir;
end;
procedure TMonster.GetPossibleDirections;
begin
// debug
Main.Edit1.Text:='';
// empty directions
SetLength(Direction,0);
// no wall found up, way is free
if Map[StartY-1,StartX] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dUp;
Main.Edit1.Text:=Main.Edit1.Text+' UP ';
end;
// no wall found down, way is free
if Map[StartY+1,StartX] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dDown;
Main.Edit1.Text:=Main.Edit1.Text+' DOWN ';
end;
// no wall found left , way is free
if Map[StartY,StartX-1] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dLeft;
Main.Edit1.Text:=Main.Edit1.Text+' LEFT ';
end;
// no wall found right , way is free
if Map[StartY,StartX+1] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dRight;
Main.Edit1.Text:=Main.Edit1.Text+' RIGHT ';
end;
end;
procedure TMonster.OnMove;
begin
if SetDir = dUp then
begin
if Map[StartY-1,StartX] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dDown then
begin
if Map[StartY+1,StartX] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dRight then
begin
if Map[StartY,StartX+1] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dLeft then
begin
if Map[StartY,StartX-1] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dUp then
StartY:=StartY-1;
if SetDir = dDown then
StartY:=StartY+1;
if SetDir = dRight then
StartX:=StartX+1;
if SetDir = dLeft then
StartX:=StartX-1;

end;
procedure TMain.Button1Click(Sender: TObject);
var i : integer;
begin
DrawMap();
// we do 2 monsters for now
SetLength(Monster,2);
SetLength(AI,2);
Monster[0] := TMonster.Create('Robert',1,1,dDown);
Monster[1] := TMonster.Create('Devid',3,1,dDown);
for i := 0 to length(Monster)-1 do
begin
AI[i] := TShape.Create(self);
AI[i].Parent:=Panel2;
AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
AI[i].Height:=Panel2.Height div 15;
AI[i].Width:=Panel2.Width div 20;
AI[i].Brush.Color:=clGreen;
AI[i].Pen.Color:=clGreen;
AI[i].Visible:=true;
end;
end;
procedure TMain.Button2Click(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
procedure TMain.DrawMonster;
var i : integer;
begin
// re-draw monster at new position
for i := 0 to length(Monster)-1 do
begin
AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
end;
end;
procedure TMain.Timer1Timer(Sender: TObject);
begin
Monster[0].OnMove;
Monster[1].OnMove;
DrawMonster();
end;
procedure TMain.DrawMap;
var x,y: integer;
Wall : TShape;
begin
for y := 0 to 14 do
for x := 0 to 19 do
begin
if Map[y,x] = 1 then
begin
Wall := TShape.Create(self);
Wall.Parent:=Panel2;
Wall.Top:=y*(Panel2.Height div 15);
Wall.Left:=x*(Panel2.Width div 20);
Wall.Height:=Panel2.Height div 15;
Wall.Width:=Panel2.Width div 20;
Wall.Brush.Color:=clRed;
Wall.Pen.Color:=clRed;
Wall.Visible:=true;
end;
end;
end;
end.


the code might be a little ugly I was In a hurry to make 2 monsters walk around....

USAGE!!!!
1. Click Draw
2. Click Move

Otherwise EXCEPTION...

robert83
16-03-2014, 07:11 PM
Hi,

I've been trying to alter the above code, to move the Shape smoothly, instead of this grid jump mode , well I'm having a hell of a time ... since shape has TOP and LEFT value and I'm calculating using that...everything Is fine if I go down, an right...
but the moment I go up or left.... it has problems :
11111
1X001
10111

1 is wall, 0 is nothing, X is the object, my problem is if I move UP or LEFT ... it calculates down to 1,1 the moment the TOP,LEFT first pixel line enters 1,1... is there a way to make this stop... only have 1,1 if I'm fully inside ... (sorry for the terrible explanation)



unit MainFrm;
// idea is
// I check available directions...
// then I set a direction
// when I would hit a wall...I check again available directions...
// then I set a direction...
// could be good
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TMain = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Button1: TButton;
Button2: TButton;
Timer1: TTimer;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure DrawMap();
procedure DrawMonster();
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TDirection = ( dUp, dDown , dLeft, dRight );
TMonster = class
strict private
FName : string;
FPositionX,FPositionY : integer;
FVelocityX,FVelocityY : integer;
FSetDir : TDirection;
Direction : array of TDirection;
published
property Name : string
read FName write FName;
property PositionX : integer
read FPositionX write FPositionX;
property PositionY : integer
read FPositionY write FPositionY;
property SetDir : TDirection
read FSetDir write FSetDir;
property VelocityX : integer
read FVelocityX write FVelocityX;
property VelocityY : integer
read FVelocityY write FVelocityY;
constructor Create( Name : string ; PositionX,PositionY : integer ; SetDir : TDirection );
procedure GetPossibleDirections();
procedure OnMove;
end;



TMapLine = array [0..19] of Integer;
TMap = array [0..14] of TMapLine;
const
Map: Array [0..14,0..19] of Integer = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1),
(1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,1),
(1,0,1,1,1,0,1,1,1,0,0,1,1,0,0,0,0,1,0,1),
(1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1),
(1,0,1,0,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,1),
(1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1),
(1,0,1,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1),
(1,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1),
(1,0,1,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,1),
(1,0,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,0,1),
(1,0,1,0,1,0,1,1,1,0,1,1,0,0,1,0,1,0,0,1),
(1,0,1,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,1),
(1,0,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,0,0,1),
(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1),
(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));

var
Main: TMain;
Monster : array of TMonster;
// hacky way later fix
AI : array of TShape;


implementation
{$R *.dfm}
constructor TMonster.Create(Name: string ; PositionX,PositionY : integer; SetDir : TDirection);
begin
FName := Name;
FPositionX := PositionX;
FPositionY := PositionY;
FSetDir := SetDir;
FVelocityX := 0;
FVelocityY := 0;
end;
procedure TMonster.GetPossibleDirections;
begin
// debug
Main.Edit1.Text:='';
// empty directions
SetLength(Direction,0);
// no wall found up, way is free
if Map[PositionY-1,PositionX] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dUp;
Main.Edit1.Text:=Main.Edit1.Text+' UP ';
end;
// no wall found down, way is free
if Map[PositionY+1,PositionX] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dDown;
Main.Edit1.Text:=Main.Edit1.Text+' DOWN ';
end;
// no wall found left , way is free
if Map[PositionY,PositionX-1] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dLeft;
Main.Edit1.Text:=Main.Edit1.Text+' LEFT ';
end;
// no wall found right , way is free
if Map[PositionY,PositionX+1] <> 1 then
begin
SetLength(Direction,length(Direction)+1);
Direction[High(Direction)]:=dRight;
Main.Edit1.Text:=Main.Edit1.Text+' RIGHT ';
end;
end;
procedure TMonster.OnMove;
begin
VelocityY := 0;
VelocityX := 0;
if SetDir = dUp then
begin
if Map[PositionY-1,PositionX] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dDown then
begin
if Map[PositionY+1,PositionX] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dRight then
begin
if Map[PositionY,PositionX+1] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;
if SetDir = dLeft then
begin
if Map[PositionY,PositionX-1] = 1 then
begin
GetPossibleDirections();
SetDir := Direction[random(length(Direction))];
end;
end;

if SetDir = dUp then
VelocityY:=-2;
if SetDir = dDown then
VelocityY:=+2;
if SetDir = dRight then
VelocityX:=+2;
if SetDir = dLeft then
VelocityX:=-2;

end;
procedure TMain.Button1Click(Sender: TObject);
var i : integer;
begin
DrawMap();
// we do 2 monsters for now
SetLength(Monster,1);
SetLength(AI,1);
Monster[0] := TMonster.Create('Robert',1,1,dDown);
// Monster[1] := TMonster.Create('Devid',3,1,dDown);
// Monster[2] := TMonster.Create('Antal',18,4,dLeft);
for i := 0 to length(Monster)-1 do
begin
AI[i] := TShape.Create(self);
AI[i].Parent:=Panel2;
AI[i].Top:=Monster[i].PositionY*(Panel2.Height div 15);
AI[i].Left:=Monster[i].PositionX*(Panel2.Width div 20);
AI[i].Height:=Panel2.Height div 15;
AI[i].Width:=Panel2.Width div 20;
AI[i].Brush.Color:=clGreen;
AI[i].Pen.Color:=clGreen;
AI[i].Visible:=true;
end;
end;
procedure TMain.Button2Click(Sender: TObject);
var i : integer;
begin
Timer1.Enabled:=true;
{for i := 0 to length(Monster)-1 do
begin
Monster[i].OnMove;
end;
DrawMonster(); }
end;
procedure TMain.DrawMonster;
var i : integer;
begin
// re-draw monster at new position
for i := 0 to length(Monster)-1 do
begin
// AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
// AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
AI[i].Top:=AI[i].Top+Monster[i].VelocityY;
AI[i].Left:=AI[i].Left+Monster[i].VelocityX;
Monster[i].PositionY:=AI[i].Top div (Panel2.Height div 15);
Edit2.Text:=inttostr(Monster[i].PositionY);
Monster[i].PositionX:=AI[i].Left div (panel2.Width div 20);
Edit3.Text:=inttostr(Monster[i].PositionX);
end;
end;
procedure TMain.Timer1Timer(Sender: TObject);
var i : integer;
begin
for i := 0 to length(Monster)-1 do
begin
Monster[i].OnMove;
end;
DrawMonster();
end;
procedure TMain.DrawMap;
var x,y: integer;
Wall : TShape;
begin
for y := 0 to 14 do
for x := 0 to 19 do
begin
if Map[y,x] = 1 then
begin
Wall := TShape.Create(self);
Wall.Parent:=Panel2;
Wall.Top:=y*(Panel2.Height div 15);
Wall.Left:=x*(Panel2.Width div 20);
Wall.Height:=Panel2.Height div 15;
Wall.Width:=Panel2.Width div 20;
Wall.Brush.Color:=clRed;
Wall.Pen.Color:=clRed;
Wall.Visible:=true;
end;
end;
end;
end.


I've altered so there is only one moving monster.

Please help :)

Greetings
Robert

Carver413
16-03-2014, 08:42 PM
it is generally better to use a case statement for TDirection to prevent more then one block of code from executing in a single pass.
also you should build a table of directions in a counter clock list either 4 or 8 directions. now there is no up or down only left or right foward or back.
use an index var to set the direction add to location to move forward, subtract to move back. change index var to turn left or right.
now you can move the monster in the same direction until it hits a wall then chose to go left or right.
this is a link for a hex map there is some good stuff in there that could help you in your own design
http://www.redblobgames.com/grids/hexagons/

robert83
17-03-2014, 07:39 AM
Hi,

thank you for trying, but that confuses me only, and it is not the question I've asked...
the Monster is moving inside the "labyrinth" automatically...


my problem is the grid based collision is only working correctly if I move from grid to grid... if I move smoothly it does not
example :

wall is (pixels)
0,0 - 31,31 (wall)
32,32 - 63,63 (air)

according to my logic the top,left corner of the monster square enters 63,63 , then it is divided down to array position of 1,1
logic checks 1,1 top tile free or not ? -1 = wall! ( is correct... )
movement stops at 63,63 because we hit wall...

what I would like to solve is it should result in 1,1 if I get all the way down to 32,32 .... if it would div by 32 here now
check for wall... and turn in another possible direction I would have "perfect" collision....


Greetings
Robert

ps.: when I get back to work... I'll think about this a bit more, problem is there are many tutorials online, but most of em not work, or are
way to complicated to grasp... I for starters don't need a fully fledged platform game sample which is beyond my comprehension for now :( :( :(

robert83
18-03-2014, 09:14 PM
Hello,

After reading a few tile based collision detection tutorials with smooth scrolling enabled...I realized , that I need to check collisions based on direction I'm traveling in ( this might not work if I go both X,Y at same time, but for now I don't really care, one step at a time and not get overwhelmed)

So right now I kinda hacked my own code... which is ugly


procedure TMain.DrawMonster;
var i : integer;
begin
// re-draw monster at new position
for i := 0 to length(Monster)-1 do
begin
// AI[i].Top:=Monster[i].StartY*(Panel2.Height div 15);
// AI[i].Left:=Monster[i].StartX*(Panel2.Width div 20);
AI[i].Top:=AI[i].Top+Monster[i].VelocityY;
AI[i].Left:=AI[i].Left+Monster[i].VelocityX;
if (Monster[i].SetDir = dDown) or (Monster[i].SetDir = DRight) then
begin
Monster[i].PositionY:=AI[i].Top div (Panel2.Height div 15);
Edit2.Text:=inttostr(Monster[i].PositionY);
Monster[i].PositionX:=AI[i].Left div (panel2.Width div 20);
Edit3.Text:=inttostr(Monster[i].PositionX);
end;

if (Monster[i].SetDir = dUp) or (Monster[i].SetDir = DLeft) then
begin
Monster[i].PositionY:=(AI[i].Top+(AI[i].Height-1)) div (Panel2.Height div 15);
Edit2.Text:=inttostr(Monster[i].PositionY);
Monster[i].PositionX:=(AI[i].Left+(AI[i].Width-1)) div (panel2.Width div 20);
Edit3.Text:=inttostr(Monster[i].PositionX);
end;


end;


My mistake up till now, and with all my previous attempts as well was this
when I calculated bottom and right side I simply did LEFT+WIDTH or TOP+WIDTH...which
in the case of a 32x32 tile
which has top,left 1,1
resulted in a bottom,right 33,33 :) I know it seems so stupid now, but somehow I did not see it...

that Is why now I add to left and top , height-1 and width-1...so I
get 1,1 and 32,32...

I tried it and it works...on the far right corner of the level there is no perfect touch on my monitor 1920x1080, but that is probably because (or 99% sure) my tile sizes are not n2...

so I guess I did It...this will work nicely at 640x480 with 32x32 tiles...

I'll modify my class in the upcoming days..to handle this calculation inside it...and not like this

Greetings
Robert

SilverWarior
18-03-2014, 10:00 PM
Hi!
I would suggest that you start unsig node based approach whend dealing with movment or pathfinding. Nodes are placed in the center of your map cells. Each node contains information about to which other node you can travel from it and movment cost for such movment.
You also handle your units position based on ther center position instead of their TopLeft corner.
So when you are moving your units from one point to another you are simply moving their center position from one map node to another.

Advantages:
- since you store posible movment in node connections you no longer need to always go and check all nearby tiles to figure out if you can move there or not
- becouse you store each posible movment (node connections) for each node you can easily implement one way movment (moving from node A to node B is posible, but moving from node B to anode A is not posible)
- you can have node connection between any two nodes. This alows you to implement things like portals with ease.
- each node connection also stores the movment cost/movment speed so you can easily implement the ability to move faster when going downhill vercus goung uphill.
- most popular pathfinding algorithms bases on node systems so you are already one step closer in implementing any of them.
- by storing aditional value to each node you can easily determine whether a multitile sized unit can move to that tile or not (http://aigamedev.com/open/tutorial/clearance-based-pathfinding/)

Disadvantages:
- you need to generate and store node graph which could take some time and increases memory consumption
- if using dynamic map loading (chunk based maps) you need to also dynamically update this node graph upon loading and unloading of each chunk.

If you want I can make you a quick and dirty example of this but you will have to wait athleast till tomorow as I need to go to bed now in order to wake up early enough to go to work tomorow.