-
Notifications
You must be signed in to change notification settings - Fork 0
/
BouncyBallGame.ts
131 lines (115 loc) · 3.68 KB
/
BouncyBallGame.ts
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class Vector {
constructor(
public X: number,
public Y: number
) {}
}
class Ball {
constructor(
public position: Vector,
public velocity: Vector,
public radius: number,
public color:string
) {}
}
class ColorPicker {
constructor(colors: string[]) {
this.colors = colors;
}
colors: string[];
next(): string {
const color: string = this.colors.shift();
this.colors.push(color);
return color;
}
}
const balls: Ball[] = [];
const colorPicker = new ColorPicker([
"#095903",
"#ED5407",
"#032459",
"#590303"
]);
const frameDurationMs: number = 15; // temporal duration of one frame in milliseconds
const bouncyBallsCanvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("bouncy-ball");
const canvasContext: CanvasRenderingContext2D = bouncyBallsCanvas.getContext("2d");
bouncyBallsCanvas.onclick = (ev: MouseEvent) => {
const newBall = new Ball(
eventCoordsOnElement(ev, bouncyBallsCanvas),
newBallVelocity(),
5 + (Math.random() * 15), // 5 <= r <= 20
colorPicker.next(),
);
balls.push(newBall);
};
function eventCoordsOnElement(ev: MouseEvent, elem: HTMLElement): Vector {
let totalOffsetX: number = 0;
let totalOffsetY: number = 0;
let currentElement: HTMLElement = elem;
do {
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while(currentElement = <HTMLElement>currentElement.offsetParent)
const canvasX: number = ev.pageX - totalOffsetX;
const canvasY: number = ev.pageY - totalOffsetY;
return new Vector(canvasX, canvasY);
}
function newBallVelocity(): Vector {
// Pick a speed so the ball will cross the canvas in ~4 seconds
const initialSpeed: number = (bouncyBallsCanvas.width + bouncyBallsCanvas.height) / 8;
const splay: number = 0.5 + Math.random(); // 0.5 <= s <= 1.5
const speed = initialSpeed * splay;
const direction: number = Math.random() * 2 * Math.PI;
return new Vector(
speed * Math.cos(direction),
speed * Math.sin(direction),
);
}
function animateBall(ball: Ball) {
var deltaX = ball.velocity.X * (frameDurationMs / 1000);
var deltaY = ball.velocity.Y * (frameDurationMs / 1000);
// x movement and bounce
if ((ball.position.X + deltaX - ball.radius) < 0) {
ball.position.X = ball.radius;
ball.velocity.X *= -1;
}
else if ((ball.position.X + deltaX + ball.radius) < bouncyBallsCanvas.width) {
ball.position.X += deltaX;
} else {
ball.position.X = bouncyBallsCanvas.width - ball.radius;
ball.velocity.X *= -1;
}
// y movement and bounce
if ((ball.position.Y + deltaY - ball.radius) < 0) {
ball.position.Y = ball.radius;
ball.velocity.Y *= -1;
}
else if ((ball.position.Y + deltaY + ball.radius) < bouncyBallsCanvas.height) {
ball.position.Y += deltaY;
} else {
ball.position.Y = bouncyBallsCanvas.height - ball.radius;
ball.velocity.Y *= -1;
}
}
function drawBall(ball: Ball) {
canvasContext.beginPath();
canvasContext.arc(
ball.position.X,
ball.position.Y,
ball.radius,
0, 2 * Math.PI);
canvasContext.fillStyle=ball.color;
canvasContext.fill();
canvasContext.closePath();
}
function redrawBalls() {
canvasContext.clearRect(
0,
0,
bouncyBallsCanvas.width,
bouncyBallsCanvas.height);
balls.forEach(animateBall);
balls.forEach(drawBall);
}
const animationHandle: number = setInterval(redrawBalls, frameDurationMs);