A Rudimentary Logic Circuit Simulator in JavaScript

The JavaScript code below implements a rudimentary logic circuit simulator. The very simple demo circuit includes two constant signal sources, two logic gates, and two logic probes.

CircuitSimulator



<html>
<body>

<div id="divMain" />

<script type="text/javascript">

// main

function main()
{
	var universe = DemoData.universe();

	Globals.Instance.initialize
	(
		new Coords(400, 200), // viewSizeInPixels
		universe
	);
}

// extensions

function ArrayExtensions()
{
	// extension class
}
{
	Array.prototype.addLookups = function(keyName)
	{
		for (var i = 0; i < this.length; i++)
		{
			var item = this[i];
			var keyValue = item[keyName];
			this[keyValue] = item;
		}

		return this;
	}
}

// classes

function Component(name, defnName, pos)
{
	this.name = name;
	this.defnName = defnName;
	this.pos = pos;
}
{
	Component.prototype.lead = function(leadName)
	{
		var returnValue;

		var connection = this.connections[leadName];
		if (connection != null)
		{
			returnValue = connection.wire();
		}
		else
		{
			returnValue = new Wire();
		}

		return returnValue;
	}

	Component.prototype.defn = function()
	{
		return Globals.Instance.universe.componentDefns[this.defnName];	
	}

	Component.prototype.update = function(layout)
	{
		this.defn().update(layout, this);
	}
}

function ComponentDefn(name, size, leadDefns, draw, update)
{
	this.name = name;
	this.size = size;
	this.leadDefns = leadDefns;
	this.draw = draw;
	this.update = update;

	this.sizeHalf = this.size.clone().divideScalar(2);
	this.leadDefns.addLookups("name");
}
{
	// instances

	function ComponentDefn_Instances()
	{
		this._All = 
		[
			new ComponentDefn
			(
				"GateAnd", // name
				new Coords(30, 40), // size
				// leadDefns
				[
					new ComponentLeadDefn
					(
						"InA",
						new Coords(-20, 5)
					),
					new ComponentLeadDefn
					(
						"InB",
						new Coords(-20, 25)
					),
					new ComponentLeadDefn
					(
						"Out",
						new Coords(30, 15)
					),
				],
				null, // draw
				// update
				function(layout, component)
				{
					var inA = component.lead("InA");
					var inB = component.lead("InB");
					var out = component.lead("Out");

					var signalLevelOut = 0;
					if (inA.signalLevel == 1 && inB.signalLevel == 1)
					{
						signalLevelOut = 1;	
					}
					out.signalLevel = signalLevelOut;
				}
			),

			new ComponentDefn
			(
				"GateOr", // name
				new Coords(30, 40), // size
				// leadDefns
				[
					new ComponentLeadDefn
					(
						"InA",
						new Coords(-20, 5)
					),
					new ComponentLeadDefn
					(
						"InB",
						new Coords(-20, 25)
					),
					new ComponentLeadDefn
					(
						"Out",
						new Coords(30, 15)
					),
				],
				null, // draw
				// update
				function(layout, component)
				{
					var inA = component.lead("InA");
					var inB = component.lead("InB");
					var out = component.lead("Out");

					var signalLevelOut = 0;
					if (inA.signalLevel == 1 || inB.signalLevel == 1)
					{
						signalLevelOut = 1;	
					}
					out.signalLevel = signalLevelOut;
				}
			),

			new ComponentDefn
			(
				"SourceHigh", // name
				new Coords(30, 20), // size
				// leadDefns
				[
					new ComponentLeadDefn
					(
						"Out",
						new Coords(30, 5)
					),
				],
				null, // draw
				// update
				function(layout, component)
				{
					component.lead("Out").signalLevel = 1;
				}
			),

			new ComponentDefn
			(
				"SourceLow", // name
				new Coords(30, 20), // size
				// leadDefns
				[
					new ComponentLeadDefn
					(
						"Out",
						new Coords(30, 5)
					),
				],
				null, // draw
				// update
				function(layout, component)
				{
					component.lead("Out").signalLevel = 0;
				}
			),

			new ComponentDefn
			(
				"Probe", // name
				new Coords(30, 20), // size
				// leadDefns
				[
					new ComponentLeadDefn
					(
						"In",
						new Coords(-20, 5)
					),
				],
				// draw
				function(displayHelper, component)
				{
					var drawPos = component.pos.clone().add
					(
						component.defn().sizeHalf
					);
					var wire = component.connections["In"].wire();
					var signalLevel = wire.signalLevel;
					displayHelper.graphics.fillText
					(
						signalLevel,
						drawPos.x, drawPos.y
					);	
				},
				// update
				function(layout, component)
				{
					// do nothing
				}
			),

		].addLookups("name");
	}

	// static methods

	ComponentDefn.static = function()
	{
		ComponentDefn.Instances = new ComponentDefn_Instances();
	}
}

function ComponentLeadDefn(name, posWithinComponent)
{
	this.name = name;
	this.posWithinComponent = posWithinComponent;
}
{
	ComponentLeadDefn.static = function()
	{
		ComponentLeadDefn.SizeStandard = new Coords(20, 10);
		ComponentLeadDefn.SizeStandardHalf = new Coords(10, 5);
	}
}

function Connection(componentName, leadName)
{
	this.componentName = componentName;
	this.leadName = leadName;
}
{
	Connection.prototype.component = function()
	{
		return Globals.Instance.universe.layout.components[this.componentName];
	}

	Connection.prototype.pos = function()
	{
		var component = this.component();
		var componentDefn = component.defn();
		var leadDefn = componentDefn.leadDefns[this.leadName];
		var leadOffset = leadDefn.posWithinComponent;
		var leadSizeHalf = ComponentLeadDefn.SizeStandardHalf;
		var returnValue = component.pos.clone().add
		(
			leadOffset
		).add
		(
			leadSizeHalf
		);
		return returnValue;
	}

	Connection.prototype.wire = function()
	{
		return Globals.Instance.universe.layout.wires[this.wireName];
	}
}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;
		return this;
	}

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		return this;
	}
}

function DisplayHelper()
{
	// do nothing
}
{
	// constants

	DisplayHelper.ColorBack = "White";
	DisplayHelper.ColorFore = "Gray";

	// methods

	DisplayHelper.prototype.clear = function()
	{
		this.graphics.fillStyle = DisplayHelper.ColorBack;
		this.graphics.fillRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);

		this.graphics.strokeStyle = DisplayHelper.ColorFore;
		this.graphics.strokeRect
		(
			0, 0,
			this.viewSizeInPixels.x,
			this.viewSizeInPixels.y
		);

		this.graphics.fillStyle = DisplayHelper.ColorFore;
	}

	DisplayHelper.prototype.drawComponent = function(component)
	{
		var defn = component.defn();

		var pos = component.pos;

		var size = defn.size;

		this.graphics.strokeRect
		(
			pos.x, pos.y, size.x, size.y
		);

		var leadDefns = defn.leadDefns;
		for (var i = 0; i < leadDefns.length; i++)
		{
			var leadDefn = leadDefns[i];
			this.drawLead(leadDefn, pos);
		}

		var name = defn.name + ":" + component.name;

		this.graphics.fillText
		(
			name,
			pos.x, pos.y
		);
		
		if (defn.draw != null)
		{
			defn.draw(this, component);
		}
	}

	DisplayHelper.prototype.drawLead = function(leadDefn, componentPos)
	{
		var drawPos = componentPos.clone().add
		(
			leadDefn.posWithinComponent
		);

		var size = ComponentLeadDefn.SizeStandard;

		this.graphics.strokeRect
		(
			drawPos.x, drawPos.y,
			size.x, size.y
		);

		this.graphics.fillText
		(
			leadDefn.name,
			drawPos.x, drawPos.y + size.y
		);

	}

	DisplayHelper.prototype.drawLayout = function(layout)
	{
		this.clear();

		var components = layout.components;
		for (var i = 0; i < components.length; i++)
		{
			var component = components[i];
			this.drawComponent(component);
		}

		var wires = layout.wires;
		for (var i = 0; i < wires.length; i++)
		{
			var wire = wires[i];
			this.drawWire(wire);
		}
	}

	DisplayHelper.prototype.drawLine = function(startPos, endPos)
	{
		this.graphics.beginPath();
		this.graphics.moveTo(startPos.x, startPos.y);
		this.graphics.lineTo(endPos.x, endPos.y);
		this.graphics.stroke();
	}

	DisplayHelper.prototype.drawWire = function(wire)
	{
		var connections = wire.connections;
		var numberOfConnections = connections.length;
		var wirePos = wire.pos;

		for (var i = 0; i < numberOfConnections; i++)
		{
			var connection = connections[i];
			var connectionPos = connection.pos();
			this.drawLine(wirePos, connectionPos);
		}

		/*
		this.graphics.fillText
		(
			wire.name, 
			wirePos.x, 
			wirePos.y
		);
		*/
	}

	DisplayHelper.prototype.initialize = function(viewSizeInPixels)
	{
		this.viewSizeInPixels = viewSizeInPixels;

		var canvas = document.createElement("canvas");
		canvas.width = this.viewSizeInPixels.x;
		canvas.height = this.viewSizeInPixels.y;

		var divMain = document.getElementById("divMain");
		divMain.appendChild(canvas);

		this.graphics = canvas.getContext("2d");
	}
}

function Globals()
{
	// do nothing
}
{
	// instance

	Globals.Instance = new Globals();

	// methods

	Globals.prototype.initialize = function(viewSizeInPixels, universe)
	{
		this.universe = universe;
		this.universe.initialize();

		this.displayHelper = new DisplayHelper();
		this.displayHelper.initialize(viewSizeInPixels);

		this.inputHelper = new InputHelper();
		this.inputHelper.initialize();

		this.universe.update();
	}
}

function InputHelper()
{
	// do nothing
}
{
	InputHelper.prototype.initialize = function()
	{
		document.onkeydown = this.handleEventKeyDown.bind(this);
	}

	// events

	InputHelper.prototype.handleEventKeyDown = function(event)
	{
		// For eventual clocked logic support.
		Globals.Instance.universe.update();
	}
}

function Layout(name, components, wires)
{
	this.name = name;
	this.components = components;
	this.wires = wires;
}
{
	Layout.prototype.initialize = function()
	{
		this.components.addLookups("name");
		this.wires.addLookups("name");

		for (var i = 0; i < this.components.length; i++)
		{
			var component = this.components[i];
			component.connections = [];
		}

		for (var i = 0; i < this.wires.length; i++)
		{
			var wire = this.wires[i];
			wire.initialize();

			var connections = wire.connections;
			for (var c = 0; c < connections.length; c++)
			{
				var connection = connections[c];
				var connectionComponent = connection.component();
				connectionComponent.connections.push(connection);
			}
		}	

		for (var i = 0; i < this.components.length; i++)
		{
			var component = this.components[i];
			component.connections.addLookups("leadName");
		}	
	}

	Layout.prototype.update = function()
	{
		for (var i = 0; i < this.components.length; i++)
		{
			var component = this.components[i];
			component.update(this);
		}

		for (var i = 0; i < this.wires.length; i++)
		{
			var wire = this.wires[i];
			wire.update();
		}
	}
}

function Universe(name, componentDefns, layout)
{
	this.name = name;
	this.componentDefns = componentDefns;
	this.layout = layout;
}
{
	// instance methods

	Universe.prototype.initialize = function()
	{
		this.layout.initialize();
	}

	Universe.prototype.update = function()
	{
		this.layout.update();
		Globals.Instance.displayHelper.drawLayout(this.layout);
	}
}

function Wire(name, pos, connections)
{
	this.name = name;
	this.pos = pos;
	this.connections = connections;
	this.signalLevel = 0;

	// For eventual clocked logic support.
	this.signalLevelNext = null;
}
{
	Wire.prototype.initialize = function()
	{
		for (var i = 0; i < this.connections.length; i++)
		{
			var connection = this.connections[i];
			connection.wireName = this.name;
		}
	}

	Wire.prototype.signalLevelSet = function(value)
	{
		// For eventual clocked logic support.
		this.signalLevelNext = value;
	}

	Wire.prototype.update = function()
	{
		if (this.signalLevelNext != null)
		{
			this.signalLevel = this.signalLevelNext;
		}
	}
}

// demo data

function DemoData()
{
	// static class
}
{
	DemoData.universe = function()
	{
		var componentDefns = ComponentDefn.Instances._All;

		var layout = new Layout
		(
			"LayoutDemo",
			// components
			[
				new Component
				(
					"Src0",
					componentDefns["SourceLow"].name,
					new Coords(20, 50)
				),

				new Component
				(
					"Src1",
					componentDefns["SourceHigh"].name,
					new Coords(20, 100)
				),

				new Component
				(
					"And0",
					componentDefns["GateAnd"].name,
					new Coords(200, 30)
				),

				new Component
				(
					"Or0",
					componentDefns["GateOr"].name,
					new Coords(200, 120)
				),

				new Component
				(
					"Probe0",
					componentDefns["Probe"].name,
					new Coords(320, 50)
				),

				new Component
				(
					"Probe1",
					componentDefns["Probe"].name,
					new Coords(320, 120)
				),
			],
			// wires
			[
				new Wire
				(
					"W0",
					new Coords(120, 60), 
					[
						new Connection("Src0", "Out"),
						new Connection("And0", "InA"),
						new Connection("Or0", "InA"),
					]
				),
				new Wire
				(
					"W1",
					new Coords(120, 110), 
					[
						new Connection("Src1", "Out"),
						new Connection("And0", "InB"),
						new Connection("Or0", "InB"),
					]
				),
				new Wire
				(
					"W2",
					new Coords(275, 50), 
					[
						new Connection("And0", "Out"),
						new Connection("Probe0", "In"),
					]
				),
				new Wire
				(
					"W3",
					new Coords(275, 140), 
					[
						new Connection("Or0", "Out"),
						new Connection("Probe1", "In"),
					]
				)
			]
		);

		var returnValue = new Universe
		(
			"UniverseDemo",
			componentDefns,
			layout
		);

		return returnValue;
	}
}

// static setup

{
	ComponentDefn.static();
	ComponentLeadDefn.static();
}

// run

main();

</script>

</body>
</html>

This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s