Commit old files
This commit is contained in:
114
js/SlidePuzzleController.js
Normal file
114
js/SlidePuzzleController.js
Normal file
@ -0,0 +1,114 @@
|
||||
var SLIDE_PUZZLE = window.SLIDE_PUZZLE || {};
|
||||
|
||||
SLIDE_PUZZLE.VideoController = function(rows, columns) {
|
||||
this.STOPWATCH_DELAY = 500;
|
||||
|
||||
this._rows = rows;
|
||||
this._columns = columns;
|
||||
|
||||
this._model = null;
|
||||
this._view = null;
|
||||
this._stopwatch = new SLIDE_PUZZLE.Stopwatch(this.STOPWATCH_DELAY);
|
||||
|
||||
this._undoing = false;
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype.initView = function() {
|
||||
var self = this;
|
||||
|
||||
this._view = new SLIDE_PUZZLE.VideoView(this._rows, this._columns);
|
||||
|
||||
this._view.addEventListener("pieceClicked", this._tryMove.bind(this));
|
||||
this._view.addEventListener('undo', this._undo.bind(this));
|
||||
this._view.addEventListener('newGame', this._newGame.bind(this));
|
||||
this._stopwatch.addEventListener('tick', function() {
|
||||
self._view.timerText = self._stopwatch.elapsedString;
|
||||
});
|
||||
|
||||
this._newGame();
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._tryMove = function(event) {
|
||||
var index = this._rowColumnToIndex(event.detail.row, event.detail.column);
|
||||
var piece = this._rowColumnToIndex(event.detail.correctRow, event.detail.correctColumn);
|
||||
|
||||
try {
|
||||
this._model.move(piece);
|
||||
if (this._model.isSolved()) {
|
||||
var listener;
|
||||
listener = function() {
|
||||
this._endGame();
|
||||
this._view.removeEventListener('doneMoving', listener);
|
||||
}.bind(this);
|
||||
this._view.addEventListener('doneMoving', listener);
|
||||
}
|
||||
this._view.movePiece(event.detail.row, event.detail.column);
|
||||
this._view.canUndo = true;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._undo = function() {
|
||||
if (!this._undoing && !this._view.moving) {
|
||||
this._undoing = true;
|
||||
try {
|
||||
var piece = this._model.getMoves().pop();
|
||||
var index = this._model.getPositionOf(piece);
|
||||
var rowCol = this._indexToRowColumn(index);
|
||||
|
||||
this._model.undo();
|
||||
this._view.canUndo = this._model.getMoves().length !== 0;
|
||||
this._view.movePiece(rowCol.row, rowCol.column);
|
||||
} catch (err) {
|
||||
console.log("can't undo: " + err.message);
|
||||
}
|
||||
}
|
||||
this._undoing = false;
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._newGame = function() {
|
||||
this._model = new SLIDE_PUZZLE.Model(this._rows, this._columns);
|
||||
|
||||
var modelPos = this._model.getPosition();
|
||||
var viewPos = [];
|
||||
|
||||
for (var row = 0; row < this._rows; row++) {
|
||||
viewPos.push([]);
|
||||
for (var col = 0; col < this._columns; col++) {
|
||||
var modelPosIndex = row * this._columns + col;
|
||||
viewPos[row].push(modelPos[modelPosIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
this._view.setBoard(viewPos);
|
||||
this._view.showEmptyPiece = false;
|
||||
this._view.canMovePieces = true;
|
||||
this._view.canUndo = false;
|
||||
|
||||
if (this._stopwatch.isRunning) {
|
||||
this._stopwatch.stop();
|
||||
}
|
||||
this._stopwatch.reset();
|
||||
this._view.timerText = this._stopwatch.elapsedString;
|
||||
this._stopwatch.start();
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._endGame = function() {
|
||||
// TODO
|
||||
this._stopwatch.stop();
|
||||
this._view.showEmptyPiece = true;
|
||||
this._view.canMovePieces = false;
|
||||
this._view.canUndo = false;
|
||||
console.log("end game");
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._rowColumnToIndex = function(row, column) {
|
||||
return (row * this._columns) + column;
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoController.prototype._indexToRowColumn = function(index) {
|
||||
var row = Math.floor(index / this._columns);
|
||||
var column = index % this._columns;
|
||||
return {row:row, column:column};
|
||||
}
|
151
js/SlidePuzzleModel.js
Normal file
151
js/SlidePuzzleModel.js
Normal file
@ -0,0 +1,151 @@
|
||||
var SLIDE_PUZZLE = window.SLIDE_PUZZLE || {};
|
||||
|
||||
// Model constructor builds a solveable, shuffled slide puzzle model.
|
||||
SLIDE_PUZZLE.Model = function(rows, columns) {
|
||||
'use strict';
|
||||
this.ROWS = rows;
|
||||
this.COLUMNS = columns;
|
||||
this.EMPTY = this.ROWS * this.COLUMNS - 1;
|
||||
|
||||
this._position = [];
|
||||
this._moves = [];
|
||||
|
||||
for (var i = 0; i < this.ROWS * this.COLUMNS; i++) {
|
||||
this._position.push(i);
|
||||
}
|
||||
do {
|
||||
this._shuffle();
|
||||
} while (!this._isSolveable());
|
||||
};
|
||||
|
||||
// getPosition returns the current layout of the puzzle pieces.
|
||||
SLIDE_PUZZLE.Model.prototype.getPosition = function() {
|
||||
'use strict';
|
||||
return this._position.slice();
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Model.prototype.getPositionOf = function(piece) {
|
||||
'use strict';
|
||||
for (var i = 0; i < this._position.length; i++) {
|
||||
if (this._position[i] === piece) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// getMoves returns the history of all moves.
|
||||
SLIDE_PUZZLE.Model.prototype.getMoves = function() {
|
||||
'use strict';
|
||||
return this._moves.slice();
|
||||
};
|
||||
|
||||
// isSolved indicates whether the puzzle is in its solved order.
|
||||
SLIDE_PUZZLE.Model.prototype.isSolved = function() {
|
||||
'use strict';
|
||||
for (var i = 0; i < this._position.length - 1; i++) {
|
||||
if (this._position[i] > this._position[i+1]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// move moves piece into the blank space and logs the move in the move history.
|
||||
// move throws an error if the move is invalid or if the piece doesn't exist.
|
||||
SLIDE_PUZZLE.Model.prototype.move = function(piece) {
|
||||
'use strict';
|
||||
if (!this._isValidMove(piece)) {
|
||||
throw new Error('Invalid move');
|
||||
}
|
||||
var iPiece = this._position.indexOf(piece);
|
||||
var iEmpty = this._position.indexOf(this.EMPTY);
|
||||
this._swap(iPiece, iEmpty);
|
||||
this._moves.push(piece);
|
||||
};
|
||||
|
||||
// undo reverses the last move and removes it from the move history.
|
||||
// undo throws an error if there are no moves in the history.
|
||||
SLIDE_PUZZLE.Model.prototype.undo = function() {
|
||||
'use strict';
|
||||
if (this._moves.length === 0) {
|
||||
throw new Error('No moves to undo');
|
||||
}
|
||||
var piece = this._moves.pop();
|
||||
var iPiece = this._position.indexOf(piece);
|
||||
var iEmpty = this._position.indexOf(this.EMPTY);
|
||||
this._swap(iPiece, iEmpty);
|
||||
};
|
||||
|
||||
// _isValidMove determines whether piece can move, that is, whether it is next to the empty space.
|
||||
SLIDE_PUZZLE.Model.prototype._isValidMove = function(piece) {
|
||||
'use strict';
|
||||
if (!this._isValidPiece(piece)) {
|
||||
return false;
|
||||
}
|
||||
var iPiece = this._position.indexOf(piece);
|
||||
var iEmpty = this._position.indexOf(this.EMPTY);
|
||||
return Math.abs(iPiece - iEmpty) === this.COLUMNS ||
|
||||
Math.abs(iPiece - iEmpty) === 1;
|
||||
};
|
||||
|
||||
// _isValidPiece indicates whether piece is present in this puzzle.
|
||||
SLIDE_PUZZLE.Model.prototype._isValidPiece = function(piece) {
|
||||
'use strict';
|
||||
return piece >= 0 && piece < this.EMPTY;
|
||||
};
|
||||
|
||||
// _isSolveable indicates whether the puzzle is solveable, using Mark Ryan's algorithm.
|
||||
// See https://www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html.
|
||||
SLIDE_PUZZLE.Model.prototype._isSolveable = function() {
|
||||
'use strict';
|
||||
function even(n) {
|
||||
return n % 2 === 0;
|
||||
}
|
||||
|
||||
// Count inversions
|
||||
var inversions = 0;
|
||||
for (var i = 0; i < this._position.length; i++) {
|
||||
for (var j = i + 1; j < this._position.length; j++) {
|
||||
if (this._position[j] < this._position[i]) {
|
||||
inversions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var iEmpty = this._position.indexOf(this.EMPTY);
|
||||
var isEmptyOddFromBottom = !even(this.ROWS - Math.floor(iEmpty / this.ROWS));
|
||||
var width = this.COLUMNS;
|
||||
return (!even(width) && even(inversions)) ||
|
||||
(even(width) && isEmptyOddFromBottom === even(inversions));
|
||||
};
|
||||
|
||||
// _swap swaps the pieces at indices i1 and i2 in this._position.
|
||||
SLIDE_PUZZLE.Model.prototype._swap = function(i1, i2) {
|
||||
'use strict';
|
||||
var tmp = this._position[i1];
|
||||
this._position[i1] = this._position[i2];
|
||||
this._position[i2] = tmp;
|
||||
};
|
||||
|
||||
// _shuffle performs a Fisher-Yates shuffle on this._position.
|
||||
SLIDE_PUZZLE.Model.prototype._shuffle = function() {
|
||||
'use strict';
|
||||
// Returns a random int n where 0 ≤ n < max
|
||||
function randomInt(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
//TODO: should possible random ints include i?
|
||||
for (var i = this._position.length - 1; i > 0; i--) {
|
||||
this._swap(randomInt(i), i);
|
||||
}
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Model.prototype.toString = function() {
|
||||
'use strict';
|
||||
var lines = [];
|
||||
for (var i = 0; i < this.ROWS; i++) {
|
||||
var rowStart = i * this.COLUMNS;
|
||||
lines.push(this._position.slice(rowStart, rowStart + this.COLUMNS).join('\t'));
|
||||
}
|
||||
return lines.join('\n');
|
||||
};
|
118
js/SlidePuzzleStopwatch.js
Normal file
118
js/SlidePuzzleStopwatch.js
Normal file
@ -0,0 +1,118 @@
|
||||
var SLIDE_PUZZLE = window.SLIDE_PUZZLE || {};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch = function(tickMillis) {
|
||||
var self = this;
|
||||
|
||||
this._tickMillis = tickMillis;
|
||||
this._startTime = null;
|
||||
this._intervalID = null;
|
||||
this._priorElapsed = 0;
|
||||
this._currentElapsed = 0;
|
||||
|
||||
this._listeners = [];
|
||||
|
||||
this.__defineGetter__("isRunning", function() {
|
||||
return self._intervalID !== null;
|
||||
});
|
||||
|
||||
this.__defineGetter__("elapsedMillis", function() {
|
||||
return self._priorElapsed + self._currentElapsed;
|
||||
});
|
||||
|
||||
function divideInt(a, b) {
|
||||
return Math.floor(a/b);
|
||||
}
|
||||
|
||||
this.__defineGetter__("elapsedString", function() {
|
||||
var totalMillis = self._priorElapsed + self._currentElapsed;
|
||||
|
||||
var seconds = divideInt(totalMillis, 1000) % 60;
|
||||
var minutes = divideInt(totalMillis, 60000) % 60;
|
||||
var hours = divideInt(totalMillis, 3600000);
|
||||
|
||||
var showHours = hours > 0;
|
||||
var showMinutes = minutes > 0 || showHours;
|
||||
var fixMinutes = showHours;
|
||||
var fixSeconds = showMinutes;
|
||||
|
||||
var hoursString;
|
||||
if (showHours) {
|
||||
hoursString = hours + ':';
|
||||
} else {
|
||||
hoursString = '';
|
||||
}
|
||||
|
||||
var minutesString;
|
||||
if (showMinutes) {
|
||||
if (fixMinutes && minutes < 10) {
|
||||
minutesString = '0' + minutes + ':';
|
||||
} else {
|
||||
minutesString = minutes + ':';
|
||||
}
|
||||
} else {
|
||||
minutesString = '';
|
||||
}
|
||||
|
||||
var secondsString;
|
||||
if (fixSeconds && seconds < 10) {
|
||||
secondsString = '0' + seconds;
|
||||
} else {
|
||||
secondsString = seconds.toString();
|
||||
}
|
||||
|
||||
return hoursString + minutesString + secondsString;
|
||||
});
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch.prototype.start = function() {
|
||||
if (!this.isRunning) {
|
||||
var self = this;
|
||||
|
||||
function tick() {
|
||||
self._currentElapsed = window.performance.now() - self._startTime;
|
||||
self._dispatchTickEvent();
|
||||
}
|
||||
|
||||
this._startTime = window.performance.now();
|
||||
this._intervalID = window.setInterval(tick, this._tickMillis);
|
||||
} else {
|
||||
throw new Error("can't start a started stopwatch");
|
||||
}
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch.prototype.stop = function() {
|
||||
if (this.isRunning) {
|
||||
window.clearInterval(this._intervalID);
|
||||
this._intervalID = null;
|
||||
this._startTime = null;
|
||||
|
||||
this._priorElapsed += this._currentElapsed;
|
||||
this._currentElapsed = 0;
|
||||
} else {
|
||||
throw new Error("can't stop a stopped stopwatch");
|
||||
}
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch.prototype.reset = function() {
|
||||
if (!this.isRunning) {
|
||||
this._priorElapsed = 0;
|
||||
this._currentElapsed = 0;
|
||||
this._dispatchTickEvent();
|
||||
} else {
|
||||
throw new Error("can't reset a started stopwatch");
|
||||
}
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch.prototype.addEventListener = function(type, listener) {
|
||||
if (type === 'tick') {
|
||||
this._listeners.push(listener);
|
||||
}
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.Stopwatch.prototype._dispatchTickEvent = function() {
|
||||
var event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent('tick', false, false, null);
|
||||
for (var i = 0; i < this._listeners.length; i++) {
|
||||
this._listeners[i](event);
|
||||
}
|
||||
}
|
369
js/SlidePuzzleView.js
Normal file
369
js/SlidePuzzleView.js
Normal file
@ -0,0 +1,369 @@
|
||||
var SLIDE_PUZZLE = window.SLIDE_PUZZLE || {};
|
||||
|
||||
SLIDE_PUZZLE.VideoView = function(rows, columns) {
|
||||
var self = this;
|
||||
|
||||
this.FRAME_DELAY = 50;
|
||||
|
||||
this._rows = rows;
|
||||
this._columns = columns;
|
||||
|
||||
this._videoListURL = "video/videolist.json";
|
||||
this._videoList = null;
|
||||
|
||||
// DOM elements
|
||||
this._container = document.getElementById('slidepuzzle-container');
|
||||
this._board = document.getElementById('slidepuzzle-board');
|
||||
this._video = document.getElementById('slidepuzzle-video');
|
||||
this._copyright = document.getElementById('slidepuzzle-copyright');
|
||||
this._videoSelect = document.getElementById('slidepuzzle-videolist');
|
||||
this._timer = document.getElementById('slidepuzzle-timer');
|
||||
this._newGameButton = document.getElementById('slidepuzzle-newgame');
|
||||
this._undoButton = document.getElementById('slidepuzzle-undo');
|
||||
|
||||
// _origPosition is the unshuffled pieces, used for slicing the video.
|
||||
// Its order never changes during a game
|
||||
this._origPosition = [];
|
||||
|
||||
// _currPosition is the shuffled position of the pieces.
|
||||
this._currPosition = [];
|
||||
|
||||
this._updateInterval = null;
|
||||
|
||||
this._moving = false;
|
||||
this._listeners = {
|
||||
pieceClicked: [],
|
||||
undo: [],
|
||||
newGame: [],
|
||||
doneMoving: []
|
||||
};
|
||||
|
||||
// VideoView.moving
|
||||
this.__defineGetter__('moving', function() {
|
||||
return this._moving;
|
||||
});
|
||||
|
||||
// VideoView.timerText
|
||||
this.__defineGetter__('timerText', function() {
|
||||
return this._timer.textContent;
|
||||
});
|
||||
this.__defineSetter__('timerText', function(value) {
|
||||
this._timer.textContent = value;
|
||||
});
|
||||
|
||||
// VideoView.showEmptyPiece
|
||||
this.__defineGetter__('showEmptyPiece', function() {
|
||||
document.getElementById('slidepuzzle-empty-piece').classList.contains('hidden');
|
||||
});
|
||||
this.__defineSetter__('showEmptyPiece', function(value) {
|
||||
var classes = document.getElementById('slidepuzzle-empty-piece').classList;
|
||||
if (value) {
|
||||
classes.remove('hidden');
|
||||
} else {
|
||||
classes.add('hidden')
|
||||
}
|
||||
});
|
||||
|
||||
// VideoView.canUndo
|
||||
this.__defineGetter__('canUndo', function() {
|
||||
return !this._undoButton.disabled;
|
||||
});
|
||||
this.__defineSetter__('canUndo', function(value) {
|
||||
this._undoButton.disabled = !value;
|
||||
});
|
||||
|
||||
// VideoView.canMovePieces
|
||||
this.canMovePieces = true;
|
||||
|
||||
// Create gameboard cells
|
||||
for (var i = 0; i < this._rows; i++) {
|
||||
var row = document.createElement('tr');
|
||||
for (var j = 0; j < this._columns; j++) {
|
||||
row.appendChild(document.createElement('td'));
|
||||
}
|
||||
this._board.appendChild(row);
|
||||
}
|
||||
|
||||
// Create puzzle pieces
|
||||
for (var i = 0; i < this._rows; i++) {
|
||||
this._currPosition.push([]);
|
||||
this._origPosition.push([]);
|
||||
for (var j = 0; j < this._columns; j++) {
|
||||
var piece = document.createElement('canvas');
|
||||
piece.dataset.correctRow = i;
|
||||
piece.dataset.correctColumn = j;
|
||||
if (i === this._rows - 1 && j === this._columns - 1) {
|
||||
piece.id = 'slidepuzzle-empty-piece';
|
||||
}
|
||||
this._currPosition[i].push(piece);
|
||||
this._origPosition[i].push(piece);
|
||||
}
|
||||
}
|
||||
|
||||
this._placePieces();
|
||||
this.startUpdates();
|
||||
|
||||
function makePieceClickHandler(cellIndex) {
|
||||
var row = Math.floor(cellIndex / self._columns);
|
||||
var column = cellIndex % self._columns;
|
||||
var listener = function() {
|
||||
self._pieceClicked(row, column);
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
|
||||
var cells = this._board.getElementsByTagName("td");
|
||||
for (var i = 0; i < cells.length; i++) {
|
||||
cells[i].addEventListener('click', makePieceClickHandler(i));
|
||||
}
|
||||
|
||||
this._videoSelect.addEventListener("change", function() {
|
||||
if (self._videoList) {
|
||||
console.log("selected video: " + self._videoSelect.value);
|
||||
var name = self._videoSelect.value;
|
||||
self.loadVideo(name);
|
||||
}
|
||||
});
|
||||
this._loadVideoList();
|
||||
|
||||
this._undoButton.addEventListener('click', function() {
|
||||
self._dispatchEvent('undo');
|
||||
});
|
||||
|
||||
this._newGameButton.addEventListener('click', function() {
|
||||
self._dispatchEvent('newGame');
|
||||
});
|
||||
};
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._loadVideoList = function() {
|
||||
// Create an XMLHttpRequest to this._videoListURL.
|
||||
// Parse the JSON and store the resulting object as this._videoList.
|
||||
var self = this;
|
||||
this._videoSelect.textContent = '';
|
||||
this._videoList = {};
|
||||
var req = new XMLHttpRequest();
|
||||
//req.responseType = "json";
|
||||
req.overrideMimeType("application/json");
|
||||
req.onload = function() {
|
||||
console.log("loaded video list");
|
||||
var videos = JSON.parse(req.responseText);
|
||||
|
||||
for (var i = 0; i < videos.length; i++) {
|
||||
var opt = document.createElement('option');
|
||||
//opt.value = videos[i].url;
|
||||
opt.text = videos[i].name;
|
||||
self._videoSelect.appendChild(opt);
|
||||
|
||||
self._videoList[videos[i].name] = videos[i];
|
||||
}
|
||||
|
||||
var changeEvent = document.createEvent("Event");
|
||||
changeEvent.initEvent("change", true, true);
|
||||
self._videoSelect.dispatchEvent(changeEvent);
|
||||
};
|
||||
req.onerror = function() {
|
||||
console.log("error loading video list");
|
||||
};
|
||||
req.open("GET", this._videoListURL);
|
||||
req.send();
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.addEventListener = function(type, listener) {
|
||||
this._listeners[type].push(listener);
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.removeEventListener = function(type, listener) {
|
||||
for (var i = 0; i < this._listeners[type].length; i++) {
|
||||
if (this._listeners[type][i] === listener) {
|
||||
this._listeners[type].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._dispatchEvent = function(type, detail) {
|
||||
if (detail === undefined) {
|
||||
detail = null;
|
||||
console.log("detail is undefined");
|
||||
}
|
||||
var event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, false, false, detail);
|
||||
for ( var i = 0; i < this._listeners[type].length; i++ ) {
|
||||
this._listeners[type][i](event);
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._pieceClicked = function(row, column) {
|
||||
var self = this;
|
||||
|
||||
if (!self._moving) {
|
||||
var piece = self._currPosition[row][column];
|
||||
var detail = {
|
||||
row: row,
|
||||
column: column,
|
||||
correctRow: parseInt(piece.dataset.correctRow, 10),
|
||||
correctColumn: parseInt(piece.dataset.correctColumn, 10),
|
||||
};
|
||||
|
||||
self._dispatchEvent('pieceClicked', detail);
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.loadVideo = function(name) {
|
||||
// We can't reference `this` normally in the closure unless we use bind, which we won't.
|
||||
var self = this;
|
||||
function setBoardSize() {
|
||||
// The board will stretch to match the container.
|
||||
self._container.style.height = self._video.videoHeight + "px";
|
||||
self._container.style.width = self._video.videoWidth + "px";
|
||||
self._board.style.height = self._container.style.height;
|
||||
self._board.style.width = self._container.style.width;
|
||||
|
||||
for (var row = 0; row < self._rows; row++) {
|
||||
for (var col = 0; col < self._columns; col++) {
|
||||
var piece = self._currPosition[row][col];
|
||||
piece.width = self._video.videoWidth / self._columns;
|
||||
piece.height = self._video.videoHeight / self._rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._video.onloadeddata = setBoardSize;
|
||||
this._video.src = this._videoList[name].url;
|
||||
var copyright = document.createElement('a');
|
||||
copyright.href = this._videoList[name].infoUrl;
|
||||
copyright.text = this._videoList[name].copyright;
|
||||
this._copyright.textContent = '';
|
||||
this._copyright.appendChild(copyright);
|
||||
};
|
||||
|
||||
// setBoard arranges the pieces to the given position.
|
||||
//
|
||||
// boardPosition is a 2-dimensional array indexed by row, then column.
|
||||
// The values in boardPosition are numbers representing the pieces'
|
||||
// order in the solved puzzle, with the highest number being the empty space.
|
||||
// E.g. a solved 4×4 puzzle is:
|
||||
// [[0,1,2,3],
|
||||
// [4,5,6,7],
|
||||
// [8,9,10,11],
|
||||
// [12,13,14,15]]
|
||||
// boardPosition must have the proper dimensions and valid piece values,
|
||||
// or bad things will happen.
|
||||
// TODO rewrite setBoard to not use _origPosition
|
||||
SLIDE_PUZZLE.VideoView.prototype.setBoard = function(boardPosition) {
|
||||
for (var row = 0; row < this._rows; row++) {
|
||||
for (var col = 0; col < this._columns; col++) {
|
||||
var pieceNum = boardPosition[row][col];
|
||||
var origRow = Math.floor(pieceNum / this._rows);
|
||||
var origCol = pieceNum % this._columns;
|
||||
|
||||
this._currPosition[row][col] = this._origPosition[origRow][origCol];
|
||||
}
|
||||
}
|
||||
this._placePieces();
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.movePiece = function(row, column) {
|
||||
var self = this;
|
||||
if (!self._moving && self.canMovePieces) {
|
||||
self._moving = true;
|
||||
|
||||
var empty = this._getEmptyPosition();
|
||||
var boardRect = this._board.getBoundingClientRect()
|
||||
var movingPiece = this._currPosition[row][column];
|
||||
var leftDistance = (empty.column - column) * boardRect.width / this._columns;
|
||||
var topDistance = (empty.row - row) * boardRect.height / this._rows;
|
||||
|
||||
movingPiece.style.left = '0px';
|
||||
movingPiece.style.top = '0px';
|
||||
|
||||
function animateMove() {
|
||||
var moveFactor = 6; // Must be an integer
|
||||
var left = parseInt(movingPiece.style.left, 10);
|
||||
var top = parseInt(movingPiece.style.top, 10);
|
||||
if (Math.abs(left) < Math.abs(leftDistance) && Math.abs(leftDistance - left) > Math.abs(leftDistance / moveFactor)) {
|
||||
left += leftDistance / moveFactor;
|
||||
movingPiece.style.left = left + "px";
|
||||
window.setTimeout(animateMove, self.FRAME_DELAY);
|
||||
} else if (Math.abs(top) < Math.abs(topDistance) && Math.abs(topDistance - top) > Math.abs(topDistance / moveFactor)) {
|
||||
top += topDistance / moveFactor;
|
||||
movingPiece.style.top = top + "px";
|
||||
window.setTimeout(animateMove, self.FRAME_DELAY);
|
||||
} else {
|
||||
self._currPosition[row][column] = self._currPosition[empty.row][empty.column];
|
||||
self._currPosition[empty.row][empty.column] = movingPiece;
|
||||
movingPiece.style.left = '0px';
|
||||
movingPiece.style.top = '0px';
|
||||
self._placePieces();
|
||||
self._moving = false;
|
||||
self._dispatchEvent('doneMoving');
|
||||
}
|
||||
}
|
||||
|
||||
animateMove();
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.startUpdates = function() {
|
||||
var self = this;
|
||||
if (this._updateInterval === null) {
|
||||
this._updateInterval = window.setInterval(function() {
|
||||
self._updatePieces();
|
||||
}, this.FRAME_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype.stopUpdates = function() {
|
||||
if (this._updateInterval !== null) {
|
||||
window.clearInterval(this._updateInterval);
|
||||
this._updateInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._getEmptyPosition = function() {
|
||||
var pos = {};
|
||||
for (var row = 0; row < this._rows; row++) {
|
||||
for (var col = 0; col < this._columns; col++) {
|
||||
if (this._currPosition[row][col].id === 'slidepuzzle-empty-piece') return {row:row,column:col};
|
||||
}
|
||||
}
|
||||
throw new Error('no empty position found');
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._updatePieces = function() {
|
||||
// If the pieces' videos end up out of sync, we can optimize
|
||||
// this method by first taking a still of the video and then slicing it.
|
||||
for (var row = 0; row < this._rows; row++) {
|
||||
for (var col = 0; col < this._columns; col++) {
|
||||
/*
|
||||
var piece = this._origPosition[row][col];
|
||||
var ctx = piece.getContext('2d');
|
||||
var w = piece.width;
|
||||
var h = piece.height;
|
||||
var sourceX = col * w;
|
||||
var sourceY = row * h;
|
||||
ctx.drawImage(this._video, sourceX, sourceY, w, h, 0, 0, w, h);
|
||||
*/
|
||||
var piece = this._currPosition[row][col];
|
||||
var origRow = parseInt(piece.dataset.correctRow, 10);
|
||||
var origCol = parseInt(piece.dataset.correctColumn, 10);
|
||||
var ctx = piece.getContext('2d');
|
||||
var w = piece.width;
|
||||
var h = piece.height;
|
||||
var sourceX = origCol * w;
|
||||
var sourceY = origRow * h;
|
||||
ctx.drawImage(this._video, sourceX, sourceY, w, h, 0, 0, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SLIDE_PUZZLE.VideoView.prototype._placePieces = function() {
|
||||
for (var row = 0; row < this._rows; row++) {
|
||||
for (var col = 0; col < this._columns; col++) {
|
||||
var cell = this._board.rows[row].cells[col];
|
||||
cell.innerHTML = '';
|
||||
var piece = this._currPosition[row][col];
|
||||
cell.appendChild(piece);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user