├── .gitignore ├── LICENSE ├── README.md ├── circles-bouncing-off-lines ├── circles-bouncing-off-lines.js ├── docs │ ├── circles-bouncing-off-lines.html │ ├── docco.css │ └── public │ │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ │ └── stylesheets │ │ └── normalize.css ├── index.html └── screenshot.gif ├── gitlet └── index.html ├── index.html ├── main.css ├── package.json └── space-invaders ├── docs ├── docco.css ├── public │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css └── space-invaders.html ├── index.html ├── screenshot.gif ├── shoot.mp3 └── space-invaders.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mary Rose Cook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Annotated code 2 | 3 | A series of short, heavily annotated JavaScript programs. 4 | 5 | * By Mary Rose Cook 6 | * http://maryrosecook.com 7 | * maryrosecook@maryrosecook.com 8 | 9 | ## Space Invaders 10 | 11 | Demonstrates an animation loop, collision detection, keyboard input, drawing to the canvas and playing sounds. 12 | 13 | ![A screenshot of Space Invaders](/space-invaders/screenshot.gif) 14 | 15 | To read the annotated source, open `/space-invaders/docs/space-invaders.html` in your browser. 16 | 17 | ## Circles bouncing off lines 18 | 19 | Demonstrates some simple trig that calculates the movement of circles as they bounce off lines. 20 | 21 | ![An animation of circles bouncing off lines](/circles-bouncing-off-lines/screenshot.gif) 22 | 23 | To read the annotated source, open `/circles-bouncing-off-lines/docs/circles-bouncing-off-lines.html` in your browser. 24 | -------------------------------------------------------------------------------- /circles-bouncing-off-lines/circles-bouncing-off-lines.js: -------------------------------------------------------------------------------- 1 | ;(function(exports) { 2 | 3 | // The top-level functions that run the simulation 4 | // ----------------------------------------------- 5 | 6 | // **start()** creates the lines and circles and starts the simulation. 7 | function start() { 8 | 9 | // In index.html, there is a canvas tag that the game will be drawn in. 10 | // Grab that canvas out of the DOM. From it, get the drawing 11 | // context, an object that contains functions that allow drawing to the canvas. 12 | var screen = document.getElementById('circles-bouncing-off-lines').getContext('2d'); 13 | 14 | // `world` holds the current state of the world. 15 | var world = { 16 | circles: [], 17 | 18 | // Set up the five lines. 19 | lines: [ 20 | makeLine({ x: 100, y: 100 }), 21 | makeLine({ x: 200, y: 100 }), 22 | makeLine({ x: 150, y: 150 }), 23 | makeLine({ x: 100, y: 200 }), 24 | makeLine({ x: 220, y: 200 }), 25 | ], 26 | 27 | dimensions: { x: screen.canvas.width, y: screen.canvas.height }, 28 | 29 | // `timeLastCircleMade` is used in the periodic creation of new circles. 30 | timeLastCircleMade: 0 31 | }; 32 | 33 | // **tick()** is the main simulation tick function. It loops forever, running 60ish times a second. 34 | function tick() { 35 | 36 | // Update state of circles and lines. 37 | update(world); 38 | 39 | // Draw circles and lines. 40 | draw(world, screen); 41 | 42 | // Queue up the next call to tick with the browser. 43 | requestAnimationFrame(tick); 44 | }; 45 | 46 | // Run the first game tick. All future calls will be scheduled by 47 | // `tick()` itself. 48 | tick(); 49 | }; 50 | 51 | // Export `start()` so it can be run by index.html 52 | exports.start = start; 53 | 54 | // **update()** updates the state of the lines and circles. 55 | function update(world) { 56 | 57 | // Move and bounce the circles. 58 | updateCircles(world) 59 | 60 | // Create new circle if one hasn't been created for a while. 61 | createNewCircleIfDue(world); 62 | 63 | // Rotate the lines. 64 | updateLines(world); 65 | }; 66 | 67 | // **updateCircles()** moves and bounces the circles. 68 | function updateCircles(world) { 69 | for (var i = world.circles.length - 1; i >= 0; i--) { 70 | var circle = world.circles[i]; 71 | 72 | // Run through all lines. 73 | for (var j = 0; j < world.lines.length; j++) { 74 | var line = world.lines[j]; 75 | 76 | // If `line` is intersecting `circle`, bounce circle off line. 77 | if (trig.isLineIntersectingCircle(circle, line)) { 78 | physics.bounceCircle(circle, line); 79 | } 80 | } 81 | 82 | // Apply gravity to the velocity of `circle`. 83 | physics.applyGravity(circle); 84 | 85 | // Move `circle` according to its velocity. 86 | physics.moveCircle(circle); 87 | 88 | // Remove circles that are off screen. 89 | if (!isCircleInWorld(circle, world.dimensions)) { 90 | world.circles.splice(i, 1); 91 | } 92 | } 93 | }; 94 | 95 | // **createNewCircleIfDue()** creates a new circle every so often. 96 | function createNewCircleIfDue(world) { 97 | var now = new Date().getTime(); 98 | if (now - world.timeLastCircleMade > 400) { 99 | world.circles.push(makeCircle({ x: world.dimensions.x / 2, y: -5 })); 100 | 101 | // Update last circle creation time. 102 | world.timeLastCircleMade = now; 103 | } 104 | }; 105 | 106 | // **updateLines()** rotates the lines. 107 | function updateLines(world) { 108 | for (var i = 0; i < world.lines.length; i++) { 109 | world.lines[i].angle += world.lines[i].rotateSpeed; 110 | } 111 | }; 112 | 113 | // **draw()** draws the all the circles and lines in the simulation. 114 | function draw(world, screen) { 115 | // Clear away the drawing from the previous tick. 116 | screen.clearRect(0, 0, world.dimensions.x, world.dimensions.y); 117 | 118 | var bodies = world.circles.concat(world.lines); 119 | for (var i = 0; i < bodies.length; i++) { 120 | bodies[i].draw(screen); 121 | } 122 | }; 123 | 124 | // **makeCircle()** creates a circle that has the passed `center`. 125 | function makeCircle(center) { 126 | return { 127 | center: center, 128 | velocity: { x: 0, y: 0 }, 129 | radius: 5, 130 | 131 | // The circle has its own built-in `draw()` function. This allows 132 | // the main `draw()` to just polymorphicly call `draw()` on circles and lines. 133 | draw: function(screen) { 134 | screen.beginPath(); 135 | screen.arc(this.center.x, this.center.y, this.radius, 0, Math.PI * 2, true); 136 | screen.closePath(); 137 | screen.fillStyle = "black"; 138 | screen.fill(); 139 | } 140 | }; 141 | }; 142 | 143 | // **makeLine()** creates a line that has the passed `center`. 144 | function makeLine(center) { 145 | return { 146 | center: center, 147 | len: 70, 148 | 149 | // Angle of the line in degrees. 150 | angle: 0, 151 | 152 | rotateSpeed: 0.5, 153 | 154 | // The line has its own built-in `draw()` function. This allows 155 | // the main `draw()` to just polymorphicly call `draw()` on circles and lines. 156 | draw: function(screen) { 157 | var end1 = trig.lineEndPoints(this)[0]; 158 | var end2 = trig.lineEndPoints(this)[1]; 159 | 160 | screen.beginPath(); 161 | screen.lineWidth = 1.5; 162 | screen.moveTo(end1.x, end1.y); 163 | screen.lineTo(end2.x, end2.y); 164 | screen.closePath(); 165 | 166 | screen.strokeStyle = "black"; 167 | screen.stroke(); 168 | } 169 | }; 170 | }; 171 | 172 | // **isCircleInWorld()** returns true if `circle` is on screen. 173 | function isCircleInWorld(circle, worldDimensions) { 174 | return circle.center.x > -circle.radius && 175 | circle.center.x < worldDimensions.x + circle.radius && 176 | circle.center.y > -circle.radius && 177 | circle.center.y < worldDimensions.y + circle.radius; 178 | }; 179 | 180 | // Trigonometry functions to help with calculating circle movement 181 | // ------------------------------------------------------------- 182 | 183 | var trig = { 184 | 185 | // **distance()** returns the distance between `point1` and `point2` 186 | // as the crow flies. Uses Pythagoras's theorem. 187 | distance: function(point1, point2) { 188 | var x = point1.x - point2.x; 189 | var y = point1.y - point2.y; 190 | return Math.sqrt(x * x + y * y); 191 | }, 192 | 193 | // **magnitude()** returns the magnitude of the passed vector. 194 | // Sort of like the vector's speed. A vector with a larger x or y 195 | // will have a larger magnitude. 196 | magnitude: function(vector) { 197 | return Math.sqrt(vector.x * vector.x + vector.y * vector.y); 198 | }, 199 | 200 | // **unitVector()** returns the unit vector for `vector`. 201 | // A unit vector points in the same direction as the original, 202 | // but has a magnitude of 1. It's like a direction with a 203 | // speed that is the same as all other unit vectors. 204 | unitVector: function(vector) { 205 | return { 206 | x: vector.x / trig.magnitude(vector), 207 | y: vector.y / trig.magnitude(vector) 208 | }; 209 | }, 210 | 211 | // **dotProduct()** returns the dot product of `vector1` and 212 | // `vector2`. A dot product represents the amount one vector goes 213 | // in the direction of the other. Imagine `vector2` runs along 214 | // the ground and `vector1` represents a ball fired from a 215 | // cannon. If `vector2` is multiplied by the dot product of the 216 | // two vectors, it produces a vector that represents the amount 217 | // of ground covered by the ball. 218 | dotProduct: function(vector1, vector2) { 219 | return vector1.x * vector2.x + vector1.y * vector2.y; 220 | }, 221 | 222 | // **vectorBetween()** returns the vector that runs between `startPoint` 223 | // and `endPoint`. 224 | vectorBetween: function(startPoint, endPoint) { 225 | return { 226 | x: endPoint.x - startPoint.x, 227 | y: endPoint.y - startPoint.y 228 | }; 229 | }, 230 | 231 | // **lineEndPoints()** returns an array containing the points 232 | // at each end of `line`. 233 | lineEndPoints: function(line) { 234 | var angleRadians = line.angle * 0.01745; 235 | 236 | // Create a unit vector that represents the heading of 237 | // `line`. 238 | var lineUnitVector = trig.unitVector({ 239 | x: Math.cos(angleRadians), 240 | y: Math.sin(angleRadians) 241 | }); 242 | 243 | // Multiply the unit vector by half the line length. This 244 | // produces a vector that represents the offset of one of the 245 | // ends of the line from the center. 246 | var endOffsetFromCenterVector = { 247 | x: lineUnitVector.x * line.len / 2, 248 | y: lineUnitVector.y * line.len / 2 249 | }; 250 | 251 | // Return an array that contains the points at the two `line` ends. 252 | return [ 253 | 254 | // Add the end offset to the center to get one end of 'line'. 255 | { 256 | x: line.center.x + endOffsetFromCenterVector.x, 257 | y: line.center.y + endOffsetFromCenterVector.y 258 | }, 259 | 260 | // Subtract the end offset from the center to get the other 261 | // end of `line`. 262 | { 263 | x: line.center.x - endOffsetFromCenterVector.x, 264 | y: line.center.y - endOffsetFromCenterVector.y 265 | } 266 | ]; 267 | }, 268 | 269 | // **pointOnLineClosestToCircle()** returns the point on `line` 270 | // closest to `circle`. 271 | pointOnLineClosestToCircle: function(circle, line) { 272 | 273 | // Get the points at each end of `line`. 274 | var lineEndPoint1 = trig.lineEndPoints(line)[0]; 275 | var lineEndPoint2 = trig.lineEndPoints(line)[1]; 276 | 277 | // Create a vector that represents the line 278 | var lineUnitVector = trig.unitVector( 279 | trig.vectorBetween(lineEndPoint1, lineEndPoint2)); 280 | 281 | // Pick a line end and create a vector that represents the 282 | // imaginary line between the end and the circle. 283 | var lineEndToCircleVector = trig.vectorBetween(lineEndPoint1, circle.center); 284 | 285 | // Get a dot product of the vector between the line end and circle, and 286 | // the line vector. (See the `dotProduct()` function for a 287 | // fuller explanation.) This projects the line end and circle 288 | // vector along the line vector. Thus, it represents how far 289 | // along the line to go from the end to get to the point on the 290 | // line that is closest to the circle. 291 | var projection = trig.dotProduct(lineEndToCircleVector, lineUnitVector); 292 | 293 | // If `projection` is less than or equal to 0, the closest point 294 | // is at or past `lineEndPoint1`. So, return `lineEndPoint1`. 295 | if (projection <= 0) { 296 | return lineEndPoint1; 297 | 298 | // If `projection` is greater than or equal to the length of the 299 | // line, the closest point is at or past `lineEndPoint2`. So, 300 | // return `lineEndPoint2`. 301 | } else if (projection >= line.len) { 302 | return lineEndPoint2; 303 | 304 | // The projection indicates a point part way along the line. 305 | // Return that point. 306 | } else { 307 | return { 308 | x: lineEndPoint1.x + lineUnitVector.x * projection, 309 | y: lineEndPoint1.y + lineUnitVector.y * projection 310 | }; 311 | } 312 | }, 313 | 314 | // **isLineIntersectingCircle()** returns true if `line` is 315 | // intersecting `circle`. 316 | isLineIntersectingCircle: function(circle, line) { 317 | 318 | // Get point on line closest to circle. 319 | var closest = trig.pointOnLineClosestToCircle(circle, line); 320 | 321 | // Get the distance between the closest point and the center of 322 | // the circle. 323 | var circleToLineDistance = trig.distance(circle.center, closest); 324 | 325 | // Return true if distance is less than the radius. 326 | return circleToLineDistance < circle.radius; 327 | } 328 | } 329 | 330 | // Physics functions for calculating circle movement 331 | // ----------------------------------------------- 332 | 333 | var physics = { 334 | 335 | // **applyGravity()** adds gravity to the velocity of `circle`. 336 | applyGravity: function(circle) { 337 | circle.velocity.y += 0.06; 338 | }, 339 | 340 | // **moveCircle()** adds the velocity of the circle to its center. 341 | moveCircle: function(circle) { 342 | circle.center.x += circle.velocity.x; 343 | circle.center.y += circle.velocity.y; 344 | }, 345 | 346 | // **bounceCircle()** assumes `line` is intersecting `circle` and 347 | // bounces `circle` off `line`. 348 | bounceCircle: function(circle, line) { 349 | 350 | // Get the vector that points out from the surface the circle is 351 | // bouncing on. 352 | var bounceLineNormal = physics.bounceLineNormal(circle, line); 353 | 354 | // Set the new circle velocity by reflecting the old velocity in 355 | // `bounceLineNormal`. 356 | var dot = trig.dotProduct(circle.velocity, bounceLineNormal); 357 | circle.velocity.x -= 2 * dot * bounceLineNormal.x; 358 | circle.velocity.y -= 2 * dot * bounceLineNormal.y; 359 | 360 | // Move the circle until it has cleared the line. This stops 361 | // the circle getting stuck in the line. 362 | while (trig.isLineIntersectingCircle(circle, line)) { 363 | physics.moveCircle(circle); 364 | } 365 | }, 366 | 367 | // **bounceLineNormal()** assumes `line` intersects `circle`. It 368 | // returns the normal to the side of the line that the `circle` is 369 | // hitting. 370 | bounceLineNormal: function(circle, line) { 371 | 372 | // Get vector that starts at the closest point on 373 | // the line and ends at the circle. If the circle is hitting 374 | // the flat of the line, this vector will point perpenticular to 375 | // the line. If the circle is hitting the end of the line, the 376 | // vector will point from the end to the center of the circle. 377 | var circleToClosestPointOnLineVector = 378 | trig.vectorBetween( 379 | trig.pointOnLineClosestToCircle(circle, line), 380 | circle.center); 381 | 382 | // Make the normal a unit vector and return it. 383 | return trig.unitVector(circleToClosestPointOnLineVector); 384 | } 385 | }; 386 | 387 | // Start 388 | // ----- 389 | 390 | // When the DOM is ready, start the simulation. 391 | window.addEventListener('load', start); 392 | })(this); 393 | -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/circles-bouncing-off-lines.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | circles-bouncing-off-lines.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 1300 |
1301 | 1302 | 1303 | -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "novecento-bold"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | 79 | hr { 80 | border: 0; 81 | background: 1px #ddd; 82 | height: 1px; 83 | margin: 20px 0; 84 | } 85 | 86 | pre, tt, code { 87 | font-size: 12px; line-height: 16px; 88 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 89 | margin: 0; padding: 0; 90 | } 91 | .annotation pre { 92 | display: block; 93 | margin: 0; 94 | padding: 7px 10px; 95 | background: #fcfcfc; 96 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 97 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 98 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 99 | overflow-x: auto; 100 | } 101 | .annotation pre code { 102 | border: 0; 103 | padding: 0; 104 | background: transparent; 105 | } 106 | 107 | 108 | blockquote { 109 | border-left: 5px solid #ccc; 110 | margin: 0; 111 | padding: 1px 0 1px 1em; 112 | } 113 | .sections blockquote p { 114 | font-family: Menlo, Consolas, Monaco, monospace; 115 | font-size: 12px; line-height: 16px; 116 | color: #999; 117 | margin: 10px 0 0; 118 | white-space: pre-wrap; 119 | } 120 | 121 | ul.sections { 122 | list-style: none; 123 | padding:0 0 5px 0;; 124 | margin:0; 125 | } 126 | 127 | /* 128 | Force border-box so that % widths fit the parent 129 | container without overlap because of margin/padding. 130 | 131 | More Info : http://www.quirksmode.org/css/box.html 132 | */ 133 | ul.sections > li > div { 134 | -moz-box-sizing: border-box; /* firefox */ 135 | -ms-box-sizing: border-box; /* ie */ 136 | -webkit-box-sizing: border-box; /* webkit */ 137 | -khtml-box-sizing: border-box; /* konqueror */ 138 | box-sizing: border-box; /* css3 */ 139 | } 140 | 141 | 142 | /*---------------------- Jump Page -----------------------------*/ 143 | #jump_to, #jump_page { 144 | margin: 0; 145 | background: white; 146 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 147 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 148 | font: 16px Arial; 149 | cursor: pointer; 150 | text-align: right; 151 | list-style: none; 152 | } 153 | 154 | #jump_to a { 155 | text-decoration: none; 156 | } 157 | 158 | #jump_to a.large { 159 | display: none; 160 | } 161 | #jump_to a.small { 162 | font-size: 22px; 163 | font-weight: bold; 164 | color: #676767; 165 | } 166 | 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 10px 15px; 171 | margin:0; 172 | } 173 | 174 | #jump_wrapper { 175 | display: none; 176 | padding:0; 177 | } 178 | 179 | #jump_to:hover #jump_wrapper { 180 | display: block; 181 | } 182 | 183 | #jump_page { 184 | padding: 5px 0 3px; 185 | margin: 0 0 25px 25px; 186 | } 187 | 188 | #jump_page .source { 189 | display: block; 190 | padding: 15px; 191 | text-decoration: none; 192 | border-top: 1px solid #eee; 193 | } 194 | 195 | #jump_page .source:hover { 196 | background: #f5f5ff; 197 | } 198 | 199 | #jump_page .source:first-child { 200 | } 201 | 202 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 203 | @media only screen and (min-width: 320px) { 204 | .pilwrap { display: none; } 205 | 206 | ul.sections > li > div { 207 | display: block; 208 | padding:5px 10px 0 10px; 209 | } 210 | 211 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 212 | padding-left: 30px; 213 | } 214 | 215 | ul.sections > li > div.content { 216 | overflow-x:auto; 217 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 218 | box-shadow: inset 0 0 5px #e5e5ee; 219 | border: 1px solid #dedede; 220 | margin:5px 10px 5px 10px; 221 | padding-bottom: 5px; 222 | } 223 | 224 | ul.sections > li > div.annotation pre { 225 | margin: 7px 0 7px; 226 | padding-left: 15px; 227 | } 228 | 229 | ul.sections > li > div.annotation p tt, .annotation code { 230 | background: #f8f8ff; 231 | border: 1px solid #dedede; 232 | font-size: 12px; 233 | padding: 0 0.2em; 234 | } 235 | } 236 | 237 | /*---------------------- (> 481px) ---------------------*/ 238 | @media only screen and (min-width: 481px) { 239 | #container { 240 | position: relative; 241 | } 242 | body { 243 | background-color: #F5F5FF; 244 | font-size: 15px; 245 | line-height: 21px; 246 | } 247 | pre, tt, code { 248 | line-height: 18px; 249 | } 250 | p, ul, ol { 251 | margin: 0 0 15px; 252 | } 253 | 254 | 255 | #jump_to { 256 | padding: 5px 10px; 257 | } 258 | #jump_wrapper { 259 | padding: 0; 260 | } 261 | #jump_to, #jump_page { 262 | font: 10px Arial; 263 | text-transform: uppercase; 264 | } 265 | #jump_page .source { 266 | padding: 5px 10px; 267 | } 268 | #jump_to a.large { 269 | display: inline-block; 270 | } 271 | #jump_to a.small { 272 | display: none; 273 | } 274 | 275 | 276 | 277 | #background { 278 | position: absolute; 279 | top: 0; bottom: 0; 280 | width: 350px; 281 | background: #fff; 282 | border-right: 1px solid #e5e5ee; 283 | z-index: -1; 284 | } 285 | 286 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 287 | padding-left: 40px; 288 | } 289 | 290 | ul.sections > li { 291 | white-space: nowrap; 292 | } 293 | 294 | ul.sections > li > div { 295 | display: inline-block; 296 | } 297 | 298 | ul.sections > li > div.annotation { 299 | max-width: 350px; 300 | min-width: 350px; 301 | min-height: 5px; 302 | padding: 13px; 303 | overflow-x: hidden; 304 | white-space: normal; 305 | vertical-align: top; 306 | text-align: left; 307 | } 308 | ul.sections > li > div.annotation pre { 309 | margin: 15px 0 15px; 310 | padding-left: 15px; 311 | } 312 | 313 | ul.sections > li > div.content { 314 | padding: 13px; 315 | vertical-align: top; 316 | border: none; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .pilwrap { 322 | position: relative; 323 | display: inline; 324 | } 325 | 326 | .pilcrow { 327 | font: 12px Arial; 328 | text-decoration: none; 329 | color: #454545; 330 | position: absolute; 331 | top: 3px; left: -20px; 332 | padding: 1px 2px; 333 | opacity: 0; 334 | -webkit-transition: opacity 0.2s linear; 335 | } 336 | .for-h1 .pilcrow { 337 | top: 47px; 338 | } 339 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 340 | top: 35px; 341 | } 342 | 343 | ul.sections > li > div.annotation:hover .pilcrow { 344 | opacity: 1; 345 | } 346 | } 347 | 348 | /*---------------------- (> 1025px) ---------------------*/ 349 | @media only screen and (min-width: 1025px) { 350 | 351 | body { 352 | font-size: 16px; 353 | line-height: 24px; 354 | } 355 | 356 | #background { 357 | width: 525px; 358 | } 359 | ul.sections > li > div.annotation { 360 | max-width: 525px; 361 | min-width: 525px; 362 | padding: 10px 25px 1px 50px; 363 | } 364 | ul.sections > li > div.content { 365 | padding: 9px 15px 16px 25px; 366 | } 367 | } 368 | 369 | /*---------------------- Syntax Highlighting -----------------------------*/ 370 | 371 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 372 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 373 | /* 374 | 375 | github.com style (c) Vasily Polovnyov 376 | 377 | */ 378 | 379 | pre code { 380 | display: block; padding: 0.5em; 381 | color: #000; 382 | background: #f8f8ff 383 | } 384 | 385 | pre .hljs-comment, 386 | pre .hljs-template_comment, 387 | pre .hljs-diff .hljs-header, 388 | pre .hljs-javadoc { 389 | color: #408080; 390 | font-style: italic 391 | } 392 | 393 | pre .hljs-keyword, 394 | pre .hljs-assignment, 395 | pre .hljs-literal, 396 | pre .hljs-css .hljs-rule .hljs-keyword, 397 | pre .hljs-winutils, 398 | pre .hljs-javascript .hljs-title, 399 | pre .hljs-lisp .hljs-title, 400 | pre .hljs-subst { 401 | color: #954121; 402 | /*font-weight: bold*/ 403 | } 404 | 405 | pre .hljs-number, 406 | pre .hljs-hexcolor { 407 | color: #40a070 408 | } 409 | 410 | pre .hljs-string, 411 | pre .hljs-tag .hljs-value, 412 | pre .hljs-phpdoc, 413 | pre .hljs-tex .hljs-formula { 414 | color: #219161; 415 | } 416 | 417 | pre .hljs-title, 418 | pre .hljs-id { 419 | color: #19469D; 420 | } 421 | pre .hljs-params { 422 | color: #00F; 423 | } 424 | 425 | pre .hljs-javascript .hljs-title, 426 | pre .hljs-lisp .hljs-title, 427 | pre .hljs-subst { 428 | font-weight: normal 429 | } 430 | 431 | pre .hljs-class .hljs-title, 432 | pre .hljs-haskell .hljs-label, 433 | pre .hljs-tex .hljs-command { 434 | color: #458; 435 | font-weight: bold 436 | } 437 | 438 | pre .hljs-tag, 439 | pre .hljs-tag .hljs-title, 440 | pre .hljs-rules .hljs-property, 441 | pre .hljs-django .hljs-tag .hljs-keyword { 442 | color: #000080; 443 | font-weight: normal 444 | } 445 | 446 | pre .hljs-attribute, 447 | pre .hljs-variable, 448 | pre .hljs-instancevar, 449 | pre .hljs-lisp .hljs-body { 450 | color: #008080 451 | } 452 | 453 | pre .hljs-regexp { 454 | color: #B68 455 | } 456 | 457 | pre .hljs-class { 458 | color: #458; 459 | font-weight: bold 460 | } 461 | 462 | pre .hljs-symbol, 463 | pre .hljs-ruby .hljs-symbol .hljs-string, 464 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 465 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 466 | pre .hljs-lisp .hljs-keyword, 467 | pre .hljs-tex .hljs-special, 468 | pre .hljs-input_number { 469 | color: #990073 470 | } 471 | 472 | pre .hljs-builtin, 473 | pre .hljs-constructor, 474 | pre .hljs-built_in, 475 | pre .hljs-lisp .hljs-title { 476 | color: #0086b3 477 | } 478 | 479 | pre .hljs-preprocessor, 480 | pre .hljs-pi, 481 | pre .hljs-doctype, 482 | pre .hljs-shebang, 483 | pre .hljs-cdata { 484 | color: #999; 485 | font-weight: bold 486 | } 487 | 488 | pre .hljs-deletion { 489 | background: #fdd 490 | } 491 | 492 | pre .hljs-addition { 493 | background: #dfd 494 | } 495 | 496 | pre .hljs-diff .hljs-change { 497 | background: #0086b3 498 | } 499 | 500 | pre .hljs-chunk { 501 | color: #aaa 502 | } 503 | 504 | pre .hljs-tex .hljs-formula { 505 | opacity: 0.5; 506 | } 507 | -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /circles-bouncing-off-lines/docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /circles-bouncing-off-lines/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | Circles bouncing off lines - Annotated code 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |

Annotated code

26 |
short, heavily annotated JavaScript programs
27 |
28 |
29 | 30 |
31 | 32 | 33 |

Circles bouncing off lines

34 | 38 |
39 | 40 |
41 |

42 | Uses some simple trigonometry to calculate the movement of 43 | circles bouncing off lines. 44 |

45 | 46 |

47 | When I first tried to write this program, I had no idea how to bounce a circle off a line. 48 | And I couldn't find any good explanations on the web. Some resources were prose descriptions that did not 49 | derive the maths. Some derived the maths, but didn't explain what was going on. Some resouces showed only code. 50 | Some resources showed no code. 51 |

52 | 53 |

54 | My approach for the code is to make it proceed in small, pedantic steps. 55 | My approach for the comments is to explain all the maths spatially, rather than algebraicly. Hopefully, this means 56 | that the explanations are intuitive. 57 |

58 |
59 | 60 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /circles-bouncing-off-lines/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/circles-bouncing-off-lines/screenshot.gif -------------------------------------------------------------------------------- /gitlet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | Git - Annotated code 17 | 18 | 19 | 20 | 21 |
22 | 23 |

Annotated code

24 |
short, heavily annotated JavaScript programs
25 |
26 |
27 | 28 |
29 | 30 | 31 |

Git

32 | 36 |
37 | 38 |
39 |

An implemention of Git in JavaScript. See the main Gitlet website for more information.

40 | 41 |

Over the last six years, I've become better at using Git for version control. But my conceptions of the index, the working copy, the object graph and remotes have just grown fuzzier.

42 | 43 |

Sometimes, I can only understand something by implementing it. So, I wrote Gitlet, my own version of Git. I pored over tutorials. I read articles about internals. I tried to understand how API commands work by reading the docs, then gave up and ran hundreds of experiments on repositories and rummaged throught the .git directory to figure out the results.

44 | 45 |

I discovered that, if approached from the inside out, Git is easy to understand. It is the product of simple ideas that, when combined, produce something very deep and beautiful.

46 | 47 |

If you would like to understand what happens when you run the basic Git commands, you can read Git in six hundred words.

48 | 49 |

Afterwards, read the heavily annotated Gitlet source. 1000 lines of code sounds intimidating. But it's OK. The annotations explain both Git and the code in great detail. The code mirrors the terminology of the Git command line interface, so it should be approachable. And the implementation of the main Git commands is just 350 lines.

50 | 51 |

I wrote an article, Introducing Gitlet, about the process of writing the code.

52 |
53 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | Annotated code 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |

Annotated code

27 |
short, heavily annotated JavaScript programs
28 |
29 |
30 | 31 | 32 | 33 |
34 | 35 |
36 |
37 |
$ gitlet init
38 |
$ echo first > number.txt
39 |
$ gitlet add number.txt
40 |
$ gitlet commit -m "first"
41 |
[master 2912d7a2] first
42 |
43 |

Git

44 |
45 |
46 | 47 | 48 |
49 | 50 | 51 |

Circles bouncing off lines

52 |
53 |
54 | 55 | 56 |
57 | 58 | 59 |

Space Invaders

60 |
61 |
62 |
63 | 64 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Gill Sans WGL W01 Light"; 3 | font-size: 18px; 4 | line-height: 1.26em; 5 | text-rendering: optimizeLegibility; 6 | font-feature-settings: "kern"; 7 | -webkit-font-feature-settings: "kern"; 8 | -moz-font-feature-settings: "kern"; 9 | -moz-font-feature-settings: "kern=1"; 10 | 11 | width: 680px; 12 | margin-left:auto; 13 | margin-right:auto; 14 | padding: 0 10px 0 10px; 15 | color: #333; 16 | } 17 | 18 | a { 19 | color: #444; 20 | } 21 | 22 | a:hover { 23 | text-decoration: none; 24 | } 25 | 26 | h1 { 27 | font-size: 36px; 28 | line-height: 1em; 29 | font-weight:normal; 30 | margin-left:auto; 31 | margin-right:auto; 32 | text-align:center; 33 | margin-top:15px; 34 | margin-bottom:0; 35 | padding-bottom: 0; 36 | padding: 0; 37 | } 38 | 39 | h2 { 40 | font-size: 18px; 41 | font-weight:normal; 42 | text-align: center; 43 | margin-left:auto; 44 | margin-right:auto; 45 | margin-top:8px; 46 | margin-bottom:2px; 47 | padding: 0; 48 | } 49 | 50 | .header a { 51 | text-decoration:none; 52 | } 53 | 54 | h2 a { 55 | text-decoration:none; 56 | } 57 | 58 | p { 59 | margin:0 0 12px 0; 60 | } 61 | 62 | .strapline { 63 | margin-left:auto; 64 | margin-right:auto; 65 | text-align:center; 66 | padding:0; 67 | margin:0; 68 | } 69 | 70 | .header { 71 | border-bottom: 1px solid black; 72 | padding-bottom: 8px; 73 | } 74 | 75 | .footer { 76 | margin-left:auto; 77 | margin-right:auto; 78 | text-align:center; 79 | padding-top: 8px; 80 | border-top:1px solid black; 81 | height: 25px; 82 | } 83 | 84 | .grid { 85 | margin-left:auto; 86 | margin-right:auto; 87 | } 88 | 89 | .grid-element { 90 | height: 360px; 91 | width: 300px; 92 | margin: 20px; 93 | } 94 | 95 | .grid-element.project { 96 | margin-left: auto; 97 | margin-right: auto; 98 | } 99 | 100 | a:hover .grid-element.main { 101 | border: 1px black solid; 102 | margin: 19px; /* to compensote for push down of border */ 103 | } 104 | 105 | .grid-element.main { 106 | float: left; 107 | } 108 | 109 | .project-links { 110 | text-align: center; 111 | } 112 | 113 | .gitlet-introduction { 114 | width: 205px; 115 | height: 208px; 116 | font-family: "courier", "courier new"; 117 | font-size: 13px; 118 | margin-top: 100px; 119 | margin-left: auto; 120 | margin-right: auto; 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annotated-code", 3 | "description": "Short, heavily annotated JavaScript programs.", 4 | "author": "Mary Rose Cook (http://maryrosecook.com/)", 5 | "version": "0.1.0", 6 | 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/maryrosecook/annotated-code.git" 10 | }, 11 | 12 | "scripts": { 13 | "docs": "docco space-invaders/*.js --output space-invaders/docs; docco circles-bouncing-off-lines/*.js --output circles-bouncing-off-lines/docs" 14 | }, 15 | 16 | "devDependencies": { 17 | "docco": "0.6.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /space-invaders/docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "novecento-bold"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | 79 | hr { 80 | border: 0; 81 | background: 1px #ddd; 82 | height: 1px; 83 | margin: 20px 0; 84 | } 85 | 86 | pre, tt, code { 87 | font-size: 12px; line-height: 16px; 88 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 89 | margin: 0; padding: 0; 90 | } 91 | .annotation pre { 92 | display: block; 93 | margin: 0; 94 | padding: 7px 10px; 95 | background: #fcfcfc; 96 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 97 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 98 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 99 | overflow-x: auto; 100 | } 101 | .annotation pre code { 102 | border: 0; 103 | padding: 0; 104 | background: transparent; 105 | } 106 | 107 | 108 | blockquote { 109 | border-left: 5px solid #ccc; 110 | margin: 0; 111 | padding: 1px 0 1px 1em; 112 | } 113 | .sections blockquote p { 114 | font-family: Menlo, Consolas, Monaco, monospace; 115 | font-size: 12px; line-height: 16px; 116 | color: #999; 117 | margin: 10px 0 0; 118 | white-space: pre-wrap; 119 | } 120 | 121 | ul.sections { 122 | list-style: none; 123 | padding:0 0 5px 0;; 124 | margin:0; 125 | } 126 | 127 | /* 128 | Force border-box so that % widths fit the parent 129 | container without overlap because of margin/padding. 130 | 131 | More Info : http://www.quirksmode.org/css/box.html 132 | */ 133 | ul.sections > li > div { 134 | -moz-box-sizing: border-box; /* firefox */ 135 | -ms-box-sizing: border-box; /* ie */ 136 | -webkit-box-sizing: border-box; /* webkit */ 137 | -khtml-box-sizing: border-box; /* konqueror */ 138 | box-sizing: border-box; /* css3 */ 139 | } 140 | 141 | 142 | /*---------------------- Jump Page -----------------------------*/ 143 | #jump_to, #jump_page { 144 | margin: 0; 145 | background: white; 146 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 147 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 148 | font: 16px Arial; 149 | cursor: pointer; 150 | text-align: right; 151 | list-style: none; 152 | } 153 | 154 | #jump_to a { 155 | text-decoration: none; 156 | } 157 | 158 | #jump_to a.large { 159 | display: none; 160 | } 161 | #jump_to a.small { 162 | font-size: 22px; 163 | font-weight: bold; 164 | color: #676767; 165 | } 166 | 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 10px 15px; 171 | margin:0; 172 | } 173 | 174 | #jump_wrapper { 175 | display: none; 176 | padding:0; 177 | } 178 | 179 | #jump_to:hover #jump_wrapper { 180 | display: block; 181 | } 182 | 183 | #jump_page { 184 | padding: 5px 0 3px; 185 | margin: 0 0 25px 25px; 186 | } 187 | 188 | #jump_page .source { 189 | display: block; 190 | padding: 15px; 191 | text-decoration: none; 192 | border-top: 1px solid #eee; 193 | } 194 | 195 | #jump_page .source:hover { 196 | background: #f5f5ff; 197 | } 198 | 199 | #jump_page .source:first-child { 200 | } 201 | 202 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 203 | @media only screen and (min-width: 320px) { 204 | .pilwrap { display: none; } 205 | 206 | ul.sections > li > div { 207 | display: block; 208 | padding:5px 10px 0 10px; 209 | } 210 | 211 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 212 | padding-left: 30px; 213 | } 214 | 215 | ul.sections > li > div.content { 216 | overflow-x:auto; 217 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 218 | box-shadow: inset 0 0 5px #e5e5ee; 219 | border: 1px solid #dedede; 220 | margin:5px 10px 5px 10px; 221 | padding-bottom: 5px; 222 | } 223 | 224 | ul.sections > li > div.annotation pre { 225 | margin: 7px 0 7px; 226 | padding-left: 15px; 227 | } 228 | 229 | ul.sections > li > div.annotation p tt, .annotation code { 230 | background: #f8f8ff; 231 | border: 1px solid #dedede; 232 | font-size: 12px; 233 | padding: 0 0.2em; 234 | } 235 | } 236 | 237 | /*---------------------- (> 481px) ---------------------*/ 238 | @media only screen and (min-width: 481px) { 239 | #container { 240 | position: relative; 241 | } 242 | body { 243 | background-color: #F5F5FF; 244 | font-size: 15px; 245 | line-height: 21px; 246 | } 247 | pre, tt, code { 248 | line-height: 18px; 249 | } 250 | p, ul, ol { 251 | margin: 0 0 15px; 252 | } 253 | 254 | 255 | #jump_to { 256 | padding: 5px 10px; 257 | } 258 | #jump_wrapper { 259 | padding: 0; 260 | } 261 | #jump_to, #jump_page { 262 | font: 10px Arial; 263 | text-transform: uppercase; 264 | } 265 | #jump_page .source { 266 | padding: 5px 10px; 267 | } 268 | #jump_to a.large { 269 | display: inline-block; 270 | } 271 | #jump_to a.small { 272 | display: none; 273 | } 274 | 275 | 276 | 277 | #background { 278 | position: absolute; 279 | top: 0; bottom: 0; 280 | width: 350px; 281 | background: #fff; 282 | border-right: 1px solid #e5e5ee; 283 | z-index: -1; 284 | } 285 | 286 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 287 | padding-left: 40px; 288 | } 289 | 290 | ul.sections > li { 291 | white-space: nowrap; 292 | } 293 | 294 | ul.sections > li > div { 295 | display: inline-block; 296 | } 297 | 298 | ul.sections > li > div.annotation { 299 | max-width: 350px; 300 | min-width: 350px; 301 | min-height: 5px; 302 | padding: 13px; 303 | overflow-x: hidden; 304 | white-space: normal; 305 | vertical-align: top; 306 | text-align: left; 307 | } 308 | ul.sections > li > div.annotation pre { 309 | margin: 15px 0 15px; 310 | padding-left: 15px; 311 | } 312 | 313 | ul.sections > li > div.content { 314 | padding: 13px; 315 | vertical-align: top; 316 | border: none; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .pilwrap { 322 | position: relative; 323 | display: inline; 324 | } 325 | 326 | .pilcrow { 327 | font: 12px Arial; 328 | text-decoration: none; 329 | color: #454545; 330 | position: absolute; 331 | top: 3px; left: -20px; 332 | padding: 1px 2px; 333 | opacity: 0; 334 | -webkit-transition: opacity 0.2s linear; 335 | } 336 | .for-h1 .pilcrow { 337 | top: 47px; 338 | } 339 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 340 | top: 35px; 341 | } 342 | 343 | ul.sections > li > div.annotation:hover .pilcrow { 344 | opacity: 1; 345 | } 346 | } 347 | 348 | /*---------------------- (> 1025px) ---------------------*/ 349 | @media only screen and (min-width: 1025px) { 350 | 351 | body { 352 | font-size: 16px; 353 | line-height: 24px; 354 | } 355 | 356 | #background { 357 | width: 525px; 358 | } 359 | ul.sections > li > div.annotation { 360 | max-width: 525px; 361 | min-width: 525px; 362 | padding: 10px 25px 1px 50px; 363 | } 364 | ul.sections > li > div.content { 365 | padding: 9px 15px 16px 25px; 366 | } 367 | } 368 | 369 | /*---------------------- Syntax Highlighting -----------------------------*/ 370 | 371 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 372 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 373 | /* 374 | 375 | github.com style (c) Vasily Polovnyov 376 | 377 | */ 378 | 379 | pre code { 380 | display: block; padding: 0.5em; 381 | color: #000; 382 | background: #f8f8ff 383 | } 384 | 385 | pre .hljs-comment, 386 | pre .hljs-template_comment, 387 | pre .hljs-diff .hljs-header, 388 | pre .hljs-javadoc { 389 | color: #408080; 390 | font-style: italic 391 | } 392 | 393 | pre .hljs-keyword, 394 | pre .hljs-assignment, 395 | pre .hljs-literal, 396 | pre .hljs-css .hljs-rule .hljs-keyword, 397 | pre .hljs-winutils, 398 | pre .hljs-javascript .hljs-title, 399 | pre .hljs-lisp .hljs-title, 400 | pre .hljs-subst { 401 | color: #954121; 402 | /*font-weight: bold*/ 403 | } 404 | 405 | pre .hljs-number, 406 | pre .hljs-hexcolor { 407 | color: #40a070 408 | } 409 | 410 | pre .hljs-string, 411 | pre .hljs-tag .hljs-value, 412 | pre .hljs-phpdoc, 413 | pre .hljs-tex .hljs-formula { 414 | color: #219161; 415 | } 416 | 417 | pre .hljs-title, 418 | pre .hljs-id { 419 | color: #19469D; 420 | } 421 | pre .hljs-params { 422 | color: #00F; 423 | } 424 | 425 | pre .hljs-javascript .hljs-title, 426 | pre .hljs-lisp .hljs-title, 427 | pre .hljs-subst { 428 | font-weight: normal 429 | } 430 | 431 | pre .hljs-class .hljs-title, 432 | pre .hljs-haskell .hljs-label, 433 | pre .hljs-tex .hljs-command { 434 | color: #458; 435 | font-weight: bold 436 | } 437 | 438 | pre .hljs-tag, 439 | pre .hljs-tag .hljs-title, 440 | pre .hljs-rules .hljs-property, 441 | pre .hljs-django .hljs-tag .hljs-keyword { 442 | color: #000080; 443 | font-weight: normal 444 | } 445 | 446 | pre .hljs-attribute, 447 | pre .hljs-variable, 448 | pre .hljs-instancevar, 449 | pre .hljs-lisp .hljs-body { 450 | color: #008080 451 | } 452 | 453 | pre .hljs-regexp { 454 | color: #B68 455 | } 456 | 457 | pre .hljs-class { 458 | color: #458; 459 | font-weight: bold 460 | } 461 | 462 | pre .hljs-symbol, 463 | pre .hljs-ruby .hljs-symbol .hljs-string, 464 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 465 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 466 | pre .hljs-lisp .hljs-keyword, 467 | pre .hljs-tex .hljs-special, 468 | pre .hljs-input_number { 469 | color: #990073 470 | } 471 | 472 | pre .hljs-builtin, 473 | pre .hljs-constructor, 474 | pre .hljs-built_in, 475 | pre .hljs-lisp .hljs-title { 476 | color: #0086b3 477 | } 478 | 479 | pre .hljs-preprocessor, 480 | pre .hljs-pi, 481 | pre .hljs-doctype, 482 | pre .hljs-shebang, 483 | pre .hljs-cdata { 484 | color: #999; 485 | font-weight: bold 486 | } 487 | 488 | pre .hljs-deletion { 489 | background: #fdd 490 | } 491 | 492 | pre .hljs-addition { 493 | background: #dfd 494 | } 495 | 496 | pre .hljs-diff .hljs-change { 497 | background: #0086b3 498 | } 499 | 500 | pre .hljs-chunk { 501 | color: #aaa 502 | } 503 | 504 | pre .hljs-tex .hljs-formula { 505 | opacity: 0.5; 506 | } 507 | -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /space-invaders/docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /space-invaders/docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /space-invaders/docs/space-invaders.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | space-invaders.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
    15 | 16 |
  • 17 |
    18 |

    space-invaders.js

    19 |
    20 |
  • 21 | 22 | 23 | 24 |
  • 25 |
    26 | 27 |
    28 | 29 |
    30 | 31 |
    32 | 33 |
    ;(function() {
    34 | 35 |
  • 36 | 37 | 38 |
  • 39 |
    40 | 41 |
    42 | 43 |
    44 |

    Main game object

    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 |

    new Game() Creates the game object with the game state and logic.

    70 | 71 |
    72 | 73 |
      var Game = function() {
    74 | 75 |
  • 76 | 77 | 78 |
  • 79 |
    80 | 81 |
    82 | 83 |
    84 |

    In index.html, there is a canvas tag that the game will be drawn in. 85 | Grab that canvas out of the DOM.

    86 | 87 |
    88 | 89 |
        var canvas = document.getElementById("space-invaders");
    90 | 91 |
  • 92 | 93 | 94 |
  • 95 |
    96 | 97 |
    98 | 99 |
    100 |

    Get the drawing context. This contains functions that let you draw to the canvas.

    101 | 102 |
    103 | 104 |
        var screen = canvas.getContext('2d');
    105 | 106 |
  • 107 | 108 | 109 |
  • 110 |
    111 | 112 |
    113 | 114 |
    115 |

    Note down the dimensions of the canvas. These are used to 116 | place game bodies.

    117 | 118 |
    119 | 120 |
        var gameSize = { x: canvas.width, y: canvas.height };
    121 | 122 |
  • 123 | 124 | 125 |
  • 126 |
    127 | 128 |
    129 | 130 |
    131 |

    Create the bodies array to hold the player, invaders and bullets.

    132 | 133 |
    134 | 135 |
        this.bodies = [];
    136 | 137 |
  • 138 | 139 | 140 |
  • 141 |
    142 | 143 |
    144 | 145 |
    146 |

    Add the invaders to the bodies array.

    147 | 148 |
    149 | 150 |
        this.bodies = this.bodies.concat(createInvaders(this));
    151 | 152 |
  • 153 | 154 | 155 |
  • 156 |
    157 | 158 |
    159 | 160 |
    161 |

    Add the player to the bodies array.

    162 | 163 |
    164 | 165 |
        this.bodies = this.bodies.concat(new Player(this, gameSize));
    166 | 167 |
  • 168 | 169 | 170 |
  • 171 |
    172 | 173 |
    174 | 175 |
    176 |

    In index.html, there is an audio tag that loads the shooting sound. 177 | Get the shoot sound from the DOM and store it on the game object.

    178 | 179 |
    180 | 181 |
        this.shootSound = document.getElementById('shoot-sound');
     182 | 
     183 |     var self = this;
    184 | 185 |
  • 186 | 187 | 188 |
  • 189 |
    190 | 191 |
    192 | 193 |
    194 |

    Main game tick function. Loops forever, running 60ish times a second.

    195 | 196 |
    197 | 198 |
        var tick = function() {
    199 | 200 |
  • 201 | 202 | 203 |
  • 204 |
    205 | 206 |
    207 | 208 |
    209 |

    Update game state.

    210 | 211 |
    212 | 213 |
          self.update();
    214 | 215 |
  • 216 | 217 | 218 |
  • 219 |
    220 | 221 |
    222 | 223 |
    224 |

    Draw game bodies.

    225 | 226 |
    227 | 228 |
          self.draw(screen, gameSize);
    229 | 230 |
  • 231 | 232 | 233 |
  • 234 |
    235 | 236 |
    237 | 238 |
    239 |

    Queue up the next call to tick with the browser.

    240 | 241 |
    242 | 243 |
          requestAnimationFrame(tick);
     244 |     };
    245 | 246 |
  • 247 | 248 | 249 |
  • 250 |
    251 | 252 |
    253 | 254 |
    255 |

    Run the first game tick. All future calls will be scheduled by 256 | the tick() function itself.

    257 | 258 |
    259 | 260 |
        tick();
     261 |   };
     262 | 
     263 |   Game.prototype = {
    264 | 265 |
  • 266 | 267 | 268 |
  • 269 |
    270 | 271 |
    272 | 273 |
    274 |

    update() runs the main game logic.

    275 | 276 |
    277 | 278 |
        update: function() {
     279 |       var self = this;
    280 | 281 |
  • 282 | 283 | 284 |
  • 285 |
    286 | 287 |
    288 | 289 |
    290 |

    notCollidingWithAnything returns true if passed body 291 | is not colliding with anything.

    292 | 293 |
    294 | 295 |
          var notCollidingWithAnything = function(b1) {
     296 |         return self.bodies.filter(function(b2) { return colliding(b1, b2); }).length === 0;
     297 |       };
    298 | 299 |
  • 300 | 301 | 302 |
  • 303 |
    304 | 305 |
    306 | 307 |
    308 |

    Throw away bodies that are colliding with something. They 309 | will never be updated or draw again.

    310 | 311 |
    312 | 313 |
          this.bodies = this.bodies.filter(notCollidingWithAnything);
    314 | 315 |
  • 316 | 317 | 318 |
  • 319 |
    320 | 321 |
    322 | 323 |
    324 |

    Call update on every body.

    325 | 326 |
    327 | 328 |
          for (var i = 0; i < this.bodies.length; i++) {
     329 |         this.bodies[i].update();
     330 |       }
     331 |     },
    332 | 333 |
  • 334 | 335 | 336 |
  • 337 |
    338 | 339 |
    340 | 341 |
    342 |

    draw() draws the game.

    343 | 344 |
    345 | 346 |
        draw: function(screen, gameSize) {
    347 | 348 |
  • 349 | 350 | 351 |
  • 352 |
    353 | 354 |
    355 | 356 |
    357 |

    Clear away the drawing from the previous tick.

    358 | 359 |
    360 | 361 |
          screen.clearRect(0, 0, gameSize.x, gameSize.y);
    362 | 363 |
  • 364 | 365 | 366 |
  • 367 |
    368 | 369 |
    370 | 371 |
    372 |

    Draw each body as a rectangle.

    373 | 374 |
    375 | 376 |
          for (var i = 0; i < this.bodies.length; i++) {
     377 |         drawRect(screen, this.bodies[i]);
     378 |       }
     379 |     },
    380 | 381 |
  • 382 | 383 | 384 |
  • 385 |
    386 | 387 |
    388 | 389 |
    390 |

    invadersBelow() returns true if invader is directly 391 | above at least one other invader.

    392 | 393 |
    394 | 395 |
        invadersBelow: function(invader) {
    396 | 397 |
  • 398 | 399 | 400 |
  • 401 |
    402 | 403 |
    404 | 405 |
    406 |

    If filtered array is not empty, there are invaders below.

    407 | 408 |
    409 | 410 |
          return this.bodies.filter(function(b) {
    411 | 412 |
  • 413 | 414 | 415 |
  • 416 |
    417 | 418 |
    419 | 420 |
    421 |

    Keep b if it is an invader, if it is in the same column 422 | as invader, and if it is somewhere below invader.

    423 | 424 |
    425 | 426 |
            return b instanceof Invader &&
     427 |           Math.abs(invader.center.x - b.center.x) < b.size.x &&
     428 |           b.center.y > invader.center.y;
     429 |       }).length > 0;
     430 |     },
    431 | 432 |
  • 433 | 434 | 435 |
  • 436 |
    437 | 438 |
    439 | 440 |
    441 |

    addBody() adds a body to the bodies array.

    442 | 443 |
    444 | 445 |
        addBody: function(body) {
     446 |       this.bodies.push(body);
     447 |     }
     448 |   };
    449 | 450 |
  • 451 | 452 | 453 |
  • 454 |
    455 | 456 |
    457 | 458 |
    459 |

    Invaders

    460 | 461 |
    462 | 463 |
  • 464 | 465 | 466 |
  • 467 |
    468 | 469 |
    470 | 471 |
    472 | 473 |
    474 | 475 |
  • 476 | 477 | 478 |
  • 479 |
    480 | 481 |
    482 | 483 |
    484 |

    new Invader() creates an invader.

    485 | 486 |
    487 | 488 |
      var Invader = function(game, center) {
     489 |     this.game = game;
     490 |     this.center = center;
     491 |     this.size = { x: 15, y: 15 };
    492 | 493 |
  • 494 | 495 | 496 |
  • 497 |
    498 | 499 |
    500 | 501 |
    502 |

    Invaders patrol from left to right and back again. 503 | this.patrolX records the current (relative) position of the 504 | invader in their patrol. It starts at 0, increases to 40, then 505 | decreases to 0, and so forth.

    506 | 507 |
    508 | 509 |
        this.patrolX = 0;
    510 | 511 |
  • 512 | 513 | 514 |
  • 515 |
    516 | 517 |
    518 | 519 |
    520 |

    The x speed of the invader. A positive value moves the invader 521 | right. A negative value moves it left.

    522 | 523 |
    524 | 525 |
        this.speedX = 0.3;
     526 |   };
     527 | 
     528 |   Invader.prototype = {
    529 | 530 |
  • 531 | 532 | 533 |
  • 534 |
    535 | 536 |
    537 | 538 |
    539 |

    update() updates the state of the invader for a single tick.

    540 | 541 |
    542 | 543 |
        update: function() {
    544 | 545 |
  • 546 | 547 | 548 |
  • 549 |
    550 | 551 |
    552 | 553 |
    554 |

    If the invader is outside the bounds of their patrol…

    555 | 556 |
    557 | 558 |
          if (this.patrolX < 0 || this.patrolX > 30) {
    559 | 560 |
  • 561 | 562 | 563 |
  • 564 |
    565 | 566 |
    567 | 568 |
    569 |

    … reverse direction of movement.

    570 | 571 |
    572 | 573 |
            this.speedX = -this.speedX;
     574 |       }
    575 | 576 |
  • 577 | 578 | 579 |
  • 580 |
    581 | 582 |
    583 | 584 |
    585 |

    If coin flip comes up and no friends below in this 586 | invader’s column…

    587 | 588 |
    589 | 590 |
          if (Math.random() > 0.995 &&
     591 |           !this.game.invadersBelow(this)) {
    592 | 593 |
  • 594 | 595 | 596 |
  • 597 |
    598 | 599 |
    600 | 601 |
    602 |

    … create a bullet just below the invader that will move 603 | downward…

    604 | 605 |
    606 | 607 |
            var bullet = new Bullet({ x: this.center.x, y: this.center.y + this.size.y / 2 },
     608 |                                 { x: Math.random() - 0.5, y: 2 });
    609 | 610 |
  • 611 | 612 | 613 |
  • 614 |
    615 | 616 |
    617 | 618 |
    619 |

    … and add the bullet to the game.

    620 | 621 |
    622 | 623 |
            this.game.addBody(bullet);
     624 |       }
    625 | 626 |
  • 627 | 628 | 629 |
  • 630 |
    631 | 632 |
    633 | 634 |
    635 |

    Move according to current x speed.

    636 | 637 |
    638 | 639 |
          this.center.x += this.speedX;
    640 | 641 |
  • 642 | 643 | 644 |
  • 645 |
    646 | 647 |
    648 | 649 |
    650 |

    Update variable that keeps track of current position in patrol.

    651 | 652 |
    653 | 654 |
          this.patrolX += this.speedX;
     655 |     }
     656 |   };
    657 | 658 |
  • 659 | 660 | 661 |
  • 662 |
    663 | 664 |
    665 | 666 |
    667 |

    createInvaders() returns an array of twenty-four invaders.

    668 | 669 |
    670 | 671 |
      var createInvaders = function(game) {
     672 |     var invaders = [];
     673 |     for (var i = 0; i < 24; i++) {
    674 | 675 |
  • 676 | 677 | 678 |
  • 679 |
    680 | 681 |
    682 | 683 |
    684 |

    Place invaders in eight columns.

    685 | 686 |
    687 | 688 |
          var x = 30 + (i % 8) * 30;
    689 | 690 |
  • 691 | 692 | 693 |
  • 694 |
    695 | 696 |
    697 | 698 |
    699 |

    Place invaders in three rows.

    700 | 701 |
    702 | 703 |
          var y = 30 + (i % 3) * 30;
    704 | 705 |
  • 706 | 707 | 708 |
  • 709 |
    710 | 711 |
    712 | 713 |
    714 |

    Create invader.

    715 | 716 |
    717 | 718 |
          invaders.push(new Invader(game, { x: x, y: y}));
     719 |     }
     720 | 
     721 |     return invaders;
     722 |   };
    723 | 724 |
  • 725 | 726 | 727 |
  • 728 |
    729 | 730 |
    731 | 732 |
    733 |

    Player

    734 | 735 |
    736 | 737 |
  • 738 | 739 | 740 |
  • 741 |
    742 | 743 |
    744 | 745 |
    746 | 747 |
    748 | 749 |
  • 750 | 751 | 752 |
  • 753 |
    754 | 755 |
    756 | 757 |
    758 |

    new Player() creates a player.

    759 | 760 |
    761 | 762 |
      var Player = function(game, gameSize) {
     763 |     this.game = game;
     764 |     this.size = { x: 15, y: 15 };
     765 |     this.center = { x: gameSize.x / 2, y: gameSize.y - this.size.y * 2 };
    766 | 767 |
  • 768 | 769 | 770 |
  • 771 |
    772 | 773 |
    774 | 775 |
    776 |

    Create a keyboard object to track button presses.

    777 | 778 |
    779 | 780 |
        this.keyboarder = new Keyboarder();
     781 |   };
     782 | 
     783 |   Player.prototype = {
    784 | 785 |
  • 786 | 787 | 788 |
  • 789 |
    790 | 791 |
    792 | 793 |
    794 |

    update() updates the state of the player for a single tick.

    795 | 796 |
    797 | 798 |
        update: function() {
    799 | 800 |
  • 801 | 802 | 803 |
  • 804 |
    805 | 806 |
    807 | 808 |
    809 |

    If left cursor key is down…

    810 | 811 |
    812 | 813 |
          if (this.keyboarder.isDown(this.keyboarder.KEYS.LEFT)) {
    814 | 815 |
  • 816 | 817 | 818 |
  • 819 |
    820 | 821 |
    822 | 823 |
    824 |

    … move left.

    825 | 826 |
    827 | 828 |
            this.center.x -= 2;
     829 | 
     830 |       } else if (this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT)) {
     831 |         this.center.x += 2;
     832 |       }
    833 | 834 |
  • 835 | 836 | 837 |
  • 838 |
    839 | 840 |
    841 | 842 |
    843 |

    If S key is down…

    844 | 845 |
    846 | 847 |
          if (this.keyboarder.isDown(this.keyboarder.KEYS.S)) {
    848 | 849 |
  • 850 | 851 | 852 |
  • 853 |
    854 | 855 |
    856 | 857 |
    858 |

    … create a bullet just above the player that will move upwards…

    859 | 860 |
    861 | 862 |
            var bullet = new Bullet({ x: this.center.x, y: this.center.y - this.size.y - 10 },
     863 |                                 { x: 0, y: -7 });
    864 | 865 |
  • 866 | 867 | 868 |
  • 869 |
    870 | 871 |
    872 | 873 |
    874 |

    … add the bullet to the game…

    875 | 876 |
    877 | 878 |
            this.game.addBody(bullet);
    879 | 880 |
  • 881 | 882 | 883 |
  • 884 |
    885 | 886 |
    887 | 888 |
    889 |

    … rewind the shoot sound…

    890 | 891 |
    892 | 893 |
            this.game.shootSound.load();
    894 | 895 |
  • 896 | 897 | 898 |
  • 899 |
    900 | 901 |
    902 | 903 |
    904 |

    … and play the shoot sound.

    905 | 906 |
    907 | 908 |
            this.game.shootSound.play();
     909 |       }
     910 |     }
     911 |   };
    912 | 913 |
  • 914 | 915 | 916 |
  • 917 |
    918 | 919 |
    920 | 921 |
    922 |

    Bullet

    923 | 924 |
    925 | 926 |
  • 927 | 928 | 929 |
  • 930 |
    931 | 932 |
    933 | 934 |
    935 | 936 |
    937 | 938 |
  • 939 | 940 | 941 |
  • 942 |
    943 | 944 |
    945 | 946 |
    947 |

    new Bullet() creates a new bullet.

    948 | 949 |
    950 | 951 |
      var Bullet = function(center, velocity) {
     952 |     this.center = center;
     953 |     this.size = { x: 3, y: 3 };
     954 |     this.velocity = velocity;
     955 |   };
     956 | 
     957 |   Bullet.prototype = {
    958 | 959 |
  • 960 | 961 | 962 |
  • 963 |
    964 | 965 |
    966 | 967 |
    968 |

    update() updates the state of the bullet for a single tick.

    969 | 970 |
    971 | 972 |
        update: function() {
    973 | 974 |
  • 975 | 976 | 977 |
  • 978 |
    979 | 980 |
    981 | 982 |
    983 |

    Add velocity to center to move bullet.

    984 | 985 |
    986 | 987 |
          this.center.x += this.velocity.x;
     988 |       this.center.y += this.velocity.y;
     989 |     }
     990 |   };
    991 | 992 |
  • 993 | 994 | 995 |
  • 996 |
    997 | 998 |
    999 | 1000 |
    1001 |

    Keyboard input tracking

    1002 | 1003 |
    1004 | 1005 |
  • 1006 | 1007 | 1008 |
  • 1009 |
    1010 | 1011 |
    1012 | 1013 |
    1014 | 1015 |
    1016 | 1017 |
  • 1018 | 1019 | 1020 |
  • 1021 |
    1022 | 1023 |
    1024 | 1025 |
    1026 |

    new Keyboarder() creates a new keyboard input tracking object.

    1027 | 1028 |
    1029 | 1030 |
      var Keyboarder = function() {
    1031 | 1032 |
  • 1033 | 1034 | 1035 |
  • 1036 |
    1037 | 1038 |
    1039 | 1040 |
    1041 |

    Records up/down state of each key that has ever been pressed.

    1042 | 1043 |
    1044 | 1045 |
        var keyState = {};
    1046 | 1047 |
  • 1048 | 1049 | 1050 |
  • 1051 |
    1052 | 1053 |
    1054 | 1055 |
    1056 |

    When key goes down, record that it is down.

    1057 | 1058 |
    1059 | 1060 |
        window.addEventListener('keydown', function(e) {
    1061 |       keyState[e.keyCode] = true;
    1062 |     });
    1063 | 1064 |
  • 1065 | 1066 | 1067 |
  • 1068 |
    1069 | 1070 |
    1071 | 1072 |
    1073 |

    When key goes up, record that it is up.

    1074 | 1075 |
    1076 | 1077 |
        window.addEventListener('keyup', function(e) {
    1078 |       keyState[e.keyCode] = false;
    1079 |     });
    1080 | 1081 |
  • 1082 | 1083 | 1084 |
  • 1085 |
    1086 | 1087 |
    1088 | 1089 |
    1090 |

    Returns true if passed key is currently down. keyCode is a 1091 | unique number that represents a particular key on the keyboard.

    1092 | 1093 |
    1094 | 1095 |
        this.isDown = function(keyCode) {
    1096 |       return keyState[keyCode] === true;
    1097 |     };
    1098 | 1099 |
  • 1100 | 1101 | 1102 |
  • 1103 |
    1104 | 1105 |
    1106 | 1107 |
    1108 |

    Handy constants that give keyCodes human-readable names.

    1109 | 1110 |
    1111 | 1112 |
        this.KEYS = { LEFT: 37, RIGHT: 39, S: 83 };
    1113 |   };
    1114 | 1115 |
  • 1116 | 1117 | 1118 |
  • 1119 |
    1120 | 1121 |
    1122 | 1123 |
    1124 |

    Other functions

    1125 | 1126 |
    1127 | 1128 |
  • 1129 | 1130 | 1131 |
  • 1132 |
    1133 | 1134 |
    1135 | 1136 |
    1137 | 1138 |
    1139 | 1140 |
  • 1141 | 1142 | 1143 |
  • 1144 |
    1145 | 1146 |
    1147 | 1148 |
    1149 |

    drawRect() draws passed body as a rectangle to screen, the drawing context.

    1150 | 1151 |
    1152 | 1153 |
      var drawRect = function(screen, body) {
    1154 |     screen.fillRect(body.center.x - body.size.x / 2, body.center.y - body.size.y / 2,
    1155 |                     body.size.x, body.size.y);
    1156 |   };
    1157 | 1158 |
  • 1159 | 1160 | 1161 |
  • 1162 |
    1163 | 1164 |
    1165 | 1166 |
    1167 |

    colliding() returns true if two passed bodies are colliding. 1168 | The approach is to test for five situations. If any are true, 1169 | the bodies are definitely not colliding. If none of them 1170 | are true, the bodies are colliding.

    1171 |
      1172 |
    1. b1 is the same body as b2.
    2. 1173 |
    3. Right of b1 is to the left of the left of b2.
    4. 1174 |
    5. Bottom of b1 is above the top of b2.
    6. 1175 |
    7. Left of b1 is to the right of the right of b2.
    8. 1176 |
    9. Top of b1 is below the bottom of b2.
    10. 1177 |
    1178 | 1179 |
    1180 | 1181 |
      var colliding = function(b1, b2) {
    1182 |     return !(
    1183 |       b1 === b2 ||
    1184 |         b1.center.x + b1.size.x / 2 < b2.center.x - b2.size.x / 2 ||
    1185 |         b1.center.y + b1.size.y / 2 < b2.center.y - b2.size.y / 2 ||
    1186 |         b1.center.x - b1.size.x / 2 > b2.center.x + b2.size.x / 2 ||
    1187 |         b1.center.y - b1.size.y / 2 > b2.center.y + b2.size.y / 2
    1188 |     );
    1189 |   };
    1190 | 1191 |
  • 1192 | 1193 | 1194 |
  • 1195 |
    1196 | 1197 |
    1198 | 1199 |
    1200 |

    Start game

    1201 | 1202 |
    1203 | 1204 |
  • 1205 | 1206 | 1207 |
  • 1208 |
    1209 | 1210 |
    1211 | 1212 |
    1213 | 1214 |
    1215 | 1216 |
  • 1217 | 1218 | 1219 |
  • 1220 |
    1221 | 1222 |
    1223 | 1224 |
    1225 |

    When the DOM is ready, create (and start) the game.

    1226 | 1227 |
    1228 | 1229 |
      window.addEventListener('load', function() {
    1230 |     new Game();
    1231 |   });
    1232 | })();
    1233 | 1234 |
  • 1235 | 1236 |
1237 |
1238 | 1239 | 1240 | -------------------------------------------------------------------------------- /space-invaders/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | Space Invaders - Annotated code 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 |
33 | 34 | 35 |

Space Invaders

36 | 40 |
41 | 42 |
43 |

44 | Move with the arrow keys. Shoot with the S key. Refresh to play again. 45 |

46 | 47 |

48 | Demonstrates a game loop, collision detection, keyboard input, 49 | drawing to the canvas and playing sounds. 50 |

51 |
52 | 53 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /space-invaders/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/screenshot.gif -------------------------------------------------------------------------------- /space-invaders/shoot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maryrosecook/annotated-code/1c52010318426be81ec741b300a3107e13f8d131/space-invaders/shoot.mp3 -------------------------------------------------------------------------------- /space-invaders/space-invaders.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | 3 | // Main game object 4 | // ---------------- 5 | 6 | // **new Game()** Creates the game object with the game state and logic. 7 | var Game = function() { 8 | 9 | // In index.html, there is a canvas tag that the game will be drawn in. 10 | // Grab that canvas out of the DOM. 11 | var canvas = document.getElementById("space-invaders"); 12 | 13 | // Get the drawing context. This contains functions that let you draw to the canvas. 14 | var screen = canvas.getContext('2d'); 15 | 16 | // Note down the dimensions of the canvas. These are used to 17 | // place game bodies. 18 | var gameSize = { x: canvas.width, y: canvas.height }; 19 | 20 | // Create the bodies array to hold the player, invaders and bullets. 21 | this.bodies = []; 22 | 23 | // Add the invaders to the bodies array. 24 | this.bodies = this.bodies.concat(createInvaders(this)); 25 | 26 | // Add the player to the bodies array. 27 | this.bodies = this.bodies.concat(new Player(this, gameSize)); 28 | 29 | // In index.html, there is an audio tag that loads the shooting sound. 30 | // Get the shoot sound from the DOM and store it on the game object. 31 | this.shootSound = document.getElementById('shoot-sound'); 32 | 33 | var self = this; 34 | 35 | // Main game tick function. Loops forever, running 60ish times a second. 36 | var tick = function() { 37 | 38 | // Update game state. 39 | self.update(); 40 | 41 | // Draw game bodies. 42 | self.draw(screen, gameSize); 43 | 44 | // Queue up the next call to tick with the browser. 45 | requestAnimationFrame(tick); 46 | }; 47 | 48 | // Run the first game tick. All future calls will be scheduled by 49 | // the tick() function itself. 50 | tick(); 51 | }; 52 | 53 | Game.prototype = { 54 | 55 | // **update()** runs the main game logic. 56 | update: function() { 57 | var self = this; 58 | 59 | // `notCollidingWithAnything` returns true if passed body 60 | // is not colliding with anything. 61 | var notCollidingWithAnything = function(b1) { 62 | return self.bodies.filter(function(b2) { return colliding(b1, b2); }).length === 0; 63 | }; 64 | 65 | // Throw away bodies that are colliding with something. They 66 | // will never be updated or draw again. 67 | this.bodies = this.bodies.filter(notCollidingWithAnything); 68 | 69 | // Call update on every body. 70 | for (var i = 0; i < this.bodies.length; i++) { 71 | this.bodies[i].update(); 72 | } 73 | }, 74 | 75 | // **draw()** draws the game. 76 | draw: function(screen, gameSize) { 77 | // Clear away the drawing from the previous tick. 78 | screen.clearRect(0, 0, gameSize.x, gameSize.y); 79 | 80 | // Draw each body as a rectangle. 81 | for (var i = 0; i < this.bodies.length; i++) { 82 | drawRect(screen, this.bodies[i]); 83 | } 84 | }, 85 | 86 | // **invadersBelow()** returns true if `invader` is directly 87 | // above at least one other invader. 88 | invadersBelow: function(invader) { 89 | // If filtered array is not empty, there are invaders below. 90 | return this.bodies.filter(function(b) { 91 | // Keep `b` if it is an invader, if it is in the same column 92 | // as `invader`, and if it is somewhere below `invader`. 93 | return b instanceof Invader && 94 | Math.abs(invader.center.x - b.center.x) < b.size.x && 95 | b.center.y > invader.center.y; 96 | }).length > 0; 97 | }, 98 | 99 | // **addBody()** adds a body to the bodies array. 100 | addBody: function(body) { 101 | this.bodies.push(body); 102 | } 103 | }; 104 | 105 | // Invaders 106 | // -------- 107 | 108 | // **new Invader()** creates an invader. 109 | var Invader = function(game, center) { 110 | this.game = game; 111 | this.center = center; 112 | this.size = { x: 15, y: 15 }; 113 | 114 | // Invaders patrol from left to right and back again. 115 | // `this.patrolX` records the current (relative) position of the 116 | // invader in their patrol. It starts at 0, increases to 40, then 117 | // decreases to 0, and so forth. 118 | this.patrolX = 0; 119 | 120 | // The x speed of the invader. A positive value moves the invader 121 | // right. A negative value moves it left. 122 | this.speedX = 0.3; 123 | }; 124 | 125 | Invader.prototype = { 126 | 127 | // **update()** updates the state of the invader for a single tick. 128 | update: function() { 129 | 130 | // If the invader is outside the bounds of their patrol... 131 | if (this.patrolX < 0 || this.patrolX > 30) { 132 | 133 | // ... reverse direction of movement. 134 | this.speedX = -this.speedX; 135 | } 136 | 137 | // If coin flip comes up and no friends below in this 138 | // invader's column... 139 | if (Math.random() > 0.995 && 140 | !this.game.invadersBelow(this)) { 141 | 142 | // ... create a bullet just below the invader that will move 143 | // downward... 144 | var bullet = new Bullet({ x: this.center.x, y: this.center.y + this.size.y / 2 }, 145 | { x: Math.random() - 0.5, y: 2 }); 146 | 147 | // ... and add the bullet to the game. 148 | this.game.addBody(bullet); 149 | } 150 | 151 | // Move according to current x speed. 152 | this.center.x += this.speedX; 153 | 154 | // Update variable that keeps track of current position in patrol. 155 | this.patrolX += this.speedX; 156 | } 157 | }; 158 | 159 | // **createInvaders()** returns an array of twenty-four invaders. 160 | var createInvaders = function(game) { 161 | var invaders = []; 162 | for (var i = 0; i < 24; i++) { 163 | 164 | // Place invaders in eight columns. 165 | var x = 30 + (i % 8) * 30; 166 | 167 | // Place invaders in three rows. 168 | var y = 30 + (i % 3) * 30; 169 | 170 | // Create invader. 171 | invaders.push(new Invader(game, { x: x, y: y})); 172 | } 173 | 174 | return invaders; 175 | }; 176 | 177 | // Player 178 | // ------ 179 | 180 | // **new Player()** creates a player. 181 | var Player = function(game, gameSize) { 182 | this.game = game; 183 | this.size = { x: 15, y: 15 }; 184 | this.center = { x: gameSize.x / 2, y: gameSize.y - this.size.y * 2 }; 185 | 186 | // Create a keyboard object to track button presses. 187 | this.keyboarder = new Keyboarder(); 188 | }; 189 | 190 | Player.prototype = { 191 | 192 | // **update()** updates the state of the player for a single tick. 193 | update: function() { 194 | // If left cursor key is down... 195 | if (this.keyboarder.isDown(this.keyboarder.KEYS.LEFT)) { 196 | 197 | // ... move left. 198 | this.center.x -= 2; 199 | 200 | } else if (this.keyboarder.isDown(this.keyboarder.KEYS.RIGHT)) { 201 | this.center.x += 2; 202 | } 203 | 204 | // If S key is down... 205 | if (this.keyboarder.isDown(this.keyboarder.KEYS.S)) { 206 | // ... create a bullet just above the player that will move upwards... 207 | var bullet = new Bullet({ x: this.center.x, y: this.center.y - this.size.y - 10 }, 208 | { x: 0, y: -7 }); 209 | 210 | // ... add the bullet to the game... 211 | this.game.addBody(bullet); 212 | 213 | // ... rewind the shoot sound... 214 | this.game.shootSound.load(); 215 | 216 | // ... and play the shoot sound. 217 | this.game.shootSound.play(); 218 | } 219 | } 220 | }; 221 | 222 | // Bullet 223 | // ------ 224 | 225 | // **new Bullet()** creates a new bullet. 226 | var Bullet = function(center, velocity) { 227 | this.center = center; 228 | this.size = { x: 3, y: 3 }; 229 | this.velocity = velocity; 230 | }; 231 | 232 | Bullet.prototype = { 233 | 234 | // **update()** updates the state of the bullet for a single tick. 235 | update: function() { 236 | 237 | // Add velocity to center to move bullet. 238 | this.center.x += this.velocity.x; 239 | this.center.y += this.velocity.y; 240 | } 241 | }; 242 | 243 | // Keyboard input tracking 244 | // ----------------------- 245 | 246 | // **new Keyboarder()** creates a new keyboard input tracking object. 247 | var Keyboarder = function() { 248 | 249 | // Records up/down state of each key that has ever been pressed. 250 | var keyState = {}; 251 | 252 | // When key goes down, record that it is down. 253 | window.addEventListener('keydown', function(e) { 254 | keyState[e.keyCode] = true; 255 | }); 256 | 257 | // When key goes up, record that it is up. 258 | window.addEventListener('keyup', function(e) { 259 | keyState[e.keyCode] = false; 260 | }); 261 | 262 | // Returns true if passed key is currently down. `keyCode` is a 263 | // unique number that represents a particular key on the keyboard. 264 | this.isDown = function(keyCode) { 265 | return keyState[keyCode] === true; 266 | }; 267 | 268 | // Handy constants that give keyCodes human-readable names. 269 | this.KEYS = { LEFT: 37, RIGHT: 39, S: 83 }; 270 | }; 271 | 272 | // Other functions 273 | // --------------- 274 | 275 | // **drawRect()** draws passed body as a rectangle to `screen`, the drawing context. 276 | var drawRect = function(screen, body) { 277 | screen.fillRect(body.center.x - body.size.x / 2, body.center.y - body.size.y / 2, 278 | body.size.x, body.size.y); 279 | }; 280 | 281 | // **colliding()** returns true if two passed bodies are colliding. 282 | // The approach is to test for five situations. If any are true, 283 | // the bodies are definitely not colliding. If none of them 284 | // are true, the bodies are colliding. 285 | // 1. b1 is the same body as b2. 286 | // 2. Right of `b1` is to the left of the left of `b2`. 287 | // 3. Bottom of `b1` is above the top of `b2`. 288 | // 4. Left of `b1` is to the right of the right of `b2`. 289 | // 5. Top of `b1` is below the bottom of `b2`. 290 | var colliding = function(b1, b2) { 291 | return !( 292 | b1 === b2 || 293 | b1.center.x + b1.size.x / 2 < b2.center.x - b2.size.x / 2 || 294 | b1.center.y + b1.size.y / 2 < b2.center.y - b2.size.y / 2 || 295 | b1.center.x - b1.size.x / 2 > b2.center.x + b2.size.x / 2 || 296 | b1.center.y - b1.size.y / 2 > b2.center.y + b2.size.y / 2 297 | ); 298 | }; 299 | 300 | // Start game 301 | // ---------- 302 | 303 | // When the DOM is ready, create (and start) the game. 304 | window.addEventListener('load', function() { 305 | new Game(); 306 | }); 307 | })(); 308 | --------------------------------------------------------------------------------