# Khan/khan-exercises

Factor out and document odd-shape-generating code

1 parent d518edd commit cfa53885132fff27b2b04f0b5fb95b885b2f2e9d beneater committed Jun 15, 2012
Showing with 187 additions and 190 deletions.
1. +11 −97 exercises/area_1.html
2. +37 −93 exercises/perimeter_1.html
3. +139 −0 utils/graphie-geometry.js
108 exercises/area_1.html
 @@ -1,5 +1,5 @@ - + Area 1 @@ -159,96 +159,13 @@
randRange(5, 10) randRange(5, 10) - - (function() { - var sides = []; - var squares = []; - - var left = randRange(0, WIDTH - 1); - var right = randRange(left + 1, WIDTH); - - sides.push({ - start: [left, HEIGHT - 1], - end: [right, HEIGHT - 1], - length: right - left, - labelPos: "above" - }); - var leftStart = HEIGHT - 1; - var rightStart = HEIGHT - 1; - - _(right - left).times(function(dx) { - squares.push([HEIGHT - 1, left + dx]); - }); - - _(HEIGHT - 3).times(function(y) { - var prevLeft = left; - var prevRight = right; - left = randRangeWeighted(0, prevRight - 1, - prevLeft, 0.7); - right = randRangeWeighted(max(left, prevLeft) - + 1, WIDTH, prevRight, 0.7); - _(right - left).times(function(dx) { - squares.push([HEIGHT - y - 2, left + dx]); - }); - if (left !== prevLeft) { - sides.push({ - start: [prevLeft, leftStart], - end: [prevLeft, HEIGHT - y - 2], - length: leftStart - (HEIGHT - y - 2), - labelPos: "left" - }); - sides.push({ - start: [prevLeft, HEIGHT - y - 2], - end: [left, HEIGHT - y - 2], - length: abs(left - prevLeft), - labelPos: "center" - }); - leftStart = HEIGHT - y - 2; - } - if (right !== prevRight) { - sides.push({ - start: [prevRight, rightStart], - end: [prevRight, HEIGHT - y - 2], - length: rightStart - (HEIGHT - y - 2), - labelPos: "right" - }); - sides.push({ - start: [prevRight, HEIGHT - y - 2], - end: [right, HEIGHT - y - 2], - length: abs(right - prevRight), - labelPos: "center" - }); - rightStart = HEIGHT - y - 2; - } - }); - - sides.push({ - start: [left, leftStart], - end: [left, 1], - length: leftStart - 1, - labelPos: "left" - }); - sides.push({ - start: [right, rightStart], - end: [right, 1], - length: rightStart - 1, - labelPos: "right" - }); - sides.push({ - start: [left, 1], - end: [right, 1], - length: right - left, - labelPos: "below" - }); - - return [sides, squares]; - })() + createOddShape({ + width: WIDTH, + height: HEIGHT + }) - _.reduce(SIDES, function(m, v) { - return m + v.length; - }, 0) - SQUARES.length

