var model = require('../model.js');
var Rule = require('./rule.js');
/**
* Most popular variant played in Bulgaria called casual (обикновена).
* @constructor
* @extends Rule
*/
function RuleBgCasual() {
Rule.call(this);
/**
* Rule name, matching the class name (eg. 'RuleBgCasual')
* @type {string}
*/
this.name = 'RuleBgCasual';
/**
* Short title describing rule specifics
* @type {string}
*/
this.title = 'General';
/**
* Full description of rule
* @type {string}
*/
this.description = 'Most popular variant of backgammon played in Bulgaria.';
/**
* Full name of country where this rule (variant) is played.
* To list multiple countries use a pipe ('|') character as separator.
* @type {string}
*/
this.country = 'Bulgaria';
/**
* Two character ISO code of country where this rule (variant) is played.
* To list multiple codes use a pipe ('|') character as separator.
* List codes in same order as countries in the field above.
* @type {string}
*/
this.countryCode = 'bg';
}
RuleBgCasual.prototype = Object.create(Rule.prototype);
RuleBgCasual.prototype.constructor = RuleBgCasual;
/**
* Reset state to initial position of pieces according to current rule.
* @memberOf RuleBgCasual
* @param {State} state - Board state
*/
RuleBgCasual.prototype.resetState = function(state) {
/**
* Move pieces to correct initial positions for both players.
* Values in state.points are zero based and denote the .
* the number of pieces on each position.
* Index 0 of array is position 1 and increments to the number of maximum
* points.
*
* Position: |12 13 14 15 16 17| |18 19 20 21 22 23| White
* |5w 3b | |5b 2w| <-
* | | | |
* | | | |
* | | | |
* |5b 3w | |5w 2b| <-
* Position: |11 10 09 08 07 06| |05 04 03 02 01 00| Black
*
*/
model.State.clear(state);
this.place(state, 5, model.PieceType.WHITE, 5);
this.place(state, 3, model.PieceType.WHITE, 7);
this.place(state, 5, model.PieceType.WHITE, 12);
this.place(state, 2, model.PieceType.WHITE, 23);
this.place(state, 5, model.PieceType.BLACK, 18);
this.place(state, 3, model.PieceType.BLACK, 16);
this.place(state, 5, model.PieceType.BLACK, 11);
this.place(state, 2, model.PieceType.BLACK, 0);
};
/**
* Increment position by specified number of steps and return an incremented position
* @memberOf RuleBgCasual
* @param {number} position - Denormalized position
* @param {PieceType} type - Type of piece
* @param {number} steps - Number of steps to increment towards first home position
* @returns {number} - Incremented position (denormalized)
*/
RuleBgCasual.prototype.incPos = function(position, type, steps) {
var newPosition;
if (type === model.PieceType.WHITE) {
newPosition = position - steps;
}
else {
newPosition = position + steps;
}
return newPosition;
};
/**
* Normalize position - Normalized positions start from 0 to 23 for both players,
* where 0 is the first position in the home part of the board, 6 is the last
* position in the home part and 23 is the furthest position - in the opponent's
* home.
* @memberOf RuleBgCasual
* @param {number} position - Denormalized position (0 to 23 for white and 23 to 0 for black)
* @param {PieceType} type - Type of piece (white/black)
* @returns {number} - Normalized position (0 to 23 for both players)
*/
RuleBgCasual.prototype.normPos = function(position, type) {
var normPosition = position;
if (type === model.PieceType.BLACK) {
normPosition = 23 - position;
}
return normPosition;
};
/**
* Get denormalized position - start from 0 to 23 for white player and from
* 23 to 0 for black player.
* @memberOf RuleBgCasual
* @param {number} position - Normalized position (0 to 23 for both players)
* @param {PieceType} type - Type of piece (white/black)
* @return {number} - Denormalized position (0 to 23 for white and 23 to 0 for black)
*/
RuleBgCasual.prototype.denormPos = function(position, type) {
var denormPosition = position;
if (type === model.PieceType.BLACK) {
denormPosition = 23 - position;
}
return denormPosition;
};
/**
* Call this method after a request for moving a piece has been made.
* Determines if the move is allowed and what actions will have to be made as
* a result. Actions can be `move`, `place`, `hit` or `bear`.
*
* If move is allowed or not depends on the current state of the game. For example,
* if the player has pieces on the bar, they will only be allowed to place pieces.
*
* Multiple actions can be returned, if required. Placing (or moving) a piece over
* an opponent's blot will result in two actions: `hit` first, then `place` (or `move`).
*
* The list of actions returned would usually be appllied to game state and then
* sent to client. The client's UI would play the actions (eg. with movement animation)
* in the same order.
*
* @memberOf RuleBgCasual
* @param {State} state - State
* @param {Piece} piece - Piece to move
* @param {PieceType} type - Type of piece
* @param {number} steps - Number of steps to increment towards first home position
* @returns {MoveAction[]} - List of actions if move is allowed, empty list otherwise.
*/
RuleBgCasual.prototype.getMoveActions = function(state, piece, steps) {
var actionList = [];
// Next, check conditions specific to this game rule and build the list of
// actions that has to be made.
/**
* Create a new move action and add it to actionList. Used internally.
*
* @alias RuleBgCasual.getMoveActions.addAction
* @memberof RuleBgCasual.getMoveActions
* @method RuleBgCasual.getMoveActions.addAction
* @param {MoveActionType} moveActionType - Type of move action (eg. move, hit, bear)
* @param {Piece} piece - Piece to move
* @param {number} from - Denormalized source position. If action uses only one position parameter, this one is used.
* @param {number} to - Denormalized destination position.
* @returns {MoveAction}
* @see {@link getMoveActions} for more information on purpose of move actions.
*/
function addAction(moveActionType, piece, from, to) {
var action = new model.MoveAction();
action.type = moveActionType;
action.piece = piece;
action.position = from;
action.from = from;
if (typeof to != "undefined") {
action.to = to;
}
actionList.push(action);
return action;
}
// TODO: Catch exceptions due to disallowed move requests and pass them as error message to the client.
try {
var position = model.State.getPiecePos(state, piece);
// TODO: Consider using state machine? Is it worth, can it be useful in other methods too?
if (this.havePiecesOnBar(state, piece.type)) {
/*
If there are pieces on the bar, the player can only place pieces on.
Input data: steps=3
Cases:
- Opponent has no pieces there --> place the checker at position 21
- Opponent has exactly one piece --> hit oponent piece and place at position 21
- Opponent has two or more pieces --> point is blocked, cannot place piece there
!
+12-13-14-15-16-17------18-19-20-21-22-23-+
| O | @ | X |
| O | | X |
| | | X |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | O |
| | | O O O |
+11-10--9--8--7--6-------5--4--3--2--1--0-+ -1
*/
if (model.State.isPieceOnBar(state, piece)) {
// Make sure that the piece that the player wants to move
// is on the bar
var destination = (piece.type === model.PieceType.WHITE) ? (24 - steps) : (steps - 1);
var destTopPiece = model.State.getTopPiece(state, destination);
var destTopPieceType = (destTopPiece) ? destTopPiece.type : null;
if ((destTopPieceType === null) || (destTopPieceType === piece.type)) {
// There are no pieces at this point or the top piece is owned by player,
// so directly place piece from bar to opponent's home field
addAction(
model.MoveActionType.RECOVER, piece, destination
);
}
else if (model.State.countAtPos(state, destination, destTopPieceType) === 1) {
// The top piece is opponent's and is only one (i.e. the point is not blocked),
// so hit opponent's piece from destination and place ours at this position
addAction(
model.MoveActionType.HIT, destTopPiece, destination
);
addAction(
model.MoveActionType.RECOVER, piece, destination
);
}
}
}
else if (this.allPiecesAreHome(state, piece.type)) {
/*
If all pieces are in home field, the player can bear pieces
Cases:
- Normalized position >= 0 --> Just move the piece
- Normalized position === -1 --> Bear piece
- Normalized position < -1 --> Bear piece, only if there are no player pieces at higher positions
+12-13-14-15-16-17------18-19-20-21-22-23-+
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | O O |
| | | O O O |
+11-10--9--8--7--6-------5--4--3--2--1--0-+ -1
*/
var destination = this.incPos(position, piece.type, steps);
var normDestination = this.normPos(destination, piece.type);
// Move the piece, unless point is blocked by opponent
if (normDestination >= 0) {
var destTopPiece = model.State.getTopPiece(state, destination);
var destTopPieceType = (destTopPiece) ? destTopPiece.type : null;
// There are no pieces at this point or the top piece is owned by player,
// so just move piece to that position
if ((destTopPieceType === null) || (destTopPieceType === piece.type)) {
addAction(
model.MoveActionType.MOVE, piece, position, destination
);
}
// The top piece is opponent's and is only one (i.e. the point is not blocked),
// so hit opponent's piece from destination and move ours at this position
else if (model.State.countAtPos(state, destination, destTopPieceType) === 1) {
addAction(
model.MoveActionType.HIT, destTopPiece, destination
);
addAction(
model.MoveActionType.MOVE, piece, position, destination
);
}
}
// If steps are just enought to reach position -1, bear piece
else if (normDestination === -1) {
addAction(
model.MoveActionType.BEAR, piece, position
);
}
// If steps move the piece beyond -1 position, the player can bear the piece,
// only if there are no other pieces at higher positions
else {
var normSource = this.normPos(position, piece.type);
if (this.countAtHigherPos(state, normSource + 1, piece.type) <= 0) {
addAction(
model.MoveActionType.BEAR, piece, position
);
}
}
}
else {
/*
If there are no pieces at bar, and at least one piece outside home,
just move the piece.
Input data: position=13, steps=3
Cases:
- Opponent has no pieces there --> place the checker at position 10
- Opponent has exactly one piece --> hit oponent piece and place at position 10
- Opponent has two or more pieces --> point is blocked, cannot place piece there
!
+12-13-14-15-16-17------18-19-20-21-22-23-+
| O | | X |
| O | | X |
| | | X |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | O |
| | | O O O |
+11-10--9--8--7--6-------5--4--3--2--1--0-+ -1
!
*/
var destination = this.incPos(position, piece.type, steps);
// Make sure that destination is within board
if ((destination >= 0) && (destination <= 23)) {
var normDest = this.normPos(destination, piece.type);
// TODO: Make sure position is not outside board
var destTopPiece = model.State.getTopPiece(state, destination);
var destTopPieceType = (destTopPiece) ? destTopPiece.type : null;
// There are no pieces at this point or the top piece is owned by player
if ((destTopPieceType === null) || (destTopPieceType === piece.type)) {
addAction(
model.MoveActionType.MOVE, piece, position, destination
);
}
// The top piece is opponent's and is a blot (i.e. the point is not blocked)
else if (model.State.countAtPos(state, destination, destTopPieceType) === 1) {
addAction(
model.MoveActionType.HIT, destTopPiece, destination
);
addAction(
model.MoveActionType.MOVE, piece, position, destination
);
}
}
}
}
catch (e) {
actionList = [];
return actionList;
}
return actionList;
};
/**
* Call this method to apply a list of actions to a game state.
* Actions can be `move`, `place`, `hit` or `bear`.
*
* @memberOf RuleBgCasual
* @param {State} state - State to change
* @param {MoveAction[]} actionList - List of action to apply.
*/
RuleBgCasual.prototype.applyMoveActions = function(state, actionList) {
for (var i = 0; i < actionList.length; i++) {
var action = actionList[i];
if (action.type === model.MoveActionType.MOVE) {
this.move(state, action.piece, action.to);
}
else if (action.type === model.MoveActionType.RECOVER) {
this.recover(state, action.piece, action.position);
}
else if (action.type === model.MoveActionType.HIT) {
this.hit(state, action.piece);
}
else if (action.type === model.MoveActionType.BEAR) {
this.bear(state, action.piece);
}
}
};
module.exports = new RuleBgCasual();