aboutsummaryrefslogtreecommitdiff
path: root/src/components/life.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/life.py')
-rw-r--r--src/components/life.py276
1 files changed, 150 insertions, 126 deletions
diff --git a/src/components/life.py b/src/components/life.py
index e19ed36..b3c2c58 100644
--- a/src/components/life.py
+++ b/src/components/life.py
@@ -1,16 +1,21 @@
-from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtWidgets import QUndoCommand
+from PyQt6 import QtGui, QtCore, QtWidgets
+from PyQt6.QtGui import QUndoCommand
from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
import os
import math
+import logging
+
from ..component import Component
from ..toolkit.frame import BlankFrame, scale
+log = logging.getLogger("AVP.Component.Life")
+
+
class Component(Component):
- name = 'Conway\'s Game of Life'
- version = '1.0.0'
+ name = "Conway's Game of Life"
+ version = "1.0.0"
def widget(self, *args):
super().widget(*args)
@@ -18,34 +23,50 @@ class Component(Component):
self.updateGridSize()
# The initial grid: a "Queen Bee Shuttle"
# https://conwaylife.com/wiki/Queen_bee_shuttle
- self.startingGrid = set([
- (3, 7), (3, 8),
- (4, 7), (4, 8),
- (8, 7),
- (9, 6), (9, 8),
- (10, 5), (10, 9),
- (11, 6), (11, 7), (11, 8),
- (12, 4), (12, 5), (12, 9), (12, 10),
- (23, 6), (23, 7),
- (24, 6), (24, 7)
- ])
+ self.startingGrid = set(
+ [
+ (3, 7),
+ (3, 8),
+ (4, 7),
+ (4, 8),
+ (8, 7),
+ (9, 6),
+ (9, 8),
+ (10, 5),
+ (10, 9),
+ (11, 6),
+ (11, 7),
+ (11, 8),
+ (12, 4),
+ (12, 5),
+ (12, 9),
+ (12, 10),
+ (23, 6),
+ (23, 7),
+ (24, 6),
+ (24, 7),
+ ]
+ )
# Amount of 'bleed' (off-canvas coordinates) on each side of the grid
self.bleedSize = 40
self.page.pushButton_pickImage.clicked.connect(self.pickImage)
- self.trackWidgets({
- 'tickRate': self.page.spinBox_tickRate,
- 'scale': self.page.spinBox_scale,
- 'color': self.page.lineEdit_color,
- 'shapeType': self.page.comboBox_shapeType,
- 'shadow': self.page.checkBox_shadow,
- 'customImg': self.page.checkBox_customImg,
- 'showGrid': self.page.checkBox_showGrid,
- 'image': self.page.lineEdit_image,
- }, colorWidgets={
- 'color': self.page.pushButton_color,
- })
+ self.trackWidgets(
+ {
+ "tickRate": self.page.spinBox_tickRate,
+ "scale": self.page.spinBox_scale,
+ "color": self.page.lineEdit_color,
+ "shapeType": self.page.comboBox_shapeType,
+ "shadow": self.page.checkBox_shadow,
+ "customImg": self.page.checkBox_customImg,
+ "showGrid": self.page.checkBox_showGrid,
+ "image": self.page.lineEdit_image,
+ },
+ colorWidgets={
+ "color": self.page.pushButton_color,
+ },
+ )
self.shiftButtons = (
self.page.toolButton_up,
self.page.toolButton_down,
@@ -56,7 +77,9 @@ class Component(Component):
def shiftFunc(i):
def shift():
self.shiftGrid(i)
+
return shift
+
shiftFuncs = [shiftFunc(i) for i in range(len(self.shiftButtons))]
for i, widget in enumerate(self.shiftButtons):
widget.clicked.connect(shiftFuncs[i])
@@ -66,8 +89,11 @@ class Component(Component):
def pickImage(self):
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
- self.page, "Choose Image", imgDir,
- "Image Files (%s)" % " ".join(self.core.imageFormats))
+ self.page,
+ "Choose Image",
+ imgDir,
+ "Image Files (%s)" % " ".join(self.core.imageFormats),
+ )
if filename:
self.settings.setValue("componentDir", os.path.dirname(filename))
self.mergeUndo = False
@@ -98,20 +124,20 @@ class Component(Component):
self.page.label_image.setVisible(False)
self.page.lineEdit_image.setVisible(False)
self.page.pushButton_pickImage.setVisible(False)
- enabled = (len(self.startingGrid) > 0)
+ enabled = len(self.startingGrid) > 0
for widget in self.shiftButtons:
widget.setEnabled(enabled)
def previewClickEvent(self, pos, size, button):
pos = (
math.ceil((pos[0] / size[0]) * self.gridWidth) - 1,
- math.ceil((pos[1] / size[1]) * self.gridHeight) - 1
+ math.ceil((pos[1] / size[1]) * self.gridHeight) - 1,
)
action = ClickGrid(self, pos, button)
self.parent.undoStack.push(action)
def updateGridSize(self):
- w, h = self.core.resolutions[-1].split('x')
+ w, h = self.core.resolutions[-1].split("x")
self.gridWidth = int(int(w) / self.scale)
self.gridHeight = int(int(h) / self.scale)
self.pxWidth = math.ceil(self.width / self.gridWidth)
@@ -125,10 +151,8 @@ class Component(Component):
self.tickGrids = {0: self.startingGrid}
def properties(self):
- if self.customImg and (
- not self.image or not os.path.exists(self.image)
- ):
- return ['error']
+ if self.customImg and (not self.image or not os.path.exists(self.image)):
+ return ["error"]
return []
def error(self):
@@ -162,42 +186,47 @@ class Component(Component):
drawer = ImageDraw.Draw(frame)
rect = (
(drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + self.pxHeight)
+ (drawPtX + self.pxWidth, drawPtY + self.pxHeight),
)
shape = self.page.comboBox_shapeType.currentText().lower()
# Rectangle
- if shape == 'rectangle':
+ if shape == "rectangle":
drawer.rectangle(rect, fill=self.color)
# Elliptical
- elif shape == 'elliptical':
+ elif shape == "elliptical":
drawer.ellipse(rect, fill=self.color)
tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
smallerShape = (
- (drawPtX + tenthX + int(tenthX / 4),
- drawPtY + tenthY + int(tenthY / 2)),
- (drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
- drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)))
+ (
+ drawPtX + tenthX + int(tenthX / 4),
+ drawPtY + tenthY + int(tenthY / 2),
+ ),
+ (
+ drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
+ drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)),
+ ),
)
outlineShape = (
- (drawPtX + int(tenthX / 4),
- drawPtY + int(tenthY / 2)),
- (drawPtX + self.pxWidth - int(tenthX / 4),
- drawPtY + self.pxHeight - int(tenthY / 2))
+ (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)),
+ (
+ drawPtX + self.pxWidth - int(tenthX / 4),
+ drawPtY + self.pxHeight - int(tenthY / 2),
+ ),
)
# Circle
- if shape == 'circle':
+ if shape == "circle":
drawer.ellipse(outlineShape, fill=self.color)
drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
# Lilypad
- elif shape == 'lilypad':
+ elif shape == "lilypad":
drawer.pieslice(smallerShape, 290, 250, fill=self.color)
# Pie
- elif shape == 'pie':
+ elif shape == "pie":
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
@@ -205,12 +234,15 @@ class Component(Component):
qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
# Path
- if shape == 'path':
+ if shape == "path":
drawer.ellipse(rect, fill=self.color)
rects = {
direction: False
for direction in (
- 'up', 'down', 'left', 'right',
+ "up",
+ "down",
+ "left",
+ "right",
)
}
for cell in self.nearbyCoords(x, y):
@@ -218,60 +250,59 @@ class Component(Component):
continue
if cell[0] == x:
if cell[1] < y:
- rects['up'] = True
+ rects["up"] = True
if cell[1] > y:
- rects['down'] = True
+ rects["down"] = True
if cell[1] == y:
if cell[0] < x:
- rects['left'] = True
+ rects["left"] = True
if cell[0] > x:
- rects['right'] = True
+ rects["right"] = True
for direction, rect in rects.items():
if rect:
- if direction == 'up':
+ if direction == "up":
sect = (
(drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + hY)
+ (drawPtX + self.pxWidth, drawPtY + hY),
)
- elif direction == 'down':
+ elif direction == "down":
sect = (
(drawPtX, drawPtY + hY),
- (drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ (
+ drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight,
+ ),
)
- elif direction == 'left':
+ elif direction == "left":
sect = (
(drawPtX, drawPtY),
- (drawPtX + hX,
- drawPtY + self.pxHeight)
+ (drawPtX + hX, drawPtY + self.pxHeight),
)
- elif direction == 'right':
+ elif direction == "right":
sect = (
(drawPtX + hX, drawPtY),
- (drawPtX + self.pxWidth,
- drawPtY + self.pxHeight)
+ (
+ drawPtX + self.pxWidth,
+ drawPtY + self.pxHeight,
+ ),
)
drawer.rectangle(sect, fill=self.color)
# Duck
- elif shape == 'duck':
+ elif shape == "duck":
duckHead = (
(drawPtX + qX, drawPtY + qY),
- (drawPtX + int(qX * 3), drawPtY + int(tY * 2))
+ (drawPtX + int(qX * 3), drawPtY + int(tY * 2)),
)
duckBeak = (
(drawPtX + hX, drawPtY + qY),
- (drawPtX + self.pxWidth + qX,
- drawPtY + int(qY * 3))
- )
- duckWing = (
- (drawPtX, drawPtY + hY),
- rect[1]
+ (drawPtX + self.pxWidth + qX, drawPtY + int(qY * 3)),
)
+ duckWing = ((drawPtX, drawPtY + hY), rect[1])
duckBody = (
(drawPtX + int(qX / 4), drawPtY + int(qY * 3)),
- (drawPtX + int(tX * 2), drawPtY + self.pxHeight)
+ (drawPtX + int(tX * 2), drawPtY + self.pxHeight),
)
drawer.ellipse(duckBody, fill=self.color)
drawer.ellipse(duckHead, fill=self.color)
@@ -279,11 +310,16 @@ class Component(Component):
drawer.pieslice(duckBeak, 145, 200, fill=self.color)
# Peace
- elif shape == 'peace':
- line = ((
- drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
- (drawPtX + hX + int(tenthX / 2),
- drawPtY + self.pxHeight - int(tenthY / 2))
+ elif shape == "peace":
+ line = (
+ (
+ drawPtX + hX - int(tenthX / 2),
+ drawPtY + int(tenthY / 2),
+ ),
+ (
+ drawPtX + hX + int(tenthX / 2),
+ drawPtY + self.pxHeight - int(tenthY / 2),
+ ),
)
drawer.ellipse(outlineShape, fill=self.color)
drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
@@ -291,21 +327,15 @@ class Component(Component):
def slantLine(difference):
return (
- (drawPtX + difference), (drawPtY + self.pxHeight - qY)
+ (drawPtX + difference),
+ (drawPtY + self.pxHeight - qY),
), (
- (drawPtX + hX), (drawPtY + hY)
+ (drawPtX + hX),
+ (drawPtY + hY),
)
- drawer.line(
- slantLine(qX),
- fill=self.color,
- width=tenthX
- )
- drawer.line(
- slantLine(self.pxWidth - qX),
- fill=self.color,
- width=tenthX
- )
+ drawer.line(slantLine(qX), fill=self.color, width=tenthX)
+ drawer.line(slantLine(self.pxWidth - qX), fill=self.color, width=tenthX)
for x, y in grid:
drawPtX = x * self.pxWidth
@@ -331,44 +361,38 @@ class Component(Component):
w, h = scale(0.05, self.width, self.height, int)
for x in range(self.pxWidth, self.width, self.pxWidth):
drawer.rectangle(
- ((x, 0),
- (x + w, self.height)),
+ ((x, 0), (x + w, self.height)),
fill=self.color,
)
for y in range(self.pxHeight, self.height, self.pxHeight):
drawer.rectangle(
- ((0, y),
- (self.width, y + h)),
+ ((0, y), (self.width, y + h)),
fill=self.color,
)
return frame
def gridForTick(self, tick):
- '''
+ """
Given a tick number over 0, returns a new grid (a set of tuples).
This must compute the previous ticks' grids if not already computed
- '''
+ """
if tick - 1 not in self.tickGrids:
self.tickGrids[tick - 1] = self.gridForTick(tick - 1)
-
+
lastGrid = self.tickGrids[tick - 1]
def neighbours(x, y):
- return {
- cell for cell in self.nearbyCoords(x, y)
- if cell in lastGrid
- }
+ return {cell for cell in self.nearbyCoords(x, y) if cell in lastGrid}
newGrid = set()
# Copy cells from the previous grid if they have 2 or 3 neighbouring cells
# and if they are within the grid or its bleed area (off-canvas area)
for x, y in lastGrid:
if (
- -self.bleedSize > x > self.gridWidth + self.bleedSize
- or
- -self.bleedSize > y > self.gridHeight + self.bleedSize
- ):
+ -self.bleedSize > x > self.gridWidth + self.bleedSize
+ or -self.bleedSize > y > self.gridHeight + self.bleedSize
+ ):
continue
surrounding = len(neighbours(x, y))
if surrounding == 2 or surrounding == 3:
@@ -376,7 +400,8 @@ class Component(Component):
# Find positions around living cells which must be checked for reproduction
potentialNewCells = {
- coordTup for origin in lastGrid
+ coordTup
+ for origin in lastGrid
for coordTup in list(self.nearbyCoords(*origin))
}
# Check for reproduction
@@ -392,11 +417,11 @@ class Component(Component):
def savePreset(self):
pr = super().savePreset()
- pr['GRID'] = sorted(self.startingGrid)
+ pr["GRID"] = sorted(self.startingGrid)
return pr
def loadPreset(self, pr, *args):
- self.startingGrid = set(pr['GRID'])
+ self.startingGrid = set(pr["GRID"])
if self.startingGrid:
for widget in self.shiftButtons:
widget.setEnabled(True)
@@ -414,15 +439,17 @@ class Component(Component):
class ClickGrid(QUndoCommand):
- def __init__(self, comp, pos, id_):
- super().__init__(
- "click %s component #%s" % (comp.name, comp.compPos))
+ def __init__(self, comp, pos, button):
+ super().__init__("click %s component #%s" % (comp.name, comp.compPos))
self.comp = comp
self.pos = [pos]
- self.id_ = id_
+ if button == QtCore.Qt.MouseButton.RightButton:
+ self.button = 2
+ else:
+ self.button = 1
def id(self):
- return self.id_
+ return self.button
def mergeWith(self, other):
self.pos.extend(other.pos)
@@ -439,21 +466,21 @@ class ClickGrid(QUndoCommand):
self.comp.update(auto=True)
def redo(self):
- if self.id_ == 1: # Left-click
+ if self.button == 1: # Left-click
self.add()
- elif self.id_ == 2: # Right-click
+ elif self.button == 2: # Right-click
self.remove()
def undo(self):
- if self.id_ == 1: # Left-click
+ if self.button == 1: # Left-click
self.remove()
- elif self.id_ == 2: # Right-click
+ elif self.button == 2: # Right-click
self.add()
+
class ShiftGrid(QUndoCommand):
def __init__(self, comp, direction):
- super().__init__(
- "change %s component #%s" % (comp.name, comp.compPos))
+ super().__init__("change %s component #%s" % (comp.name, comp.compPos))
self.comp = comp
self.direction = direction
self.distance = 1
@@ -466,10 +493,7 @@ class ShiftGrid(QUndoCommand):
return True
def newGrid(self, Xchange, Ychange):
- return {
- (x + Xchange, y + Ychange)
- for x, y in self.comp.startingGrid
- }
+ return {(x + Xchange, y + Ychange) for x, y in self.comp.startingGrid}
def redo(self):
if self.direction == 0: