Skip to content

Instantly share code, notes, and snippets.

@eldad87
Last active October 10, 2017 16:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eldad87/e9fee573f06fe09bcaf0afdf7e35bcf5 to your computer and use it in GitHub Desktop.
Save eldad87/e9fee573f06fe09bcaf0afdf7e35bcf5 to your computer and use it in GitHub Desktop.
JavaScript - Checkers
/**
* Pawn object
* @param Player player
* @param string role pawn || queen
* @constructor
*/
function Pawn(player, role) {
this.player = player;
this.maxStep = 1;
this.role = role || 'pawn';
}
/**
* Get the owner
* @returns Player
*/
Pawn.prototype.getPlayer = function() {
return this.player;
}
/**
* Get the max steps this Pawn can perform
* @returns integer
*/
Pawn.prototype.getMaxStep = function() {
return this.maxStep;
}
/**
* Set the max steps this Pawn can perform
* @param maxStep
* @returns Pawn
*/
Pawn.prototype.setMaxStep = function(maxStep) {
this.maxStep = maxStep;
return this;
}
/**
* Get role
* @returns string pawn || queen
*/
Pawn.prototype.getRole = function() {
return this.role;
}
/**
* Set role
* @param string pawn || queen
* @returns Pawn
*/
Pawn.prototype.setRole = function(role) {
this.role = role;
return this;
}
/**
* Player object
* @param name
* @constructor
*/
function Player(name) {
this.name = name;
this.pawnCount = 0;
}
/**
* Get Player's name
* @returns string
*/
Player.prototype.getName = function() {
return this.name;
}
/**
* Get count of "living" pawns
* @returns number
*/
Player.prototype.getPawnCount = function() {
return this.pawnCount;
}
/**
* Increase "living" pawns count
* @returns Player
*/
Player.prototype.increasePawnCount = function() {
this.pawnCount++;
return this;
}
/**
* Decrease "living" pawns count
* @returns Player
*/
Player.prototype.decreasePawnCount = function() {
this.pawnCount--;
return this;
}
/**
* Coordinate object
* @param number
* @param number
* @constructor
*/
function Coordinate(x, y) {
this.x = x;
this.y = y;
}
/**
* Get X coordinate
* @returns number
*/
Coordinate.prototype.getX = function() {
return this.x;
}
/**
* Get Y coordinate
* @returns number
*/
Coordinate.prototype.getY = function() {
return this.y;
}
/**
* CheckersGame Object
* @param playerOne
* @param playerTwo
* @constructor
*/
function CheckersGame(playerOne, playerTwo) {
this.playerOne = playerOne;
this.playerTwo = playerTwo;
this.currentPlayerTurn = playerOne;
this.firstTurnPlayer = playerOne;
this.boardSize = 8;
this.maxPawnsPerPlayer = 12;
var board = {}; // Will hold all pawns by their coordinate
/**
* Reset our board, wipe all Pawns
*/
this.resetBoard = function() {
board = {};
}
/**
* Get board with all Pawns
* @returns {{}}
*/
this.getBoard = function() {
return board;
}
/**
* Add a new pawn for a given player
* @param pawn
* @param coordinate
* @returns {*}
*/
this.addPawn = function(pawn, coordinate) {
if(!this.isHabitableCoordinate(coordinate)) {
return false;
}
if(undefined === board[coordinate.getX()]) {
board[coordinate.getX()] = {};
}
board[coordinate.getX()][coordinate.getY()] = pawn;
pawn.getPlayer().increasePawnCount();
return this;
}
/**
* Remove a pawn by coordinate
* @param coordinate
* @returns {*}
*/
this.removePawn = function(coordinate) {
var pawn = this.getPawnByCoordinate(coordinate);
if(!pawn) {
return false;
}
pawn.getPlayer().decreasePawnCount();
delete board[coordinate.getX()][coordinate.getY()];
return this;
}
/**
* Switch turn, set the Other player
*/
this.turnToggle = function() {
this.currentPlayerTurn = (this.currentPlayerTurn == this.playerOne) ?
this.playerTwo : this.playerOne;
}
/**
* Get Pawn by location
* @param coordinate
* @returns Pawn|false
*/
this.getPawnByCoordinate = function(coordinate) {
var board = this.getBoard();
if(undefined === board[coordinate.getX()] || undefined === board[coordinate.getX()][coordinate.getY()]) {
return false;
}
return board[coordinate.getX()][coordinate.getY()];
}
/**
* Check if we can place a Pawn on a given coordinate
* - It must be free
* - On odd rows, pawns can land only on odd cols and vise versa.
* @param coordinate
* @returns {boolean}
*/
this.isHabitableCoordinate = function(coordinate) {
if(this.getPawnByCoordinate(coordinate)) {
return false; // There is a pawn in our coordinate
}
return coordinate.getX() % 2 === coordinate.getY() % 2;
}
/**
* Gte a list of coordinates that occupied in a given path
* @param Coordinate startCoordinate
* @param Coordinate destCoordinate
* @returns Array<Coordinate>
*/
this.getOccupiedCoordinatesByPath = function(startCoordinate, destCoordinate) {
// Create interpolator, they will be applied on each step to generate the next coordinate in the Pawn's path
var xInterpolator = 1;
var yInterpolator = 1;
if(startCoordinate.getX() > destCoordinate.getX()) {
xInterpolator = -1;
}
if(startCoordinate.getY() > destCoordinate.getY()) {
yInterpolator = -1;
}
var distance = Math.abs(startCoordinate.getX() - destCoordinate.getX());
// Lets create our coordinates, and check if there any Pawns along it
var coordinatesWithPawns = [];
var currCoordinate;
var pawnAtPath;
for(var i = 0; i <= distance; i++) {
currCoordinate = new Coordinate(
startCoordinate.getX() + (i * xInterpolator),
startCoordinate.getY() + (i * yInterpolator)
);
pawnAtPath = this.getPawnByCoordinate(currCoordinate);
if(pawnAtPath) {
coordinatesWithPawns.push(currCoordinate);
}
}
return coordinatesWithPawns;
}
/**
* Promote a Pawn, make it a Queen if reached enemy's base final row
* @param Coordinate currCoordinate
* @returns boolean
*/
this.promoteIfNeeded = function(currCoordinate) {
var pawn = this.getPawnByCoordinate(currCoordinate);
var xCoordinateForPromotion = 0; // Edge of the board, once a pawn reach it - he become a queen
if(this.firstTurnPlayer === pawn.getPlayer()) {
xCoordinateForPromotion = this.boardSize -1;
}
if(xCoordinateForPromotion !== currCoordinate.getX()) {
return false;
}
pawn.setMaxStep(this.boardSize);
pawn.setRole('queen');
return true;
}
}
/**
* Init the game
* Set Pawns in their location
* Define which player need to start first
*
* @param firstTurnPlayer
*/
CheckersGame.prototype.init = function(firstTurnPlayer) {
this.resetBoard();
// Define which of the players should start first
if(undefined !== firstTurnPlayer) {
// Set which of the players need to start
if(firstTurnPlayer !== this.playerOne && firstTurnPlayer !== this.playerTwo) {
throw new Error('Wrong firstTurnPlayer provided');
}
this.currentPlayerTurn = firstTurnPlayer;
} else {
// Choose randomly which player should start
switch(Math.floor(Math.random() * 2)) {
case 1:
this.currentPlayerTurn = this.playerOne;
break;
default:
this.currentPlayerTurn = this.playerTwo;
break;
}
}
this.firstTurnPlayer = this.currentPlayerTurn;
// Lets calc how many rows we should go over in order to add all pawns
var pawnsPerRow = this.boardSize / 2; // 8 / 2 = 4
var rowsNeeded = this.maxPawnsPerPlayer / pawnsPerRow; // 12 / 4 = 3
// Add player's pawns
// Start with playerOne
for(var row = 0; row < rowsNeeded; row++) {
for(var col = 0; col < this.boardSize; col++) {
if(!this.isHabitableCoordinate(new Coordinate(row, col))) {
continue;
}
this.addPawn(new Pawn(this.playerOne), new Coordinate(row, col));
}
}
//Lets do that again, this time for the 2nd player
for(var row = this.boardSize -1; row > (this.boardSize -1 -rowsNeeded); row--) {
for(var col = 0; col < this.boardSize; col++) {
if(!this.isHabitableCoordinate(new Coordinate(row, col))) {
continue;
}
this.addPawn(new Pawn(this.playerTwo), new Coordinate(row, col));
}
}
}
/**
* Get current player
* @returns Player
*/
CheckersGame.prototype.getCurrentPlayer = function() {
return this.currentPlayerTurn;
}
/**
* Move a Pawn from Coordinate X to Y
* If the Pawn Leap over other Pawn, it will get "eaten"
*
* @param Coordinate currCoordinate
* @param Coordinate destCoordinate
* @returns boolean
*/
CheckersGame.prototype.move = function(currCoordinate, destCoordinate) {
if(!this.validateMove(currCoordinate, destCoordinate)) {
return false;
}
var pawnsAtPath = this.getOccupiedCoordinatesByPath(currCoordinate, destCoordinate);
var pawn = this.getPawnByCoordinate(pawnsAtPath.shift()); //The 1st coordinate is occupied by our Pawn
// Remove the coordinate for the next Pawn in list, its been eaten
var eatenPawnCoordinate = pawnsAtPath.shift();
if(eatenPawnCoordinate) {
this.removePawn(eatenPawnCoordinate);
}
// Move the pawn to its final destination
this.addPawn(pawn, destCoordinate);
// remove old coordinate
this.removePawn(currCoordinate);
// Promote if needed
this.promoteIfNeeded(destCoordinate);
this.turnToggle();
return true;
}
/**
* Validate if a player can move a pawn
* @param currCoordinate
* @param destCoordinate
*/
CheckersGame.prototype.validateMove = function(currCoordinate, destCoordinate) {
// Make sure that Pawn is going to "move"
if(currCoordinate.getY() == destCoordinate.getY() || currCoordinate.getX() == destCoordinate.getX()) {
return false;
}
// Lets find if there is any pawn in current location;
var pawn = this.getPawnByCoordinate(currCoordinate);
if(!pawn) {
return false;
}
// Next, lets check if the pawn is owned by the current player
if(pawn.getPlayer() !== this.getCurrentPlayer()) {
return false;
}
// Check if the destination is outside of the board's boundaries
if(destCoordinate.getX() < 0 || destCoordinate.getX() > this.boardSize -1 ||
destCoordinate.getY() < 0 || destCoordinate.getY() > this.boardSize -1) {
return false;
}
// Check that our destination is habitable
if(!this.isHabitableCoordinate(destCoordinate)) {
return false;
}
// Check if Pawn move in right direction (x must go up for playerOne and vise versa)
if(pawn.getRole !== 'queen') {
if(pawn.getPlayer() == this.firstTurnPlayer) {
if(currCoordinate.getX() > destCoordinate.getX()) {
return false // Pawn is trying to go back
}
} else if(currCoordinate.getX() < destCoordinate.getX()) {
return false // Pawn is trying to go back
}
}
// Check for Pawns in our path
var coordinatesWithPawns = this.getOccupiedCoordinatesByPath(currCoordinate, destCoordinate);
// Our path CAN'T contain more then 2 Pawns, ours and the one we're going to eat
if (coordinatesWithPawns.length > 2) {
return false;
}
// Make sure that our destination is within our reach
var maxStepsAllowed = pawn.getMaxStep();
if (coordinatesWithPawns.length == 2) {
// In case of an "eat" operation, a simple pawn is allowed to have 1 more step
maxStepsAllowed = Math.min(this.boardSize, maxStepsAllowed+1);
// Check if the additional Pawn in our path belongs to the other player
var pawnInPath;
while(coordinatesWithPawns.length) {
pawnInPath = this.getPawnByCoordinate(coordinatesWithPawns.pop()); // Start from the last items -> first item.
if(pawnInPath === pawn) {
continue;
} else {
break;
}
}
if(pawnInPath.getPlayer() === pawn.getPlayer()) {
return false;
}
}
// Check that we moved in diagonal
var xAxisSteps = Math.abs(destCoordinate.getX() - currCoordinate.getX());
var yAxisSteps = Math.abs(destCoordinate.getY() - currCoordinate.getY());
if(xAxisSteps !== yAxisSteps) {
return false;
}
// Check for max distance
if(xAxisSteps > maxStepsAllowed ||
yAxisSteps > maxStepsAllowed) {
return false;
}
return true;
}
/**
* Determinant if a game is won
* @returns boolean
*/
CheckersGame.prototype.isGameWon = function() {
return this.playerOne.getPawnCount() == 0 ||
this.playerTwo.getPawnCount() == 0;
}
@eldad87
Copy link
Author

