AI ignores impossible balls

main
Benjamin Kraft 1 year ago
parent b9c07db9d0
commit 51a6d1fcea
  1. 73
      Assets/Scripts/Game/AIPlayer.cs
  2. 2
      Assets/Scripts/Game/Ball.cs
  3. 31
      Assets/Scripts/Game/GameManager.cs
  4. 21
      Assets/Scripts/Game/Player.cs

@ -8,7 +8,6 @@ namespace Game {
/* /*
* Possible optimizations: * Possible optimizations:
* - Ignore impossible balls
* - Try to hit ball with edge * - Try to hit ball with edge
*/ */
@ -16,6 +15,8 @@ namespace Game {
public Difficulty Difficulty { get; set; } public Difficulty Difficulty { get; set; }
private float FutureSeconds => (float) Difficulty * 0.5f;
// True if ball y velocity points towards player // True if ball y velocity points towards player
private bool BallApproaches(Ball ball) { private bool BallApproaches(Ball ball) {
var ballVy = ball.Rb.velocity.y; var ballVy = ball.Rb.velocity.y;
@ -32,8 +33,9 @@ namespace Game {
} }
// o: Origin, e: End // o: Origin, e: End
public static bool Intersect(Vector2 o1, Vector2 e1, Vector2 o2, Vector2 e2, out Vector2 point) { public static bool Intersect(Vector2 o1, Vector2 e1, Vector2 o2, Vector2 e2, out Vector2 point, out Vector2 rs) {
point = Vector2.zero; point = Vector2.zero;
rs = Vector2.zero;
Vector2 m1 = e1 - o1; Vector2 m1 = e1 - o1;
Vector2 m2 = e2 - o2; Vector2 m2 = e2 - o2;
@ -61,64 +63,84 @@ namespace Game {
if (s is > 0 and < 1 && r is > 0 and < 1) { if (s is > 0 and < 1 && r is > 0 and < 1) {
point = o1 + r * m1; point = o1 + r * m1;
rs = new Vector2(r, s);
return true; return true;
} }
return false; return false;
} }
private Vector2 Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft) { private float IdlePosition => Difficulty >= Difficulty.Medium ? 0 : X();
private bool IsPositionReachableInTime(float futurePosition, float seconds) {
float requiredDistance = Mathf.Abs(futurePosition - X()) - Width / 2;
if (requiredDistance < 0)
return true;
return Speed * seconds > requiredDistance;
}
private float Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft, float secondsUsed, out bool impossible) {
impossible = false;
Vector2 validAreaOrigin = new Vector2(BorderSize.Singleton.x1, BorderSize.Singleton.y1) + new Vector2(radius, radius); Vector2 validAreaOrigin = new Vector2(BorderSize.Singleton.x1, BorderSize.Singleton.y1) + new Vector2(radius, radius);
Vector2 validAreaSize = BorderSize.Singleton.Size - new Vector2(radius, radius) * 2; Vector2 validAreaSize = BorderSize.Singleton.Size - new Vector2(radius, radius) * 2;
Rect area = new Rect(validAreaOrigin, validAreaSize); Rect area = new Rect(validAreaOrigin, validAreaSize);
// Try to follow this line from origin -> end
Vector2 end = origin + velocity * secondsLeft; Vector2 end = origin + velocity * secondsLeft;
// Line ends in playground
if (area.Contains(end)) if (area.Contains(end))
return end; return end.x;
float playerY = Side == ESide.Bottom ? area.yMin : area.yMax; float playerY = Side == ESide.Bottom ? area.yMin : area.yMax;
Vector2 playerLeft = new Vector2(area.xMin, playerY); Vector2 playerLeft = new Vector2(area.xMin, playerY);
Vector2 playerRight = new Vector2(area.xMax, playerY); Vector2 playerRight = new Vector2(area.xMax, playerY);
// Horizontal line (player line) -> stop simulating // Horizontal line (player line) -> stop simulating
if (Intersect(origin, end, playerLeft, playerRight, out Vector2 point)) if (Intersect(origin, end, playerLeft, playerRight, out Vector2 point, out Vector2 rs)) {
return point; secondsUsed += secondsLeft * rs.x;
if (!IsPositionReachableInTime(point.x, secondsUsed))
impossible = true;
return point.x;
}
bool borderHit = false; bool borderHit = false;
Vector2 borderHitPoint = Vector2.zero; Vector2 borderHitPoint = Vector2.zero;
Vector2 borderRs = Vector2.zero;
// Left vertical border // Left vertical border
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point)) { if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point, out rs)) {
borderHit = true; borderHit = true;
borderHitPoint = point; borderHitPoint = point;
borderRs = rs;
} }
// Right vertical border // Right vertical border
if (Intersect(origin, end, new Vector2(area.xMax, area.yMin), new Vector2(area.xMax, area.yMax), out point)) { if (Intersect(origin, end, new Vector2(area.xMax, area.yMin), new Vector2(area.xMax, area.yMax), out point, out rs)) {
borderHit = true; borderHit = true;
borderHitPoint = point; borderHitPoint = point;
borderRs = rs;
} }
// Any border -> invert x velocity and simulate all seconds left // Any border -> invert x velocity and simulate again from there
if (borderHit) { if (borderHit) {
secondsLeft -= (borderHitPoint - origin).magnitude / Speed; float secondsUsedHere = borderRs.x * secondsLeft;
secondsLeft -= secondsUsedHere;
secondsUsed += secondsUsedHere;
velocity = new Vector2(-velocity.x, velocity.y); velocity = new Vector2(-velocity.x, velocity.y);
origin = borderHitPoint; origin = borderHitPoint;
return Simulate(origin, velocity, radius, secondsLeft); return Simulate(origin, velocity, radius, secondsLeft, secondsUsed, out impossible);
} }
// No intersection -> Ball outside of field -> dont simulate further // No intersection -> Ball outside of field -> dont simulate further
return origin; return origin.x;
} }
private float FutureSeconds => (float) Difficulty * 0.5f;
private float GetTargetPosition() { private float GetTargetPosition() {
var balls = GameManager.Singleton.Balls.Where(b => b.IsAlive); var balls = GameManager.Singleton.Balls.Where(b => b.IsAlive);
var approaching = balls.Where(BallApproaches).ToList(); var approaching = balls.Where(BallApproaches).ToList();
if (approaching.Count == 0) while (approaching.Count > 0) {
return Difficulty >= Difficulty.Medium ? 0 : X();
// Nearest by Y-Distance // Nearest by Y-Distance
Ball ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev); Ball ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev);
@ -127,9 +149,19 @@ namespace Game {
Vector2 velocity = ball.Rb.velocity; Vector2 velocity = ball.Rb.velocity;
float radius = ball.Radius; float radius = ball.Radius;
Vector2 simulatedPosition = Simulate(origin, velocity, radius, FutureSeconds); float target = Simulate(origin, velocity, radius, FutureSeconds, 0, out bool impossible);
if (!impossible)
return target;
approaching.Remove(ball);
// This ball was impossible to catch, try next one
}
return IdlePosition;
}
return simulatedPosition.x; private void ApproachPosition(float pos) {
// Move smoothly, velocity capped by Speed
} }
private void FixedUpdate() { private void FixedUpdate() {
@ -137,7 +169,10 @@ namespace Game {
const float h = 0.5f; const float h = 0.5f;
goingLeft = target < X() - h; goingLeft = target < X() - h;
goingRight = target > X() + h; goingRight = target > X() + h;
TryMove(Time.fixedDeltaTime); if (goingLeft || goingRight)
TryLinearMove(Time.fixedDeltaTime);
else
ApproachPosition(target);
} }
} }
} }

