/******************************************************************************* * * Copyright 2012 Bess Siegal * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ /** * This function creates a Shape object. * You must call Shape.init so it * can contain a Group clustering of * the shape path and text */ function Shape(/*Path*/ shapeItem, /*int*/ mass) { /* the Path shapeItem */ this.shapeItem = shapeItem; /* the color when enabled */ this.color = shapeItem.fillColor; /* the value for mass of this shape */ this.mass = mass; /* the PointText */ this.text = null; //set during init /* the group for the shapeItem and text together */ this.group = null; //set during init /* the Shape this was cloned from, if this is an original, will be null */ this.clonedFrom = null; /* number of clones it has (if it is a clone it will always be 0) */ this.cloneCount = 0; this.init = function() { this.shapeItem.strokeColor = '#d5b99e'; var text = new PointText(this.shapeItem.position); text.fillColor = 'black'; text.characterStyle.fontSize = 14; text.content = '' + mass; text.paragraphStyle.justification = 'center'; text.position = text.position + new Point(0, text.characterStyle.fontSize / 2); this.text = text; this.group = new Group(shapeItem, text); }; this.move = function(/*Point*/ point) { /* * trying to move the group to the new point does not give the desired result, * so move each child manually. */ this.shapeItem.position = point; this.text.position = point + new Point(0, this.text.characterStyle.fontSize / 2); }; this.disable = function() { this.shapeItem.fillColor = '#e0e0dd'; }; this.enable = function() { this.shapeItem.fillColor = this.color; } } /** all the functions and member variables except for mouse handlers */ var pjs = { /** holder of all shape objects */ shapes: [], /** current shape object "clone" - groups cannot be cloned so properties and children are manually set */ currClone: null, /** boolean - when true, remove the currClone onMouseUp, otherwise add it to my pan */ removeClone: false, /** the balance scale Items and 'shapes' array. This is not a group, because the beam rotates while the pans move but do not rotate */ balance: {shapes: []}, /** the total PointText Item on the right pan including an int 'total' property */ total: null, /** current beam rotation */ currRotation: 0, /** group of PointText, miniShapes, miniTexts and buttons to show the hooray message and equation */ hooray: null, /** initial location of hooray.firstChild */ hoorayDest: new Point(500, 180), /** boolean to start/stop animation */ hoorayGo: true, init: function() { /* * Define this scope so ShapeEquate will have * functions that can be accessed directly from javascript */ PaperScope.each(function(/*PaperScope*/ scope) { if (scope.project) { ShapeEquate.scope = scope; ShapeEquate.clearPan = function() { ShapeEquate.total = pjs.total.total; pjs.clearPan(); ShapeEquate.draw(); }; ShapeEquate.newTotal = function() { pjs.newTotal(); ShapeEquate.draw(); }; } }); pjs.createShapeBank(); pjs.createShapes(); pjs.createBalance(); pjs.createTotal(); pjs.angleBeam(); }, createShapeBank: function() { /* * rectangle area for the shape bank */ var point = new Point(5, 5); var size = new Size(990, 125); var rect = new Rectangle(point, size); var bank = new Path.RoundRectangle(rect, 10); bank.strokeColor = '#d5b99e'; bank.fillColor = '#f8ede2'; }, createShapes: function() { /* * Shapes for the Bank * 1 - circle (orange) * 2 - heart (pink) * 3 - triangle (purple) * 4 - square (blue) * 5 - house (hunter green) * 6 - angular hourglass (sandy gold) * 7 - arrow (indigo) * 8 - octagon (red) * 9 - 3-leaf (green) * 10 - 5-point star (yellow) */ /* * Circle */ var circle = new Path.Circle(new Point(55, 70), 45); circle.fillColor = '#FF7538'; pjs.shapes[0] = new Shape(circle, 1); /* * Heart */ var heart = new Path(); heart.add(new Point(155, 110)); heart.add(new Point(195, 60)); heart.arcTo(new Point(155, 40), false); heart.arcTo(new Point(115, 60), false); heart.closed = true; heart.fillColor = '#FFAACC'; pjs.shapes[1] = new Shape(heart, 2); /* * Triangle */ var triangle = new Path.RegularPolygon(new Point(240, 82), 3, 54); triangle.fillColor = '#7851A9'; pjs.shapes[2] = new Shape(triangle, 3); /* * Square */ var point = new Point(300, 30); var size = new Size(80, 80); var rect = new Rectangle(point, size); var square = new Path.Rectangle(rect); square.fillColor = '#1F75FE'; pjs.shapes[3] = new Shape(square, 4); /* * House */ var house = new Path(); house.add(new Point(400, 110)); house.add(new Point(480, 110)); house.add(new Point(480, 60)); house.add(new Point(440, 25)); house.add(new Point(400, 60)); house.closed = true; house.fillColor = '#158078'; pjs.shapes[4] = new Shape(house, 5); /* * Hour glass */ var hourglass = new Path(); hourglass.add(new Point(500, 110)); hourglass.add(new Point(580, 110)); hourglass.add(new Point(565, 65)); hourglass.add(new Point(580, 20)); hourglass.add(new Point(500, 20)); hourglass.add(new Point(515, 65)); hourglass.closed = true; hourglass.fillColor = '#FCD975'; pjs.shapes[5] = new Shape(hourglass, 6); /* * Arrow */ var arrow = new Path(); arrow.add(new Point(600, 90)); arrow.add(new Point(640, 90)); arrow.add(new Point(640, 110)); arrow.add(new Point(680, 65)); arrow.add(new Point(640, 20)); arrow.add(new Point(640, 40)); arrow.add(new Point(600, 40)); arrow.closed = true; arrow.fillColor = '#5D76CB'; pjs.shapes[6] = new Shape(arrow, 7); /* * Octagon */ var octagon = new Path.RegularPolygon(new Point(740, 65), 8, 50); octagon.fillColor = '#EE204D'; pjs.shapes[7] = new Shape(octagon, 8); /* * Shamrock */ var shamrock = new Path(); shamrock.add(new Point(830, 110)); shamrock.add(new Point(850, 110)); shamrock.arcTo(new Point(860, 90)); shamrock.arcTo(new Point(880, 70), false); shamrock.arcTo(new Point(860, 50), false); shamrock.arcTo(new Point(840, 20), false); shamrock.arcTo(new Point(820, 50), false); shamrock.arcTo(new Point(800, 70), false); shamrock.arcTo(new Point(820, 90), false); shamrock.arcTo(new Point(830, 110)); shamrock.closed = true; shamrock.fillColor = '#45CEA2'; pjs.shapes[8] = new Shape(shamrock, 9); /* * Star */ var star = new Path.Star(new Point(940, 65), 5, 30, 50); star.rotate(36); star.fillColor = '#fff425'; pjs.shapes[9] = new Shape(star, 10); /* * Initialize all the shapes */ for (var i = 0; i < pjs.shapes.length; i++) { pjs.shapes[i].init(); } /* * For some reason the star's text is off in the bank, * so fudging... */ pjs.shapes[9].text.position -= new Point(-1, 5); }, createBalance: function() { /* * fulcrum */ var fulcrum = new Path(); fulcrum.add(new Point(430, 650)); fulcrum.add(new Point(570, 650)); fulcrum.arcTo(new Point(525, 580), new Point(500, 480)); fulcrum.arcTo(new Point(475, 580), new Point(430, 650)); fulcrum.fillColor = '#CD9575'; /* * beam */ var beam = new Path(); beam.add(new Point(250, 475)); beam.add(new Point(750, 475)); beam.strokeColor = '#CD9575'; beam.strokeWidth = 10; pjs.balance.beam = beam; /* * left pan and post */ var leftPanPost = new Path(); leftPanPost.add(new Point(250, 480)); leftPanPost.add(new Point(250, 430)); leftPanPost.strokeColor = '#CD9575'; leftPanPost.strokeWidth = 10; pjs.balance.leftPanPost = leftPanPost; var leftPan = new Path(); leftPan.add(new Point(25, 430)); leftPan.add(new Point(475, 430)); leftPan.strokeColor = '#CD9575'; leftPan.strokeWidth = 10; pjs.balance.leftPan = leftPan; /* * right pan and post */ var rightPanPost = new Path(); rightPanPost.add(new Point(750, 480)); rightPanPost.add(new Point(750, 430)); rightPanPost.strokeColor = '#CD9575'; rightPanPost.strokeWidth = 10; pjs.balance.rightPanPost = rightPanPost; var rightPan = new Path(); rightPan.add(new Point(700, 430)); rightPan.add(new Point(800, 430)); rightPan.strokeColor = '#CD9575'; rightPan.strokeWidth = 10; pjs.balance.rightPan = rightPan; }, createTotal: function() { /* * The total is ShapeEquate.total * or if undefined then a random number * between 1 and 60 */ pjs.total = new PointText(new Point(750, 400)); pjs.total.fillColor = 'black'; pjs.total.characterStyle.fontSize = 30; // pjs.consoleLog("ShapeEquate.level = " + ShapeEquate.level); var num = ShapeEquate.total ? ShapeEquate.total : /* random int between (1 and 20) or (21 and 40) or (41 and 60), inclusive */ ShapeEquate.level >= 0 && ShapeEquate.level <= 2 ? Math.floor(Math.random() * 20) + ShapeEquate.level * 20 + 1 : /* random int between 1 and 55, inclusive */ ShapeEquate.level === 100 ? Math.floor(Math.random() * 55) + 1 : /* random composite number (random int 1-10 multiplied by random int 2-6) */ ShapeEquate.level === 200 ? (Math.floor(Math.random() * 10) + 1) * (Math.floor(Math.random() * 5) + 2) : /* random int between 1 and 60, inclusive */ Math.floor(Math.random() * 60) + 1; pjs.total.content = '' + num; pjs.total.paragraphStyle.justification = 'center'; pjs.total.total = num; }, angleBeam: function() { /* * Rotate the beam * based on the total on the left pan * and the total */ var shapesTotal = 0; for (var i = 0; i < pjs.balance.shapes.length; i++) { shapesTotal += pjs.balance.shapes[i].mass; } var degrees = pjs.total.total - shapesTotal; /* * rotate the beam back to neutral position * so that it can be rotated again to the * newly calculated degrees. */ pjs.balance.beam.rotate(-pjs.currRotation); /* * never rotate passed -60 degrees */ if (degrees < -60) { degrees = -60; } pjs.balance.beam.rotate(degrees); pjs.currRotation = degrees; pjs.positionPansOnBeam(degrees < 0); if (degrees < 0) { ShapeEquate.playOops(); } /* * If balanced, show a message that * says congratulations and * the equation you created. */ if (degrees == 0) { pjs.showHooray(); } }, positionPansOnBeam: function(/*boolean*/ negativeDegrees) { /* * Position the pans based on the rotated beam. * If it's negativeDegrees, we have to use bottomLeft and topRight, * otherwise topLeft and bottomRight. */ if (negativeDegrees) { pjs.balance.leftPanPost.position = pjs.balance.beam.bounds.bottomLeft - new Point(0, 20); pjs.balance.rightPanPost.position = pjs.balance.beam.bounds.topRight - new Point(0, 20); } else { pjs.balance.leftPanPost.position = pjs.balance.beam.bounds.topLeft - new Point(0, 20); pjs.balance.rightPanPost.position = pjs.balance.beam.bounds.bottomRight - new Point(0, 20); } pjs.balance.leftPan.position = pjs.balance.leftPanPost.bounds.topCenter; pjs.balance.rightPan.position = pjs.balance.rightPanPost.bounds.topCenter; pjs.total.position = pjs.balance.rightPan.position - new Point(0, 5); }, positionClonesOnPan: function() { /* * initial offset */ var dropPositionOffsetX = 40; var dropPositionMultipleY = 0.5; /* * Loop through all the clones and set them down * and move their texts */ for (var i = 0; i < pjs.balance.shapes.length; i++) { var balShape = pjs.balance.shapes[i]; var clone = balShape.shapeItem; var newPoint = new Point(pjs.balance.leftPan.strokeBounds.topLeft.x + dropPositionOffsetX, pjs.balance.leftPan.strokeBounds.topLeft.y - dropPositionMultipleY * clone.bounds.height); balShape.move(newPoint); /* * move the x offset over by the width of the clone */ dropPositionOffsetX += clone.bounds.width; /* * if the new x offset is off the pan, decrement * the y offset and reset the x */ if (dropPositionOffsetX > pjs.balance.leftPan.strokeBounds.topRight.x) { dropPositionMultipleY++; dropPositionOffsetX = 40; } } }, showHooray: function() { /* * Remove any existing hooray group */ if (pjs.hooray && pjs.hooray.remove()) { pjs.hooray = null; }; pjs.hooray = new Group(); /* * Create an array of sums for each shape */ var sums = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; for (var i = 0; i < pjs.balance.shapes.length; i++) { var mass = pjs.balance.shapes[i].mass; sums[mass - 1] = sums[mass - 1] + 1; } var roundRec = new Rectangle(new Point(5, 580), new Size(990, 60)); var container = new Path.RoundRectangle(roundRec, new Size(10, 10)); container.fillColor = '#FFFFE0'; container.strokeColor = '#E6DB55'; container.strokeWidth = 1; pjs.hooray.addChild(container); var congrats = new PointText(new Point(10, 600)); congrats.fillColor = 'black'; congrats.characterStyle.fontSize = 12; congrats.content = ' You did it! '; pjs.hooray.addChild(congrats); /* * String the equation together */ var eqPos = {x: 10, y: 625}; var debugEq = ""; var plus; for (var i = 0; i < 10; i++) { var sum = sums[i]; if (sum > 0) { debugEq = debugEq + " (" + sum + " x " + (i + 1) + ") +"; var s = new PointText(eqPos); s.fillColor = 'black'; s.characterStyle.fontSize = 12; s.content = ' (' + sum + ' x '; pjs.hooray.addChild(s); eqPos.x += s.bounds.width + 50; //eqPos.x += s.content.length * s.characterStyle.fontSize; var miniShape = pjs.shapes[i].shapeItem.clone(); miniShape.fillColor = pjs.shapes[i].color; miniShape.scale(0.33); miniShape.position.x = eqPos.x + 5; miniShape.position.y = eqPos.y - 7; pjs.hooray.addChild(miniShape); eqPos.x += miniShape.bounds.width - 5; var miniText = pjs.shapes[i].text.clone(); miniText.scale(0.7); miniText.paragraphStyle.justification = 'center'; miniText.position = miniShape.position + new Point(0, 5); pjs.hooray.addChild(miniText); plus = new PointText(eqPos); plus.fillColor = 'black'; plus.characterStyle.fontSize = 12; plus.content = ') + '; pjs.hooray.addChild(plus); eqPos.x += plus.bounds.width + 20; } } debugEq = debugEq.substring(0, debugEq.length - 1); debugEq = debugEq + " = " + pjs.total.total; pjs.consoleLog("You did it! " + debugEq); /* * remove the last + */ plus.content = ')'; /* * add the total */ var tot = new PointText(eqPos); tot.fillColor = 'black'; tot.characterStyle.fontSize = 12; tot.content = ' = ' + pjs.total.total; pjs.hooray.addChild(tot); /* * always reset allowing animation */ pjs.hoorayGo = true; ShapeEquate.playHooray(); }, getOtherShapesCloneCount: function(/*Shape*/ shape) { var cloneCount = 0; for (var i = 0; i < pjs.shapes.length; i++) { if (pjs.shapes[i] === shape) { continue; } cloneCount += pjs.shapes[i].cloneCount; } return cloneCount; }, clearPan: function() { /* * Clear the pan. */ for (var i = 0; i < pjs.balance.shapes.length; i++) { pjs.balance.shapes[i].group.remove(); } pjs.balance.shapes = []; pjs.angleBeam(); /* * Remove the hooray group */ if (pjs.hooray && pjs.hooray.remove()) { pjs.hooray = null; } /* * reset all cloneCount and re-'enable' them all */ for (var i = 0; i < pjs.shapes.length; i++) { pjs.shapes[i].cloneCount = 0; pjs.shapes[i].enable(); } }, newTotal: function() { /* * Reset the total, then call * clearPan. */ if (pjs.total) { ShapeEquate.total = null; pjs.total.remove(); pjs.createTotal(); } pjs.clearPan(); }, clearCloneFromPan: function() { /* * play sound effect */ ShapeEquate.playRemove(); /* * Remove clone * from the pjs.balance.shapes and from the canvas */ var spliceIndex = 0; for (var j = 0; j < pjs.balance.shapes.length; j++) { if (pjs.currClone === pjs.balance.shapes[j]) { spliceIndex = j; break; } } pjs.balance.shapes.splice(j, 1); /* * decrement the clone count */ pjs.currClone.clonedFrom.cloneCount = pjs.currClone.clonedFrom.cloneCount - 1; // pjs.consoleLog("decrementing cloneCount to " + pjs.currClone.clonedFrom.cloneCount); /* * remove the clone from the canvas */ pjs.currClone.group.remove(); /* * reposition the balance */ pjs.angleBeam(); pjs.positionClonesOnPan(); /* * If it is level 100 (can only be cloned 1x), * then re-'enable' this shape */ if (ShapeEquate.level === 100) { pjs.currClone.clonedFrom.enable(); } /* * If it is level 200 (only one shape can have clones), * then 'enable' all shapes if the pan is empty */ if (ShapeEquate.level === 200 && pjs.balance.shapes.length === 0) { for (var i = 0; i < pjs.shapes.length; i++) { pjs.shapes[i].enable(); } } }, addCloneToPan: function() { /* * play sound effect */ ShapeEquate.playAdd(); /* * Drop the clone onto the pan */ pjs.balance.shapes.push(pjs.currClone); /* * increment the clone count */ pjs.currClone.clonedFrom.cloneCount = pjs.currClone.clonedFrom.cloneCount + 1; // pjs.consoleLog("incrementing cloneCount to " + pjs.currClone.clonedFrom.cloneCount); /* * reposition the balance */ pjs.angleBeam(); pjs.positionClonesOnPan(); /* * If it is level 100 (can only be cloned 1x), * then 'disable' this shape */ if (ShapeEquate.level === 100) { pjs.currClone.clonedFrom.disable(); } /* * If it is level 200 (only one shape can have clones), * then 'disable' all other shapes */ if (ShapeEquate.level === 200) { for (var i = 0; i < pjs.shapes.length; i++) { if (pjs.currClone.clonedFrom !== pjs.shapes[i]) { pjs.shapes[i].disable(); } } } }, consoleLog: function(msg) { if (ShapeEquate.debug && console && console.log) { console.log(msg); } } }; function onMouseDown(event) { var hitResult = project.hitTest(event.point); if (hitResult && hitResult.item) { for (var i = 0; i < pjs.shapes.length; i++) { if (pjs.shapes[i].shapeItem === hitResult.item) { /* * Remove hooray in case * still playing after success hooray */ if (pjs.hooray && pjs.hooray.remove()) { pjs.hooray = null; } /* * If it's a shape, clone it... */ pjs.consoleLog("cloneCount is " + pjs.shapes[i].cloneCount); /* * ... unless it is level 100 (can only be cloned 1x) and already has a clone */ if (ShapeEquate.level === 100 && pjs.shapes[i].cloneCount > 0) { ShapeEquate.playInvalid(); return; } /* * ... unless it is level 200 (only one shape can have clones) * and already has a clone that is not this shape */ if (ShapeEquate.level === 200 && pjs.getOtherShapesCloneCount(pjs.shapes[i]) > 0) { ShapeEquate.playInvalid(); return; } pjs.consoleLog("pjs.shapes[i].shapeItem === hitResult.item, so cloning..."); /* * Make clone and create a new shape */ pjs.currClone = new Shape(hitResult.item.clone(), pjs.shapes[i].mass); pjs.currClone.init(); /* * mark from whom it was cloned, * which is necessary for level >= 100 */ pjs.currClone.clonedFrom = pjs.shapes[i]; /* * Show the clone as 'selected' */ pjs.currClone.shapeItem.selected = true; /* * set the mode so onMouseUp will know what to do */ pjs.removeClone = false; return; } } for (var i = 0; i < pjs.balance.shapes.length; i++) { if (pjs.balance.shapes[i].shapeItem === hitResult.item) { /* * Remove hooray in case * still playing after success hooray */ if (pjs.hooray && pjs.hooray.remove()) { pjs.hooray = null; } /* * mark the clone to be removed */ pjs.currClone = pjs.balance.shapes[i]; pjs.currClone.shapeItem.selected = true; pjs.removeClone = true; return; } } /* * If it's the hooray container, stop animation, or * if already stopped, then, remove */ if (pjs.hooray && pjs.hooray.firstChild === hitResult.item) { if (!pjs.hoorayGo && pjs.hooray.remove()) { pjs.hooray = null; } pjs.hoorayGo = !pjs.hoorayGo; } } } function onMouseDrag(event) { if (pjs.currClone) { /* * Move the current clone along the mouse drag. * This is not necessary, but a nice way * to visualize dragging a shape on or off the pan. */ pjs.currClone.move(event.point); } } function onMouseUp(event) { if (pjs.currClone) { if (pjs.removeClone) { pjs.clearCloneFromPan(); } else { pjs.addCloneToPan(); } /* * de-select the clone */ pjs.currClone.shapeItem.selected = false; pjs.currClone = null; } } function onFrame(event) { if (pjs.hooray && pjs.hoorayGo) { /* * Animate moving the hooray message up */ var vector = pjs.hoorayDest - pjs.hooray.firstChild.position; /* * The closer it gets, the smaller the vector, so it slows down */ pjs.hooray.position += vector / 25; /* * When alost there, stop */ if (vector.length < 5) { pjs.hoorayGo = false; } } } pjs.init();