eldad87 commented Oct 10, 2017

To start "playing" add the players and hit init:

var playerOne = new Player('first');
var playerTwo = new Player('second');
var checkers = new CheckersGame(playerOne, playerTwo);
checkers.init(playerOne);

Print the initial positions of our Pawns and Board:

var board = checkers.getBoard();
for (var row in board) {
    for (var col in board[row]) {
        var pawn = board[row][col];
        console.log("Row: " + row + ", Col: " + col + ", Player: " + pawn.getPlayer().getName());
    }
}

Row: 0, Col: 0, Player: first
Row: 0, Col: 2, Player: first
Row: 0, Col: 4, Player: first
Row: 0, Col: 6, Player: first
Row: 1, Col: 1, Player: first
Row: 1, Col: 3, Player: first
Row: 1, Col: 5, Player: first
Row: 1, Col: 7, Player: first
Row: 2, Col: 0, Player: first
Row: 2, Col: 2, Player: first
Row: 2, Col: 4, Player: first
Row: 2, Col: 6, Player: first
Row: 5, Col: 1, Player: second
Row: 5, Col: 3, Player: second
Row: 5, Col: 5, Player: second
Row: 5, Col: 7, Player: second
Row: 6, Col: 0, Player: second
Row: 6, Col: 2, Player: second
Row: 6, Col: 4, Player: second
Row: 6, Col: 6, Player: second
Row: 7, Col: 1, Player: second
Row: 7, Col: 3, Player: second
Row: 7, Col: 5, Player: second
Row: 7, Col: 7, Player: second

