function getUniqueId()
{
getUniqueId.latestId ++;
return "id" + getUniqueId.latestId;
}
getUniqueId.latestId = 0;
function slidingBlocksPopup( page )
{
var url = document.location.toString();
url = url.replace( /slidingblocks.*/, "slidingblocks/" + page );
window.open( url );
}
solution = null;
board = null;
/* -------------- Board ------------- */
function Board( width, height, scaleX, scaleY, marginX, marginY, doc )
{
if (doc == null) {
doc = document;
}
this.visible = true;
this.doc = doc;
this.baseUrl = this.doc.location.toString().replace( /index.jsp.*/, "" );
this.controller = CreatePlayController( this );
this.width = width; // The number of elements across the grid.
this.height = height; // The number of elements down the grid
this.scaleX = scaleX; // The width of each element in pixels
this.scaleY = scaleY; // The height of each element in pixels
this.marginX = marginX; // The margin on the left of the background image
this.marginY = marginY;
this.offsetX = 0; // The position of the background image relative to the page.
this.offset = 0; // This is evaluated when board.positionPieces is called.
this.rows = new Array( height ); // The 2D array which make up the grid.
this.pieces = new Array();
this.pieceMap = new Object();
var x, y;
for ( y = 0; y < height; y ++ ) {
this.rows[ y ] = new Array( width );
for ( x = 0; x < width; x ++ ) {
(this.rows[ y ])[ x ] = Board.EMPTY;
}
}
}
Board.EMPTY = " ";
Board.prototype.clone = function( )
{
var cloned = new Board( this.width, this.height, this.scaleX, this.scaleY, this.marginX, this.marginY, this.doc );
cloned.baseUrl = this.baseUrl;
cloned.background = this.background;
var i;
for ( i = 0; i < this.pieces.length; i ++ ) {
var boardPiece = this.pieces[ i ];
cloned.addPiece( boardPiece.piece, boardPiece.coord.x, boardPiece.coord.y, boardPiece.id );
}
return cloned;
}
Board.prototype.addPiece = function( piece, x, y, id )
{
var boardPiece = new BoardPiece( this, piece, x, y, id );
this.pieces[ this.pieces.length ] = boardPiece;
this.pieceMap[ boardPiece.id ] = boardPiece;
}
Board.prototype.setFixed = function( x, y )
{
var fixedPiece = new Piece( "X", null );
fixedPiece.addElement( 0, 0 );
var boardPiece = new BoardPiece( this, fixedPiece, x, y );
boardPiece.isFixed = true;
this.pieces[ this.pieces.length ] = boardPiece;
}
Board.prototype.getPieceAt = function( x, y )
{
if ( ! this.insideGrid( x, y ) ) {
return null;
}
return (this.rows[ y ])[ x ];
}
Board.prototype.isEmptyAt = function( x, y )
{
return this.getPieceAt( x, y ) == Board.EMPTY;
}
Board.prototype.setPieceAt = function( boardPiece, x, y )
{
if ( ! this.insideGrid( x, y ) ) {
alert( "Attempted to place piece outside the board. Piece = " + boardPiece.piece.label );
} else {
(this.rows[ y ])[ x ] = boardPiece;
}
}
Board.prototype.setEmptyAt = function( x, y )
{
this.setPieceAt( Board.EMPTY, x, y );
}
Board.prototype.toString = function()
{
var result = "";
var x, y;
for ( y = 0; y < this.height; y ++ ) {
for ( x = 0; x < this.width; x ++ ) {
if ( this.isEmptyAt( x, y ) ) {
result = result + "o ";
} else {
result = result + this.getPieceAt( x, y ).piece.label + " ";
}
}
result = result + "\n";
}
return result;
}
Board.prototype.generatePieceKeyOLD = function( doc )
{
if ( doc == null ) {
doc = this.doc;
}
var i;
var html = "";
html += '<table class="pieceKey">';
html += '<tr>';
for ( i = 0; i < this.pieces.length; i ++ ) {
var piece = this.pieces[ i ].piece;
if ( piece.image != null ) {
html += '<td>';
html += ' <img alt="' + this.pieces[ i ].id +'" src="' + this.baseUrl + piece.image + '"/>';
html += '<br/>';
html += this.pieces[ i ].id;
html += '</td>';
}
}
html += '</tr>';
html +='</table>';
return html;
}
Board.prototype.generatePieceKey = function( doc )
{
if ( doc == null ) {
doc = this.doc;
}
var i;
var html = "";
for ( i = 0; i < this.pieces.length; i ++ ) {
var piece = this.pieces[ i ].piece;
if ( piece.image != null ) {
html += '<div class="thumbnailBox">';
html += '<div class="thumbnailContainer"><div class="thumbnailWrapper">';
html += ' <img alt="' + this.pieces[ i ].id +'" src="' + this.baseUrl + piece.image + '"/>';
html += '</div></div>';
html += '<div class="thumbnailLabel">';
html += this.pieces[ i ].id;
html += '</div>';
html += '</div>';
}
}
return html;
}
Board.prototype.generate = function()
{
var playArea = ww_findObj( "playArea" );
var html = '<div id="pieces">\n';
for ( var i = 0; i < this.pieces.length; i ++ ) {
var piece = this.pieces[ i ].piece;
if ( piece.image != null ) {
html += '<img class="piece" id="' + this.pieces[i].id + '" alt="' + piece.label +'" src="' + this.baseUrl + piece.image + '"/>\n';
}
}
html += "</div>\n";
html += '<div id="glass">\n';
html += '<img alt="background" src="' + this.baseUrl + this.background + '"/>';
html += '</div>\n';
html += '<img id="boardBackground" alt="background" src="' + this.baseUrl + this.background + '"/>';
html += '</div>';
playArea.innerHTML = html;
var glass = ww_findObj( "glass" );
glass.onmousedown = function( event ) { return board.controller.mouseDown( event ); };
glass.onmouseup = function( event ) { return board.controller.mouseUp( event ); };
glass.onmousemove = function( event ) { return board.controller.mouseMove( event ); };
}
Board.prototype.positionPieces = function( )
{
var i;
var background = ww_findObj( "boardBackground", this.doc );
this.offsetX = background.pageX;
this.offsetY = background.pageY;
for ( i = 0; i < this.pieces.length; i ++ ) {
var boardPiece = this.pieces[ i ];
var screenCoord = this.gameToScreen( this.pieces[ i ].coord );
// alert( "Looking for piece : " + boardPiece.id );
var img = ww_findObj( boardPiece.id, this.doc );
if ( img != null ) {
img.style.left = screenCoord.x + "px";
img.style.top = screenCoord.y + "px";
img.style.visibility = "visible";
}
}
}
Board.prototype.gameToScreen = function( coord )
{
var result = new Coord( 0, 0 );
result.x = coord.x * this.scaleX + this.marginX;
result.y = coord.y * this.scaleY + this.marginY;
return result;
}
Board.prototype.screenToGame = function( coord )
{
var result = new Coord( 0, 0 );
result.x = Math.floor( (coord.x - this.offsetX - this.marginX) / this.scaleX);
result.y = Math.floor( (coord.y - this.offsetY - this.marginY) / this.scaleY);
return result;
}
Board.prototype.containerToGame = function( coord )
{
var result = new Coord( 0, 0 );
result.x = Math.floor( (coord.x - this.marginX) / this.scaleX);
result.y = Math.floor( (coord.y - this.marginY) / this.scaleY);
return result;
}
Board.prototype.eventToScreen = function( event )
{
return new Coord( event.screenX, event.screenY );
}
Board.prototype.toString = function()
{
var result = "";
var x, y;
for ( y = 0; y < this.height; y ++ ) {
for ( x = 0; x < this.width; x ++ ) {
if ( this.isEmptyAt( x, y ) ) {
result = result + "o ";
} else {
result = result + this.getPieceAt( x, y ).piece.label + " ";
}
}
result = result + "\n";
}
return result;
}
Board.prototype.asString = function()
{
var result = "";
var i;
for (i = 0; i < this.pieces.length; i ++ ) {
var boardPiece = this.pieces[ i ];
if ( boardPiece.isFixed == false ) {
result = result + boardPiece.piece.label + ":" + boardPiece.coord.x + "," + boardPiece.coord.y + ".";
}
}
return result;
}
Board.prototype.insideGrid = function( x, y )
{
return (x >= 0) && (x < this.width) && (y >= 0) && (y < this.height);
}
Board.prototype.canMove = function( boardPiece, newCoord )
{
var i;
if ( boardPiece.isFixed ) {
return false;
}
for ( i = 0; i < boardPiece.piece.elements.length; i ++ ) {
var coord = boardPiece.piece.elements[ i ];
var x = coord.x + newCoord.x;
var y = coord.y + newCoord.y;
if ( ! this.insideGrid( x, y ) ) {
return false;
}
var other = this.getPieceAt( x, y );
if ( (other != Board.EMPTY) && (other != boardPiece) ) {
return false;
}
}
return true;
}
Board.prototype.move = function( boardPiece, newCoord )
{
boardPiece.place( newCoord );
if ( this.visible ) {
var img = ww_findObj( boardPiece.id, this.doc );
var screenCoord = this.gameToScreen( newCoord );
img.style.left = screenCoord.x + "px";
img.style.top = screenCoord.y + "px";
}
}
/* -------------- Piece ------------- */
function Piece( label, image )
{
this.elements = new Array();
this.label = label;
this.image = image;
}
Piece.prototype.addElement = function( x, y )
{
this.elements[ this.elements.length ] = new Coord( x, y );
}
Piece.prototype.toString = function()
{
if ( this.elements.length == 0 ) {
return "empty piece";
}
var result = "";
var i;
for ( i = 0; i < this.elements.length; i ++ ) {
result = result + this.elements[ i ] + "\n";
}
return result;
}
/* -------------- Coord ------------- */
function Coord( x, y )
{
this.x = x;
this.y = y;
}
Coord.prototype.toString = function()
{
return "(" + this.x + "," + this.y + ")";
}
/* -------------- BoardPiece ------------- */
/*
BoardPiece states where a piece is on a grid.
You can think of it as the middle of a many-to-many
relationship in a database.
A piece can appear multiple times on a grid.
*/
function BoardPiece( board, piece, x, y, id )
{
this.board = board;
this.piece = piece;
this.coord = null;
this.isFixed = false;
this.place( new Coord( x, y ) );
if ( id == null ) {
id = getUniqueId();
}
this.id = id;
}
BoardPiece.prototype.place = function( coord )
{
if (this.coord != null) {
this.remove();
}
this.coord = coord;
var i;
for ( i = 0; i < this.piece.elements.length; i ++ ) {
var eleCoord = this.piece.elements[ i ];
this.board.setPieceAt( this, eleCoord.x + this.coord.x, eleCoord.y + this.coord.y );
}
}
BoardPiece.prototype.remove = function()
{
var i;
for ( i = 0; i < this.piece.elements.length; i ++ ) {
var eleCoord = this.piece.elements[ i ];
this.board.setEmptyAt( eleCoord.x + this.coord.x, eleCoord.y + this.coord.y );
}
this.coord = null;
}
BoardPiece.prototype.toString = function()
{
var result = "BoardPiece : " + this.id + " @ " + this.coord + " piece : " + this.piece;
return result;
}
/* -------------- Move ------------- */
function Move( boardPiece, fromCoord, toCoord )
{
this.boardPiece = boardPiece;
this.fromCoord = fromCoord;
this.toCoord = toCoord;
}
/* -------------- Controller ------------- */
function Controller( board )
{
}
Controller.prototype.initController = function( board )
{
this.board = board;
this.history = new MoveHistory( board );
}
Controller.prototype.moveBoardPieceId = function( id, newCoord )
{
var boardPiece = this.board.pieceMap[ id ];
// alert( "Moving piece id : " + id + " ie " + boardPiece );
if ( boardPiece == null ) {
alert( "ERROR: piece : '" + id + "' not found." );
} else {
this.moveBoardPiece( boardPiece, newCoord );
}
}
Controller.prototype.moveBoardPiece = function( boardPiece, newCoord )
{
this.history.addMove( boardPiece.id, newCoord );
this.board.move( boardPiece, newCoord );
}
Controller.prototype.undo = function( )
{
this.history.undo();
return false;
}
Controller.prototype.redo = function( )
{
this.history.redo();
return false;
}
Controller.prototype.showMoves = function( )
{
var html = "<pre>\n";
html += this.history.listMoves();
html += "</pre>";
html += this.board.generatePieceKey();
ww_findObj( "moves" ).innerHTML = html;
return false;
}
Controller.prototype.cloneBoard = function( )
{
// This is a bodge. On my browser, window.opener gives the wrong result if a pop-up window
// pops up ANOTHER pop-up window.
// So instead of doing opener.board.clone(), we do opener.boardToClone.clone()
window.boardToClone = this.board;
if (opener) {
opener.boardToClone = this.board;
}
slidingBlocksPopup( "clone.jsp" );
}
/* -------------- MoveHistory ------------- */
function MoveHistory( board )
{
this.board = board;
this.moves = new Array();
this.moveIndex = 0;
this.moveCount = 0;
}
MoveHistory.prototype.clear = function()
{
this.moveIndex = 0;
this.moveCount = 0;
}
MoveHistory.prototype.addMove = function( id, newCoord )
{
var boardPiece = this.board.pieceMap[ id ];
if ( boardPiece == null ) {
alert( "ERROR: boardPiece : '" + id + "' not found." );
}
if ( ( this.moveIndex > 0 ) && ( this.moves[ this.moveIndex -1 ].boardPiece == boardPiece ) ) {
// moved the same piece
var move = this.moves[ this.moveIndex - 1 ];
if ( (move.fromCoord.x == newCoord.x) && (move.fromCoord.y == newCoord.y) ) {
// back to its starting place
this.moveIndex --;
this.moveCount = this.moveIndex;
} else {
move.toCoord = newCoord;
}
} else {
// moved a different piece;
this.moves[ this.moveIndex ] = new Move( boardPiece, boardPiece.coord, newCoord );
this.moveIndex ++;
this.moveCount = this.moveIndex;
}
}
MoveHistory.prototype.undo = function()
{
if ( this.moveIndex > 0 ) {
var move = this.moves[ this.moveIndex -1 ];
var boardPiece = move.boardPiece;
this.moveIndex --;
this.board.move( boardPiece, move.fromCoord );
} else {
alert( "Nothing to undo" );
}
}
MoveHistory.prototype.undoAll = function()
{
while ( this.moveIndex > 0 ) {
this.undo();
}
}
MoveHistory.prototype.redo = function()
{
if ( this.moveIndex < this.moveCount ) {
var move = this.moves[ this.moveIndex ];
var boardPiece = move.boardPiece;
this.moveIndex ++;
//alert( "Redo piece : " + boardPiece.id + " to " + move.toCoord );
this.board.move( boardPiece, move.toCoord );
} else {
alert( "Nothing to redo" );
}
}
MoveHistory.prototype.listMoves = function( )
{
var result = "";
var i;
result = "solution = new Solution( board );\n\n";
for ( i = 0; i < this.moveIndex; i ++ ) {
var move = this.moves[ i ];
result = result + "solution.addMove(\"" + move.boardPiece.id + "\", " +
move.toCoord.x + "," + move.toCoord.y + ");\n";
}
return result;
}
/* -------------- PlayController ------------- */
// Inherits from Controller
function PlayController( board )
{
}
PlayController.prototype = new Controller();
PlayController.prototype.initPlayController = function( board )
{
this.initController( board );
this.dragging = null;
}
// There are inconsistancies in handling inhreitance with different versions of javascript
// So this function gets around the inconsistancies. Do not call the contructor directly.
function CreatePlayController( board )
{
var controller = new PlayController();
controller.initPlayController( board );
return controller;
}
function fixMouseEvent(e)
{
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
e.documentX = e.pageX;
e.documentY = e.pageY;
} else if (e.clientX || e.clientY) {
e.documentX = e.clientX + document.body.scrollLeft;
e.documentY = e.clientY + document.body.scrollTop;
}
if ( e.layerX ) {
e.containerX = e.layerX;
e.containerY = e.layerY;
} else if ( e.offsetX ) {
e.containerX = e.offsetX;
e.containerY = w.offsetY;
}
// documentX and documentY contain the mouse position relative to the document
// containerX and containerY contain the mouse position relative to the containing element.
return e;
}
PlayController.prototype.mouseDown = function( event )
{
fixMouseEvent( event );
var containerCoord = new Coord( event.containerX, event.containerY );
var gameCoord = this.board.containerToGame( containerCoord );
var boardPiece = this.board.getPieceAt( gameCoord.x, gameCoord.y );
if ( ( boardPiece != null ) && ( boardPiece.isFixed == false ) ) {
this.dragging = new Object();
this.dragging.boardPiece = boardPiece;
this.dragging.startCoord = containerCoord;
}
return false;
}
PlayController.prototype.mouseUp = function( event )
{
this.dragging = null;
return false;
}
PlayController.prototype.mouseMove = function( event )
{
fixMouseEvent( event );
if ( (this.dragging != null) ) {
var containerCoord = new Coord( event.containerX, event.containerY );
var diffx = containerCoord.x - this.dragging.startCoord.x;
var diffy = containerCoord.y - this.dragging.startCoord.y;
var dx = 0;
var dy = 0;
if ( Math.abs( diffy ) > Math.abs( diffx ) ) {
// Move vertical
if ( diffy < - this.board.scaleY / 2 ) {
dy = -1;
} else if ( diffy > this.board.scaleY / 2 ) {
dy = 1;
} else {
return false;
}
} else {
// Move horizontal
if ( diffx < - this.board.scaleX / 2 ) {
dx = -1;
} else if ( diffx > this.board.scaleX / 2 ) {
dx = 1;
} else {
return false;
}
}
var boardPiece = this.dragging.boardPiece;
var newCoord = new Coord( boardPiece.coord.x + dx, boardPiece.coord.y + dy );
if ( this.board.canMove( boardPiece, newCoord ) ) {
this.moveBoardPiece( boardPiece, newCoord );
this.dragging.startCoord.x += dx * this.board.scaleX;
this.dragging.startCoord.y += dy * this.board.scaleY;
}
}
return false;
}
PlayController.prototype.cheat = function( solution )
{
if ( solution == null ) {
alert( "There is no solution known. Perhaps you could supply me with one :-)" );
} else {
this.history.undoAll();
this.history.clear();
var i;
for ( i = 0; i < solution.history.moves.length; i ++ ) {
var move = solution.history.moves[ i ];
var boardPiece = this.board.pieceMap[ move.boardPiece.id ];
// alert( "Adding move : " + boardPiece + " : "+ move.fromCoord + "," + move.toCoord );
this.history.addMove( boardPiece.id, move.toCoord );
this.board.move( boardPiece, move.toCoord );
}
this.history.undoAll();
alert( "You can now move through all of the moves using the Redo button. CHEATER!" );
}
return false;
}
/* -------------- Solution ------------- */
function Solution( board )
{
if (board == null) {
board = window.board;
}
this.board = board.clone();
this.board.visible = false;
this.history = new MoveHistory( this.board );
}
Solution.prototype.addMove = function( id, toX, toY )
{
var newCoord = new Coord( toX, toY );
var boardPiece = this.board.pieceMap[ id ];
if ( boardPiece == null ) {
alert( "ERROR: piece : '" + id + "' not found." );
}
this.history.addMove( id, newCoord );
this.board.move( boardPiece, newCoord );
}
/* -------------- End ------------- */