From 6870e43a9024723eb877e48e6d84c18fdf890721 Mon Sep 17 00:00:00 2001 From: Dylan Smith <69414500+QuantumFox42@users.noreply.github.com> Date: Tue, 24 Oct 2023 20:39:01 +0100 Subject: [PATCH] (v2) Rewrote Entire Program --- README.md | 7 +- source/{ => assets/images}/bin.png | Bin source/{ => assets/images}/denali.png | Bin source/{ => assets/images}/gates/and.png | Bin source/{ => assets/images}/gates/not.png | Bin source/{ => assets/images}/gates/or.png | Bin source/{ => assets/images}/gates/push.png | Bin source/{ => assets/images}/gates/xor.png | Bin source/denali.py | 327 -------------------- source/denali2.py | 344 ++++++++++++++++++++++ 10 files changed, 348 insertions(+), 330 deletions(-) rename source/{ => assets/images}/bin.png (100%) rename source/{ => assets/images}/denali.png (100%) rename source/{ => assets/images}/gates/and.png (100%) rename source/{ => assets/images}/gates/not.png (100%) rename source/{ => assets/images}/gates/or.png (100%) rename source/{ => assets/images}/gates/push.png (100%) rename source/{ => assets/images}/gates/xor.png (100%) delete mode 100644 source/denali.py create mode 100644 source/denali2.py diff --git a/README.md b/README.md index 731c273..2609b82 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# Denali -Logic Gate Simulator (STILL VERY ALPHA) +# Denali LOGIC v2 +Logic Gate Simulator ![denali](https://repository-images.githubusercontent.com/403918808/3f29e36b-64ff-427a-a5c4-9074ee1d4e45) Controls: ```TAB```: Access inventory. - ```LEFT MOUSE BUTTON``` + ```DRAG```: Move gate when on single gate, or all gates if on background. + ```LEFT MOUSE BUTTON```: Toggle Input Switch. + ```LEFT MOUSE BUTTON``` + ```DRAG```: Move gate when on single gate, or view if on background. ```RIGHT MOUSE BUTTON``` + ```DRAG```: Creates wire if from an I/O to another I/O, or remove wires if used on background. diff --git a/source/bin.png b/source/assets/images/bin.png similarity index 100% rename from source/bin.png rename to source/assets/images/bin.png diff --git a/source/denali.png b/source/assets/images/denali.png similarity index 100% rename from source/denali.png rename to source/assets/images/denali.png diff --git a/source/gates/and.png b/source/assets/images/gates/and.png similarity index 100% rename from source/gates/and.png rename to source/assets/images/gates/and.png diff --git a/source/gates/not.png b/source/assets/images/gates/not.png similarity index 100% rename from source/gates/not.png rename to source/assets/images/gates/not.png diff --git a/source/gates/or.png b/source/assets/images/gates/or.png similarity index 100% rename from source/gates/or.png rename to source/assets/images/gates/or.png diff --git a/source/gates/push.png b/source/assets/images/gates/push.png similarity index 100% rename from source/gates/push.png rename to source/assets/images/gates/push.png diff --git a/source/gates/xor.png b/source/assets/images/gates/xor.png similarity index 100% rename from source/gates/xor.png rename to source/assets/images/gates/xor.png diff --git a/source/denali.py b/source/denali.py deleted file mode 100644 index 6900799..0000000 --- a/source/denali.py +++ /dev/null @@ -1,327 +0,0 @@ -import pygame -from math import sqrt, sin, cos, radians, degrees, atan -from shapely.geometry import Point -from shapely.geometry.polygon import Polygon -from numpy import clip -from glob import glob -from os import path, remove -from time import time -from json import dumps, loads - -# store wires from inputs in gate instead?! good idea maybe!!!? - -targetFPS = 150 -displayFPS = True -backgroundColour = (31, 32, 38) -inventoryRadius = 110 - -portSize = 7 -portSelectionSize=15 -portColour = (127, 127, 133) -portBorderColour = [clip(value+25, 0, 255) for value in portColour] -inputSpread = 25 - -gateSize = 50 -gateColour = (112, 119, 130) -gateShadowColour = [clip(value-20, 0, 255) for value in gateColour] - -buttonSize = 30 -wireOffColour = (66,111,122) #(52, 99, 115) -wireOnColour = (109,163,176) #(89, 173, 201) -buttonBorderColour = [clip(value-60, 0, 255) for value in gateColour] - -pygame.init() -pygame.font.init() -screen = pygame.display.set_mode((400,400), pygame.RESIZABLE) -pygame.display.set_caption("Denali LOGIC") -pygame.display.set_icon(pygame.image.load("denali.png")) -clock = pygame.time.Clock() -running = True - -gateImages = {path.basename(image.removesuffix(".png")).upper():pygame.image.load(image) for image in glob("gates/*.png")} -gateTypes = list(gateImages.keys()) - -gates = {} -wires = {} - -gateCount = 0 - -binImage = pygame.image.load("bin.png") - -AND = lambda inputs:inputs[1]["active"] and inputs[2]["active"] -OR = lambda inputs: inputs[1]["active"] or inputs[2]["active"] -XOR = lambda inputs: inputs[1]["active"] != inputs[2]["active"] -NOT = lambda inputs: not inputs[1]["active"] - -tickFunctions = { - "AND": AND, - "OR": OR, - "XOR": XOR, - "NOT": NOT -} - -def createNewGate(gateType): - gate = { - "gateType": gateType, - "position": [0,0], - "drag": False, - "inputs": {} if gateType in ["PUSH", "TOGGLE"] else {1:{"angle":180, "active":False}} if gateType == "NOT" else {1:{"angle":180+inputSpread, "active":False}, 2:{"angle":180-inputSpread, "active":False}}, - "output": False, - "tick": tickFunctions[gateType] if gateType in tickFunctions.keys() else None - } - global gateCount - gateID = gateCount - gateCount += 1 - return gateID, gate - -touchingPoint = lambda pos, pointPos, radius: sqrt(abs(pos[0] - pointPos[0])**2+abs(pos[1] - pointPos[1])**2) < radius -circlePointLocation = lambda pos, angle, radius: [(radius * cos(angle))+pos[0], (radius * sin(angle))+pos[1]] - -# VERY MESSY FUNCTION, PLEASE FIX -angleBetweenPoints = lambda point1, point2: degrees(atan((point2[0]-point1[0])/(point1[1]-point2[1]))) + (90 if (point2[0]-point1[0])>0 else -90) + (180 if (point2[0]-point1[0])>0!=(point2[1]-point1[1])>0 else 180 if (point2[0]-point1[0])<0 and (point2[1]-point1[1])<0 else 0) - -dragging = False -drawing = False -drawingfrompoint = False -wireDelete = False -viewDrag = False -pressingButton = False - -inventoryGates = {} - -fpstimer = time() -fpscount = 0 -fps = targetFPS -fpscountamount = 75 - -font = pygame.font.SysFont('Consolas', 15) - -gateIOLocations = {} - -while running: - gatesToRemove = [] - wiresToRemove = [] - - mouseX, mouseY = pygame.mouse.get_pos() - - fpscount += 1 - if fpscount == fpscountamount: - fpscount = 0 - fps = 1/((time()-fpstimer)/fpscountamount) - fpstimer = time() - - # COMPUTE - for event in pygame.event.get(): - # End if window X is pressed - if event.type == pygame.QUIT: - running = False - - # Open Inventory - if event.type == pygame.KEYDOWN and event.key == pygame.K_TAB and not dragging and not drawing: - dragging = True - inventoryGates = {gateID:gate for gateID, gate in [createNewGate(gateType) for gateType in gateTypes]} - for gateID, gate in inventoryGates.items(): - inventoryGates[gateID]["position"] = circlePointLocation((mouseX, mouseY), radians((list(inventoryGates.keys()).index(gateID)/len(inventoryGates))*360-90), inventoryRadius) - gateIOLocations[gateID] = { - "inputs": {InputID:circlePointLocation(gate["position"], radians(Input["angle"]), gateSize) for InputID, Input in gate["inputs"].items()}, - "output": circlePointLocation(gate["position"], 0, gateSize) - } - - # Close Inventory And Choose Gate - elif event.type == pygame.KEYUP and event.key == pygame.K_TAB and not drawing: - for gateID, gate in inventoryGates.items(): - if touchingPoint((mouseX, mouseY), gate["position"], gateSize): - gates[gateID] = gate - inventoryGates.clear() - dragging = False - - for gateID, gate in gates.items(): - gateIOLocations[gateID] = { - "inputs": {InputID:circlePointLocation(gate["position"], radians(Input["angle"]), gateSize) for InputID, Input in gate["inputs"].items()}, - "output": circlePointLocation(gate["position"], 0, gateSize) - } - - if gate["tick"]: gate["output"] = gate["tick"](gate["inputs"]) - - if event.type == pygame.MOUSEBUTTONDOWN: - if event.button == 3: - for InputID, Input in gate["inputs"].items(): - if touchingPoint((mouseX, mouseY), gateIOLocations[gateID]["inputs"][InputID], portSelectionSize): - drawing = True - drawingfrompoint = {"gateID":gateID, "inputID": InputID} - break - - if not drawing: - if touchingPoint((mouseX, mouseY), gateIOLocations[gateID]["output"], portSelectionSize): - drawing = True - drawingfrompoint = {"gateID":gateID, "inputID": False} - - elif event.button == 1 and not dragging: - if gate["gateType"] == "PUSH" and touchingPoint((mouseX, mouseY), gate["position"], buttonSize+5): - gates[gateID]["output"] = True - dragging = True - pressingButton = True - elif touchingPoint((mouseX, mouseY), gate["position"], gateSize): - dragging = True - gate["drag"] = True - gate["offsetX"] = gate["position"][0] - mouseX - gate["offsetY"] = gate["position"][1] - mouseY - - elif event.type == pygame.MOUSEBUTTONUP: - if event.button == 3 and drawing: - drawnTo = False - - for InputID, Input in gate["inputs"].items(): - if touchingPoint((mouseX, mouseY), gateIOLocations[gateID]["inputs"][InputID], portSelectionSize): - drawnTo = {"gateID":gateID, "inputID": InputID} - break - - if not drawnTo: - if touchingPoint((mouseX, mouseY), gateIOLocations[gateID]["output"], portSelectionSize): - drawnTo = {"gateID":gateID, "inputID": False} - - if drawnTo and drawingfrompoint and bool(drawingfrompoint["inputID"]) != bool(drawnTo["inputID"]) and drawingfrompoint["gateID"] != drawnTo["gateID"]: - if drawingfrompoint["inputID"] == False: - wires[dumps(drawnTo)] = drawingfrompoint - else: - wires[dumps(drawingfrompoint)] = drawnTo - - elif event.button == 1 and gate["drag"]: - gate["drag"] = False - if not viewDrag and 0 <= gate["position"][0] <= 100 and pygame.display.get_surface().get_size()[1] >= gate["position"][1] >= pygame.display.get_surface().get_size()[1]-105 and dragging: - gatesToRemove.append(gateID) - - elif event.button == 1: - if gate["gateType"] == "PUSH": - gates[gateID]["output"] = False - dragging = False - pressingButton = False - - elif event.type == pygame.MOUSEMOTION and gate["drag"]: - gate["position"][0] = mouseX + gate["offsetX"] - gate["position"][1] = mouseY + gate["offsetY"] - gateIOLocations[gateID] = { - "inputs": {InputID:circlePointLocation(gate["position"], radians(Input["angle"]), gateSize) for InputID, Input in gate["inputs"].items()}, - "output": circlePointLocation(gate["position"], 0, gateSize) - } - - if not dragging and event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: - for gateID, gate in gates.items(): - gate["drag"] = True - gate["offsetX"] = gate["position"][0] - mouseX - gate["offsetY"] = gate["position"][1] - mouseY - dragging = True - viewDrag = True - - if not dragging and not drawing and event.type == pygame.MOUSEBUTTONDOWN and event.button == 3: - wireDelete = True - dragging = True - drawing = True - - if wireDelete: - for wireInput,wireOutput in wires.items(): - wireInputLoaded = loads(wireInput) - - angle = angleBetweenPoints(gateIOLocations[wireInputLoaded["gateID"]]["inputs"][wireInputLoaded["inputID"]], gateIOLocations[wireOutput["gateID"]]["output"]) - - vertexes = [circlePointLocation(gateIOLocations[wireInputLoaded["gateID"]]["inputs"][wireInputLoaded["inputID"]], radians(angle+45), 10), - circlePointLocation(gateIOLocations[wireInputLoaded["gateID"]]["inputs"][wireInputLoaded["inputID"]], radians(angle-45), 10), - circlePointLocation(gateIOLocations[wireOutput["gateID"]]["output"], radians(angle-180+45), 10), - circlePointLocation(gateIOLocations[wireOutput["gateID"]]["output"], radians(angle-180-45), 10)] - - if Polygon(vertexes).contains(Point(mouseX,mouseY)): - wiresToRemove.append(wireInput) - - if event.type == pygame.MOUSEBUTTONUP and event.button == 3 and drawing: - wireDelete = False - drawing = False - dragging = False - drawingfrompoint = False - - if event.type == pygame.MOUSEBUTTONUP and event.button == 1 and dragging: - dragging = False - viewDrag = False - - for gateID in gatesToRemove: - gates.pop(gateID) - - for wireInput,wireOutput in wires.items(): - wireInputLoaded = loads(wireInput) - if wireInputLoaded["gateID"] in gatesToRemove or wireOutput["gateID"] in gatesToRemove: - wiresToRemove.append(wireInput) - else: - gates[wireInputLoaded["gateID"]]["inputs"][wireInputLoaded["inputID"]]["active"] = gates[wireOutput["gateID"]]["output"] - - for wireInput in wiresToRemove: - wires.pop(wireInput) - - # CLEANS UP CURRENT ACTION VARIABLES (FIXES BUG OF ACTIONS REMAINING ACTIVE AFTER RELEASE SOMETIMES FOR SOME REASON) - # if not pygame.mouse.get_pressed(num_buttons=3)[0] and not pygame.mouse.get_pressed(num_buttons=3)[2]: - # dragging = False - # drawing = False - # drawingfrompoint = False - # wireDelete = False - # viewDrag = False - # pressingButton = False - - - # RENDER - - screen.fill(backgroundColour) - - allGates = gates|inventoryGates - for gateID, gate in allGates.items(): - # SHADOW - pygame.draw.circle(screen, gateShadowColour, gate["position"], gateSize+2) - pygame.draw.circle(screen, gateShadowColour, [value + 3 for value in gate["position"]], gateSize) - - # MAIN - pygame.draw.circle(screen, gateColour, gate["position"], gateSize) - - # IMAGE - image = gateImages[gate["gateType"]] - imageLocation = [value - gateSize/2 for value in gate["position"]] - imageLocation[0] -= 15 # X ADJUSTMENT - imageLocation[1] += 5 # Y ADJUSTMENT - screen.blit(image, imageLocation) - - # INPUTS - for InputID, Input in gate["inputs"].items(): - location = gateIOLocations[gateID]["inputs"][InputID] - pygame.draw.circle(screen, portBorderColour, location, portSize+2) - pygame.draw.circle(screen, portColour, location, portSize) - - # OUTPUT - location = gateIOLocations[gateID]["output"] - pygame.draw.circle(screen, portBorderColour, location, portSize+2) - pygame.draw.circle(screen, portColour, location, portSize) - - # BUTTONS - if gate["gateType"] == "PUSH": pygame.draw.circle(screen, buttonBorderColour, gate["position"], buttonSize+5) - if gate["gateType"] == "PUSH": pygame.draw.circle(screen, wireOnColour if gate["output"] else wireOffColour, gate["position"], buttonSize) - - if drawingfrompoint: - gate = gates[drawingfrompoint["gateID"]] - location = circlePointLocation(gate["position"], radians(gate["inputs"][drawingfrompoint["inputID"]]["angle"] if drawingfrompoint["inputID"] else 0), gateSize) - pygame.draw.line(screen, wireOffColour if drawingfrompoint["inputID"]!=False else wireOnColour if gates[drawingfrompoint["gateID"]]["output"] == True else wireOffColour, location, pygame.mouse.get_pos()) - - # WIRES - for wireInput,wireOutput in wires.items(): - wireInputLoaded = loads(wireInput) - - pygame.draw.line(screen, wireOnColour if gates[wireOutput["gateID"]]["output"] == True else wireOffColour, - gateIOLocations[wireInputLoaded["gateID"]]["inputs"][wireInputLoaded["inputID"]], - gateIOLocations[wireOutput["gateID"]]["output"] - ) - - # BIN - screen.blit(binImage, (0, pygame.display.get_surface().get_size()[1]-105)) - - #FPS COUNT - if displayFPS: screen.blit(font.render(f'FPS: {round(fps)}', False, (255, 255, 255)),(0,0)) - - pygame.display.flip() - - clock.tick(targetFPS) - -pygame.quit() diff --git a/source/denali2.py b/source/denali2.py new file mode 100644 index 0000000..cca86bb --- /dev/null +++ b/source/denali2.py @@ -0,0 +1,344 @@ +import pygame +from numpy import array, transpose, dot +from os import path, listdir +from math import sin, cos, pi, sqrt, asin + +# Constant Variables +IMAGES_DIRECTORY = "assets\images" + +INITIAL_WINDOW_SIZE = (900, 900) + +TARGET_FPS = 150 + +BACKGROUND_COLOUR = (31, 32, 38) +GATE_SIZE = 50 +GATE_COLOUR = (112, 119, 130) +GATE_SHADOW_COLOUR = (92, 99, 110) + +PORT_SIZE = 7 +PORT_SELECTION_SIZE=15 +PORT_COLOUR = (127, 127, 133) +PORT_BORDER_COLOUR = (152, 152, 158) +INPUT_PORT_SPREAD = (5 * pi) / 18 + +WIRE_THICKNESS = 5 +WIRE_DELETE_SIZE = 10 +WIRE_OFF_COLOUR = (66,111,122) +WIRE_ON_COLOUR = (124,178,191) + +BIN_SIZE = 35 + +INVENTORY_RADIUS = 115 +INVENTORY_ANGLE_OFFSET = pi/2 + +GATE_TYPES = ["AND", "OR", "XOR", "NOT", "IO"] + +INPUT_OUTPUT_LIGHT_SIZE=30 + +# Lambda Functions +getDistance = lambda pos1, pos2: sqrt((pos1[0] - pos2[0])**2 + (pos1[1] - pos2[1])**2) + +# Load Images +gateImages = { + path.basename(image.removesuffix(".png")).upper(): + pygame.image.load(path.join(IMAGES_DIRECTORY, "Gates", image)) + for image in listdir(path.join(IMAGES_DIRECTORY,"Gates")) + } +binImage = pygame.image.load(path.join(IMAGES_DIRECTORY,"bin.png")) +denaliImage = pygame.image.load(path.join(IMAGES_DIRECTORY, "denali.png")) + +# Boolean Logic Functions for Gate Tick +tickFunctions = { + "AND" : lambda inputs: inputs[0].state and inputs[1].state, + "OR" : lambda inputs: inputs[0].state or inputs[1].state, + "XOR" : lambda inputs: inputs[0].state ^ inputs[1].state, + "NOT" : lambda inputs: not inputs[0].state +} + +# Visual Offsets for Gate Elements +inputOffsets = [ + [ + [-GATE_SIZE, 0] + ], + [ + [-(cos(INPUT_PORT_SPREAD) * GATE_SIZE), -(sin(INPUT_PORT_SPREAD) * GATE_SIZE)], + [-(cos(INPUT_PORT_SPREAD) * GATE_SIZE), sin(INPUT_PORT_SPREAD) * GATE_SIZE] + ] + ] + +outputOffset = [GATE_SIZE, 0] + +class NullInput: + def __init__(self): + self.state = False + +falseInput = NullInput() + +class Gate: + def __init__(self, gateType, position): + self.position = position + + self.type = gateType + self.state = False + self.inputs = [falseInput for i in range(1 if gateType == "NOT" else 2)] + self.tickFunction = tickFunctions[gateType] + + self.dragging = False + self.delete = False + + def tick(self): + self.state = self.tickFunction(self.inputs) + +class InputOutput: + def __init__(self,position): + self.position = position + + self.differentiated = False + self.state = False + self.inputs = [falseInput] + + self.dragging = False + self.delete = False + + def tick(self): + if self.differentiated == "Output": + self.state = self.inputs[0].state + + elif self.inputs[0].__class__ != NullInput: + self.differentiated = "Output" + +# Setup pygame +pygame.init() +pygame.font.init() +screen = pygame.display.set_mode(INITIAL_WINDOW_SIZE, pygame.RESIZABLE) +pygame.display.set_caption("Denali LOGIC v2") +pygame.display.set_icon(denaliImage) +clock = pygame.time.Clock() +font = pygame.font.SysFont('Consolas', 15) + +# Setup Logic Simulator Variables +gates = [] +inventoryGates = [] + +running = True + +flags = { + "viewDrag": False, + "gateDrag": False, + "wireDeleteDrag": False, + "inventoryDisplayed": False, + } + +initialCursorPosition = array(pygame.mouse.get_pos()) +viewOffset = array([0,0]) +initialViewOffset = viewOffset +gateDragOffset = array([0,0]) + +wireOrigin = None + +while running: + actualCursorPosition = array(pygame.mouse.get_pos()) + cursorPosition = array(pygame.mouse.get_pos()) - viewOffset + + # Compute + if flags["viewDrag"]: viewOffset = initialViewOffset + actualCursorPosition - initialCursorPosition + + for gate in gates: + gate.tick() + + # Move gate if dragging gate + if gate.dragging: + gate.position = cursorPosition + gateDragOffset + if not flags["gateDrag"]: + gate.dragging = False + if getDistance(actualCursorPosition, (50, pygame.display.get_surface().get_size()[1]-55)) < BIN_SIZE: gate.delete = True + + # Delete gate if marked for deletion + if gate.delete: gates.remove(gate) + + # Delete Wires Near Cursor + if flags["wireDeleteDrag"]: + for index, gateInput in enumerate(gate.inputs): + if gateInput.__class__ != NullInput: + startPoint = array(gateInput.position) + array(outputOffset) + endPoint = array(gate.position) + array(inputOffsets[len(gate.inputs)-1][index]) + + lineDistance = getDistance(startPoint, endPoint) + + yDiffDistanceRatio = (((startPoint[1] - endPoint[1]) / lineDistance) * (-1 if startPoint[0] <= endPoint[0] else 1)) + + rotationMatrix = array([ + [cos(asin(yDiffDistanceRatio)), yDiffDistanceRatio], + [-yDiffDistanceRatio, cos(asin(yDiffDistanceRatio))] + ]) + + transformedCursorPosition = dot(rotationMatrix, transpose(cursorPosition-(0.5 * (endPoint + startPoint)))) + + if -(lineDistance/2) < transformedCursorPosition[0] < (lineDistance/2) and -(WIRE_DELETE_SIZE/2) < transformedCursorPosition[1] < (WIRE_DELETE_SIZE/2): + gate.inputs[index] = falseInput + + for event in pygame.event.get(): + if event.type == pygame.QUIT: running = False + if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP: + # Inventory + if event.key == pygame.K_TAB: + if event.type == pygame.KEYDOWN and not any(flags.values()): + flags["inventoryDisplayed"] = True + for index, gateType in enumerate(GATE_TYPES): + rotationAngle = ((2*pi*index) / len(GATE_TYPES)) + INVENTORY_ANGLE_OFFSET + rotationMatrix = array([ + [cos(rotationAngle), sin(rotationAngle)], + [-sin(rotationAngle), cos(rotationAngle)] + ]) + position = transpose(transpose(array([cursorPosition])) + dot(rotationMatrix, array([[INVENTORY_RADIUS], [0]])))[0] + inventoryGates.append(Gate(gateType, position) if gateType != "IO" else InputOutput(position)) + if event.type == pygame.KEYUP: + flags["inventoryDisplayed"] = False + for index, gate in enumerate(inventoryGates): + if getDistance(cursorPosition, gate.position) < GATE_SIZE: + gates.append(inventoryGates.pop(index)) + break + inventoryGates.clear() + + elif event.type == pygame.MOUSEBUTTONDOWN and not any(flags.values()): + if event.button == 3: + # Create Wire Origin + for gate in gates: + outputPosition = array(gate.position) + array(outputOffset) + if getDistance(cursorPosition, outputPosition) < PORT_SELECTION_SIZE: + wireOrigin = {"Start":False, "Gate": gate, "GateInputIndex": None} + else: + for gateInputIndex in range(len(gate.inputs)): + inputPosition = array(gate.position) + array(inputOffsets[len(gate.inputs)-1][gateInputIndex]) + if getDistance(cursorPosition, inputPosition) < PORT_SELECTION_SIZE: + wireOrigin = {"Start":True, "Gate": gate, "GateInputIndex": gateInputIndex} + break + if wireOrigin: break + + # Activate Wire Delete Drag + if not wireOrigin: flags["wireDeleteDrag"] = True + + elif event.button == 1: + # Activate View Drag + if not any([getDistance(cursorPosition, gate.position) < GATE_SIZE for gate in gates]): + flags["viewDrag"] = True + initialCursorPosition = actualCursorPosition + initialViewOffset = viewOffset + # Activate Gate Drag + else: + for index, gate in enumerate(gates): + if getDistance(cursorPosition, gate.position) < GATE_SIZE: + if gate.__class__ == InputOutput and gate.differentiated == "Input" and getDistance(cursorPosition, gate.position) < INPUT_OUTPUT_LIGHT_SIZE: + gate.state = not gate.state + break + flags["gateDrag"] = True + gate.dragging = True + gateDragOffset = array(gate.position) - cursorPosition + gates.append(gates.pop(index)) + break + + elif event.type == pygame.MOUSEBUTTONUP: + if event.button == 3: + # Create Wire + if wireOrigin: + if wireOrigin["Start"]: + for gate in gates: + outputPosition = array(gate.position) + array(outputOffset) + if getDistance(cursorPosition, outputPosition) < PORT_SELECTION_SIZE: + if gate == wireOrigin["Gate"]: break + wireOrigin["Gate"].inputs[wireOrigin["GateInputIndex"]] = gate + + # Differentiate Input / Output + if gate.__class__ == InputOutput: gate.differentiated = "Input" + + break + else: + for gate in gates: + for gateInputIndex in range(len(gate.inputs)): + inputPosition = array(gate.position) + array(inputOffsets[len(gate.inputs)-1][gateInputIndex]) + if getDistance(cursorPosition, inputPosition) < PORT_SELECTION_SIZE: + if gate == wireOrigin["Gate"]: break + gate.inputs[gateInputIndex] = wireOrigin["Gate"] + + # Differentiate Input / Output + if wireOrigin["Gate"].__class__ == InputOutput: wireOrigin["Gate"].differentiated = "Input" + + break + wireOrigin = None + # Deactivate Wire Delete Drag + elif flags["wireDeleteDrag"]: flags["wireDeleteDrag"] = False + + if event.button == 1: + # Deactivate View Drag + if flags["viewDrag"]: flags["viewDrag"] = False + # Deactivate Gate Drag + else: flags["gateDrag"] = False + + + + + + # Render + screen.fill(BACKGROUND_COLOUR) + + for gate in gates + inventoryGates: + + # Render Gate Shadow + pygame.draw.circle(screen, GATE_SHADOW_COLOUR, gate.position + viewOffset, GATE_SIZE+2) + pygame.draw.circle(screen, GATE_SHADOW_COLOUR, [value + 3 for value in gate.position + viewOffset], GATE_SIZE) + + # Render Gate Main Body + pygame.draw.circle(screen, GATE_COLOUR, gate.position + viewOffset, GATE_SIZE) + + # Input / Output Gates Only + if gate.__class__ == InputOutput: + # Render Input / Output Centre + pygame.draw.circle(screen, GATE_SHADOW_COLOUR, gate.position + viewOffset, INPUT_OUTPUT_LIGHT_SIZE+2) + pygame.draw.circle(screen, WIRE_ON_COLOUR if gate.state else WIRE_OFF_COLOUR, gate.position + viewOffset, INPUT_OUTPUT_LIGHT_SIZE) + + # Logical Gates Only + else: + # Render Gate Image + image = gateImages[gate.type] + imageLocation = array([value - (GATE_SIZE/2) for value in gate.position + viewOffset]) + array([-15,5]) + screen.blit(image, imageLocation) + + # Render Gate Ports + if gate.__class__ == Gate or gate.differentiated != "Input": + for offset in inputOffsets[len(gate.inputs)-1]: + inputPosition = array(gate.position + viewOffset) + array(offset) + pygame.draw.circle(screen, PORT_BORDER_COLOUR, inputPosition, PORT_SIZE+2) + pygame.draw.circle(screen, PORT_COLOUR, inputPosition, PORT_SIZE) + + if gate.__class__ == Gate or gate.differentiated != "Output": + outputPosition = array(gate.position + viewOffset) + array(outputOffset) + pygame.draw.circle(screen, PORT_BORDER_COLOUR, outputPosition, PORT_SIZE+2) + pygame.draw.circle(screen, PORT_COLOUR, outputPosition, PORT_SIZE) + + for index, inputGate in enumerate(gate.inputs): + if inputGate.__class__ != NullInput: + # Null Deleted Inputs + if inputGate.delete: + gate.inputs[index] = falseInput + continue + # Render Wires + pygame.draw.line(screen, WIRE_ON_COLOUR if inputGate.state else WIRE_OFF_COLOUR, + array(inputGate.position) + array(outputOffset) + viewOffset, + array(gate.position) + array(inputOffsets[len(gate.inputs)-1][index]) + viewOffset, WIRE_THICKNESS) + + # Render Drawn Wire + if wireOrigin: + startPosition = (array(wireOrigin["Gate"].position) + + array(outputOffset if not wireOrigin["Start"] else inputOffsets[len(wireOrigin["Gate"].inputs)-1][wireOrigin["GateInputIndex"]]) + + viewOffset) + pygame.draw.line(screen, WIRE_ON_COLOUR if not wireOrigin["Start"] and wireOrigin["Gate"].state else WIRE_OFF_COLOUR, + startPosition, + actualCursorPosition, WIRE_THICKNESS) + + # Bin + screen.blit(binImage, (0, pygame.display.get_surface().get_size()[1]-105)) + + clock.tick(TARGET_FPS) + pygame.display.flip() + +pygame.quit()