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