using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Media; using System.Windows.Forms; using WinTimer = System.Windows.Forms.Timer; namespace BallCollider { public partial class Form1 : Form { // Ball properties private PointF ballPos; private PointF ballVel; private const float BallRadius = 12f; private const float MinSpeed = 6f; private const float MaxSpeed = 20f; // Shape properties private List> shapes; private List shapeColors; private const int MinWalls = 5; private const int MaxWalls = 220; private const float BaseShapeRadius = 220f; private PointF shapeCenter; // Game mode private bool multiShapeMode = false; private int currentShapeIndex = 0; // Rendering optimization private BufferedGraphicsContext context; private BufferedGraphics buffer; private readonly SolidBrush ballBrush; private readonly List shapePens; // Game loop private readonly WinTimer gameTimer; private readonly Random rnd; // Collision cooldown to prevent multiple hits private int collisionCooldown = 0; // Tracer system private List tracers; private const int MaxTracers = 80; private int tracerCooldown = 0; private const int TracerInterval = 2; // Predictive collision detection private PointF predictedPos; private bool showPrediction = false; public Form1() { InitializeComponent(); // Optimize form for rendering this.DoubleBuffered = true; this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.Opaque, true); this.ClientSize = new Size(800, 600); this.Text = "balls"; this.BackColor = Color.Black; rnd = new Random(); ballBrush = new SolidBrush(Color.White); shapePens = new List(); tracers = new List(); // Initialize buffered graphics context = BufferedGraphicsManager.Current; context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1); buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle); shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f); InitializeGame(); // High-frequency timer for smooth gameplay gameTimer = new WinTimer(); gameTimer.Interval = 20; // ~60 FPS gameTimer.Tick += GameLoop; gameTimer.Start(); this.Resize += (s, e) => RecreateBuffer(); this.KeyDown += Form1_KeyDown; this.KeyPreview = true; } private void Form1_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.M) { multiShapeMode = !multiShapeMode; if (multiShapeMode) { InitializeMultiShapeMode(); } else { InitializeSingleShapeMode(); } } else if (e.KeyCode == Keys.P) { showPrediction = !showPrediction; } } private void RecreateBuffer() { if (this.Width > 0 && this.Height > 0) { buffer?.Dispose(); context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1); buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle); shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f); } } private void InitializeGame() { shapes = new List>(); shapeColors = new List(); shapePens.Clear(); tracers.Clear(); CreateShape(MinWalls, 0); // Spawn ball at center ballPos = new PointF(shapeCenter.X, shapeCenter.Y); // Random initial velocity float angle = (float)(rnd.NextDouble() * Math.PI * 2); float speed = MinSpeed * 1.5f; ballVel = new PointF( (float)Math.Cos(angle) * speed, (float)Math.Sin(angle) * speed ); } private void InitializeSingleShapeMode() { shapes.Clear(); shapeColors.Clear(); shapePens.Clear(); tracers.Clear(); CreateShape(MinWalls, 0); currentShapeIndex = 0; } private void InitializeMultiShapeMode() { shapes.Clear(); shapeColors.Clear(); shapePens.Clear(); tracers.Clear(); // Create 3 concentric shapes for (int i = 0; i < 3; i++) { CreateShape(MinWalls + i * 2, i); } currentShapeIndex = 0; } private void CreateShape(int sides, int shapeIndex) { while (shapes.Count <= shapeIndex) { shapes.Add(new List()); shapeColors.Add(Color.White); shapePens.Add(new Pen(Color.White, 4f)); } List shapePoints = new List(); float angleStep = (float)(2 * Math.PI / sides); float startAngle = (float)(rnd.NextDouble() * Math.PI * 2); float radius = multiShapeMode ? BaseShapeRadius * (1.0f - shapeIndex * 0.2f) : BaseShapeRadius; for (int i = 0; i < sides; i++) { float angle = startAngle + i * angleStep; shapePoints.Add(new PointF( shapeCenter.X + (float)Math.Cos(angle) * radius, shapeCenter.Y + (float)Math.Sin(angle) * radius )); } shapes[shapeIndex] = shapePoints; Color newColor = Color.FromArgb( rnd.Next(100, 256), rnd.Next(100, 256), rnd.Next(100, 256) ); shapeColors[shapeIndex] = newColor; shapePens[shapeIndex].Color = newColor; } private void GameLoop(object sender, EventArgs e) { UpdateBall(); UpdateTracers(); Render(); } private void UpdateBall() { if (collisionCooldown > 0) collisionCooldown--; // Store previous position for tracer PointF previousPos = ballPos; predictedPos = new PointF(ballPos.X + ballVel.X, ballPos.Y + ballVel.Y); if (collisionCooldown == 0) { if (CheckPredictiveCollision()) { // Predictive collision detected - handle it immediately HandlePredictiveCollision(); } else { // No collision predicted - apply normal movement ballPos = predictedPos; } } else { // Apply normal movement during cooldown ballPos = predictedPos; } // Gradually increase speed for excitement (capped) float speed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y); if (speed < MaxSpeed) { float factor = 1.005f; ballVel.X *= factor; ballVel.Y *= factor; } // Add tracer if (tracerCooldown <= 0) { AddTracer(previousPos); tracerCooldown = TracerInterval; } else { tracerCooldown--; } // Final boundary enforcement (safety net) EnforceBoundaries(); } private bool CheckPredictiveCollision() { if (shapes.Count == 0) return false; List currentShape = shapes[currentShapeIndex]; for (int i = 0; i < currentShape.Count; i++) { PointF p1 = currentShape[i]; PointF p2 = currentShape[(i + 1) % currentShape.Count]; PointF closest = ClosestPointOnSegment(p1, p2, predictedPos); float dx = predictedPos.X - closest.X; float dy = predictedPos.Y - closest.Y; float dist = (float)Math.Sqrt(dx * dx + dy * dy); if (dist < BallRadius) { return true; } } return false; } private void HandlePredictiveCollision() { if (shapes.Count == 0) return; List currentShape = shapes[currentShapeIndex]; PointF collisionPoint = PointF.Empty; PointF wallNormal = PointF.Empty; int wallIndex = -1; float minDist = float.MaxValue; // Find the closest wall that will be collided with for (int i = 0; i < currentShape.Count; i++) { PointF p1 = currentShape[i]; PointF p2 = currentShape[(i + 1) % currentShape.Count]; PointF closest = ClosestPointOnSegment(p1, p2, predictedPos); float dx = predictedPos.X - closest.X; float dy = predictedPos.Y - closest.Y; float dist = (float)Math.Sqrt(dx * dx + dy * dy); if (dist < BallRadius && dist < minDist) { minDist = dist; collisionPoint = closest; wallIndex = i; // Calculate wall normal float edgeX = p2.X - p1.X; float edgeY = p2.Y - p1.Y; float edgeLen = (float)Math.Sqrt(edgeX * edgeX + edgeY * edgeY); if (edgeLen > 0) { wallNormal.X = -edgeY / edgeLen; wallNormal.Y = edgeX / edgeLen; // Ensure normal points inward float midX = (p1.X + p2.X) / 2f; float midY = (p1.Y + p2.Y) / 2f; float toCenterX = shapeCenter.X - midX; float toCenterY = shapeCenter.Y - midY; if (wallNormal.X * toCenterX + wallNormal.Y * toCenterY < 0) { wallNormal.X = -wallNormal.X; wallNormal.Y = -wallNormal.Y; } } } } if (wallIndex >= 0 && wallNormal != PointF.Empty) { // Calculate exact collision position (ball surface touching wall) float penetration = BallRadius - minDist; PointF correctedPos = new PointF( predictedPos.X + wallNormal.X * penetration, predictedPos.Y + wallNormal.Y * penetration ); // Set ball to corrected position ballPos = correctedPos; // Reflect velocity float dotProduct = ballVel.X * wallNormal.X + ballVel.Y * wallNormal.Y; if (dotProduct < 0) { ballVel.X -= 2 * dotProduct * wallNormal.X; ballVel.Y -= 2 * dotProduct * wallNormal.Y; // Apply boost and clamp speed float boost = 1.05f; ballVel.X *= boost; ballVel.Y *= boost; float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y); if (currentSpeed > MaxSpeed) { ballVel.X = (ballVel.X / currentSpeed) * MaxSpeed; ballVel.Y = (ballVel.Y / currentSpeed) * MaxSpeed; } PlayHitSound(); // Handle shape evolution PointF p1 = currentShape[wallIndex]; PointF p2 = currentShape[(wallIndex + 1) % currentShape.Count]; if (currentShape.Count < MaxWalls) { // Add vertex to make shape more circular PointF newPoint = new PointF( (p1.X + p2.X) / 2f, (p1.Y + p2.Y) / 2f ); float radius = multiShapeMode ? BaseShapeRadius * (1.0f - currentShapeIndex * 0.2f) : BaseShapeRadius; // Project to maintain circular shape float angle = (float)Math.Atan2(newPoint.Y - shapeCenter.Y, newPoint.X - shapeCenter.X); newPoint = new PointF( shapeCenter.X + (float)Math.Cos(angle) * radius, shapeCenter.Y + (float)Math.Sin(angle) * radius ); currentShape.Insert(wallIndex + 1, newPoint); } else { if (multiShapeMode && currentShapeIndex < shapes.Count - 1) { currentShapeIndex++; PlayShapeChangeSound(); } else { int sides = multiShapeMode ? MinWalls + currentShapeIndex * 2 : MinWalls; CreateShape(sides, currentShapeIndex); PlayShapeChangeSound(); } } collisionCooldown = 8; } } } private void EnforceBoundaries() { bool isInsideAnyShape = false; foreach (var shape in shapes) { if (IsPointInPolygon(ballPos, shape)) { isInsideAnyShape = true; break; } } if (!isInsideAnyShape && shapes.Count > 0) { PointF closestBoundary = FindClosestBoundaryPoint(ballPos, shapes[0]); float pushX = closestBoundary.X - ballPos.X; float pushY = closestBoundary.Y - ballPos.Y; float pushDist = (float)Math.Sqrt(pushX * pushX + pushY * pushY); if (pushDist > 0) { float margin = BallRadius + 2f; ballPos.X = closestBoundary.X - (pushX / pushDist) * margin; ballPos.Y = closestBoundary.Y - (pushY / pushDist) * margin; // Emergency bounce float randomAngle = (float)(rnd.NextDouble() * Math.PI * 2); float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y); ballVel.X = (float)Math.Cos(randomAngle) * currentSpeed; ballVel.Y = (float)Math.Sin(randomAngle) * currentSpeed; } } } private void UpdateTracers() { // Update and remove old tracers for (int i = tracers.Count - 1; i >= 0; i--) { tracers[i].Update(); if (tracers[i].IsDead) { tracers.RemoveAt(i); } } } private void AddTracer(PointF position) { if (tracers.Count >= MaxTracers) { tracers.RemoveAt(0); } Color tracerColor = shapes.Count > 0 ? Color.FromArgb(150, shapeColors[currentShapeIndex]) : Color.FromArgb(150, Color.White); tracers.Add(new Tracer(position, tracerColor, BallRadius * 0.7f)); } private PointF FindClosestBoundaryPoint(PointF point, List shape) { PointF closest = shape[0]; float closestDist = float.MaxValue; for (int i = 0; i < shape.Count; i++) { PointF p1 = shape[i]; PointF p2 = shape[(i + 1) % shape.Count]; PointF segmentClosest = ClosestPointOnSegment(p1, p2, point); float dist = Distance(point, segmentClosest); if (dist < closestDist) { closestDist = dist; closest = segmentClosest; } } return closest; } private float Distance(PointF a, PointF b) { float dx = a.X - b.X; float dy = a.Y - b.Y; return (float)Math.Sqrt(dx * dx + dy * dy); } private bool IsPointInPolygon(PointF point, List polygon) { bool inside = false; int j = polygon.Count - 1; for (int i = 0; i < polygon.Count; i++) { if ((polygon[i].Y > point.Y) != (polygon[j].Y > point.Y) && point.X < (polygon[j].X - polygon[i].X) * (point.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) + polygon[i].X) { inside = !inside; } j = i; } return inside; } private PointF ClosestPointOnSegment(PointF a, PointF b, PointF p) { float dx = b.X - a.X; float dy = b.Y - a.Y; if (dx == 0 && dy == 0) return a; float len2 = dx * dx + dy * dy; float t = ((p.X - a.X) * dx + (p.Y - a.Y) * dy) / len2; t = Math.Max(0, Math.Min(1, t)); return new PointF(a.X + t * dx, a.Y + t * dy); } private void PlayHitSound() { try { int freq = rnd.Next(400, 1000); System.Threading.Tasks.Task.Run(() => Console.Beep(freq, 30)); } catch { } } private void PlayShapeChangeSound() { try { System.Threading.Tasks.Task.Run(() => { Console.Beep(300, 80); System.Threading.Thread.Sleep(50); Console.Beep(500, 80); }); } catch { } } private void Render() { Graphics g = buffer.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.Black); // Draw tracers first (behind everything) foreach (var tracer in tracers) { tracer.Draw(g); } // Draw all shapes for (int shapeIndex = 0; shapeIndex < shapes.Count; shapeIndex++) { var shape = shapes[shapeIndex]; if (shape.Count >= 2) { Color drawColor = shapeColors[shapeIndex]; if (multiShapeMode && shapeIndex == currentShapeIndex) { drawColor = Color.FromArgb(255, Math.Min(255, drawColor.R + 50), Math.Min(255, drawColor.G + 50), Math.Min(255, drawColor.B + 50)); } using (Pen pen = new Pen(drawColor, shapeIndex == currentShapeIndex ? 5f : 3f)) { for (int i = 0; i < shape.Count; i++) { PointF p1 = shape[i]; PointF p2 = shape[(i + 1) % shape.Count]; g.DrawLine(pen, p1, p2); } } } } // Draw prediction line if enabled if (showPrediction) { using (Pen predictionPen = new Pen(Color.FromArgb(100, Color.Yellow), 1f)) { g.DrawLine(predictionPen, ballPos, predictedPos); } // Draw predicted position using (Brush predictionBrush = new SolidBrush(Color.FromArgb(100, Color.Red))) { g.FillEllipse(predictionBrush, predictedPos.X - BallRadius / 2, predictedPos.Y - BallRadius / 2, BallRadius, BallRadius); } } // Draw ball with glow effect using (GraphicsPath path = new GraphicsPath()) { path.AddEllipse( ballPos.X - BallRadius, ballPos.Y - BallRadius, BallRadius * 2, BallRadius * 2 ); using (PathGradientBrush brush = new PathGradientBrush(path)) { Color activeColor = shapes.Count > 0 ? shapeColors[currentShapeIndex] : Color.White; brush.CenterColor = Color.White; brush.SurroundColors = new[] { Color.FromArgb(100, activeColor) }; g.FillPath(brush, path); } } // Draw UI info using (Font font = new Font("Arial", 14, FontStyle.Bold)) { string sidesText = $"Sides: {shapes[currentShapeIndex].Count}"; string predictionText = $"Prediction: {(showPrediction ? "ON" : "OFF")} (P)"; g.DrawString(sidesText, font, Brushes.White, 10, 10); g.DrawString(predictionText, font, Brushes.LightGreen, 10, 35); } buffer.Render(); } protected override void OnPaint(PaintEventArgs e) { if (buffer != null) { buffer.Render(e.Graphics); } } protected override void Dispose(bool disposing) { if (disposing) { gameTimer?.Stop(); gameTimer?.Dispose(); buffer?.Dispose(); context?.Dispose(); ballBrush?.Dispose(); foreach (var pen in shapePens) pen?.Dispose(); components?.Dispose(); } base.Dispose(disposing); } } // Tracer class for ball trail effect public class Tracer { public PointF Position { get; private set; } public Color Color { get; private set; } public float Radius { get; private set; } public float Life { get; private set; } public bool IsDead => Life <= 0; private readonly float initialRadius; private readonly Color initialColor; public Tracer(PointF position, Color color, float radius) { Position = position; Color = color; Radius = radius; initialRadius = radius; initialColor = color; Life = 1.0f; // Start with full life } public void Update() { Life -= 0.03f; // Fade speed if (Life < 0) Life = 0; // Shrink and fade Radius = initialRadius * Life; // Fade color Color = Color.FromArgb( (int)(initialColor.A * Life), initialColor.R, initialColor.G, initialColor.B ); } public void Draw(Graphics g) { if (IsDead) return; using (Brush brush = new SolidBrush(Color)) { g.FillEllipse(brush, Position.X - Radius, Position.Y - Radius, Radius * 2, Radius * 2); } } } }