Autor Beitrag
F34r0fTh3D4rk
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 5284
Erhaltene Danke: 27

Win Vista (32), Win 7 (64)
Eclipse, SciTE, Lazarus
BeitragVerfasst: Mo 29.05.06 16:25 
PONG KI Tutorial

user defined image
(Quelle www.wikipedia.de)

Jeder von euch kennt das Spiel und viele würden es sicher gerne einmal
programmieren. Nunja, aber was nun, 2 Spieler an einem Rechner ?
Ein wenig eng oder ? Netzwerk, nicht ganz einfach zu programmieren,
was also tun ? Die Lösung ist eine KI. Das Wort mag ein wenig abschrecken,
aber es ist nicht so schwer wie es sich anhört, denn hier stelle ich eine
sehr einfache und schnelle Möglichkeit vor,
eine KI zu erstellen, vorausgesetzt ist, dass das gröbste vom Spiel
schon fertig ist.

Gehen wir mal von folgenden Typen aus:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
type
//TBall hat eine Position, eine Geschwindigkeit und einen Radius
  TBall = record
    position,
    velocity: TVector2d;
    radius: double;
  end;
//TSchlaeger hat eine Position, eine Geschwindigkeit und eine Größe
  TSchlaeger = record
    position,
    velocity,
    size: TVector2d;
  end;
//TRaum definiert die Raumausmaße
  TRaum = record
    size: TVector2d;
  end;
//Richtung für die Schlägerbewegung
  TDirection = (dirUp, dirDown);

TVector2d ist hier nichts anderes als:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
type
  TVector2d = record
    x,
    y: double;
  end;

Ich habe dem Schläger auch mal eine X-Koordinate gegeben, obwohl diese letzenendes statisch ist.

Die Kollisionsabfrage Ball<->Wand, Ball<->Schläger sollte funktionieren, denn jetzt geht es an die KI, nehmen wir mal an, dass der Ball sich mit negativer X-Geschwindigkeit in die Rihctung unseres Computerschlägers bewegt, die Raummitte bei (0|0) liegt und "nach oben" positiv ist:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
//Einen Schläger bewegen
procedure MoveSchlaeger(var aSchlaeger: TSchlaeger; aDirection: TDirection);
begin
  //Nach oben bzw unten, mit der jeweiligen Geschwinddigkeit
  case aDirection of
    dirUp: aSchlaeger.position.y := aSchlaeger.position.y + aSchlaeger.velocity.y * timestep;
    dirDown: aSchlaeger.position.y := aSchlaeger.position.y - aSchlaeger.velocity.y * timestep;
  end;
end;

//Verhindern, dass der Schlaeger dem Spielfeld entweicht
procedure SchlaegerKollision(var aSchlaeger: TSchlaeger; aRaum: TRaum);
begin
  //Bei Kollision mit der Wand zurücksetzen
  if aSchlaeger.position.y + aSchlaeger.size.y / 2 >= aRaum.size.y / 2 then
    aSchlaeger.position.y := aRaum.size.y / 2 - aSchlaeger.size.y / 2;

  if aSchlaeger.position.y - aSchlaeger.size.y / 2 <= -aRaum.size.y / 2 then
    aSchlaeger.position.y := - aRaum.size.y / 2 + aSchlaeger.size.y / 2;
end;

