PONG KI Tutorial
(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:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| type TBall = record position, velocity: TVector2d; radius: double; end; TSchlaeger = record position, velocity, size: TVector2d; end; TRaum = record size: TVector2d; end; TDirection = (dirUp, dirDown); |
TVector2d ist hier nichts anderes als:
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:
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:
| procedure MoveSchlaeger(var aSchlaeger: TSchlaeger; aDirection: TDirection); begin 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;
procedure SchlaegerKollision(var aSchlaeger: TSchlaeger; aRaum: TRaum); begin 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;
procedure Game.KI(aBall: TBall; aSchlaeger: TSchlaeger; aRaum: TRaum); begin if aBall.velocity.x > 0 then begin if (aSchlaeger.y < 0) then MoveSchlaeger(aSchlaeger, dirUp); if (aSchlaeger.y > 0) then MoveSchlaeger(aSchlaeger, dirDown); end else 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:
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:
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; if aBall.velocity.x > 0 then begin if (aSchlaeger.y < 0) then MoveSchlaeger(aSchlaeger, dirUp); if (aSchlaeger.y > 0) then MoveSchlaeger(aSchlaeger, dirDown); end else 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:
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