Vezérek elhelyezése sakktáblán Genetikus algoritmussal

 

A genetikus algoritmusoknak ott nagy a létjogosultsága, ahol a feladatok megoldását nagyon magas elemszámú állapottérből kell megkeresni. Ekkor a fentebb (előző vezér-elhelyezési programokban) is használt keresési eljárások a gépek korlátozott sebessége miatt nem használható.

 

Mint láttuk, vannak olyan reprezentációi a kitűzött feladatnak, melyek egész hamar megoldásra vezetnek. Ezek a reprezentációk már önmagukban hordozzák a feladat specifikus tulajdonságait, így az állapottér elemszáma értelmes méretekre csökkenthető volt.

 

Ebben a szakaszban mintegy elfelejtve az imént említett reprezentációkat, úgy tekintünk a feladatra, mintha az elemi tulajdonságait nem ismernénk, azt csak az algoritmus közben, az egyedek jóságának vizsgálatakor érvényesítjük. Így az egyedek a populációk során válnak külön-külön a jó tulajdonságok hordozóivá.

 

         A Genetikus algoritmus lényege az, hogy kezdetben véletlenül megválasztott elemekből létrehozunk egy populációt (genetikus kódot, elrendezést, az elemszámot a feladat maga, illetve a rendelkezésre álló erőforrás erősen befolyásolja). Majd a következő populációt (generációt) az előzőből például keresztezéssel és/vagy mutációval létrehozzuk. A keresztezés azt jelenti, hogy két egyed genetikus kódját részben kicseréljük, így új egyedeket hozunk létre. A mutációnál az egyes egyed genetikus kódját véletlen módon megváltoztatjuk.

 

A keresztezésnél az a cél, hogy azon egyedek kódja öröklődjenek, amelyek jók. Tehát két jónak minősített egyedet keresztezünk, és két rossznak (gyengének) mutatkozó egyed helyett ezek fognak szerepelni a következő populációban. Így elvileg a populáció egyre jobb tulajdonságú elemekből fog állni. Azt várjuk, hogy keresztezés révén a populációban elő fog fordulni legalább egy, a lehető legjobb tulajdonságokkal rendelkező egyed, ami a feladat megoldását jelenti. Ez persze általában nem így van. Ha az egyedek összességében nem hordozzák a legjobb megoldás kódját, akkor nem kapjuk meg a feladat megoldását. Ekkor az egyedek jósága egy bizonyos szint fölé nem fog emelkedni és szükség lesz a mutációra.

 

         Mutációt akkor kell alkalmazni, amikor a populáció jósága egy szinten (de nem a tökéletes megoldást jelentőn) megakad, azaz keresztezéssel már nem képes tovább fejlődni. Ekkor az egyedek genetikus kódját egy adott százalékos valószínűséggel, véletlen értékre változtatjuk, azaz a kódot frissítjük. Ettől azt várjuk, hogy a holtponti helyzetből a populáció kimozdul, lesznek újra rossz és jó tulajdonságú egyedek, és reményeink szerint a véletlen választás folytán előáll a legjobb tulajdonságú egyed előállítására alkalmas genetikai kód is. Általában igaz, hogy a mutációt nem túl magas százalékban kell alkalmazni, különben a már felhalmozódott jó tulajdonságok eltűnnek az egyedekből.

 

Az itt leírtak szerint készült a címben megfogalmazott feladatra a program. A programnak nem az a célja, hogy az előző feladatokban látható megoldások felett győzedelmeskedjen. Az összes lehetséges elhelyezést megadó programon egyébként már nincs mit javítani, az pillanatok alatt mindent elmond a problémáról. A cél tehát a genetikus algoritmus bemutatása egy viszonylag egyszerű, más eszközökkel is megoldott problémára. A program listája szerintem megfelelően kommentezett, így magyarázatra nem szorul.

 

A program futási képei. Induláskor:

 

 

A keresés végén:

 

 

 