//Die KI Prozedur
procedure Game.KI(aBall: TBall; aSchlaeger: TSchlaeger; aRaum: TRaum);
begin
  //Wenn der Ball sich vom Schläger wegbewegt gehe zurück zur Mitte
  if aBall.velocity.x > 0 then
  begin
    if (aSchlaeger.y < 0then
      MoveSchlaeger(aSchlaeger, dirUp);
    if (aSchlaeger.y > 0then
      MoveSchlaeger(aSchlaeger, dirDown);
  end else
  //Wenn der Ball sich auf den Schläger zubewegt und sich mindestens in der Raumhäfte des Computer Spielers bewegt reagieren
  if aBall.position < 0 then
  begin
    if aSchlaeger.position.y < aBall.position.y then
      MoveSchlaeger(aSchlaeger, dirUp);
    if aSchlaeger.position.y > aBall.position.y then
      MoveSchlaeger(aSchlaeger, dirDown);
    SchlaegerKollision(aSchlaeger, aRaum);
  end;
end;

timestep ist die Zeitdifferenz zum letzten Rechenschritt, wer mehr darüber erfahren möchte kann einmal hier reinschauen: wiki.delphigl.com/in...p/Timebased_Movement

Jetzt sollte die KI funktionieren, leider ist das ganze ein wenig langweilig, da der Schlaeger immer nur dem Ball folgt und ohne jegliche Fehlertoleranz spielt.

Jetzt muss der PC Spieler also von intelligent wieder auf dumm gestellt werden, nur wie bewerkstelligen ?

Der Klasse TGame oder bei den globalen Variablen müssen einige Ergänzungen gemacht werden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
const
  kistrength = 0.4;
var
  KiTime,
  KiTarget: double


Zur Erklärung, KI Strength ist die Stärke unserer KI, sie bestimmt, inwelchen Intervallen das Ziel der KI berechnet werden soll. Das ganze passiert somit nicht jeden Rechenschritt. Bewegen tut sich der Schläger trotzdem, sonst würde er stottern, bzw der gewünschte Effekt tritt nicht ein. Mit dem Wert muss ein wenig rumgespielt werden bis es passt, 0 sollte auf jedenfall das Optimum sein, der PC sollte dann so spielen wie zuvor.

wir passen unsere Prozedur also folgendermaßen an:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
procedure Game.KI(aBall: TBall; aSchlaeger: TSchlaeger; aRaum: TRaum);
begin
  KITime := KITime + 2 * timestep;
  if KITime >= KIStrength then
  begin
    KITime := 0;
    KITarget := aBall.position.y;
  end;

  //Wenn der Ball sich vom Schläger wegbewegt gehe zurück zur Mitte
  if aBall.velocity.x > 0 then
  begin
    if (aSchlaeger.y < 0then
      MoveSchlaeger(aSchlaeger, dirUp);
    if (aSchlaeger.y > 0then
      MoveSchlaeger(aSchlaeger, dirDown);
  end else
  //Wenn der Ball sich auf den Schläger zubewegt und sich mindestens in der Raumhäfte des Computer Spielers bewegt reagieren
  if aBall.position < 0 then
  begin
    if aSchlaeger.position.y < KITarget then
      MoveSchlaeger(aSchlaeger, dirUp);
    if aSchlaeger.position.y > KITarget then
      MoveSchlaeger(aSchlaeger, dirDown);
    SchlaegerKollision(aSchlaeger, aRaum);
  end;
end;

Die bearbeiteten Zeilen habe ich markiert, Die 2 in der
Zeile mit dem Timestep hat eigentlich mehr oder weniger eine symbolische Bedeutung, wenn ihr keinen timestep nutzt, dann kann dort auch eine 1 stehen. Man kann auch nur timestep schreiben, das hängt von der Programmierweise ab, also mal wieder etwas zum rumprobieren (nicht "Rum probieren" sondern rumprobieren ;) ).

Das ganze kann man folgendermaßen zusammenfassen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure Game.KI(aBall: TBall; aSchlaeger: TSchlaeger; aRaum: TRaum);
begin
  KITime := KITime + 2 * timestep;
  if KITime >= KIStrength then
  begin
    KITime := 0;
    if aBall.velocity.x > 0 then
      KITarget := aBall.position.y else
        KITarget := 0;
  end;
  if aSchlaeger.position.y < KITarget then
    MoveSchlaeger(aSchlaeger, dirUp);
  if aSchlaeger.position.y > KITarget then
    MoveSchlaeger(aSchlaeger, dirDown);
  SchlaegerKollision(aSchlaeger, aRaum);
end;


Weiterführend kann man den KIStrength Wert auch noch an die Spielerstärke anpassen um die KI an den Spieler anzupassen, das kann eventuell den Spielspass erhöhen.

Das wars vorerst, ich freue mich auf Feedback, ein Beispiel werde ich in kürze nachreichen, der Code wurde auch nicht getestet, Fehler bitte melden ;)

MFG


Zuletzt bearbeitet von F34r0fTh3D4rk am Di 01.01.08 16:15, insgesamt 3-mal bearbeitet
Leuchtturm
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1087

Win Vista, Knoppix, Ubuntu
Delphi 7 Pe, Turbo Delphi, C#(VS 2005 Express), (X)HTML + CSS, bald Assembler
BeitragVerfasst: Fr 06.10.06 18:25 
Hi,
ganz gutes Tutorial um sich in die Funktionsweise eine PONGKI hineinzuversetzen :zustimm:
Ein Fehler in habe ich in der Prozedur SchlaegerKollision gefunden
user profile iconF34r0fTh3D4rk hat folgendes geschrieben:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure SchlaegerKollision(var aSchlaeger: TSchlaeger; aRaum: TRaum);
begin
  //Bei Kollision mit der Wand zurücksetzen
  if aSchlaeger.position.y + aSchlaeger.size.y / 2 >= aRoom.size.y / 2 then
    aSchlaeger.position.y := aRoom.size.y / 2 - aSchlaeger.size.y / 2;

  if aSchlaeger.position.y - aSchlaeger.size.y / 2 <= -aRoom.size.y / 2 then
    aSchlaeger.position.y := - aRoom.size.y / 2 + aSchlaeger.size.y / 2;
end;

das gehighlightete stimmt nicht ganz überein :wink:

Bei dem angehangenem Projekt kommen die Fehlermeldungen:
Fehler hat folgendes geschrieben:
[Fehler] pong_fear2d.pas(233): Inkompatible Typen
[Fehler] pong_fear2d.pas(17): Ungenügende Forward- oder External-Deklaration: 'TForm1.MoveSchlaeger'
[Fehler] pong_fear2d.pas(18): Ungenügende Forward- oder External-Deklaration: 'TForm1.SchlaegerKollision'
[Fehler] pong_fear2d.pas(19): Ungenügende Forward- oder External-Deklaration: 'TForm1.GameKI'
[Fataler Fehler] Pong.dpr(5): Verwendete Unit 'pong_fear2d.pas' kann nicht compiliert werden


Ansonsten ist das ganze ziemlich gut
Leuchtturm
Einloggen, um Attachments anzusehen!
_________________
Ich bin dafür verantwortlich was ich sage - nicht dafür was du verstehst.