├── images ├── red.png ├── blue.png ├── green.png ├── orange.png └── yellow.png ├── readme.md ├── index.html ├── bookmarklet └── index.html └── browser-breakout.js /images/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/browser-breakout/master/images/red.png -------------------------------------------------------------------------------- /images/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/browser-breakout/master/images/blue.png -------------------------------------------------------------------------------- /images/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/browser-breakout/master/images/green.png -------------------------------------------------------------------------------- /images/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/browser-breakout/master/images/orange.png -------------------------------------------------------------------------------- /images/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/browser-breakout/master/images/yellow.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Browser Breakout 2 | 3 | ## Post 4 | 5 | - [https://f90.co.uk/labs/browser-breakout/](https://f90.co.uk/labs/browser-breakout/) 6 | 7 | ## Example 8 | 9 | - [https://orangespaceman.github.io/browser-breakout](https://orangespaceman.github.io/browser-breakout) 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Browser Breakout 6 | 42 | 43 | 44 |
45 |

Browser Breakout!

46 |

Play Browser Breakout!

47 |
48 | 49 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /bookmarklet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bookmarklet 5 | 29 | 30 | 31 | Browser Breakout 32 | 33 | 34 | -------------------------------------------------------------------------------- /browser-breakout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Browser Breakout! 3 | * 4 | * Thanks to http://billmill.org/static/canvastutorial/ 5 | * 6 | */ 7 | var browserBreakout = function() { 8 | 9 | /* 10 | * The HTML body element 11 | */ 12 | var body = null, 13 | 14 | /* 15 | * The canvas HTMl element 16 | */ 17 | canvas = null, 18 | 19 | /* 20 | * The canvas draw context 21 | */ 22 | drawContext = null, 23 | 24 | /* 25 | * The draw interval 26 | */ 27 | drawInterval = null, 28 | 29 | /* 30 | * Reseting on browser resize 31 | */ 32 | reseting = false, 33 | 34 | /* 35 | * Current Game State. Can be: 36 | * 'title' => Game initialised for the first time 37 | * 'running' => Game is running! 38 | * 'gameEnded' => Game has ended unsuccessfully 39 | * 'victory' => Game has ended successfully 40 | * 'noImages' => No appropriate images were found 41 | */ 42 | gameState = 'title', 43 | 44 | /* 45 | * text blocks 46 | */ 47 | textBlocks = {}, 48 | textAnimationTypes = ['default', 'vertical', 'horizontal', 'random', 'reverse'], 49 | 50 | /* 51 | * Acceptable images to play with 52 | */ 53 | images = [], 54 | imagesAcceptable = 0, 55 | imagesRemaining = 0, 56 | 57 | /* 58 | * Visible position (within page) 59 | */ 60 | visibleXStart = 0, 61 | visibleXEnd = 0, 62 | visibleYStart = 0, 63 | visibleYEnd = 0, 64 | visibleWidth = 0, 65 | visibleHeight = 0, 66 | 67 | /* 68 | * Ball positions 69 | */ 70 | ballRadius = 7, 71 | ballColour = '#FFFFFF', 72 | ballXStart = ballX = 35, 73 | ballYStart = ballY = 35, 74 | ballDxStart = ballDx = 2, 75 | ballDyStart = ballDy = 2, 76 | ballVelocity = 3, 77 | 78 | /* 79 | * Paddle positions 80 | */ 81 | paddleColour = '#FFFFFF', 82 | paddleX = 0, 83 | paddleY = 0, 84 | paddleHeight = 10, 85 | paddleWidth = 100, 86 | 87 | /* 88 | * scores 89 | */ 90 | lastScore = 0, 91 | topScore = 0, 92 | 93 | /* 94 | * Current canvas transparency 95 | */ 96 | canvasTransparency = 0, 97 | targetTransparency = 0.7, 98 | 99 | /* 100 | * keys 101 | */ 102 | downDown = false, 103 | upDown = false, 104 | leftDown = false, 105 | rightDown = false, 106 | 107 | /* 108 | * Display debug messages? 109 | */ 110 | debugMode = true, 111 | 112 | /* 113 | * Debug timeout 114 | */ 115 | debugTimeout = null, 116 | 117 | 118 | /* 119 | * Fix browser CSS to disable scrolling 120 | */ 121 | hideOverflow = function() { 122 | 123 | debug('hideOverflow()'); 124 | 125 | body = document.getElementsByTagName('body')[0]; 126 | // body.style.height = '100%'; 127 | // body.style.width = '100%'; 128 | body.style.overflow = 'hidden'; 129 | }, 130 | 131 | 132 | /* 133 | * Create canvas element 134 | */ 135 | createCanvas = function() { 136 | 137 | debug('createCanvas()'); 138 | 139 | // create canvas 140 | canvas = document.createElement('canvas'); 141 | canvas.id = 'canvas'; 142 | canvas.style.position = 'absolute'; 143 | canvas.style.zIndex = 10000000; // hopefully that's high enough... 144 | 145 | setCanvasPosition(); 146 | 147 | // add the canvas into the page 148 | body.appendChild(canvas); 149 | 150 | // get the draw context 151 | drawContext = canvas.getContext('2d'); 152 | }, 153 | 154 | 155 | /* 156 | * position canvas within visible portion of the screen 157 | */ 158 | setCanvasPosition = function() { 159 | canvas.width = visibleWidth; 160 | canvas.height = visibleHeight; 161 | canvas.style.left = visibleXStart + 'px'; 162 | canvas.style.top = visibleYStart + 'px'; 163 | }, 164 | 165 | 166 | /* 167 | * Calculate visible area of page 168 | */ 169 | calculatePositions = function() { 170 | 171 | visibleXStart = window.pageXOffset; 172 | visibleYStart = window.pageYOffset; 173 | 174 | visibleWidth = window.innerWidth; 175 | visibleHeight = window.innerHeight; 176 | 177 | visibleXEnd = visibleWidth + visibleXStart; 178 | visibleYEnd = visibleHeight + visibleYStart; 179 | 180 | // set the ball to start within the visible frame 181 | ballX += visibleXStart; 182 | ballY += visibleYStart; 183 | }, 184 | 185 | 186 | 187 | /* 188 | * Retrieve all images of a reasonable size 189 | */ 190 | collectImages = function() { 191 | 192 | debug('collectImages()'); 193 | 194 | // find all images on the page 195 | var allImages = document.getElementsByTagName('img'); 196 | 197 | debug('collectImages: visible frame - X:' + visibleXStart + '-' + visibleXEnd + '; Y: ' + visibleYStart + '-' + visibleYEnd + ';'); 198 | 199 | // loop through all images 200 | for (var counter = allImages.length - 1; counter >= 0; counter--){ 201 | 202 | // get image details 203 | var imagePosition = findPos(allImages[counter]); 204 | var imageXStart = imagePosition[0] - visibleXStart; 205 | var imageYStart = imagePosition[1] - visibleYStart; 206 | var imageWidth = allImages[counter].width || allImages[counter].style.width; 207 | var imageHeight = allImages[counter].height || allImages[counter].style.height; 208 | 209 | // condition : test all images are large enough to be worth using 210 | if (imageWidth > 20 && imageHeight > 20) { 211 | 212 | 213 | // condition : if image is within visible (& safe) viewable area, use it 214 | if ( 215 | imageXStart > (paddleHeight*2) && 216 | imageYStart > (paddleHeight*2) && 217 | (visibleXStart + imageXStart + imageWidth) < visibleXEnd - (paddleHeight*2) && 218 | (visibleYStart + imageYStart + imageHeight) < visibleYEnd - (paddleHeight*2) 219 | ) { 220 | 221 | images.push({ 222 | 'imageXStart' : imageXStart, 223 | 'imageYStart' : imageYStart, 224 | 'imageWidth' : imageWidth, 225 | 'imageHeight' : imageHeight, 226 | 'image' : allImages[counter], 227 | 'state' : 1, 228 | 'visible' : 0 229 | }); 230 | } 231 | } 232 | } 233 | 234 | // store values for later use 235 | imagesAcceptable = imagesRemaining = images.length; 236 | 237 | debug('collectImages: - found ' + imagesAcceptable + ' out of ' + allImages.length); 238 | }, 239 | 240 | 241 | /* 242 | * findpos - find an element's exact co-ordinates on a page 243 | * from : http://www.quirksmode.org/js/findpos.html 244 | */ 245 | findPos = function(obj) { 246 | var curleft = curtop = 0; 247 | if (obj.offsetParent) { 248 | while (obj.offsetParent) { 249 | curleft += obj.offsetLeft; 250 | curtop += obj.offsetTop; 251 | obj = obj.offsetParent; 252 | } 253 | } 254 | return [curleft,curtop]; 255 | }, 256 | 257 | 258 | /* 259 | * keyboard navigation detection 260 | */ 261 | onKeyDown = function(evt) { 262 | 263 | if (!evt) { evt = window.event; } 264 | 265 | if (evt.keyCode == 40) { 266 | downDown = true; 267 | } else if (evt.keyCode == 38) { 268 | upDown = true; 269 | } 270 | 271 | if (evt.keyCode == 39) { 272 | rightDown = true; 273 | } else if (evt.keyCode == 37) { 274 | leftDown = true; 275 | } 276 | }, 277 | 278 | 279 | /* 280 | * keyboard navigation detection 281 | */ 282 | onKeyUp = function(evt) { 283 | 284 | if (!evt) { evt = window.event; } 285 | 286 | if (evt.keyCode == 40) { 287 | downDown = false; 288 | } else if (evt.keyCode == 38) { 289 | upDown = false; 290 | } 291 | 292 | if (evt.keyCode == 39) { 293 | rightDown = false; 294 | } else if (evt.keyCode == 37) { 295 | leftDown = false; 296 | } 297 | }, 298 | 299 | 300 | /* 301 | * mouse navigation detection 302 | */ 303 | onMouseMove = function(evt) { 304 | 305 | if (!evt) { evt = window.event; } 306 | 307 | if (evt.pageX > 0 && evt.pageX < visibleXStart + visibleWidth) { 308 | paddleX = Math.max(evt.pageX - visibleXStart - (paddleWidth/2), 0); 309 | paddleX = Math.min(canvas.width - paddleWidth, paddleX); 310 | } 311 | 312 | if (evt.pageY > 0 && evt.pageY < visibleYStart + visibleHeight) { 313 | paddleY = Math.max(evt.pageY - visibleYStart - (paddleWidth/2), 0); 314 | paddleY = Math.min(canvas.height - paddleWidth, paddleY); 315 | } 316 | }, 317 | 318 | 319 | /* 320 | * mouse clicking 321 | */ 322 | onMouseClick = function(evt) { 323 | 324 | if (!evt) { evt = window.event; } 325 | 326 | // only used to start a game, so detect if we're waiting for this 327 | if (gameState != 'running') { 328 | 329 | if (imagesAcceptable == 0) { 330 | updateGameState('noImages'); 331 | } else { 332 | updateGameState('running'); 333 | } 334 | } 335 | }, 336 | 337 | 338 | 339 | /* 340 | * draw bg 341 | */ 342 | drawBg = function() { 343 | drawContext.clearRect(0, 0, canvas.width, canvas.height); 344 | drawContext.fillStyle = 'rgba(0, 0, 0, '+canvasTransparency+')'; 345 | drawContext.fillRect(0, 0, canvas.width, canvas.height); 346 | }, 347 | 348 | 349 | /* 350 | * create titles 351 | */ 352 | drawTitle = function() { 353 | if (!textBlocks.titleBlock) { 354 | textBlocks.titleBlock = new CanvasLetters({ 355 | textString:'Browser', 356 | name: 'titleBlock', 357 | x: 25, 358 | y: 25, 359 | blockSize: 10, 360 | animate:true, 361 | ordering:textAnimationTypes[Math.round(Math.random()*textAnimationTypes.length-1)] 362 | }); 363 | } 364 | if (!textBlocks.subTitleBlock) { 365 | textBlocks.subTitleBlock = new CanvasLetters({ 366 | textString:'Breakout', 367 | name: 'subTitleBlock', 368 | x: 25, 369 | y: 125, 370 | blockSize: 10, 371 | animate:true, 372 | ordering:textAnimationTypes[Math.round(Math.random()*textAnimationTypes.length-1)] 373 | }); 374 | } 375 | }, 376 | 377 | 378 | /* 379 | * create play now text 380 | */ 381 | drawPlayNow = function() { 382 | if (!textBlocks.playNow) { 383 | textBlocks.playNow = new CanvasLetters({ 384 | textString:'Click to play', 385 | name:'playNow', 386 | x: 25, 387 | y: 250, 388 | blockSize: 5 389 | }); 390 | } 391 | }, 392 | 393 | 394 | /* 395 | * create top scores 396 | */ 397 | drawTopScores = function() { 398 | if (!textBlocks.lastScore) { 399 | textBlocks.lastScore = new CanvasLetters({ 400 | textString:'Last score - ' + lastScore, 401 | name:'lastScore', 402 | x: 25, 403 | y: -25, 404 | blockSize: 2, 405 | clearance: 2 406 | }); 407 | } 408 | 409 | if (!textBlocks.topScore) { 410 | textBlocks.topScore = new CanvasLetters({ 411 | textString:'Top score - ' + topScore, 412 | name:'topScore', 413 | x: 25, 414 | y: -50, 415 | blockSize: 2, 416 | clearance: 2 417 | }); 418 | } 419 | }, 420 | 421 | 422 | /* 423 | * create creds 424 | */ 425 | drawCreds = function() { 426 | if (!textBlocks.creds) { 427 | textBlocks.creds = new CanvasLetters({ 428 | textString:'blah', 429 | name:'creds', 430 | x: -25, 431 | y: -25, 432 | blockSize: 2, 433 | clearance: 2 434 | }); 435 | } 436 | }, 437 | 438 | 439 | /* 440 | * create in-game scores 441 | */ 442 | drawScore = function() { 443 | if (!textBlocks.score) { 444 | textBlocks.score = new CanvasLetters({ 445 | textString:'Score - ' + (imagesAcceptable - imagesRemaining), 446 | name:'score', 447 | x: 25, 448 | y: -25, 449 | blockSize: 2, 450 | clearance: 2 451 | }); 452 | } 453 | 454 | if (!textBlocks.remaining) { 455 | textBlocks.remaining = new CanvasLetters({ 456 | textString:'Blocks Remaining - ' + imagesRemaining, 457 | name:'remaining', 458 | x: -25, 459 | y: -25, 460 | blockSize: 2, 461 | clearance: 2 462 | }); 463 | } 464 | }, 465 | 466 | 467 | /* 468 | * create game end message 469 | */ 470 | drawLoseMessage = function() { 471 | if (!textBlocks.loseMessage) { 472 | textBlocks.loseMessage = new CanvasLetters({ 473 | textString:'You Lose', 474 | name:'loseMessage', 475 | x: -25, 476 | y: 25, 477 | blockSize: 5, 478 | clearance: 5 479 | }); 480 | } 481 | }, 482 | 483 | 484 | /* 485 | * create game end message 486 | */ 487 | drawWinMessage = function() { 488 | if (!textBlocks.winMessage) { 489 | textBlocks.winMessage = new CanvasLetters({ 490 | textString:'You Win!', 491 | name:'winMessage', 492 | x: -25, 493 | y: 25, 494 | blockSize: 5, 495 | clearance: 5 496 | }); 497 | } 498 | }, 499 | 500 | 501 | /* 502 | * create no images message 503 | */ 504 | drawNoImagesMessage = function() { 505 | if (!textBlocks.noImages) { 506 | textBlocks.noImages = new CanvasLetters({ 507 | textString:'No suitable images found', 508 | name:'noImages', 509 | x: 25, 510 | y: 400, 511 | blockSize: 5, 512 | clearance: 5 513 | }); 514 | } 515 | }, 516 | 517 | 518 | /* 519 | * draw text blocks - called every time by the draw() loop 520 | */ 521 | drawBlocks = function() { 522 | for (textBlock in textBlocks){ 523 | if (textBlocks[textBlock].isActive()) { 524 | textBlocks[textBlock].drawBlocks(); 525 | } 526 | }; 527 | }, 528 | 529 | 530 | /* 531 | * Change the game state - 532 | * resets blocks and game parameters 533 | */ 534 | updateGameState = function(state) { 535 | 536 | // reset game state 537 | drawBg(); 538 | removeAllText(); 539 | updateHighScore(); 540 | gameState = state; 541 | ballX = ballXStart; 542 | ballY = ballYStart; 543 | ballDx = ballDxStart; 544 | ballDy = ballDyStart; 545 | 546 | // reset images 547 | for (var i = images.length - 1; i >= 0; i--){ 548 | images[i].state = 1; 549 | images[i].visible = 0; 550 | } 551 | 552 | // reset text 553 | if (state != 'running') { 554 | textBlocks.titleBlock.setActiveVal(1); 555 | textBlocks.subTitleBlock.setActiveVal(1); 556 | textBlocks.creds.setActiveVal(1); 557 | textBlocks.lastScore.updateString('Last score - ' + lastScore); 558 | textBlocks.topScore.updateString('Top score - ' + topScore); 559 | 560 | // condition : show optional extra message 561 | if (state == 'noImages') { 562 | if (!!textBlocks.noImages) { 563 | textBlocks.noImages.setActiveVal(1); 564 | } 565 | } else if (state == 'gameEnded') { 566 | if (!!textBlocks.loseMessage) { 567 | textBlocks.loseMessage.setActiveVal(1); 568 | } 569 | } else if (state == 'victory') { 570 | if (!!textBlocks.winMessage) { 571 | textBlocks.winMessage.setActiveVal(1); 572 | } 573 | } 574 | } else { 575 | if (!!textBlocks.score && !!textBlocks.remaining) { 576 | textBlocks.score.updateString('Score - ' + (imagesAcceptable - imagesRemaining)); 577 | textBlocks.remaining.updateString('Blocks Remaining - ' + imagesRemaining); 578 | } 579 | } 580 | 581 | imagesRemaining = imagesAcceptable; 582 | debug('changed gameState to ' + state); 583 | }, 584 | 585 | 586 | /* 587 | * Remove all existing text 588 | */ 589 | removeAllText = function() { 590 | 591 | // reset all blocks ready to be redrawn 592 | for (textBlock in textBlocks){ 593 | textBlocks[textBlock].setActiveVal(0); 594 | } 595 | }, 596 | 597 | 598 | 599 | /* 600 | * During the game, when a block has been hit, update the score 601 | */ 602 | updateScore = function() { 603 | imagesRemaining--; 604 | textBlocks.score.updateString('Score - ' + (imagesAcceptable - imagesRemaining)); 605 | textBlocks.remaining.updateString('Blocks Remaining - ' + imagesRemaining); 606 | }, 607 | 608 | 609 | /* 610 | * When the game is complete, check whether this is a high score 611 | */ 612 | updateHighScore = function() { 613 | lastScore = imagesAcceptable - imagesRemaining; 614 | if (lastScore > topScore) { 615 | topScore = lastScore; 616 | } 617 | }, 618 | 619 | 620 | /* 621 | * draw images 622 | */ 623 | drawImages = function() { 624 | 625 | for (var counter = images.length - 1; counter >= 0; counter--){ 626 | var image = images[counter]; 627 | 628 | // condition : detect collision 629 | if ( 630 | image.state == 1 && 631 | ballX > image.imageXStart && ballX < image.imageXStart + image.imageWidth && 632 | ballY > image.imageYStart && ballY < image.imageYStart + image.imageHeight 633 | ) { 634 | 635 | // detect hit placement 636 | var hit = [ 637 | { dir: 'top', val: ballY - image.imageYStart }, 638 | { dir: 'bottom', val: (image.imageYStart+image.imageHeight) - ballY }, 639 | { dir: 'left', val: ballX - image.imageXStart }, 640 | { dir: 'right', val: (image.imageXStart+image.imageWidth) - ballX } 641 | ]; 642 | 643 | // sort to find where it hit 644 | function sortByLowest(a, b) { 645 | var x = a.val; 646 | var y = b.val; 647 | return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 648 | } 649 | hit.sort(sortByLowest); 650 | 651 | if (hit[0].dir == 'top' || hit[0].dir == 'bottom') { 652 | ballDy = -ballDy; 653 | } else { 654 | ballDx = -ballDx; 655 | } 656 | 657 | 658 | updateScore(); 659 | image.state = 0; 660 | } 661 | 662 | 663 | // condition : if the still image exists, display it! 664 | if (image.state == 1) { 665 | drawContext.drawImage(image.image, image.imageXStart, image.imageYStart); 666 | } 667 | }; 668 | }, 669 | 670 | 671 | /* 672 | * draw the ball 673 | */ 674 | drawBall = function() { 675 | 676 | // condition : adjust velocity for slow-moving ball 677 | var v = ballVelocity; 678 | if (Math.abs(ballDx) < 1 || Math.abs(ballDy) < 1) { v +=1; } 679 | if (Math.abs(ballDx) < 1 && Math.abs(ballDy) < 1) { v +=1; } 680 | if (Math.abs(ballDx) < 0.75 && Math.abs(ballDy) < 0.75) { v +=1; } 681 | if (Math.abs(ballDx) < 0.5 && Math.abs(ballDy) < 0.5) { v +=1; } 682 | 683 | // set new ball position 684 | ballX += v * ballDx; 685 | ballY += v * ballDy; 686 | 687 | // draw new ball 688 | drawContext.fillStyle = ballColour; 689 | drawCircle(ballX, ballY, ballRadius); 690 | }, 691 | 692 | 693 | /* 694 | * draw the four paddles around the edge 695 | */ 696 | drawPaddles = function() { 697 | 698 | // calculate whether to move paddles 699 | if (rightDown) { 700 | paddleX += 20; 701 | } else if (leftDown) { 702 | paddleX -= 20; 703 | } 704 | 705 | if (downDown) { 706 | paddleY += 20; 707 | } else if (upDown) { 708 | paddleY -= 20; 709 | } 710 | 711 | if (paddleX < 0) { paddleX = 0; } //left 712 | if (paddleX > visibleWidth-paddleWidth) { paddleX=visibleWidth-paddleWidth; } // right 713 | if (paddleY < 0) { paddleY = 0; } // top 714 | if (paddleY > visibleHeight-paddleWidth) { paddleY=visibleHeight-paddleWidth; } // bottom 715 | 716 | 717 | drawContext.fillStyle = 'rgba(0, 0, 0, '+canvasTransparency+')'; 718 | 719 | 720 | // calculate bottom paddle hit detection 721 | if (ballY + ballDy + ballRadius > visibleHeight - paddleHeight) { 722 | if (ballX > paddleX && ballX < paddleX + paddleWidth) { 723 | ballDx = 5 * ((ballX - (paddleX + paddleWidth / 2)) / paddleWidth); 724 | ballDy = -ballDy; 725 | debug('Bottom Paddle Hit - Dx: ' + ballDx + '; Dy: ' + ballDy); 726 | } 727 | } 728 | 729 | 730 | // calculate top paddle hit detection 731 | if (ballY + ballDy - ballRadius < paddleHeight) { 732 | if (ballX > paddleX && ballX < paddleX + paddleWidth) { 733 | ballDx = 5 * ((ballX - (paddleX + paddleWidth / 2)) / paddleWidth); 734 | ballDy = -ballDy; 735 | debug('Top Paddle Hit - Dx: ' + ballDx + '; Dy: ' + ballDy); 736 | } 737 | } 738 | 739 | 740 | // calculate right paddle hit detection 741 | if (ballX + ballDx + ballRadius > visibleWidth - paddleHeight) { 742 | if (ballY > paddleY && ballY < paddleY + paddleWidth) { 743 | ballDy = 5 * ((ballY - (paddleY + paddleWidth / 2)) / paddleWidth); 744 | ballDx = -ballDx; 745 | debug('Right Paddle Hit: ' + ballDx + '; Dy: ' + ballDy); 746 | } 747 | } 748 | 749 | 750 | // calculate left paddle hit detection 751 | if (ballX + ballDx - ballRadius < paddleHeight) { 752 | if (ballY > paddleY && ballY < paddleY + paddleWidth) { 753 | ballDy = 5 * ((ballY - (paddleY + paddleWidth / 2)) / paddleWidth); 754 | ballDx = -ballDx; 755 | debug('Left Paddle Hit: ' + ballDx + '; Dy: ' + ballDy); 756 | } 757 | } 758 | 759 | 760 | // draw paddles 761 | drawContext.fillStyle = paddleColour; 762 | drawRectangle(paddleX, 0, paddleWidth, paddleHeight); // top 763 | drawRectangle(paddleX, visibleHeight - paddleHeight, paddleWidth, paddleHeight); // bottom 764 | drawRectangle(0, paddleY, paddleHeight, paddleWidth); // left 765 | drawRectangle(visibleWidth - paddleHeight, paddleY, paddleHeight, paddleWidth); // right 766 | }, 767 | 768 | 769 | /* 770 | * draw a circle 771 | */ 772 | drawCircle = function(x,y,r) { 773 | drawContext.beginPath(); 774 | drawContext.arc(x, y, r, 0, Math.PI*2, true); 775 | drawContext.closePath(); 776 | drawContext.fill(); 777 | }, 778 | 779 | 780 | /* 781 | * draw a rectangle 782 | */ 783 | drawRectangle = function(x,y,w,h) { 784 | drawContext.beginPath(); 785 | drawContext.rect(x,y,w,h); 786 | drawContext.closePath(); 787 | drawContext.fill(); 788 | }, 789 | 790 | 791 | /* 792 | * Draw method called by the loop 793 | */ 794 | draw = function() { 795 | 796 | // condition : detect game state 797 | switch (gameState) { 798 | 799 | // show title screen? 800 | case 'title' : case 'gameEnded' : case 'victory' : case 'noImages' : 801 | 802 | // draw background (with initial fade in) 803 | if (canvasTransparency < targetTransparency) { 804 | canvasTransparency +=0.1; 805 | drawBg(); 806 | 807 | // bg is faded in, show title screen 808 | } else { 809 | 810 | // show title 811 | drawTitle(); 812 | 813 | // show play now text 814 | drawPlayNow(); 815 | 816 | // display scores 817 | drawTopScores(); 818 | 819 | // display creds 820 | drawCreds(); 821 | 822 | // draw above text 823 | drawBlocks(); 824 | 825 | 826 | // condition : if we've just lost, show message 827 | if (gameState == 'gameEnded') { 828 | 829 | drawLoseMessage(); 830 | 831 | // show VICTORY message 832 | } else if (gameState == 'victory') { 833 | 834 | drawWinMessage(); 835 | 836 | // show NO IMAGES message 837 | } else if (gameState == 'noImages') { 838 | 839 | drawNoImagesMessage(); 840 | 841 | } 842 | } 843 | 844 | break; 845 | 846 | 847 | // game is in progress? 848 | case 'running' : 849 | 850 | drawBg(); 851 | 852 | // draw ball 853 | drawBall(); 854 | 855 | // draw score 856 | drawScore(); 857 | 858 | // draw images 859 | drawImages(); 860 | 861 | // draw paddles 862 | drawPaddles(); 863 | 864 | // if text needs drawing, draw it 865 | drawBlocks(); 866 | 867 | 868 | // calculate game state 869 | if (imagesRemaining == 0) { 870 | updateGameState('victory'); 871 | } 872 | 873 | if ( 874 | ballX + ballDx + ballRadius > visibleWidth || // right 875 | ballX + ballDx - ballRadius < 0 || // left 876 | ballY + ballDy + ballRadius > visibleHeight || // bottom 877 | ballY + ballDy - ballRadius < 0 // top 878 | ) { 879 | updateGameState('gameEnded'); 880 | } 881 | 882 | 883 | break; 884 | } 885 | }, 886 | 887 | 888 | /* 889 | * Debug 890 | * output debug messages 891 | * 892 | * @return void 893 | * @private 894 | */ 895 | debug = function(content) { 896 | if (!!debugMode) { 897 | console.log(content); 898 | clearTimeout(debugTimeout); 899 | debugTimeout = setTimeout(debugSpacer, 2000); 900 | } 901 | }, 902 | debugSpacer = function() { 903 | if (!!debugMode) { 904 | console.log('----------------------------------------------------------------------------------'); 905 | } 906 | }, 907 | 908 | 909 | /* 910 | * initialisation method 911 | */ 912 | init = function(){ 913 | 914 | debug('init()'); 915 | 916 | 917 | // fix browser CSS to disable scrolling 918 | hideOverflow(); 919 | 920 | 921 | // calculate browser positions 922 | calculatePositions(); 923 | 924 | 925 | // retrieve all images of a reasonable size 926 | collectImages(); 927 | 928 | 929 | // create canvas element 930 | if (!canvas) { 931 | createCanvas(); 932 | } 933 | 934 | 935 | // set up listeners for keys and mouse... 936 | document.onmousemove = function(e) { 937 | onMouseMove(e); 938 | }; 939 | document.onkeydown = function(e) { 940 | onKeyDown(e); 941 | }; 942 | document.onkeyup = function(e) { 943 | onKeyUp(e); 944 | }; 945 | document.onclick = function(e) { 946 | onMouseClick(e); 947 | }; 948 | document.onkeypress = function(e) { 949 | onMouseClick(e); 950 | 951 | // stop browser scrolling on click 952 | if (!e) { e = window.event; } 953 | if (e.keyCode == 40 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 37) { 954 | return false; 955 | } 956 | 957 | // escape or X - remove canvas 958 | if (e.keyCode == 27 || e.keyCode == 120) { 959 | canvas.parentNode.removeChild(canvas); 960 | //return false; 961 | } 962 | }; 963 | 964 | 965 | // draw! 966 | drawInterval = setInterval(draw, 25); 967 | }, 968 | 969 | 970 | /* 971 | * reset game (on browser resize) 972 | */ 973 | resetGame = function() { 974 | if (!reseting) { 975 | reseting = true; 976 | clearInterval(drawInterval); 977 | debug('game reset'); 978 | updateGameState('title'); 979 | canvasTransparency = 0; 980 | images = []; 981 | imagesAcceptable = 0; 982 | calculatePositions(); 983 | setCanvasPosition(); 984 | init(); 985 | } 986 | reseting = false; 987 | }; 988 | 989 | 990 | /* 991 | * restart on resize 992 | */ 993 | window.onresize = function() { 994 | resetGame(); 995 | }; 996 | 997 | 998 | 999 | 1000 | 1001 | /* 1002 | * Canvas Letters - used for in-game text 1003 | * 1004 | */ 1005 | var CanvasLetters = function(initOptions) { 1006 | 1007 | /* 1008 | * Array of blocks to draw 1009 | */ 1010 | var blocks = [], 1011 | blockCount = 0, 1012 | 1013 | /* 1014 | * current block drawing details 1015 | */ 1016 | currentX = 0, 1017 | currentY = 0, 1018 | currentBlock = 0, 1019 | lineCount = 1, 1020 | 1021 | /* 1022 | * Character block dimensions 1023 | */ 1024 | characterBlockWidth = 5, 1025 | characterBlockHeight = 7, 1026 | 1027 | /* 1028 | * the (potentially modified) text string we're drawing 1029 | */ 1030 | textString = '', 1031 | 1032 | /* 1033 | * Characters 1034 | */ 1035 | characters = { 1036 | 'a': [0,0,1,0,0,0,1,0,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,0,0,0,1,1,0,0,0,1], 1037 | 'b': [1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,0], 1038 | 'c': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,1,1,0], 1039 | 'd': [1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,1,1,0,0], 1040 | 'e': [1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1], 1041 | 'f': [1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0], 1042 | 'g': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,0,1,0,1,1,1,1,0,0,0,1,1,0,0,0,1,0,1,1,1,1], 1043 | 'h': [1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1], 1044 | 'i': [1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,1,1,1,1], 1045 | 'j': [1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0,1,1,1,0,0], 1046 | 'k': [1,0,0,0,1,1,0,0,1,0,1,0,1,0,0,1,1,0,0,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,1], 1047 | 'l': [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1], 1048 | 'm': [1,0,0,0,1,1,1,0,1,1,1,0,1,0,1,1,0,1,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1], 1049 | 'n': [1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1], 1050 | 'o': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1051 | 'p': [1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0], 1052 | 'q': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,1,1,0,1], 1053 | 'r': [1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,0,1,0,1,0,0,1,0,0,1,0,1,0,0,0,1], 1054 | 's': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1055 | 't': [1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0], 1056 | 'u': [1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1057 | 'v': [1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,0,1,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0], 1058 | 'w': [1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,1,0,1,1,0,1,0,1,1,0,1,0,1,0,1,0,1,0], 1059 | 'x': [1,0,0,0,1,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,0,0,1,1,0,0,0,1], 1060 | 'y': [1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0], 1061 | 'z': [1,1,1,1,1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1], 1062 | '0': [0,1,1,1,0,1,0,0,0,1,1,0,0,1,1,1,0,1,0,1,1,1,0,0,1,1,0,0,0,1,0,1,1,1,0], 1063 | '1': [0,0,1,0,0,0,1,1,0,0,1,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,1,1,1,1,1], 1064 | '2': [0,1,1,1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1], 1065 | '3': [0,1,1,1,0,1,0,0,0,1,0,0,0,0,1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1066 | '4': [0,0,0,1,0,0,0,1,1,0,0,1,0,1,0,1,0,0,1,0,1,1,1,1,1,0,0,0,1,0,0,0,0,1,0], 1067 | '5': [1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1068 | '6': [0,0,1,1,0,0,1,0,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1069 | '7': [1,1,1,1,1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0], 1070 | '8': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,1,0,1,1,1,0,1,0,0,0,1,1,0,0,0,1,0,1,1,1,0], 1071 | '9': [0,1,1,1,0,1,0,0,0,1,1,0,0,0,1,0,1,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,1,0,0], 1072 | '-': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 1073 | '?': [0,1,1,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0], 1074 | '!': [0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0], 1075 | '@': [0,1,1,1,0,1,0,0,0,1,1,0,1,1,1,1,0,1,0,1,1,0,1,1,0,1,0,0,0,1,0,1,1,1,0], 1076 | '&': [0,1,1,0,0,1,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,1,0,1,1,0,0,1,0,0,1,1,0,1], 1077 | '.': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0], 1078 | ' ': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 1079 | }, 1080 | 1081 | 1082 | 1083 | /* 1084 | * default options 1085 | * (the ones to copy from if an option isn't specified specifically) 1086 | */ 1087 | defaults = { 1088 | blockColour : 'ff9900', 1089 | blockSize : 10, 1090 | textString : '...', 1091 | clearance : 10, 1092 | ordering : 'default', 1093 | animate : false, 1094 | name : null, 1095 | active : 1, 1096 | x : 0, 1097 | y : 0 1098 | }, 1099 | 1100 | /* 1101 | * config options 1102 | * (the combined options, the ones to use) 1103 | */ 1104 | options = {}, 1105 | 1106 | 1107 | /* 1108 | * save any options sent through to the intialisation script, if set 1109 | */ 1110 | saveOptions = function() { 1111 | for (var option in defaults) { 1112 | if (!!initOptions[option]) { 1113 | options[option] = initOptions[option]; 1114 | } else { 1115 | options[option] = defaults[option]; 1116 | } 1117 | } 1118 | }, 1119 | 1120 | 1121 | /* 1122 | * condition : is this textBlock active? 1123 | */ 1124 | isActive = function() { 1125 | return options.active; 1126 | }, 1127 | 1128 | 1129 | /* 1130 | * condition : is this textBlock active? 1131 | */ 1132 | setActiveVal = function(val) { 1133 | options.active = val; 1134 | if (!!options.animate) { 1135 | currentBlock = 0; 1136 | } 1137 | }, 1138 | 1139 | 1140 | /* 1141 | * condition : redraw new string 1142 | */ 1143 | updateString = function(str) { 1144 | debug('updateString'); 1145 | options.textString = str; 1146 | setActiveVal(1); 1147 | startLetters(); 1148 | }, 1149 | 1150 | 1151 | /* 1152 | * Start letters 1153 | */ 1154 | startLetters = function() { 1155 | 1156 | // init values 1157 | lineCount = 1; 1158 | currentBlock = 0; 1159 | blocks = []; 1160 | blockCount = 0; 1161 | 1162 | if (options.x < 0) { 1163 | currentX = visibleWidth - (options.blockSize*characterBlockHeight*(options.textString.length-1)) + options.x; 1164 | } else { 1165 | currentX = options.x + 0; 1166 | } 1167 | 1168 | if (options.y < 0) { 1169 | currentY = visibleHeight - options.clearance - (options.blockSize*characterBlockHeight) + options.y; 1170 | } else { 1171 | currentY = options.y + 0; 1172 | } 1173 | 1174 | fixTextLength(); 1175 | calculateBlockPositions(); 1176 | 1177 | // if we're not animating, show everything at once 1178 | if (!options.animate) { 1179 | currentBlock = blocks.length; 1180 | } 1181 | 1182 | // add this into the queue of text to be drawn 1183 | if (!!textBlocks['options.name']) { 1184 | textBlocks['options.name'].setActiveVal(1); 1185 | } 1186 | debug('adding ' + options.name + ' to queue'); 1187 | }, 1188 | 1189 | 1190 | 1191 | /* 1192 | * 1193 | */ 1194 | fixTextLength = function() { 1195 | 1196 | textString = options.textString.toLowerCase(); 1197 | 1198 | // calculate line length 1199 | var lineLength = Math.floor( ( canvas.width - options.clearance ) / ( ( characterBlockWidth * options.blockSize ) + options.clearance ) ); 1200 | 1201 | // test each word invidivually 1202 | textStringArray = textString.split(' '); 1203 | for (var counter = textStringArray.length - 1; counter >= 0; counter--){ 1204 | 1205 | // if any words are longer than the line-length, hyphenate 1206 | if (textStringArray[counter].length > lineLength) { 1207 | 1208 | var originalWord = word = textStringArray[counter]; 1209 | var wordArray = []; 1210 | 1211 | // split the word every time it hits the line length 1212 | while (word.length > lineLength) { 1213 | wordArray.push(word.substr(0, lineLength-1)); 1214 | word = word.substr(lineLength-1); 1215 | } 1216 | wordArray.push(word); 1217 | 1218 | textString = textString.replace(originalWord, wordArray.join('- ')); 1219 | } 1220 | }; 1221 | }, 1222 | 1223 | 1224 | 1225 | /* 1226 | * 1227 | */ 1228 | calculateBlockPositions = function() { 1229 | 1230 | // draw the text string 1231 | for (var character = 0, textStringLength = textString.length; character < textString.length; character++) { 1232 | 1233 | // if we can draw this letter, begin 1234 | if (!!characters[textString[character]]) { 1235 | 1236 | // if this isn't the first character, work out how far along the line to put it 1237 | if (character > 0) { 1238 | currentX += (options.blockSize * characterBlockWidth) + options.clearance; 1239 | } 1240 | 1241 | // find the position of the next space (to calculate the word length) 1242 | var nextSpacePosition = textString.indexOf(' ', character); 1243 | if (nextSpacePosition == -1) { nextSpacePosition = textStringLength; } 1244 | 1245 | // start working out where to place the new letter/word 1246 | var newLineRequired = false; 1247 | 1248 | 1249 | // condition : is this word going to fit on the current line? 1250 | if (currentX + (options.blockSize * (characterBlockWidth*(nextSpacePosition-character))) + (options.clearance*(nextSpacePosition-character)) > canvas.width - options.clearance) { 1251 | newLineRequired = true; 1252 | } 1253 | 1254 | 1255 | // condition : start a new line? 1256 | if (newLineRequired && textString[character] != ' ') { 1257 | currentX = options.clearance; 1258 | currentY = (lineCount*(characterBlockHeight*options.blockSize)) + (options.clearance*++lineCount); 1259 | } 1260 | 1261 | 1262 | // get the blocks for this character 1263 | var blockArray = characters[textString[character]]; 1264 | 1265 | // for each block within a character 1266 | for (var block = 0, blockArrayLength = blockArray.length; block < blockArrayLength; block++) { 1267 | 1268 | // calculate X & Y positions for each block 1269 | var x = currentX; 1270 | var y = currentY; 1271 | x += (options.blockSize * (block % characterBlockWidth)); 1272 | if (block >= characterBlockWidth) { 1273 | y += (options.blockSize*(Math.floor(block/characterBlockWidth))); 1274 | } 1275 | 1276 | // if we're drawing a block, add it to the array 1277 | if (blockArray[block] == 1) { 1278 | blocks.push({x:x,y:y,opacity:0}); 1279 | } 1280 | } 1281 | } 1282 | } 1283 | 1284 | // condition : change order of appearing blocks 1285 | switch (options.ordering) { 1286 | case 'vertical': 1287 | function vertical(a, b) { return a.y - b.y; } 1288 | blocks.sort(vertical); 1289 | break; 1290 | 1291 | case 'horizontal': 1292 | function horizontal(a, b) { return a.x - b.x; } 1293 | blocks.sort(horizontal); 1294 | break; 1295 | 1296 | case 'reverse': 1297 | blocks.reverse(); 1298 | break; 1299 | 1300 | case 'random': 1301 | function randOrd(){ return (Math.round(Math.random())-0.5); } 1302 | blocks.sort(randOrd); 1303 | break; 1304 | } 1305 | 1306 | 1307 | blockCount = blocks.length; 1308 | }, 1309 | 1310 | 1311 | /* 1312 | * 1313 | */ 1314 | drawBlocks = function() { 1315 | 1316 | // normal direction, add blocks 1317 | var drawColour = options.blockColour; 1318 | 1319 | // calculate which blocks to work on 1320 | var animateLimit = (!!options.animate) ? currentBlock-10 : 0; 1321 | 1322 | // loop through blocks and draw! 1323 | for (var counter = animateLimit; counter < currentBlock; counter++) { 1324 | if (!!blocks[counter]) { 1325 | if (blocks[counter].opacity < 1) { blocks[counter].opacity += 0.1; } 1326 | drawContext.fillStyle = 'rgba('+HexToRGB(drawColour)+', '+blocks[counter].opacity+')'; 1327 | drawRectangle(blocks[counter].x, blocks[counter].y, options.blockSize, options.blockSize); 1328 | } 1329 | }; 1330 | 1331 | // add one to loop 1332 | currentBlock++; 1333 | 1334 | // calculate whether to end the drawing 1335 | if (currentBlock == blockCount+10) { 1336 | 1337 | // remove this from the queue of to be drawn 1338 | debug('finished drawing ' + options.textString); 1339 | } 1340 | }, 1341 | 1342 | 1343 | /* 1344 | * Turn Hex into RGB, for block colour 1345 | */ 1346 | HexToRGB = function(h) {return HexToR(h) +','+HexToG(h)+','+HexToB(h);}, 1347 | HexToR = function(h) {return parseInt((cutHex(h)).substring(0,2),16);}, 1348 | HexToG = function(h) {return parseInt((cutHex(h)).substring(2,4),16);}, 1349 | HexToB = function(h) {return parseInt((cutHex(h)).substring(4,6),16);}, 1350 | cutHex = function(h) {return (h.charAt(0)=='#') ? h.substring(1,7):h;}, 1351 | 1352 | 1353 | 1354 | /* 1355 | * initialisation method 1356 | */ 1357 | __init = function(){ 1358 | 1359 | // save the init options 1360 | saveOptions(); 1361 | 1362 | debug('drawing ' + options.textString); 1363 | 1364 | // init canvas set-up 1365 | startLetters(); 1366 | 1367 | }(); 1368 | 1369 | 1370 | /* 1371 | * expose public methods 1372 | */ 1373 | return { 1374 | init: init, 1375 | isActive : isActive, 1376 | setActiveVal : setActiveVal, 1377 | drawBlocks: drawBlocks, 1378 | updateString: updateString 1379 | }; 1380 | }; 1381 | 1382 | 1383 | // start the game! 1384 | init(); 1385 | 1386 | }(); --------------------------------------------------------------------------------