function Map(display, __game) { /* private variables */ var __player; var __grid; var __dynamicObjects = []; var __objectDefinitions; var __lines; var __dom; var __domCSS = ''; var __allowOverwrite; var __keyDelay; var __refreshRate; var __intervals = []; var __chapterHideTimeout; /* unexposed variables */ this._properties = {}; this._display = display; this._dummy = false; // overridden by dummyMap in validate.js this._status = ''; /* wrapper */ function wrapExposedMethod(f, map) { return function () { var args = arguments; return __game._callUnexposedMethod(function () { return f.apply(map, args); }); }; }; /* unexposed getters */ this._getObjectDefinition = function(objName) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getObjectDefinition()';} return __objectDefinitions[objName]; }; this._getObjectDefinitions = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getObjectDefinitions()';} return __objectDefinitions; }; this._getGrid = function () { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getGrid()';} return __grid; }; this._getLines = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getLines()';} return __lines; }; /* exposed getters */ this.getDynamicObjects = function () { // copy dynamic object list to fix issue#166 var copy = []; for (var i = 0; i < __dynamicObjects.length; i++) { copy[i] = __dynamicObjects[i]; } return copy; }; this.getPlayer = function () { return __player; }; this.getWidth = function () { return __game._dimensions.width; }; this.getHeight = function () { return __game._dimensions.height; }; /* unexposed methods */ this._reset = function () { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._reset()';} __objectDefinitions = __game.getListOfObjects(); this._display.clear(); __grid = new Array(__game._dimensions.width); for (var x = 0; x < __game._dimensions.width; x++) { __grid[x] = new Array(__game._dimensions.height); for (var y = 0; y < __game._dimensions.height; y++) { __grid[x][y] = {type: 'empty'}; } } this.getDynamicObjects().forEach(function (obj) { obj._destroy(true); }); __dynamicObjects = []; __player = null; this._clearIntervals(); __lines = []; __dom = ''; this._overrideKeys = {}; // preload stylesheet for DOM level $.get('styles/dom.css', function (css) { __domCSS = css; }); this.finalLevel = false; this._callbackValidationFailed = false; }; this._clearIntervals = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._clearIntervals()';} for (var i = 0; i < __intervals.length; i++) { clearInterval(__intervals[i]); } __intervals = []; }; this._ready = function () { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._ready()';} // set refresh rate if one is specified if (__refreshRate) { // wrapExposedMethod is necessary here to prevent the `_moveToNextLevel` // call from breaking this.startTimer(wrapExposedMethod(function () { // refresh the map this.refresh(); // check for nonstandard victory condition __game._checkObjective() }, this), __refreshRate); } }; this._setProperties = function (mapProperties) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._setProperties()';} // set defaults this._properties = {}; __allowOverwrite = false; __keyDelay = 0; __refreshRate = null; // now set any properties that were passed in if (mapProperties) { this._properties = mapProperties; if (mapProperties.allowOverwrite === true) { __allowOverwrite = true; } if (mapProperties.keyDelay) { __keyDelay = mapProperties.keyDelay; } if (mapProperties.refreshRate) { __refreshRate = mapProperties.refreshRate; } } }; this._canMoveTo = function (x, y, myType) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._canMoveTo()';} var x = Math.floor(x); var y = Math.floor(y); if (x < 0 || x >= __game._dimensions.width || y < 0 || y >= __game._dimensions.height) { return false; } // look for static objects that can serve as obstacles var objType = __grid[x][y].type; var object = __objectDefinitions[objType]; if (object.impassable) { if (myType && object.passableFor && object.passableFor.indexOf(myType) > -1) { // this object is of a type that can pass the obstacle return true; } else if (typeof object.impassable === 'function') { // the obstacle is impassable only in certain circumstances return this._validateCallback(function () { return !object.impassable(__player, object); }); } else { // the obstacle is always impassable return false; } } else if (myType && object.impassableFor && object.impassableFor.indexOf(myType) > -1) { // this object is of a type that cannot pass the obstacle return false; } else { // no obstacle return true; } }; // Returns the object of the given type closest to target coordinates this._findNearestToPoint = function (type, targetX, targetY) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._findNearestToPoint()';} var foundObjects = []; // look for static objects for (var x = 0; x < this.getWidth(); x++) { for (var y = 0; y < this.getHeight(); y++) { if (__grid[x][y].type === type) { foundObjects.push({x: x, y: y}); } } } // look for dynamic objects for (var i = 0; i < __dynamicObjects.length; i++) { var object = __dynamicObjects[i]; if (object.getType() === type) { foundObjects.push({x: object.getX(), y: object.getY()}); } } // look for player if (type === 'player') { foundObjects.push({x: __player.getX(), y: __player.getY()}); } var dists = []; for (var i = 0; i < foundObjects.length; i++) { var obj = foundObjects[i]; dists[i] = Math.sqrt(Math.pow(targetX - obj.x, 2) + Math.pow(targetY - obj.y, 2)); // We want to find objects distinct from ourselves if (dists[i] === 0) { dists[i] = 999; } } var minDist = Math.min.apply(Math, dists); var closestTarget = foundObjects[dists.indexOf(minDist)]; return closestTarget; }; this._isPointOccupiedByDynamicObject = function (x, y) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._isPointOccupiedByDynamicObject()';} var x = Math.floor(x); var y = Math.floor(y); for (var i = 0; i < __dynamicObjects.length; i++) { var object = __dynamicObjects[i]; if (object.getX() === x && object.getY() === y) { return true; } } return false; }; this._findDynamicObjectAtPoint = function (x, y) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._findDynamicObjectAtPoint()';} var x = Math.floor(x); var y = Math.floor(y); for (var i = 0; i < __dynamicObjects.length; i++) { var object = __dynamicObjects[i]; if (object.getX() === x && object.getY() === y) { return object; } } return false; }; this._moveAllDynamicObjects = function () { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._moveAllDynamicObjects()';} // the way things work right now, teleporters must take precedence // over all other objects -- otherwise, pointers.jsx will not work // correctly. // TODO: make this not be the case // "move" teleporters __dynamicObjects.filter(function (object) { return (object.getType() === 'teleporter'); }).forEach(function(object) { object._onTurn(); }); // move everything else __dynamicObjects.filter(function (object) { return (object.getType() !== 'teleporter'); }).forEach(function(object) { object._onTurn(); }); // refresh only at the end this.refresh(); }; this._removeItemFromMap = function (x, y, klass) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._removeItemFromMap()';} var x = Math.floor(x); var y = Math.floor(y); if (__grid[x][y].type === klass) { __grid[x][y].type = 'empty'; } }; this._reenableMovementForPlayer = function (player) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._reenableMovementForPlayer()';} if (!this._callbackValidationFailed) { setTimeout(function () { player._canMove = true; }, __keyDelay); } }; this._hideChapter = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._hideChapter()';} // start fading out chapter immediately // unless it's a death message, in which case wait 2.5 sec clearInterval(__chapterHideTimeout); __chapterHideTimeout = setTimeout(function () { $('#chapter').fadeOut(1000); }, $('#chapter').hasClass('death') ? 2500 : 0); }; this._refreshDynamicObjects = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._refreshDynamicObjects()';} __dynamicObjects = __dynamicObjects.filter(function (obj) { return !obj.isDestroyed(); }); }; this._countTimers = function() { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._countTimers()';} return __intervals.length; } /* (unexposed) wrappers for game methods */ this._startOfStartLevelReached = function() { __game._startOfStartLevelReached = true; }; this._endOfStartLevelReached = function() { __game._endOfStartLevelReached = true; }; this._playSound = function (sound) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._playSound()';} __game.sound.playSound(sound); }; this._validateCallback = function (callback) { if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._validateCallback()';} return __game.validateCallback(callback); }; /* exposed methods */ this.refresh = wrapExposedMethod(function () { if (__dom) { this._display.clear(); var domHTML = __dom[0].outerHTML .replace(/"/g, "'") .replace(/