@ -27,7 +27,7 @@ namespace Game {
} }
private void Start() { private void Start() {
Rb.velocity = new Vector2(0, 20); Rb.velocity = new Vector2(0, 25);
} }
} }
} }

@ -32,25 +32,28 @@ namespace Game {
var v8 = new Vector2(-1, 4); var v8 = new Vector2(-1, 4);
var v9 = new Vector2(-2, 1); var v9 = new Vector2(-2, 1);
var v10 = new Vector2(3, -1); var v10 = new Vector2(3, -1);
Assert.IsTrue(AIPlayer.Intersect(v8, v4, v1, v9, out _)); Vector2 p, rs;
Assert.IsTrue(AIPlayer.Intersect(v1, v2, v4, v7, out _)); Assert.IsTrue(AIPlayer.Intersect(v8, v4, v1, v9, out p, out rs));
Assert.IsTrue(AIPlayer.Intersect(v10, v6, v9, v2, out _)); Assert.IsTrue(AIPlayer.Intersect(v1, v2, v4, v7, out p, out rs));
Assert.IsTrue(AIPlayer.Intersect(v9, v5, v8, v10, out _)); Assert.IsTrue(AIPlayer.Intersect(v10, v6, v9, v2, out p, out rs));
Assert.IsFalse(AIPlayer.Intersect(v8, v4, v6, v5, out _)); Assert.IsTrue(AIPlayer.Intersect(v9, v5, v8, v10, out p, out rs));
Assert.IsFalse(AIPlayer.Intersect(v3, v5, v6, v8, out _)); Assert.IsFalse(AIPlayer.Intersect(v8, v4, v6, v5, out p, out rs));
Assert.IsFalse(AIPlayer.Intersect(v10, v4, v8, v9, out _)); Assert.IsFalse(AIPlayer.Intersect(v3, v5, v6, v8, out p, out rs));
Assert.IsFalse(AIPlayer.Intersect(v1, v7, v3, v2, out _)); Assert.IsFalse(AIPlayer.Intersect(v10, v4, v8, v9, out p, out rs));
Assert.IsFalse(AIPlayer.Intersect(v1, v7, v3, v2, out p, out rs));
var v11 = new Vector2(0, 4.8f); var v11 = new Vector2(0, 0);
var v12 = new Vector2(0, 14.8f); var v12 = new Vector2(0, 5);
var v13 = new Vector2(-9.75f, 14.75f); var v13 = new Vector2(-2, 2);
var v14 = new Vector2(9.75f, 14.75f); var v14 = new Vector2(2, 2);
Assert.IsTrue(AIPlayer.Intersect(v11, v12, v13, v14, out _)); Assert.IsTrue(AIPlayer.Intersect(v11, v12, v13, v14, out p, out rs));
Assert.AreApproximatelyEqual(rs.x, 0.4f);
Assert.AreApproximatelyEqual(rs.y, 0.5f);
} }
private void Start() { private void Start() {
Settings.Type = Type.Hybrid; Settings.Type = Type.Hybrid;
Settings.AIDifficulty = Difficulty.Easy; Settings.AIDifficulty = Difficulty.VeryHard;
var ball = Instantiate(ballPrefab).GetComponent<Ball>(); var ball = Instantiate(ballPrefab).GetComponent<Ball>();
Balls.Add(ball); Balls.Add(ball);

@ -23,13 +23,10 @@ namespace Game {
private SpeedModification speedModification; private SpeedModification speedModification;
private BorderModification borderModification; private BorderModification borderModification;
private float LeftSide() { protected float Width => transform.localScale.x;
return X() - transform.localScale.x / 2;
}
private float RightSide() { private float LeftSide => X() - Width / 2;
return X() + transform.localScale.x / 2; private float RightSide => X() + Width / 2;
}
protected float X() { protected float X() {
return transform.position.x; return transform.position.x;
@ -39,14 +36,14 @@ namespace Game {
return transform.position.y; return transform.position.y;
} }
protected void TryMove(float h) { protected void TryLinearMove(float h) {
Vector2 trans = new Vector2((goingLeft ? -1 : 0) + (goingRight ? 1 : 0), 0); Vector2 trans = new Vector2((goingLeft ? -1 : 0) + (goingRight ? 1 : 0), 0);
trans *= Speed * h; trans *= Speed * h;
transform.Translate(trans); transform.Translate(trans);
if (LeftSide() < -Border) if (LeftSide < -Border)
transform.Translate(Vector2.right * (-Border - LeftSide())); transform.Translate(Vector2.right * (-Border - LeftSide));
if (RightSide() > Border) if (RightSide > Border)
transform.Translate(Vector2.left * (RightSide() - Border)); transform.Translate(Vector2.left * (RightSide - Border));
} }
private void Start() { private void Start() {
@ -70,7 +67,7 @@ namespace Game {
goingLeft = keyboard.aKey.isPressed; goingLeft = keyboard.aKey.isPressed;
goingRight = keyboard.dKey.isPressed; goingRight = keyboard.dKey.isPressed;
TryMove(Time.fixedDeltaTime); TryLinearMove(Time.fixedDeltaTime);
} }
} }
} }

Loading…
Cancel
Save