--- /dev/null
+#!/usr/bin/python3
+# (c) Copyright 2013 TJ <hacker@iam.tj>
+# Licensed on the terms of the GNU General Public License version 3 (see COPYING)
+#
+# Base2 Runner: Circuit design and management
+#
+# TODO: module in development, not ready for any use yet
+#
+
+import os.path, pygame, engine
+from library import enum
+
+class TraceSegment:
+ """ Models a straight line segment of a complete Trace """
+ # C-style pseudo-enum representing the directions a segment might run
+ directions = enum('N', 'NE', 'E', 'SE', 'S' , 'SW', 'W' , 'NW')
+ angles = list( 0 , 45 , 90, 135, 180, 225, 270, 315)
+
+ # references to an attached Via and its position
+ via = None
+ via_pos = enum('START', 'MID', 'END')
+ via_position = None
+
+ def __init__(self, length=1, direction=TraceSegment.directions.E, via=None, position=TraceSegment.via_pos.START):
+ """ default segment runs for one game tile length left-to-right
+ length: number of game 'tiles' this segment covers
+ direction: direction
+ """
+ # ensure all values are legal before doing any assignment
+ if not direction in TraceSegment.directions.reverse_mapping:
+ # 'direction' is an index enum, so ensure it exists in the enum before using it to select the angle
+ raise IndexError('direction invalid')
+ elif not position in TraceSegment.via_pos:
+ # 'position' is an enum so ensure it is valid
+ raise IndexError('position invalid')
+ elif via and not isinstance(via, Via):
+ raise TypeError('expecting a Via')
+
+ self.length = length
+ self.angle = angles[direction]
+ self.via = via
+ self.via_position = position
+
+ def via_set(self, via, position):
+ """ link to the related Via
+ via: Via object
+ position: enum via_pos
+ """
+ if via and isinstance(via, Via):
+ if position in via_pos:
+ # FIXME: ensure the line above works for testing position's membership of via_pos
+ self.via = via
+ self.via_position = position
+
+
+class Trace:
+ """ Models a single circuit trace made up of multiple segments """
+ segments = list() # ordered list of one or more TraceSegments
+ colours = dict()
+ colours['metal'] = (192, 192, 192) # colours of the trace
+ colours['shadow'] = (64, 64, 64)
+ colours['reflection'] = (255, 255, 255)
+
+ def check_range(self, colour):
+ """ Ensure a colour value is legal """
+ return colour >= 0 and colour <= 255
+
+ def __init__(self, metal=None, shadow=None, reflection=None, segments=TraceSegment()):
+ # set the colours of the trace to give a 3D raised appearance
+ if metal and check_range(metal): self.colours['metal'] = metal
+ if shadow and check_range(shadow): self.colours['shadow'] = shadow
+ if reflection and check_range(reflection): self.colours['reflection'] = reflection
+
+ # initialise the via dictionary
+ with self.segments[0]:
+ via = None
+ via_position = via_pos.START
+
+ def draw(self, surface):
+ """ Draw the Trace onto pygame.Surface """
+
+class Via:
+ """ Models a through-hole otherwise known as a Via """
+ trace = None # the Trace this via is attached to
+ linked = None # the Via this via links to
+
+
+class Bus:
+ """ Group of traces all going in the same direction """
+ width = 8 # default is an 8-bit bus
+ group = None
+
+ def __init__(self, buswidth=8):
+ """ Create a new bus consisting of 'buswidth' traces """
+
+
+class IntegratedCircuit:
+ """ semiconductor component """
+ signals = None # key = signal name, value = pin number
+
+ def __init__(self):
+ """ populate pseudo-enum with common signal names """
+ if not IntegratedCircuit.signals:
+ signals = ('NC', 'GND', 'Vcc', 'Vdd')
+ # logic gate signal names (covers multi-gate components up to quad-gate)
+ for l in ('A', 'B', 'Y'):
+ for n in range(1, 5):
+ signals.append("%s%s" % (n, l))
+
+ IntegratedCircuit.signals = enum(signals)
+
+
+class DualInLine(IntegratedCircuit):
+ """ Dual In-Line Integrated Circuit """
+ sides = 2
+ qty = 0
+ pins = list()
+
+ def __init__(self, pins):
+ if pins > 0 and pins % 2 == 0:
+ for p in range(1, pins+1):
+ pins[p] = IntegratedCircuit.signals.NC
+
+
+class Gate:
+ """ Models a single logic gate """
+ types = None
+ inputs = list()
+ output = False
+ type = None
+
+ def __init__(self, type, inputs=None):
+ # populate dictionary with common logic gate types
+ if not Gate.types:
+ Gate.types = enum('AND', 'OR', 'XOR', 'NOT', 'NAND', 'NOR')
+
+ if type in Gate.types:
+ self.type = type
+
+ if inputs and type(inputs) is list and isinstance(inputs[0], bool):
+ self.inputs = inputs
+ else:
+ # default to 2 inputs at logic 0
+ self.inputs = (False, False)
+
+ self.input_max = len(self.inputs)
+ self.update()
+
+ def update(self):
+ """ Needs to be over-ridden in derived classes """
+ return
+
+ def set_input(self, input, value):
+ if input >= 0 and input < self.input_max:
+ if isinstance(value, bool):
+ self.inputs[input] = value
+ self.update()
+
+ def get_output(self):
+ return self.output
+
+ def min_inputs(self, inputs, minimum):
+ """ enforce a minimum number of logic inputs """
+ return inputs if len(inputs) >= minimum else (inputs, (False for i in range(0, minimum - len(inputs))))
+
+
+# Derive each of the common logic gate types
+class GateAND(Gate):
+ def __init__(self, inputs=None):
+ Gate.__init__(Gate.types.AND, self.min_inputs(inputs, 2))
+
+ def update(self):
+ self.output = self.inputs[0]
+ for i in range(1, self.input_max):
+ self.output &= self.inputs[i] # AND
+
+
+class GateOR(Gate):
+ def __init__(self, inputs=None):
+ Gate.__init__(Gate.types.OR, self.min_inputs(inputs, 2))
+
+ def update(self):
+ self.output = self.inputs[0]
+ for i in range (1, self.input_max):
+ self.output |= self.inputs[i] # OR
+
+
+class GateXOR(Gate):
+ def __init__(self, inputs=None):
+ Gate.__init__(Gate.types.XOR, self.min_inputs(inputs, 2))
+
+ def update(self):
+ self.output = self.inputs[0]
+ for i in range (1, self.input_max):
+ self.output ^= self.inputs[i] # XOR
+
+
+class GateNOT(Gate):
+ def __init__(self, inputs=(False, )):
+ if len(inputs) > 1:
+ # inverters can only have 1 input
+ inputs = (inputs[0], )
+ Gate.__init__(Gate.types.NOT, inputs)
+
+ def update(self):
+ self.output = ~self.inputs[0] # invert
+
+
+class GateNAND(Gate):
+ def __init__(self, inputs=None):
+ Gate.__init__(Gate.types.NAND, self.min_inputs(inputs, 2))
+
+ def update(self):
+ self.output = self.inputs[0]
+ for i in range(1, self.input_max):
+ self.output &= self.inputs[i] # AND
+ self.output = ~self.output # invert
+
+
+class GateNOR(Gate):
+ def __init__(self, inputs=None):
+ Gate.__init__(Gate.types.NOR, self.min_inputs(inputs, 2))
+
+ def update(self):
+ self.output = self.inputs[0]
+ for i in range (1, self.input_max):
+ self.output |= self.inputs[i] # OR
+ self.output = ~self.output # invert
+
+
+class ICLogic:
+ """ Models an IC that contains one or more digital logic gates """
+ gates = list() # list of gates
+ package = None
+
+ def __init__(self, gates=(), package=DualInLine(14)):
+ if package: self.package = package
+ if type(gates) is list and type gates[0] is Gate: self.gates = gates
+
+ def add(self, gate):
+ if gate and type(gate) is Gate: self.gates.append(gate)
+
+
+class SN7400(ICLogic, DualInLine):
+ """ Model of a Texas Instruments SN7400 quad 2-input NAND gate DIP14 package """
+
+ def __init__(self):
+ # call the super classes constructors
+ gates = list()
+ for i in range(0, 4):
+ gates.append(GateNAND())
+ # TODO: may need to move this functionality into an intermediate super class that combines package and Logic chip
+ ICLogic.__init__(gates)
+ DualInLine.__init(14)
+
+
+class SN741G00(ICLogic, ...):
+ """ Model of a Texas Instruments SN741G00 single 2-input NAND gate """
+
+
+class TSSOP(IntegratedCircuit):
+ """ Thin Shrink Small Outline Package """
+ sides = 2
+ pins = list()
+
+ def __init__(self, pins):
+
+
+
+class PCB:
+ """ Models a digital electronics logic circuit printed on a circuit board """
+ layout = None
+
+ def __init__(self):
+ """ """
+
+ def trace_add(self, trace_list):
+ """ Add a """
+
+ def component_insert(self, component):
+ """ Adds a component to """
+