package game import ( "math" "testing" "github.com/soer/football/internal/entities" ) const epsilon = 1e-9 func TestWorld_Friction(t *testing.T) { w := NewWorld() w.Puck.Velocity = entities.Vector2{X: 10, Y: 0} w.Update() expectedX := 10.0 * Friction if math.Abs(w.Puck.Velocity.X-expectedX) > epsilon { t.Errorf("Expected velocity X to be %f, got %f", expectedX, w.Puck.Velocity.X) } } func TestWorld_StopThreshold(t *testing.T) { w := NewWorld() // Set velocity just below StopThreshold w.Puck.Velocity = entities.Vector2{X: StopThreshold * 0.5, Y: 0} w.Update() if math.Abs(w.Puck.Velocity.X) > epsilon || math.Abs(w.Puck.Velocity.Y) > epsilon { t.Errorf("Expected velocity to be 0, got {%f, %f}", w.Puck.Velocity.X, w.Puck.Velocity.Y) } } func TestWorld_Restitution(t *testing.T) { w := NewWorld() w.Puck.Position = entities.Vector2{X: 8, Y: 360} w.Puck.Velocity = entities.Vector2{X: -2, Y: 0} w.Update() expectedX := (-2.0 * Friction) * -Restitution if math.Abs(w.Puck.Velocity.X-expectedX) > epsilon { t.Errorf("Expected velocity X to be %f, got %f", expectedX, w.Puck.Velocity.X) } } func TestWorld_ZoningAI(t *testing.T) { w := NewWorld() // TeamRed Defender redDef := &entities.Player{ Position: entities.Vector2{X: 200, Y: 360}, HomePosition: entities.Vector2{X: 200, Y: 360}, Team: entities.TeamRed, Role: entities.RoleDefender, } // TeamRed Striker redStr := &entities.Player{ Position: entities.Vector2{X: 400, Y: 360}, HomePosition: entities.Vector2{X: 400, Y: 360}, Team: entities.TeamRed, Role: entities.RoleStriker, } // Case 1: Puck in Defensive Zone (Zone 0) puckPos0 := entities.Vector2{X: 100, Y: 360} // Defender should intercept targetDef0 := w.calculateTarget(redDef, puckPos0) expectedDef0 := entities.Vector2{X: (100.0 + GoalLeft.X) / 2, Y: 360} if targetDef0 != expectedDef0 { t.Errorf("Zone 0: Expected Red Defender to target %v, got %v", expectedDef0, targetDef0) } // Striker should return to center targetStr0 := w.calculateTarget(redStr, puckPos0) expectedStr0 := entities.Vector2{X: WorldCenterX, Y: WorldCenterY} if targetStr0 != expectedStr0 { t.Errorf("Zone 0: Expected Red Striker to target %v, got %v", expectedStr0, targetStr0) } // Case 2: Puck in Middle Zone (Zone 1) puckPos1 := entities.Vector2{X: 600, Y: 360} // Defender should stay slightly behind targetDef1 := w.calculateTarget(redDef, puckPos1) expectedDef1 := entities.Vector2{X: 600 + (GoalLeft.X-600)*0.2, Y: 360} if targetDef1 != expectedDef1 { t.Errorf("Zone 1: Expected Red Defender to target %v, got %v", expectedDef1, targetDef1) } // Striker should pursue targetStr1 := w.calculateTarget(redStr, puckPos1) if targetStr1 != puckPos1 { t.Errorf("Zone 1: Expected Red Striker to target puck %v, got %v", puckPos1, targetStr1) } // Case 3: Puck in Offensive Zone (Zone 2) puckPos2 := entities.Vector2{X: 1000, Y: 360} // Defender should return home targetDef2 := w.calculateTarget(redDef, puckPos2) if targetDef2 != redDef.HomePosition { t.Errorf("Zone 2: Expected Red Defender to target HomePosition %v, got %v", redDef.HomePosition, targetDef2) } // Striker should aggressively pursue targetStr2 := w.calculateTarget(redStr, puckPos2) if targetStr2 != puckPos2 { t.Errorf("Zone 2: Expected Red Striker to target puck %v, got %v", puckPos2, targetStr2) } } func TestWorld_GoalieAI(t *testing.T) { w := NewWorld() // Create a Red Goalie redGoalie := &entities.Player{ Position: entities.Vector2{X: GoalLeft.X, Y: GoalLeft.Y}, HomePosition: entities.Vector2{X: GoalLeft.X, Y: GoalLeft.Y}, Team: entities.TeamRed, Role: entities.RoleGoalie, } // Case 1: Puck is far away. Target should be at GoalieMaxDistance from goal. w.Puck.Position = entities.Vector2{X: 500, Y: 360} target := w.calculateTarget(redGoalie, w.Puck.Position) dx := target.X - GoalLeft.X dy := target.Y - GoalLeft.Y dist := math.Sqrt(dx*dx + dy*dy) if math.Abs(dist-GoalieMaxDistance) > epsilon { t.Errorf("Expected Red Goalie to target distance %f, got %f", GoalieMaxDistance, dist) } // Case 2: Puck is close. Target should be exactly at puck position. w.Puck.Position = entities.Vector2{X: GoalLeft.X + 10, Y: GoalLeft.Y + 10} target = w.calculateTarget(redGoalie, w.Puck.Position) if target != w.Puck.Position { t.Errorf("Expected Red Goalie to target puck position when close, got %v", target) } // Case 3: Puck is behind the goal. Target should be goal center. w.Puck.Position = entities.Vector2{X: GoalLeft.X - 10, Y: GoalLeft.Y} target = w.calculateTarget(redGoalie, w.Puck.Position) if target != GoalLeft { t.Errorf("Expected Red Goalie to target goal center when puck is behind, got %v", target) } // Case 4: Puck is within GoalieDeadzone. Target should be goal center. w.Puck.Position = entities.Vector2{X: GoalLeft.X + GoalieDeadzone*0.5, Y: GoalLeft.Y} target = w.calculateTarget(redGoalie, w.Puck.Position) if target != GoalLeft { t.Errorf("Expected Red Goalie to target goal center when puck is within deadzone, got %v", target) } // Case 5: Blue Goalie blueGoalie := &entities.Player{ Position: entities.Vector2{X: GoalRight.X, Y: GoalRight.Y}, HomePosition: entities.Vector2{X: GoalRight.X, Y: GoalRight.Y}, Team: entities.TeamBlue, Role: entities.RoleGoalie, } w.Puck.Position = entities.Vector2{X: 700, Y: 360} target = w.calculateTarget(blueGoalie, w.Puck.Position) dx = target.X - GoalRight.X dy = target.Y - GoalRight.Y dist = math.Sqrt(dx*dx + dy*dy) if math.Abs(dist-GoalieMaxDistance) > epsilon { t.Errorf("Expected Blue Goalie to target distance %f, got %f", GoalieMaxDistance, dist) } } func TestWorld_CheckGoal(t *testing.T) { w := NewWorld() // Case 1: Puck goes off left side (Blue scores) w.Puck.Position = entities.Vector2{X: -1, Y: 360} team, scored := w.CheckGoal() if !scored || team != entities.TeamBlue { t.Errorf("Expected TeamBlue to score, got %v, %v", team, scored) } // Case 2: Puck goes off right side (Red scores) w.Puck.Position = entities.Vector2{X: WorldWidth + 1, Y: 360} team, scored = w.CheckGoal() if !scored || team != entities.TeamRed { t.Errorf("Expected TeamRed to score, got %v, %v", team, scored) } // Case 3: Puck is in bounds w.Puck.Position = entities.Vector2{X: WorldCenterX, Y: WorldCenterY} team, scored = w.CheckGoal() if scored { t.Errorf("Expected no goal, but scored: %v", team) } } func TestWorld_ResetPositions(t *testing.T) { w := NewWorld() // Move puck and players away from home w.Puck.Position = entities.Vector2{X: 100, Y: 100} w.Puck.Velocity = entities.Vector2{X: 5, Y: 5} for _, p := range w.Players { p.Position = entities.Vector2{X: 500, Y: 500} p.Velocity = entities.Vector2{X: 2, Y: 2} } w.ResetPositions() // Verify puck if w.Puck.Position != (entities.Vector2{X: WorldCenterX, Y: WorldCenterY}) { t.Errorf("Expected puck at center, got %v", w.Puck.Position) } if w.Puck.Velocity != (entities.Vector2{X: 0, Y: 0}) { t.Errorf("Expected puck velocity 0, got %v", w.Puck.Velocity) } // Verify players for _, p := range w.Players { if p.Position != p.HomePosition { t.Errorf("Expected player at home position %v, got %v", p.HomePosition, p.Position) } if p.Velocity != (entities.Vector2{X: 0, Y: 0}) { t.Errorf("Expected player velocity 0, got %v", p.Velocity) } } }