├── 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 | Simple Pool Billiard Demo in JavaScript 6 | 7 | 8 | 9 |
10 |

Simple pool in JavaScript

11 |
for use as demo at CodeAbbey problem statements
12 |

13 | 14 |
15 |
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 | --------------------------------------------------------------------------------