html5-video-slidepuzzle/js/SlidePuzzleModel.js
2024-06-21 10:14:17 -06:00

152 lines
4.3 KiB
JavaScript

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');
};