936982ea39ed7fae79e9cc2860baf755a24a5872
[base2-runner.git] / circuits.py
1 #!/usr/bin/python3
2 # (c) Copyright 2013 TJ <hacker@iam.tj>
3 # Licensed on the terms of the GNU General Public License version 3 (see COPYING)
4 #
5 # Base2 Runner: Circuit design and management 
6 #
7 # TODO: module in development, not ready for any use yet
8 #
9
10 import os.path, pygame, engine, library
11 from library import enum
12
13
14 class TraceSegment:
15  """ Models a straight line segment of a complete Trace """
16  #          N    NE    E    SE    S     SW    W     NW
17  angles = ( 0 ,  45 ,  90,  135,  180,  225,  270,  315)
18
19  # references to an attached Via and its position
20  via = None
21  via_position = None
22
23  def __init__(self, length=1, direction=library.directions.E, via=None, via_position=library.positions.NONE):
24   """ default segment runs for one game tile length left-to-right
25    length:        number of game 'tiles' this segment covers
26    direction:     enum directions
27    via:           Via
28    via_position:  enum positions
29   """
30   # ensure all values are legal before doing any assignment
31   if not direction in library.directions.reverse_mapping:
32    # 'direction' is an index enum, so ensure it exists in the enum before using it to select the angle
33    raise IndexError('direction invalid')
34   elif not via_position in library.positions:
35    # 'position' is an enum so ensure it is valid
36    raise IndexError('position invalid')
37   elif via and not isinstance(via, Via):
38    raise TypeError('expecting a Via')
39
40   self.length = length
41   self.angle = angles[direction]
42   self.via = via
43   self.via_position = via_position
44  
45  def set_via(self, via, position):
46   """ link to the related Via
47    via:       Via object
48    position:  enum via_pos
49   """
50   if not isinstance(via, Via):
51    raise TypeError('expecting a Via')
52   elif not  position in library.positions:
53    # FIXME: ensure the line above works for testing position's membership
54    raise IndexError('position invalid')
55
56   self.via = via
57   self.via_position = position
58
59  def set_direction(self, direction):
60   """ set the direction the segment is headed
61    direction: enum directions
62   """
63   if not direction in library.directions.reverse_mapping:
64    raise IndexError('direction invalid')
65   self.angle = self.angles(direction)
66
67
68 class Trace:
69  """ Models a single circuit trace made up of multiple segments """
70  segments = list() # ordered list of one or more TraceSegments
71  colours = dict()
72  # colours['metal'] = (192, 192, 192) # colours of the trace
73  # colours['shadow'] = (64, 64, 64)
74  # colours['reflection'] = (255, 255, 255)
75
76  def check_range(self, colour):
77   """ Ensure a colour value is legal """
78   if not (type(colour) == tuple or type(colour) == int):
79    raise TypeError('colour expeted to be tuple or int')
80   elif not len(colour) == 3:
81    raise ValueError('colour tuples require exactly 3 items (RGB)')
82
83   if type(colour) == int:
84    return colour >= 0 and colour <= 255
85   else:
86    return self.check_range(colour[0]) and self.check_range(colour[1]) and self.check_range(colour[2])
87
88  def __init__(self, metal=(192, 192, 192), shadow=(64, 64, 64), reflection=(255, 255, 255), segments=None):
89   # set the colours of the trace to give a 3D raised appearance
90   if metal and check_range(metal): self.colours['metal'] = metal
91   if shadow and check_range(shadow): self.colours['shadow'] = shadow
92   if reflection and check_range(reflection): self.colours['reflection'] = reflection
93
94   if segments == None:
95    self.segments = TraceSegment()
96   else:
97    self.segments = segments
98
99   with self.segments[0]:
100    via = None
101    via_position = library.positions.NONE
102
103  def draw(self, surface):
104   """ Draw the Trace onto pygame.Surface """
105   return
106
107
108 class Via:
109  """ Models a through-hole otherwise known as a Via """
110  trace = None # the Trace this via is attached to
111  linked = None # the Via this via links to
112
113
114 class Bus:
115  """ Group of traces all going in the same direction """
116  width = 8 # default is an 8-bit bus
117  group = None
118
119  def __init__(self, buswidth=8):
120   """ Create a new bus consisting of 'buswidth' traces """
121
122
123 class IntegratedCircuit:
124  """ semiconductor component """
125  pins = list() # index = pin number, value = signal name
126
127
128 class DualInLine(IntegratedCircuit):
129  """ Dual In-Line Integrated Circuit """
130  sides = 2
131  qty = 0
132
133  def __init__(self, pin_qty):
134   if not (pin_qty > 0 and pin_qty % 2 == 0):
135    raise ValueError('DIL ICs have even number of pins')
136  
137   for p in range(1, pin_qty+1):
138    self.pins.insert(p, library.signals.NC)
139
140
141 class Gate:
142  """ Models a single logic gate """
143  types = None
144  inputs = list()
145  output = False
146  gate_type = None
147
148  def __init__(self, gate_type, inputs=None):
149   if not gate_type in library.gates:
150    raise IndexError()
151
152   self.gate_type = gate_type
153
154   if inputs == None:
155    # default to 2 inputs at logic 0
156    self.inputs = (False, False)
157   elif not (type(inputs) is list and isinstance(inputs[0], bool)):
158    raise TypeError()
159  
160   self.inputs = inputs
161   self.input_max = len(self.inputs)
162   self.update()
163
164  def update(self):
165   """ Needs to be over-ridden in derived classes """
166   return
167
168  def set_input(self, input, value):
169   if not(input >= 0 and input < self.input_max):
170    raise ValueError("input expected between 0 and" % self.input_max)
171  
172   if not isinstance(value, bool):
173    raise TypeError('Boolean expected')
174
175   self.inputs[input] = value
176   self.update()
177
178  def get_output(self):
179   return self.output
180
181  def min_inputs(self, inputs, minimum):
182   """ enforce a minimum number of logic inputs in the list """
183   return inputs if len(inputs) >= minimum else (inputs, (False for i in range(0, minimum - len(inputs))))
184    
185
186 # Derive each of the common logic gate types
187 class GateAND(Gate):
188  def __init__(self, inputs=None):
189   Gate.__init__(library.gates.AND, self.min_inputs(inputs, 2))
190
191  def update(self):
192   self.output = self.inputs[0]
193   for i in range(1, self.input_max):
194    self.output &= self.inputs[i] # AND
195
196
197 class GateOR(Gate):
198  def __init__(self, inputs=None):
199   Gate.__init__(library.gates.OR, self.min_inputs(inputs, 2))
200
201  def update(self):
202   self.output = self.inputs[0]
203   for i in range (1, self.input_max):
204    self.output |= self.inputs[i] # OR
205
206
207 class GateXOR(Gate):
208  def __init__(self, inputs=None):
209   Gate.__init__(library.gates.XOR, self.min_inputs(inputs, 2))
210
211  def update(self):
212   self.output = self.inputs[0]
213   for i in range (1, self.input_max):
214    self.output ^= self.inputs[i] # XOR
215
216
217 class GateNOT(Gate):
218  def __init__(self, inputs=(False, )):
219   if len(inputs) != 1:
220    raise ValueError('NOT must have only 1 input')
221   Gate.__init__(library.gates.NOT, inputs)
222
223  def update(self):
224   self.output = ~self.inputs[0] # invert
225
226
227 class GateNAND(Gate):
228  def __init__(self, inputs=None):
229   Gate.__init__(library.gates.NAND, self.min_inputs(inputs, 2))
230
231  def update(self):
232   self.output = self.inputs[0]
233   for i in range(1, self.input_max):
234    self.output &= self.inputs[i] # AND
235   self.output = ~self.output # invert
236
237
238 class GateNOR(Gate):
239  def __init__(self, inputs=None):
240   Gate.__init__(library.gates.NOR, self.min_inputs(inputs, 2))
241
242  def update(self):
243   self.output = self.inputs[0]
244   for i in range (1, self.input_max):
245    self.output |= self.inputs[i] # OR
246   self.output = ~self.output # invert
247
248
249 class ICLogic:
250  """ Models an IC that contains one or more digital logic gates """
251  gates = list() # list of gates
252
253  def __init__(self, gates=()):
254   if not type(gates) is list:
255    raise TypeError('expected list')
256   elif not type(gates[0]) is Gate:
257    raise TypeError('expected Gate')
258
259   self.gates = gates
260
261  def add(self, gate):
262   if not type(gate) is Gate:
263    raise TypeError('expected Gate')
264
265   self.gates.append(gate)
266
267 class ICLogicDIL(ICLogic, DualInLine):
268  """ Model of a Logic IC ina  DIL package  """
269  def __init__(self, gates=(), pins_qty=14):
270   ICLogic.__init(gates)
271   DualInLine.__init__(pins_qty)
272
273
274 class SN7400(ICLogicDIL):
275  """ Model of a Texas Instruments SN7400 quad 2-input NAND gate DIP14 package """
276
277  def __init__(self):
278   # call the super classes constructors
279   gates = list()
280   for i in range(0, 4):
281    gates.append(GateNAND())
282   ICLogicDIL.__init__(gates, 14)
283
284
285 class SN741G00(ICLogicDIL):
286  """ Model of a Texas Instruments SN741G00 single 2-input NAND gate """
287
288
289 class TSSOP(IntegratedCircuit):
290  """ Thin Shrink Small Outline Package """
291  sides = 2
292  pins = list()
293
294  def __init__(self, pins):
295   return  
296
297
298 class PCB:
299  """ Models a digital electronics logic circuit printed on a circuit board  """
300  layout = None
301
302  def __init__(self):
303   """ """
304   return
305
306  def trace_add(self, trace_list):
307   """ Add a  Trace """
308   return
309  
310  def component_insert(self, component):
311   """ Adds a component to the circuit """
312   return
313