Results 1 to 9 of 9

Thread: Object Oriented programin (step one of many)

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1

    Object Oriented programin (step one of many)

    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....

    Code:
    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

  2. #2
    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.
    Code:
    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.

  3. #3
    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 ?

    Code:
    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.

  4. #4
    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.

    Code:
    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...
    Attached Files Attached Files

  5. #5
    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)

    Code:
    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

  6. #6
    PGDCE Developer Carver413's Avatar
    Join Date
    Jun 2010
    Location
    Spokane,WA,Usa
    Posts
    206
    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/

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •