| 3 | import { pad, unpad } from "../padding"; |
| 4 | |
| 5 | export class Ball { |
| 6 | private x: number; |
| 7 | private y: number; |
| 8 | private radius: number; |
| 9 | private color: string; |
| 10 | private vx: number; |
| 11 | private vy: number; |
| 12 | private ctx: CanvasRenderingContext2D; |
| 13 | private obstacles: Obstacle[] |
| 14 | private sinks: Sink[] |
| 15 | private onFinish: (index: number) => void; |
| 16 | |
| 17 | constructor(x: number, y: number, radius: number, color: string, ctx: CanvasRenderingContext2D, obstacles: Obstacle[], sinks: Sink[], onFinish: (index: number) => void) { |
| 18 | this.x = x; |
| 19 | this.y = y; |
| 20 | this.radius = radius; |
| 21 | this.color = color; |
| 22 | this.vx = 0; |
| 23 | this.vy = 0; |
| 24 | this.ctx = ctx; |
| 25 | this.obstacles = obstacles; |
| 26 | this.sinks = sinks; |
| 27 | this.onFinish = onFinish; |
| 28 | } |
| 29 | |
| 30 | draw() { |
| 31 | this.ctx.beginPath(); |
| 32 | this.ctx.arc(unpad(this.x), unpad(this.y), this.radius, 0, Math.PI * 2); |
| 33 | this.ctx.fillStyle = this.color; |
| 34 | this.ctx.fill(); |
| 35 | this.ctx.closePath(); |
| 36 | } |
| 37 | |
| 38 | update() { |
| 39 | this.vy += gravity; |
| 40 | this.x += this.vx; |
| 41 | this.y += this.vy; |
| 42 | |
| 43 | // Collision with obstacles |
| 44 | this.obstacles.forEach(obstacle => { |
| 45 | const dist = Math.hypot(this.x - obstacle.x, this.y - obstacle.y); |
| 46 | if (dist < pad(this.radius + obstacle.radius)) { |
| 47 | // Calculate collision angle |
| 48 | const angle = Math.atan2(this.y - obstacle.y, this.x - obstacle.x); |
| 49 | // Reflect velocity |
| 50 | const speed = Math.sqrt(this.vx * this.vx + this.vy * this.vy); |
| 51 | this.vx = (Math.cos(angle) * speed * horizontalFriction); |
| 52 | this.vy = Math.sin(angle) * speed * verticalFriction; |
| 53 | |
| 54 | // Adjust position to prevent sticking |
| 55 | const overlap = this.radius + obstacle.radius - unpad(dist); |
| 56 | this.x += pad(Math.cos(angle) * overlap); |
| 57 | this.y += pad(Math.sin(angle) * overlap); |
| 58 | } |
| 59 | }); |
| 60 | |
| 61 | // Collision with sinks |
| 62 | for (let i = 0; i < this.sinks.length; i++) { |
nothing calls this directly
no outgoing calls
no test coverage detected