diff --git a/app/browser/js/config.js b/app/browser/js/config.js
index 5086903..44ed08a 100644
--- a/app/browser/js/config.js
+++ b/app/browser/js/config.js
@@ -3,9 +3,9 @@ var config = {
'boardUI': '../app/browser/js/SimpleBoardUI.js',
'defaultRule': 'RuleBgCasual',
'selectableRules': [
- 'RuleBgCasual',
- 'RuleBgGulbara',
- 'RuleBgTapa'
+ 'RuleUsUpgammon',
+ 'RuleBgCasual'
+
]
};
diff --git a/app/browser/js/main.js b/app/browser/js/main.js
index 842ac6a..6d9f16e 100644
--- a/app/browser/js/main.js
+++ b/app/browser/js/main.js
@@ -18,6 +18,7 @@ require('../../../lib/rules/rule.js');
require('../../../lib/rules/RuleBgCasual.js');
require('../../../lib/rules/RuleBgGulbara.js');
require('../../../lib/rules/RuleBgTapa.js');
+require('../../../lib/rules/RuleUsUpgammon.js');
function App() {
this._config = {};
diff --git a/app/server/config.js b/app/server/config.js
index 6ba1feb..1c8fcca 100644
--- a/app/server/config.js
+++ b/app/server/config.js
@@ -1,9 +1,8 @@
var config = {
'rulePath': '../../lib/rules/',
'enabledRules': [
- 'RuleBgCasual',
- 'RuleBgGulbara',
- 'RuleBgTapa'
+ 'RuleUsUpgammon',
+ 'RuleBgCasual'
]
};
diff --git a/lib/client.js b/lib/client.js
index 51209a5..2a66eba 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -9,6 +9,7 @@ require('./rules/rule.js');
require('./rules/RuleBgCasual.js');
require('./rules/RuleBgGulbara.js');
require('./rules/RuleBgTapa.js');
+require('./rules/RuleUsUpgammon.js');
/**
* Backgammon client
diff --git a/lib/rules/RuleBgCasual.js b/lib/rules/RuleBgCasual.js
index 772623e..fa237ef 100644
--- a/lib/rules/RuleBgCasual.js
+++ b/lib/rules/RuleBgCasual.js
@@ -19,7 +19,7 @@ function RuleBgCasual() {
* Short title describing rule specifics
* @type {string}
*/
- this.title = 'General';
+ this.title = 'Traditional';
/**
* Full description of rule
diff --git a/lib/rules/RuleUsUpgammon.js b/lib/rules/RuleUsUpgammon.js
new file mode 100644
index 0000000..7322dbd
--- /dev/null
+++ b/lib/rules/RuleUsUpgammon.js
@@ -0,0 +1,398 @@
+var model = require('../model.js');
+var Rule = require('./rule.js');
+
+/**
+ * Most popular variant played in Bulgaria called casual (обикновена).
+ * @constructor
+ * @extends Rule
+ */
+function RuleUsUpgammon() {
+ Rule.call(this);
+
+ /**
+ * Rule name, matching the class name (eg. 'RuleBgCasual')
+ * @type {string}
+ */
+ this.name = 'RuleUsUpgammon';
+
+ /**
+ * Short title describing rule specifics
+ * @type {string}
+ */
+ this.title = 'Upgammon';
+
+ /**
+ * Full description of rule
+ * @type {string}
+ */
+ this.description = 'Most popular variant of backgammon for us.';
+
+ /**
+ * Full name of country where this rule (variant) is played.
+ * To list multiple countries use a pipe ('|') character as separator.
+ * @type {string}
+ */
+ this.country = 'USA';
+
+ /**
+ * 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 = 'us';
+
+ /**
+ * Descendents should list all action types that are allowed in this rule.
+ * @type {MoveActionType[]}
+ */
+ this.allowedActions = [
+ model.MoveActionType.MOVE,
+ model.MoveActionType.BEAR,
+ model.MoveActionType.HIT,
+ model.MoveActionType.RECOVER
+ ];
+}
+
+RuleUsUpgammon.prototype = Object.create(Rule.prototype);
+RuleUsUpgammon.prototype.constructor = RuleUsUpgammon;
+
+/**
+ * Reset state to initial position of pieces according to current rule.
+ * @memberOf RuleUsUpgammon
+ * @param {State} state - Board state
+ */
+RuleUsUpgammon.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 RuleUsUpgammon
+ * @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)
+ */
+RuleUsUpgammon.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 RuleUsUpgammon
+ * @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)
+ */
+RuleUsUpgammon.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 RuleUsUpgammon
+ * @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)
+ */
+RuleUsUpgammon.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 RuleUsUpgammon
+ * @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.
+ */
+RuleUsUpgammon.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 RuleUsUpgammon.getMoveActions.addAction
+ * @memberof RuleUsUpgammon.getMoveActions
+ * @method RuleUsUpgammon.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;
+};
+
+module.exports = new RuleUsUpgammon();
From cd72971cdbdd980c98edfabb2e7b2dfc5f18a01e Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 17:16:07 +0100
Subject: [PATCH 04/13] support up
---
app/browser/js/SimpleBoardUI.js | 168 ++++++++++++++++----------------
lib/model.js | 32 +++---
lib/rules/RuleUsUpgammon.js | 15 +--
3 files changed, 109 insertions(+), 106 deletions(-)
diff --git a/app/browser/js/SimpleBoardUI.js b/app/browser/js/SimpleBoardUI.js
index d660d59..7424c3b 100644
--- a/app/browser/js/SimpleBoardUI.js
+++ b/app/browser/js/SimpleBoardUI.js
@@ -18,7 +18,7 @@ function SimpleBoardUI(client) {
* @type {Client}
*/
this.client = client;
-
+
/**
* @type {Match}
*/
@@ -33,7 +33,7 @@ function SimpleBoardUI(client) {
this.container = $('#' + this.client.config.containerID);
this.container.append($('#tmpl-board').html());
this.container.append($(''));
-
+
this.board = $('#board');
this.fields = [];
for (var i = 0; i < 4; i++) {
@@ -58,30 +58,30 @@ function SimpleBoardUI(client) {
n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
return n.toFixed(digits);
};
-
+
this.notifyOhSnap = function (message, params) {
if (!params.duration) {
params.duration = 1500;
}
ohSnap(message, params);
};
-
+
this.notifyInfo = function (message, timeout) {
this.notifyOhSnap(message, {color: 'blue', duration: timeout});
};
-
+
this.notifyPositive = function (message, timeout) {
this.notifyOhSnap(message, {color: 'green', duration: timeout});
};
-
+
this.notifyNegative = function (message, timeout) {
this.notifyOhSnap(message, {color: 'red', duration: timeout});
};
-
+
this.notifySuccess = function (message, timeout) {
this.notifyOhSnap(message, {color: 'green', duration: timeout});
};
-
+
this.notifyError = function (message, timeout) {
this.notifyOhSnap(message, {color: 'red', duration: timeout});
};
@@ -89,7 +89,7 @@ function SimpleBoardUI(client) {
this.getPointElem = function (pos) {
return $('#point' + pos);
};
-
+
this.getPieceElem = function (piece) {
return $('#piece' + piece.id);
};
@@ -109,7 +109,7 @@ function SimpleBoardUI(client) {
}
return null;
};
-
+
this.getBarElem = function (type) {
var barID = (type === this.client.player.currentPieceType) ? 'top-bar' : 'bottom-bar';
var bar = $('#' + barID);
@@ -119,10 +119,10 @@ function SimpleBoardUI(client) {
this.getBarTopPieceElem = function (type) {
var barElem = this.getBarElem(type);
var pieceElem = barElem.find('div.piece').last();
-
+
return pieceElem;
};
-
+
this.getBarTopPiece = function (type) {
var pieceElem = this.getBarTopPieceElem(type);
if (pieceElem) {
@@ -134,14 +134,14 @@ function SimpleBoardUI(client) {
this.getPieceByID = function (id) {
return $('#piece' + id);
};
-
+
/**
* Handles clicking on a point (position)
*/
this.handlePointClick = function (e) {
var self = e.data;
var game = self.match.currentGame;
-
+
console.log('mousedown click', game);
if (!model.Game.hasMoreMoves(game)) {
return;
@@ -164,7 +164,7 @@ function SimpleBoardUI(client) {
}
e.preventDefault();
};
-
+
/**
* Handles clicking on a point (position)
*/
@@ -189,17 +189,17 @@ function SimpleBoardUI(client) {
var pieceElem = $(e.currentTarget).find('div.piece').last();
var piece = pieceElem.data('piece');
if (piece) {
- self.client.reqMove(piece, steps);
+ self.client.reqMove(piece, steps);
}
e.preventDefault();
};
-
+
/**
* Assign actions to DOM elements
*/
this.assignActions = function () {
var self = this;
-
+
// Game actions
$('#btn-roll').unbind('click');
$('#btn-roll').click(function (e) {
@@ -212,24 +212,24 @@ function SimpleBoardUI(client) {
$('#btn-confirm').click(function (e) {
self.client.reqConfirmMoves();
});
-
+
$('#btn-undo').unbind('click');
$('#btn-undo').click(function (e) {
self.client.reqUndoMoves();
});
-
+
$('#menu-undo').unbind('click');
$('#menu-undo').click(function (e) {
$('.navbar').collapse('hide');
self.client.reqUndoMoves();
});
-
+
$('#menu-resign').unbind('click');
$('#menu-resign').click(function (e) {
// Ask player if they want to resign from current game only
// or abandon the whole match
$('.navbar').collapse('hide');
-
+
BootstrapDialog.show({
title: 'Resign from game or match?',
type: BootstrapDialog.TYPE_DEFAULT,
@@ -263,34 +263,34 @@ function SimpleBoardUI(client) {
]
});
});
-
+
if ((!this.match) || (!this.match.currentGame) || (!this.client.player)) {
return;
}
-
+
// Actions for points
for (var pos = 0; pos < 24; pos++) {
var pointElem = this.getPointElem(pos);
$(document).on('contextmenu', pointElem, function(e){
// Block browser menu
- return false;
+ // return false;
});
pointElem.unbind('mousedown');
pointElem.mousedown(self, self.handlePointClick);
}
-
+
// Actions for bar
for (var pieceType = 0; pieceType <= model.PieceType.BLACK; pieceType++) {
console.log('pieceType', pieceType);
var barElem = this.getBarElem(pieceType);
console.log(barElem);
-
+
$(document).on('contextmenu', barElem, function(e){
// Block browser menu
- return false;
+ // return false;
});
-
+
barElem.unbind('mousedown');
barElem.mousedown(self, self.handleBarClick);
}
@@ -310,9 +310,9 @@ function SimpleBoardUI(client) {
- Field 1 - bottom left
- Field 2 - top right
- Field 3 - bottom right
-
+
Fields are arrange on the board in the following way:
-
+
+12-13-14-15-16-17------18-19-20-21-22-23-+
| | | |
| Field 0 | | Field 2 |
@@ -326,38 +326,38 @@ function SimpleBoardUI(client) {
| Field 1 | | Field 3 |
| | | |
+11-10--9--8--7--6-------5--4--3--2--1--0-+ -1
-
+
*/
-
+
var pieceType = this.client.player.currentPieceType;
var i;
var k;
var typeClass;
-
+
for (i = 12; i < 18; i++) {
typeClass = i % 2 === 0 ? 'even' : 'odd';
-
+
k = (pieceType === model.PieceType.BLACK) ? i - 12 : i;
this.createPoint(this.fields[0], k, typeClass);
}
for (i = 11; i >= 6; i--) {
typeClass = i % 2 === 0 ? 'even' : 'odd';
-
+
k = (pieceType === model.PieceType.BLACK) ? i + 12 : i;
this.createPoint(this.fields[1], k, typeClass);
}
for (i = 18; i < 24; i++) {
typeClass = i % 2 === 0 ? 'even' : 'odd';
-
+
k = (pieceType === model.PieceType.BLACK) ? i - 12 : i;
this.createPoint(this.fields[2], k, typeClass);
}
for (i = 5; i >= 0; i--) {
typeClass = i % 2 === 0 ? 'even' : 'odd';
-
+
k = (pieceType === model.PieceType.BLACK) ? i + 12 : i;
this.createPoint(this.fields[3], k, typeClass);
}
@@ -371,7 +371,7 @@ function SimpleBoardUI(client) {
parentElem.append(pieceElem);
};
-
+
/**
* Compact pieces in all positions
*/
@@ -382,7 +382,7 @@ function SimpleBoardUI(client) {
this.compactElement(this.getBarElem(model.PieceType.WHITE), this.client.player.currentPieceType === model.PieceType.WHITE ? 'top' : 'bottom');
this.compactElement(this.getBarElem(model.PieceType.BLACK), this.client.player.currentPieceType === model.PieceType.BLACK ? 'top' : 'bottom');
};
-
+
/**
* Compact pieces in specific DOM element to make them fit vertically.
* @param {number} pos - Position of point
@@ -406,19 +406,19 @@ function SimpleBoardUI(client) {
// margin in percent = 100 - ((8 / 88) * 100)
ratio = 100 - (((overflow / (itemCount - 1)) / itemHeight) * 100);
}
-
+
if (ratio > 100) {
ratio = 100;
}
if (ratio <= 0) {
ratio = 1;
}
-
+
var self = this;
element.children().each(function(i) {
var marginPercent = ratio * i;
var negAlignment = (alignment === 'top') ? 'bottom' : 'top';
-
+
$(this).css(alignment, "0");
$(this).css("margin-" + alignment, self.toFixedDown(marginPercent, 2) + "%");
@@ -435,14 +435,14 @@ function SimpleBoardUI(client) {
this.compactPosition = function (pos) {
var pointElement = this.getPointElem(pos);
var alignment;
-
+
if (this.client.player.currentPieceType === model.PieceType.BLACK) {
alignment = ((pos >= 0) && (pos <= 11)) ? 'top' : 'bottom';
}
else {
alignment = ((pos >= 12) && (pos <= 23)) ? 'top' : 'bottom';
}
-
+
this.compactElement(pointElement, alignment);
};
@@ -450,7 +450,7 @@ function SimpleBoardUI(client) {
var game = this.match.currentGame;
var i, pos;
var b;
-
+
for (pos = 0; pos < game.state.points.length; pos++) {
var point = game.state.points[pos];
for (i = 0; i < point.length; i++) {
@@ -459,8 +459,8 @@ function SimpleBoardUI(client) {
}
this.compactPosition(pos);
}
-
-
+
+
for (b = 0; b < game.state.bar.length; b++) {
var bar = game.state.bar[b];
for (i = 0; i < bar.length; i++) {
@@ -479,13 +479,13 @@ function SimpleBoardUI(client) {
this.removePieces = function () {
var game = this.match.currentGame;
-
+
for (var pos = 0; pos < game.state.points.length; pos++) {
var point = game.state.points[pos];
var pointElem = this.getPointElem(pos);
pointElem.empty();
}
-
+
this.getBarElem(model.PieceType.BLACK).empty();
this.getBarElem(model.PieceType.WHITE).empty();
};
@@ -504,24 +504,24 @@ function SimpleBoardUI(client) {
this.createPoints();
this.createPieces();
-
+
this.randomizeDiceRotation();
-
+
this.assignActions();
this.updateControls();
this.updateScoreboard();
-
+
this.compactAllPositions();
};
-
+
this.handleTurnStart = function () {
this.randomizeDiceRotation();
};
-
+
this.handleEventUndoMoves = function () {
this.notifyInfo('Player undid last move.');
};
-
+
this.handleEventGameRestart = function () {
var yourscore = this.match.score[this.client.player.currentPieceType];
var oppscore = this.match.score[this.client.otherPlayer.currentPieceType];
@@ -541,7 +541,7 @@ function SimpleBoardUI(client) {
this.randomizeDiceRotation = function () {
this.rotationAngle = [];
for (var i = 0; i < 10; i++) {
- this.rotationAngle[i] = Math.random() * 30 - 15;
+ this.rotationAngle[i] = Math.random() * 30 - 15;
}
};
@@ -555,7 +555,7 @@ function SimpleBoardUI(client) {
$('#menu-undo').hide();
return;
}
-
+
var game = this.match.currentGame;
$('#btn-roll').toggle(
@@ -565,7 +565,7 @@ function SimpleBoardUI(client) {
(!model.Game.diceWasRolled(game)) &&
(!game.turnConfirmed)
);
-
+
var canConfirmMove =
game.hasStarted &&
(!game.isOver) &&
@@ -580,10 +580,10 @@ function SimpleBoardUI(client) {
model.Game.isPlayerTurn(game, this.client.player) &&
model.Game.diceWasRolled(game) &&
(!game.turnConfirmed);
-
+
$('#btn-confirm').toggle(canConfirmMove);
$('#btn-undo').toggle(canConfirmMove);
-
+
$('#menu-resign').toggle(game.hasStarted && (!game.isOver));
$('#menu-undo').toggle(canUndoMove);
@@ -602,12 +602,12 @@ function SimpleBoardUI(client) {
console.log('Game:', game);
console.log('Player:', this.client.player);
};
-
+
this.updateScoreboard = function () {
if ((!this.match) || (!this.match.currentGame)) {
return;
}
-
+
var isInMatch = (this.match.currentGame);
var matchText = (isInMatch) ?
'Match "' + this.rule.title + '", ' + this.match.length + '/' + this.match.length
@@ -619,7 +619,7 @@ function SimpleBoardUI(client) {
'Match has not been started';
$('#match-state').text(matchText);
$('#match-state').attr('title', matchTextTitle);
-
+
var yourscore = this.match.score[this.client.player.currentPieceType];
$('#yourscore').text(yourscore);
@@ -631,42 +631,42 @@ function SimpleBoardUI(client) {
$('#oppscore').text('');
}
};
-
+
this.showGameEndMessage = function (winner, resigned) {
$('#game-result-overlay').show();
-
+
var result = winner.id === this.client.player.id;
var message;
var matchState;
-
+
if (resigned) {
message = (result) ? 'Other player resigned!' : 'You resigned.';
}
else {
message = (result) ? 'You WON!' : 'You lost.';
}
-
+
matchState = 'Match standing ';
if (this.match.isOver) {
message += message = ' Match is over.';
matchState = 'Match result ';
}
-
+
var color = (result) ? 'green' : 'red';
-
+
$('.game-result').css('color', color);
$('.game-result .message').html(message);
$('.game-result .state').html(matchState);
-
+
var yourscore = this.match.score[this.client.player.currentPieceType];
var oppscore = this.match.score[this.client.otherPlayer.currentPieceType];
$('.game-result .yourscore').text(yourscore);
$('.game-result .oppscore').text(oppscore);
-
+
$('.game-result .text').each(function () {
fitText($(this));
});
-
+
if (resigned) {
this.notifyInfo('Other player resigned from game');
}
@@ -682,24 +682,24 @@ function SimpleBoardUI(client) {
this.updateDie = function (dice, index, type) {
var color = (type === model.PieceType.BLACK) ? 'black' : 'white';
var id = '#die' + index;
-
+
// Set text
$(id).text(dice.values[index]);
-
+
// Change image
$(id).removeClass('digit-1-white digit-2-white digit-3-white digit-4-white digit-5-white digit-6-white digit-1-black digit-2-black digit-3-black digit-4-black digit-5-black digit-6-black played');
$(id).addClass('digit-' + dice.values[index] + '-' + color);
if (dice.movesLeft.length === 0) {
$(id).addClass('played');
}
-
+
var angle = this.rotationAngle[index];
$(id).css('transform', 'rotate(' + angle + 'deg)');
};
/**
* Recreate DOM elements representing dice and render them in dice container.
- * Player's dice are shown in right pane. Other player's dice are shown in
+ * Player's dice are shown in right pane. Other player's dice are shown in
* left pane.
* @param {Dice} dice - Dice to render
* @param {number} index - Index of dice value in array
@@ -719,12 +719,12 @@ function SimpleBoardUI(client) {
else {
diceElem = $('#dice-left');
}
-
+
for (var i = 0; i < dice.values.length; i++) {
diceElem.append('');
this.updateDie(dice, i, type);
}
-
+
var self = this;
$('.dice .die').unbind('click');
$('.dice .die').click(function (e) {
@@ -758,7 +758,7 @@ function SimpleBoardUI(client) {
// all of them comfortly
}
};
-
+
this.playMoveAction = function (action) {
if (!action.piece) {
throw new Error('No piece!');
@@ -774,7 +774,7 @@ function SimpleBoardUI(client) {
this.compactPosition(srcPointElem.data('position'));
this.compactPosition(dstPointElem.data('position'));
};
-
+
this.playRecoverAction = function (action) {
if (!action.piece) {
throw new Error('No piece!');
@@ -790,7 +790,7 @@ function SimpleBoardUI(client) {
this.compactElement(srcPointElem, action.piece.type === this.client.player.currentPieceType ? 'top' : 'bottom');
this.compactPosition(dstPointElem.data('position'));
};
-
+
this.playHitAction = function (action) {
if (!action.piece) {
throw new Error('No piece!');
@@ -806,7 +806,7 @@ function SimpleBoardUI(client) {
this.compactPosition(srcPointElem.data('position'));
this.compactElement(dstPointElem, action.piece.type === this.client.player.currentPieceType ? 'top' : 'bottom');
};
-
+
this.playBearAction = function (action) {
if (!action.piece) {
throw new Error('No piece!');
@@ -819,7 +819,7 @@ function SimpleBoardUI(client) {
this.compactPosition(srcPointElem.data('position'));
};
-
+
/**
* Compact pieces after UI was resized
*/
diff --git a/lib/model.js b/lib/model.js
index 11fd59d..97a67f6 100644
--- a/lib/model.js
+++ b/lib/model.js
@@ -86,7 +86,7 @@ Utils.removeItem = function (array, item) {
*
* Example:
* [6, 4, 1] becomes [4, 1, 6]
- *
+ *
* @param {Array} array - Array of elements
*/
Utils.rotateLeft = function (array) {
@@ -95,7 +95,7 @@ Utils.rotateLeft = function (array) {
/**
* Create shallow copy of object.
- *
+ *
* @param {Object} oldObj - Object to copy
* @returns {Object} - Shallow copy of object
*/
@@ -112,7 +112,7 @@ Utils.shallowCopy = function (oldObj) {
/**
* Create deep copy of a value object.
* The object should have no functions/methods.
- *
+ *
* @param {Object} oldObj - Object to copy
* @returns {Object} - Deep copy of object
*/
@@ -177,7 +177,7 @@ function Random() {
*/
Random.get = function() {
// TODO: replace with quality random generator
-
+
// Combine Math random generator with crypto one
var buffer = crypto.randomBytes(1);
var value = buffer.readUInt8(0);
@@ -186,7 +186,7 @@ Random.get = function() {
value = 255;
}
var k = value / 256;
-
+
return (Math.floor(k * 6) + 1);
};
@@ -238,7 +238,7 @@ function Dice() {
* @type {Array}
*/
this.movesLeft = [];
-
+
/**
* After a piece is moved, the value of the die used is added to movesPlayed array.
* @type {Array}
@@ -252,7 +252,7 @@ function Dice() {
*/
Dice.roll = function() {
var dice = new Dice();
-
+
dice.values[0] = Random.get();
dice.values[1] = Random.get();
dice.values.sort(function (a, b) { return b - a; });
@@ -303,7 +303,7 @@ Dice.getRemainingMoves = function (dice) {
remaining.push(dice.moves[i]);
}
}
-
+
return remaining;
};
@@ -611,7 +611,7 @@ function Player() {
* @type {string}
*/
this.name = '';
-
+
/**
* Reference to current match
* @type {Match}
@@ -683,7 +683,7 @@ function Game() {
* @type {boolean}
*/
this.isOver = false;
-
+
/**
* Number (index) of turn
* @type {number}
@@ -854,25 +854,25 @@ function Match() {
* @type {string}
*/
this.ruleName = '';
-
+
/**
* Match length - the score needed to win the match.
* @type {number}
*/
this.length = 5;
-
+
/**
* Score of players for current match
* @type {Array}
*/
this.score = [];
-
+
/**
* Current game
* @type {Game}
*/
this.currentGame = null;
-
+
/**
* Is match over
* @type {boolean}
@@ -973,7 +973,9 @@ var MoveActionType = {
/** HIT: Hit opponent's piece and sent it to bar */
HIT: 'hit',
/** BEAR: Bear piece - move it outside the board */
- BEAR: 'bear'
+ BEAR: 'bear',
+ /** UP: Move vertically to pass a tower */
+ UP: 'up',
};
/**
diff --git a/lib/rules/RuleUsUpgammon.js b/lib/rules/RuleUsUpgammon.js
index 7322dbd..3f44990 100644
--- a/lib/rules/RuleUsUpgammon.js
+++ b/lib/rules/RuleUsUpgammon.js
@@ -50,7 +50,8 @@ function RuleUsUpgammon() {
model.MoveActionType.MOVE,
model.MoveActionType.BEAR,
model.MoveActionType.HIT,
- model.MoveActionType.RECOVER
+ model.MoveActionType.RECOVER,
+ model.MoveActionType.UP,
];
}
@@ -207,7 +208,7 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
// 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)) {
/*
@@ -233,11 +234,11 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
+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;
@@ -245,7 +246,7 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
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
);
@@ -253,7 +254,7 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
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
);
@@ -391,7 +392,7 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
actionList = [];
return actionList;
}
-
+
return actionList;
};
From a35453b10df6997a6c20413de95461290c537f25 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 17:16:25 +0100
Subject: [PATCH 05/13] gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index c1f2cba..bdb7604 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ nbproject
/app/server/db
npm-debug.log*
*.*~*
+package-lock.json
From fc673dd1cf0c453d2b465c63423121906bfca529 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 20:11:04 +0100
Subject: [PATCH 06/13] can move up!
---
.vscode/launch.json | 26 ++++
app/browser/js/SimpleBoardUI.js | 33 ++++-
app/server/server.js | 219 +++++++++++++++++++++++---------
lib/client.js | 115 ++++++++++++-----
lib/comm.js | 2 +
lib/model.js | 7 +
lib/rules/RuleUsUpgammon.js | 159 +++++++++++++++--------
lib/rules/rule.js | 121 +++++++++++-------
8 files changed, 489 insertions(+), 193 deletions(-)
create mode 100644 .vscode/launch.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..c9dcf18
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,26 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Run Server",
+ "program": "${workspaceFolder}/app/server/server.js",
+ "request": "launch",
+ "skipFiles": [
+ "/**"
+ ],
+ "type": "node"
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Launch Program",
+ "skipFiles": [
+ "/**"
+ ],
+ "program": "${file}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/browser/js/SimpleBoardUI.js b/app/browser/js/SimpleBoardUI.js
index 7424c3b..b2d4f32 100644
--- a/app/browser/js/SimpleBoardUI.js
+++ b/app/browser/js/SimpleBoardUI.js
@@ -137,6 +137,7 @@ function SimpleBoardUI(client) {
/**
* Handles clicking on a point (position)
+ * Add [SHIFT] to move UP
*/
this.handlePointClick = function (e) {
var self = e.data;
@@ -160,13 +161,18 @@ function SimpleBoardUI(client) {
var position = $(e.currentTarget).data('position');
var piece = self.getTopPiece(position);
if (piece) {
- self.client.reqMove(piece, steps);
+ // If shift key was pressed, move UP by height=steps
+ if (e.shiftKey === true) {
+ self.client.reqUp(piece, steps);
+ } else {
+ self.client.reqMove(piece, steps);
+ }
}
e.preventDefault();
};
/**
- * Handles clicking on a point (position)
+ * Handles clicking on bar
*/
this.handleBarClick = function (e) {
var self = e.data;
@@ -419,6 +425,14 @@ function SimpleBoardUI(client) {
var marginPercent = ratio * i;
var negAlignment = (alignment === 'top') ? 'bottom' : 'top';
+ // push up last piece if height override is set
+ if (i === itemCount - 1) {
+ const height = $(this).data('height');
+ if (height) {
+ marginPercent = ratio * (i + height);
+ }
+ $(this).removeData('height');
+ }
$(this).css(alignment, "0");
$(this).css("margin-" + alignment, self.toFixedDown(marginPercent, 2) + "%");
@@ -753,6 +767,9 @@ function SimpleBoardUI(client) {
else if (action.type === model.MoveActionType.BEAR) {
this.playBearAction(action);
}
+ else if (action.type === model.MoveActionType.UP) {
+ this.playUpAction(action);
+ }
// TODO: Make sure actions are played back slow enough for player to see
// all of them comfortly
@@ -820,6 +837,18 @@ function SimpleBoardUI(client) {
this.compactPosition(srcPointElem.data('position'));
};
+ this.playUpAction = function (action) {
+ if (!action.piece) {
+ throw new Error('No piece!');
+ }
+
+ var pieceElem = this.getPieceElem(action.piece);
+ var srcPointElem = pieceElem.parent();
+ pieceElem.data('height', action.to);
+
+ this.compactPosition(srcPointElem.data('position'));
+ };
+
/**
* Compact pieces after UI was resized
*/
diff --git a/app/server/server.js b/app/server/server.js
index c7ef000..1091593 100644
--- a/app/server/server.js
+++ b/app/server/server.js
@@ -30,7 +30,7 @@ function Server() {
* @type {Player[]}
*/
this.players = [];
-
+
/**
* List of all matches
* @type {Match[]}
@@ -48,7 +48,7 @@ function Server() {
* @type {{rulePath: string, enabledRules: string[]}}
*/
this.config = require('./config');
-
+
/**
* Load enabled rules
*/
@@ -58,14 +58,14 @@ function Server() {
require(this.config.rulePath + ruleName + '.js');
}
};
-
+
/**
* Save server state to database, in order to be able to resume active games later
*/
this.snapshotServer = function () {
if (db) {
console.log("Saving server state...");
-
+
var players = db.collection('players');
players.remove();
players.insert(this.players);
@@ -77,7 +77,7 @@ function Server() {
console.log("State saved.");
}
};
-
+
/**
* Load saved server state from database
*/
@@ -130,16 +130,16 @@ function Server() {
console.log("State restored.");
}
};
-
+
/**
* Run server instance
*/
this.run = function () {
/** Reference to server instance */
var self = this;
-
+
this.loadRules();
-
+
this.restoreServer();
expressServer.use(express.static(path.join(__dirname, '../browser')));
@@ -157,7 +157,7 @@ function Server() {
console.log(e);
}
});
-
+
// Subscribe for client requests:
var m = comm.Message;
var messages = [
@@ -168,6 +168,7 @@ function Server() {
m.JOIN_MATCH,
m.ROLL_DICE,
m.MOVE_PIECE,
+ m.UP_PIECE,
m.CONFIRM_MOVES,
m.UNDO_MOVES,
m.RESIGN_GAME,
@@ -199,7 +200,7 @@ function Server() {
console.log('listening on *:' + port);
});
};
-
+
/**
* Get match object associated with a socket
* @param {Socket} socket - Client's socket
@@ -235,7 +236,7 @@ function Server() {
this.getSocketRule = function (socket) {
return socket.rule;
};
-
+
/**
* Associate match object with socket
* @param {Socket} socket - Client's socket
@@ -279,7 +280,8 @@ function Server() {
* @param {Object} params - Object map with message parameters
*/
this.sendMessage = function (socket, msg, params) {
- console.log('Sending message ' + msg + ' to client ' + socket.id);
+ const timestamp = new Date().toTimeString();
+ console.log('Sending message ' + msg + ' to client ' + socket.id + ' @ ' + timestamp);
socket.emit(msg, params);
};
@@ -330,13 +332,13 @@ function Server() {
*/
this.handleDisconnect = function (socket) {
console.log('Client disconnected');
-
+
// DONE: remove this client from the waiting queue
var player = this.getSocketPlayer(socket);
if (!player) {
return;
}
-
+
this.queueManager.removeFromAll(player);
};
@@ -352,7 +354,7 @@ function Server() {
var reply = {
'result': false
};
-
+
// Return client's sequence number back. Client uses this number
// to find the right callback that should be executed on message reply.
if (params.clientMsgSeq) {
@@ -380,6 +382,9 @@ function Server() {
else if (msg === comm.Message.MOVE_PIECE) {
reply.result = this.handleMovePiece(socket, params, reply);
}
+ else if (msg === comm.Message.UP_PIECE) {
+ reply.result = this.handleUpPiece(socket, params, reply);
+ }
else if (msg === comm.Message.CONFIRM_MOVES) {
reply.result = this.handleConfirmMoves(socket, params, reply);
}
@@ -408,7 +413,7 @@ function Server() {
// First send reply
this.sendMessage(socket, msg, reply);
-
+
// After that execute provided sendAfter callback. The callback
// allows any additional events to be sent after the reply
// has been sent.
@@ -416,11 +421,11 @@ function Server() {
{
// Execute provided callback
reply.sendAfter();
-
+
// Remove it from reply, it does not need to be sent to client
delete reply.sendAfter;
}
-
+
this.snapshotServer();
};
@@ -434,7 +439,7 @@ function Server() {
*/
this.handleCreateGuest = function (socket, params, reply) {
console.log('Creating guest player');
-
+
var player = null;
if (!this.getSocketPlayer(socket) && params && params.playerID) {
player = this.getPlayerByID(params.playerID);
@@ -452,11 +457,11 @@ function Server() {
console.log('Player ID found in cookie: ' + playerID);
console.log(player);
}
-
+
if (player) {
// Player already exists, but has been disconnected
var match = this.getMatchByID(player.currentMatch);
-
+
// If there is a pending match, use existing player,
// else create a new player object
if (match && !match.isOver)
@@ -477,16 +482,16 @@ function Server() {
}
);
};
-
+
reply.player = player;
reply.reconnected = true;
-
+
console.log('Player: ', player);
- return true;
+ return true;
}
}
-
+
// New player will be created
player = model.Player.createNew();
player.name = 'Player ' + player.id;
@@ -524,7 +529,7 @@ function Server() {
return true;
};
-
+
/**
* Handle client's request to play a random match.
* If there is another player waiting in queue, start a match
@@ -539,7 +544,7 @@ function Server() {
*/
this.handlePlayRandom = function (socket, params, reply) {
console.log('Play random match');
-
+
var player = this.getSocketPlayer(socket);
if (!player) {
reply.errorMessage = 'Player not found!';
@@ -552,36 +557,36 @@ function Server() {
}
var popResult = this.queueManager.popFromRandom(params.ruleName);
-
+
otherPlayer = popResult.player;
// TODO: Make sure otherPlayer has not disconnected while waiting.
// If that is the case, pop another player from the queue.
-
+
if (otherPlayer) {
if (params.ruleName === '.*')
{
params.ruleName = popResult.ruleName;
}
-
+
if (params.ruleName === '.*')
{
params.ruleName = model.Utils.getRandomElement(this.config.enabledRules);
}
-
+
// Start a new match with this other player
var rule = model.Utils.loadRule(params.ruleName);
var match = model.Match.createNew(rule);
-
+
otherPlayer.currentMatch = match.id;
otherPlayer.currentPieceType = model.PieceType.WHITE;
model.Match.addHostPlayer(match, otherPlayer);
-
+
player.currentMatch = match.id;
player.currentPieceType = model.PieceType.BLACK;
model.Match.addGuestPlayer(match, player);
-
+
this.matches.push(match);
-
+
var game = model.Match.createNewGame(match, rule);
game.hasStarted = true;
game.turnPlayer = otherPlayer;
@@ -590,11 +595,11 @@ function Server() {
// Assign match and rule objects to sockets of both players
this.setSocketMatch(socket, match);
this.setSocketRule(socket, rule);
-
+
var otherSocket = this.clients[otherPlayer.socketID];
this.setSocketMatch(otherSocket, match);
this.setSocketRule(otherSocket, rule);
-
+
// Remove players from waiting queue
this.queueManager.remove(player);
this.queueManager.remove(otherPlayer);
@@ -603,7 +608,7 @@ function Server() {
reply.host = otherPlayer;
reply.guest = player;
reply.ruleName = params.ruleName;
-
+
var self = this;
reply.sendAfter = function () {
self.sendMatchMessage(
@@ -614,13 +619,13 @@ function Server() {
}
);
};
-
+
return true;
}
else {
// Put player in queue, and wait for another player
this.queueManager.addToRandom(player, params.ruleName);
-
+
reply.isWaiting = true;
return true;
}
@@ -657,7 +662,7 @@ function Server() {
player.currentMatch = match.id;
player.currentPieceType = model.PieceType.WHITE;
this.matches.push(match);
-
+
var game = model.Match.createNewGame(match, rule);
this.setSocketMatch(socket, match);
@@ -686,7 +691,7 @@ function Server() {
reply.errorMessage = 'Match with ID ' + params.matchID + ' not found!';
return false;
}
-
+
var match = this.getMatchByID(params.matchID);
if (!match) {
reply.errorMessage = 'Match with ID ' + params.matchID + ' not found!';
@@ -751,9 +756,9 @@ function Server() {
var match = this.getSocketMatch(socket);
var player = this.getSocketPlayer(socket);
var rule = this.getSocketRule(socket);
-
+
var game = match.currentGame;
-
+
if (!game) {
reply.errorMessage = 'Match with ID ' + match.id + ' has no current game!';
return false;
@@ -811,14 +816,14 @@ function Server() {
var match = this.getSocketMatch(socket);
var player = this.getSocketPlayer(socket);
var rule = this.getSocketRule(socket);
-
+
console.log('Piece:', params.piece);
-
+
if (!params.piece) {
reply.errorMessage = 'No piece selected!';
return false;
}
-
+
if (!match.currentGame) {
reply.errorMessage = 'Match created, but current game is null!';
return false;
@@ -844,9 +849,9 @@ function Server() {
try {
rule.applyMoveActions(match.currentGame.state, actionList);
rule.markAsPlayed(match.currentGame, params.steps);
-
+
match.currentGame.moveSequence++;
-
+
reply.piece = params.piece;
reply.type = params.type;
reply.steps = params.steps;
@@ -864,7 +869,7 @@ function Server() {
}
);
- return true;
+ return true;
}
catch (e) {
reply.piece = params.piece;
@@ -875,11 +880,99 @@ function Server() {
if (process.env.DEBUG) {
throw e;
}
-
+
return false;
}
};
+/**
+ * Handle client's request to UP a piece.
+ * @param {Socket} socket - Client socket
+ * @param {Object} params - Request parameters
+ * @param {number} params.piece - Piece to move
+ * @param {number} params.height - Number of steps to move in height
+ * @param {PieceType} params.type - Type of piece
+ * @param {Object} reply - Object to be send as reply
+ * @returns {boolean} - Returns true if message have been processed
+ * successfully and a reply should be sent.
+ */
+this.handleUpPiece = function (socket, params, reply) {
+ console.log('Moving UP a piece', params);
+
+ var match = this.getSocketMatch(socket);
+ var player = this.getSocketPlayer(socket);
+ var rule = this.getSocketRule(socket);
+
+ console.log('Piece:', params.piece);
+
+ if (!params.piece) {
+ reply.errorMessage = 'No piece selected!';
+ return false;
+ }
+
+ if (!match.currentGame) {
+ reply.errorMessage = 'Match created, but current game is null!';
+ return false;
+ }
+
+ if (params.moveSequence < match.currentGame.moveSequence) {
+ reply.errorMessage = 'This move has already been played!';
+ return false;
+ }
+
+ // First, check status of the game: if game was started, if it is player's turn, etc.
+ if (!rule.validateUpMove(match.currentGame, player, params.piece, params.height)) {
+ reply.errorMessage = 'Requested UP move is not valid!';
+ return false;
+ }
+
+ var actionList = rule.getMoveActions(match.currentGame.state, params.piece, params.height, model.MoveActionType.UP);
+ if (actionList.length === 0) {
+ reply.errorMessage = 'Requested move is not allowed!';
+ return false;
+ }
+
+ try {
+ rule.applyMoveActions(match.currentGame.state, actionList);
+ rule.markAsPlayed(match.currentGame, params.height);
+
+ match.currentGame.moveSequence++;
+
+ reply.piece = params.piece;
+ reply.type = params.type;
+ reply.steps = params.steps;
+ reply.height = params.height;
+ reply.moveActionList = actionList;
+
+ this.sendMatchMessage(
+ match,
+ comm.Message.EVENT_PIECE_UP,
+ {
+ 'match': match,
+ 'piece': params.piece,
+ 'type': params.type,
+ 'steps': params.steps,
+ 'height': params.steps,
+ 'moveActionList': actionList
+ }
+ );
+
+ return true;
+ }
+ catch (e) {
+ reply.piece = params.piece;
+ reply.type = params.type;
+ reply.steps = params.steps;
+ reply.moveActionList = [];
+
+ if (process.env.DEBUG) {
+ throw e;
+ }
+
+ return false;
+ }
+};
+
/**
* Handle client's request to confirm moves made in current turn
* @param {Socket} socket - Client socket
@@ -890,7 +983,7 @@ function Server() {
*/
this.handleConfirmMoves = function (socket, params, reply) {
console.log('Confirming piece movement', params);
-
+
var self = this;
var match = this.getSocketMatch(socket);
@@ -901,9 +994,9 @@ function Server() {
reply.errorMessage = 'Confirming moves is not allowed!';
return false;
}
-
+
var otherPlayer = (model.Match.isHost(match, player)) ? match.guest : match.host;
-
+
console.log('CONFIRM MOVES');
// Check if player has won
if (rule.hasWon(match.currentGame.state, player)) {
@@ -961,7 +1054,7 @@ function Server() {
return true;
};
-
+
/**
* Handle client's request to resign from current game (game only, not whole match)
* @param {Socket} socket - Client socket
@@ -977,12 +1070,12 @@ function Server() {
var player = this.getSocketPlayer(socket);
var rule = this.getSocketRule(socket);
var otherPlayer = (model.Match.isHost(match, player)) ? match.guest : match.host;
-
+
this.endGame(socket, otherPlayer, true, reply);
-
+
return true;
};
-
+
/**
* Handle client's request to resign from whole match
* @param {Socket} socket - Client socket
@@ -998,9 +1091,9 @@ function Server() {
var player = this.getSocketPlayer(socket);
var rule = this.getSocketRule(socket);
var otherPlayer = (model.Match.isHost(match, player)) ? match.guest : match.host;
-
+
var self = this;
-
+
reply.sendAfter = function () {
self.sendMatchMessage(
match,
@@ -1012,10 +1105,10 @@ function Server() {
}
);
};
-
+
return true;
};
-
+
/**
* End game
* @param {Socket} socket - Client socket
@@ -1031,7 +1124,7 @@ function Server() {
var player = this.getSocketPlayer(socket);
var rule = this.getSocketRule(socket);
var otherPlayer = (model.Match.isHost(match, player)) ? match.guest : match.host;
-
+
// 1. Update score
var score = rule.getGameScore(match.currentGame.state, winner);
match.score[winner.currentPieceType] += score;
diff --git a/lib/client.js b/lib/client.js
index 2a66eba..16340be 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -27,26 +27,26 @@ function Client(config) {
* @type {Socket}
*/
this._socket = null;
-
+
/**
* Counter used to generate unique sequence number for messages in client's session
* @type {number}
*/
this._clientMsgSeq = 0;
-
+
/**
* Map of callback functions to be executed after server replies to a message
* @type {Object}
*/
this._callbackList = {};
-
+
/**
* Dictionary of arrays, containing subscriptions for reception of messages by id/type.
* The key of the dictionary is the message ID.
* The value of the dictionary is an array with callback functions to execute when message is received.
* @type {{Array}}
*/
- this._msgSubscriptions = {};
+ this._msgSubscriptions = {};
/**
* Client's player object
@@ -59,7 +59,7 @@ function Client(config) {
* @type {Player}
*/
this.otherPlayer = null;
-
+
/**
* Current match
* @type {Match}
@@ -118,7 +118,7 @@ function Client(config) {
self.handleConnect();
self.updateUI();
});
-
+
// Subscribe for other messages:
var m = comm.Message;
var messages = [
@@ -129,17 +129,19 @@ function Client(config) {
m.JOIN_MATCH,
m.ROLL_DICE,
m.MOVE_PIECE,
+ m.UP_PIECE,
m.EVENT_PLAYER_JOINED,
m.EVENT_TURN_START,
m.EVENT_DICE_ROLL,
m.EVENT_PIECE_MOVE,
+ m.EVENT_PIECE_UP,
m.EVENT_MATCH_START,
m.EVENT_GAME_OVER,
m.EVENT_MATCH_OVER,
m.EVENT_GAME_RESTART,
m.EVENT_UNDO_MOVES
];
-
+
var createHandler = function(msg){
return function(params) {
self.handleMessage(msg, params);
@@ -153,7 +155,7 @@ function Client(config) {
}
};
-
+
/**
* Message callback
*
@@ -162,7 +164,7 @@ function Client(config) {
* @param {number} clientMsgSeq - An integer.
* @param {Object} reply - Object containing reply data.
* @param {boolean} reply.result - Result of command execution
- */
+ */
/**
* Send message to server.
@@ -173,12 +175,12 @@ function Client(config) {
this.sendMessage = function (msg, params, callback) {
params = params || {};
params.clientMsgSeq = ++this._clientMsgSeq;
-
+
// Store reference to callback. It will be executed when server replies to this message
this._callbackList[params.clientMsgSeq] = callback;
-
+
console.log('Sending message ' + msg + ' with ID ' + params.clientMsgSeq);
-
+
this._socket.emit(msg, params);
};
@@ -238,12 +240,18 @@ function Client(config) {
else if (msg == comm.Message.MOVE_PIECE) {
this.handleMovePiece(params);
}
+ else if (msg == comm.Message.UP_PIECE) {
+ this.handleUpPiece(params);
+ }
else if (msg == comm.Message.EVENT_PLAYER_JOINED) {
this.handleEventPlayerJoined(params);
}
else if (msg == comm.Message.EVENT_PIECE_MOVE) {
this.handleEventPieceMove(params);
}
+ else if (msg == comm.Message.EVENT_PIECE_UP) {
+ this.handleEventPieceUp(params);
+ }
else if (msg == comm.Message.EVENT_TURN_START) {
this.handleEventTurnStart(params);
}
@@ -269,16 +277,16 @@ function Client(config) {
console.log('Unknown message!');
return;
}
-
+
if (params.clientMsgSeq) {
var callback = this._callbackList[params.clientMsgSeq];
if (callback) {
callback(msg, params.clientMsgSeq, params);
-
+
delete this._callbackList[params.clientMsgSeq];
}
}
-
+
this._notify(msg, params);
this.updateUI();
@@ -294,7 +302,7 @@ function Client(config) {
// TODO: update UI
console.log('Created guest player (ID): ' + this.player.id);
-
+
// Store player ID as cookie. It will be used to retrieve the player
// object later, if page is reloaded.
document.cookie = 'player_id=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
@@ -309,7 +317,7 @@ function Client(config) {
// TODO: update UI
console.log('List of matches (IDs): ' + params.list.length);
};
-
+
/**
* Handle reply - Start random match
* @param {Object} params - Message parameters
@@ -362,7 +370,7 @@ function Client(config) {
};
/**
- * Handle reply - Piece moved
+ * Handle reply - Piece move (user-initiated)
* @param {Object} params - Message parameters
*/
this.handleMovePiece = function (params) {
@@ -372,6 +380,17 @@ function Client(config) {
}
};
+ /**
+ * Handle reply - Piece UP (user-initiated)
+ * @param {Object} params - Message parameters
+ */
+ this.handleUpPiece = function (params) {
+ console.log('Piece UP move');
+ if (!params.result) {
+ this.boardUI.notifyError(params.errorMessage);
+ }
+ };
+
/**
* Handle event - Another player joined match
* @param {Object} params - Message parameters
@@ -388,7 +407,7 @@ function Client(config) {
*/
this.handleEventTurnStart = function (params) {
console.log('Turn start');
-
+
this.boardUI.handleTurnStart();
};
@@ -412,15 +431,28 @@ function Client(config) {
console.log('Piece moved');
this.boardUI.playActions(params.moveActionList);
};
-
+
/**
- * Handle event - Piece moved
+ * Handle event - Piece UP moved
+ * @param {Object} params - Message parameters
+ * @param {number} params.position - Position of piece being moved
+ * @param {PieceType} params.type - Type of piece being moved
+ * @param {number} params.steps - Number steps the piece is moved with (height)
+ * @param {MoveAction[]} params.moveActionList - List of actions that have to be played in UI
+ */
+ this.handleEventPieceUp = function (params) {
+ console.log('Piece UP moved');
+ this.boardUI.playActions(params.moveActionList);
+ };
+
+ /**
+ * Handle event - Start of a match
* @param {Object} params - Message parameters
* @param {number} params.match - Match that has been started
*/
this.handleEventMatchStart = function (params) {
console.log('Match started');
-
+
if (model.Match.isHost(params.match, this.player)) {
this.updatePlayer(params.match.host);
this.updateOtherPlayer(params.match.guest);
@@ -429,12 +461,12 @@ function Client(config) {
this.updatePlayer(params.match.guest);
this.updateOtherPlayer(params.match.host);
}
-
+
this.updateMatch(params.match);
this.updateRule(this.loadRule(params.match.ruleName));
this.resetBoard(this.match, this.rule);
};
-
+
/**
* Handle event - Game over. Current game is over. Prepare for next game of match, if any.
* @param {Object} params - Message parameters
@@ -444,7 +476,7 @@ function Client(config) {
console.log('Game is over. Winner:', params.winner);
this.boardUI.showGameEndMessage(params.winner, params.resigned);
};
-
+
/**
* Handle event - Match is over. Offer rematch or starting a new game.
* @param {Object} params - Message parameters
@@ -454,7 +486,7 @@ function Client(config) {
console.log('Match is over. Winner:', params.winner);
this.boardUI.showGameEndMessage(params.winner, params.resigned);
};
-
+
/**
* Handle event - Game restart. Current game in match is over. Match is not finished, so start next game.
* @param {Object} params - Message parameters
@@ -550,7 +582,7 @@ function Client(config) {
this.boardUI.updateControls();
this.boardUI.updateScoreboard();
};
-
+
/**
* Subscribe for notification on message reception
* @param {number} msgID - The type of message to subscribe for
@@ -561,7 +593,7 @@ function Client(config) {
this._msgSubscriptions[msgID].push(callback);
console.log(this._msgSubscriptions);
};
-
+
/**
* Subscribe for notification on message reception
* @param {number} msg - The ID of the message received
@@ -576,7 +608,7 @@ function Client(config) {
}
}
};
-
+
/**
* Request playing a match with random player - from waiting queue.
* @param {string} ruleName - Name of rule to use (eg. RuleBgCasual)
@@ -649,7 +681,7 @@ function Client(config) {
this.reqUndoMoves = function (callback) {
this.sendMessage(comm.Message.UNDO_MOVES, undefined, callback);
};
-
+
/**
* Resign from current game only
* @param {messageCallback} [callback] - Callback function to be called when server sends a reply
@@ -657,7 +689,7 @@ function Client(config) {
this.reqResignGame = function (callback) {
this.sendMessage(comm.Message.RESIGN_GAME, undefined, callback);
};
-
+
/**
* Resign from whole match
* @param {messageCallback} [callback] - Callback function to be called when server sends a reply
@@ -684,7 +716,26 @@ function Client(config) {
callback
);
};
-
+
+ /**
+ * Request elevating a piece up.
+ * @param {Piece} piece - Denormalized position from which a piece has to be moved
+ * @param {number} height - Number of steps to move up
+ * @param {messageCallback} [callback] - Callback function to be called when server sends a reply
+ */
+ this.reqUp = function (piece, height, callback) {
+ console.log('Move (up) sequence: ', this.match.currentGame.moveSequence);
+ this.sendMessage(
+ comm.Message.UP_PIECE,
+ {
+ 'piece': piece,
+ 'height': height,
+ 'moveSequence': this.match.currentGame.moveSequence
+ },
+ callback
+ );
+ };
+
/**
* Notify UI that DOM was rezised and UI may have to be updated
*/
diff --git a/lib/comm.js b/lib/comm.js
index c8db42f..b95a52f 100644
--- a/lib/comm.js
+++ b/lib/comm.js
@@ -24,6 +24,7 @@ var Message = {
JOIN_MATCH: 'joinMatch',
ROLL_DICE: 'rollDice',
MOVE_PIECE: 'movePiece',
+ UP_PIECE: 'upPiece',
CONFIRM_MOVES: 'confirmMoves',
UNDO_MOVES: 'undoMoves',
RESIGN_GAME: 'resignGame',
@@ -32,6 +33,7 @@ var Message = {
EVENT_TURN_START: 'eventTurnStart',
EVENT_DICE_ROLL: 'eventDiceRoll',
EVENT_PIECE_MOVE: 'eventPieceMove',
+ EVENT_PIECE_UP: 'eventPieceUp',
EVENT_MATCH_START: 'eventMatchStart',
EVENT_MATCH_OVER: 'eventMatchOver',
EVENT_GAME_OVER: 'eventGameOver',
diff --git a/lib/model.js b/lib/model.js
index 97a67f6..f692314 100644
--- a/lib/model.js
+++ b/lib/model.js
@@ -328,6 +328,12 @@ function State() {
*/
this.points = [];
+ /**
+ * Height: if a piece(s) has moved vertically, store its new height here; index is
+ * piece number
+ */
+ this.heightOverrides = [];
+
/**
* Players have separate bar places and so separate list.
* First element of array is for white pieces and second one for black.
@@ -370,6 +376,7 @@ State.clear = function(state) {
for (var i = 0; i < state.points.length; i++) {
state.points[i].length = 0;
}
+ state.heightOverrides = [];
state.whiteBar.length = 0;
state.blackBar.length = 0;
state.whiteOutside.length = 0;
diff --git a/lib/rules/RuleUsUpgammon.js b/lib/rules/RuleUsUpgammon.js
index 3f44990..f473e5e 100644
--- a/lib/rules/RuleUsUpgammon.js
+++ b/lib/rules/RuleUsUpgammon.js
@@ -25,7 +25,7 @@ function RuleUsUpgammon() {
* Full description of rule
* @type {string}
*/
- this.description = 'Most popular variant of backgammon for us.';
+ this.description = 'Backgammon in two dimensions';
/**
* Full name of country where this rule (variant) is played.
@@ -151,6 +151,54 @@ RuleUsUpgammon.prototype.denormPos = function(position, type) {
return denormPosition;
};
+/**
+ * Validate piece UP move.
+ *
+ * This is the base method for validation of moves that make a few general
+ * checks like:
+ * - Is the game started and is finished?
+ * - Is it player's turn?
+ * - Was dice rolled?
+ * - Are moves with values equal to the steps left?
+ *
+ * Descendant rules must extend this method and add additional validation checks
+ * according to the rule specifics.
+ *
+ * @memberOf Rule
+ * @param {Game} game - Game
+ * @param {Player} player - Player requesting move
+ * @param {Piece} piece - Piece to move
+ * @param {number} height - Number of steps to make forward to the first home position
+ * @returns {boolean} True if move is valid and should be allowed.
+ */
+Rule.prototype.validateUpMove = function(game, player, piece, steps) {
+ if (!this.validateTurn(game, player)) {
+ return false;
+ }
+
+ if (!model.Game.diceWasRolled(game)) {
+ console.log('Dice was not rolled!');
+ return false;
+ }
+
+ if (!model.Game.hasMove(game, steps)) {
+ console.log('No such move left!');
+ return false;
+ }
+
+ if (piece.type !== player.currentPieceType) {
+ console.log('Piece is of wrong type!');
+ return false;
+ }
+
+ if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps)) {
+ console.log('Move is restricted -- this is using old (non-UP) logic!');
+ return false;
+ }
+
+ return true;
+};
+
/**
* 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
@@ -169,11 +217,11 @@ RuleUsUpgammon.prototype.denormPos = function(position, type) {
* @memberOf RuleUsUpgammon
* @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
+ * @param {type} moveType - Type of move (MOVE or UP)
* @returns {MoveAction[]} - List of actions if move is allowed, empty list otherwise.
*/
-RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
+RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps, moveType = model.MoveActionType.MOVE) {
var actionList = [];
// Next, check conditions specific to this game rule and build the list of
@@ -334,61 +382,68 @@ RuleUsUpgammon.prototype.getMoveActions = function(state, piece, steps) {
}
}
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
- );
+ if (moveType === model.MoveActionType.UP) {
+ addAction(
+ model.MoveActionType.UP, piece, position, steps
+ )
+ } 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
+ !
+ */
- addAction(
- model.MoveActionType.MOVE, piece, position, destination
- );
+ 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) {
+ console.log('Error in RuleUsUpgammon.getMoveActions: ' + e);
actionList = [];
return actionList;
}
diff --git a/lib/rules/rule.js b/lib/rules/rule.js
index 6be2cb4..f0a2248 100644
--- a/lib/rules/rule.js
+++ b/lib/rules/rule.js
@@ -109,7 +109,7 @@ Rule.prototype.initialize = function(state) {
Rule.prototype.rollDice = function(game, values) {
// Create dice object with 2 random values
var dice = model.Dice.roll();
-
+
if (typeof values !== "undefined") {
dice.values[0] = values[0];
dice.values[1] = values[1];
@@ -126,16 +126,16 @@ Rule.prototype.rollDice = function(game, values) {
// Sort moves in descending order for convenience later in enforcing
// move rules
dice.moves.sort(function (a, b) { return b - a; });
-
+
// TODO: Put in movesLeft only moves that are playable.
var weight = this.calculateMoveWeights(game.state, dice.moves, game.turnPlayer.currentPieceType, null, true);
dice.moves = weight.playableMoves;
-
+
// Copy move values to movesLeft array. Moves will be removed from movesLeft
// after being played by player, whereas values in moves array will remain
// in case the player wants to undo his actions.
dice.movesLeft = dice.movesLeft.concat(dice.moves);
-
+
console.log('Playable moves:', weight.playableMoves);
return dice;
@@ -303,16 +303,16 @@ Rule.prototype.validateMove = function(game, player, piece, steps) {
console.log('No such move left!');
return false;
}
-
+
if (piece.type !== player.currentPieceType) {
console.log('Piece is of wrong type!');
return false;
}
-
+
if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps)) {
return false;
}
-
+
return true;
};
@@ -411,17 +411,17 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
var weight = {};
weight.max = 0;
weight.playableMoves = [];
-
+
var self = this;
-
+
var movesLeftSum = 0;
for (var i = 0; i < movesLeft.length; i++) {
movesLeftSum += movesLeft[i];
}
-
+
// TODO: Replace recursion with linear loop over a queue
// Don't check moves twice (eg. 5:2 and 2:5 for the same pieces)
-
+
function calculateBranchWeights(st, moves, id, branchSum, level, branchMoves) {
// 1. Try out all possible moves (for all of player's pieces).
// 2. Sum the move values for all resulting branches with possible moves.
@@ -429,7 +429,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
// all move values to be used.
// 4. If there are no better branches than the one chosen by the player, allow the
// move
-
+
/**
* Check if recursion should stop because a branch that allows
* the player to use all moves has been found.
@@ -445,26 +445,26 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
}
return false;
}
-
+
/** Local copy of moves left */
var movesLeft = moves.slice();
-
+
// Get steps (value) for next move
var steps = movesLeft.shift();
if (!steps) {
return;
}
-
+
//console.log('Piece type:', pieceType);
console.log(Array(level + 2).join("-") + ' Begin consider move ' + steps);
-
+
// Iterate all of player's pieces
for (var p = 0; p < st.pieces[pieceType].length; p++) {
var piece = st.pieces[pieceType][p];
if ((!piece) || (piece.type !== pieceType)) {
continue;
}
-
+
// If a root piece has been specified, check
// only the branches that start at this piece.
// Ignore other branches
@@ -473,12 +473,12 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
continue;
}
}
-
+
// Do not check pieces that are already outside the board
if (model.State.isPieceOutside(st, piece)) {
continue;
}
-
+
console.log('Piece ID', piece.id);
console.log('Outside', st.outside);
if (piece.id === 5) {
@@ -489,7 +489,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
console.log(st.outside);
console.log(pieceType);
}
-
+
// Check if the player has any pieces on bar. If that is the
// case only pieces on the bar can be moved
if (model.State.havePiecesOnBar(st, pieceType)) {
@@ -514,7 +514,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
// Make a deep copy of the state. Moves will be applied to the copy. The
// copy will be passed one level down - to the move (next node of the branch).
var tempState = model.Utils.deepCopy(st);
-
+
// Check if current piece can be moved
var actions = self.getMoveActions(tempState, piece, steps);
//console.log('Piece ID', piece.id);
@@ -522,14 +522,14 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
//console.log('No actions, next piece');
continue;
}
-
+
// If yes, apply the move action to the temporary state.
//console.log('Actions', actions);
self.applyMoveActions(tempState, actions);
-
+
var tempMoves = branchMoves.slice();
tempMoves.push(steps);
-
+
// If we are still at level 0, create a new branch
var pieceID = (id !== 0) ? id: piece.id;
if (!weight[pieceID]) {
@@ -538,24 +538,24 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
moves: []
};
}
-
+
// Keep track of the maximum weight for this branch. The branch starts with the first
// piece moved (the root node) and check if the moves in this branch (in the nodes so far,
// it might extend on next recursion) have a total sum greather than the one saved for this
// branch (the root node - associated with the first piece being moved).
-
+
var w = branchSum + steps;
-
+
if (w > weight[pieceID].max) {
weight[pieceID].max = w;
}
weight[pieceID].moves = tempMoves;
-
+
if (w > weight.max) {
weight.max = w;
weight.playableMoves = tempMoves;
}
-
+
if (shouldStop()) {
return;
}
@@ -564,19 +564,19 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
calculateBranchWeights(tempState, movesLeft, pieceID, w, level + 1, tempMoves);
}
}
-
+
console.log(Array(level + 2).join("-") + ' End consider move ' + steps);
}
-
+
console.time('Recursion time');
-
+
// Simulate moving the piece with all dice values, starting from highest die value
// (eg. for dice 5:3 try moving 5 first and after that 3). Try this for all pieces
// (multiple branches)
console.log('moves 1:', movesLeft);
calculateBranchWeights(state, movesLeft, 0, 0, 0, []);
console.log('Intermediate weight:', weight);
-
+
// Then try playing from lowest die value first
// (eg. for dice 5:3 try moving 3 first and after that 5). Also try this for all pieces
// (more branches)
@@ -589,9 +589,9 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
}
console.log('Final weight:', weight);
-
+
console.timeEnd('Recursion time');
-
+
return weight;
};
@@ -616,10 +616,10 @@ Rule.prototype.isMoveActionRestricted = function(state, movesLeft, piece, steps)
// all move values to be used.
// 4. If there are no better branches than the one chosen by the player, allow the
// move
-
+
var weight = this.calculateMoveWeights(state, movesLeft, piece.type, piece, false);
var maxWeight = weight.max;
-
+
if ((!weight[piece.id]) || (weight[piece.id].max < maxWeight)) {
console.log('There is better move. Piece weight:', weight[piece.id]);
return true;
@@ -683,6 +683,9 @@ Rule.prototype.applyMoveActions = function(state, actionList) {
else if (action.type === model.MoveActionType.BEAR) {
this.bear(state, action.piece);
}
+ else if (action.type === model.MoveActionType.UP) {
+ this.up(state, action.piece, action.to);
+ }
}
};
@@ -699,6 +702,7 @@ Rule.prototype.place = function (state, number, type, position) {
var piece = new model.Piece(type, state.nextPieceID);
state.pieces[type].push(piece);
state.points[position].push(piece);
+ state.heightOverrides[state.nextPieceID] = 0;
state.nextPieceID++;
}
};
@@ -714,7 +718,7 @@ Rule.prototype.place = function (state, number, type, position) {
Rule.prototype.move = function(state, piece, toPos) {
// Find the current position of the piece
var fromPos = model.State.getPiecePos(state, piece);
-
+
var topPiece = state.points[fromPos].pop();
if (!topPiece) {
throw new Error("No piece found at position " + parseInt(fromPos) + " !");
@@ -739,7 +743,7 @@ Rule.prototype.move = function(state, piece, toPos) {
Rule.prototype.bear = function (state, piece) {
// Find the current position of the piece
var fromPos = model.State.getPiecePos(state, piece);
-
+
var topPiece = state.points[fromPos].pop();
if (!topPiece) {
throw new Error("No piece found at position " + parseInt(fromPos) + " !");
@@ -754,6 +758,35 @@ Rule.prototype.bear = function (state, piece) {
state.outside[piece.type].push(topPiece);
};
+/**
+ * UP piece - remove from board and place outside
+ * @memberOf Rule
+ * @param {State} state - Board state
+ * @param {Piece} piece - Piece to move UP
+ * @param {int} steps - How high to move up
+ * @throws Throws an error if there is no piece at fromPos or piece is of wrong type
+ */
+Rule.prototype.up = function (state, piece, steps) {
+ // Find the current position of the piece
+ var fromPos = model.State.getPiecePos(state, piece);
+
+ var topPiece = state.points[fromPos].pop();
+ if (!topPiece) {
+ throw new Error("No piece found at position " + parseInt(fromPos) + " !");
+ }
+ // put it back because we won't be physically removing it
+ state.points[fromPos].push(topPiece);
+
+ if (topPiece.id !== piece.id) {
+ console.log(fromPos, topPiece);
+ throw new Error("The top piece at position " + fromPos + " is different than the one the player wants to move!");
+ }
+
+ // set the HEIGHT appropriately
+ state.heightOverrides[piece] = state.points[fromPos].length + steps;
+
+};
+
/**
* Hit piece - send piece to bar
* @memberOf Rule
@@ -764,7 +797,7 @@ Rule.prototype.bear = function (state, piece) {
Rule.prototype.hit = function (state, piece) {
// Find the current position of the piece
var fromPos = model.State.getPiecePos(state, piece);
-
+
var topPiece = state.points[fromPos].pop();
if (!topPiece) {
throw new Error("No piece found at position " + parseInt(fromPos) + " !");
@@ -832,7 +865,7 @@ Rule.prototype.hasWon = function (state, player) {
/**
* Check game state and determine how much points the player
* should be awared for this state.
- *
+ *
* If opponent player has not borne any pieces, award 2 points.
* If opponent has not borne any pieces, and still has pieces in home field of player, award 3 points.
* In all other cases award 1 point.
@@ -847,7 +880,7 @@ Rule.prototype.getGameScore = function (state, player) {
model.PieceType.BLACK
:
model.PieceType.WHITE;
-
+
if (state.outside[oppType].length <= 0) {
// The opponent has not borne any pieces, so we need to check
// if the player should be awarded 2 or 3 points
@@ -871,8 +904,8 @@ Rule.prototype.getGameScore = function (state, player) {
* Start next turn:
* 1. Reset turn
* 2. Change players
- * 3. Roll new dice
- *
+ * 3. Roll new dice
+ *
* @memberOf Rule
* @param {Match} match - Match
*/
From 5466516d51587008a42c3d35e6647c8dca77627f Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 20:12:17 +0100
Subject: [PATCH 07/13] height offset
---
app/browser/js/SimpleBoardUI.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/browser/js/SimpleBoardUI.js b/app/browser/js/SimpleBoardUI.js
index b2d4f32..78a9cf6 100644
--- a/app/browser/js/SimpleBoardUI.js
+++ b/app/browser/js/SimpleBoardUI.js
@@ -429,7 +429,7 @@ function SimpleBoardUI(client) {
if (i === itemCount - 1) {
const height = $(this).data('height');
if (height) {
- marginPercent = ratio * (i + height);
+ marginPercent = ratio * (i + (height - 1));
}
$(this).removeData('height');
}
From dd6b8ec60956a26e80a531afec4dcf08063ee983 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 20:14:16 +0100
Subject: [PATCH 08/13] don't reset height
---
app/browser/js/SimpleBoardUI.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/browser/js/SimpleBoardUI.js b/app/browser/js/SimpleBoardUI.js
index 78a9cf6..5c4d14c 100644
--- a/app/browser/js/SimpleBoardUI.js
+++ b/app/browser/js/SimpleBoardUI.js
@@ -429,9 +429,9 @@ function SimpleBoardUI(client) {
if (i === itemCount - 1) {
const height = $(this).data('height');
if (height) {
- marginPercent = ratio * (i + (height - 1));
+ marginPercent = ratio * (i + height);
}
- $(this).removeData('height');
+ // $(this).removeData('height');
}
$(this).css(alignment, "0");
$(this).css("margin-" + alignment, self.toFixedDown(marginPercent, 2) + "%");
From 8fcbe0dbcbfc40f5d7c2722985b1c480099ee929 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 22:09:20 +0100
Subject: [PATCH 09/13] height logic
---
app/server/server.js | 2 +-
lib/rules/rule.js | 4 +++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/server/server.js b/app/server/server.js
index 1091593..eef5082 100644
--- a/app/server/server.js
+++ b/app/server/server.js
@@ -281,7 +281,7 @@ function Server() {
*/
this.sendMessage = function (socket, msg, params) {
const timestamp = new Date().toTimeString();
- console.log('Sending message ' + msg + ' to client ' + socket.id + ' @ ' + timestamp);
+ console.log(timestamp + ' - Sending message ' + msg + ' to client ' + socket.id);
socket.emit(msg, params);
};
diff --git a/lib/rules/rule.js b/lib/rules/rule.js
index f0a2248..1a53c77 100644
--- a/lib/rules/rule.js
+++ b/lib/rules/rule.js
@@ -783,7 +783,9 @@ Rule.prototype.up = function (state, piece, steps) {
}
// set the HEIGHT appropriately
- state.heightOverrides[piece] = state.points[fromPos].length + steps;
+ if (state.heightOverrides[piece] === undefined)
+ state.heightOverrides[piece] = 0;
+ state.heightOverrides[piece] += steps;
};
From 7df981c0acb787de528a47aa74273ebf40530ac8 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 22:36:33 +0100
Subject: [PATCH 10/13] fix css paths
---
app/browser/index.html | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/app/browser/index.html b/app/browser/index.html
index 2b98168..24453dd 100644
--- a/app/browser/index.html
+++ b/app/browser/index.html
@@ -4,12 +4,12 @@
-
-
+
+
-
+
-
+
-
+
@@ -98,7 +98,7 @@
Upgammon
-
+
-
+
@@ -127,12 +127,12 @@
Upgammon
-
+
Message
State 0 : 0
-
+
Waiting another player...
@@ -147,7 +147,7 @@
Upgammon
-
+
From 84a8f903a124cc423b0565e895ad1b1063091e70 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Fri, 26 Jan 2024 23:01:43 +0100
Subject: [PATCH 11/13] up move validation
---
app/server/server.js | 2 +-
lib/rules/RuleUsUpgammon.js | 2 +-
lib/rules/rule.js | 20 ++++++++++----------
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/app/server/server.js b/app/server/server.js
index eef5082..ac173f2 100644
--- a/app/server/server.js
+++ b/app/server/server.js
@@ -835,7 +835,7 @@ function Server() {
}
// First, check status of the game: if game was started, if it is player's turn, etc.
- if (!rule.validateMove(match.currentGame, player, params.piece, params.steps)) {
+ if (!rule.validateMove(match.currentGame, player, params.piece, params.steps, model.MoveActionType.MOVE)) {
reply.errorMessage = 'Requested move is not valid!';
return false;
}
diff --git a/lib/rules/RuleUsUpgammon.js b/lib/rules/RuleUsUpgammon.js
index f473e5e..f67124e 100644
--- a/lib/rules/RuleUsUpgammon.js
+++ b/lib/rules/RuleUsUpgammon.js
@@ -191,7 +191,7 @@ Rule.prototype.validateUpMove = function(game, player, piece, steps) {
return false;
}
- if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps)) {
+ if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps, model.MoveActionType.UP)) {
console.log('Move is restricted -- this is using old (non-UP) logic!');
return false;
}
diff --git a/lib/rules/rule.js b/lib/rules/rule.js
index 1a53c77..6a27b60 100644
--- a/lib/rules/rule.js
+++ b/lib/rules/rule.js
@@ -309,7 +309,7 @@ Rule.prototype.validateMove = function(game, player, piece, steps) {
return false;
}
- if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps)) {
+ if (this.isMoveActionRestricted(game.state, game.turnDice.movesLeft, piece, steps, model.MoveActionType.MOVE)) {
return false;
}
@@ -407,7 +407,7 @@ Rule.prototype.validateUndo = function(game, player) {
* @returns {Object} - Map containing maximum weight for each branch, indexed by piece ID
* and total maximum weight for all branches, accessed with 'max' index
*/
-Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, rootPiece, stopAtMax) {
+Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, rootPiece, stopAtMax, moveType) {
var weight = {};
weight.max = 0;
weight.playableMoves = [];
@@ -422,7 +422,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
// TODO: Replace recursion with linear loop over a queue
// Don't check moves twice (eg. 5:2 and 2:5 for the same pieces)
- function calculateBranchWeights(st, moves, id, branchSum, level, branchMoves) {
+ function calculateBranchWeights(st, moves, id, branchSum, level, branchMoves, moveType) {
// 1. Try out all possible moves (for all of player's pieces).
// 2. Sum the move values for all resulting branches with possible moves.
// 3. Check if the move request of the player can be used in a branch that allows
@@ -516,7 +516,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
var tempState = model.Utils.deepCopy(st);
// Check if current piece can be moved
- var actions = self.getMoveActions(tempState, piece, steps);
+ var actions = self.getMoveActions(tempState, piece, steps, moveType);
//console.log('Piece ID', piece.id);
if (actions.length === 0) {
//console.log('No actions, next piece');
@@ -561,7 +561,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
}
if (movesLeft.length > 0) {
- calculateBranchWeights(tempState, movesLeft, pieceID, w, level + 1, tempMoves);
+ calculateBranchWeights(tempState, movesLeft, pieceID, w, level + 1, tempMoves, moveType);
}
}
@@ -574,7 +574,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
// (eg. for dice 5:3 try moving 5 first and after that 3). Try this for all pieces
// (multiple branches)
console.log('moves 1:', movesLeft);
- calculateBranchWeights(state, movesLeft, 0, 0, 0, []);
+ calculateBranchWeights(state, movesLeft, 0, 0, 0, [], moveType);
console.log('Intermediate weight:', weight);
// Then try playing from lowest die value first
@@ -585,7 +585,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
movesLeft = movesLeft.slice();
movesLeft.reverse();
console.log('moves2:', movesLeft);
- calculateBranchWeights(state, movesLeft, 0, 0, 0, []);
+ calculateBranchWeights(state, movesLeft, 0, 0, 0, [], moveType);
}
console.log('Final weight:', weight);
@@ -609,7 +609,7 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
* @param {number} steps - Number of steps to move
* @returns {boolean} - Returns true if move is restricted (not allowed).
*/
-Rule.prototype.isMoveActionRestricted = function(state, movesLeft, piece, steps) {
+Rule.prototype.isMoveActionRestricted = function(state, movesLeft, piece, steps, moveType) {
// 1. Try out all possible moves (for all of player's pieces).
// 2. Sum the move values for all resulting branches with possible moves.
// 3. Check if the move request of the player can be used in a branch that allows
@@ -617,7 +617,7 @@ Rule.prototype.isMoveActionRestricted = function(state, movesLeft, piece, steps)
// 4. If there are no better branches than the one chosen by the player, allow the
// move
- var weight = this.calculateMoveWeights(state, movesLeft, piece.type, piece, false);
+ var weight = this.calculateMoveWeights(state, movesLeft, piece.type, piece, false, moveType);
var maxWeight = weight.max;
if ((!weight[piece.id]) || (weight[piece.id].max < maxWeight)) {
@@ -651,7 +651,7 @@ Rule.prototype.isMoveActionRestricted = function(state, movesLeft, piece, steps)
* @returns {MoveAction[]} - List of actions if move is allowed, empty list otherwise.
* @see {@link RuleBgCasual.getMoveActions} for an example on how to implement this method
*/
-Rule.prototype.getMoveActions = function(state, piece, steps) {
+Rule.prototype.getMoveActions = function(state, piece, steps, moveType) {
throw new Error("Abstract method!");
};
From f26eb76c08f8ea77a436e40655deaabd5bb33761 Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Sat, 27 Jan 2024 16:47:58 +0100
Subject: [PATCH 12/13] third die works
---
app/browser/js/SimpleBoardUI.js | 11 +-
lib/model.js | 23 ++--
lib/rules/rule.js | 189 ++++++++++++++++++--------------
3 files changed, 129 insertions(+), 94 deletions(-)
diff --git a/app/browser/js/SimpleBoardUI.js b/app/browser/js/SimpleBoardUI.js
index 5c4d14c..31af827 100644
--- a/app/browser/js/SimpleBoardUI.js
+++ b/app/browser/js/SimpleBoardUI.js
@@ -33,6 +33,7 @@ function SimpleBoardUI(client) {
this.container = $('#' + this.client.config.containerID);
this.container.append($('#tmpl-board').html());
this.container.append($(''));
+ this.displayPieceId = true;
this.board = $('#board');
this.fields = [];
@@ -372,7 +373,15 @@ function SimpleBoardUI(client) {
this.createPiece = function (parentElem, piece, count) {
var pieceTypeClass = piece.type === model.PieceType.WHITE ? 'white' : 'black';
- var pieceElem = $('
"
+ );
pieceElem.data('piece', piece);
parentElem.append(pieceElem);
diff --git a/lib/model.js b/lib/model.js
index f692314..5e11a97 100644
--- a/lib/model.js
+++ b/lib/model.js
@@ -216,16 +216,16 @@ function Piece(type, id) {
*/
function Dice() {
/**
- * Values of the two dice
+ * Values of the three dice
* @type {Array}
*/
- this.values = [0, 0];
+ this.values = [0, 0, 0];
/**
* List of moves the player can make. Usually moves are equal to values,
* but in most rules doubles (eg. 6:6) are played four times, instead of
* two, in which case moves array will contain four values in stead of
- * only two (eg. [6, 6, 6, 6]).
+ * only two (eg. [6, 6, 6, 6]). With three dice, if all three match then 8
* @type {Array}
*/
this.moves = [];
@@ -255,6 +255,7 @@ Dice.roll = function() {
dice.values[0] = Random.get();
dice.values[1] = Random.get();
+ dice.values[2] = Random.get();
dice.values.sort(function (a, b) { return b - a; });
return dice;
};
@@ -275,14 +276,14 @@ Dice.markAsPlayed = function (dice, move) {
throw new Error("No such move!");
};
-/**
- * Check if the dice object has double (equal) values.
- * @param {Dice} dice - New dice with random values
- * @returns {boolean} - True if dice object has dobule values, false otherwise
- */
-Dice.isDouble = function (dice) {
- return dice.values[0] === dice.values[1];
-};
+// /**
+// * Check if the dice object has double (equal) values.
+// * @param {Dice} dice - New dice with random values
+// * @returns {boolean} - True if dice object has dobule values, false otherwise
+// */
+// Dice.isDouble = function (dice) {
+// return dice.values[0] === dice.values[1] || dice.values[1] === dice.values[2] || dice.values[0] === dice.values[2];
+// };
/**
* Get remaining moves from dice object - moves that have not been played.
diff --git a/lib/rules/rule.js b/lib/rules/rule.js
index 6a27b60..c93c31c 100644
--- a/lib/rules/rule.js
+++ b/lib/rules/rule.js
@@ -113,14 +113,24 @@ Rule.prototype.rollDice = function(game, values) {
if (typeof values !== "undefined") {
dice.values[0] = values[0];
dice.values[1] = values[1];
+ dice.values[2] = values[2];
}
// Add those values to moves list - the individual moves the player has to make
dice.moves = dice.moves.concat(dice.values);
- // Dices with equal values are played four times, so add two more moves
- if (dice.moves[0] == dice.moves[1]) {
- dice.moves = dice.moves.concat(dice.values);
+ // Doubles & Triples?
+ if (dice.values[0] == dice.values[1]) {
+ dice.moves.push(dice.values[0]);
+ dice.moves.push(dice.values[1]);
+ }
+ if (dice.values[1] == dice.values[2]) {
+ dice.moves.push(dice.values[1]);
+ dice.moves.push(dice.values[2]);
+ }
+ if (dice.values[0] == dice.values[2]) {
+ dice.moves.push(dice.values[0]);
+ dice.moves.push(dice.values[2]);
}
// Sort moves in descending order for convenience later in enforcing
@@ -446,85 +456,10 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
return false;
}
- /** Local copy of moves left */
- var movesLeft = moves.slice();
-
- // Get steps (value) for next move
- var steps = movesLeft.shift();
- if (!steps) {
- return;
- }
-
- //console.log('Piece type:', pieceType);
- console.log(Array(level + 2).join("-") + ' Begin consider move ' + steps);
-
- // Iterate all of player's pieces
- for (var p = 0; p < st.pieces[pieceType].length; p++) {
- var piece = st.pieces[pieceType][p];
- if ((!piece) || (piece.type !== pieceType)) {
- continue;
- }
-
- // If a root piece has been specified, check
- // only the branches that start at this piece.
- // Ignore other branches
- if (level === 0 && rootPiece) {
- if (rootPiece.id !== piece.id) {
- continue;
- }
- }
-
- // Do not check pieces that are already outside the board
- if (model.State.isPieceOutside(st, piece)) {
- continue;
- }
-
- console.log('Piece ID', piece.id);
- console.log('Outside', st.outside);
- if (piece.id === 5) {
- console.log('TEN');
- console.log(piece.type);
- console.log(st.outside[0].length);
- console.log(st.outside[0]);
- console.log(st.outside);
- console.log(pieceType);
- }
-
- // Check if the player has any pieces on bar. If that is the
- // case only pieces on the bar can be moved
- if (model.State.havePiecesOnBar(st, pieceType)) {
- // Player can only move the top piece on the bar.
- // If there are more pieces on the bar they could be moved on next
- // move, but not on this one
- if (model.State.getBarTopPiece(st, pieceType).id !== piece.id) {
- continue;
- }
- //console.log('Bar');
- }
- else {
- // If there are no pieces on the bar, make sure this piece is the
- // top piece at its position. Only top pieces can be moved
- var pos = model.State.getPiecePos(st, piece);
- if (model.State.getTopPiece(st, pos).id !== piece.id) {
- continue;
- }
- //console.log('Pos', pos);
- }
-
- // Make a deep copy of the state. Moves will be applied to the copy. The
- // copy will be passed one level down - to the move (next node of the branch).
- var tempState = model.Utils.deepCopy(st);
-
- // Check if current piece can be moved
- var actions = self.getMoveActions(tempState, piece, steps, moveType);
- //console.log('Piece ID', piece.id);
- if (actions.length === 0) {
- //console.log('No actions, next piece');
- continue;
- }
-
+ // create a new State where we apply these actions
+ function applyTempActions (actions,tempState,id,branchSum,shouldStop,calculateBranchWeights,level,moveType) {
// If yes, apply the move action to the temporary state.
- //console.log('Actions', actions);
+ console.log('Applying actions', actions);
self.applyMoveActions(tempState, actions);
var tempMoves = branchMoves.slice();
@@ -565,6 +500,95 @@ Rule.prototype.calculateMoveWeights = function(state, movesLeft, pieceType, root
}
}
+ /** Local copy of moves left */
+ var movesLeft = moves.slice();
+
+ // Get steps (value) for next move
+ var steps = movesLeft.shift();
+ if (!steps) {
+ return;
+ }
+
+ //console.log('Piece type:', pieceType);
+ console.log(Array(level + 2).join("-") + ' Begin consider move with die=' + steps);
+
+ // Iterate all of player's pieces
+ for (var p = 0; p < st.pieces[pieceType].length; p++) {
+ var piece = st.pieces[pieceType][p];
+ if ((!piece) || (piece.type !== pieceType)) {
+ continue;
+ }
+
+ // If a root piece has been specified, check
+ // only the branches that start at this piece.
+ // Ignore other branches
+ if (level === 0 && rootPiece) {
+ if (rootPiece.id !== piece.id) {
+ continue;
+ }
+ }
+
+ // Do not check pieces that are already outside the board
+ if (model.State.isPieceOutside(st, piece)) {
+ continue;
+ }
+
+ console.log('Considering moving Piece ID', piece.id);
+ // console.log('Outside', st.outside);
+ // if (piece.id === 5) {
+ // console.log('TEN');
+ // console.log(piece.type);
+ // console.log(st.outside[0].length);
+ // console.log(st.outside[0]);
+ // console.log(st.outside);
+ // console.log(pieceType);
+ // }
+
+ // Check if the player has any pieces on bar. If that is the
+ // case only pieces on the bar can be moved
+ if (model.State.havePiecesOnBar(st, pieceType)) {
+ // Player can only move the top piece on the bar.
+ // If there are more pieces on the bar they could be moved on next
+ // move, but not on this one
+ if (model.State.getBarTopPiece(st, pieceType).id !== piece.id) {
+ continue;
+ }
+ //console.log('Bar');
+ }
+ else {
+ // If there are no pieces on the bar, make sure this piece is the
+ // top piece at its position. Only top pieces can be moved
+ var pos = model.State.getPiecePos(st, piece);
+ if (model.State.getTopPiece(st, pos).id !== piece.id) {
+ continue;
+ }
+ //console.log('Pos', pos);
+ }
+
+ // Make a deep copy of the state. Moves will be applied to the copy. The
+ // copy will be passed one level down - to the move (next node of the branch).
+ var tempState = model.Utils.deepCopy(st);
+
+ // Check if current piece can be moved either UP or MOVE
+ var moveActions = self.getMoveActions(tempState, piece, steps, model.MoveActionType.MOVE);
+
+ // If the piece can be moved
+ //console.log('Piece ID', piece.id);
+ if (moveActions.length === 0) {
+ console.log('No MOVE actions for this piece');
+ }
+ else {
+ applyTempActions(moveActions,tempState,id,branchSum,shouldStop,calculateBranchWeights,level,model.MoveActionType.MOVE);
+ }
+ var upActions = self.getMoveActions(tempState, piece, steps, model.MoveActionType.UP);
+ if (upActions.length === 0) {
+ console.log('No UP actions for this piece');
+ }
+ else {
+ applyTempActions(upActions,tempState,id,branchSum,shouldStop,calculateBranchWeights,level,model.MoveActionType.UP);
+ }
+ }
+
console.log(Array(level + 2).join("-") + ' End consider move ' + steps);
}
@@ -774,7 +798,7 @@ Rule.prototype.up = function (state, piece, steps) {
if (!topPiece) {
throw new Error("No piece found at position " + parseInt(fromPos) + " !");
}
- // put it back because we won't be physically removing it
+ // put it back because it'll remain on this point
state.points[fromPos].push(topPiece);
if (topPiece.id !== piece.id) {
@@ -783,6 +807,7 @@ Rule.prototype.up = function (state, piece, steps) {
}
// set the HEIGHT appropriately
+ // TODO(risher): move this into the Piece itself
if (state.heightOverrides[piece] === undefined)
state.heightOverrides[piece] = 0;
state.heightOverrides[piece] += steps;
From 9493bbdaeb8a2f1a9f446f94e13b30fc626be2fe Mon Sep 17 00:00:00 2001
From: Mark Risher <218038+mrisher@users.noreply.github.com>
Date: Sat, 27 Jan 2024 16:54:57 +0100
Subject: [PATCH 13/13] store height Boost in piece
---
lib/rules/rule.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lib/rules/rule.js b/lib/rules/rule.js
index c93c31c..7d99447 100644
--- a/lib/rules/rule.js
+++ b/lib/rules/rule.js
@@ -811,6 +811,9 @@ Rule.prototype.up = function (state, piece, steps) {
if (state.heightOverrides[piece] === undefined)
state.heightOverrides[piece] = 0;
state.heightOverrides[piece] += steps;
+ if (piece.heightBoost === undefined)
+ piece.heightBoost = 0;
+ piece.heightBoost += steps;
};