diff --git a/apps/package.json b/apps/package.json index 13fdbffdc12f9..171d626c50dbc 100644 --- a/apps/package.json +++ b/apps/package.json @@ -49,6 +49,7 @@ "@code-dot-org/johnny-five": "0.11.1-cdo.2", "@code-dot-org/js-interpreter-tyrant": "0.2.2", "@code-dot-org/js-numbers": "0.1.0-cdo.0", + "@code-dot-org/maze": "1.0.1", "@code-dot-org/p5.play": "1.2.2-cdo", "@code-dot-org/piskel": "0.13.0-cdo.3", "@storybook/addon-info": "3.2.11", diff --git a/apps/src/code-studio/components/BeeCellEditor.jsx b/apps/src/code-studio/components/BeeCellEditor.jsx index 4d6febe34f15f..b246089676c20 100644 --- a/apps/src/code-studio/components/BeeCellEditor.jsx +++ b/apps/src/code-studio/components/BeeCellEditor.jsx @@ -1,11 +1,13 @@ /** * @overview React component to allow for easy editing and creation of BeeCells - * @see @cdo/apps/maze/beeCell + * @see @code-dot-org/maze/src/beeCell */ import React from 'react'; import CellEditor from './CellEditor'; -import BeeCell from '@cdo/apps/maze/beeCell'; +import { cells } from '@code-dot-org/maze'; + +const BeeCell = cells.BeeCell; export default class BeeCellEditor extends CellEditor { /** diff --git a/apps/src/code-studio/components/CellEditor.jsx b/apps/src/code-studio/components/CellEditor.jsx index 2e4676efcb377..0eb916c9aff3a 100644 --- a/apps/src/code-studio/components/CellEditor.jsx +++ b/apps/src/code-studio/components/CellEditor.jsx @@ -1,10 +1,12 @@ /** * @overview React component to allow for easy editing and creation of Cells. * can be extended to allow for editing of various specialized kinds of cells. - * @see @cdo/apps/maze/cell + * @see @code-dot-org/maze/src/cell */ import React, {PropTypes} from 'react'; -import { SquareType } from '@cdo/apps/maze/tiles'; +import { tiles } from '@code-dot-org/maze'; + +const SquareType = tiles.SquareType; export default class CellEditor extends React.Component { static propTypes = { diff --git a/apps/src/code-studio/components/Grid.jsx b/apps/src/code-studio/components/Grid.jsx index 73335bc478b4e..f38a94532fc23 100644 --- a/apps/src/code-studio/components/Grid.jsx +++ b/apps/src/code-studio/components/Grid.jsx @@ -7,7 +7,7 @@ import { WallCoordColMask, WallCoordColShift } from '@cdo/apps/studio/constants'; -import mazeUtils from '@cdo/apps/maze/mazeUtils'; +import { utils as mazeUtils } from '@code-dot-org/maze'; const CELL_WIDTH = 48; const CELL_HEIGHT = 38; diff --git a/apps/src/code-studio/components/GridEditor.jsx b/apps/src/code-studio/components/GridEditor.jsx index 758997e77f424..c6809f9073f9b 100644 --- a/apps/src/code-studio/components/GridEditor.jsx +++ b/apps/src/code-studio/components/GridEditor.jsx @@ -4,12 +4,10 @@ * Supports both Bee and Farmer skins. */ import React, {PropTypes} from 'react'; -var HarvesterCell = require('@cdo/apps/maze/harvesterCell'); -var PlanterCell = require('@cdo/apps/maze/planterCell'); -var BeeCell = require('@cdo/apps/maze/beeCell'); -var Cell = require('@cdo/apps/maze/cell'); + +import {cells, utils as mazeUtils} from '@code-dot-org/maze'; + var StudioCell = require('@cdo/apps/studio/cell'); -var mazeUtils = require('@cdo/apps/maze/mazeUtils'); var HarvesterCellEditor = require('./HarvesterCellEditor'); var PlanterCellEditor = require('./PlanterCellEditor'); @@ -80,13 +78,13 @@ export default class GridEditor extends React.Component { if (this.props.skin === 'playlab' || this.props.skin === 'starwarsgrid') { return StudioCell; } else if (mazeUtils.isBeeSkin(this.props.skin)) { - return BeeCell; + return cells.BeeCell; } else if (mazeUtils.isHarvesterSkin(this.props.skin)) { - return HarvesterCell; + return cells.HarvesterCell; } else if (mazeUtils.isPlanterSkin(this.props.skin)) { - return PlanterCell; + return cells.PlanterCell; } - return Cell; + return cells.Cell; } getEditorClass() { diff --git a/apps/src/code-studio/components/HarvesterCellEditor.jsx b/apps/src/code-studio/components/HarvesterCellEditor.jsx index 2ad3724f6dbe1..b594d72ffe098 100644 --- a/apps/src/code-studio/components/HarvesterCellEditor.jsx +++ b/apps/src/code-studio/components/HarvesterCellEditor.jsx @@ -1,11 +1,13 @@ /** * @overview React component to allow for easy editing and creation of HarvesterCells - * @see @cdo/apps/maze/harvesterCell + * @see @code-dot-org/maze/src/harvesterCell */ import React from 'react'; import CellEditor from './CellEditor'; -import HarvesterCell from '@cdo/apps/maze/harvesterCell'; +import { cells } from '@code-dot-org/maze'; + +const HarvesterCell = cells.HarvesterCell; export default class PlanterCellEditor extends CellEditor { /** diff --git a/apps/src/code-studio/components/PlanterCellEditor.jsx b/apps/src/code-studio/components/PlanterCellEditor.jsx index 61ce511ea3a50..7c04260ab5197 100644 --- a/apps/src/code-studio/components/PlanterCellEditor.jsx +++ b/apps/src/code-studio/components/PlanterCellEditor.jsx @@ -1,12 +1,13 @@ /** * @overview React component to allow for easy editing and creation of PlanterCells - * @see @cdo/apps/maze/harvesterCell + * @see @code-dot-org/maze/src/harvesterCell */ import React from 'react'; import CellEditor from './CellEditor'; -import PlanterCell from '@cdo/apps/maze/planterCell'; -import { SquareType } from '@cdo/apps/maze/tiles'; +import { cells, tiles } from '@code-dot-org/maze'; +const PlanterCell = cells.PlanterCell; +const SquareType = tiles.SquareType; export default class PlanterCellEditor extends CellEditor { diff --git a/apps/src/code-studio/components/stageExtras/MazeThumbnail.jsx b/apps/src/code-studio/components/stageExtras/MazeThumbnail.jsx index 101d4491575f3..cbdfa24a5dd96 100644 --- a/apps/src/code-studio/components/stageExtras/MazeThumbnail.jsx +++ b/apps/src/code-studio/components/stageExtras/MazeThumbnail.jsx @@ -1,11 +1,13 @@ import React, {PropTypes} from 'react'; import ProtectedStatefulDiv from '@cdo/apps/templates/ProtectedStatefulDiv'; import skins from "@cdo/apps/maze/skins"; -import {getSubtypeForSkin} from '@cdo/apps/maze/mazeUtils'; -import MazeMap from '@cdo/apps/maze/mazeMap'; -import drawMap from '@cdo/apps/maze/drawMap'; import assetUrl from '@cdo/apps/code-studio/assetUrl'; +import { utils, MazeMap, drawMap } from '@code-dot-org/maze'; + +const getSubtypeForSkin = utils.getSubtypeForSkin; + + export default class MazeThumbnail extends React.Component { static propTypes = { level: PropTypes.shape({ diff --git a/apps/src/maze/animationsController.js b/apps/src/maze/animationsController.js deleted file mode 100644 index 65d16ee3544b9..0000000000000 --- a/apps/src/maze/animationsController.js +++ /dev/null @@ -1,650 +0,0 @@ -import {SVG_NS} from '../constants'; -const drawMap = require('./drawMap'); -const displayPegman = drawMap.displayPegman; -const getPegmanYForRow = drawMap.getPegmanYForRow; -const timeoutList = require('../lib/util/timeoutList'); -const utils = require('../utils'); -const tiles = require('./tiles'); - -module.exports = class AnimationsController { - constructor(maze, svg) { - this.maze = maze; - this.svg = svg; - - this.createAnimations_(); - } - - createAnimations_() { - // Add idle pegman. - if (this.maze.skin.idlePegmanAnimation) { - this.createPegmanAnimation_({ - idStr: 'idle', - pegmanImage: this.maze.skin.idlePegmanAnimation, - row: this.maze.subtype.start.y, - col: this.maze.subtype.start.x, - direction: this.maze.startDirection, - numColPegman: this.maze.skin.idlePegmanCol, - numRowPegman: this.maze.skin.idlePegmanRow - }); - - if (this.maze.skin.idlePegmanCol > 1 || this.maze.skin.idlePegmanRow > 1) { - // our idle is a sprite sheet instead of a gif. schedule cycling through - // the frames - var numFrames = this.maze.skin.idlePegmanRow; - var idlePegmanIcon = document.getElementById('idlePegman'); - var timePerFrame = 600; // timeForAnimation / numFrames; - var idleAnimationFrame = 0; - - setInterval(() => { - if (idlePegmanIcon.getAttribute('visibility') === 'visible') { - this.updatePegmanAnimation_({ - idStr: 'idle', - row: this.maze.subtype.start.y, - col: this.maze.subtype.start.x, - direction: this.maze.startDirection, - animationRow: idleAnimationFrame - }); - idleAnimationFrame = (idleAnimationFrame + 1) % numFrames; - } - }, timePerFrame); - } - } - - if (this.maze.skin.celebrateAnimation) { - this.createPegmanAnimation_({ - idStr: 'celebrate', - pegmanImage: this.maze.skin.celebrateAnimation, - row: this.maze.subtype.start.y, - col: this.maze.subtype.start.x, - direction: tiles.Direction.NORTH, - numColPegman: this.maze.skin.celebratePegmanCol, - numRowPegman: this.maze.skin.celebratePegmanRow - }); - } - - // Add the hidden dazed pegman when hitting the wall. - if (this.maze.skin.wallPegmanAnimation) { - this.createPegmanAnimation_({ - idStr: 'wall', - pegmanImage: this.maze.skin.wallPegmanAnimation - }); - } - - // create element for our hitting wall spritesheet - if (this.maze.skin.hittingWallAnimation && this.maze.skin.hittingWallAnimationFrameNumber) { - this.createPegmanAnimation_({ - idStr: 'wall', - pegmanImage: this.maze.skin.hittingWallAnimation, - numColPegman: this.maze.skin.hittingWallPegmanCol, - numRowPegman: this.maze.skin.hittingWallPegmanRow - }); - document.getElementById('wallPegman').setAttribute('visibility', 'hidden'); - } - - // Add the hidden moving pegman animation. - if (this.maze.skin.movePegmanAnimation) { - this.createPegmanAnimation_({ - idStr: 'move', - pegmanImage: this.maze.skin.movePegmanAnimation, - numColPegman: 4, - numRowPegman: (this.maze.skin.movePegmanAnimationFrameNumber || 9) - }); - } - - // Add wall hitting animation - if (this.maze.skin.hittingWallAnimation) { - var wallAnimationIcon = document.createElementNS(SVG_NS, 'image'); - wallAnimationIcon.setAttribute('id', 'wallAnimation'); - wallAnimationIcon.setAttribute('height', this.maze.SQUARE_SIZE); - wallAnimationIcon.setAttribute('width', this.maze.SQUARE_SIZE); - wallAnimationIcon.setAttribute('visibility', 'hidden'); - this.svg.appendChild(wallAnimationIcon); - } - } - - reset(first) { - if (first) { - // Dance consists of 5 animations, each of which get 150ms - var danceTime = 150 * 5; - if (this.maze.skin.danceOnLoad) { - this.scheduleDance(false, danceTime); - } - timeoutList.setTimeout(() => { - this.maze.stepSpeed = 100; - this.scheduleTurn(this.maze.startDirection); - }, danceTime + 150); - } else { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, tiles.directionToFrame(this.maze.pegmanD)); - - const finishIcon = document.getElementById('finish'); - if (finishIcon) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.maze.skin.goalIdle); - - // skins with a celebration animation (like scrat) hide the finish icon - // after finishing; to support resetting those, we need to restore the - // finish icon here - finishIcon.setAttribute('visibility', 'visible'); - } - } - - // Make 'look' icon invisible and promote to top. - var lookIcon = document.getElementById('look'); - lookIcon.style.display = 'none'; - lookIcon.parentNode.appendChild(lookIcon); - var paths = lookIcon.getElementsByTagName('path'); - for (let i = 0; i < paths.length; i++) { - var path = paths[i]; - path.setAttribute('stroke', this.maze.skin.look); - } - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - - if (this.maze.skin.idlePegmanAnimation) { - pegmanIcon.setAttribute('visibility', 'hidden'); - var idlePegmanIcon = document.getElementById('idlePegman'); - idlePegmanIcon.setAttribute('visibility', 'visible'); - } else { - pegmanIcon.setAttribute('visibility', 'visible'); - } - - if (this.maze.skin.wallPegmanAnimation) { - var wallPegmanIcon = document.getElementById('wallPegman'); - wallPegmanIcon.setAttribute('visibility', 'hidden'); - } - - if (this.maze.skin.movePegmanAnimation) { - var movePegmanIcon = document.getElementById('movePegman'); - movePegmanIcon.setAttribute('visibility', 'hidden'); - } - - if (this.maze.skin.celebrateAnimation) { - var celebrateAnimation = document.getElementById('celebratePegman'); - celebrateAnimation.setAttribute('visibility', 'hidden'); - } - } - - /** - * Create sprite assets for pegman. - * @param options Specify different features of the pegman animation. - * idStr required identifier for the pegman. - * pegmanImage required which image to use for the animation. - * col which column the pegman is at. - * row which row the pegman is at. - * direction which direction the pegman is facing at. - * numColPegman number of the pegman in each row, default is 4. - * numRowPegman number of the pegman in each column, default is 1. - */ - createPegmanAnimation_(options) { - // Create clip path. - var clip = document.createElementNS(SVG_NS, 'clipPath'); - clip.setAttribute('id', options.idStr + 'PegmanClip'); - var rect = document.createElementNS(SVG_NS, 'rect'); - rect.setAttribute('id', options.idStr + 'PegmanClipRect'); - if (options.col !== undefined) { - rect.setAttribute('x', options.col * this.maze.SQUARE_SIZE + 1 + this.maze.PEGMAN_X_OFFSET); - } - if (options.row !== undefined) { - rect.setAttribute('y', getPegmanYForRow(this.maze.skin, options.row)); - } - rect.setAttribute('width', this.maze.PEGMAN_WIDTH); - rect.setAttribute('height', this.maze.PEGMAN_HEIGHT); - clip.appendChild(rect); - this.svg.appendChild(clip); - // Create image. - var imgSrc = options.pegmanImage; - var img = document.createElementNS(SVG_NS, 'image'); - img.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', imgSrc); - img.setAttribute('height', this.maze.PEGMAN_HEIGHT * (options.numRowPegman || 1)); - img.setAttribute('width', this.maze.PEGMAN_WIDTH * (options.numColPegman || 4)); - img.setAttribute('clip-path', 'url(#' + options.idStr + 'PegmanClip)'); - img.setAttribute('id', options.idStr + 'Pegman'); - this.svg.appendChild(img); - // Update pegman icon & clip path. - if (options.col !== undefined && options.direction !== undefined) { - var x = this.maze.SQUARE_SIZE * options.col - - options.direction * this.maze.PEGMAN_WIDTH + 1 + this.maze.PEGMAN_X_OFFSET; - img.setAttribute('x', x); - } - if (options.row !== undefined) { - img.setAttribute('y', getPegmanYForRow(this.maze.skin, options.row)); - } - } - - /** - * Calculate the Y offset within the sheet - */ - getPegmanFrameOffsetY_(animationRow) { - animationRow = animationRow || 0; - return animationRow * this.maze.PEGMAN_HEIGHT; - } - - /** - * Update sprite assets for pegman. - * @param options Specify different features of the pegman animation. - * idStr required identifier for the pegman. - * col required which column the pegman is at. - * row required which row the pegman is at. - * direction required which direction the pegman is facing at. - * animationRow which row of the sprite sheet the pegman animation needs - */ - updatePegmanAnimation_(options) { - var rect = document.getElementById(options.idStr + 'PegmanClipRect'); - rect.setAttribute('x', options.col * this.maze.SQUARE_SIZE + 1 + this.maze.PEGMAN_X_OFFSET); - rect.setAttribute('y', getPegmanYForRow(this.maze.skin, options.row)); - var img = document.getElementById(options.idStr + 'Pegman'); - var x = this.maze.SQUARE_SIZE * options.col - - options.direction * this.maze.PEGMAN_WIDTH + 1 + this.maze.PEGMAN_X_OFFSET; - img.setAttribute('x', x); - var y = getPegmanYForRow(this.maze.skin, options.row) - this.getPegmanFrameOffsetY_(options.animationRow); - img.setAttribute('y', y); - img.setAttribute('visibility', 'visible'); - } - - /** - * Schedule a movement animating using a spritesheet. - */ - scheduleSheetedMovement_(start, delta, numFrames, timePerFrame, idStr, direction, hidePegman) { - var pegmanIcon = document.getElementById('pegman'); - utils.range(0, numFrames - 1).forEach((frame) => { - timeoutList.setTimeout(() => { - if (hidePegman) { - pegmanIcon.setAttribute('visibility', 'hidden'); - } - this.updatePegmanAnimation_({ - idStr: idStr, - col: start.x + delta.x * frame / numFrames, - row: start.y + delta.y * frame / numFrames, - direction: direction, - animationRow: frame - }); - }, timePerFrame * frame); - }); - } - - /** - * Schedule the animations for a move from the current position - * @param {number} endX X coordinate of the target position - * @param {number} endY Y coordinate of the target position - */ - scheduleMove(endX, endY, timeForAnimation) { - var startX = this.maze.pegmanX; - var startY = this.maze.pegmanY; - var direction = this.maze.pegmanD; - - var deltaX = (endX - startX); - var deltaY = (endY - startY); - var numFrames; - var timePerFrame; - - if (this.maze.skin.movePegmanAnimation) { - numFrames = this.maze.skin.movePegmanAnimationFrameNumber; - // If move animation of pegman is set, and this is not a turn. - // Show the animation. - var pegmanIcon = document.getElementById('pegman'); - var movePegmanIcon = document.getElementById('movePegman'); - timePerFrame = timeForAnimation / numFrames; - - this.scheduleSheetedMovement_({ - x: startX, - y: startY - }, { - x: deltaX, - y: deltaY - }, - numFrames, timePerFrame, 'move', direction, true); - - // Hide movePegman and set pegman to the end position. - timeoutList.setTimeout(() => { - movePegmanIcon.setAttribute('visibility', 'hidden'); - pegmanIcon.setAttribute('visibility', 'visible'); - this.displayPegman(endX, endY, tiles.directionToFrame(direction)); - if (this.maze.subtype.isWordSearch()) { - this.maze.subtype.markTileVisited(endY, endX, true); - } - }, timePerFrame * numFrames); - } else { - // we don't have an animation, so just move the x/y pos - numFrames = 4; - timePerFrame = timeForAnimation / numFrames; - utils.range(1, numFrames).forEach((frame) => { - timeoutList.setTimeout(() => { - this.displayPegman( - startX + deltaX * frame / numFrames, - startY + deltaY * frame / numFrames, - tiles.directionToFrame(direction)); - }, timePerFrame * frame); - }); - } - - if (this.maze.skin.approachingGoalAnimation) { - var finishIcon = document.getElementById('finish'); - // If pegman is close to the goal - // Replace the goal file with approachingGoalAnimation - if (this.maze.subtype.finish && Math.abs(endX - this.maze.subtype.finish.x) <= 1 && - Math.abs(endY - this.maze.subtype.finish.y) <= 1) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.approachingGoalAnimation); - } else { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.goalIdle); - } - } - } - - /** - * Schedule the animations for a turn from the current direction - * @param {number} endDirection The direction we're turning to - */ - scheduleTurn(endDirection) { - var numFrames = 4; - var startDirection = this.maze.pegmanD; - var deltaDirection = endDirection - startDirection; - utils.range(1, numFrames).forEach((frame) => { - timeoutList.setTimeout(() => { - this.displayPegman( - this.maze.pegmanX, - this.maze.pegmanY, - tiles.directionToFrame(startDirection + deltaDirection * frame / numFrames)); - }, this.maze.stepSpeed * (frame - 1)); - }); - } - - crackSurroundingIce(targetX, targetY) { - // Remove cracked ice, replace surrounding ice with cracked ice. - this.updateSurroundingTiles_(targetY, targetX, (tileElement, cell) => { - if (cell.getTile() === tiles.SquareType.OPEN) { - tileElement.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.largerObstacleAnimationTiles - ); - } else if (cell.getTile() === tiles.SquareType.OBSTACLE) { - tileElement.setAttribute('opacity', 0); - } - }); - } - - /** - * Replace the tiles surrounding the obstacle with broken tiles. - */ - updateSurroundingTiles_(obstacleY, obstacleX, callback) { - var tileCoords = [ - [obstacleY - 1, obstacleX - 1], - [obstacleY - 1, obstacleX], - [obstacleY - 1, obstacleX + 1], - [obstacleY, obstacleX - 1], - [obstacleY, obstacleX], - [obstacleY, obstacleX + 1], - [obstacleY + 1, obstacleX - 1], - [obstacleY + 1, obstacleX], - [obstacleY + 1, obstacleX + 1] - ]; - for (let idx = 0; idx < tileCoords.length; ++idx) { - const row = tileCoords[idx][1]; - const col = tileCoords[idx][0]; - const tileIdx = row + this.maze.map.COLS * col; - const tileElement = document.getElementById('tileElement' + tileIdx); - if (tileElement) { - callback(tileElement, this.maze.map.getCell(col, row)); - } - } - } - - scheduleWallHit(targetX, targetY, deltaX, deltaY, frame) { - // Play the animation of hitting the wall - if (this.maze.skin.hittingWallAnimation) { - var wallAnimationIcon = document.getElementById('wallAnimation'); - var numFrames = this.maze.skin.hittingWallAnimationFrameNumber || 0; - - if (numFrames > 1) { - - // The Scrat "wall" animation has him falling backwards into the water. - // This looks great when he falls into the water above him, but looks a - // little off when falling to the side/forward. Tune that by bumping the - // deltaY by one. Hacky, but looks much better - if (deltaY >= 0) { - deltaY += 1; - } - // animate our sprite sheet - var timePerFrame = 100; - this.scheduleSheetedMovement_({ - x: this.maze.pegmanX, - y: this.maze.pegmanY - }, { - x: deltaX, - y: deltaY - }, numFrames, timePerFrame, 'wall', - tiles.Direction.NORTH, true); - setTimeout(function () { - document.getElementById('wallPegman').setAttribute('visibility', 'hidden'); - }, numFrames * timePerFrame); - } else { - // active our gif - timeoutList.setTimeout(() => { - wallAnimationIcon.setAttribute('x', - this.maze.SQUARE_SIZE * (this.maze.pegmanX + 0.5 + deltaX * 0.5) - - wallAnimationIcon.getAttribute('width') / 2); - wallAnimationIcon.setAttribute('y', - this.maze.SQUARE_SIZE * (this.maze.pegmanY + 1 + deltaY * 0.5) - - wallAnimationIcon.getAttribute('height')); - wallAnimationIcon.setAttribute('visibility', 'visible'); - wallAnimationIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.hittingWallAnimation); - }, this.maze.stepSpeed / 2); - } - } - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, frame); - }, this.maze.stepSpeed); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX + deltaX / 4, this.maze.pegmanY + deltaY / 4, - frame); - }, this.maze.stepSpeed * 2); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, frame); - }, this.maze.stepSpeed * 3); - - if (this.maze.skin.wallPegmanAnimation) { - timeoutList.setTimeout(() => { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('visibility', 'hidden'); - this.updatePegmanAnimation_({ - idStr: 'wall', - row: this.maze.pegmanY, - col: this.maze.pegmanX, - direction: this.maze.pegmanD - }); - }, this.maze.stepSpeed * 4); - } - } - - scheduleObstacleHit(targetX, targetY, deltaX, deltaY, frame) { - // Play the animation - var obsId = targetX + this.maze.map.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.obstacleAnimation); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX + deltaX / 2, - this.maze.pegmanY + deltaY / 2, - frame); - }, this.maze.stepSpeed); - - // Replace the objects around obstacles with broken objects - if (this.maze.skin.largerObstacleAnimationTiles) { - timeoutList.setTimeout(() => { - this.updateSurroundingTiles_(targetY, targetX, tileElement => ( - tileElement.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.largerObstacleAnimationTiles - ) - )); - }, this.maze.stepSpeed); - } - - // Remove pegman - if (!this.maze.skin.nonDisappearingPegmanHittingObstacle) { - var pegmanIcon = document.getElementById('pegman'); - - timeoutList.setTimeout(function () { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, this.maze.stepSpeed * 2); - } - } - - scheduleLook(x, y, d) { - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - this.scheduleLookStep_(path, this.maze.stepSpeed * i); - } - } - - /** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ - scheduleLookStep_(path, delay) { - timeoutList.setTimeout(() => { - path.style.display = 'inline'; - window.setTimeout(function () { - path.style.display = 'none'; - }, this.maze.stepSpeed * 2); - }, delay); - } - - stopIdling() { - // Removing the idle animation and replace with pegman sprite - if (this.maze.skin.idlePegmanAnimation) { - var pegmanIcon = document.getElementById('pegman'); - var idlePegmanIcon = document.getElementById('idlePegman'); - idlePegmanIcon.setAttribute('visibility', 'hidden'); - pegmanIcon.setAttribute('visibility', 'visible'); - } - } - - /** - * Schedule the animations and sound for a dance. - * @param {boolean} victoryDance This is a victory dance after completing the - * puzzle (vs. dancing on load). - * @param {integer} timeAlloted How much time we have for our animations - */ - scheduleDance(victoryDance, timeAlloted) { - const finishIcon = document.getElementById('finish'); - - // Some skins (like scrat) have custom celebration animations we want to - // suport - if (victoryDance && this.maze.skin.celebrateAnimation) { - if (finishIcon) { - finishIcon.setAttribute('visibility', 'hidden'); - } - const numFrames = this.maze.skin.celebratePegmanRow; - const timePerFrame = timeAlloted / numFrames; - const start = { x: this.maze.pegmanX, y: this.maze.pegmanY }; - - this.scheduleSheetedMovement_( - { x: start.x, y: start.y }, - { x: 0, y: 0 }, - numFrames, - timePerFrame, - 'celebrate', - tiles.Direction.NORTH, - true, - ); - return; - } - - var originalFrame = tiles.directionToFrame(this.maze.pegmanD); - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, 16); - - // If victoryDance === true, play the goal animation, else reset it - if (victoryDance && finishIcon) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - this.maze.skin.goalAnimation); - } - - var danceSpeed = timeAlloted / 5; - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, 18); - }, danceSpeed); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, 20); - }, danceSpeed * 2); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, 18); - }, danceSpeed * 3); - timeoutList.setTimeout(() => { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, 20); - }, danceSpeed * 4); - - timeoutList.setTimeout(() => { - if (!victoryDance || this.maze.skin.turnAfterVictory) { - this.displayPegman(this.maze.pegmanX, this.maze.pegmanY, originalFrame); - } - - if (victoryDance && this.maze.skin.transparentTileEnding) { - this.setTileTransparent_(); - } - - if (this.maze.subtype.isWordSearch()) { - this.setPegmanTransparent_(); - } - }, danceSpeed * 5); - } - - /** - * Set the tiles to be transparent gradually. - */ - setTileTransparent_() { - var tileId = 0; - for (var y = 0; y < this.maze.map.ROWS; y++) { - for (var x = 0; x < this.maze.map.COLS; x++) { - // Tile sprite. - var tileElement = document.getElementById('tileElement' + tileId); - var tileAnimation = document.getElementById('tileAnimation' + tileId); - if (tileElement) { - tileElement.setAttribute('opacity', 0); - } - if (tileAnimation && tileAnimation.beginElement) { - // IE doesn't support beginElement, so check for it. - tileAnimation.beginElement(); - } - tileId++; - } - } - } - - setPegmanTransparent_() { - var pegmanFadeoutAnimation = document.getElementById('pegmanFadeoutAnimation'); - var pegmanIcon = document.getElementById('pegman'); - if (pegmanIcon) { - pegmanIcon.setAttribute('opacity', 0); - } - if (pegmanFadeoutAnimation && pegmanFadeoutAnimation.beginElement) { - // IE doesn't support beginElement, so check for it. - pegmanFadeoutAnimation.beginElement(); - } - } - - /** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} frame Direction (0 - 15) or dance (16 - 17). - */ - displayPegman(x, y, frame) { - var pegmanIcon = document.getElementById('pegman'); - var clipRect = document.getElementById('clipRect'); - displayPegman(this.maze.skin, pegmanIcon, clipRect, x, y, frame); - } -}; diff --git a/apps/src/maze/api.js b/apps/src/maze/api.js index d3eb04f62c9a2..d77adb9c04739 100644 --- a/apps/src/maze/api.js +++ b/apps/src/maze/api.js @@ -1,4 +1,4 @@ -var tiles = require('./tiles'); +var tiles = require('@code-dot-org/maze').tiles; var Direction = tiles.Direction; var MoveDirection = tiles.MoveDirection; var TurnDirection = tiles.TurnDirection; diff --git a/apps/src/maze/bee.js b/apps/src/maze/bee.js deleted file mode 100644 index 8bff13f6a5034..0000000000000 --- a/apps/src/maze/bee.js +++ /dev/null @@ -1,434 +0,0 @@ -import { randomValue } from '../utils'; -import Gatherer from './gatherer'; -import BeeCell from './beeCell'; -import BeeItemDrawer from './beeItemDrawer'; - -const UNLIMITED_HONEY = -99; -const UNLIMITED_NECTAR = 99; - -const EMPTY_HONEY = -98; // Hive with 0 honey -const EMPTY_NECTAR = 98; // flower with 0 honey - -const HONEY_SOUND = 'honey'; -const NECTAR_SOUND = 'nectar'; - -export default class Bee extends Gatherer { - constructor(maze, config) { - super(maze, config); - - this.defaultFlowerColor_ = (config.level.flowerType === 'redWithNectar' ? - 'red' : 'purple'); - if (this.defaultFlowerColor_ === 'purple' && - config.level.flowerType !== 'purpleNectarHidden') { - throw new Error(`bad flowerType for Bee: ${config.level.flowerType}`); - } - - // at each location, tracks whether user checked to see if it was a flower or - // honeycomb using an if block - this.userChecks_ = []; - - this.overrideStepSpeed = 2; - this.honey_ = undefined; - this.nectars_ = undefined; - } - - /** - * @override - */ - isBee() { - return true; - } - - /** - * @override - */ - getCellClass() { - return BeeCell; - } - - /** - * @override - */ - loadAudio(skin) { - if (skin.beeSound) { - this.maze_.loadAudio(skin.nectarSound, NECTAR_SOUND); - this.maze_.loadAudio(skin.honeySound, HONEY_SOUND); - } - } - - /** - * @override - */ - createDrawer(svg) { - this.drawer = new BeeItemDrawer(this.maze_.map, this.skin_, svg, this); - } - - /** - * Resets current state, for easy reexecution of tests - * @override - */ - reset() { - this.honey_ = 0; - // list of the locations we've grabbed nectar from - this.nectars_ = []; - for (let i = 0; i < this.maze_.map.currentStaticGrid.length; i++) { - this.userChecks_[i] = []; - for (let j = 0; j < this.maze_.map.currentStaticGrid[i].length; j++) { - this.userChecks_[i][j] = { - checkedForFlower: false, - checkedForHive: false, - checkedForNectar: false - }; - } - } - if (this.drawer) { - this.drawer.updateNectarCounter(this.nectars_); - this.drawer.updateHoneyCounter(this.honey_); - } - super.reset(); - } - - /** - * Get the total count of all honey collected - */ - getHoneyCount() { - return this.honey_; - } - - /** - * Get the total count of all nectar collected - */ - getNectarCount() { - return this.nectars_.length; - } - - /** - * @param {boolean} userCheck Is this being called from user code - */ - isHive(row, col, userCheck=false) { - if (userCheck) { - this.userChecks_[row][col].checkedForHive = true; - } - const cell = this.maze_.map.currentStaticGrid[row][col]; - return cell.isHive(); - } - - /** - * @param {boolean} userCheck Is this being called from user code - */ - isFlower(row, col, userCheck=false) { - if (userCheck) { - this.userChecks_[row][col].checkedForFlower = true; - } - const cell = this.maze_.map.currentStaticGrid[row][col]; - return cell.isFlower(); - } - - /** - * Returns true if cell should be clovered by a cloud while running - */ - isCloudable(row, col) { - return this.maze_.map.currentStaticGrid[row][col].isStaticCloud(); - } - - /** - * The only clouds we care about checking are clouds that were defined - * as static clouds in the original grid; quantum clouds will handle - * 'requiring' checks through their quantum nature. - */ - shouldCheckCloud(row, col) { - return this.maze_.map.getVariableCell(row, col).isStaticCloud(); - } - - /** - * Likewise, the only flowers we care about checking are flowers that - * were defined as purple flowers without a variable range in the - * original grid; variable range flowers will handle 'requiring' checks - * through their quantum nature. - */ - shouldCheckPurple(row, col) { - return this.isPurpleFlower(row, col) && !this.maze_.map.getVariableCell(row, col).isVariableRange(); - } - - /** - * Returns true if cell has been checked for either a flower or a hive - */ - checkedCloud(row, col) { - return this.userChecks_[row][col].checkedForFlower || this.userChecks_[row][col].checkedForHive; - } - - /** - * Did we check every flower/honey that was covered by a cloud? - */ - checkedAllClouded() { - for (let row = 0; row < this.maze_.map.currentStaticGrid.length; row++) { - for (let col = 0; col < this.maze_.map.currentStaticGrid[row].length; col++) { - if (this.shouldCheckCloud(row, col) && !this.checkedCloud(row, col)) { - return false; - } - } - } - return true; - } - - /** - * Did we check every purple flower - */ - checkedAllPurple() { - for (let row = 0; row < this.maze_.map.currentStaticGrid.length; row++) { - for (let col = 0; col < this.maze_.map.currentStaticGrid[row].length; col++) { - if (this.shouldCheckPurple(row, col) && !this.userChecks_[row][col].checkedForNectar) { - return false; - } - } - } - return true; - } - - /** - * Flowers are either red or purple. This function returns true if a flower is red. - */ - isRedFlower(row, col) { - if (!this.isFlower(row, col, false)) { - return false; - } - - // If the flower has been overridden to be red, return true. - // Otherwise, if the flower has been overridden to be purple, return - // false. If neither of those are true, then the flower is whatever - // the default flower color is. - if (this.maze_.map.currentStaticGrid[row][col].isRedFlower()) { - return true; - } else if (this.maze_.map.currentStaticGrid[row][col].isPurpleFlower()) { - return false; - } else { - return this.defaultFlowerColor_ === 'red'; - } - } - - /** - * Row, col contains a flower that is purple - */ - isPurpleFlower(row, col) { - return this.isFlower(row, col, false) && !this.isRedFlower(row, col); - } - - /** - * How much more honey can the hive at (row, col) produce before it hits the goal - */ - hiveRemainingCapacity(row, col) { - if (!this.isHive(row, col)) { - return 0; - } - - const val = this.getValue(row, col); - if (val === UNLIMITED_HONEY) { - return Infinity; - } - if (val === EMPTY_HONEY) { - return 0; - } - return val; - } - - /** - * How much more nectar can be collected from the flower at (row, col) - */ - flowerRemainingCapacity(row, col) { - if (!this.isFlower(row, col)) { - return 0; - } - - const val = this.getValue(row, col); - if (val === UNLIMITED_NECTAR) { - return Infinity; - } - if (val === EMPTY_NECTAR) { - return 0; - } - return val; - } - - /** - * Update model to represent made honey. Does no validation - */ - madeHoneyAt(row, col) { - if (this.getValue(row, col) !== UNLIMITED_HONEY) { - this.setValue(row, col, this.getValue(row, col) - 1); - } - - this.honey_ += 1; - } - - /** - * Update model to represent gathered nectar. Does no validation - */ - gotNectarAt(row, col) { - if (this.getValue(row, col) !== UNLIMITED_NECTAR) { - this.setValue(row, col, this.getValue(row, col) - 1); - } - - this.nectars_.push({ row, col }); - } - - // API - - /** - * Attempt to harvest nectar from the current location; terminate the - * execution if this is not a valid place at which to get nectar. - * - * This method is preferred over animateGetNectar for "headless" operation (ie - * when validating quantum levels) - * - * @fires notAtFlower - * @fires flowerEmpty - * @return {boolean} whether or not this attempt was successful - */ - tryGetNectar() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - // Make sure we're at a flower. - if (!this.isFlower(row, col)) { - this.emit('notAtFlower'); - return false; - } - // Nectar is positive. Make sure we have it. - if (this.flowerRemainingCapacity(row, col) === 0) { - this.emit('flowerEmpty'); - return false; - } - - this.gotNectarAt(row, col); - return true; - } - - /** - * Attempt to make honey at the current location; terminate the execution if - * this is not a valid place at which to make honey. - * Note that this deliberately does not check whether bee has gathered nectar. - * - * This method is preferred over animateGetHoney for "headless" operation (ie - * when validating quantum levels) - * - * @fires notAtHive - * @fires hiveFull - * @return {boolean} whether or not this attempt was successful - */ - tryMakeHoney() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - if (!this.isHive(row, col)) { - this.emit('notAtHive'); - return false; - } - if (this.hiveRemainingCapacity(row, col) === 0) { - this.emit('hiveFull'); - return false; - } - - this.madeHoneyAt(row, col); - return true; - } - - nectarRemaining(userCheck=false) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - if (userCheck) { - this.userChecks_[row][col].checkedForNectar = true; - } - - return this.flowerRemainingCapacity(row, col); - } - - honeyAvailable() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - return this.hiveRemainingCapacity(row, col); - } - - /** - * Display the harvesting of nectar from the current location; raise a runtime - * error if the current location is not a valid spot from which to gather - * nectar. - * - * This method is preferred over tryGetNectar for live operation (ie when - * actually displaying something to the user) - * - * @throws Will throw an error if the current cell has no nectar. - */ - animateGetNectar() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - if (this.getValue(row, col) <= 0) { - throw new Error("Shouldn't be able to end up with a nectar animation if " + - "there was no nectar to be had"); - } - - this.playAudio_(NECTAR_SOUND); - this.gotNectarAt(row, col); - - this.drawer.updateItemImage(row, col, true); - this.drawer.updateNectarCounter(this.nectars_); - } - - /** - * Display the making of honey from the current location; raise a runtime - * error if the current location is not a valid spot at which to make honey. - * - * This method is preferred over tryMakeHoney for live operation (ie when - * actually displaying something to the user) - * - * @throws Will throw an error if the current cell is not a hive. - */ - animateMakeHoney() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - if (!this.isHive(row, col)) { - throw new Error("Shouldn't be able to end up with a honey animation if " + - "we arent at a hive or dont have nectar"); - } - - this.playAudio_(HONEY_SOUND); - this.madeHoneyAt(row, col); - - this.drawer.updateItemImage(row, col, true); - this.drawer.updateHoneyCounter(this.honey_); - } - - /** - * @override - */ - getEmptyTile(x, y, adjacentToPath, wallMap) { - // begin with three trees - var tileChoices = ['null3', 'null4', 'null0']; - var noTree = 'null1'; - // want it to be more likely to have a tree when adjacent to path - var n = adjacentToPath ? tileChoices.length * 2 : tileChoices.length * 6; - for (var i = 0; i < n; i++) { - tileChoices.push(noTree); - } - - return randomValue(tileChoices); - } - - /** - * @override - */ - drawTile(svg, tileSheetLocation, row, col, tileId) { - super.drawTile(svg, tileSheetLocation, row, col, tileId); - - // Draw checkerboard - if ((row + col) % 2 === 0) { - const isPath = !this.isWallOrOutOfBounds_(col, row); - this.drawer.addCheckerboardTile(row, col, isPath); - } - } - -} - diff --git a/apps/src/maze/beeCell.js b/apps/src/maze/beeCell.js deleted file mode 100644 index 2df249f400d3f..0000000000000 --- a/apps/src/maze/beeCell.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @overview BeeCell represents the contets of the grid elements for Bee. - * Bee BeeCells are more complex than many other kinds of cell; they can be - * "hidden" with clouds, they can represent multiple different kinds of - * element (flower, hive), some of which can be multiple colors (red, - * purple), and which can have a range of possible values. - * - * Some cells can also be "variable", meaning that their contents are - * not static but can in fact be randomized between runs. - */ - -import Cell from './cell'; -import tiles from './tiles'; -const SquareType = tiles.SquareType; - -export default class BeeCell extends Cell { - constructor(tileType, featureType, value, cloudType, flowerColor, range) { - - // BeeCells require features to have values - if (featureType === BeeCell.FeatureType.NONE) { - value = undefined; - range = undefined; - } - - super(tileType, value, range); - - /** - * @type {Number} - */ - this.featureType_ = featureType; - - /** - * @type {Number} - */ - this.flowerColor_ = flowerColor; - - /** - * @type {Number} - */ - this.cloudType_ = cloudType; - } - - /** - * @return {boolean} - */ - isFlower() { - return this.featureType_ === FeatureType.FLOWER; - } - - /** - * @return {boolean} - */ - isHive() { - return this.featureType_ === FeatureType.HIVE; - } - - /** - * Flowers can be red, purple, or undefined. - * @return {boolean} - */ - isRedFlower() { - return this.isFlower() && this.flowerColor_ === FlowerColor.RED; - } - - /** - * Flowers can be red, purple, or undefined. - * @return {boolean} - */ - isPurpleFlower() { - return this.isFlower() && this.flowerColor_ === FlowerColor.PURPLE; - } - - /** - * @return {boolean} - */ - isStaticCloud() { - return this.cloudType_ === CloudType.STATIC; - } - - /** - * @return {boolean} - */ - isVariableCloud() { - if (this.cloudType_ === CloudType.NONE || this.cloudType_ === CloudType.STATIC) { - return false; - } - return true; - } - - /** - * @return {boolean} - */ - isVariable() { - return this.isVariableRange() || this.isVariableCloud(); - } - - /** - * Variable cells can represent multiple possible kinds of grid assets, - * whereas non-variable cells can represent only a single kind. This - * method returns an array of non-variable BeeCells based on this BeeCell's - * configuration. - * @return {BeeCell[]} - * @override - */ - getPossibleGridAssets() { - let possibilities = []; - if (this.isVariableCloud()) { - const flower = new BeeCell(this.tileType_, FeatureType.FLOWER, this.originalValue_, CloudType.STATIC, this.flowerColor_); - const hive = new BeeCell(this.tileType_, FeatureType.HIVE, this.originalValue_, CloudType.STATIC); - const nothing = new BeeCell(this.tileType_, FeatureType.NONE, undefined, CloudType.STATIC); - switch (this.cloudType_) { - case CloudType.HIVE_OR_FLOWER: - possibilities = [flower, hive]; - break; - case CloudType.FLOWER_OR_NOTHING: - possibilities = [flower, nothing]; - break; - case CloudType.HIVE_OR_NOTHING: - possibilities = [hive, nothing]; - break; - case CloudType.ANY: - possibilities = [flower, hive, nothing]; - break; - } - } else if (this.isVariableRange()) { - for (let i = this.originalValue_; i <= this.range_; i++) { - possibilities.push(new BeeCell(this.tileType_, FeatureType.FLOWER, i, CloudType.NONE, FlowerColor.PURPLE)); - } - } else { - possibilities.push(this); - } - - return possibilities; - } - - /** - * Serializes this BeeCell into JSON - * @return {Object} - * @override - */ - serialize() { - return Object.assign({}, super.serialize(), { - featureType: this.featureType_, - cloudType: this.cloudType_, - flowerColor: this.flowerColor_, - }); - } -} - -const FeatureType = BeeCell.FeatureType = { - NONE: undefined, - HIVE: 0, - FLOWER: 1, - VARIABLE: 2 -}; - -const CloudType = BeeCell.CloudType = { - NONE: undefined, - STATIC: 0, - HIVE_OR_FLOWER: 1, - FLOWER_OR_NOTHING: 2, - HIVE_OR_NOTHING: 3, - ANY: 4 -}; - -const FlowerColor = BeeCell.FlowerColor = { - DEFAULT: undefined, - RED: 0, - PURPLE: 1 -}; - -/** - * Creates a new BeeCell from serialized JSON - * @param {Object} - * @return {BeeCell} - * @override - */ -BeeCell.deserialize = serialized => new BeeCell( - serialized.tileType, - serialized.featureType, - serialized.value, - serialized.cloudType, - serialized.flowerColor, - serialized.range -); - -/** - * @param {String|Number} mapCell - * @param {String|Number} initialDirtCell - * @return {BeeCell} - * @override - * @see Cell.parseFromOldValues - */ -BeeCell.parseFromOldValues = (mapCell, initialDirtCell) => { - mapCell = mapCell.toString(); - initialDirtCell = parseInt(initialDirtCell); - let tileType, featureType, value, cloudType, flowerColor; - - if (!isNaN(initialDirtCell) && mapCell.match(/[1|R|P|FC]/) && initialDirtCell !== 0) { - tileType = SquareType.OPEN; - featureType = initialDirtCell > 0 ? FeatureType.FLOWER : FeatureType.HIVE; - value = Math.abs(initialDirtCell); - cloudType = (mapCell === 'FC') ? CloudType.STATIC : CloudType.NONE; - flowerColor = (mapCell === 'R') ? FlowerColor.RED : (mapCell === 'P') ? FlowerColor.PURPLE : FlowerColor.DEFAULT; - } else { - tileType = parseInt(mapCell); - } - return new BeeCell(tileType, featureType, value, cloudType, flowerColor); -}; diff --git a/apps/src/maze/beeItemDrawer.js b/apps/src/maze/beeItemDrawer.js deleted file mode 100644 index cae7948860593..0000000000000 --- a/apps/src/maze/beeItemDrawer.js +++ /dev/null @@ -1,229 +0,0 @@ -import Drawer, { SQUARE_SIZE, SVG_NS } from './drawer'; - -/** - * Extends Drawer to draw flowers/honeycomb for bee. - * @param {MaseMap} map - * @param {Object} skin The app's skin, used to get URLs for our images - * @param {Bee} bee The maze's Bee object. - */ -export default class BeeItemDrawer extends Drawer { - constructor(map, skin, svg, bee) { - super(map, '', svg); - this.skin_ = skin; - this.bee_ = bee; - - this.honeyImages_ = []; - this.nectarImages_ = []; - this.pegman_ = null; - - // is item currently covered by a cloud? - this.clouded_ = undefined; - this.resetClouded(); - } - - /** - * @override - */ - getAsset(prefix, row, col) { - switch (prefix) { - case 'cloud': - return this.skin_.cloud; - case 'cloudAnimation': - return this.skin_.cloudAnimation; - case 'beeItem': - if (this.bee_.isHive(row, col, false)) { - return this.skin_.honey; - } else if (this.bee_.isFlower(row, col, false)) { - return this.flowerImageHref_(row, col); - } - } - } - - /** - * Generic reset function, shared by DirtDrawer so that we can call - * drawer.reset() blindly. - * @override - */ - reset() { - this.resetClouded(); - } - - /** - * Resets our tracking of clouded/revealed squares. Used on - * initialization and also to reset the drawer between randomized - * conditionals runs. - */ - resetClouded() { - this.clouded_ = this.map_.currentStaticGrid.map(row => []); - } - - /** - * Override DirtDrawer's updateItemImage. - * @override - * @param {number} row - * @param {number} col - * @param {boolean} running Is user code currently running - */ - updateItemImage(row, col, running) { - - var isCloudable = this.bee_.isCloudable(row, col); - var isClouded = !running && isCloudable; - var wasClouded = isCloudable && (this.clouded_[row][col] === true); - - var counterText; - var ABS_VALUE_UNLIMITED = 99; // Repesents unlimited nectar/honey. - var ABS_VALUE_ZERO = 98; // Represents zero nectar/honey. - var absVal = Math.abs(this.bee_.getValue(row, col)); - if (isClouded || isNaN(absVal)) { - counterText = ""; - } else if (!running && this.bee_.isPurpleFlower(row, col)) { - // Initially, hide counter values of purple flowers. - counterText = "?"; - } else if (absVal === ABS_VALUE_UNLIMITED) { - counterText = ""; - } else if (absVal === ABS_VALUE_ZERO) { - counterText = "0"; - } else { - counterText = "" + absVal; - } - - // Display the images. - let img = this.drawImage_('beeItem', row, col); - this.updateOrCreateText_('counter', row, col, img ? counterText : ''); - - if (isClouded) { - this.showCloud_(row, col); - this.clouded_[row][col] = true; - } else if (wasClouded) { - this.hideCloud_(row, col); - this.clouded_[row][col] = false; - } - } - - createCounterImage_(prefix, i, row, href) { - var id = prefix + (i + 1); - var image = document.createElementNS(SVG_NS, 'image'); - image.setAttribute('id', id); - image.setAttribute('width', SQUARE_SIZE); - image.setAttribute('height', SQUARE_SIZE); - image.setAttribute('y', row * SQUARE_SIZE); - - image.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', href); - - this.svg_.insertBefore(image, this.getPegmanElement_()); - - return image; - } - - flowerImageHref_(row, col) { - return this.bee_.isRedFlower(row, col) ? this.skin_.redFlower : - this.skin_.purpleFlower; - } - - updateHoneyCounter(honeyCount) { - for (var i = 0; i < honeyCount; i++) { - if (!this.honeyImages_[i]) { - this.honeyImages_[i] = this.createCounterImage_('honey', i, 1, - this.skin_.honey); - } - - var deltaX = SQUARE_SIZE; - if (honeyCount > 8) { - deltaX = (8 - 1) * SQUARE_SIZE / (honeyCount - 1); - } - this.honeyImages_[i].setAttribute('x', i * deltaX); - } - - for (i = 0; i < this.honeyImages_.length; i++) { - this.honeyImages_[i].setAttribute('display', i < honeyCount ? 'block' : 'none'); - } - } - - updateNectarCounter(nectars) { - var nectarCount = nectars.length; - // create any needed images - for (var i = 0; i < nectarCount; i++) { - var href = this.flowerImageHref_(nectars[i].row, nectars[i].col); - - if (!this.nectarImages_[i]) { - this.nectarImages_[i] = this.createCounterImage_('nectar', i, 0, href); - } - - var deltaX = SQUARE_SIZE; - if (nectarCount > 8) { - deltaX = (8 - 1) * SQUARE_SIZE / (nectarCount - 1); - } - this.nectarImages_[i].setAttribute('x', i * deltaX); - this.nectarImages_[i].setAttributeNS('http://www.w3.org/1999/xlink', - 'xlink:href', href); - } - - for (i = 0; i < this.nectarImages_.length; i++) { - this.nectarImages_[i].setAttribute('display', i < nectarCount ? 'block' : 'none'); - } - } - - /** - * Cache pegman element - */ - getPegmanElement_() { - if (!this.pegman_) { - this.pegman_ = document.getElementsByClassName('pegman-location')[0]; - } - return this.pegman_; - } - - /** - * Show the cloud icon. - */ - showCloud_(row, col) { - this.drawImage_('cloud', row, col); - - // Make sure the animation is cached by the browser. - this.displayCloudAnimation_(row, col, false /* animate */); - } - - /** - * Hide the cloud icon, and display the cloud hiding animation. - */ - hideCloud_(row, col) { - var cloudElement = document.getElementById(Drawer.cellId('cloud', row, col)); - if (cloudElement) { - cloudElement.setAttribute('visibility', 'hidden'); - } - - this.displayCloudAnimation_(row, col, true /* animate */); - } - - /** - * Create the cloud animation element, and perform the animation if necessary - */ - displayCloudAnimation_(row, col, animate) { - let cloudAnimation = this.getOrCreateImage_('cloudAnimation', row, col, false); - - // We want to create the element event if we're not animating yet so that we - // can make sure it gets loaded. - cloudAnimation.setAttribute('visibility', animate ? 'visible' : 'hidden'); - } - - /** - * Draw our checkerboard tile, making path tiles lighter. For non-path tiles, we - * want to be sure that the checkerboard square is below the tile element (i.e. - * the trees). - */ - addCheckerboardTile(row, col, isPath) { - var rect = document.createElementNS(SVG_NS, 'rect'); - rect.setAttribute('width', SQUARE_SIZE); - rect.setAttribute('height', SQUARE_SIZE); - rect.setAttribute('x', col * SQUARE_SIZE); - rect.setAttribute('y', row * SQUARE_SIZE); - rect.setAttribute('fill', '#78bb29'); - rect.setAttribute('opacity', isPath ? 0.2 : 0.5); - if (isPath) { - this.svg_.appendChild(rect); - } else { - var tile = this.svg_.querySelector(`#tileElement${row * 8 + col}`); - this.svg_.insertBefore(rect, tile); - } - } -} diff --git a/apps/src/maze/blocks.js b/apps/src/maze/blocks.js index 771599ae720de..131628095c577 100644 --- a/apps/src/maze/blocks.js +++ b/apps/src/maze/blocks.js @@ -21,11 +21,13 @@ * @fileoverview Demonstration of Blockly: Solving a maze. * @author fraser@google.com (Neil Fraser) */ -var msg = require('./locale'); var commonMsg = require('@cdo/locale'); -var codegen = require('../lib/tools/jsinterpreter/codegen'); +var mazeUtils = require('@code-dot-org/maze').utils; + var blockUtils = require('../block_utils'); -var mazeUtils = require('./mazeUtils'); +var codegen = require('../lib/tools/jsinterpreter/codegen'); + +var msg = require('./locale'); // Install extensions to Blockly's language and JavaScript generator. exports.install = function (blockly, blockInstallOptions) { diff --git a/apps/src/maze/cell.js b/apps/src/maze/cell.js deleted file mode 100644 index eb468c5646c1d..0000000000000 --- a/apps/src/maze/cell.js +++ /dev/null @@ -1,169 +0,0 @@ -class Cell { - constructor(tileType, value, range) { - - /** - * @type {Number} - */ - this.tileType_ = tileType; - - /** - * @type {Number} - */ - this.originalValue_ = value; - - /** - * @type {Number} - */ - this.currentValue_ = undefined; - this.resetCurrentValue(); - - /** - * @type {Number} - */ - this.range_ = isNaN(range) ? value : range; - } - - /** - * Returns a new Cell that's an exact replica of this one - * @return {Cell} - */ - clone() { - // serialization/deserialization captures the configuration - const newCell = this.constructor.deserialize(this.serialize()); - - // currentValue assignment captures the state - newCell.setCurrentValue(this.currentValue_); - return newCell; - } - - /** - * @return {Number} - */ - getTile() { - return this.tileType_; - } - - /** - * @return {boolean} - */ - hasValue() { - return this.currentValue_ !== undefined; - } - - /** - * @return {boolean} - */ - isDirt() { - return this.currentValue_ !== undefined; - } - - /** - * @return {boolean} - */ - isVariableRange() { - return this.range_ !== this.originalValue_; - } - - /** - * @return {boolean} - */ - isVariable() { - return this.isVariableRange(); - } - - /** - * @return {Number} - */ - getOriginalValue() { - return this.originalValue_; - } - - /** - * @return {Number} - */ - getCurrentValue() { - return this.currentValue_; - } - - /** - * @param {Number} - */ - setCurrentValue(val) { - this.currentValue_ = val; - } - - resetCurrentValue() { - this.currentValue_ = this.originalValue_; - } - - /** - * Variable cells can represent a range of possible values. This method - * returns an array of non-variable Cells based on this Cell's - * configuration. - * @return {Cell[]} - */ - getPossibleGridAssets() { - const possibilities = []; - if (this.isVariableRange()) { - // range can be greater than or less than original value - const min = Math.min(this.originalValue_, this.range_); - const max = Math.max(this.originalValue_, this.range_); - for (let i = min; i <= max; i++) { - possibilities.push(new Cell(this.tileType_, i)); - } - } else { - possibilities.push(this); - } - - return possibilities; - } - - /** - * Serializes this Cell into JSON - * @return {Object} - */ - serialize() { - return { - tileType: this.tileType_, - value: this.originalValue_, - range: this.range_ - }; - } -} - -export default Cell; - -/** - * Creates a new Cell from serialized JSON - * @param {Object} - * @return {Cell} - */ -Cell.deserialize = serialized => new Cell( - serialized.tileType, - serialized.value, - serialized.range -); - -/** - * Creates a new Cell from a mapCell and an initialDirtCell. This - * represents the old style of storing map data, and should not be used - * for any new levels. Note that this style does not support new - * features such as dynamic ranges or new cloud types. Only used for - * backwards compatibility. - * @param {String|Number} mapCell - * @param {String|Number} initialDirtCell - * @return {Cell} - */ -Cell.parseFromOldValues = (mapCell, initialDirtCell) => { - mapCell = parseInt(mapCell); - initialDirtCell = parseInt(initialDirtCell); - - let tileType, value; - - tileType = parseInt(mapCell); - if (!isNaN(initialDirtCell) && initialDirtCell !== 0) { - value = initialDirtCell; - } - - return new Cell(tileType, value); -}; diff --git a/apps/src/maze/collector.js b/apps/src/maze/collector.js deleted file mode 100644 index 505ecdc17c50a..0000000000000 --- a/apps/src/maze/collector.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @fileoverview defines a new Maze Level sub-type: Collector. - * Collector levels are simple Maze levels that define a set of - * collectible items on the screen which the user can programmatically - * collect, and which strictly enforce the concept of block limits. - * Success is primarily determined by remaining within the block limit, - * and secondarily determined by collecting at least one of the - * available collectibles. - */ - - -import Subtype from './subtype'; -import CollectorDrawer from './collectorDrawer'; - -export default class Collector extends Subtype { - /** - * @fires collected - */ - scheduleDirtChange(row, col) { - super.scheduleDirtChange(row, col); - - // Play one of our various collect sounds, looping through them - if (this.collectSoundsCount) { - this.collectSoundsI = this.collectSoundsI || 0; - this.playAudio_('collect' + this.collectSoundsI); - this.collectSoundsI += 1; - this.collectSoundsI %= this.collectSoundsCount; - } - this.emit("collected", this.getTotalCollected()); - } - - /** - * @override - */ - isCollector() { - return true; - } - - /** - * Attempt to collect from the specified location; terminate the execution if - * there is nothing there to collect. - * - * Note that the animation for this action is handled by the default - * "scheduleDig" operation - * - * @fires collectedTooMany - * @return {boolean} whether or not this attempt was successful - */ - tryCollect(row, col) { - const currVal = this.maze_.map.getValue(row, col); - - if (currVal === undefined || currVal < 1) { - this.emit('collectedTooMany'); - return false; - } - - this.maze_.map.setValue(row, col, currVal - 1); - return true; - } - - /** - * @return {number} The number of collectibles collected - */ - getTotalCollected() { - let count = 0; - this.maze_.map.forEachCell((cell, x, y) => { - if (cell.isDirt()) { - count += (cell.getOriginalValue() - cell.getCurrentValue()); - } - }); - return count; - } - - /** - * @override - */ - loadAudio(skin) { - if (skin.collectSounds) { - this.collectSoundsCount = skin.collectSounds.length; - skin.collectSounds.forEach((sound, i) => { - this.maze_.loadAudio(sound, 'collect' + i); - }); - } - } - - /** - * @override - */ - createDrawer(svg) { - this.drawer = new CollectorDrawer(this.maze_.map, this.skin_.goal, svg); - } - - /** - * @override - */ - getEmptyTile() { - return 'null0'; - } - - /** - * @override - */ - drawTile(svg, tileSheetLocation, row, col, tileId) { - super.drawTile(svg, tileSheetLocation, row, col, tileId); - this.drawCorners(svg, row, col, tileId); - } - - drawCorners(svg, row, col, tileId) { - const corners = { - 'NE': [1, -1], - 'NW': [-1, -1], - 'SE': [1, 1], - 'SW': [-1, 1], - }; - - const SVG_NS = 'http://www.w3.org/2000/svg'; - const SQUARE_SIZE = 50; - - const pegmanElement = svg.getElementsByClassName('pegman-location')[0]; - - if (!this.isWallOrOutOfBounds_(col, row)) { - Object.keys(corners).filter(corner => { - // we need a corner if there is a wall in the corner direction and open - // space in the two cardinal directions "surrounding" the corner - const direction = corners[corner]; - const needsCorner = !this.isWallOrOutOfBounds_(col + direction[0], row) && - !this.isWallOrOutOfBounds_(col, row + direction[1]) && - this.isWallOrOutOfBounds_(col + direction[0], row + direction[1]); - - return needsCorner; - }).forEach(corner => { - const tileClip = document.createElementNS(SVG_NS, 'clipPath'); - tileClip.setAttribute('id', `tileCorner${corner}ClipPath${tileId}`); - const tileClipRect = document.createElementNS(SVG_NS, 'rect'); - tileClipRect.setAttribute('width', SQUARE_SIZE / 2); - tileClipRect.setAttribute('height', SQUARE_SIZE / 2); - - // clip the asest to only the quadrant we care about - const direction = corners[corner]; - tileClipRect.setAttribute('x', col * SQUARE_SIZE + (direction[0] + 1) * SQUARE_SIZE / 4); - tileClipRect.setAttribute('y', row * SQUARE_SIZE + (direction[1] + 1) * SQUARE_SIZE / 4); - tileClip.appendChild(tileClipRect); - svg.appendChild(tileClip); - - // Create image. - const img = document.createElementNS(SVG_NS, 'image'); - img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.skin_.corners); - img.setAttribute('height', SQUARE_SIZE); - img.setAttribute('width', SQUARE_SIZE); - img.setAttribute('x', SQUARE_SIZE * col); - img.setAttribute('y', SQUARE_SIZE * row); - img.setAttribute('id', `tileCorner${corner}${tileId}`); - img.setAttribute('clip-path', `url(#${tileClip.id})`); - svg.insertBefore(img, pegmanElement); - }); - } - } -} diff --git a/apps/src/maze/collectorDrawer.js b/apps/src/maze/collectorDrawer.js deleted file mode 100644 index a67270781be15..0000000000000 --- a/apps/src/maze/collectorDrawer.js +++ /dev/null @@ -1,42 +0,0 @@ -import Drawer from './drawer'; - -/** - * Extends Drawer to draw collectibles for Collector - */ -export default class CollectorDrawer extends Drawer { - - /** - * @override - */ - drawImage_(prefix, row, col) { - const img = super.drawImage_(prefix, row, col); - const val = this.map_.getValue(row, col); - img.setAttribute('visibility', val ? 'visible' : 'hidden'); - return img; - } - - /** - * @override - */ - updateItemImage(row, col, running) { - if (this.shouldUpdateItemImage(row, col, running)) { - // update image - super.updateItemImage(row, col); - - // update counter - const counterText = this.map_.getValue(row, col) || null; - this.updateOrCreateText_('counter', row, col, counterText); - } - } - - /** - * Should the specified row and column be updated? - * @param {number} row - * @param {number} col - * @return {boolean} - */ - shouldUpdateItemImage(row, col) { - const cell = this.map_.getCell(row, col); - return cell && cell.getOriginalValue(); - } -} diff --git a/apps/src/maze/dirtDrawer.js b/apps/src/maze/dirtDrawer.js deleted file mode 100644 index 1c3b31f20315b..0000000000000 --- a/apps/src/maze/dirtDrawer.js +++ /dev/null @@ -1,88 +0,0 @@ -import Drawer, { SQUARE_SIZE } from './drawer'; - -// The number line is [-inf, min, min+1, ... no zero ..., max-1, max, +inf] -const DIRT_MAX = 10; -const DIRT_COUNT = DIRT_MAX * 2 + 2; -const ASSET_UNCLIPPED_WIDTH = SQUARE_SIZE * DIRT_COUNT; - -/** - * Extends Drawer to draw dirt piles for Farmer. - */ -export default class DirtDrawer extends Drawer { - constructor(map, dirtAsset, svg) { - super(map, dirtAsset, svg); - } - - getAsset(prefix, row, col) { - let val = this.map_.getValue(row, col) || 0; - return (val === 0) ? undefined : super.getAsset(prefix, row, col); - } - - /** - * @override - */ - updateItemImage(row, col, running) { - let img = super.updateItemImage(row, col, running); - - if (!img) { - return; - } - - let val = this.map_.getValue(row, col) || 0; - - // If the cell is a variable cell and we are not currently running, - // draw it as either a max-height pile or a max-depth pit. - // Also draw a "?"; other numbers are automatically included in the image - if (this.map_.getVariableCell(row, col).isVariable()) { - if (running) { - this.updateOrCreateText_('counter', row, col, ''); - } else { - val = (val < 0) ? -11 : 11; - this.updateOrCreateText_('counter', row, col, '?'); - } - } - - let spriteIndex = DirtDrawer.spriteIndexForDirt(val); - let hiddenImage = spriteIndex < 0; - img.setAttribute('visibility', hiddenImage ? 'hidden' : 'visible'); - if (!hiddenImage) { - img.setAttribute('x', SQUARE_SIZE * (col - spriteIndex)); - } - - return img; - } - - /** - * @override - */ - getOrCreateImage_(prefix, row, col) { - let img = super.getOrCreateImage_(prefix, row, col); - img && img.setAttribute('width', ASSET_UNCLIPPED_WIDTH); - return img; - } - - /** - * Given a dirt value, returns the index of the sprite to use in our spritesheet. - * Returns -1 if we want to display no sprite. - * @param {number} val - * @return {number} - */ - static spriteIndexForDirt(val) { - let spriteIndex; - - if (val === 0) { - spriteIndex = -1; - } else if (val < -DIRT_MAX) { - spriteIndex = 0; - } else if (val < 0) { - spriteIndex = DIRT_MAX + val + 1; - } else if (val > DIRT_MAX) { - spriteIndex = DIRT_COUNT - 1; - } else if (val > 0) { - spriteIndex = DIRT_MAX + val; - } - - return spriteIndex; - } - -} diff --git a/apps/src/maze/drawMap.js b/apps/src/maze/drawMap.js deleted file mode 100644 index 31a35a2fda6c2..0000000000000 --- a/apps/src/maze/drawMap.js +++ /dev/null @@ -1,148 +0,0 @@ -import {SVG_NS} from '../constants'; -import tiles from './tiles'; -import {createUuid} from '../utils'; - -const SquareType = tiles.SquareType; - -// Height and width of the goal and obstacles. -const MARKER_HEIGHT = 43; -const MARKER_WIDTH = 50; - -/** - * Calculate the y coordinates for pegman sprite. - */ -export function getPegmanYForRow(skin, mazeRow, squareSize = 50) { - return Math.floor(squareSize * (mazeRow + 0.5) - skin.pegmanHeight / 2 + - skin.pegmanYOffset); -} - -export function displayPegman(skin, pegmanIcon, clipRect, x, y, frame, squareSize = 50) { - const xOffset = skin.pegmanXOffset || 0; - pegmanIcon.setAttribute('x', - x * squareSize - frame * skin.pegmanWidth + 1 + xOffset); - pegmanIcon.setAttribute('y', getPegmanYForRow(skin, y)); - - clipRect.setAttribute('x', x * squareSize + 1 + xOffset); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -} - -export default function drawMap(svg, skin, subtype, map, squareSize = 50) { - const MAZE_WIDTH = map.COLS * squareSize; - const MAZE_HEIGHT = map.ROWS * squareSize; - - var x, y, tile; - - // Draw the outer square. - var square = document.createElementNS(SVG_NS, 'rect'); - square.setAttribute('width', MAZE_WIDTH); - square.setAttribute('height', MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (skin.background) { - tile = document.createElementNS(SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - skin.background); - tile.setAttribute('height', MAZE_HEIGHT); - tile.setAttribute('width', MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - subtype.drawMapTiles(svg); - - // Add hint path. - const hintPath = document.createElementNS(SVG_NS, 'path'); - hintPath.setAttribute('id', 'hintPath'); - hintPath.setAttribute('stroke', '#c00'); - hintPath.setAttribute('stroke-width', '5'); - hintPath.setAttribute('fill', 'none'); - hintPath.setAttribute('stroke-linecap', 'round'); - hintPath.setAttribute('stroke-linejoin', 'round'); - svg.appendChild(hintPath); - - if (subtype.start) { - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - const pegmanClip = document.createElementNS(SVG_NS, 'clipPath'); - const pegmanClipId = `pegmanClipPath-${createUuid()}`; - pegmanClip.setAttribute('id', pegmanClipId); - const clipRect = document.createElementNS(SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', skin.pegmanWidth); - clipRect.setAttribute('height', skin.pegmanHeight); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add pegman. - var pegmanIcon = document.createElementNS(SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttribute('class', 'pegman-location'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - skin.avatar); - pegmanIcon.setAttribute('height', skin.pegmanHeight); - pegmanIcon.setAttribute('width', skin.pegmanWidth * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', `url(#${pegmanClipId})`); - svg.appendChild(pegmanIcon); - - displayPegman(skin, pegmanIcon, clipRect, subtype.start.x, subtype.start.y, - tiles.directionToFrame(subtype.startDirection)); - - var pegmanFadeoutAnimation = document.createElementNS(SVG_NS, 'animate'); - pegmanFadeoutAnimation.setAttribute('id', 'pegmanFadeoutAnimation'); - pegmanFadeoutAnimation.setAttribute('attributeType', 'CSS'); - pegmanFadeoutAnimation.setAttribute('attributeName', 'opacity'); - pegmanFadeoutAnimation.setAttribute('from', 1); - pegmanFadeoutAnimation.setAttribute('to', 0); - pegmanFadeoutAnimation.setAttribute('dur', '1s'); - pegmanFadeoutAnimation.setAttribute('begin', 'indefinite'); - pegmanIcon.appendChild(pegmanFadeoutAnimation); - } - - if (subtype.finish && skin.goalIdle) { - // Add finish marker. - var finishMarker = document.createElementNS(SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - skin.goalIdle); - finishMarker.setAttribute('height', MARKER_HEIGHT); - finishMarker.setAttribute('width', MARKER_WIDTH); - svg.appendChild(finishMarker); - - // Move the finish icon into position. - finishMarker.setAttribute('x', squareSize * (subtype.finish.x + 0.5) - - finishMarker.getAttribute('width') / 2); - finishMarker.setAttribute('y', squareSize * (subtype.finish.y + 0.9) - - finishMarker.getAttribute('height')); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - skin.goalIdle); - finishMarker.setAttribute('visibility', 'visible'); - } - - // Add obstacles. - if (skin.obstacleIdle) { - var obsId = 0; - for (y = 0; y < map.ROWS; y++) { - for (x = 0; x < map.COLS; x++) { - if (map.getTile(y, x) === SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', MARKER_HEIGHT * skin.obstacleScale); - obsIcon.setAttribute('width', MARKER_WIDTH * skin.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', skin.obstacleIdle); - obsIcon.setAttribute('x', - squareSize * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - squareSize * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - } -} diff --git a/apps/src/maze/drawer.js b/apps/src/maze/drawer.js deleted file mode 100644 index c717c2c5352ea..0000000000000 --- a/apps/src/maze/drawer.js +++ /dev/null @@ -1,220 +0,0 @@ -/** - * @fileoverview Base class for drawing the various Maze Skins (Bee, - * Farmer, Collector). Intended to be inherited from to provide - * skin-specific functionality. - */ - -export const SQUARE_SIZE = 50; -export const SVG_NS = 'http://www.w3.org/2000/svg'; - -/** - * @param {MaseMap} map The map from the maze, which shows the current - * state of the dirt, flowers/honey, or treasure. - * @param {string} asset the asset url to draw - */ -export default class Drawer { - constructor(map, asset, svg) { - this.map_ = map; - this.asset_ = asset; - this.svg_ = svg; - } - - /** - * Generalized function for generating ids for cells in a table - */ - static cellId(prefix, row, col) { - return prefix + '_' + row + '_' + col; - } - - /** - * Return the appropriate asset url for the given location. Overridden - * by child classes to do much more interesting things. - * @param {string} prefix - * @param {number} row - * @param {number} col - * @return {string} asset url - */ - getAsset(prefix, row, col) { - return this.asset_; - } - - /** - * Intentional noop function; BeeItemDrawer needs to be able to reset - * between runs, so we implement a shared reset function so that we can - * call drawer.reset() blindly. Overridden by BeeItemDrawer - */ - reset() {} - - /** - * Update the image at the given row,col - * @param {number} row - * @param {number} col - * @param {boolean} running - */ - updateItemImage(row, col, running) { - return this.drawImage_('', row, col); - } - - /** - * Creates/Update the image at the given row,col with the given prefix - * @param {string} prefix - * @param {number} row - * @param {number} col - * @return {Element} img - */ - drawImage_(prefix, row, col) { - let img = this.svg_.querySelector('#' + Drawer.cellId(prefix, row, col)); - let href = this.getAsset(prefix, row, col); - - // if we have not already created this image and don't want one, - // return - if (!img && !href) { - return; - } - - // otherwise create the image if we don't already have one, update - // the href to whatever we want it to be, and hide it if we don't - // have one - img = this.getOrCreateImage_(prefix, row, col); - if (img) { - img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', href || ''); - img.setAttribute('visibility', href ? 'visible' : 'hidden'); - } - - return img; - } - - /** - * Creates a new image and optional clipPath, or returns the image if - * it already exists - * @param {string} prefix - * @param {number} row - * @param {number} col - * @param {boolean} createClipPath - * @return {Element} img - */ - getOrCreateImage_(prefix, row, col, createClipPath=true) { - let href = this.getAsset(prefix, row, col); - - let imgId = Drawer.cellId(prefix, row, col); - - // Don't create an image if one with this identifier already exists - let img = this.svg_.querySelector('#' + imgId); - if (img) { - return img; - } - - // Don't create an empty image - if (!href) { - return; - } - - let pegmanElement = this.svg_.getElementsByClassName('pegman-location')[0]; - - let clipId; - // Create clip path. - if (createClipPath) { - clipId = Drawer.cellId(prefix + 'Clip', row, col); - let clip = document.createElementNS(SVG_NS, 'clipPath'); - clip.setAttribute('id', clipId); - let rect = document.createElementNS(SVG_NS, 'rect'); - rect.setAttribute('x', col * SQUARE_SIZE); - rect.setAttribute('y', row * SQUARE_SIZE); - rect.setAttribute('width', SQUARE_SIZE); - rect.setAttribute('height', SQUARE_SIZE); - clip.appendChild(rect); - this.svg_.insertBefore(clip, pegmanElement); - } - - // Create image. - img = document.createElementNS(SVG_NS, 'image'); - img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', href); - img.setAttribute('height', SQUARE_SIZE); - img.setAttribute('width', SQUARE_SIZE); - img.setAttribute('x', SQUARE_SIZE * col); - img.setAttribute('y', SQUARE_SIZE * row); - img.setAttribute('id', imgId); - if (createClipPath) { - img.setAttribute('clip-path', 'url(#' + clipId + ')'); - } - this.svg_.insertBefore(img, pegmanElement); - - return img; - } - - /** - * Create SVG text element for given cell - * @param {string} prefix - * @param {number} row - * @param {number} col - * @param {string} text - */ - updateOrCreateText_(prefix, row, col, text) { - const pegmanElement = this.svg_.getElementsByClassName('pegman-location')[0]; - let textElement = this.svg_.querySelector('#' + Drawer.cellId(prefix, row, col)); - - if (!textElement) { - // Create text. - const hPadding = 2; - const vPadding = 2; - textElement = document.createElementNS(SVG_NS, 'text'); - textElement.setAttribute('class', 'karel-counter-text'); - - // Position text just inside the bottom right corner. - textElement.setAttribute('x', (col + 1) * SQUARE_SIZE - hPadding); - textElement.setAttribute('y', (row + 1) * SQUARE_SIZE - vPadding); - textElement.setAttribute('id', Drawer.cellId(prefix, row, col)); - textElement.appendChild(document.createTextNode('')); - this.svg_.insertBefore(textElement, pegmanElement); - } - - textElement.firstChild.nodeValue = text; - return textElement; - } - - /** - * Draw the given tile at row, col - */ - drawTile(svg, tileSheetLocation, row, col, tileId, tileSheetHref) { - const [left, top] = tileSheetLocation; - - const tileSheetWidth = SQUARE_SIZE * 5; - const tileSheetHeight = SQUARE_SIZE * 4; - - // Tile's clipPath element. - const tileClip = document.createElementNS(SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - const tileClipRect = document.createElementNS(SVG_NS, 'rect'); - tileClipRect.setAttribute('width', SQUARE_SIZE); - tileClipRect.setAttribute('height', SQUARE_SIZE); - - tileClipRect.setAttribute('x', col * SQUARE_SIZE); - tileClipRect.setAttribute('y', row * SQUARE_SIZE); - tileClip.appendChild(tileClipRect); - svg.appendChild(tileClip); - - // Tile sprite. - const tileElement = document.createElementNS(SVG_NS, 'image'); - tileElement.setAttribute('id', 'tileElement' + tileId); - tileElement.setAttributeNS('http://www.w3.org/1999/xlink', - 'xlink:href', tileSheetHref); - tileElement.setAttribute('height', tileSheetHeight); - tileElement.setAttribute('width', tileSheetWidth); - tileElement.setAttribute('clip-path', - 'url(#tileClipPath' + tileId + ')'); - tileElement.setAttribute('x', (col - left) * SQUARE_SIZE); - tileElement.setAttribute('y', (row - top) * SQUARE_SIZE); - svg.appendChild(tileElement); - - // Tile animation - const tileAnimation = document.createElementNS(SVG_NS, 'animate'); - tileAnimation.setAttribute('id', 'tileAnimation' + tileId); - tileAnimation.setAttribute('attributeType', 'CSS'); - tileAnimation.setAttribute('attributeName', 'opacity'); - tileAnimation.setAttribute('from', 1); - tileAnimation.setAttribute('to', 0); - tileAnimation.setAttribute('dur', '1s'); - tileAnimation.setAttribute('begin', 'indefinite'); - tileElement.appendChild(tileAnimation); - } -} diff --git a/apps/src/maze/farmer.js b/apps/src/maze/farmer.js deleted file mode 100644 index bcd771f29b915..0000000000000 --- a/apps/src/maze/farmer.js +++ /dev/null @@ -1,11 +0,0 @@ -import Subtype from './subtype'; - -export default class Farmer extends Subtype { - - /** - * @override - */ - isFarmer() { - return true; - } -} diff --git a/apps/src/maze/gatherer.js b/apps/src/maze/gatherer.js deleted file mode 100644 index 8139800ec911d..0000000000000 --- a/apps/src/maze/gatherer.js +++ /dev/null @@ -1,28 +0,0 @@ -import Subtype from './subtype'; - -export default class Gatherer extends Subtype { - - reset() { - this.maze_.map.resetDirt(); - } - - /** - * @return {boolean} - */ - collectedEverything() { - const missedSomething = this.maze_.map.currentStaticGrid.some( - row => row.some(cell => cell.isDirt() && cell.getCurrentValue() > 0) - ); - - return !missedSomething; - } - - /** - * Did we reach our total nectar/honey goals? - * @return {boolean} - * @override - */ - succeeded() { - return this.collectedEverything(); - } -} diff --git a/apps/src/maze/harvester.js b/apps/src/maze/harvester.js deleted file mode 100644 index 99d631cdf7f7e..0000000000000 --- a/apps/src/maze/harvester.js +++ /dev/null @@ -1,165 +0,0 @@ -import Gatherer from './gatherer'; -import HarvesterCell from './harvesterCell'; -import HarvesterDrawer from './harvesterDrawer'; - -const HARVEST_SOUND = 'harvest'; - -export default class Harvester extends Gatherer { - - /** - * @override - */ - getCellClass() { - return HarvesterCell; - } - - /** - * @override - */ - loadAudio(skin) { - if (skin.harvestSound) { - this.maze_.loadAudio(skin.harvestSound, HARVEST_SOUND); - } - } - - /** - * @override - */ - createDrawer(svg) { - this.drawer = new HarvesterDrawer(this.maze_.map, this.skin_, svg, this); - } - - hasCorn() { - return this.hasCrop(HarvesterCell.FeatureType.CORN); - } - - hasPumpkin() { - return this.hasCrop(HarvesterCell.FeatureType.PUMPKIN); - } - - hasLettuce() { - return this.hasCrop(HarvesterCell.FeatureType.LETTUCE); - } - - hasCrop(crop) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - return cell.featureType() === crop && cell.getCurrentValue() > 0; - } - - atCorn() { - return this.atCrop(HarvesterCell.FeatureType.CORN); - } - - atPumpkin() { - return this.atCrop(HarvesterCell.FeatureType.PUMPKIN); - } - - atLettuce() { - return this.atCrop(HarvesterCell.FeatureType.LETTUCE); - } - - atCrop(crop) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - return cell.featureType() === crop; - } - - gotCropAt(row, col) { - const cell = this.getCell(row, col); - cell.setCurrentValue(cell.getCurrentValue() - 1); - } - - tryGetCorn() { - return this.tryGetCrop(HarvesterCell.FeatureType.CORN); - } - - tryGetPumpkin() { - return this.tryGetCrop(HarvesterCell.FeatureType.PUMPKIN); - } - - tryGetLettuce() { - return this.tryGetCrop(HarvesterCell.FeatureType.LETTUCE); - } - - /** - * Attempt to harvest the specified crop from the current location; terminate - * the execution if this is not a valid place at which to get that crop. - * - * This method is preferred over animateGetCrop for "headless" operation (ie - * when validating quantum levels) - * - * @fires wrongCrop - * @fires emptyCrop - * @return {boolean} whether or not this attempt was successful - */ - tryGetCrop(crop) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - if (cell.featureType() !== crop) { - this.emit('wrongCrop'); - return false; - } - - if (cell.getCurrentValue() === 0) { - this.emit('emptyCrop'); - return false; - } - - this.gotCropAt(row, col); - return true; - } - - animateGetCorn() { - this.animateGetCrop(HarvesterCell.FeatureType.CORN); - } - - animateGetPumpkin() { - this.animateGetCrop(HarvesterCell.FeatureType.PUMPKIN); - } - - animateGetLettuce() { - this.animateGetCrop(HarvesterCell.FeatureType.LETTUCE); - } - - /** - * Display the harvesting of the specified from the current location; raise a - * runtime error if the current location is not a valid spot from which to - * gather that crop. - * - * This method is preferred over tryGetCrop for live operation (ie when actually - * displaying something to the user) - * - * @throws Will throw an error if the current cell does not have that crop - * available to harvest. - */ - animateGetCrop(crop) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - if (cell.featureType() !== crop) { - throw new Error("Shouldn't be able to harvest the wrong kind of crop"); - } - - if (cell.getCurrentValue() <= 0) { - throw new Error("Shouldn't be able to end up with a harvest animation if " + - "there was nothing left to harvest"); - } - - this.playAudio_(HARVEST_SOUND); - this.gotCropAt(row, col); - - this.drawer.updateItemImage(row, col, true); - } -} diff --git a/apps/src/maze/harvesterCell.js b/apps/src/maze/harvesterCell.js deleted file mode 100644 index 7b114c0e2b8c0..0000000000000 --- a/apps/src/maze/harvesterCell.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @overview HarvesterCell represents the contets of the grid elements for Harvester. - */ - -import Cell from './cell'; -import tiles from './tiles'; -const SquareType = tiles.SquareType; - -const FeatureType = { - NONE: 0, - CORN: 1, - PUMPKIN: 2, - LETTUCE: 3, -}; - -export default class HarvesterCell extends Cell { - constructor(tileType, value, range, possibleFeatures, startsHidden) { - - // possible features should default to an array containing - // FeatureType.NONE, and should only be allowed to be anything else - // if this is an Open tile. - if (possibleFeatures === undefined || tileType !== SquareType.OPEN) { - possibleFeatures = [FeatureType.NONE]; - } - - // if possible features is defined, it should be an array - if (!Array.isArray(possibleFeatures)) { - possibleFeatures = [possibleFeatures]; - } - - // If the cell has no features, it should have neither value nor - // range - if (possibleFeatures.every(feature => feature === FeatureType.NONE)) { - value = undefined; - range = undefined; - } - - super(tileType, value, range); - - if (possibleFeatures.length > 1) { - startsHidden = true; - } - - /** - * @type {Number} - */ - this.possibleFeatures_ = possibleFeatures; - - /** - * @type {Boolean} - */ - this.startsHidden_ = !!startsHidden; - } - - /** - * @return {boolean} - */ - startsHidden() { - return this.startsHidden_; - } - - /** - * @return {boolean} - */ - isVariableFeature() { - return this.possibleFeatures_.length > 1; - } - - /** - * @return {boolean} - * @override - */ - isVariable() { - return this.isVariableFeature() || super.isVariable(); - } - - /** - * Variable cells can represent multiple possible kinds of grid assets, - * whereas non-variable cells can represent only a single kind. This - * method returns an array of non-variable BeeCells based on this BeeCell's - * configuration. - * @return {BeeCell[]} - * @override - */ - getPossibleGridAssets() { - let possibilities = []; - if (this.isVariableFeature()) { - possibilities = this.possibleFeatures_.map(feature => - HarvesterCell.deserialize(Object.assign({}, this.serialize(), { - possibleFeatures: [feature] - })) - ); - } else if (this.isVariableRange()) { - for (let i = this.originalValue_; i <= this.range_; i++) { - possibilities.push(HarvesterCell.deserialize(Object.assign({}, this.serialize(), { - value: i, - range: i - }))); - } - } else { - possibilities.push(this); - } - return possibilities; - } - - featureType() { - if (this.isVariableFeature()) { - return undefined; - } - - return this.possibleFeatures_[0]; - } - - featureName() { - if (this.isVariableFeature()) { - return 'unknown'; - } - - const feature = this.possibleFeatures_[0]; - - return ['none', 'corn', 'pumpkin', 'lettuce'][feature]; - } - - isCorn() { - return this.possibleFeatures_.includes(FeatureType.CORN); - } - - isPumpkin() { - return this.possibleFeatures_.includes(FeatureType.PUMPKIN); - } - - isLettuce() { - return this.possibleFeatures_.includes(FeatureType.LETTUCE); - } - - /** - * Serializes this HarvesterCell into JSON - * @return {Object} - * @override - */ - serialize() { - return Object.assign({}, super.serialize(), { - possibleFeatures: this.possibleFeatures_, - startsHidden: this.startsHidden_ - }); - } -} - -HarvesterCell.deserialize = serialized => new HarvesterCell( - serialized.tileType, - serialized.value, - serialized.range, - serialized.possibleFeatures, - serialized.startsHidden -); - - -HarvesterCell.FeatureType = FeatureType; diff --git a/apps/src/maze/harvesterDrawer.js b/apps/src/maze/harvesterDrawer.js deleted file mode 100644 index 296f466fe74ac..0000000000000 --- a/apps/src/maze/harvesterDrawer.js +++ /dev/null @@ -1,79 +0,0 @@ -import Drawer from './drawer'; - -export default class HarvesterDrawer extends Drawer { - constructor(map, skin, svg, subtype) { - super(map, '', svg); - this.skin_ = skin; - this.subtype_ = subtype; - } - - /** - * @override - */ - getAsset(prefix, row, col) { - switch (prefix) { - case 'sprout': - return this.skin_.sprout; - case 'crop': - var crop = this.subtype_.getCell(row, col).featureName(); - return this.skin_[crop]; - } - } - - /** - * Override DirtDrawer's updateItemImage. - * @override - * @param {number} row - * @param {number} col - * @param {boolean} running Is user code currently running - */ - updateItemImage(row, col, running) { - let variableCell = this.map_.getVariableCell(row, col); - let cell = this.map_.getCell(row, col); - - if (!variableCell.hasValue()) { - return; - } - - // Image - if (cell.startsHidden() && !running) { - this.show('sprout', row, col); - this.hide('crop', row, col); - } else { - if (cell.getCurrentValue() > 0) { - this.show('crop', row, col); - } else { - this.hide('crop', row, col); - } - this.hide('sprout', row, col); - } - - // Counter - if (running) { - if (cell.getCurrentValue() > 0) { - this.updateOrCreateText_('counter', row, col, cell.getCurrentValue()); - } else { - this.updateOrCreateText_('counter', row, col, ''); - } - } else { - if (cell.startsHidden()) { - this.updateOrCreateText_('counter', row, col, ''); - } else if (variableCell.isVariableRange()) { - this.updateOrCreateText_('counter', row, col, '?'); - } else { - this.updateOrCreateText_('counter', row, col, cell.getCurrentValue()); - } - } - } - - hide(prefix, row, col) { - const element = this.getOrCreateImage_(prefix, row, col); - if (element) { - element.setAttribute('visibility', 'hidden'); - } - } - - show(prefix, row, col) { - this.drawImage_(prefix, row, col); - } -} diff --git a/apps/src/maze/karelLevels.js b/apps/src/maze/karelLevels.js index b96834dbed52f..b4201e9a4bc99 100644 --- a/apps/src/maze/karelLevels.js +++ b/apps/src/maze/karelLevels.js @@ -1,7 +1,10 @@ +var tiles = require('@code-dot-org/maze').tiles; +var Direction = tiles.Direction; + +var blockUtils = require('../block_utils'); var levelBase = require('../level_base'); -var Direction = require('./tiles').Direction; + var msg = require('./locale'); -var blockUtils = require('../block_utils'); //TODO: Fix hacky level-number-dependent toolbox. var toolbox = function (page, level) { diff --git a/apps/src/maze/levels.js b/apps/src/maze/levels.js index b66ca3f350cb4..d71647d857154 100644 --- a/apps/src/maze/levels.js +++ b/apps/src/maze/levels.js @@ -1,10 +1,13 @@ -var Direction = require('./tiles').Direction; -var karelLevels = require('./karelLevels'); -var wordsearchLevels = require('./wordsearchLevels'); -var reqBlocks = require('./requiredBlocks'); +var tiles = require('@code-dot-org/maze').tiles; +var Direction = tiles.Direction; + var blockUtils = require('../block_utils'); var utils = require('../utils'); + +var karelLevels = require('./karelLevels'); var mazeMsg = require('./locale'); +var reqBlocks = require('./requiredBlocks'); +var wordsearchLevels = require('./wordsearchLevels'); //TODO: Fix hacky level-number-dependent toolbox. var toolbox = function (page, level) { diff --git a/apps/src/maze/maze.js b/apps/src/maze/maze.js index 1093172ef443e..1a61f89ac992b 100644 --- a/apps/src/maze/maze.js +++ b/apps/src/maze/maze.js @@ -22,9 +22,10 @@ const MazeVisualizationColumn = require('./MazeVisualizationColumn'); const api = require('./api'); const dropletConfig = require('./dropletConfig'); const mazeReducer = require('./redux'); -const tiles = require('./tiles'); -const MazeController = require('./mazeController'); +const maze = require('@code-dot-org/maze'); +const MazeController = maze.MazeController; +const tiles = maze.tiles; const createResultsHandlerForSubtype = require('./results/utils').createResultsHandlerForSubtype; @@ -266,6 +267,7 @@ module.exports = class Maze { reset_ = () => { this.animating_ = false; + timeoutList.clearTimeouts(); this.controller.reset(); }; diff --git a/apps/src/maze/mazeController.js b/apps/src/maze/mazeController.js deleted file mode 100644 index c4705eb0b6395..0000000000000 --- a/apps/src/maze/mazeController.js +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Blockly Apps: Maze - * - * Copyright 2012 Google Inc. - * http://blockly.googlecode.com/ - * - * 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. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ - -const timeoutList = require('../lib/util/timeoutList'); - -const AnimationsController = require('./animationsController'); -const MazeMap = require('./mazeMap'); -const drawMap = require('./drawMap'); -const getSubtypeForSkin = require('./mazeUtils').getSubtypeForSkin; -const tiles = require('./tiles'); - -module.exports = class MazeController { - constructor(level, skin, config, options = {}) { - - this.stepSpeed = 100; - - this.level = level; - this.skin = skin; - this.startDirection = null; - - this.subtype = null; - this.map = null; - this.animationsController = null; - this.store = null; - - this.pegmanD = null; - this.pegmanX = null; - this.pegmanY = null; - - this.MAZE_HEIGHT = null; - this.MAZE_WIDTH = null; - this.PATH_WIDTH = null; - this.PEGMAN_HEIGHT = null; - this.PEGMAN_WIDTH = null; - this.PEGMAN_X_OFFSET = null; - this.PEGMAN_Y_OFFSET = null; - this.SQUARE_SIZE = null; - - if (options.reduxStore) { - this.addReduxStore(options.reduxStore); - } - - if (options.methods) { - this.rebindMethods(options.methods); - } - - const Type = getSubtypeForSkin(config.skinId); - this.subtype = new Type(this, config); - this.loadLevel_(); - } - - /** - * A few placeholder methods intended to be rebound - */ - playAudio() {} - playAudioOnFailure() {} - loadAudio() {} - getTestResults() {} - - rebindMethods(methods) { - this.playAudio = methods.playAudio || this.playAudio; - this.playAudioOnFailure = methods.playAudioOnFailure || this.playAudioOnFailure; - this.loadAudio = methods.loadAudio || this.loadAudio; - this.getTestResults = methods.getTestResults || this.getTestResults; - } - - addReduxStore(store) { - this.store = store; - } - - initWithSvg(svg) { - // Adjust outer element size. - svg.setAttribute('width', this.MAZE_WIDTH); - svg.setAttribute('height', this.MAZE_HEIGHT); - - drawMap.default(svg, this.skin, this.subtype, this.map, this.SQUARE_SIZE); - this.animationsController = new AnimationsController(this, svg); - } - - loadLevel_() { - // Load maps. - // - // "serializedMaze" is the new way of storing maps; it's a JSON array - // containing complex map data. - // - // "map" plus optionally "levelDirt" is the old way of storing maps; - // they are each arrays of a combination of strings and ints with - // their own complex syntax. This way is deprecated for new levels, - // and only exists for backwards compatibility for not-yet-updated - // levels. - if (this.level.serializedMaze) { - this.map = MazeMap.deserialize( - this.level.serializedMaze, - this.subtype.getCellClass(), - ); - } else { - this.map = MazeMap.parseFromOldValues( - this.level.map, - this.level.initialDirt, - this.subtype.getCellClass(), - ); - } - - // this could possibly be eliminated in favor of just always referencing - // this.level.startDirection - this.startDirection = this.level.startDirection; - - // this could probably be moved to the constructor - - if (this.level.fastGetNectarAnimation) { - this.skin.actionSpeedScale.nectar = 0.5; - } - - // Pixel height and width of each maze square (i.e. tile). - this.SQUARE_SIZE = 50; - this.PEGMAN_HEIGHT = this.skin.pegmanHeight; - this.PEGMAN_WIDTH = this.skin.pegmanWidth; - this.PEGMAN_X_OFFSET = this.skin.pegmanXOffset || 0; - this.PEGMAN_Y_OFFSET = this.skin.pegmanYOffset; - - this.MAZE_WIDTH = this.SQUARE_SIZE * this.map.COLS; - this.MAZE_HEIGHT = this.SQUARE_SIZE * this.map.ROWS; - this.PATH_WIDTH = this.SQUARE_SIZE / 3; - } - - /** - * Redraw all dirt images - * @param {boolean} running Whether or not user program is currently running - */ - resetDirtImages(running) { - this.map.forEachCell((cell, row, col) => { - this.subtype.drawer.updateItemImage(row, col, running); - }); - } - - /** - * Initialize Blockly and the maze. Called on page load. - */ - gridNumberToPosition_(n) { - return (n + 0.5) * this.SQUARE_SIZE; - } - - /** - * @param svg - * @param {Array} coordinates An array of x and y grid coordinates. - */ - drawHintPath(svg, coordinates) { - const path = svg.getElementById('hintPath'); - path.setAttribute('d', 'M' + coordinates.map(([x, y]) => { - return `${this.gridNumberToPosition_(x)},${this.gridNumberToPosition_(y)}`; - }).join(' ')); - } - - /** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ - reset(first) { - this.subtype.reset(); - - // Kill all tasks. - timeoutList.clearTimeouts(); - - // Move Pegman into position. - this.pegmanX = this.subtype.start.x; - this.pegmanY = this.subtype.start.y; - - this.pegmanD = this.startDirection; - this.animationsController.reset(first); - - // Move the init dirt marker icons into position. - this.map.resetDirt(); - this.resetDirtImages(false); - - // Reset the obstacle image. - var obsId = 0; - var x, y; - for (y = 0; y < this.map.ROWS; y++) { - for (x = 0; x < this.map.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', - 'xlink:href', - this.skin.obstacleIdle, - ); - } - ++obsId; - } - } - - if (this.subtype.resetTiles) { - this.subtype.resetTiles(); - } else { - this.resetTiles_(); - } - } - - resetTiles_() { - // Reset the tiles - var tileId = 0; - for (var y = 0; y < this.map.ROWS; y++) { - for (var x = 0; x < this.map.COLS; x++) { - // Tile's clipPath element. - var tileClip = document.getElementById('tileClipPath' + tileId); - tileClip.setAttribute('visibility', 'visible'); - // Tile sprite. - var tileElement = document.getElementById('tileElement' + tileId); - tileElement.setAttributeNS( - 'http://www.w3.org/1999/xlink', - 'xlink:href', - this.skin.tiles, - ); - tileElement.setAttribute('opacity', 1); - tileId++; - } - } - } - - animatedFinish(timePerStep) { - this.animationsController.scheduleDance(true, timePerStep); - } - - animatedMove(direction, timeForMove) { - var positionChange = tiles.directionToDxDy(direction); - var newX = this.pegmanX + positionChange.dx; - var newY = this.pegmanY + positionChange.dy; - this.animationsController.scheduleMove(newX, newY, timeForMove); - this.playAudio('walk'); - this.pegmanX = newX; - this.pegmanY = newY; - } - - animatedTurn(direction) { - var newDirection = this.pegmanD + direction; - this.animationsController.scheduleTurn(newDirection); - this.pegmanD = tiles.constrainDirection4(newDirection); - } - - animatedFail(forward) { - var dxDy = tiles.directionToDxDy(this.pegmanD); - var deltaX = dxDy.dx; - var deltaY = dxDy.dy; - - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = this.pegmanX + deltaX; - var targetY = this.pegmanY + deltaY; - var frame = tiles.directionToFrame(this.pegmanD); - this.animationsController.displayPegman( - this.pegmanX + deltaX / 4, - this.pegmanY + deltaY / 4, - frame, - ); - // Play sound and animation for hitting wall or obstacle - var squareType = this.map.getTile(targetY, targetX); - if ( - squareType === tiles.SquareType.WALL || - squareType === undefined || - (this.subtype.isScrat() && squareType === tiles.SquareType.OBSTACLE) - ) { - // Play the sound - this.playAudio('wall'); - if (squareType !== undefined) { - // Check which type of wall pegman is hitting - this.playAudio('wall' + this.subtype.wallMap[targetY][targetX]); - } - - if (this.subtype.isScrat() && squareType === tiles.SquareType.OBSTACLE) { - this.animationsController.crackSurroundingIce(targetX, targetY); - } - - this.animationsController.scheduleWallHit(targetX, targetY, deltaX, deltaY, frame); - timeoutList.setTimeout(() => { - this.playAudioOnFailure(); - }, this.stepSpeed * 2); - } else if (squareType === tiles.SquareType.OBSTACLE) { - // Play the sound - this.playAudio('obstacle'); - this.animationsController.scheduleObstacleHit(targetX, targetY, deltaX, deltaY, frame); - timeoutList.setTimeout(() => { - this.playAudioOnFailure(); - }, this.stepSpeed); - } - } - - /** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Direction} direction Direction (0 - 3). - */ - animatedLook(direction) { - var x = this.pegmanX; - var y = this.pegmanY; - switch (direction) { - case tiles.Direction.NORTH: - x += 0.5; - break; - case tiles.Direction.EAST: - x += 1; - y += 0.5; - break; - case tiles.Direction.SOUTH: - x += 0.5; - y += 1; - break; - case tiles.Direction.WEST: - y += 0.5; - break; - } - x *= this.SQUARE_SIZE; - y *= this.SQUARE_SIZE; - var d = direction * 90 - 45; - - this.animationsController.scheduleLook(x, y, d); - } - - scheduleDirtChange_(options) { - var col = this.pegmanX; - var row = this.pegmanY; - - // cells that started as "flat" will be undefined - var previousValue = this.map.getValue(row, col) || 0; - - this.map.setValue(row, col, previousValue + options.amount); - this.subtype.scheduleDirtChange(row, col); - this.playAudio(options.sound); - } - - /** - * Schedule to add dirt at pegman's current position. - */ - scheduleFill() { - this.scheduleDirtChange_({ - amount: 1, - sound: 'fill', - }); - } - - /** - * Schedule to remove dirt at pegman's current location. - */ - scheduleDig() { - this.scheduleDirtChange_({ - amount: -1, - sound: 'dig', - }); - } -}; diff --git a/apps/src/maze/mazeMap.js b/apps/src/maze/mazeMap.js deleted file mode 100644 index 88d05b23e9548..0000000000000 --- a/apps/src/maze/mazeMap.js +++ /dev/null @@ -1,144 +0,0 @@ -module.exports = class MazeMap { - constructor(grid) { - this.grid_ = grid; - - this.ROWS = this.grid_.length; - this.COLS = this.grid_[0].length; - - this.staticGrids = this.constructor.getAllStaticGrids(this.grid_); - - this.currentStaticGrid = this.staticGrids[0]; - } - - /** - * Clones the given grid of Cells by calling Cell.clone - * @param {Cell[][]} grid - * @return {Cell[][]} grid - */ - static cloneGrid(grid) { - return grid.map(row => row.map(cell => cell.clone())); - } - - /** - * Given a single grid of Cells, some of which may be "variable" - * cells, return a list of grids of non-variable Cells representing - * all possible variable combinations. - * @param {Cell[][]} variableGrid - * @return {Cell[][][]} grids - */ - static getAllStaticGrids(variableGrid) { - let grids = [variableGrid]; - variableGrid.forEach((row, x) => { - row.forEach((cell, y) => { - if (cell.isVariable()) { - const possibleAssets = cell.getPossibleGridAssets(); - const newGrids = []; - possibleAssets.forEach(asset => { - grids.forEach(grid => { - const newMap = this.cloneGrid(grid); - newMap[x][y] = asset; - newGrids.push(newMap); - }); - }); - grids = newGrids; - } - }); - }); - return grids; - } - - static deserialize(serializedValues, cellClass) { - return new MazeMap(serializedValues.map(row => row.map(cellClass.deserialize))); - } - - static parseFromOldValues(map, initialDirt, cellClass) { - return new MazeMap(map.map((row, x) => row.map((mapCell, y) => { - const initialDirtCell = initialDirt && initialDirt[x][y]; - return cellClass.parseFromOldValues(mapCell, initialDirtCell); - }))); - } - - resetDirt() { - this.forEachCell(cell => { - cell.resetCurrentValue(); - }); - } - - forEachCell(cb) { - this.currentStaticGrid.forEach((row, x) => { - row.forEach((cell, y) => { - cb(cell, x, y); - }); - }); - } - - /** - * Returns a flattened list of all cells in this map. Good for - * situations where we want to map or reduce the cells without caring - * about their position - * @return {Cell[]} - */ - getAllCells() { - return this.currentStaticGrid.reduce( - (prev, curr) => prev.concat(curr), [] - ); - } - - getCell(x, y) { - return this.currentStaticGrid[x] && this.currentStaticGrid[x][y]; - } - - isDirt(x, y) { - let cell = this.getCell(x, y); - return cell && cell.isDirt(); - } - - getTile(x, y) { - let cell = this.getCell(x, y); - return cell && cell.getTile(); - } - - getValue(x, y) { - let cell = this.getCell(x, y); - return cell && cell.getCurrentValue(); - } - - setValue(x, y, val) { - if (this.currentStaticGrid[x] && this.currentStaticGrid[x][y]) { - this.currentStaticGrid[x][y].setCurrentValue(val); - } - } - - /** - * Some functionality - most notably Bee's shouldCheckCloud and - * shouldCheckPurple logic - need to be able to make decisions based on - * details about the original (variable) cell at a coordinate. - * @returns {Cell} - */ - getVariableCell(x, y) { - if (this.grid_[x] && this.grid_[x][y]) { - return this.grid_[x][y]; - } - } - - /** - * Assigns this.currentStaticGrid to the appropriate grid and resets all - * current values - * @param {Number} id - */ - useGridWithId(id) { - this.currentStaticGrid = this.staticGrids[id]; - this.resetDirt(); - } - - clone() { - this.constructor.cloneGrid(this.grid_); - } - - /** - * @return {boolean} - */ - hasMultiplePossibleGrids() { - return this.staticGrids.length > 1; - } -}; diff --git a/apps/src/maze/mazeUtils.js b/apps/src/maze/mazeUtils.js deleted file mode 100644 index 09183ab5780bc..0000000000000 --- a/apps/src/maze/mazeUtils.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Is skin either farmer or farmer_night - */ -exports.isFarmerSkin = function (skinId) { - return (/farmer(_night)?/).test(skinId); -}; - -/** - * Is skin either bee or bee_night - */ -exports.isBeeSkin = function (skinId) { - return (/bee(_night)?/).test(skinId); -}; - -/** - * Is skin either collector or collector_night - */ -exports.isCollectorSkin = function (skinId) { - return (/collector(_night)?/).test(skinId); -}; - -/** - * Is skin scrat - */ -exports.isScratSkin = function (skinId) { - return (/scrat/).test(skinId); -}; - -exports.isPlanterSkin = function (skinId) { - return (/planter/).test(skinId); -}; - -exports.isHarvesterSkin = function (skinId) { - return (/harvester/).test(skinId); -}; - -exports.isWordSearchSkin = function (skinId) { - return skinId === 'letters'; -}; - -exports.getSubtypeForSkin = function (skinId) { - if (exports.isFarmerSkin(skinId)) { - return require('./farmer'); - } - if (exports.isBeeSkin(skinId)) { - return require('./bee'); - } - if (exports.isCollectorSkin(skinId)) { - return require('./collector'); - } - if (exports.isWordSearchSkin(skinId)) { - return require('./wordsearch'); - } - if (exports.isScratSkin(skinId)) { - return require('./scrat'); - } - if (exports.isHarvesterSkin(skinId)) { - return require('./harvester'); - } - if (exports.isPlanterSkin(skinId)) { - return require('./planter'); - } - - return require('./subtype'); -}; - -/** - * Rotate the given 2d array. - * @param data - */ -exports.rotate = function (data) { - return data[0].map((x, i) => data.map(x => x[data.length - i - 1])); -}; diff --git a/apps/src/maze/planter.js b/apps/src/maze/planter.js deleted file mode 100644 index ca6cde8245a53..0000000000000 --- a/apps/src/maze/planter.js +++ /dev/null @@ -1,91 +0,0 @@ -import Subtype from './subtype'; -import PlanterCell from './planterCell'; -import PlanterDrawer from './planterDrawer'; - -export default class Planter extends Subtype { - - reset() { - this.maze_.map.forEachCell(cell => { - cell.resetCurrentFeature(); - }); - } - - /** - * @override - */ - getCellClass() { - return PlanterCell; - } - - /** - * @override - */ - createDrawer(svg) { - this.drawer = new PlanterDrawer(this.maze_.map, this.skin_, svg, this); - } - - atSprout() { - return this.atType(PlanterCell.FeatureType.SPROUT); - } - - atSoil() { - return this.atType(PlanterCell.FeatureType.SOIL); - } - - atType(type) { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - return cell.featureType() === type; - } - - /** - * Attempt to plant a sprout at the current location; terminate the execution - * if this is not a valid place at which to plant. - * - * This method is preferred over animatePlant for "headless" operation (ie - * when validating quantum levels) - * - * @fires plantInNonSoil - * @return {boolean} whether or not this attempt was successful - */ - tryPlant() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - if (cell.featureType() !== PlanterCell.FeatureType.SOIL) { - this.emit('plantInNonSoil'); - return false; - } - - cell.setFeatureType(PlanterCell.FeatureType.SPROUT); - return true; - } - - /** - * Display the planting of a sprout at the current location; raise a runtime - * error if the current location is not a valid spot at which to plant. - * - * This method is preferred over tryPlant for live operation (ie when actually - * displaying something to the user) - * - * @throws Will throw an error if the current cell has no nectar. - */ - animatePlant() { - const col = this.maze_.pegmanX; - const row = this.maze_.pegmanY; - - const cell = this.getCell(row, col); - - if (cell.featureType() !== PlanterCell.FeatureType.SOIL) { - throw new Error("Shouldn't be able to plant in anything but soil"); - } - - cell.setFeatureType(PlanterCell.FeatureType.SPROUT); - this.drawer.updateItemImage(row, col, true); - } -} diff --git a/apps/src/maze/planterCell.js b/apps/src/maze/planterCell.js deleted file mode 100644 index a6de7fc0c1cd8..0000000000000 --- a/apps/src/maze/planterCell.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @overview PlanterCell represents the contets of the grid elements for Planter. - * PlanterCells can start as empty, soil, or a sprout, and can be - * changed by the user from soil to sprout. - */ - -import Cell from './cell'; -const FeatureType = { - NONE: 0, - SOIL: 1, - SPROUT: 2, -}; - -export default class PlanterCell extends Cell { - constructor(tileType, featureType) { - if (featureType === undefined) { - featureType = FeatureType.NONE; - } - - super(tileType); - - /** - * @type {Number} - */ - this.originalFeatureType_ = featureType; - - /** - * @type {Number} - */ - this.currentFeatureType_ = undefined; - this.resetCurrentFeature(); - } - - setFeatureType(type) { - this.currentFeatureType_ = type; - } - - featureType() { - return this.currentFeatureType_; - } - - originalFeatureType() { - return this.originalFeatureType_; - } - - resetCurrentFeature() { - this.currentFeatureType_ = this.originalFeatureType_; - } - - featureName() { - const feature = this.currentFeatureType_; - - return ['none', 'soil', 'sprout'][feature]; - } - - isSoil() { - return this.currentFeatureType_ === FeatureType.SOIL; - } - - isSprout() { - return this.currentFeatureType_ === FeatureType.SPROUT; - } - - /** - * Serializes this PlanterCell into JSON - * @return {Object} - * @override - */ - serialize() { - return Object.assign({}, super.serialize(), { - featureType: this.originalFeatureType_, - }); - } -} - -PlanterCell.deserialize = serialized => new PlanterCell( - serialized.tileType, - serialized.featureType -); - -PlanterCell.FeatureType = FeatureType; diff --git a/apps/src/maze/planterDrawer.js b/apps/src/maze/planterDrawer.js deleted file mode 100644 index e71acc8b317a0..0000000000000 --- a/apps/src/maze/planterDrawer.js +++ /dev/null @@ -1,17 +0,0 @@ -import Drawer from './drawer'; - -export default class PlanterDrawer extends Drawer { - constructor(map, skin, svg, subtype) { - super(map, '', svg); - this.skin_ = skin; - this.subtype_ = subtype; - } - - /** - * @override - */ - getAsset(prefix, row, col) { - const crop = this.subtype_.getCell(row, col).featureName(); - return this.skin_[crop]; - } -} diff --git a/apps/src/maze/results/utils.js b/apps/src/maze/results/utils.js index 83e217f680526..01085ef5bdfd5 100644 --- a/apps/src/maze/results/utils.js +++ b/apps/src/maze/results/utils.js @@ -1,9 +1,4 @@ -const Farmer = require('../farmer'); -const Bee = require('../bee'); -const Collector = require('../collector'); -const Wordsearch = require('../wordsearch'); -const Harvester = require('../harvester'); -const Planter = require('../planter'); +const subtypes = require('@code-dot-org/maze').subtypes; const FarmerHandler = require('./farmer'); const BeeHandler = require('./bee'); @@ -16,17 +11,17 @@ const ResultsHandler = require('./resultsHandler'); export function createResultsHandlerForSubtype(controller, config) { let handler = ResultsHandler; - if (controller.subtype instanceof Farmer) { + if (controller.subtype instanceof subtypes.Farmer) { handler = FarmerHandler; - } else if (controller.subtype instanceof Bee) { + } else if (controller.subtype instanceof subtypes.Bee) { handler = BeeHandler; - } else if (controller.subtype instanceof Collector) { + } else if (controller.subtype instanceof subtypes.Collector) { handler = CollectorHandler; - } else if (controller.subtype instanceof Wordsearch) { + } else if (controller.subtype instanceof subtypes.Wordsearch) { handler = WordsearchHandler; - } else if (controller.subtype instanceof Harvester) { + } else if (controller.subtype instanceof subtypes.Harvester) { handler = HarvesterHandler; - } else if (controller.subtype instanceof Planter) { + } else if (controller.subtype instanceof subtypes.Planter) { handler = PlanterHandler; } diff --git a/apps/src/maze/scrat.js b/apps/src/maze/scrat.js deleted file mode 100644 index 7d0ca679bb42e..0000000000000 --- a/apps/src/maze/scrat.js +++ /dev/null @@ -1,107 +0,0 @@ -import { SquareType } from './tiles'; -import { randomValue } from '../utils'; - -import Subtype from './subtype'; - -const TILE_SHAPES = { - 'log': [0, 0], - 'lily1': [1, 0], - 'land1': [2, 0], - 'island_start': [0, 1], - 'island_topRight': [1, 1], - 'island_botLeft': [0, 2], - 'island_botRight': [1, 2], - 'water': [0, 4], - - 'lily2': [2, 1], - 'lily3': [3, 1], - 'lily4': [2, 2], - 'lily5': [3, 2], - - 'ice': [3, 0], - 'cracked_ice': [4, 0], - - 'empty': [0, 4] -}; - -export default class Scrat extends Subtype { - - /** - * @override - */ - isScrat() { - return true; - } - - // Returns true if the tile at x,y is either a water tile or out of bounds - isWaterOrOutOfBounds(col, row) { - return this.isWater(col, row) || this.maze_.map.getTile(row, col) === undefined; - } - - // Returns true if the tile at x,y is a water tile that is in bounds. - isWater(col, row) { - return this.maze_.map.getTile(row, col) === SquareType.WALL; - } - - // Returns true if the tile at x,y is an obstacle tile that is in bounds. - isObstacle(col, row) { - return this.maze_.map.getTile(row, col) === SquareType.OBSTACLE; - } - - drawMapTiles(svg) { - let row, col; - - // first figure out where we want to put the island - const possibleIslandLocations = []; - for (row = 0; row < this.maze_.map.ROWS; row++) { - for (col = 0; col < this.maze_.map.COLS; col++) { - if (!this.isWater(col, row) || !this.isWater(col + 1, row) || - !this.isWater(col, row + 1) || !this.isWater(col + 1, row + 1)) { - continue; - } - possibleIslandLocations.push({row, col}); - } - } - const island = randomValue(possibleIslandLocations); - const preFilled = {}; - if (island) { - preFilled[`${island.row + 0}_${island.col + 0}`] = 'island_start'; - preFilled[`${island.row + 1}_${island.col + 0}`] = 'island_botLeft'; - preFilled[`${island.row + 0}_${island.col + 1}`] = 'island_topRight'; - preFilled[`${island.row + 1}_${island.col + 1}`] = 'island_botRight'; - } - - let tileId = 0; - let tile; - for (row = 0; row < this.maze_.map.ROWS; row++) { - for (col = 0; col < this.maze_.map.COLS; col++) { - if (this.isObstacle(col, row)) { - tile = 'cracked_ice'; - } else if (!this.isWaterOrOutOfBounds(col, row)) { - tile = 'ice'; - } else { - const adjacentToPath = !this.isWaterOrOutOfBounds(col, row - 1) || - !this.isWaterOrOutOfBounds(col + 1, row) || - !this.isWaterOrOutOfBounds(col, row + 1) || - !this.isWaterOrOutOfBounds(col - 1, row); - - // if next to the path, always just have water. otherwise, there's - // a chance of one of our other tiles - tile = 'water'; - - tile = preFilled[`${row}_${col}`]; - if (!tile) { - tile = randomValue(['empty', 'empty', 'empty', 'empty', 'empty', 'lily2', - 'lily3', 'lily4', 'lily5', 'lily1', 'log', 'lily1', 'land1']); - } - - if (adjacentToPath && tile === 'land1') { - tile = 'empty'; - } - } - this.drawTile(svg, TILE_SHAPES[tile], row, col, tileId); - tileId++; - } - } - } -} diff --git a/apps/src/maze/subtype.js b/apps/src/maze/subtype.js deleted file mode 100644 index d2d132b7f6ddd..0000000000000 --- a/apps/src/maze/subtype.js +++ /dev/null @@ -1,239 +0,0 @@ -import Cell from './cell'; -import DirtDrawer from './dirtDrawer'; - -import { SquareType } from './tiles'; -import { EventEmitter } from 'events'; // provided by webpack's node-libs-browser - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -const TILE_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3], -}; - -export default class Subtype extends EventEmitter { - constructor(maze, config) { - super(); - - this.maze_ = maze; - this.skin_ = config.skin; - this.level_ = config.level; - this.startDirection = config.level.startDirection; - } - - /** - * @param {Number} row - * @param {Number} col - * @returns {Number} val - */ - getValue(row, col) { - return this.getCell(row, col).getCurrentValue(); - } - - /** - * @param {Number} row - * @param {Number} col - * @param {Number} val - */ - setValue(row, col, val) { - this.getCell(row, col).setCurrentValue(val); - } - - /** - * @param {Number} row - * @param {Number} col - * @returns {Object} cell - */ - getCell(row, col) { - return this.maze_.map.currentStaticGrid[row][col]; - } - - getCellClass() { - return Cell; - } - - /** - * Load subtype-specific audio; called by Maze.init - * - * @param {Object} skin - */ - loadAudio(skin) { - // noop; overridable - } - - /** - * Simple safe passthrough to StudioApp.playAudio - * @param {String} sound - string key of sound to play, as defined by - * StudioApp.loadAudio - */ - playAudio_(sound) { - // Check for StudioApp, which will often be undefined in unit tests - if (this.maze_.playAudio) { - this.maze_.playAudio(sound); - } - } - - createDrawer(svg) { - this.drawer = new DirtDrawer(this.maze_.map, this.skin_.dirt, svg); - } - - /** - * @fires reset - */ - reset() { - this.emit('reset'); - } - - isFarmer() { - return false; - } - - isCollector() { - return false; - } - - isScrat() { - return false; - } - - isWordSearch() { - return false; - } - - isBee() { - return false; - } - - // Return a value of '0' if the specified square is wall or out of bounds '1' - // otherwise (empty, obstacle, start, finish). - isOnPathStr_(x, y) { - return this.isWallOrOutOfBounds_(x, y) ? "0" : "1"; - } - - // Returns true if the tile at x,y is either a wall or out of bounds - isWallOrOutOfBounds_(col, row) { - return ( - this.maze_.map.getTile(row, col) === SquareType.WALL || - this.maze_.map.getTile(row, col) === undefined - ); - } - - getEmptyTile(x, y, adjacentToPath) { - let tile; - // Empty square. Use null0 for large areas, with null1-4 for borders. - if (!adjacentToPath && Math.random() > 0.3) { - this.wallMap[y][x] = 0; - tile = 'null0'; - } else { - const wallIdx = Math.floor(1 + Math.random() * 4); - this.wallMap[y][x] = wallIdx; - tile = 'null' + wallIdx; - } - - // For the first 3 levels in maze, only show the null0 image. - if (['2_1', '2_2', '2_3'].includes(this.level_.id)) { - this.wallMap[y][x] = 0; - tile = 'null0'; - } - return tile; - } - - /** - * Draw the tiles making up the maze map. - */ - drawMapTiles(svg) { - // Compute and draw the tile for each square. - let tileId = 0; - let tile; - this.maze_.map.forEachCell((cell, row, col) => { - // Compute the tile index. - tile = this.isOnPathStr_(col, row) + - this.isOnPathStr_(col, row - 1) + // North. - this.isOnPathStr_(col + 1, row) + // West. - this.isOnPathStr_(col, row + 1) + // South. - this.isOnPathStr_(col - 1, row); // East. - - const adjacentToPath = (tile !== '00000'); - - // Draw the tile. - if (!TILE_SHAPES[tile]) { - // We have an empty square. Handle it differently based on skin. - tile = this.getEmptyTile(col, row, adjacentToPath); - } - - this.drawTile(svg, TILE_SHAPES[tile], row, col, tileId); - this.drawer.updateItemImage(row, col, false); - - tileId++; - }); - } - - scheduleDirtChange(row, col) { - this.drawer.updateItemImage(row, col, true); - } - - /** - * Draw the given tile at row, col - */ - drawTile(svg, tileSheetLocation, row, col, tileId) { - this.drawer.drawTile( - svg, - tileSheetLocation, - row, - col, - tileId, - this.skin_.tiles, - ); - } - - /** - * Initialize the wallMap. For any cell at location x,y Maze.wallMap[y][x] will - * be the index of which wall tile to use for that cell. If the cell is not a - * wall, Maze.wallMap[y][x] is undefined. - */ - initWallMap() { - this.wallMap = new Array(this.maze_.map.ROWS); - for (let y = 0; y < this.maze_.map.ROWS; y++) { - this.wallMap[y] = new Array(this.maze_.map.COLS); - } - } - - initStartFinish() { - this.start = undefined; - this.finish = undefined; - - // Locate the start and finish squares. - for (let y = 0; y < this.maze_.map.ROWS; y++) { - for (let x = 0; x < this.maze_.map.COLS; x++) { - let cell = this.maze_.map.getTile(y, x); - if (cell === SquareType.START) { - this.start = { x, y }; - } else if (cell === SquareType.FINISH) { - this.finish = { x, y }; - } else if (cell === SquareType.STARTANDFINISH) { - this.start = { x, y }; - this.finish = { x, y }; - } - } - } - } -} diff --git a/apps/src/maze/tiles.js b/apps/src/maze/tiles.js deleted file mode 100644 index 06a80fdb4cad2..0000000000000 --- a/apps/src/maze/tiles.js +++ /dev/null @@ -1,59 +0,0 @@ -var utils = require('../utils'); - -var Tiles = module.exports; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Tiles.Direction = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * The types of squares in the Maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Tiles.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -Tiles.TurnDirection = { LEFT: -1, RIGHT: 1}; -Tiles.MoveDirection = { FORWARD: 0, RIGHT: 1, BACKWARD: 2, LEFT: 3}; - -Tiles.directionToDxDy = function (direction) { - switch (direction) { - case Tiles.Direction.NORTH: - return {dx: 0, dy: -1}; - case Tiles.Direction.EAST: - return {dx: 1, dy: 0}; - case Tiles.Direction.SOUTH: - return {dx: 0, dy: 1}; - case Tiles.Direction.WEST: - return {dx: -1, dy: 0}; - } - throw new Error('Invalid direction value' + direction); -}; - -Tiles.directionToFrame = function (direction4) { - return utils.mod(direction4 * 4, 16); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Tiles.constrainDirection4 = function (d) { - return utils.mod(d, 4); -}; diff --git a/apps/src/maze/wordsearch.js b/apps/src/maze/wordsearch.js deleted file mode 100644 index 4a5183e821dd1..0000000000000 --- a/apps/src/maze/wordsearch.js +++ /dev/null @@ -1,183 +0,0 @@ -import Subtype from './subtype'; -import WordSearchDrawer from './wordsearchDrawer'; -import { SquareType } from './tiles'; -import { randomValue } from '../utils'; - -/** - * Create a new WordSearch. - */ -export default class WordSearch extends Subtype { - constructor(maze, config) { - super(maze, config); - this.goal_ = config.level.searchWord; - this.visited_ = ''; - this.map_ = config.level.map; - } - - getVisited() { - return this.visited_; - } - - /** - * @override - */ - isWordSearch() { - return true; - } - - /** - * @override - */ - createDrawer(svg) { - this.drawer = new WordSearchDrawer(this.maze_.map, '', svg); - } - - /** - * Returns true if the given row,col is both on the grid and not a wall - */ - isOpen_(row, col) { - const map = this.map_; - return ((map[row] !== undefined) && - (map[row][col] !== undefined) && - (map[row][col] !== SquareType.WALL)); - } - - /** - * Given a row and col, returns the row, col pair of any non-wall neighbors - */ - openNeighbors_(row, col) { - const neighbors = []; - if (this.isOpen_(row + 1, col)) { - neighbors.push([row + 1, col]); - } - if (this.isOpen_(row - 1, col)) { - neighbors.push([row - 1, col]); - } - if (this.isOpen_(row, col + 1)) { - neighbors.push([row, col + 1]); - } - if (this.isOpen_(row, col - 1)) { - neighbors.push([row, col - 1]); - } - - return neighbors; - } - - /** - * We never want to have a branch where either direction gets you the next - * correct letter. As such, a "wall" space should never have the same value as - * an open neighbor of an neighbor (i.e. if my non-wall neighbor has a non-wall - * neighbor whose value is E, I can't also be E) - */ - restrictedValues_(row, col) { - const map = this.map_; - const neighbors = this.openNeighbors_(row, col); - const values = []; - for (let i = 0; i < neighbors.length; i ++) { - const secondNeighbors = this.openNeighbors_(neighbors[i][0], neighbors[i][1]); - for (let j = 0; j < secondNeighbors.length; j++) { - const neighborRow = secondNeighbors[j][0]; - const neighborCol = secondNeighbors[j][1]; - // push value to restricted list - const val = WordSearch.letterValue(map[neighborRow][neighborCol], false); - values.push(val, false); - } - } - return values; - } - - /** - * Generate random tiles for walls (with some restrictions) and draw them to - * the svg. - * @override - */ - drawMapTiles(svg) { - let letter; - let restricted; - - for (let row = 0; row < this.map_.length; row++) { - for (let col = 0; col < this.map_[row].length; col++) { - const mapVal = this.map_[row][col]; - if (mapVal === WordSearch.EMPTY_CHAR) { - restricted = this.restrictedValues_(row, col); - letter = WordSearch.randomLetter(restricted); - } else { - letter = WordSearch.letterValue(mapVal, true); - } - - this.drawTile(svg, letter, row, col); - } - } - } - - /** - * Reset all tiles to beginning state - * @override - */ - resetTiles() { - for (let row = 0; row < this.map_.length; row++) { - for (let col = 0; col < this.map_[row].length; col++) { - this.drawer.updateTileHighlight(row, col, false); - } - } - document.getElementById('currentWordContents').textContent = ''; - this.visited_ = ''; - } - - /** - * Mark that we've visited a tile - * @param {number} row Row visited - * @param {number} col Column visited - * @param {boolean} animating True if this is while animating - */ - markTileVisited(row, col, animating) { - const letterCell = document.getElementById(WordSearchDrawer.cellId('letter', row, col)); - this.visited_ += letterCell.textContent; - - if (animating) { - this.drawer.updateTileHighlight(row, col, true); - document.getElementById('currentWordContents').textContent = this.visited_; - } - } - -} - -WordSearch.START_CHAR = '-'; -WordSearch.EMPTY_CHAR = '_'; -WordSearch.ALL_CHARS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", - "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; - -/** - * For wordsearch, values in Maze.map can take the form of a number (i.e. 2 means - * start), a letter ('A' means A), or a letter followed by x ('Nx' means N and - * that this is the finish. This function will strip the x, and will convert - * number values to WordSearch.START_CHAR - */ -WordSearch.letterValue = function (val) { - if (typeof(val) === "number") { - return WordSearch.START_CHAR; - } - - if (typeof(val) === "string") { - // temporary hack to allow us to have 4 as a letter - if (val.length === 2 && val[0] === '_') { - return val[1]; - } - return val[0]; - } - - throw new Error("unexpected value for letterValue"); -}; - -/** - * Return a random uppercase letter that isn't in the list of restrictions - */ -WordSearch.randomLetter = function (restrictions) { - let letterPool = WordSearch.ALL_CHARS; - if (restrictions) { - restrictions = new Set(restrictions); - letterPool = letterPool.filter(c => !restrictions.has(c)); - } - - return randomValue(letterPool); -}; diff --git a/apps/src/maze/wordsearchDrawer.js b/apps/src/maze/wordsearchDrawer.js deleted file mode 100644 index d0a49fb6730a7..0000000000000 --- a/apps/src/maze/wordsearchDrawer.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @fileoverview Base class for drawing the various Maze Skins (Bee, - * Farmer, Collector). Intended to be inherited from to provide - * skin-specific functionality. - */ -import Drawer, { SQUARE_SIZE, SVG_NS } from './drawer'; - -const color = { - black: "#000", - white: "#fff", -}; - -/** - * @param {MaseMap} map The map from the maze, which shows the current - * state of the dirt, flowers/honey, or treasure. - * @param {string} asset the asset url to draw - */ -export default class WordSearchDrawer extends Drawer { - - /** - * @override - */ - drawTile(svg, letter, row, col) { - const backgroundId = Drawer.cellId('backgroundLetter', row, col); - const group = document.createElementNS(SVG_NS, 'g'); - const background = document.createElementNS(SVG_NS, 'rect'); - - background.setAttribute('id', backgroundId); - background.setAttribute('width', SQUARE_SIZE); - background.setAttribute('height', SQUARE_SIZE); - background.setAttribute('x', col * SQUARE_SIZE); - background.setAttribute('y', row * SQUARE_SIZE); - background.setAttribute('stroke', '#000000'); - background.setAttribute('stroke-width', 3); - group.appendChild(background); - - let textElement = this.updateOrCreateText_('letter', row, col, letter); - group.appendChild(textElement); - svg.appendChild(group); - } - - /** - * @override - */ - updateOrCreateText_(prefix, row, col, text) { - let textElement = super.updateOrCreateText_(prefix, row, col, text); - textElement.setAttribute('class', 'search-letter'); - textElement.setAttribute('width', SQUARE_SIZE); - textElement.setAttribute('height', SQUARE_SIZE); - textElement.setAttribute('x', (col + 0.5) * SQUARE_SIZE); - textElement.setAttribute('y', (row + 0.5) * SQUARE_SIZE); - textElement.setAttribute('font-size', 32); - textElement.setAttribute('text-anchor', 'middle'); - textElement.setAttribute('font-family', 'Verdana'); - return textElement; - } - - /** - * Update a tile's highlighting. If we've flown over it, it should be green. - * Otherwise we have a checkboard approach. - */ - updateTileHighlight(row, col, highlighted) { - let backColor = (row + col) % 2 === 0 ? '#dae3f3' : '#ffffff'; - const textColor = highlighted ? color.white : color.black; - if (highlighted) { - backColor = '#00b050'; - } - const backgroundId = Drawer.cellId('backgroundLetter', row, col); - const textId = Drawer.cellId('letter', row, col); - - document.getElementById(backgroundId).setAttribute('fill', backColor); - const text = document.getElementById(textId); - text.setAttribute('fill', textColor); - - // should only be false in unit tests - if (text.getBBox) { - // center text. - const bbox = text.getBBox(); - const heightDiff = SQUARE_SIZE - bbox.height; - const targetTopY = row * SQUARE_SIZE + heightDiff / 2; - const offset = targetTopY - bbox.y; - - text.setAttribute("transform", `translate(0, ${offset})`); - } - } - -} diff --git a/apps/src/maze/wordsearchLevels.js b/apps/src/maze/wordsearchLevels.js index 6349419bae40d..24dfbca593733 100644 --- a/apps/src/maze/wordsearchLevels.js +++ b/apps/src/maze/wordsearchLevels.js @@ -1,7 +1,10 @@ -var Direction = require('./tiles').Direction; -var reqBlocks = require('./requiredBlocks'); +var tiles = require('@code-dot-org/maze').tiles; +var Direction = tiles.Direction; + var blockUtils = require('../block_utils'); +var reqBlocks = require('./requiredBlocks'); + var wordSearchToolbox = function () { return blockUtils.createToolbox( blockUtils.blockOfType('maze_moveNorth') + diff --git a/apps/test/unit/maze/beeCellTest.js b/apps/test/unit/maze/beeCellTest.js deleted file mode 100644 index ebbd2e3f2061f..0000000000000 --- a/apps/test/unit/maze/beeCellTest.js +++ /dev/null @@ -1,49 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import BeeCell from '@cdo/apps/maze/beeCell'; - -describe("BeeCell", function () { - var cellEquals = function (left, right) { - expect(left.tileType_).to.equal(right.tileType_); - expect(left.featureType_).to.equal(right.featureType_); - expect(left.originalValue_).to.equal(right.originalValue_); - expect(left.cloudType_).to.equal(right.cloudType_); - expect(left.flowerColor_).to.equal(right.flowerColor_); - expect(left.range_).to.equal(right.range_); - }; - - it("can parse all formerly-valid map values", function () { - var validate = function (map, dirt, expected) { - var cell = BeeCell.parseFromOldValues(map, dirt); - cellEquals(cell, expected); - }; - - validate(0, 0, new BeeCell(0)); - validate(1, 0, new BeeCell(1)); - validate(2, 0, new BeeCell(2)); - validate(1, 1, new BeeCell(1, BeeCell.FeatureType.FLOWER, 1)); - validate("P", 1, new BeeCell(1, BeeCell.FeatureType.FLOWER, 1, undefined, BeeCell.FlowerColor.PURPLE)); - validate("R", 1, new BeeCell(1, BeeCell.FeatureType.FLOWER, 1, undefined, BeeCell.FlowerColor.RED)); - validate(1, -1, new BeeCell(1, BeeCell.FeatureType.HIVE, 1)); - validate("FC", 1, new BeeCell(1, BeeCell.FeatureType.FLOWER, 1, BeeCell.CloudType.STATIC)); - validate("FC", -1, new BeeCell(1, BeeCell.FeatureType.HIVE, 1, BeeCell.CloudType.STATIC)); - }); - - it("generates all possible grid assets", function () { - var validate = function (original, expected) { - var assets = original.getPossibleGridAssets(); - expect(assets.length).to.equal(expected.length); - assets.forEach(function (asset, i) { - cellEquals(asset, expected[i]); - }); - }; - - validate(new BeeCell(0), [new BeeCell(0)]); - validate(new BeeCell(1), [new BeeCell(1)]); - validate(new BeeCell(1, 1, 1), [new BeeCell(1, 1, 1)]); - validate(new BeeCell(1, 1, 1, 0), [new BeeCell(1, 1, 1, 0)]); - validate(new BeeCell(1, 2, 1, 1), [new BeeCell(1, 1, 1, 0), new BeeCell(1, 0, 1, 0)]); - validate(new BeeCell(1, 2, 1, 2), [new BeeCell(1, 1, 1, 0), new BeeCell(1, undefined, undefined, 0)]); - validate(new BeeCell(1, 2, 1, 3), [new BeeCell(1, 0, 1, 0), new BeeCell(1, undefined, undefined, 0)]); - validate(new BeeCell(1, 2, 1, 4), [new BeeCell(1, 1, 1, 0), new BeeCell(1, 0, 1, 0), new BeeCell(1, undefined, undefined, 0)]); - }); -}); diff --git a/apps/test/unit/maze/beeDrawingTest.js b/apps/test/unit/maze/beeDrawingTest.js deleted file mode 100644 index 1fdf9d876ccf4..0000000000000 --- a/apps/test/unit/maze/beeDrawingTest.js +++ /dev/null @@ -1,208 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import Bee from '@cdo/apps/maze/bee'; -import BeeCell from '@cdo/apps/maze/beeCell'; -import MazeMap from '@cdo/apps/maze/mazeMap'; -import BeeItemDrawer from '@cdo/apps/maze/beeItemDrawer'; - -function setGlobals() { - expect(document.getElementById('svgMaze')).to.be.null; - - const svgMaze = document.createElement('div'); - svgMaze.id = 'svgMaze'; - svgMaze.innerHTML = '
'; - document.body.appendChild(svgMaze); - - expect(document).not.to.be.undefined; - expect(document.getElementById('svgMaze'), 'document has an svgMaze').not.to.be.undefined; - expect(document.getElementsByClassName('pegman-location').length).to.equal(1); - - svg = document.getElementById('svgMaze'); -} - -function cleanupGlobals() { - document.body.removeChild(document.getElementById('svgMaze')); - expect(document.getElementById('svgMaze')).to.be.null; -} - -var svg; - -function createFakeSkin() { - // BeeItemDrawer takes a skin as an input. Rather than load the actual skin, - // we'll just fake those fields that we need - return { - redFlower: 'redFlower.png', - purpleFlower: 'purpleFlower.png', - honey: 'honey.png', - cloud: 'cloud.png', - flowerComb: 'flowercomb.png', - numbers: 'numbers.png', - cloudAnimation: 'cloudAnimation.png', - }; -} - -var skin = createFakeSkin(); - -function validateImages(setup, defaultFlower) { - - // create a 1 row map with all of our values - var map = [setup.map(function (item) { return item[0]; })]; - var initialDirtMap = [setup.map(function (item) { return item[2]; })]; - - var fakeMaze = { - map: MazeMap.parseFromOldValues(map, initialDirtMap, BeeCell) - }; - - // create a config with a level based on the contraints from setup - var config = { - level: { - honeyGoal: 1, - map: map, - flowerType: defaultFlower, - startDirection: 1, - initialDirt: initialDirtMap - } - }; - - // create a bee with a shim maze - var bee = new Bee(fakeMaze, config); - - var drawer = new BeeItemDrawer(fakeMaze.map, skin, svg, bee); - - var row = 0; - - // col is the column in our 1 row map, which is the equivalent to the row - // of the same number in our setup list - setup.forEach(function (item, col) { - var running = item[3]; - var expectedCloud = item[4]; - var expectedText = item[5]; - var imgType = item[6]; - - drawer.updateItemImage(row, col, running); - - var img = document.getElementById(BeeItemDrawer.cellId('beeItem', 0, col)); - var counter = document.getElementById(BeeItemDrawer.cellId('counter', 0, col)); - var cloud = document.getElementById(BeeItemDrawer.cellId('cloud', 0, col)); - - try { - expect(img === null).to.equal(imgType === null); - - if (img) { - expect(img.getAttribute('xlink:href')).to.equal(skin[imgType]); - expect(img.getAttribute('visibility')).to.equal('visible'); - expect(parseInt(img.getAttribute('x'))).to.equal(50 * col); - expect(parseInt(img.getAttribute('width'))).to.equal(50); - } - - if (counter) { - var actualText = counter.firstChild.nodeValue; - expect(actualText).to.equal(expectedText); - } - - var actualCloud = - !!(cloud && (cloud.getAttribute('visibility') === 'visible')); - expect(actualCloud).to.equal(expectedCloud); - - } catch (exc) { - // output which item is failing - if (exc.message) { - // eslint-disable-next-line no-console - console.log(exc.message + ' for index #' + col); - } - throw exc; - } - }); - -} - -describe("beeItemDrawer", function () { - beforeEach(setGlobals); - afterEach(cleanupGlobals); - - it ("red flower default", function () { - // map, dirtMap, initialDirtmap, running, expected index, expected image - var setup = [ - // everything but the last 3 rows is the same whether or not we're running - [2, 0, 0, true, false, '', null], - [1, 1, 1, true, false, '1', 'redFlower'], - [1, 2, 2, true, false, '2', 'redFlower'], - [1, 11, 11, true, false,'11', 'redFlower'], - [1, 98, 98, true, false, '0', 'redFlower'], // 98 -> 0 - [1, 99, 99, true, false, '', 'redFlower'], // 99 -> unlimited - [1, -1, -1, true, false, '1', 'honey'], - [1, -2, -2, true, false, '2', 'honey'], - [1, -11, -11, true, false,'11', 'honey'], - [1, -98, -98, true, false, '0', 'honey'], - [1, -99, -99, true, false, '', 'honey'], - // red with default red - behaves same as map = 1 - ['R', 1, 1, true, false, '1', 'redFlower'], - // purple with default red - ['P', 1, 1, true, false, '1', 'purpleFlower'], - ['FC', 1, 1, true, false, '1', 'redFlower'], // flowercomb - ['FC', -1, -1, true, false, '1', 'honey'], // flowercomb - - [2, 0, 0, false, false, '', null], - [1, 1, 1, false, false, '1', 'redFlower'], - [1, 2, 2, false, false, '2', 'redFlower'], - [1, 11, 11, false, false,'11', 'redFlower'], - [1, 98, 98, false, false, '0', 'redFlower'], // 98 -> 0 - [1, 99, 99, false, false, '', 'redFlower'], // 99 -> unlimited - [1, -1, -1, false, false, '1', 'honey'], - [1, -2, -2, false, false, '2', 'honey'], - [1, -11, -11, false, false,'11', 'honey'], - [1, -98, -98, false, false, '0', 'honey'], - [1, -99, -99, false, false, '', 'honey'], - ['R', 1, 1, false, false, '1', 'redFlower'], - // purple with default red - ['P', 1, 1, false, false, '?', 'purpleFlower'], - ['FC', 1, 1, false, true, '', 'redFlower'], // flowercomb - ['FC', -1, -1, false, true, '', 'honey'], // flowercomb - ]; - - validateImages(setup, 'redWithNectar'); - }); - - it ("purple flower default", function () { - // map, dirtMap, initialDirtmap, expected index, expected image - var setup = [ - // everything but the last 3 rows is the same whether or not we're running - [2, 0, 0, true, false, '', null], - [1, 1, 1, true, false, '1', 'purpleFlower'], - [1, 2, 2, true, false, '2', 'purpleFlower'], - [1, 11, 11, true, false,'11', 'purpleFlower'], - [1, 98, 98, true, false, '0', 'purpleFlower'], // 98 -> 0 - [1, 99, 99, true, false, '', 'purpleFlower'], // 99 -> unlimited - [1, -1, -1, true, false, '1', 'honey'], - [1, -2, -2, true, false, '2', 'honey'], - [1, -11, -11, true, false,'11', 'honey'], - [1, -98, -98, true, false, '0', 'honey'], - [1, -99, -99, true, false, '', 'honey'], - // red with default purple - visible whether or not running - ['R', 1, 1, true, false, '1', 'redFlower'], - // purple with default purple - same as map = 1 - ['P', 1, 1, true, false, '1', 'purpleFlower'], - ['FC', 1, 1, true, false, '1', 'purpleFlower'], // flowercomb - ['FC', -1, -1, true, false, '1', 'honey'], // flowercomb - - [2, 0, 0, false, false, '', null], - [1, 1, 1, false, false, '?', 'purpleFlower'], - [1, 2, 2, false, false, '?', 'purpleFlower'], - [1, 11, 11, false, false, '?', 'purpleFlower'], - [1, 98, 98, false, false, '?', 'purpleFlower'], - [1, 99, 99, false, false, '?', 'purpleFlower'], - [1, -1, -1, false, false, '1', 'honey'], - [1, -2, -2, false, false, '2', 'honey'], - [1, -11, -11, false, false,'11', 'honey'], - [1, -98, -98, false, false, '0', 'honey'], - [1, -99, -99, false, false, '', 'honey'], - // red with default purple - visible whether or not running - ['R', 1, 1, false, false, '1', 'redFlower'], - // purple with default purple - same as map = 1 - ['P', 1, 1, false, false, '?', 'purpleFlower'], - ['FC', 1, 1, false, true, '', 'purpleFlower'], // flowercomb - ['FC', -1, -1, false, true, '', 'honey'], // flowercomb - ]; - - validateImages(setup, 'purpleNectarHidden'); - }); -}); diff --git a/apps/test/unit/maze/beeTest.js b/apps/test/unit/maze/beeTest.js deleted file mode 100644 index f92ed806394bb..0000000000000 --- a/apps/test/unit/maze/beeTest.js +++ /dev/null @@ -1,81 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import Bee from '@cdo/apps/maze/bee'; -import BeeCell from '@cdo/apps/maze/beeCell'; -import MazeMap from '@cdo/apps/maze/mazeMap'; - -var baseLevel = { - honeyGoal: 1, - map: [ - [0] - ], - flowerType: 'redWithNectar', - startDirection: 1, - initialDirt: [ - [0] - ] -}; - -describe("Bee", function () { - it("fails if no flowerType", function () { - var maze = {}; - var config = { - level: baseLevel - }; - delete config.level.flowerType; - expect(() => { - new Bee(maze, config); - }).to.throw(/bad flowerType for Bee/); - }); - - - it("fails if invalid flowerType", function () { - var maze = {}; - var config = { - level: Object.assign(baseLevel, { - flowerType: 'invalid' - }) - }; - expect(() => { - new Bee(maze, config); - }).to.throw(/bad flowerType for Bee/); - }); - - describe("isRedFlower", function () { - /** - * Shim a 1x1 maze with the given values and validate that we get the - * expected result when calling isRedFlower - */ - function validate(flowerType, mapValue, initialDirtValue, expected, msg) { - var map = [[mapValue]]; - - var config = { - level: Object.assign(baseLevel, { - flowerType: flowerType, - map: map, - initialDirt: [[initialDirtValue]] - }) - }; - var maze = { - map: MazeMap.parseFromOldValues(config.level.map, config.level.initialDirt, BeeCell) - }; - var bee = new Bee(maze, config); - expect(bee.isRedFlower(0, 0), msg).to.equal(expected); - } - - it("red default", function () { - validate('redWithNectar', 1, 1, true, 'default flower'); - validate('redWithNectar', 1, -1, false, 'default hive'); - validate('redWithNectar', 'P', 1, false, 'overriden purple'); - validate('redWithNectar', 'R', 1, true, 'overriden red'); - validate('redWithNectar', 'FC', 1, true, 'overriden cloud'); - }); - - it("purple default", function () { - validate('purpleNectarHidden', 1, 1, false, 'default flower'); - validate('purpleNectarHidden', 1, -1, false, 'default hive'); - validate('purpleNectarHidden', 'P', 1, false, 'overriden purple'); - validate('purpleNectarHidden', 'R', 1, true, 'overriden red'); - validate('purpleNectarHidden', 'FC', 1, false, 'overriden cloud'); - }); - }); -}); diff --git a/apps/test/unit/maze/cellTest.js b/apps/test/unit/maze/cellTest.js deleted file mode 100644 index fcae087dcd9e7..0000000000000 --- a/apps/test/unit/maze/cellTest.js +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import Cell from '@cdo/apps/maze/cell'; - -describe("Cell", function () { - it("counts as dirt whenever it has a value", function () { - expect(false).to.equal(new Cell(0).isDirt()); - expect(false).to.equal(new Cell(0, undefined).isDirt()); - expect(true).to.equal(new Cell(0, 1).isDirt()); - expect(true).to.equal(new Cell(0, -11).isDirt()); - expect(false).to.equal(new Cell(1).isDirt()); - expect(false).to.equal(new Cell(1, undefined).isDirt()); - expect(true).to.equal(new Cell(1, 1).isDirt()); - expect(true).to.equal(new Cell(1, -11).isDirt()); - }); -}); diff --git a/apps/test/unit/maze/dirtDrawingTest.js b/apps/test/unit/maze/dirtDrawingTest.js deleted file mode 100644 index df9906c9b5c14..0000000000000 --- a/apps/test/unit/maze/dirtDrawingTest.js +++ /dev/null @@ -1,205 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import DirtDrawer from '@cdo/apps/maze/dirtDrawer'; -import MazeMap from '@cdo/apps/maze/mazeMap'; -import Cell from '@cdo/apps/maze/cell'; - -let svg; - -function setGlobals() { - expect(document.getElementById('svgMaze')).to.be.null; - svg = document.createElement('div'); - svg.id = 'svgMaze'; - svg.innerHTML = '
'; - document.body.appendChild(svg); -} - -function cleanupGlobals() { - document.body.removeChild(svg); - expect(document.getElementById('svgMaze')).to.be.null; -} - -function createFakeSkin() { - return { - dirt: "http://fakedirt.png" - }; -} - -describe("DirtDrawer", function () { - beforeEach(setGlobals); - afterEach(cleanupGlobals); - - // The actual values of these are ignored by most of these tests - var dirtMap = MazeMap.parseFromOldValues([ - [1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 1] - ], [ - [0, 1, 2, 10, 11, -1, -2, -10, -11], - [0, 1, 2, 10, 11, -1, -2, -10, -11], - [0, 1, 2, 10, 11, -1, -2, -10, -11] - ], Cell); - - var skin = createFakeSkin(); - - it("spriteIndexForDirt", function () { - expect(DirtDrawer.spriteIndexForDirt(-11)).to.equal(0); - expect(DirtDrawer.spriteIndexForDirt(-10)).to.equal(1); - expect(DirtDrawer.spriteIndexForDirt(-9)).to.equal(2); - expect(DirtDrawer.spriteIndexForDirt(-8)).to.equal(3); - expect(DirtDrawer.spriteIndexForDirt(-7)).to.equal(4); - expect(DirtDrawer.spriteIndexForDirt(-6)).to.equal(5); - expect(DirtDrawer.spriteIndexForDirt(-5)).to.equal(6); - expect(DirtDrawer.spriteIndexForDirt(-4)).to.equal(7); - expect(DirtDrawer.spriteIndexForDirt(-3)).to.equal(8); - expect(DirtDrawer.spriteIndexForDirt(-2)).to.equal(9); - expect(DirtDrawer.spriteIndexForDirt(-1)).to.equal(10); - expect(DirtDrawer.spriteIndexForDirt(0)).to.equal(-1); - expect(DirtDrawer.spriteIndexForDirt(1)).to.equal(11); - expect(DirtDrawer.spriteIndexForDirt(2)).to.equal(12); - expect(DirtDrawer.spriteIndexForDirt(3)).to.equal(13); - expect(DirtDrawer.spriteIndexForDirt(4)).to.equal(14); - expect(DirtDrawer.spriteIndexForDirt(5)).to.equal(15); - expect(DirtDrawer.spriteIndexForDirt(6)).to.equal(16); - expect(DirtDrawer.spriteIndexForDirt(7)).to.equal(17); - expect(DirtDrawer.spriteIndexForDirt(8)).to.equal(18); - expect(DirtDrawer.spriteIndexForDirt(9)).to.equal(19); - expect(DirtDrawer.spriteIndexForDirt(10)).to.equal(20); - expect(DirtDrawer.spriteIndexForDirt(11)).to.equal(21); - }); - - it("createImage", function () { - var drawer = new DirtDrawer(dirtMap, skin.dirt, svg); - - var row = 2; - var col = 3; - - var image = document.getElementById(DirtDrawer.cellId('dirt', row, col)); - var clip = document.getElementById(DirtDrawer.cellId('dirtClip', row, col)); - - expect(image, 'image doesnt exist to start').to.be.null; - expect(clip, 'clipPath doesnt exist to start').to.be.null; - - drawer.getOrCreateImage_('dirt', row, col); - - image = document.getElementById(DirtDrawer.cellId('dirt', row, col)); - clip = document.getElementById(DirtDrawer.cellId('dirtClip', row, col)); - - expect(image, 'image got created').not.to.be.undefined; - expect(clip, 'clipPath got created').not.to.be.undefined; - expect(clip.childNodes, 'clipPath has children').not.to.be.undefined; - var rect = clip.childNodes[0]; - expect(rect, 'clipPath has a child').not.to.be.undefined; - - expect(parseInt(rect.getAttribute('x'))).to.equal(150); - expect(parseInt(rect.getAttribute('y'))).to.equal(100); - expect(parseInt(rect.getAttribute('width'))).to.equal(50); - expect(parseInt(rect.getAttribute('height'))).to.equal(50); - - expect(image.getAttribute('xlink:href')).to.equal(skin.dirt); - expect(parseInt(image.getAttribute('height'))).to.equal(50); - expect(parseInt(image.getAttribute('width'))).to.equal(1100); - expect(image.getAttribute('clip-path')).to.equal('url(#' + DirtDrawer.cellId('dirtClip', row, col) + ')'); - }); - - describe("updateItemImage", function () { - var drawer; - var row = 0; - var col = 0; - var dirtId = DirtDrawer.cellId('', row, col); - - beforeEach(function () { - drawer = new DirtDrawer(dirtMap, skin.dirt, svg); - }); - - it("update from nonExistent to hidden", function () { - expect(document.getElementById(dirtId), 'image starts out nonexistent').to.be.null; - drawer.map_.setValue(row, col, 0); - drawer.updateItemImage(row, col); - expect(document.getElementById(dirtId), 'image still doesnt exist').to.be.null; - }); - - it("update from nonExistent to visible", function () { - expect(document.getElementById(dirtId), 'image starts out nonexistent').to.be.null; - drawer.map_.setValue(row, col, -11); - drawer.updateItemImage(row, col); - expect(document.getElementById(dirtId), 'image now exists').not.to.be.null; - }); - - it("update from visible to hidden", function () { - // create image - drawer.map_.setValue(row, col, -11); - drawer.updateItemImage(row, col); - var image = document.getElementById(dirtId); - expect(image, 'image exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('visible'); - - drawer.map_.setValue(row, col, 0); - drawer.updateItemImage(row, col); - image = document.getElementById(dirtId); - expect(image, 'image still exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('hidden'); - }); - - it("update from visible to visible", function () { - // create image - drawer.map_.setValue(row, col, -11); - drawer.updateItemImage(row, col); - var image = document.getElementById(dirtId); - expect(image, 'image exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('visible'); - expect(parseInt(image.getAttribute('x'))).to.equal(50 * col); - expect(parseInt(image.getAttribute('y'))).to.equal(50 * row); - - drawer.map_.setValue(row, col, -9); - drawer.updateItemImage(row, col); - image = document.getElementById(dirtId); - expect(image, 'image still exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('visible'); - // make sure we updated x/y - expect(parseInt(image.getAttribute('x'))).to.equal(50 * (col - 2)); - expect(parseInt(image.getAttribute('y'))).to.equal(50 * row); - }); - - it("update from hidden to visible", function () { - // create image - drawer.map_.setValue(row, col, -11); - drawer.updateItemImage(row, col); - // hide it - drawer.map_.setValue(row, col, 0); - drawer.updateItemImage(row, col); - - var image = document.getElementById(dirtId); - expect(image, 'image exists').not.to.be.null; - expect(image.getAttribute('visibility'), 'image is hidden').to.equal('hidden'); - - drawer.map_.setValue(row, col, -9); - drawer.updateItemImage(row, col); - image = document.getElementById(dirtId); - expect(image, 'image still exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('visible'); - // make sure we updated x/y - expect(parseInt(image.getAttribute('x'))).to.equal(50 * (col - 2)); - expect(parseInt(image.getAttribute('y'))).to.equal(50 * row); - }); - - it("update from hidden to hidden", function () { - // create image - drawer.map_.setValue(row, col, -11); - drawer.updateItemImage(row, col); - // hide it - drawer.map_.setValue(row, col, 0); - drawer.updateItemImage(row, col); - - var image = document.getElementById(dirtId); - expect(image, 'image exists').not.to.be.null; - expect(image.getAttribute('visibility'), 'image is hidden').to.equal('hidden'); - - drawer.map_.setValue(row, col, 0); - drawer.updateItemImage(row, col); - image = document.getElementById(dirtId); - expect(image, 'image still exists').not.to.be.null; - expect(image.getAttribute('visibility')).to.equal('hidden'); - }); - }); - -}); diff --git a/apps/test/unit/maze/drawCornersTest.js b/apps/test/unit/maze/drawCornersTest.js deleted file mode 100644 index d45c843401db9..0000000000000 --- a/apps/test/unit/maze/drawCornersTest.js +++ /dev/null @@ -1,69 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import { SVG_NS } from '@cdo/apps/constants'; -import Collector from '@cdo/apps/maze/collector'; -import Cell from '@cdo/apps/maze/cell'; -import MazeMap from '@cdo/apps/maze/mazeMap'; - -describe("drawCorners", function () { - it("draws corners only when needed", function () { - const svg = document.createElementNS(SVG_NS, 'svg'); - document.body.appendChild(svg); - - const corners = ['NW', 'NE', 'SW', 'SE']; - const empty_config = { - level: {}, - skin: { - corners: 'corners.png' - } - }; - - function verify(name, maze, target, expected) { - const map = MazeMap.deserialize(maze.map(row => row.map(val => { - return {tileType: val}; - })), Cell); - - const collector = new Collector({map}, empty_config); - - collector.drawCorners(svg, target[0], target[1], name); - corners.forEach(corner => { - const id = `tileCorner${corner}ClipPath${name}`; - const corner_exists = svg.getElementById(id) !== null; - const expect_corner_to_exist = expected.indexOf(corner) !== -1; - expect(corner_exists, `${name}: expected ${corner} to be ${expect_corner_to_exist}; it is ${corner_exists}`).to.equal(expect_corner_to_exist); - }); - - svg.innerHTML = ''; - } - - verify('all corners', [ - [0, 1, 0], - [1, 1, 1], - [0, 1, 0], - ], [1, 1], ['NW', 'NE', 'SW', 'SE']); - - verify('no corners', [ - [1, 1, 1], - [1, 1, 1], - [1, 1, 1], - ], [1, 1], []); - - verify('NW', [ - [0, 1, 0], - [1, 1, 0], - [0, 0, 0], - ], [1, 1], ['NW']); - - verify('NE', [ - [1, 1, 0], - [1, 1, 1], - [1, 1, 1], - ], [1, 1], ['NE']); - - verify('S', [ - [1, 1, 1], - [1, 1, 1], - [0, 1, 0], - ], [1, 1], ['SE', 'SW']); - }); -}); - diff --git a/apps/test/unit/maze/drawerTest.js b/apps/test/unit/maze/drawerTest.js deleted file mode 100644 index 0ed60aca394d9..0000000000000 --- a/apps/test/unit/maze/drawerTest.js +++ /dev/null @@ -1,12 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import Drawer from '@cdo/apps/maze/drawer'; - -describe("Drawer", function () { - it("can generate the correct cellIds", function () { - expect(Drawer.cellId('dirt', 0, 0)).to.equal('dirt_0_0'); - expect(Drawer.cellId('dirt', 2, 4)).to.equal('dirt_2_4'); - expect(Drawer.cellId('dirt', 1, 5)).to.equal('dirt_1_5'); - expect(Drawer.cellId('dirt', 3, 1)).to.equal('dirt_3_1'); - }); -}); - diff --git a/apps/test/unit/maze/harvesterCellTest.js b/apps/test/unit/maze/harvesterCellTest.js deleted file mode 100644 index 6ad6f964a4cbe..0000000000000 --- a/apps/test/unit/maze/harvesterCellTest.js +++ /dev/null @@ -1,68 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import HarvesterCell from '@cdo/apps/maze/harvesterCell'; -import { SquareType } from '@cdo/apps/maze/tiles'; - -describe("HarvesterCell", () => { - it("has reasonable defaults", () => { - const cell = new HarvesterCell(); - - expect(cell.startsHidden()).to.equal(false); - expect(cell.isVariable()).to.equal(false); - expect(cell.featureName()).to.equal('none'); - expect(cell.featureType()).to.equal(HarvesterCell.FeatureType.NONE); - expect(cell.possibleFeatures_).to.deep.equal([HarvesterCell.FeatureType.NONE]); - }); - - it("can vary on type", () => { - const variableFeatureCell = HarvesterCell.deserialize({ - tileType: SquareType.OPEN, - value: 1, - possibleFeatures: [HarvesterCell.FeatureType.CORN, HarvesterCell.FeatureType.PUMPKIN] - }); - - const variableFeatures = variableFeatureCell.getPossibleGridAssets(); - - expect(variableFeatures.length).to.equal(2); - expect(variableFeatures[0].serialize()).to.deep.equal({ - tileType: SquareType.OPEN, - value: 1, - range: 1, - possibleFeatures: [HarvesterCell.FeatureType.CORN], - startsHidden: true - }); - expect(variableFeatures[1].serialize()).to.deep.equal({ - tileType: SquareType.OPEN, - value: 1, - range: 1, - possibleFeatures: [HarvesterCell.FeatureType.PUMPKIN], - startsHidden: true - }); - }); - - it("can vary on quantitiy", () => { - const variableRangeCell = HarvesterCell.deserialize({ - tileType: SquareType.OPEN, - value: 1, - range: 2, - possibleFeatures: [HarvesterCell.FeatureType.CORN] - }); - - const variableRanges = variableRangeCell.getPossibleGridAssets(); - - expect(variableRanges.length).to.equal(2); - expect(variableRanges[0].serialize()).to.deep.equal({ - tileType: SquareType.OPEN, - value: 1, - range: 1, - possibleFeatures: [HarvesterCell.FeatureType.CORN], - startsHidden: false - }); - expect(variableRanges[1].serialize()).to.deep.equal({ - tileType: SquareType.OPEN, - value: 2, - range: 2, - possibleFeatures: [HarvesterCell.FeatureType.CORN], - startsHidden: false - }); - }); -}); diff --git a/apps/test/unit/maze/mazeControllerTest.js b/apps/test/unit/maze/mazeControllerTest.js deleted file mode 100644 index ccb6928024005..0000000000000 --- a/apps/test/unit/maze/mazeControllerTest.js +++ /dev/null @@ -1,70 +0,0 @@ -import { expect } from '../../util/configuredChai'; -import DirtDrawer from '@cdo/apps/maze/dirtDrawer'; -import MazeController from '@cdo/apps/maze/mazeController'; - -describe("Maze", function () { - var dirtMap = [ - [{ - "tileType": 2 - }, { - "tileType": 1, - "value": 1 - }, { - "tileType": 1, - "value": -1 - }], - ]; - - describe("scheduleDirtChange", function () { - let mazeController; - - beforeEach(function () { - document.body.innerHTML = '
'; - mazeController = new MazeController({ - serializedMaze: dirtMap - }, { - }, { - skinId: 'farmer', - level: {}, - skin: { - dirt: 'dirt.png' - } - }); - mazeController.subtype.createDrawer(document.getElementById('svgMaze')); - mazeController.pegmanX = 0; - mazeController.pegmanY = 0; - }); - - it("can cycle through all types", function () { - var dirtId = DirtDrawer.cellId('', mazeController.pegmanX, mazeController.pegmanY); - var image; - - // image starts out nonexistant - expect(document.getElementById(dirtId), 'image starts out nonexistant').to.be.null; - - mazeController.scheduleFill(); - image = document.getElementById(dirtId); - // image now exists and is dirt - expect(image).not.to.be.null; - expect(image.getAttribute('x'), 'image is dirt').to.equal("-550"); - - mazeController.scheduleDig(); - image = document.getElementById(dirtId); - // tile is flat, image is therefore hidden - expect(image, 'image now exists').not.to.be.null; - expect(image.getAttribute('visibility'), 'tile is flat, image is therefore hidden').to.equal('hidden'); - - mazeController.scheduleDig(); - image = document.getElementById(dirtId); - // image is a holde - expect(image, 'image now exists').not.to.be.null; - expect(image.getAttribute('x'), 'image is a hole').to.equal("-500"); - - mazeController.scheduleFill(); - image = document.getElementById(dirtId); - // tile is flat, image is therefore hidden - expect(image, 'image now exists').not.to.be.null; - expect(image.getAttribute('visibility'), 'tile is flat, image is therefore hidden').to.equal('hidden'); - }); - }); -}); diff --git a/apps/test/unit/maze/mazeMapTest.js b/apps/test/unit/maze/mazeMapTest.js deleted file mode 100644 index f6feefa07f849..0000000000000 --- a/apps/test/unit/maze/mazeMapTest.js +++ /dev/null @@ -1,63 +0,0 @@ -const expect = require('../../util/configuredChai').expect; - -const MazeMap = require('@cdo/apps/maze/mazeMap'); -const Cell = require('@cdo/apps/maze/cell'); - -describe("MazeMap", function () { - it("can deserialize serialized values", function () { - const serializedValues = [ - [{tileType: 1, value: 4}, {tileType: 2, value: 3}], - [{tileType: 3, value: 2}, {tileType: 4, value: 1}] - ]; - const mazeMap = MazeMap.deserialize(serializedValues, Cell); - - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(1, 4)); - expect(mazeMap.getCell(0, 1)).to.deep.equal(new Cell(2, 3)); - expect(mazeMap.getCell(1, 0)).to.deep.equal(new Cell(3, 2)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(4, 1)); - }); - - it("can parse from old (deprecated) maze initialization values", function () { - const map = [ - [0, 1], - [2, 3] - ]; - const initialDirt = [ - [0, "-1"], - ["1", 0] - ]; - const mazeMap = MazeMap.parseFromOldValues(map, initialDirt, Cell); - - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(0)); - expect(mazeMap.getCell(0, 1)).to.deep.equal(new Cell(1, -1)); - expect(mazeMap.getCell(1, 0)).to.deep.equal(new Cell(2, 1)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(3)); - }); - - it("can generate a variety of static grids when initialized with variable cells", function () { - const serializedValues = [ - [{tileType: 1, value: 1, range: 2}, {tileType: 2, value: 1}], - [{tileType: 3, value: 1}, {tileType: 4, value: 2, range: 3}] - ]; - - const mazeMap = MazeMap.deserialize(serializedValues, Cell); - expect(mazeMap.hasMultiplePossibleGrids()).to.be.true; - expect(mazeMap.staticGrids.length).to.equal(4); - - mazeMap.useGridWithId(0); - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(1, 1)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(4, 2)); - - mazeMap.useGridWithId(1); - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(1, 2)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(4, 2)); - - mazeMap.useGridWithId(2); - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(1, 1)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(4, 3)); - - mazeMap.useGridWithId(3); - expect(mazeMap.getCell(0, 0)).to.deep.equal(new Cell(1, 2)); - expect(mazeMap.getCell(1, 1)).to.deep.equal(new Cell(4, 3)); - }); -}); diff --git a/apps/test/unit/maze/mazeTest.js b/apps/test/unit/maze/mazeTest.js new file mode 100644 index 0000000000000..4da6474346acb --- /dev/null +++ b/apps/test/unit/maze/mazeTest.js @@ -0,0 +1,93 @@ +import sinon from 'sinon'; + +import { expect } from '../../util/configuredChai'; + +import Maze from '@cdo/apps/maze/maze'; +import ResultsHandler from '@cdo/apps/maze/results/resultsHandler'; +import { MazeController } from '@code-dot-org/maze'; + +describe("Maze", function () { + let maze; + let clock; + + beforeEach(function () { + clock = sinon.useFakeTimers(); + maze = new Maze(); + maze.controller = new MazeController({ + map: [[]] + }, { + movePegmanAnimationSpeedScale: 1 + }, { + level: {} + }); + maze.resultsHandler = new ResultsHandler(maze.controller, {}); + maze.prepareForExecution_(); + }); + + afterEach(function () { + clock.restore(); + }); + + describe("animation queue", function () { + let animateActionSpy; + let finishAnimationsSpy; + let getActionsSpy; + + beforeEach(function () { + animateActionSpy = sinon.stub(maze, 'animateAction_'); + finishAnimationsSpy = sinon.stub(maze, 'finishAnimations_'); + getActionsSpy = sinon.stub(maze.executionInfo, 'getActions'); + getActionsSpy.returns(new Array(2)); + }); + + afterEach(function () { + animateActionSpy.restore(); + finishAnimationsSpy.restore(); + getActionsSpy.restore(); + }); + + it("is initiated by scheduleAnimations", function () { + maze.scheduleAnimations_(false); + expect(finishAnimationsSpy.called).to.be.false; + clock.tick(999); + expect(finishAnimationsSpy.called).to.be.false; + clock.tick(1); + expect(finishAnimationsSpy.called).to.be.true; + }); + + it("can be rate-adjusted", function () { + const scheduleSingleAnimationSpy = sinon.stub(maze, 'scheduleSingleAnimation_'); + + expect(finishAnimationsSpy.called).to.be.false; + + expect(maze.stepSpeed).to.equal(100); + expect(maze.scale.stepSpeed).to.equal(5); + expect(maze.controller.skin.movePegmanAnimationSpeedScale).to.equal(1); + + maze.scheduleAnimations_(false); + expect(scheduleSingleAnimationSpy.withArgs(0, new Array(2), false, 500).calledOnce).to.be.true; + + maze.stepSpeed = 200; + maze.scheduleAnimations_(false); + expect(scheduleSingleAnimationSpy.withArgs(0, new Array(2), false, 1000).calledOnce).to.be.true; + + maze.scale.stepSpeed = 1; + maze.scheduleAnimations_(false); + expect(scheduleSingleAnimationSpy.withArgs(0, new Array(2), false, 200).calledOnce).to.be.true; + + scheduleSingleAnimationSpy.restore(); + }); + + it("can be canceled by a reset", function () { + const controllerResetSpy = sinon.stub(maze.controller, 'reset'); + + maze.scheduleAnimations_(false); + expect(finishAnimationsSpy.called).to.be.false; + maze.reset_(); + clock.tick(1000); + expect(finishAnimationsSpy.called).to.be.false; + + controllerResetSpy.restore(); + }); + }); +}); diff --git a/apps/test/unit/maze/resultsHandlerTest.js b/apps/test/unit/maze/resultsHandlerTest.js index ec67b2f26c211..1ad9dfbac1889 100644 --- a/apps/test/unit/maze/resultsHandlerTest.js +++ b/apps/test/unit/maze/resultsHandlerTest.js @@ -7,7 +7,8 @@ const WordsearchHandler = require('@cdo/apps/maze/results/wordsearch'); const HarvesterHandler = require('@cdo/apps/maze/results/harvester'); const PlanterHandler = require('@cdo/apps/maze/results/planter'); const ResultsHandler = require('@cdo/apps/maze/results/resultsHandler'); -import MazeController from '@cdo/apps/maze/mazeController'; + +import { MazeController } from '@code-dot-org/maze'; const createResultsHandlerForSubtype = require('@cdo/apps/maze/results/utils').createResultsHandlerForSubtype; diff --git a/apps/test/unit/maze/wordsearchTest.js b/apps/test/unit/maze/wordsearchTest.js deleted file mode 100644 index 82d14b5335491..0000000000000 --- a/apps/test/unit/maze/wordsearchTest.js +++ /dev/null @@ -1,82 +0,0 @@ -import { expect } from '../../util/configuredChai'; - -import WordSearch from '@cdo/apps/maze/wordsearch'; - -function setGlobals() { - document.body.innerHTML = ''; -} - -describe("wordsearch: letterValue", function () { - expect(WordSearch.START_CHAR).not.to.be.undefined; - - it("letterValue", function () { - expect(WordSearch.letterValue('A', true)).to.equal('A'); - expect(WordSearch.letterValue('B', true)).to.equal('B'); - expect(WordSearch.letterValue('Z', true)).to.equal('Z'); - - expect(WordSearch.letterValue('Ax', true)).to.equal('A'); - expect(WordSearch.letterValue('Bx', true)).to.equal('B'); - expect(WordSearch.letterValue('Zx', true)).to.equal('Z'); - - expect(WordSearch.letterValue(2, true)).to.equal(WordSearch.START_CHAR); - }); -}); - -describe("wordsearch: randomLetter", function () { - it ("randomLetter without restrictions", function () { - for (var i = 0; i < 100; i++) { - expect(WordSearch.randomLetter()).to.match(/^[A-Z]$/); - } - }); - - it ("randomLetter with restrictions", function () { - var allChars = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", - "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; - var letter = WordSearch.randomLetter(allChars.slice(0, -1)); - expect(letter, 'all other chars were restricted').to.equal('Z'); - - for (var i = 0; i < 200; i++) { - letter = WordSearch.randomLetter(['A']); - expect(letter, "failed on : " + letter).to.match(/^[B-Z]$/); - } - }); -}); - -describe("wordsearch: drawMapTiles", function () { - it ("simple wordsearch", function () { - // Create a fake maze. - var map = [ - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', 2, 'R', 'U', 'N', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'], - ['-', '-', '-', '-', '-', '-', '-', '-'] - ]; - - // create our fake document - setGlobals(); - - var fakeMaze = { - map: map - }; - var fakeConfig = { - level: { - searchWord: '', - map: map - }, - skin: { - tiles: 'tiles.png' - } - }; - - var wordSearch = new WordSearch(fakeMaze, fakeConfig); - wordSearch.createDrawer(document.getElementById('svgMaze')); - // Not currently doing any validation, so mostly just making sure no - // exceptions are thrown. - wordSearch.drawMapTiles(document.getElementById('svgMaze')); - }); - -}); diff --git a/apps/webpack.js b/apps/webpack.js index 251705ef8cdab..8578647e0c77e 100644 --- a/apps/webpack.js +++ b/apps/webpack.js @@ -19,6 +19,7 @@ var toTranspileWithinNodeModules = [ path.resolve(__dirname, 'node_modules', 'json-parse-better-errors'), path.resolve(__dirname, 'node_modules', '@code-dot-org', 'artist'), path.resolve(__dirname, 'node_modules', '@code-dot-org', 'craft'), + path.resolve(__dirname, 'node_modules', '@code-dot-org', 'maze'), ]; const scssIncludePath = path.resolve(__dirname, '..', 'shared', 'css'); diff --git a/apps/yarn.lock b/apps/yarn.lock index 3c07e56837f46..a971929a10482 100644 --- a/apps/yarn.lock +++ b/apps/yarn.lock @@ -93,6 +93,10 @@ version "0.1.0-cdo.0" resolved "https://registry.yarnpkg.com/@code-dot-org/js-numbers/-/js-numbers-0.1.0-cdo.0.tgz#810092a993c3c75441f2e5a3282f1257d9670715" +"@code-dot-org/maze@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@code-dot-org/maze/-/maze-1.0.1.tgz#1b85b8ab73a072d86ed58d86dd90fad459015e5c" + "@code-dot-org/p5.play@1.2.2-cdo": version "1.2.2-cdo" resolved "https://registry.yarnpkg.com/@code-dot-org/p5.play/-/p5.play-1.2.2-cdo.tgz#aea10ee61a6e4945b691cdfeaa7581861efe2d60" diff --git a/dashboard/test/ui/features/footer.feature b/dashboard/test/ui/features/footer.feature index 6f92710e9af38..702f24687d7b7 100644 --- a/dashboard/test/ui/features/footer.feature +++ b/dashboard/test/ui/features/footer.feature @@ -230,7 +230,8 @@ Feature: Checking the footer appearance @eyes_mobile @dashboard_db_access @as_student Scenario: Mobile Applab share small footer - Given I rotate to landscape + Given I am on "http://studio.code.org/home" + And I rotate to landscape And I start a new Applab project And I navigate to the shared version of my project And I rotate to portrait