Now we can move our players, some invalid moves:

// Move into occupied coordinate
var moveRes = checkers.move(new Coordinate(0, 0), new Coordinate(1, 1));
console.log('Move into occupied coordinate: ' + moveRes); // false

// Move other player's pawn
var moveRes = checkers.move(new Coordinate(7, 7), new Coordinate(6, 6));
console.log('Move other player\'s pawn: ' + moveRes); // false

// Move forward
var moveRes = checkers.move(new Coordinate(2, 2), new Coordinate(3, 2));
console.log('Move forward: ' + moveRes); // false

Here I'm moving 2 Pawns, each belongs to a different player, eventually the 1st eat the 2nd:

// first player - Move diagonal
var moveRes = checkers.move(new Coordinate(2, 2), new Coordinate(3, 3));
console.log('Move first\'s player Pawn diagonal 2,2 -> 3,3: ' + moveRes); // true

// second player - Move diagonal
var moveRes = checkers.move(new Coordinate(5, 5), new Coordinate(4, 4));
console.log('Move first\'s player Pawn diagonal 5,5 -> 4,4:: ' + moveRes); // true

// first player - Move diagonal && eat other player's pawn
var moveRes = checkers.move(new Coordinate(3, 3), new Coordinate(5, 5));
console.log('Move first\'s player Pawn diagonal, will eat Pawn in 4,4 3,3 -> 5,5: ' + moveRes); // true

// Print the amount of pawns left:
console.log('Current pawns count for first player: ' + checkers.playerOne.getPawnCount()); // 12
console.log('Current pawns count for second player: ' + checkers.playerTwo.getPawnCount()); // 11

Print the current positions of our Pawns and Board:

Row: 0, Col: 0, Player: first
Row: 0, Col: 2, Player: first
Row: 0, Col: 4, Player: first
Row: 0, Col: 6, Player: first
Row: 1, Col: 1, Player: first
Row: 1, Col: 3, Player: first
Row: 1, Col: 5, Player: first
Row: 1, Col: 7, Player: first
Row: 2, Col: 0, Player: first
Row: 2, Col: 4, Player: first
Row: 2, Col: 6, Player: first
Row: 5, Col: 1, Player: second
Row: 5, Col: 3, Player: second
Row: 5, Col: 5, Player: first
Row: 5, Col: 7, Player: second
Row: 6, Col: 0, Player: second
Row: 6, Col: 2, Player: second
Row: 6, Col: 4, Player: second
Row: 6, Col: 6, Player: second
Row: 7, Col: 1, Player: second
Row: 7, Col: 3, Player: second
Row: 7, Col: 5, Player: second
Row: 7, Col: 7, Player: second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment