import {
Group,
Rectangle,
MultiLineText,
PolyLine,
PolyLinePoints,
PolyLinePoint
} from '../svgObjects';
import Logic from '../Logic';
import Box from './Box';
/** @module editorElements.Blackbox */
/**
* Blackbox is a box that is defined by its evaluation function
* @extends Box
*/
export default class Blackbox extends Box {
/**
* @param {App} appInstance instance of [App](./module-App.html)
* @param {number} inputConnectors number of input connectors
* @param {number} outputConnectors number of output connectors
* @param {Function} evalFunction function that takes `inputConnectors` [Logic.state](./module-Logic.html#.state)s
* and returns `outputConnectors` Logic.states.
* @param {String} [name] name that will be displayed on the blackbox
*/
constructor(appInstance, inputConnectors, outputConnectors, evalFunction, name = '') {
const width = 11;
const height = Math.max(inputConnectors, outputConnectors) * 2;
super(appInstance, name, 'blackbox', width, height);
const connectorPinLenght = 2.5 * this.gridSize;
// override default svgObj structure
this.svgObj = new Group();
// transparent background rectangle
let hitbox = new Rectangle(0, 0, this.width, this.height, 'none', 'none');
hitbox.$el.addClass('rect');
this.svgObj.addChild(hitbox);
// main rectangle
const bodyWidth = this.width - 2 * connectorPinLenght;
let rectangle = new Rectangle(
connectorPinLenght,
0,
bodyWidth,
this.height,
'white',
'black'
);
rectangle.addAttr({ 'stroke-width': '2.5' });
rectangle.$el.addClass('rect');
this.svgObj.addChild(rectangle);
// text description of the box
const textWidth = bodyWidth - this.gridSize;
const textHeight = this.height - this.gridSize;
let text = new MultiLineText(
(this.width - textWidth) / 2, // horizontal centering
(this.height - textHeight) / 2, // vertical centering
textWidth,
textHeight,
name.toUpperCase(),
this.gridSize * 1.2
);
this.svgObj.addChild(text);
// add input connectors
for (let i = 0; i < inputConnectors; ++i) {
const gridPosition = i * 2 + 1;
const pixelPosition = gridPosition * this.gridSize;
let pin = new PolyLine(
new PolyLinePoints([
new PolyLinePoint(0, pixelPosition),
new PolyLinePoint(connectorPinLenght, pixelPosition)
]),
1,
'black'
);
this.svgObj.addChild(pin);
// add the connector
this.addInputConnector(0, gridPosition);
}
// add output connectors
for (let i = 0; i < outputConnectors; ++i) {
const gridPosition = i * 2 + 1;
const pixelPosition = gridPosition * this.gridSize;
let pin = new PolyLine(
new PolyLinePoints([
new PolyLinePoint(this.width - connectorPinLenght, pixelPosition),
new PolyLinePoint(this.width, pixelPosition)
]),
1,
'black'
);
this.svgObj.addChild(pin);
this.addOutputConnector(width, gridPosition);
}
this.svgObj.$el.addClass('box');
/**
* function that takes `inputConnectors` [Logic.state](./module-Logic.html#.state)s
* and returns `outputConnectors` Logic.states.
*/
this.evalFunction = evalFunction;
// regenerate the blocked nodes after adding all the connectors
this.generateBlockNodes();
}
/**
* get data of this blackbox as a JSON-ready object
* @return {Object} javascript object containing essential data for this blackbox
*/
get exportData() {
let data = super.exportData;
data.inputs = this.inputConnectors.length;
data.outputs = this.outputConnectors.length;
// generate the truth table
data.table = [];
// array of tested input states
const stateList = Logic.stateList;
// recursive function that generates all possible inputs
const getPermutations = length => {
let permutations = [];
switch (length) {
case 0:
return [];
case 1:
for (const state of stateList) {
permutations.push([state]);
}
return permutations;
default:
for (const state of stateList) {
for (const perm of getPermutations(length - 1)) {
permutations.push([state, ...perm]);
}
}
return permutations;
}
};
// generate outputs for all the possible inputs
for (const inputValues of getPermutations(data.inputs)) {
const outputValues = this.evalFunction(...inputValues);
// if there is an output value that is not Logic.state.unknown, add this line to the
// truthtable, otherwise don't add it (if all output values are Logic.state.unknown,
// the input combination does not have to be defines, because Logic.state.unknown is the default value)
if (
outputValues.reduce((accumulator, current) => {
return accumulator || current !== Logic.state.unknown;
})
) {
data.table.push([...inputValues, ...outputValues]);
}
}
return data;
}
/**
* proccess the input connector states and reflect them in the output connector states according
* to the logic defined by this.evalFunction
*/
refreshState() {
const inputStates = this.inputConnectors.map(conn => conn.state);
// call the evalFunction to get the output states
const outputStates = this.evalFunction(...inputStates);
// apply the outputStates to the outputConnectors
for (let i = 0; i < outputStates.length; ++i) {
this.outputConnectors[i].setState(outputStates[i]);
}
}
generateBlockNodes() {
// add blocked nodes on the connectors and between them as well
let specialNodes = [];
for (let i = 1; i < this.inputConnectors.length * 2; ++i) {
specialNodes.push({
x: 0,
y: i
});
}
for (let i = 1; i < this.outputConnectors.length * 2; ++i) {
specialNodes.push({
x: this.gridWidth,
y: i
});
}
super.generateBlockNodes(0, 1, 0, 1, ...specialNodes);
}
}