// class Point ---------------------- function Point(px, py) { this.px = px; this.py = py; } //----------------------------------- // class Mine ----------------------- function Mine(p, radius, color, xSpeed, ySpeed) { this.p = p; this.radius = radius; this.color = color; this.xSpeed = xSpeed; this.ySpeed = ySpeed; } Mine.prototype.draw = function(ctx) { ctx.beginPath(); ctx.arc(this.p.px, this.p.py, this.radius, 0, 2 * Math.PI, false); ctx.fillStyle = this.color; ctx.fill(); ctx.lineWidth = 1; ctx.strokeStyle = "black"; ctx.stroke(); } Mine.prototype.updatePosition = function() { this.p.px += this.xSpeed; this.p.py += this.ySpeed; } Mine.prototype.reverseSpeedAtBoundary = function() { // When hit bottom, ricochet at equal angles. var mineBottom = this.p.py + this.radius; if (mineBottom > canvas.height) this.ySpeed = -this.ySpeed; // When hit right, ricochet at equal angles. var mineRight = this.p.px + this.radius; if (mineRight > canvas.width) this.xSpeed = -this.xSpeed; // When hit top, ricochet at equal angles. var mineTop = this.p.py - this.radius; if (mineTop < 0) this.ySpeed = -this.ySpeed; // When hit left, ricochet at equal angles. var mineLeft = this.p.px - this.radius; if (mineLeft < 0) this.xSpeed = -this.xSpeed; } Mine.prototype.contains = function(t) { var dx = t.px - this.p.px; var dy = t.py - this.p.py; return (dx * dx + dy * dy < this.radius * this.radius); } Mine.prototype.hitTest = function(ship) { for (var k = 0; k < 5; k++) if (this.contains(ship.vertices[k])) return (true); // If get here, no collision. return false; } //----------------------------------- // class Ship ----------------------- function Ship(p, radius, color, speed) { this.p = p; this.radius = radius; this.color = color; this.speed = speed; this.vertices = this.generateVertices(); } Ship.prototype.generateVertices = function() { var vertices = new Array(); var NUM_SIDES = 5; // Calculate polygon's central angle in radians (72 deg for pentagon). var theta = (2 * Math.PI) / NUM_SIDES; // Get sine and cosine of central angle. var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); // Initial vertex at top of circle; start rotating point there, store rounded copy. var rotPt = new Point(this.p.px, this.p.py - this.radius); vertices.push(new Point(Math.round(rotPt.px), Math.round(rotPt.py))); // Go clockwise around circle calulating polygon points - // would be ccw in standard Cartesian system. for (var side = 1; side < NUM_SIDES; side++) { // Calculate the radial spoke : rotPt - center (p). var spoke = new Point(rotPt.px - this.p.px, rotPt.py - this.p.py); // Generate new rotating point (rotate around center). rotPt.px = (spoke.px * cosTheta - spoke.py * sinTheta) + this.p.px; rotPt.py = (spoke.px * sinTheta + spoke.py * cosTheta) + this.p.py; // Store rounded copy of rotating point. vertices.push(new Point(Math.round(rotPt.px), Math.round(rotPt.py))); } return vertices; } Ship.prototype.draw = function(ctx) { // Draw five-pointed star inscribed in ship's circle. ctx.beginPath(); ctx.moveTo(this.vertices[0].px, this.vertices[0].py); // Connect every other vertex of pentagon for a star. for (var k = 2; k < 10; k += 2) { var s = k % 5; ctx.lineTo(this.vertices[s].px, this.vertices[s].py); } ctx.closePath(); ctx.lineWidth = 1; ctx.strokeStyle = "black"; ctx.stroke(); ctx.fillStyle = this.color; ctx.fill(); } //----------------------------------- // Button classes ------------------- function PlayButton(p, radius) { this.p = p; this.radius = radius; } PlayButton.prototype.draw = function(ctx, bEnabled) { var backColor = bEnabled ? "lightGray" : "#ececec"; var foreColor = bEnabled ? "black" : "#c0c0c0"; // Entire button as circle. ctx.beginPath(); ctx.arc(this.p.px, this.p.py, this.radius, 0, 2 * Math.PI, false); ctx.fillStyle = backColor; ctx.fill(); ctx.lineWidth = 1; ctx.strokeStyle = foreColor; ctx.stroke(); var innerRadius = Math.floor(this.radius / 2); var dx = Math.floor(innerRadius / 2); var dy = Math.floor((Math.sqrt(3) / 2) * innerRadius); var pt1 = new Point(this.p.px + innerRadius, this.p.py); var pt2 = new Point(this.p.px - dx, this.p.py - dy); var pt3 = new Point(this.p.px - dx, this.p.py + dy); // Draw little triangle inscribed in inner circle. ctx.beginPath(); ctx.moveTo(pt1.px, pt1.py); ctx.lineTo(pt2.px, pt2.py); ctx.lineTo(pt3.px, pt3.py); ctx.closePath(); ctx.fillStyle = foreColor; ctx.fill(); } function PauseButton(p, radius) { this.p = p; this.radius = radius; } PauseButton.prototype.draw = function(ctx, bEnabled) { var backColor = bEnabled ? "lightGray" : "#ececec"; var foreColor = bEnabled ? "black" : "#c0c0c0"; // Entire button as circle. ctx.beginPath(); ctx.arc(this.p.px, this.p.py, this.radius, 0, 2 * Math.PI, false); ctx.fillStyle = backColor; ctx.fill(); ctx.lineWidth = 1; ctx.strokeStyle = foreColor; ctx.stroke(); // Key variables for size and position of bars. var barOffset = Math.floor(0.145 * this.radius); var barWidth = Math.floor(0.3 * this.radius); var barHeight = Math.floor(0.9 * this.radius); // Upper left of left bar. var x = this.p.px - barOffset - barWidth; var y = this.p.py - Math.floor(barHeight / 2); // Left bar. ctx.beginPath(); ctx.rect(x, y, barWidth, barHeight); ctx.fillStyle = foreColor; ctx.fill(); // Right bar. x = this.p.px + barOffset; ctx.beginPath(); ctx.rect(x, y, barWidth, barHeight); ctx.fillStyle = foreColor; ctx.fill(); } //----------------------------------- //----------------------------------- // Key global variables & constants. //----------------------------------- var canvas; var ctx; var radius = 30; var btnPlay; var btnPause; var mineColors = new Array("forestgreen", "navy", "yellow", "orange", "olive", "darkviolet"); var mines = new Array(new Mine(new Point(100, 50), radius, "green", 2, 2), new Mine(new Point(50, 100), radius, "blue", 2, -2)); var ship = new Ship(new Point(250, 50), radius, "red", 5); // Says if actively playing (mines moving, not paused). var bPlaying = false; // Says if game over. var bGameOver = false; // Date when game started or restarted after pause. var dStartPlay; // Accumulated playing time in ms (score). var timePlayed = 0; var CURSOR_LEFT = 37; var CURSOR_UP = 38; var CURSOR_RIGHT = 39; var CURSOR_DOWN = 40; jQuery(document).ready(function() { canvas = document.getElementById("game"); if (canvas.getContext) { ctx = canvas.getContext("2d"); btnPlay = new PlayButton(new Point(20, canvas.height - 20), 15); btnPause = new PauseButton(new Point(60, canvas.height - 20), 15); createMines() drawScreen(); // Have invalidate() called every 100 ms. setInterval(invalidate, 100); document.addEventListener("keydown", keyDown); canvas.addEventListener("mousedown", mouseDown, false); } }); function createMines() { // Assign random positions, speeds, and colors. // Left part of canvas. var minx = radius; var maxx = Math.floor(0.65 * canvas.width); var miny = radius; var maxy = canvas.height - radius; for (var k = 0; k < mines.length; k++) { var x = nextInt(minx, maxx); var y = nextInt(miny, maxy); var colorNdx = nextInt(0, mineColors.length); var mineSpeed = nextDouble(0.5, 3); mines[k] = new Mine(new Point(x, y), radius, mineColors[colorNdx], mineSpeed, mineSpeed); } } function invalidate() { // Key check to say if game is ongoing. if (!bPlaying || bGameOver) return; // Update all mines. for (var k = 0; k < mines.length; k++) { mines[k].updatePosition(); mines[k].reverseSpeedAtBoundary(); } drawScreen(); // Detect ship-mine collision (end of game). for (var k = 0; k < mines.length; k++) if (mines[k].hitTest(ship)) { bPlaying = false; bGameOver = true; btnPlay.draw(ctx, true); btnPause.draw(ctx, false); timePlayed += ((new Date()) - dStartPlay); alert("Game over - score = " + timePlayed + "\r\nClick play button for new game."); } } function drawScreen() { ctx.clearRect(0, 0, canvas.width, canvas.height); // - - - - - - - - - - - - - - - - - - - - // Color and frame canvas ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "lightgray"; ctx.fill(); ctx.strokeStyle = "black"; ctx.stroke(); // - - - - - - - - - - - - - - - - - - - - drawButtons(); ship.draw(ctx); // Draw all mines. for (var k = 0; k < mines.length; k++) mines[k].draw(ctx); } function drawButtons() { btnPlay.draw(ctx, !bPlaying); btnPause.draw(ctx, bPlaying); } function keyDown(event) { if (bGameOver) return; var key = event.keyCode; // Quick exit if not a cursor key. if ((key != CURSOR_LEFT) && (key != CURSOR_UP) && (key != CURSOR_RIGHT) && (key != CURSOR_DOWN)) return; // Adjust ship's position depending on cursor key. switch (key) { case CURSOR_LEFT: ship.p.px -= ship.speed; if (ship.p.px < 0) ship.p.px += canvas.width; break; case CURSOR_UP: event.preventDefault(); ship.p.py -= ship.speed; if (ship.p.py < 0) ship.p.py += canvas.height; break; case CURSOR_RIGHT: ship.p.px += ship.speed; if (ship.p.px > canvas.width) ship.p.px -= canvas.width; break; case CURSOR_DOWN: event.preventDefault(); ship.p.py += ship.speed; if (ship.p.py > canvas.height) ship.p.py -= canvas.height; break; } ship.vertices = ship.generateVertices(); invalidate(); } function mouseDown(event) { // User requests new game. if (bGameOver) { bGameOver = false; timePlayed = 0; createMines(); ship = new Ship(new Point(250, 50), radius, "red", 5); } var userPt = getMousePos(canvas, event); if (contains(btnPlay, userPt)) { dStartPlay = new Date(); bPlaying = true; } if (contains(btnPause, userPt)) { // Accumulate the time played. timePlayed += ((new Date()) - dStartPlay); bPlaying = false; drawButtons(); } } //------------------------ // Utility functions. //------------------------ function nextInt(min, max) { return Math.floor(Math.random() * (max - min) + min); } function nextDouble(min, max) { return Math.random() * (max - min) + min; } function drawPoint(ctx, pt) { ctx.beginPath(); ctx.arc(pt.px, pt.py, 2, 0, 2 * Math.PI, false); ctx.fillStyle = "black"; ctx.fill(); } function getMousePos(canvas, event) { // Ints not always returned! var rect = canvas.getBoundingClientRect(); return { px: event.clientX - Math.round(rect.left), py: event.clientY - Math.round(rect.top) }; } function contains(btn, pt) { // btn is a button with center point btn.p. // pt is a test point to check for being in the circle (button). var dx = pt.px - btn.p.px; var dy = pt.py - btn.p.py; return (dx * dx + dy * dy < btn.radius * btn.radius); }