morrisboard
Version:
A Node.js controller for a nine men's morris board
282 lines (249 loc) • 11.2 kB
JavaScript
(function() {
const helper = require("./helper");
const sides = 4;
class MorrisBoard {
constructor(size, points) {
var self = this;
// Generate map
this.pointCount = (points.length - 1) * sides;
// Loop trough rows to set their fields
for (var currRow = 0; currRow < size; currRow++) {
// Add all fields of a row to map
this.map = this.map.concat(new Array(this.pointCount).fill(function(i) {
// Protofill a point on the board
return {
team: false,
get surroundings() {
var index = self.map.indexOf(this);
return self.getSurroundings(index);
},
get position() {
return self.getPointPosition(self.map.indexOf(this));
},
get mills() {
return self.getMills(self.map.indexOf(this));
},
get sides() {
return self.getSides(self.map.indexOf(this));
},
get line() {
return self.getLineIndex(self.map.indexOf(this));
}
};
}));
}
this.points = points;
}
// Getter that returns all rows as arrays containing point objects
get rows() {
var rows = [];
// Loop trough map with 'pointCount' as step size
// 'pointCount' represents the amount of points within a row (E.g. 8)
for (var i = 0; i < this.map.length; i += this.pointCount) {
// Push for each row a sliced version of 'map' starting ta row's start index and ending with the last point of the current row
rows.push(this.map.slice(i, i + this.pointCount));
}
return rows;
}
getSurroundings(index) {
var pos = this.getPointPosition(index);
// Get the line the point is a part of (If not, false)
var line = this.getLineIndex(index);
/*
NOTE
Please remember that the coordinate system behind this is circular. That means the "left" surrounding is not always the left you see on your screen. Also "up" and down are just in relation to the center point of the board
*/
return {
// Try to add just 1 to the current index. But if such a position would be bigger or equal as the amount of points in the row, we are looking at the last point of a row
// Therefore we have to use the first point of row as right surrounding
right: (pos.position + 1) < this.pointCount ? this.map[index + 1] : this.map[pos.row * this.pointCount + 0],
// Same here but in reverse logic with substrating. If such a position is bigger or equal to 0, it is okay. But if not, the current point seems to be the first in row
// Therefore we have use the last point of row as left surrounding
left: (pos.position - 1 >= 0) ? this.map[index - 1] : this.map[pos.row * this.pointCount + this.pointCount - 1],
up: typeof line === "number" ? ((index + this.pointCount) in this.map ? this.map[index + this.pointCount] : null) : null,
down: typeof line === "number" ? ((index - this.pointCount) in this.map ? this.map[index - this.pointCount] : null) : null
};
return row;
}
getPointIndex(row, position) {
return row * this.pointCount + position;
}
getPoint(row, position) {
return this.map[this.getPointIndex(row, position)];
}
getPointPosition(index) {
return {
row: Math.trunc(index / this.pointCount),
position: index % this.pointCount
};
}
getSides(index) {
var pos = this.getPointPosition(index);
var allSides = [
// Primary side is the first side (returns normally by calculating)
Math.trunc(pos.position / (this.pointCount / sides)),
// Secondary side is the side, that returns when the start point is one less (If this is not same as primary side, the point borders on a second side)
Math.trunc((((pos.position - 1) >= 0 ? pos.position : this.pointCount) - 1) / (this.pointCount / sides))
];
return allSides.filter((side, index) => allSides.indexOf(side, index + 1) == -1);
}
// Returns the index of the line a point is part of (If the point is part of it). If not, returns false
getLineIndex(index) {
var point = this.map[index];
var pos = this.getPointPosition(index);
var pointConnectionIndex = pos.position % (this.pointCount / sides);
var connection = this.points[pointConnectionIndex];
return connection ? pos.position : false;
}
// Returns a line's points
getLine(lineIndex) {
if (typeof lineIndex != "number") {
return false;
}
var rowsCount = this.map.length / this.pointCount;
var line = [];
for (var row = 0; row < rowsCount; row++) {
line.push(this.map[row * this.pointCount + lineIndex]);
}
return line;
}
getSide(sideIndex, row) {
// Start point is alway the leftest point on the side
var point = this.map[this.pointCount * row + sideIndex * (this.pointCount / sides)];
// Not needed yet because the search starts always at the leftest one
/*var leftPoints = [];
var neighbouringPointLeft = point;
while (neighbouringPointLeft.sides.includes(sideIndex)) {
leftPoints.push(this.map.indexOf(neighbouringPointLeft));
neighbouringPointLeft = neighbouringPointLeft.surroundings.left;
}*/
// We start at leftest point because of "left-to-right"
// Array that will be filled with points that are right of the start point
var rightPoints = [];
// Initiale start point as first point (Because the start point also a part of the side)
var neighbouringPointRight = point;
// Loop while the current point ('neighbouringPointRight') is a part of current side we are working with (Part of it's 'sides' property)
while (neighbouringPointRight.sides.includes(sideIndex)) {
// Push current point to the list
rightPoints.push(neighbouringPointRight);
// Set new point to point right from current point
neighbouringPointRight = neighbouringPointRight.surroundings.right;
}
// Point right from last point seems to be not a part of current side
// Side list is completed
// Returning side list
return rightPoints;
}
getMills(index) {
var mills = [];
var pos = this.getPointPosition(index);
var point = this.map[index];
// Check for horizontal mills
var horizontalMills = [];
point.sides.forEach((side) => {
var sideList = this.getSide(side, pos.row);
// Loop trough all points of the side
for (var currPoint of sideList) {
// If current point's team does not equal to the team of the point whose side we are stdy here, return because this side cannot contain a mill
if (point.team != currPoint.team || !point.team) {
// Current point's team is a different one (Contrary team or no team because there is no piece). This side does not contain a mill
return;
}
}
horizontalMills.push(sideList);
});
mills = mills.concat(horizontalMills);
var verticalMill;
// Get line's object from its index
var line = this.getLine(point.line);
// If this returns a valid object (Otherwise false)
if (line) {
// Anonymous function used here to return when a team does not equals to the required one
(function() {
// Loop trough points of line and check wether their team equals to the team of the current point
for (var currPoint of line) {
if (point.team != currPoint.team || !point.team) {
// Different team (false or contrary one), return because
return;
}
}
verticalMill = line;
mills.push(verticalMill);
})();
}
return mills;
}
set(options) {
// Wether all required options exist
if ("team" in options && "row" in options && "position" in options) {
// Calculate index of field in map
var fieldIndex = this.getPointIndex(options.row, options.position);
// Set piece on point
this.map[fieldIndex].team = options.team;
// Return the index of the point to use it externaly
return fieldIndex;
}
}
move(pos, newPos) {
var teamName = this.map[pos].team;
this.map[newPos].team = teamName;
this.map[pos].team = false;
}
get mills() {
var mills = [];
this.map.filter(point => point.team).forEach(function(point) {
// Filter if a mill already exists in 'mills' array
mills = mills.concat(point.mills.filter(function(mill) {
// General function that returns wether the current mill is already located within 'mills' array
return !(function() {
// Looping trough 'mills' array and check the mills wether they are qual to the current 'mill'
for (var currMill of mills) {
// Function that returns wether the current 'mill' is exactly the same as the current mill ('currMill') of 'mills' array
var isMill = (function() {
// Looping trough all points within the current mill
for (var i = 0; i < currMill.length; i++) {
// If there exist a point that's equivalent point in 'mill' does not equal with it, this mill cannot be exactly the same
if (currMill[i] != mill[i]) {
return false;
}
}
// Obviously, no point in 'currMill' was found that does not eual to its equivalent point in 'mill'
// Therefore, 'currMill' seems to be exactly the same as 'mill'
return true;
})();
// If the current mill 'currMill' seems to be exactly the same as 'mill' we can return a true
// if not, the loop goes on to the next mill to check for equivalence with 'mill'
if (isMill) {
return true;
}
}
// Loop was finished and no mill in 'mills' was found that equals to 'mill'
// 'mill' seems to be new
return false;
})();
}));
});
return mills;
}
remove(index) {
// Just reset the 'team' property
this.map[index].team = false;
}
// Checks wether a given point borders on a second point by looping trough its surroundings
static isSurrounding(point, targetPoint) {
// Loop trough a surroundings of the point
for (var surroundingPointDirection in point.surroundings) {
// Just to exclude prototype properties within the 'surroundings' object
if (point.surroundings.hasOwnProperty(surroundingPointDirection)) {
// Wether the current surroundings equals to target point
if (point.surroundings[surroundingPointDirection] === targetPoint) {
return true;
}
}
}
return false;
}
}
MorrisBoard.prototype.map = [];
module.exports = MorrisBoard;
})();