-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New architecture for the blueprint code.
User facing improvements: - now animating the tile swap - smoother animations by using Tweens Code improvements: - greatly reduced code duplication - modular architecture with separate classes, each with its own responsibility - removed recursive code calls, now all activity is always generated by user inputs and scheduled signals (AnimEvents), removed brittle state variables because of this - abstracted away logical coordinates to screen coordinates mapping (CoordMapper) - debug feature to let the user replace any tile (it also demonstrates how to check keyboard state) - full jsdoc documentation Removed features: - currently removed score tracking as it was not being used; it's easy to add back if need be
- Loading branch information
Showing
9 changed files
with
823 additions
and
530 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* A handler of animation events. | ||
* | ||
* Keeps track of currently ongoing animations and sends a signal to any | ||
* registered entity when all the animations are done. | ||
* | ||
* @see onAnimationComplete | ||
* @see onNextAnimationComplete | ||
* | ||
* @class | ||
*/ | ||
function AnimEvents() { | ||
this.animating = 0; | ||
this.onNextAnimationCompleteCallbacks = []; | ||
this.onAnimationCompleteCallbacks = []; | ||
} | ||
|
||
/** | ||
* Whether any animations are ongoing. Obviously this only takes into account | ||
* animations that are registered with this class. | ||
* | ||
* @see animationStarted() | ||
* @see animationFinished() | ||
* | ||
* @returns {Boolean} whether any animation is ongoing | ||
*/ | ||
AnimEvents.prototype.isAnimationOngoing = function() { | ||
return this.animating !== 0; | ||
} | ||
|
||
/** | ||
* Register the start of an animation. | ||
* | ||
* @see animationFinished | ||
*/ | ||
AnimEvents.prototype.animationStarted = function() { | ||
++this.animating; | ||
} | ||
|
||
/** | ||
* Register the end of an animation. | ||
* | ||
* This event can trigger the execution of the registered callbacks. | ||
* | ||
* @see onAnimationComplete | ||
* @see onNextAnimationComplete | ||
* @see animationStarted | ||
*/ | ||
AnimEvents.prototype.animationFinished = function() { | ||
--this.animating; | ||
if (this.animating === 0) { | ||
while (this.onNextAnimationCompleteCallbacks.length) { | ||
(this.onNextAnimationCompleteCallbacks.shift())(); | ||
} | ||
this.onAnimationCompleteCallbacks.forEach(function (callback) { | ||
callback(); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Register a callback to be called every time animations are finished (a | ||
* single call as soon as all the animations have stopped). | ||
* | ||
* @param {Function} callback - the parameterless function to call | ||
*/ | ||
AnimEvents.prototype.onAnimationComplete = function(callback) { | ||
this.onAnimationCompleteCallbacks.push(callback); | ||
} | ||
|
||
/** | ||
* Register a callback to be called only once, the next time animations are | ||
* finished (a single call as soon as all the animations have stopped). After | ||
* that, the callback is automatically deregistered. | ||
* | ||
* NOTE: The callbacks registered with this function are executed *before* the | ||
* permanent ones. | ||
* | ||
* @param {Function} callback - the parameterless function to call | ||
*/ | ||
AnimEvents.prototype.onNextAnimationComplete = function(callback) { | ||
this.onNextAnimationCompleteCallbacks.push(callback); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* The game board, a convenience class to wrap access to the actual container | ||
* of tiles. | ||
* | ||
* The related class BoardCoord is an iterator of this container. | ||
* | ||
* The main invariant of this class is that, after the initialization phase, at | ||
* a given BoardCoord an object exist whose "coord" member has the same value | ||
* as the given BoardCoord: | ||
* | ||
* board.getAtCoord(coord).coord === coord | ||
* | ||
* @see BoardCoord | ||
* | ||
* @class | ||
* | ||
* @param {Object} size - the logical size of the board expressed as the x and | ||
* y values of the given object | ||
*/ | ||
function Board(size) { | ||
this.size = size; | ||
this.columns = []; | ||
for (var i = 0; i < size.x; i++) { | ||
this.columns.push(new Array(size.y)); | ||
}; | ||
} | ||
|
||
/** | ||
* An action to perform on a given tile. | ||
* @callback tileCallback | ||
* @param {MatchThreeTile} tile - the tile on which to perform the action | ||
*/ | ||
|
||
/** | ||
* Calls the given callback for each tile. Lower tiles are guaranteed to be | ||
* called before higher tiles. | ||
* | ||
* @param {tileCallback} callback - the action to perform on each tile | ||
*/ | ||
Board.prototype.forEachTile = function(callback) { | ||
// NOTE: loop from below | ||
for (var x = 0; x < this.size.x; x++) { | ||
for (var y = this.size.y - 1; y >= 0; y--) { | ||
callback(this.columns[x][y]); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* An action to perform on a given board coord. | ||
* @callback coordCallback | ||
* @param {BoardCoord} coord - the coord on which to perform the action | ||
*/ | ||
|
||
/** | ||
* Calls the given callback for each board coord. | ||
* | ||
* @param {coordCallback} callback - the action to perform on each coord | ||
*/ | ||
Board.prototype.forEachCoord = function(callback) { | ||
for (var x = 0; x < this.size.x; x++) { | ||
for (var y = 0; y < this.size.y; y++) { | ||
callback(new BoardCoord(this, x, y)); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Access a tile given a board coord. | ||
* | ||
* @param {BoardCoord} coord - the board coordinate of the tile | ||
* @returns {MatchThreeTile|undefined} the desired tile or undefined if out of | ||
* range | ||
*/ | ||
Board.prototype.getAtCoord = function(coord) { | ||
return (this.columns[coord.x] || {})[coord.y]; | ||
} | ||
|
||
/** | ||
* Sets a tile at the given board coord. | ||
* | ||
* @param {BoardCoord} coord - the desired board coordinate of the tile | ||
* @param {MatchThreeTile} tile - the given tile | ||
*/ | ||
Board.prototype.setAtCoord = function(coord, tile) { | ||
this.columns[coord.x][coord.y] = tile; | ||
tile.coord = coord; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* This is an iterator for the Board class. | ||
* | ||
* Note: y goes downward | ||
* | ||
* @param {Board} board - the board this coordinate iterates on | ||
* @param {Integer} x - the logical horizontal position on the board | ||
* @param {Integer} y - the logical vertical position on the board (starting | ||
* from the top) | ||
* | ||
* @class | ||
*/ | ||
function BoardCoord(board, x, y) { | ||
this.board = board; | ||
this.x = x; | ||
this.y = y; | ||
} | ||
|
||
/** | ||
* @return {BoardCoord|undefined} the board coordinate left of the current one | ||
* or undefined if out of bounds | ||
*/ | ||
BoardCoord.prototype.left = function() { | ||
return this.board.getAtCoord({x: this.x - 1, y: this.y}); | ||
} | ||
|
||
/** | ||
* @return {BoardCoord|undefined} the board coordinate right of the current one | ||
* or undefined if out of bounds | ||
*/ | ||
BoardCoord.prototype.right = function() { | ||
return this.board.getAtCoord({x: this.x + 1, y: this.y}); | ||
} | ||
|
||
/** | ||
* @return {BoardCoord|undefined} the board coordinate above of the current one | ||
* or undefined if out of bounds | ||
*/ | ||
BoardCoord.prototype.above = function() { | ||
return this.board.getAtCoord({x: this.x, y: this.y - 1}); | ||
} | ||
|
||
/** | ||
* @return {BoardCoord|undefined} the board coordinate below of the current one | ||
* or undefined if out of bounds | ||
*/ | ||
BoardCoord.prototype.below = function() { | ||
return this.board.getAtCoord({x: this.x, y: this.y + 1}); | ||
} | ||
|
||
/** | ||
* @param {BoardCoord} other - the board coordinate to check against | ||
* @return {Boolean} whether the two board coordinates are adjacent | ||
*/ | ||
BoardCoord.prototype.isAdjacent = function(other) { | ||
if (this.x != other.x && this.y != other.y) { | ||
return false; | ||
} | ||
return 1 >= Math.max( | ||
Math.abs(other.x - this.x), | ||
Math.abs(other.y - this.y) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Encapsulates the conversion between screen coordinates and board coordinates | ||
* taking tile size and board offset into account. | ||
* | ||
* @class | ||
* | ||
* @param {Object} size - object with x y size of a single tile, in screen | ||
* coordinates | ||
* @param {Object} offset - object with x y top-left offset of a board, in | ||
* screen coordinates | ||
*/ | ||
function CoordMapper(size, offset) { | ||
this.size = size; | ||
this.offset = offset; | ||
} | ||
|
||
/** | ||
* Converts a board coordinate (typically a BoardCoord) into its equivalent | ||
* screen coordinate given the current mapping. | ||
* | ||
* This method works on a single axis. | ||
* | ||
* @param {String} axis - "x" or "y" | ||
* @param {Integer} axisCoord - the board coordinate along that axis | ||
* | ||
* @returns {Number} the screen coordinate along the given axis | ||
*/ | ||
CoordMapper.prototype.boardToScreenAxis = function(axis, axisCoord) { | ||
return axisCoord * this.size[axis] + this.offset[axis]; | ||
}; | ||
|
||
/** | ||
* Converts board coordinates (typically a BoardCoord) into their equivalent | ||
* screen coordinates given the current mapping. | ||
* | ||
* @param {Object} coord - the board coordinates as x and y values of the given | ||
* object | ||
* | ||
* @returns {Object} the screen coordinates as x and y values | ||
*/ | ||
CoordMapper.prototype.boardToScreen = function(coord) { | ||
return { | ||
x: this.boardToScreenAxis('x', coord.x), | ||
y: this.boardToScreenAxis('y', coord.y), | ||
}; | ||
}; | ||
|
||
/** | ||
* Converts a screen coordinate into its equivalent board coordinate given the | ||
* current mapping. The resulting coordinate isn't necessarily valid but can be | ||
* validated through other means, e.g. by creating a BoardCoord object out of | ||
* it. | ||
* | ||
* This method works on a single axis. | ||
* | ||
* @param {String} axis - "x" or "y" | ||
* @param {Number} axisCoord - the screen coordinate along that axis | ||
* | ||
* @returns {Integer} the board coordinate along the given axis | ||
*/ | ||
CoordMapper.prototype.screenToBoardAxis = function(axis, axisCoord) { | ||
return Math.floor((axisCoord - this.offset[axis]) / this.size[axis]); | ||
}; | ||
|
||
/** | ||
* Converts screen coordinates into their equivalent board coordinates given | ||
* the current mapping. The resulting coordinates aren't necessarily valid but | ||
* can be validated through other means, e.g. by creating a BoardCoord object | ||
* out of them. | ||
* | ||
* @param {Object} coord - the screen coordinates as x and y values of the | ||
* given object | ||
* | ||
* @returns {Object} the board coordinates as x and y values | ||
*/ | ||
CoordMapper.prototype.screenToBoard = function(coord) { | ||
return { | ||
x: this.screenToBoardAxis('x', coord.x), | ||
y: this.screenToBoardAxis('y', coord.y), | ||
}; | ||
}; |
Oops, something went wrong.