/**
* Traverser module <br>
* Currently only check if any circuit on the board is closed or not.
* @module Traverser
*/
'use strict';
/**
* Traverser
*
* @instance
* @param {boolean} [{ debug = false }={ debug : false }]
* @returns {Traverser}
*
* @example
* let traverser = Traverser();
*/
let Traverser = function Traverser({ debug = false } = { debug : false }) {
/**
* Deactivates all open-ended branches
*
* @private
* @method deactivateOpenBranches
* @param {Board} board
*/
function deactivateOpenBranches(board) {
board.occupiedSlots.forEach((slot) => {
deactivateOpenEnds(board, slot);
});
}
/**
* Given a slot, we recursively see if the components connected to it needs deactivating or not.
*
* @private
* @method deactivateOpenEnds
* @param {Board} board
* @param {Slot} slot
*/
function deactivateOpenEnds(board, slot) {
if (slot.activeCount === 1) {
let pinAndComponent = slot.activeConnections.values() // returns @@iterator that contains values
.next().value, // getting the only value in that Map
component = pinAndComponent.component,
pin = pinAndComponent.pin;
let otherPins = component.getOtherPins([slot.x, slot.y]);
/* If the component is NOT open ended AND active, deactivate
If the component is open ended AND the pin index is 0 AND active, deactivate
The second condition is to deactivate stray/isolated open ended components
*/
if ((!component.openEnded || (component.openEnded && pin === 0)) && component.active) {
component.active = false;
otherPins.forEach((pos) => {
deactivateOpenEnds(board, board.board[pos[0]][pos[1]]);
});
}
}
}
/**
* Errors if no source component found
*
* @private
* @method checkSourceExists
* @param {Board} board
*/
function checkSourceExists(board) {
if(!board.hasType(ComponentType.Source)){
Utility.logger('No source component found!');
}
}
/**
* Errors if no ground component found
*
* @private
* @method checkGroundExists
* @param {Board} board
*/
function checkGroundExists(board) {
if(!board.hasType(ComponentType.Ground)){
Utility.logger('No ground component found!');
}
}
/**
* Traverses through each active source component to check if any circuit is closed or not.
*
* @private
* @method checkClosedCircuit
* @param {Board} board
*/
function checkClosedCircuit(board) {
checkSourceExists(board);
checkGroundExists(board);
deactivateOpenBranches(board);
// We have to find a list of active Sources, so we can start traversing!
let activeSources = board.activeSources;
// The source component(s) might be deactivated due to being on an open branch
// so that counts as an open circuit too
if(activeSources.length === 0){
Utility.logger('No closed circuit found');
}
// Start traversal from each source components
activeSources.forEach((source)=>{
traverseSource(board, source);
});
if(!board.closed){
Utility.logger('No closed circuit found');
}
}
/**
* Traverse through the circuit starting from the source component. <br>
* Sets the Board.closed field to 'true' if circuit is closed. <br>
* In this process, the traverser also records each circuit's nodes' positions into a Circuit object, which wil then be stored in Board.circuits.
*
* @private
* @method traverseSource
* @param {Board} board
* @param {Source} source
*/
function traverseSource(board, source) {
// Note: This is code is assuming that the source has two pins
// Exit if this source has been traveled already
if(source.traveled){
return;
}
source.traveled = true;
// Set up new Circuit object
let circuit = new Circuit(board);
// Store the negative pin positions of the source so we can tell if we are back at source later
let currentPos = source.pins[0],
negativePinPos = source.pins[1];
// This acts like a 'stack', storing the most recent traveled node
let nodeTrace = [];
trace('traverseSource', 'START OF TRAVERSAL');
// Start traversing from the source
checkBackAtSource(currentPos);
/**
* Check to see if we're back at the source component
*
* @private
* @memberOf traverseSource
* @method checkBackAtSource
* @param {Position} pos
*/
function checkBackAtSource(pos) {
trace('checkBackAtSource', pos);
if(pos.toString() === negativePinPos.toString()){
board.closed = true;
// This pos might be a true node, while being back at source component
if(board.getSlot(pos).isTrueNode){
existsInTrace(pos);
}
else {
findUnfinishedNode();
}
}
else {
checkTrueNode(pos);
}
};
/**
* Find node(s) that has untraveled connection(s). <br>
* Returns true if the traversal is done.
*
* @private
* @memberOf traverseSource
* @method findUnfinishedNode
* @returns {boolean}
*/
function findUnfinishedNode() {
trace('findUnfinishedNode');
if(board.hasUnfinishedNode){
backToLastNode();
}
else {
trace('findUnfinishedNode', 'END OF TRAVERSAL');
circuit.addToBoard();
return true;
}
}
/**
* Jump back to the most recent traveled node and start traversing from there again
*
* @private
* @memberOf traverseSource
* @method backToLastNode
*/
function backToLastNode() {
trace('backToLastNode');
let lastNodePos = nodeTrace[nodeTrace.length-1];
findUntraveledConnections(lastNodePos);
}
/**
* Find untraveled Connection(s) in a position/node
*
* @private
* @memberOf traverseSource
* @method findUntraveledConnections
* @param {Position} pos
*/
function findUntraveledConnections(pos) {
trace('findUntraveledConnections', pos);
if(board.getSlot(pos).hasAllTraveled){ // Not found
nodeTrace.pop();
findUnfinishedNode();
}
else { // Found
goNextSlot(pos);
}
}
/**
* Check if this position is a true node or not
*
* @private
* @memberOf traverseSource
* @method checkTrueNode
* @param {Position} pos
*/
function checkTrueNode(pos) {
trace('checkTrueNode', pos);
if(board.getSlot(pos).isTrueNode){
existsInTrace(pos);
}
else {
goNextSlot(pos);
}
}
/**
* Check if this position/node exists in the trace already or not
*
* @private
* @memberOf traverseSource
* @method existsInTrace
* @param {Position} pos
*/
function existsInTrace(pos) {
trace('existsInTrace', pos);
if(nodeTrace.find((node) => node.toString() === pos.toString())){ // Found in nodeTrace
backToLastNode();
}
else {
// Add to this Circuit's node array
circuit.addNode(pos);
nodeTrace.push(pos);
goNextSlot(pos);
}
}
/**
* Picks the component's other pin and moves onto its position to continue traversal <br>
*
* @private
* @memberOf traverseSource
* @method goNextSlot
* @param {Position} pos Current position
*/
function goNextSlot(pos) {
trace('goNextSlot', pos);
let connection = board.getSlot(pos).untraveledConnections[0],
component = connection.component,
nextPos = component.getOtherPins(pos)[0];
component.traveled = true;
// We don't need to go to the next slot if the current component is a ground
if(component.type === ComponentType.Ground) {
// Add the last node's position as the ground node position
circuit.setGround(nodeTrace[nodeTrace.length-1]);
checkBackAtSource(pos);
}
else {
checkBackAtSource(nextPos);
}
}
}
/**
* Start the traversal
*
* @public
* @instance
* @method start
* @param {Board} board
*
* @example
* let traverser = Traverser();
* traverser.start(board);
*/
function start(board) {
checkClosedCircuit(board);
}
/**
* Tracing the steps in the traversal
*
* @private
* @method trace
* @param {any} params
*/
function trace(...params) {
if(debug) {
console.log(...params);
}
}
return {
start: start,
_checkSourceExists: checkSourceExists,
_checkGroundExists: checkGroundExists,
_deactivateOpenBranches: deactivateOpenBranches,
_checkClosedCircuit: checkClosedCircuit
};
}
module.exports = Traverser;