@@ -270,14 +187,14 @@ stroke: "#bbb" }); }); - _.each(SIDES, function(side) { + _.each(SHAPE.sides, function(side) { path([side.start, side.end], {stroke: BLUE}); });

- AREA + SHAPE.area square plural(UNIT_TEXT)
@@ -288,15 +205,12 @@

- _.each(SQUARES, function(square, n) { - label([square[1] + 0.5, square[0] - 0.5], n + 1, - "center", false); - }); + SHAPE.labelSquares();

Count the number of squares covered.

- The area is AREA square + The area is SHAPE.area square plural(UNIT_TEXT).

130 exercises/perimeter_1.html
 @@ -1,5 +1,5 @@ - + Perimeter 1 @@ -226,85 +226,19 @@
randRange(5, 10) randRange(5, 10) - (function() { - var sides = []; - - var left = randRange(0, WIDTH - 1); - var right = randRange(left + 1, WIDTH); - - sides.push({ - start: [left, HEIGHT - 1], - end: [right, HEIGHT - 1], - length: right - left, - labelPos: "above" - }); - var leftStart = HEIGHT - 1; - var rightStart = HEIGHT - 1; - - _(HEIGHT - 3).times(function(y) { - var prevLeft = left; - var prevRight = right; - left = randRangeWeighted(0, prevRight - 1, prevLeft, 0.7); - right = randRangeWeighted(max(left, prevLeft) + 1, WIDTH, prevRight, 0.7); - if (left !== prevLeft) { - sides.push({ - start: [prevLeft, leftStart], - end: [prevLeft, HEIGHT - y - 2], - length: leftStart - (HEIGHT - y - 2), - labelPos: "left" - }); - sides.push({ - start: [prevLeft, HEIGHT - y - 2], - end: [left, HEIGHT - y - 2], - length: abs(left - prevLeft), - labelPos: "center" - }); - leftStart = HEIGHT - y - 2; - } - if (right !== prevRight) { - sides.push({ - start: [prevRight, rightStart], - end: [prevRight, HEIGHT - y - 2], - length: rightStart - (HEIGHT - y - 2), - labelPos: "right" - }); - sides.push({ - start: [prevRight, HEIGHT - y - 2], - end: [right, HEIGHT - y - 2], - length: abs(right - prevRight), - labelPos: "center" - }); - rightStart = HEIGHT - y - 2; - } - }); - - sides.push({ - start: [left, leftStart], - end: [left, 1], - length: leftStart - 1, - labelPos: "left" - }); - sides.push({ - start: [right, rightStart], - end: [right, 1], - length: rightStart - 1, - labelPos: "right" - }); - sides.push({ - start: [left, 1], - end: [right, 1], - length: right - left, - labelPos: "below" - }); - - return sides; - })() - _.reduce(SIDES, function(m, v) { return m + v.length; }, 0) + + createOddShape({ + width: WIDTH, + height: HEIGHT + }) + + SHAPE.sides

- What is the perimeter of the shape? Each square in the grid is a 1 \times 1 - UNIT_TEXT square. + What is the perimeter of the shape? Each square in the grid is + a 1 \times 1 UNIT_TEXT square.

@@ -313,41 +247,51 @@ var shape = []; _(WIDTH + 1).times(function(i) { - line([i, 0], [i, HEIGHT], { "stroke-width": 1, stroke: "#bbb" }); + line([i, 0], [i, HEIGHT], { "stroke-width": 1, + stroke: "#bbb" }); }); _(HEIGHT + 1).times(function(i) { - line([0, i], [WIDTH, i], { "stroke-width": 1, stroke: "#bbb" }); + line([0, i], [WIDTH, i], { "stroke-width": 1, + stroke: "#bbb" }); }); - - _.each(SIDES, function(side) { + _.each(SHAPE.sides, function(side) { path([side.start, side.end], {stroke: BLUE}); }); - -
- PERIMETER plural(UNIT_TEXT) + SHAPE.perimeter + plural(UNIT_TEXT)

- The perimeter is the total length of all the sides of the shape added together. + The perimeter is the total length of all the sides of the + shape added together.

- _.each(SIDES, function(side) { - label([(side.start[0] + side.end[0]) / 2, - (side.start[1] + side.end[1]) / 2], side.length, - side.labelPos); - }); + SHAPE.labelSides();
-

Add up the lengths of all SIDES.length sides.

-

\qquad_.map(SIDES, function(v) { return v.length; }).join("+") = \text{ ?}

+

+ Add up the lengths of all SHAPE.numSides + sides. +

+

\qquad + + _.map(SHAPE.sides, function(v) { + return v.length; + }).join("+") + + = \text{ ?} +

-

The perimeter is PERIMETER plural(UNIT_TEXT).

+

+ The perimeter is SHAPE.perimeter + plural(UNIT_TEXT). +

139 utils/graphie-geometry.js
 @@ -707,3 +707,142 @@ function newKite(center) { var angC = 360 - angB - 2 * angA; return new Quadrilateral(center, randomQuadAngles.kite(), 1 , "", 2); } + +$.extend(KhanUtil, { + + // Creates a representation of a weird blocky shape that you can find + // the perimeter or area of + createOddShape: function(options) { + shape =$.extend({ + width: 10, + height: 10, + squares: [], + sides: [] + }, options); + + // Start at the top of the shape and pick a random left and right + // edge for the top row. + var y = shape.height - 1; + var left = KhanUtil.randRange(0, shape.width - 1); + var right = KhanUtil.randRange(left + 1, shape.width); + + // Add the top side + shape.sides.push({ + start: [left, y], + end: [right, y], + length: right - left, + labelPos: "above" + }); + // The y-positions where the next vertical line on the left and + // right sides of the figure starts. + var leftStart = y; + var rightStart = y; + + // Add each square in the top row + _(right - left).times(function(dx) { + shape.squares.push([left + dx, y]); + }); + + // Iterate through each subsequent row + while (y > 2) { + y -= 1; + // Pick a new left and right edge for each row, with a 70% + // probability of continuing on the same row as the row above + var prevLeft = left; + var prevRight = right; + left = KhanUtil.randRangeWeighted(0, prevRight - 1, prevLeft, 0.7); + right = KhanUtil.randRangeWeighted(Math.max(left, prevLeft) + 1, + shape.width, prevRight, 0.7); + + // Add each square in this row + _(right - left).times(function(dx) { + shape.squares.push([left + dx, y]); + }); + + // If the left side isn't the same as the row above, + // add a new vertical and horizontal side + if (left !== prevLeft) { + shape.sides.push({ + start: [prevLeft, leftStart], + end: [prevLeft, y], + length: leftStart - y, + labelPos: "left" + }); + shape.sides.push({ + start: [prevLeft, y], + end: [left, y], + length: Math.abs(left - prevLeft), + labelPos: "center" + }); + // record where the next vertical side on the left starts + leftStart = y; + } + + // If the right side isn't the same as the row above, + // add a new vertical and horizontal side + if (right !== prevRight) { + shape.sides.push({ + start: [prevRight, rightStart], + end: [prevRight, y], + length: rightStart - y, + labelPos: "right" + }); + shape.sides.push({ + start: [prevRight, y], + end: [right, y], + length: Math.abs(right - prevRight), + labelPos: "center" + }); + // record where the next vertical side on the right starts + rightStart = y; + } + } + + // Add the last left and right vertical sides + shape.sides.push({ + start: [left, leftStart], + end: [left, 1], + length: leftStart - 1, + labelPos: "left" + }); + shape.sides.push({ + start: [right, rightStart], + end: [right, 1], + length: rightStart - 1, + labelPos: "right" + }); + + // Add the bottom side + shape.sides.push({ + start: [left, 1], + end: [right, 1], + length: right - left, + labelPos: "below" + }); + + shape.perimeter = _.reduce(shape.sides, function(perimeter, side) { + return perimeter + side.length; + }, 0); + + shape.area = shape.squares.length; + + shape.numSides = shape.sides.length; + + shape.labelSquares = function() { + _.each(shape.squares, function(square, n) { + KhanUtil.currentGraph.label([square[0] + 0.5, square[1] - 0.5], + n + 1, "center", false); + }); + }; + + shape.labelSides = function() { + _.each(shape.sides, function(side) { + KhanUtil.currentGraph.label([(side.start[0] + side.end[0]) / 2, + (side.start[1] + side.end[1]) / 2], side.length, + side.labelPos); + }); + }; + + return shape; + } +});