' ).addClass( 'FormicariumMenu' );
this.zoomInButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumZoomInButton',
'src': '//upload.wikimedia.org/wikipedia/commons/2/2e/WikiWidgetZoomInButton.png',
'title': mw.message( 'zoom-in-button-tooltip' ),
'alt': mw.message( 'zoom-in-button' )
});
this.zoomOutButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumZoomOutButton',
'src': '//upload.wikimedia.org/wikipedia/commons/6/63/WikiWidgetZoomOutButton.png',
'title': mw.message( 'zoom-out-button-tooltip' ),
'alt': mw.message( 'zoom-out-button' )
});
this.gridButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumGridButton',
'src': '//upload.wikimedia.org/wikipedia/commons/a/a9/WikiWidgetGridButton.png',
'title': mw.message( 'grid-button-tooltip' ),
'alt': mw.message( 'grid-button' )
});
this.resetButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumResetButton',
'src': '//upload.wikimedia.org/wikipedia/commons/0/0e/WikiWidgetResetButton.png',
'title': mw.message( 'reset-button-tooltip' ),
'alt': mw.message( 'reset-button' )
});
this.previousGenerationButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumPreviousGenerationButton',
'src': '//upload.wikimedia.org/wikipedia/commons/c/c3/WikiWidgetPreviousFrameButton.png',
'title': mw.message( 'previous-generation-button-tooltip' ),
'alt': mw.message( 'previous-generation-button' )
});
this.playButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumPlayButton',
'src': '//upload.wikimedia.org/wikipedia/commons/b/b8/WikiWidgetPlayButton.png',
'title': mw.message( 'play-button-tooltip' ),
'alt': mw.message( 'play-button' )
});
this.pauseButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumPauseButton',
'src': '//upload.wikimedia.org/wikipedia/commons/6/6e/WikiWidgetPauseButton.png',
'title': mw.message( 'pause-button-tooltip' ),
'alt': mw.message( 'pause-button' )
}).hide(); // The pause button starts hidden
this.nextGenerationButton = $( '
' ).attr({
'class': 'FormicariumButton FormicariumNextGenerationButton',
'src': '//upload.wikimedia.org/wikipedia/commons/b/bf/WikiWidgetNextFrameButton.png',
'title': mw.message( 'next-generation-button-tooltip' ),
'alt': mw.message( 'next-generation-button' )
});
// Put it all together
this.menu.append(
this.zoomInButton,
this.zoomOutButton,
this.gridButton,
this.resetButton,
this.previousGenerationButton,
this.playButton,
this.pauseButton,
this.nextGenerationButton
);
this.container.append(
this.canvas,
this.menu,
this.generationCounter,
this.cellCounter,
this.antCounter
);
this.wrapper.html( this.container );
this.bindEvents = function ( board, game, mouse, touch ) {
// Board events
this.zoomOutButton.click( function () { board.zoomOut(); } );
this.zoomInButton.click( function () { board.zoomIn(); } );
this.gridButton.click( function () { board.toggleGrid(); } );
// Game events
this.resetButton.click( function () { game.reset(); } );
this.previousGenerationButton.click( function () { game.previousGeneration(); } );
this.playButton.click( function () { game.play(); } );
this.pauseButton.click( function () { game.pause(); } );
this.nextGenerationButton.click( function () { game.nextGeneration(); } );
// Mouse events
this.canvas.mousedown( function ( event ) { mouse.down( event ) } );
this.canvas.mousemove( function ( event ) { mouse.move( event ) } );
this.canvas.mouseup( function ( event ) { mouse.up( event ) } );
// Touch events
this.canvas.bind( 'touchstart', function ( event ) { touch.start( event ) } );
this.canvas.bind( 'touchmove', function ( event ) { touch.move( event ) } );
this.canvas.bind( 'touchend', function ( event ) { touch.end( event ) } );
};
},
Board: function ( gui ) {
this.gui = gui;
this.canvas = this.gui.canvas[0];
this.context = this.canvas.getContext( '2d' );
this.width = this.canvas.width;
this.height = this.canvas.height;
this.centerX = 0;
this.centerY = 0;
this.cellSize = 4;
this.xCells = Math.floor( this.width / this.cellSize );
this.yCells = Math.floor( this.height / this.cellSize );
this.grid = false;
this.cellCounter = 0;
this.antCounter = 0;
this.oldCells = [];
this.cells = [];
this.ants = [];
/**
* Constructor
*/
this.init = function () {
this.oldCells = [];
this.cells = [];
this.ants = [];
this.centerX = 0;
this.centerY = 0;
this.setCellCounter( 0 );
this.setAntCounter( 0 );
var wrapper = this.gui.wrapper,
width = wrapper.data( 'width' ),
height = wrapper.data( 'height' ),
cells = wrapper.data( 'cells' ),
ants = wrapper.data( 'ants' ),
zoom = wrapper.data( 'zoom' ),
grid = wrapper.data( 'grid' );
if ( width ) {
this.setWidth( width );
}
if ( height ) {
this.setHeight( height );
}
if ( cells ) {
cells = cells.replace( /\s/g, '' ).split( ';' );
for ( var i in cells ) {
coords = cells[ i ].split( ',' );
x = coords[0];
y = coords[1];
this.addCell( x, y );
}
}
if ( ants ) {
ants = ants.replace( /\s/g, '' ).split( ';' );
for ( var i in ants ) {
coords = ants[ i ].split( ',' );
x = coords[0];
y = coords[1];
this.addAnt( x, y );
}
}
if ( zoom ) {
this.setCellSize( zoom );
}
if ( grid ) {
this.grid = true;
}
this.refill();
};
/* Getters */
this.getXcells = function () {
return Math.floor( this.width / this.cellSize );
};
this.getYcells = function () {
return Math.floor( this.height / this.cellSize );
};
this.getCell = function ( x, y ) {
var i, cell;
for ( i in this.cells ) {
cell = this.cells[ i ];
if ( cell.x === x && cell.y === y ) {
return cell;
}
}
return null;
};
this.getOldCell = function ( x, y ) {
var i, cell;
for ( i in this.oldCells ) {
cell = this.oldCells[ i ];
if ( cell.x === x && cell.y === y ) {
return cell;
}
}
return null;
};
this.getAnt = function ( x, y ) {
var i, ant;
for ( i in this.ants ) {
ant = this.ants[ i ];
if ( ant.x === x && ant.y === y ) {
return ant;
}
}
return null;
};
/* Setters */
this.setCellCounter = function ( value ) {
this.cellCounter = value;
this.gui.cellCounter.text( mw.message( 'cell-counter' ) + value );
};
this.setAntCounter = function ( value ) {
this.antCounter = value;
this.gui.antCounter.text( mw.message( 'ant-counter' ) + value );
};
this.setWidth = function ( value ) {
this.width = parseInt( value );
this.canvas.setAttribute( 'width', value );
this.xCells = this.getXcells();
};
this.setHeight = function ( value ) {
this.height = parseInt( value );
this.canvas.setAttribute( 'height', value );
this.yCells = this.getYcells();
};
this.setCellSize = function ( value ) {
this.cellSize = parseInt( value );
this.xCells = this.getXcells();
this.yCells = this.getYcells();
};
/* Actions */
this.zoomIn = function () {
if ( this.cellSize === 32 ) {
return;
}
this.setCellSize( this.cellSize * 2 );
this.refill();
};
this.zoomOut = function () {
if ( this.cellSize === 1 ) {
return;
}
this.setCellSize( this.cellSize / 2 );
this.refill();
};
this.toggleGrid = function () {
this.grid = this.grid ? false : true;
this.refill();
};
this.drawGrid = function () {
if ( this.cellSize < 4 ) {
return; // Cells are too small for the grid
}
this.context.beginPath();
for ( var x = 0; x <= this.xCells; x++ ) {
this.context.moveTo( x * this.cellSize - 0.5, 0 ); // The 0.5 avoids getting blury lines
this.context.lineTo( x * this.cellSize - 0.5, this.height );
}
for ( var y = 0; y <= this.yCells; y++ ) {
this.context.moveTo( 0, y * this.cellSize - 0.5 );
this.context.lineTo( this.width, y * this.cellSize - 0.5 );
}
this.context.strokeStyle = '#333';
this.context.stroke();
};
this.fill = function () {
var i, cell, ant;
for ( i in this.cells ) {
cell = this.cells[ i ];
this.fillCell( cell.x, cell.y, cell.color );
}
for ( i in this.ants ) {
ant = this.ants[ i ];
this.fillCell( ant.x, ant.y, ant.color );
}
if ( this.grid ) {
this.drawGrid();
}
};
this.clear = function () {
this.context.clearRect( 0, 0, this.canvas.width, this.canvas.height );
};
this.refill = function () {
this.clear();
this.fill();
};
this.fillCell = function ( x, y, color ) {
var minX = this.centerX - Math.floor( this.xCells / 2 ),
minY = this.centerY - Math.floor( this.yCells / 2 ),
maxX = minX + this.xCells,
maxY = minY + this.yCells;
if ( x < minX || y < minY || x > maxX || y > maxY ) {
return; // If the cell is beyond view, don't draw it
}
var rectX = Math.abs( this.centerX - Math.floor( this.xCells / 2 ) - x ) * this.cellSize,
rectY = Math.abs( this.centerY - Math.floor( this.yCells / 2 ) - y ) * this.cellSize,
rectW = this.cellSize,
rectH = this.cellSize;
this.context.fillStyle = color;
this.context.fillRect( rectX, rectY, rectW, rectH );
};
this.addCell = function ( x, y, color ) {
var x = parseInt( x ),
y = parseInt( y ),
cell = this.getCell( x, y ),
index = this.cells.indexOf( cell );
if ( index === -1 ) {
var color = color ? color : 'white';
this.fillCell( x, y, color );
this.cells.push({ 'x': x, 'y': y, 'color': color });
this.setCellCounter( this.cellCounter + 1 );
}
};
this.addOldCell = function ( x, y, color ) {
var cell = this.getOldCell( x, y );
index = this.oldCells.indexOf( cell );
if ( index === -1 ) {
var color = color ? color : 'white';
this.fillCell( x, y, color );
this.oldCells.push({ 'x': x, 'y': y, 'color': color });
}
};
this.removeCell = function ( x, y ) {
var cell = this.getCell( x, y ),
index = this.cells.indexOf( cell );
if ( index > -1 ) {
this.cells.splice( index, 1 );
this.setCellCounter( this.cellCounter - 1 );
}
};
this.removeOldCell = function ( x, y ) {
var cell = this.getOldCell( x, y ),
index = this.oldCells.indexOf( cell );
if ( index > -1 ) {
this.oldCells.splice( index, 1 );
}
};
this.addAnt = function ( x, y ) {
var x = parseInt( x ),
y = parseInt( y ),
ant = new Formicarium.Ant( this, x, y );
this.ants.push( ant );
this.setAntCounter( this.antCounter + 1 );
};
this.removeAnt = function ( x, y ) {
var x = parseInt( x ),
y = parseInt( y ),
ant = this.getAnt( x, y ),
index = this.ants.indexOf( ant );
this.ants.splice( index, 1 );
this.setAntCounter( this.antCounter - 1 );
};
},
Game: function ( board ) {
this.board = board;
this.playing = false;
this.generationCounter = 0;
/* Setters */
this.setGenerationCounter = function ( value ) {
this.generationCounter = value;
this.board.gui.generationCounter.text( mw.message( 'generation-counter' ) + value );
};
/* Actions */
this.previousGeneration = function () {
this.board.oldCells = this.board.cells.slice(); // Clone the array
for ( var i in this.board.ants ) {
this.board.ants[ i ].undoRoutine();
}
this.board.refill();
this.setGenerationCounter( this.generationCounter - 1 );
};
this.nextGeneration = function () {
this.board.oldCells = this.board.cells.slice(); // Clone the array
for ( var i in this.board.ants ) {
this.board.ants[ i ].doRoutine();
}
this.board.refill();
this.setGenerationCounter( this.generationCounter + 1 );
};
this.reset = function () {
// Reset the board
this.board.init();
// Reset the game
this.pause();
this.setGenerationCounter( 0 );
};
this.play = function () {
if ( this.playing ) {
return; // The game is already playing
}
var game = this;
this.playing = setInterval( function () { game.nextGeneration(); }, 1 ); // The interval id is stored in the playing property
this.board.gui.playButton.hide();
this.board.gui.pauseButton.show();
};
this.pause = function () {
if ( !this.playing ) {
return; // The game is already paused
}
clearInterval( this.playing );
this.playing = false;
this.board.gui.playButton.show();
this.board.gui.pauseButton.hide();
};
},
Mouse: function ( board ) {
this.board = board;
/**
* The position relative to the origin of the coordinate system of the board (in cells, not pixels)
*/
this.oldX = null;
this.oldY = null;
this.newX = null;
this.newY = null;
this.state = null; // up or down
this.dragged = false;
/* Getters */
this.getX = function ( event ) {
var offsetX = event.pageX - $( event.target ).offset().left - 1, // The -1 is to correct a minor displacement
newX = this.board.centerX - Math.floor( this.board.xCells / 2 ) + Math.floor( offsetX / this.board.cellSize );
return newX;
};
this.getY = function ( event ) {
var offsetY = event.pageY - $( event.target ).offset().top - 2, // The -2 is to correct a minor displacement
newY = this.board.centerY - Math.floor( this.board.yCells / 2 ) + Math.floor( offsetY / this.board.cellSize );
return newY;
};
/* Event handlers */
this.up = function ( event ) {
this.state = 'up';
if ( !this.dragged ) {
var x = this.newX,
y = this.newY,
ant = this.board.getAnt( x, y );
if ( ant ) {
this.board.removeAnt( x, y );
} else {
this.board.addAnt( x, y );
}
this.board.refill();
}
this.dragged = false;
};
this.move = function ( event ) {
if ( this.state === 'down' ) {
this.oldX = this.newX;
this.oldY = this.newY;
this.newX = this.getX( event );
this.newY = this.getY( event );
if ( this.newX !== this.oldX || this.newY !== this.oldY ) {
this.dragged = true;
this.board.centerX += this.oldX - this.newX;
this.board.centerY += this.oldY - this.newY;
this.board.refill();
// Bugfix: without the following, the board flickers when moving, not sure why
this.newX = this.getX( event );
this.newY = this.getY( event );
}
}
};
this.down = function ( event ) {
this.state = 'down';
this.newX = this.getX( event );
this.newY = this.getY( event );
};
},
Touch: function ( board ) {
this.board = board;
// The distance from the origin of the coordinate system in virtual pixels (not real ones)
this.newX = null;
this.newX = null;
this.oldX = null;
this.oldY = null;
this.moved = false;
/**
* Getters
*/
this.getX = function ( event ) {
var offsetX = event.originalEvent.changedTouches[0].pageX - $( event.target ).offset().left,
newX = this.board.centerX - Math.floor( this.board.xCells / 2 ) + Math.floor( offsetX / this.board.cellSize );
return newX;
};
this.getY = function ( event ) {
var offsetY = event.originalEvent.changedTouches[0].pageY - $( event.target ).offset().top,
newY = this.board.centerY - Math.floor( this.board.yCells / 2 ) + Math.floor( offsetY / this.board.cellSize );
return newY;
};
/**
* Event handlers
*/
this.start = function ( event ) {
this.newX = this.getX( event );
this.newY = this.getY( event );
};
this.move = function ( event ) {
this.oldX = this.newX;
this.oldY = this.newY;
this.newX = this.getX( event );
this.newY = this.getY( event );
this.board.centerX += this.oldX - this.newX;
this.board.centerY += this.oldY - this.newY;
this.board.refill();
// Bugfix: without the following, the board flickers when moving, not sure why
this.newX = this.getX( event );
this.newY = this.getY( event );
this.moved = true;
event.preventDefault();
};
this.end = function ( event ) {
this.moved = false;
};
},
Ant: function ( board, x, y, color, direction ) {
this.board = board;
this.x = x ? x : 0;
this.y = y ? y : 0;
this.color = color ? color : 'red';
this.direction = direction ? direction : 'N';
this.getCell = function () {
return this.board.getCell( this.x, this.y );
};
this.getOldCell = function () {
return this.board.getOldCell( this.x, this.y );
};
this.addCell = function ( color ) {
this.board.addCell( this.x, this.y, color );
return this;
};
this.removeCell = function () {
this.board.removeCell( this.x, this.y );
return this;
};
this.addOldCell = function ( color ) {
this.board.addOldCell( this.x, this.y, color );
return this;
};
this.removeOldCell = function () {
this.board.removeOldCell( this.x, this.y );
return this;
};
this.turnLeft = function () {
if ( this.direction === 'N' ) {
this.direction = 'W';
} else if ( this.direction === 'W' ) {
this.direction = 'S';
} else if ( this.direction === 'S' ) {
this.direction = 'E';
} else if ( this.direction === 'E' ) {
this.direction = 'N';
}
return this;
};
this.turnRight = function () {
if ( this.direction === 'N' ) {
this.direction = 'E';
} else if ( this.direction === 'W' ) {
this.direction = 'N';
} else if ( this.direction === 'S' ) {
this.direction = 'W';
} else if ( this.direction === 'E' ) {
this.direction = 'S';
}
return this;
};
this.moveBack = function () {
if ( this.direction === 'N' ) {
this.y--;
} else if ( this.direction === 'W' ) {
this.x++;
} else if ( this.direction === 'S' ) {
this.y++;
} else if ( this.direction === 'E' ) {
this.x--;
}
return this;
};
this.moveForward = function () {
if ( this.direction === 'N' ) {
this.y++;
} else if ( this.direction === 'W' ) {
this.x--;
} else if ( this.direction === 'S' ) {
this.y--;
} else if ( this.direction === 'E' ) {
this.x++;
}
return this;
};
this.doRoutine = function () {
var cell = this.getOldCell();
if ( cell ) {
this.removeCell().turnRight();
} else {
this.addCell().turnLeft();
}
this.moveForward();
};
this.undoRoutine = function () {
this.moveBack();
var cell = this.getOldCell();
if ( cell ) {
this.removeCell().turnRight();
} else {
this.addCell().turnLeft();
}
};
}
}
$( Formicarium.init );