A program listája.

 

unit UGenVezer;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes,

  Graphics, Controls, Forms, Dialogs, StdCtrls, Grids;

Const EgyedSz=40;
      Max=8;
type
  TfmGenVezer = class(TForm)
    lbHenVezer: TLabel;
    btKilepes: TButton;
    sgGenVezer: TStringGrid;
    btUjPop: TButton;
    sgTabla: TStringGrid;
    lbJosag: TLabel;
    edJosag: TEdit;
    lbEgyedSz: TLabel;
    edEgyedSz: TEdit;
    lbJokSz: TLabel;
    edJokSz: TEdit;
    btStart: TButton;
    lbPopSz: TLabel;
    edPSz: TEdit;
    lbKesz: TLabel;
    edKesz: TEdit;
    lbIndex: TLabel;
    edIndex: TEdit;
    lbMutacio: TLabel;
    edMutacio: TEdit;
    lbSzazalek: TLabel;
    lbKereszt: TLabel;
    edKereszt: TEdit;
    Procedure PopInit;
    Procedure Vizsgal;
    Procedure PopKepre;
    Procedure Tablara(Ind: Word);
    Procedure Keresztez;
    Procedure Mutacio;
    procedure FormCreate(Sender: TObject);
    procedure btKilepesClick(Sender: TObject);
    procedure sgGenVezerDrawCell(Sender: TObject; Col, Row: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure sgGenVezerClick(Sender: TObject);
    procedure btUjPopClick(Sender: TObject);
    procedure sgTablaDrawCell(Sender: TObject; Col, Row: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure btStartClick(Sender: TObject);
    procedure edMutacioChange(Sender: TObject);
    procedure edKeresztChange(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  THely=Record
    X, Y: Byte;
    Jo: Boolean;      //true, ha a vezér nincs ütésben
  End;

  TEgyed=Record
    EXY: Array[1..Max] Of THely;
    EOK: Byte;        //az ütésben nem álló vezérek száma
    JoEgyed: Boolean; //true, ha az ütésben nem álló vezérek szám átlag feletti
  End;

var
  fmGenVezer: TfmGenVezer;
  ACol, ARow: Integer;
  Colors: Array[1..Max,1..Max] Of Boolean;
  EgyedT: Array[1..EgyedSz] Of TEgyed;
  JokSz: Word;        //az átlag feletti egyedek száma
  Josag: Real;        //a populáció ütésben nem álló vezéreinek átlaga
  Kereszt: Byte;      //a keresztezési index
  Uj1, Uj2: TEgyed;   //új egyedek
  IR1, IR2, IJ1, IJ2: Word;  //régi és új egyedek tömbindexei
  PSz: Word;                 //populációk száma
  OKMax, IMax, OldMax, //segédváltozók a populációk generálásánál
  MutSz: Word;         //mutáció erősségét mutató százalékérték

implementation

{$R *.dfm}

procedure TfmGenVezer.btKilepesClick(Sender: TObject);
begin
  //kilépés a programból
  Close;
end;

procedure TfmGenVezer.sgGenVezerClick(Sender: TObject);
begin
  //kattintás után az egyed megjelenítése a képernyőn lévő sakktáblán
  PopKepre;
  With sgGenVezer Do Begin ACol:= Col; ARow:= Row; RePaint End;
  Tablara(ARow);
end;

procedure TfmGenVezer.sgGenVezerDrawCell(Sender: TObject; Col,
  Row: Integer; Rect: TRect; State: TGridDrawState);
begin
  //a populációt megjelenítő StringGrid beállításai
  With sgGenVezer.Canvas.Brush Do
  Begin
    {rögzített cellák}
    If (gdFixed In State) And ((Col=ACol) Or (Row=ARow)) Then
    Color:= clYellow Else Color:=clBtnFace;

    {kiválasztott cella}
    If gdSelected In State Then Color:= clRed;

    {a táblázat belseje}
    If Not((gdSelected In State) Or (gdFixed In State)) Then
    If Odd(Col) Then Color:= clAqua Else Color:= clWindow;
  End;
  sgGenVezer.Canvas.TextRect(Rect,Rect.Left+3,Rect.Top+1,
                             sgGenVezer.Cells[Col,Row]);
  If gdFocused In State Then sgGenVezer.Canvas.DrawFocusRect(Rect);
end;

procedure TfmGenVezer.sgTablaDrawCell(Sender: TObject; Col, Row: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  //a sakktáblát és felállást megjelenítő StringGrid beállításai
  With sgTabla.Canvas.Brush Do
  Begin
    {rögzített cellák}
    If gdFixed In State Then Color:=clWhite;

    {kiválasztott cella}
    If gdSelected In State Then Color:= clSilver;

    {a táblázat belseje}
    If Not((gdSelected In State) Or (gdFixed In State)) Then
    Begin
      Case Odd(Col) XOr Odd(Row) Of
        False: Color:= clWindow;
        True: Color:= clSilver;
      End;
      If Colors[Col,Row] Then Color:= clGreen;
    End;
    sgTabla.Canvas.Font.Size:= 17;
  End;
  sgTabla.Canvas.TextRect(Rect,Rect.Left+16,
                          Rect.Top+4,sgTabla.Cells[Col,Row]);
  If gdFocused In State Then sgTabla.Canvas.DrawFocusRect(Rect);
end;

procedure TfmGenVezer.edKeresztChange(Sender: TObject);
Var Kod: Integer;
begin
  Val(edKereszt.Text, Kereszt, Kod);
end;

procedure TfmGenVezer.edMutacioChange(Sender: TObject);
Var Kod: Integer;
begin
  Val(edMutacio.Text, MutSz, Kod);
end;

Procedure TfmGenVezer.PopInit;
Var I, J: Word;
Begin
  //egy teljes populáció létrehozása
  For I:= 1 To EgyedSz Do With EgyedT[I] Do
  Begin
    For J:= 1 To Max Do With EXY[J] Do
    Begin
      X:= Random(Max)+1;
      Y:= Random(Max)+1;
      Jo:= True;
    End;
    EOK:= 0;
    JoEgyed:= False;
  End;
End;

Procedure TfmGenVezer.Vizsgal;
Var I, J, K, Sz: Word;
    Utesben: Boolean;
Begin
  //a populáció vizsgálata
  //megállapítja minden vezérről, hogy ütésben van-e (-> Utesben)
  //megállapítja minden egyedről, hogy hány vezér elhelyezkedése jó (-> Jo)
  JokSz:= 0; OKMax:= 0;
  For I:= 1 To EgyedSz Do With EgyedT[I] Do
  Begin
    Sz:= 0;
    For J:= 1 To Max Do
    Begin
      Utesben:= False; For K:= 1 To Max Do If J<>K Then
      If EXY[J].X=EXY[K].X Then Begin Utesben:= True; Break End;
      If Not Utesben Then For K:= 1 To Max Do If J<>K Then
      If EXY[J].Y=EXY[K].Y Then Begin Utesben:= True; Break End;
      If Not Utesben Then For K:= 1 To Max Do If J<>K Then
      If Abs(EXY[J].X-EXY[K].X)=Abs(EXY[J].Y-EXY[K].Y) Then
      Begin Utesben:= True; Break End;
      EXY[J].Jo:= Not Utesben;
      If Not Utesben Then Inc(Sz);
    End;
    EOK:= Sz; If EOK>OKMax Then Begin OKMax:= EOK; IMax:= I End;
    Inc(JokSz,EOK);
  End;
  //megállapítja a populáció jóságát:
  //jó helyezetű vezérek számának átlaga-> Josag
  Josag:= JokSz/EgyedSz;
  edJosag.Text:= FloatToStr(Josag);
  //minden egyedről megállapítja, hogy jó-e:
  //átlag feletti a jó helyzetű vezérek száma -> JoEgyed:= True
  JokSz:= 0;
  For I:= 1 To EgyedSz Do With EgyedT[I] Do If EOK>Josag Then
  Begin
    Inc(JokSz);
    JoEgyed:= True;
  End Else JoEgyed:= False;
  edJokSz.Text:= IntToStr(JokSz);
End;

Procedure TfmGenVezer.Keresztez;
Var I, V, R: Word;
Begin
  Inc(PSz);
  //a két legrosszabb egyed keresése
  V:= Random(EgyedSz)+1; R:= Max; IR1:= 0;
  For I:= V+1 To EgyedSz Do With EgyedT[I] Do
  If Not JoEgyed And (EOK<R) Then Begin R:= EOK; IR1:= I End;
  For I:= 1 To V  Do With EgyedT[I] Do
  If Not JoEgyed And (EOK<R) Then Begin R:= EOK; IR1:= I End;

  V:= Random(EgyedSz)+1; R:= Max; IR2:= 0;
  For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IR1 Then
  If Not JoEgyed And (EOK<R)Then Begin R:= EOK; IR2:= I End;
  For I:= 1 To V  Do With EgyedT[I] Do If I<>IR1 Then
  If Not JoEgyed And (EOK<R) Then Begin R:= EOK; IR2:= I End;

  //két jó egyed keresése:
  //nem a két legjobbat, mert akkor nem lenne eléggé nagy a változatosság
  V:= Random(EgyedSz)+1; IJ1:= 0;
  For I:= V+1 To EgyedSz Do With EgyedT[I] Do If JoEgyed Then IJ1:= I;
  For I:= 1 To V  Do With EgyedT[I] Do If JoEgyed Then IJ1:= I;

  V:= Random(EgyedSz)+1; IJ2:= 0;
  For I:= V+1 To EgyedSz Do With EgyedT[I] Do If I<>IJ1 Then
  If JoEgyed Then IJ2:= I;
  For I:= 1 To V  Do With EgyedT[I] Do If I<>IJ1 Then
  If JoEgyed Then IJ2:= I;

  //két jó egyed keresztezése -> Uj1, Uj2: TEgyed
  If IJ1*IJ2<>0 Then
  Begin
    For I:= 1 To Kereszt Do Uj1.EXY[I]:= EgyedT[IJ1].EXY[I];
    For I:= Kereszt+1 To Max Do Uj1.EXY[I]:= EgyedT[IJ2].EXY[I];
    Uj1.JoEgyed:= False;
    For I:= 1 To Kereszt Do Uj2.EXY[I]:= EgyedT[IJ2].EXY[I];
    For I:= Kereszt+1 To Max Do Uj2.EXY[I]:= EgyedT[IJ1].EXY[I];
    Uj2.JoEgyed:= False;
  End;
End;

Procedure TfmGenVezer.Mutacio;
Var I, J: Word;
Begin
  //a populáció minden egyedének mindkét koordinátáját MutSz valószínűséggel
  //módosítjuk egy véletlen értékr
  For I:= 1 To EgyedSz Do With EgyedT[I] Do
  For J:= 1 To Max Do With EXY[J] Do
  Begin
    If Random(100)<MutSz Then X:= Random(Max)+1;
    If Random(100)<MutSz Then Y:= Random(Max)+1;
  End;
End;

procedure TfmGenVezer.btStartClick(Sender: TObject);
begin
  //populációk generálása és vizsgálata
  OKMax:= 0; IMax:= 0; PSz:= 0;
  Repeat
    OldMax:= OKMax;
    Keresztez;
    If JokSz In [2,3,4] Then Mutacio;
    If (IR1*IR2<>0) And (IJ1*IJ2<>0) Then
    Begin
      EgyedT[IR1]:= Uj1;
      EgyedT[IR2]:= Uj2;
    End
    Else Mutacio;
    Vizsgal;
    PopKepre;
    edPSz.Repaint;
    If OKMax>OldMax Then
    Begin
      sgGenVezer.Repaint;
      edKesz.Text:= IntToStr(OKMax); edKesz.Repaint;
      edIndex.Text:= IntToStr(IMax); edIndex.Repaint;
      Tablara(IMax); sgTabla.Repaint;
    End;
  Until (OKMax>=Max) Or (PSz>20000);
  //max értékig, vagy maximum 20000 permutációig keresünk

end;

Procedure TfmGenVezer.PopKepre;
Var I, J: Word;
Begin
  //a populáció minden egyedét, a jó elhelyezkedésü vezérek számával együtt
  //megjeleníti egy StringGrid-ben
  With sgGenVezer Do
  Begin
    For I:= 1 To EgyedSz Do With EgyedT[I] Do
    Begin
      For J:= 1 To Max Do With EXY[J] Do
      Begin
        Cells[2*J-1,I]:= Char(96+X);
        Cells[2*J  ,I]:= IntToStr(Y);
      End;
      Cells[ColCount-1,I]:= IntToStr(EOK);
    End;
  End;
  edPSz.Text:= IntToStr(PSz);
End;

Procedure TfmGenVezer.Tablara(Ind: Word);
Var I, J: Word;
Begin
  //egy egyed megjelenítés a sakktáblán
  With sgTabla Do
  Begin
    For I:= 1 To ColCount-1 Do For J:= 1 To RowCount-1 Do Cells[I,J]:= '';
    For I:=1 To Max Do For J:= 1 To Max Do Colors[I,J]:= False;
    With EgyedT[Ind] Do For I:= 1 To Max Do With EXY[I] Do
    Begin
      Cells[X,Max-Y+1]:= 'V';
      Colors[X,Max-Y+1]:= Jo;
    End;
  End;
  sgTabla.Repaint;
End;

procedure TfmGenVezer.FormCreate(Sender: TObject);
Var I: Word;
    Ch: Char;
begin
  //a program vízuális elemeinek megjelenítése, a kezdeti populáció generálása
  ACol:= 1; ARow:= 1;
  With sgGenVezer Do
  Begin
    RowCount:= EgyedSz+1;
    ColWidths[0]:= 35;
    For I:= 1 To RowCount-1 Do Cells[0,I]:= IntToStr(I)+'.';
    For I:= 1 To Max Do
    Begin
      Cells[2*I-1,0]:= IntToStr(I)+'.X';
      Cells[2*I  ,0]:= IntToStr(I)+'.Y';
    End;
    Cells[ColCount-1,0]:= 'EOK';
  End;

  With sgTabla Do
  Begin
    ColWidths[9]:= 0;
    RowHeights[9]:= 0;
    For I:= 1 To 8 Do Cells[0,I]:= IntToStr(9-I);
    For Ch:= 'a' To 'h' Do Cells[Ord(Ch)-96,0]:= Ch;
    Col:= 9; Row:= 9;
  End;

  Randomize;
  PopInit;
  Vizsgal;
  PopKepre;

  //kezdő és alapértelmezett értékek:
  edEgyedSz.Text:= IntToStr(EgyedSz);
  PSz:= 1; edPSz.Text:= IntToStr(PSz);
  MutSz:= 25; edMutacio.Text:= IntToStr(MutSz);
  Kereszt:= 4; edKereszt.Text:= IntToStr(Kereszt);

end;

procedure TfmGenVezer.btUjPopClick(Sender: TObject);
Var I, J: Word;
begin
  //új populáció generálása
  For I:= 1 To Max Do For J:= 1 To Max Do Colors[I,J]:= False;
  With sgTabla Do
  For I:= 1 To ColCount-1 Do For J:= 1 To RowCount-1 Do Cells[I,J]:= '';
  PopInit;
  Vizsgal;
  PopKepre;
  PSz:= 1; edPSz.Text:= IntToStr(PSz);
end;

end.