├── README.md
├── index.html
└── pool.js
/README.md:
--------------------------------------------------------------------------------
1 | Simple Pool
2 | ============
3 |
4 | Simple billiard game implemented with HTML5 and JavaScript - here is the [demo page](http://codeabbey.github.io/simple-pool)
5 |
6 | Intended to use as a demo for several [CodeAbbey](http://www.codeabbey.com) problems.
7 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
20 |
21 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/pool.js:
--------------------------------------------------------------------------------
1 | var balls = [];
2 | var nBalls = 7;
3 | var cue = null;
4 | var dt = 0.2;
5 | var rad = 15;
6 | var cueLength = 70;
7 | var maxSpeed = 120;
8 | var minSpeed = 1;
9 | var slowDown = 5;
10 | var delta = 20;
11 | var moving = false;
12 | var winW = 500, winH = 250;
13 | var logTimings = false;
14 | var colors = {table: '#004000', ballStroke: '#005000', ballFill: '#004000', cue: '#808000'};
15 |
16 | function lineRel(x1, y1, dx, dy) {
17 | lineRel(x1, y1, x1 + dx, y1 + dy);
18 | }
19 |
20 | function line(x1, y1, x2, y2) {
21 | ctx.beginPath();
22 | ctx.moveTo(x1, y1);
23 | ctx.lineTo(x2, y2);
24 | ctx.closePath();
25 | ctx.stroke();
26 | }
27 |
28 | function circle(x, y, r) {
29 | ctx.beginPath();
30 | ctx.arc(x, y, r, 0, 2 * Math.PI, false);
31 | ctx.closePath();
32 | ctx.fill();
33 | ctx.stroke();
34 | }
35 |
36 | function hypot(x, y) {
37 | return Math.sqrt(x * x + y * y);
38 | }
39 |
40 | function posFromEvent(e) {
41 | var e = e || window.event;
42 | var cnv = getCanvas();
43 | var offsetX = e.pageX - cnv.clientLeft - cnv.offsetLeft;
44 | var offsetY = e.pageY - cnv.clientTop - cnv.offsetTop;
45 | return {x: offsetX, y: offsetY};
46 | }
47 |
48 | function draw() {
49 | ctx.fillStyle = colors.table;
50 | ctx.fillRect(0, 0, winW, winH);
51 | ctx.strokeStyle = colors.ballStroke;
52 | ctx.fillStyle = colors.ballFill;
53 | ctx.lineWidth = 1;
54 | for (var i in balls) {
55 | circle(Math.round(balls[i].x), Math.round(balls[i].y), rad);
56 | }
57 | if (cue != null) {
58 | ctx.lineWidth = 2;
59 | ctx.strokeStyle = colors.cue;
60 | line(Math.round(cue.x), Math.round(cue.y), Math.round(cue.x2), Math.round(cue.y2));
61 | }
62 | }
63 |
64 | function setupBalls() {
65 | balls = [];
66 | tryAnotherBall:
67 | while (balls.length < nBalls) {
68 | var x = Math.floor(Math.random() * (winW - 2 * rad)) + rad;
69 | var y = Math.floor(Math.random() * (winH - 2 * rad)) + rad;
70 | if (findBall(x, y, rad * 3) >= 0) {
71 | continue;
72 | }
73 | balls.push({x: x, y: y, vx: 0, vy: 0});
74 | }
75 | }
76 |
77 | function findBall(x, y, r) {
78 | for (var i in balls) {
79 | if (hypot(balls[i].x - x, balls[i].y - y) < r) {
80 | return i;
81 | }
82 | }
83 | return -1;
84 | }
85 |
86 | function onTimer() {
87 | var t0 = Date.now();
88 | recalc();
89 | var t1 = Date.now();
90 | draw();
91 | var t2 = Date.now();
92 | if (logTimings) {
93 | console.log('Recalc: ' + (t1 - t0) + ', Redraw: ' + (t2 - t1));
94 | }
95 | }
96 |
97 | function recalc() {
98 | moving = false;
99 | for (var i in balls) {
100 | moveBall(balls[i]);
101 | }
102 | for (var j = 0; j < balls.length; j++) {
103 | for (var k = j + 1; k < balls.length; k++) {
104 | checkCollision(balls[j], balls[k]);
105 | }
106 | }
107 | moving = false;
108 | for (var i in balls) {
109 | var speed = hypot(balls[i].vx, balls[i].vy);
110 | if (speed > minSpeed) {
111 | moving = true;
112 | } else {
113 | balls[i].vx = 0, balls[i].vy = 0;
114 | }
115 | }
116 | }
117 |
118 | function checkBorders(ball) {
119 | if (ball.x < rad && ball.vx < 0) {
120 | ball.x += 2 * (rad - ball.x);
121 | ball.vx *= -1;
122 | }
123 | if (ball.y < rad && ball.vy < 0) {
124 | ball.y += 2 * (rad - ball.y);
125 | ball.vy *= -1;
126 | }
127 | if (ball.x >= winW - rad && ball.vx > 0) {
128 | ball.x -= 2 * (winW - rad - ball.x);
129 | ball.vx *= -1;
130 | }
131 | if (ball.y >= winH - rad && ball.vy > 0) {
132 | ball.y -= 2 * (winH - rad - ball.y);
133 | ball.vy *= -1;
134 | }
135 | }
136 |
137 | function checkCollision(a, b) {
138 | var dx = b.x - a.x;
139 | var dy = b.y - a.y;
140 | var dist = hypot(dx, dy);
141 | if (dist >= 2 * rad) {
142 | return;
143 | }
144 | var c = {vx: (a.vx + b.vx) / 2, vy: (a.vy + b.vy) / 2};
145 | var ux = a.vx - c.vx;
146 | var uy = a.vy - c.vy;
147 | var u = hypot(ux, uy);
148 | if (u < 1e-7) {
149 | return;
150 | }
151 | ur = (ux * dx + uy * dy) / dist;
152 | if (ur <= 0) {
153 | return;
154 | }
155 | urx = ur * dx / dist;
156 | ury = ur * dy / dist;
157 | a.vx -= 2 * urx;
158 | a.vy -= 2 * ury;
159 | b.vx += 2 * urx;
160 | b.vy += 2 * ury;
161 | }
162 |
163 | function moveBall(ball) {
164 | var v = hypot(ball.vx, ball.vy);
165 | if (v == 0) {
166 | return;
167 | }
168 | var divisor = 1000 / delta;
169 | ball.x += ball.vx / divisor;
170 | ball.y += ball.vy / divisor;
171 | checkBorders(ball);
172 | ball.vx -= ball.vx / v * slowDown / divisor;
173 | ball.vy -= ball.vy / v * slowDown / divisor;
174 | }
175 |
176 | function onMouseDown(event) {
177 | if (moving) {
178 | return;
179 | }
180 | var pos = posFromEvent(event);
181 | var i = findBall(pos.x, pos.y, rad);
182 | if (i < 0) {
183 | alert('Try to press mouse button on the center\nof some ball and drag the mouse');
184 | return;
185 | }
186 | var x = balls[i].x;
187 | var y = balls[i].y;
188 | cue = {x: x, y: y, x2: x, y2: y}
189 | event.preventDefault();
190 | }
191 |
192 | function onMouseUp(event) {
193 | if (cue == null || moving) {
194 | return;
195 | }
196 | var i = findBall(cue.x, cue.y, rad / 2);
197 | var dx = cue.x2 - cue.x;
198 | var dy = cue.y2 - cue.y;
199 | cue = null;
200 | balls[i].vx = dx * maxSpeed / cueLength;
201 | balls[i].vy = dy * maxSpeed / cueLength;
202 | moving = true;
203 | }
204 |
205 | function onMouseMove(event) {
206 | if (cue == null || moving) {
207 | return;
208 | }
209 | var pos = posFromEvent(event);
210 | var dx = pos.x - cue.x;
211 | var dy = pos.y - cue.y;
212 | var len = hypot(dx, dy);
213 | if (len > cueLength) {
214 | dx = dx * cueLength / len;
215 | dy = dy * cueLength / len;
216 | }
217 | cue.x2 = cue.x + dx;
218 | cue.y2 = cue.y + dy;
219 | event.preventDefault();
220 | }
221 |
222 | function setupGeometry(canvas) {
223 | winW = canvas.width;
224 | winH = canvas.height;
225 | }
226 |
227 | function getCanvas() {
228 | return document.getElementById('demo');
229 | }
230 |
231 | function poolInit() {
232 | var canvas = getCanvas();
233 | setupGeometry(canvas);
234 | if (typeof(overrideSettings) != 'undefined') {
235 | overrideSettings();
236 | }
237 | window.ctx = canvas.getContext('2d');
238 | setupBalls();
239 | draw();
240 | canvas.onmousedown = onMouseDown;
241 | canvas.onmouseup = onMouseUp;
242 | canvas.onmousemove = onMouseMove;
243 | setInterval(onTimer, delta);
244 | }
245 |
246 | function buttonReset() {
247 | setupBalls();
248 | }
249 |
--------------------------------------------------------------------------------