I'm trying to adapt this PyQt implementation of FlowLayout to allow vertical flow as well as horizontal. This is my current implementation:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class FlowLayout(QLayout):
def __init__(self, orientation=Qt.Horizontal, parent=None, margin=0, spacing=-1):
super().__init__(parent)
self.orientation = orientation
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.itemList = []
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
def addItem(self, item):
self.itemList.append(item)
def count(self):
return len(self.itemList)
def itemAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList[index]
return None
def takeAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList.pop(index)
return None
def expandingDirections(self):
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self):
return self.orientation == Qt.Horizontal
def heightForWidth(self, width):
return self.doLayout(QRect(0, 0, width, 0), True)
def hasWidthForHeight(self):
return self.orientation == Qt.Vertical
def widthForHeight(self, height):
return self.doLayout(QRect(0, 0, 0, height), True)
def setGeometry(self, rect):
super().setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
margin, _, _, _ = self.getContentsMargins()
size += QSize(2 * margin, 2 * margin)
return size
def doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
offset = 0
horizontal = self.orientation == Qt.Horizontal
for item in self.itemList:
wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
if horizontal:
next = x + item.sizeHint().width() + spaceX
if next - spaceX > rect.right() and offset > 0:
x = rect.x()
y += offset + spaceY
next = x + item.sizeHint().width() + spaceX
offset = 0
else:
next = y + item.sizeHint().height() + spaceY
if next - spaceY > rect.bottom() and offset > 0:
x += offset + spaceX
y = rect.y()
next = y + item.sizeHint().height() + spaceY
offset = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
if horizontal:
x = next
offset = max(offset, item.sizeHint().height())
else:
y = next
offset = max(offset, item.sizeHint().width())
return y + offset - rect.y() if horizontal else x + offset - rect.x()
if __name__ == '__main__':
class Window(QWidget):
def __init__(self):
super().__init__()
#flowLayout = FlowLayout(orientation=Qt.Horizontal)
flowLayout = FlowLayout(orientation=Qt.Vertical)
flowLayout.addWidget(QPushButton("Short"))
flowLayout.addWidget(QPushButton("Longer"))
flowLayout.addWidget(QPushButton("Different text"))
flowLayout.addWidget(QPushButton("More text"))
flowLayout.addWidget(QPushButton("Even longer button text"))
self.setLayout(flowLayout)
self.setWindowTitle("Flow Layout")
import sys
app = QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())
This implementation has 2 (likely related) problems when handling vertical layouts:
QLayout has the hasHeightForWidth and heightForWidth methods, but not their inverses hasWidthForHeight and widthForHeight. I implemented the latter two methods regardless, but I doubt they're ever actually getting called.
When using the horizontal variant of the layout, the window is automatically appropriately sized to contain all the items. When using the vertical variant, this is not the case. However, the vertical layout does work properly if you manually resize the window.
How do I properly implement a vertical flow layout?
As you already found out, Qt layouts don't support widthForHeight, and, in general, these kinds of layouts are discouraged, mostly because they tend to behave erratically in complex situation with nested layouts and mixed widget size policies. Even when being very careful about their implementation, you might end up in recursive calls to size hints, policies etc.
That said, a partial solution is to still return a height for width, but position the widgets vertically instead of horizontally.
def doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
lineHeight = columnWidth = heightForWidth = 0
for item in self.itemList:
wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
if self.orientation == Qt.Horizontal:
nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x()
y = y + lineHeight + spaceY
nextX = x + item.sizeHint().width() + spaceX
lineHeight = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = nextX
lineHeight = max(lineHeight, item.sizeHint().height())
else:
nextY = y + item.sizeHint().height() + spaceY
if nextY - spaceY > rect.bottom() and columnWidth > 0:
x = x + columnWidth + spaceX
y = rect.y()
nextY = y + item.sizeHint().height() + spaceY
columnWidth = 0
heightForWidth += item.sizeHint().height() + spaceY
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
y = nextY
columnWidth = max(columnWidth, item.sizeHint().width())
if self.orientation == Qt.Horizontal:
return y + lineHeight - rect.y()
else:
return heightForWidth - rect.y()
This is how the widget appears as soon as it's shown (which is almost the same as the horizontal flow):
Now, resizing to allow less vertical space:
And even smaller height:
While the answer provided by #musicamente works, it is incomplete:
What is missing is the widthForHeight mecanism: as items are added into the layout, the minimumWidth of the container widget is not updated.
For some reason, Qt decided that heightForWidth mecanism should exist but not widthForHeight.
It would seem that when using the heightForWidth mecanism, the minimumHeight of the parent widget is automatically updated via the Qt framework (I may be wrong but I think it is the case).
In the example provided by #musicamente, as the main window is resizable this limitation is not easilly seen.
However when using a QScrollArea, this limitation is cleary observable as the scrollbar doesn't show up and the view is truncated.
So we need to determine which row of the FlowLayout is the widest and set the minimumWidth of the parent widget accordingly.
I've implemented it like so:
As the items are placed, that they are assigned i and j indexes which represent their position in a 2D array.
Then once all of them are placed, we determine the width of the widest row (including spacing between items) and let the parent widget know using a dedicated signal which can be connected to the setMinimumWidth method.
My solution might not be perfect nor a great implementation, but it is the best alternative I found so far to achieve what I wanted.
The following code will provide a working version, while I don't find my solution very elegant, it works.
If you have ideas on how to optimize it feel free to improve my implementation by making a PR on my GitHub : https://github.com/azsde/BatchMkvToolbox/tree/main/ui/customLayout
class FlowLayout(QLayout):
widthChanged = pyqtSignal(int)
def __init__(self, parent=None, margin=0, spacing=-1, orientation=Qt.Horizontal):
super(FlowLayout, self).__init__(parent)
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.itemList = []
self.orientation = orientation
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
def addItem(self, item):
self.itemList.append(item)
def count(self):
return len(self.itemList)
def itemAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList[index]
return None
def takeAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList.pop(index)
return None
def expandingDirections(self):
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
if (self.orientation == Qt.Horizontal):
return self.doLayoutHorizontal(QRect(0, 0, width, 0), True)
elif (self.orientation == Qt.Vertical):
return self.doLayoutVertical(QRect(0, 0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
if (self.orientation == Qt.Horizontal):
self.doLayoutHorizontal(rect, False)
elif (self.orientation == Qt.Vertical):
self.doLayoutVertical(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
margin, _, _, _ = self.getContentsMargins()
size += QSize(2 * margin, 2 * margin)
return size
def doLayoutHorizontal(self, rect, testOnly):
# Get initial coordinates of the drawing region (should be 0, 0)
x = rect.x()
y = rect.y()
lineHeight = 0
i = 0
for item in self.itemList:
wid = item.widget()
# Space X and Y is item spacing horizontally and vertically
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
# Determine the coordinate we want to place the item at
# It should be placed at : initial coordinate of the rect + width of the item + spacing
nextX = x + item.sizeHint().width() + spaceX
# If the calculated nextX is greater than the outer bound...
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x() # Reset X coordinate to origin of drawing region
y = y + lineHeight + spaceY # Move Y coordinate to the next line
nextX = x + item.sizeHint().width() + spaceX # Recalculate nextX based on the new X coordinate
lineHeight = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = nextX # Store the next starting X coordinate for next item
lineHeight = max(lineHeight, item.sizeHint().height())
i = i + 1
return y + lineHeight - rect.y()
def doLayoutVertical(self, rect, testOnly):
# Get initial coordinates of the drawing region (should be 0, 0)
x = rect.x()
y = rect.y()
# Initalize column width and line height
columnWidth = 0
lineHeight = 0
# Space between items
spaceX = 0
spaceY = 0
# Variables that will represent the position of the widgets in a 2D Array
i = 0
j = 0
for item in self.itemList:
wid = item.widget()
# Space X and Y is item spacing horizontally and vertically
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
# Determine the coordinate we want to place the item at
# It should be placed at : initial coordinate of the rect + width of the item + spacing
nextY = y + item.sizeHint().height() + spaceY
# If the calculated nextY is greater than the outer bound, move to the next column
if nextY - spaceY > rect.bottom() and columnWidth > 0:
y = rect.y() # Reset y coordinate to origin of drawing region
x = x + columnWidth + spaceX # Move X coordinate to the next column
nextY = y + item.sizeHint().height() + spaceY # Recalculate nextX based on the new X coordinate
# Reset the column width
columnWidth = 0
# Set indexes of the item for the 2D array
j += 1
i = 0
# Assign 2D array indexes
item.x_index = i
item.y_index = j
# Only call setGeometry (which place the actual widget using coordinates) if testOnly is false
# For some reason, Qt framework calls the doLayout methods with testOnly set to true (WTF ??)
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
y = nextY # Store the next starting Y coordinate for next item
columnWidth = max(columnWidth, item.sizeHint().width()) # Update the width of the column
lineHeight = max(lineHeight, item.sizeHint().height()) # Update the height of the line
i += 1 # Increment i
# Only call setGeometry (which place the actual widget using coordinates) if testOnly is false
# For some reason, Qt framework calls the doLayout methods with testOnly set to true (WTF ??)
if not testOnly:
self.calculateMaxWidth(i)
self.widthChanged.emit(self.totalMaxWidth + spaceX * self.itemsOnWidestRow)
return lineHeight
# Method to calculate the maximum width among each "row" of the flow layout
# This will be useful to let the UI know the total width of the flow layout
def calculateMaxWidth(self, numberOfRows):
# Init variables
self.totalMaxWidth = 0
self.itemsOnWidestRow = 0
# For each "row", calculate the total width by adding the width of each item
# and then update the totalMaxWidth if the calculated width is greater than the current value
# Also update the number of items on the widest row
for i in range(numberOfRows):
rowWidth = 0
itemsOnWidestRow = 0
for item in self.itemList:
# Only compare items from the same row
if (item.x_index == i):
rowWidth += item.sizeHint().width()
itemsOnWidestRow += 1
if (rowWidth > self.totalMaxWidth):
self.totalMaxWidth = rowWidth
self.itemsOnWidestRow = itemsOnWidestRow
To use it do the following:
When declaring a FlowLayout, specify its orientation :
myFlowLayout = FlowLayout(containerWidget, orientation=Qt.Vertical)
Connect the FlowLayout's widthChanged signal to the setMinimumWidth method of the container:
myFlowLayout.widthChanged.connect(containerWidget.setMinimumWidth)
Related
I'm trying to create a relatively simple particle simulation, which should account for gravity, drag, the collision with other particles (inelastic collision) and the collision with walls (perfectly elastic). I got the gravity and drag part working with the velocity Verlet algorithm but its as of right now not capable of setting the particles to an equilibrium state. Furthermore if I add multiple particles they sometimes climb on each other which is due (as I believe) to them still having very small velocity components which asymptotically drives to zero. I tried to cut off the velocity of the particles if the energy of a particle gets sufficiently small but it wouldn't look realistic. Could somebody maybe point out some advice how to fix these issues. I got a particle Object:
import pygame
import random
import numpy as np
import operator
from itertools import combinations
class Particle:
def __init__(self):
self.mass = 10
self.radius = random.randint(10, 50)
self.width, self.height = 700, 500
self.pos = np.array((self.width/2, self.height/2))
self.v = np.array((0.0, 0.0))
self.acc = np.array((0.0, 0.0))
self.bounce = 0.95
I use the Verlet-Integration to account for gravity and drag forces:
def update(self, ball, dt):
new_pos = np.array((ball.pos[0] + ball.v[0]*dt + ball.acc[0]*(dt*dt*0.5), ball.pos[1] + ball.v[1]*dt + ball.acc[1]*(dt*dt*0.5)))
new_acc = np.array((self.apply_forces(ball))) # only needed if acceleration is not constant
new_v = np.array((ball.v[0] + (ball.acc[0]+new_acc[0])*(dt*0.5), ball.v[1] + (ball.acc[1]+new_acc[1])*(dt*0.5)))
ball.pos = new_pos;
ball.v = new_v;
ball.acc = new_acc;
def apply_forces(self, ball):
grav_acc = [0.0, 9.81]
drag_force = [0.5 * self.drag * (ball.v[0] * abs(ball.v[0])), 0.5 * self.drag * (ball.v[1] * abs(ball.v[1]))] #D = 0.5 * (rho * C * Area * vel^2)
drag_acc = [drag_force[0] / ball.mass, drag_force[1] / ball.mass] # a = F/m
return (-drag_acc[0]),(grav_acc[1] - drag_acc[1])
And here I calculate the collision part:
def collision(self):
pairs = combinations(range(len(self.ball_list)), 2)
for i,j in pairs:
part1 = self.ball_list[i]
part2 = self.ball_list[j]
distance = list(map(operator.sub, self.ball_list[i].pos, self.ball_list[j].pos))
if np.hypot(*distance) < self.ball_list[i].radius + self.ball_list[j].radius:
distance = part1.pos - part2.pos
rad = part1.radius + part2.radius
slength = (part1.pos[0] - part2.pos[0])**2 + (part1.pos[1] - part2.pos[1])**2
length = np.hypot(*distance)
factor = (length-rad)/length;
x = part1.pos[0] - part2.pos[0]
y = part1.pos[1] - part2.pos[1]
part1.pos[0] -= x*factor*0.5
part1.pos[1] -= y*factor*0.5
part2.pos[0] += x*factor*0.5
part2.pos[1] += y*factor*0.5
u1 = (part1.bounce*(x*part1.v[0]+y*part1.v[1]))/slength
u2 = (part2.bounce*(x*part2.v[0]+y*part2.v[1]))/slength
part1.v[0] = u2*x-u1*x
part1.v[1] = u1*x-u2*x
part2.v[0] = u2*y-u1*y
part2.v[1] = u1*y-u2*y
def check_boundaries(self, ball):
if ball.pos[0] + ball.radius > self.width:
ball.v[0] *= -ball.bounce
ball.pos[0] = self.width - ball.radius
if ball.pos[0] < ball.radius:
ball.v[0] *= -ball.bounce
ball.pos[0] = ball.radius
if ball.pos[1] + ball.radius > self.height:
self.friction = True
ball.v[1] *= -ball.bounce
ball.pos[1] = self.height - ball.radius
elif ball.pos[1] < ball.radius:
ball.v[1] *= -ball.bounce
ball.pos[1] = ball.radius
This is my code for creating a 22x16 grid in pygame:
import pygame
pygame.init()
screen = pygame.display.set_mode((440, 320))
class Grid:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, GREY, [self.x * WIDTH, self.y * HEIGHT, WIDTH, HEIGHT], 1)
pygame.display.update()
cols = 22
rows = 16
WIDTH = 20
HEIGHT = 20
BLACK = (0, 0, 0)
GREY = (127,127,127)
grid = [0 for i in range(cols)]
for node in range(cols):
grid[node] = [0 for node in range(rows)]
for x in range(cols):
for y in range(rows):
grid[x][y] = Grid(x, y)
for x in range(cols):
for y in range(rows):
grid[x][y].draw()
while True:
event = pygame.event.get()
if event == pygame.QUIT:
pygame.quit()
How do I find the verices of each square in this grid and draw them?
Drawing the verices is not that important but would be nice to see them to get a better understanding for later.
The corner points (vertices) of the tile with the column index column and the row index row can be computed as follows:
tl = column * WIDTH, row * HEIGHT
tr = (column+1) * WIDTH, row * HEIGHT
bl = column * WIDTH, (row+1) * HEIGHT
br = (column+1) * WIDTH, (row+1) * HEIGHT
However, this can be simplified by using a pygame.Rect object:
tile_rect = pygame.Rect(column * WIDTH, row * HEIGHT, WIDTH, HEIGHT)
tl = tile_rect.topleft
tr = tile_rect.topright
bl = tile_rect.bottomleft
tt = tile_rect.bottomright
In visualize_boxes_and_labels_on_image_array function:
class_name = category_index[classes[i]]['name']
if(class_name == 'a'):
class_name = '我我我我'
In draw_bounding_box_on_image function:
try:
font = ImageFont.truetype('Arial.ttf', 40)
except IOError:
font = ImageFont.load_default()
# If the total height of the display strings added to the top of the bounding
# box exceeds the top of the image, stack the strings below the bounding box
# instead of above.
display_str_heights = [font.getsize(ds)[1] for ds in display_str_list]
# Each display_str has a top and bottom margin of 0.05x.
total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights)
if top > total_display_str_height:
text_bottom = top
else:
text_bottom = bottom + total_display_str_height
# Reverse list and print from bottom to top.
for display_str in display_str_list[::-1]:
text_width, text_height = font.getsize(display_str)
margin = np.ceil(0.05 * text_height)
draw.rectangle(
[(left, text_bottom - text_height - 2 * margin), (left + text_width,
text_bottom)],
fill=color)
draw.text(
(left + margin, text_bottom - text_height - margin),
display_str,
fill='black',
font=font)
text_bottom -= text_height - 2 * margin
But the class_name will show:
this
How can I save this problem?
Thanks
I saved use MSfont.ttf in above try...
After some work I finished my first algorithm for a maze generator and now I am trying to make it visual in Pygame. I first let the algorithm generate a maze and then I make a visual representation of it.
Here I get into multiple problems, but I think they are all linked to the same thing which is that the first cell of the maze gets overwritten in some way. Because of this, what I get is totally not a maze at all but just some lines everywhere.
I tried putting the removal of walls in a seperate method, but that does not seem to work also. I looked if the remove walls method gets called at the first cell and it says it is true, but in some way the values of that cell gets overwritten.
Code:
import pygame
import random
WIDTH = 300
HEIGHT = 300
CellSize = 30
Rows = int(WIDTH/30)
Columns = int(HEIGHT/30)
current = None
grid = []
visited = []
DISPLAY = pygame.display.set_mode((WIDTH, HEIGHT))
class Cell:
def __init__(self, r, c):
self.r = r
self.c = c
self.x = CellSize * c
self.y = CellSize * r
self.sides = [True, True, True, True] # Top, Bottom, Left, Right
self.visited = False
self.neighbours = []
self.NTop = None
self.NBottom = None
self.NRight = None
self.NLeft = None
self.NTopIndex = None
self.NBottomIndex = None
self.NRightIndex = None
self.NLeftIndex = None
self.nr = None
self.nc = None
self.random = None
self.neighbour = None
def index(self, nr, nc):
self.nr = nr
self.nc = nc
if self.nr < 0 or self.nc < 0 or self.nr > Rows-1 or self.nc > Columns-1:
return -1
return self.nr + self.nc * Columns
def neighbour_check(self):
# Get neighbour positions in Grid
self.NTopIndex = self.index(self.r, self.c - 1)
self.NBottomIndex = self.index(self.r, self.c + 1)
self.NRightIndex = self.index(self.r + 1, self.c)
self.NLeftIndex = self.index(self.r - 1, self.c)
# Look if they are truly neighbours and then append to neighbour list
if self.NTopIndex >= 0:
self.NTop = grid[self.NTopIndex]
if not self.NTop.visited:
self.neighbours.append(self.NTop)
if self.NBottomIndex >= 0:
self.NBottom = grid[self.NBottomIndex]
if not self.NBottom.visited:
self.neighbours.append(self.NBottom)
if self.NRightIndex >= 0:
self.NRight = grid[self.NRightIndex]
if not self.NRight.visited:
self.neighbours.append(self.NRight)
if self.NLeftIndex >= 0:
self.NLeft = grid[self.NLeftIndex]
if not self.NLeft.visited:
self.neighbours.append(self.NLeft)
# Choose random neighbour
if len(self.neighbours) > 0:
self.random = random.randint(0, len(self.neighbours) - 1)
self.neighbour = self.neighbours[self.random]
# Remove the wall between self and neighbour
if self.neighbour == self.NTop:
if self == grid[0]:
print('TOP')
self.sides[0] = False
self.NTop.sides[1] = False
elif self.neighbour == self.NBottom:
if self == grid[0]:
print('BOTTOM')
self.sides[1] = False
self.NBottom.sides[0] = False
elif self.neighbour == self.NLeft:
if self == grid[0]:
print('LEFT')
self.sides[2] = False
self.NLeft.sides[3] = False
elif self.neighbour == self.NRight:
if self == grid[0]:
print('RIGHT')
self.sides[3] = False
self.NRight.sides[2] = False
else:
print('SIDES ERROR')
return self.neighbours[self.random]
else:
return -1
def draw(self):
global DISPLAY, CellSize
# Top
if self.sides[0]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y), (self.x + CellSize, self.y))
# Bottom
if self.sides[1]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y + CellSize), (self.x + CellSize, self.y + CellSize))
# Left
if self.sides[2]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x, self.y), (self.x, self.y + CellSize))
# Right
if self.sides[3]:
pygame.draw.line(DISPLAY, (0, 0, 0), (self.x + CellSize, self.y), (self.x + CellSize, self.y + CellSize))
class Maze:
def __init__(self):
global current
self.next = None
self.running = True
self.DISPLAY = None
self.display_running = True
def init_cells(self):
# Make grid and make cell 0 the begin of the algorithm
global current
for i in range(0, Columns):
for j in range(0, Rows):
cell = Cell(j, i)
grid.append(cell)
current = grid[0]
def init_maze(self):
global current, visited
print(grid[0].sides)
# Start Algorithm
while self.running:
# Check if the current cell is visited, if not make it visited and choose new neighbour
if not current.visited:
current.visited = True
visited.append(current)
self.next = current.neighbour_check()
if not self.next == -1:
# If it finds a neighbour then make it the new current cell
# self.next.visited = True
current = self.next
elif self.next == -1 and len(visited) > 0:
# If it doesn't then look trough the path and backtrack trough it to find a possible neighbour
if len(visited) > 1:
del visited[-1]
current = visited[-1]
# If the last cell of the visited list is Cell 0 then remove it
elif len(visited) <= 1:
del visited[-1]
elif len(visited) <= 0:
# Stop the Algorithm
self.running = False
print('Done')
def draw(self):
DISPLAY.fill((255, 255, 255))
# Get the maze made by the algorithm and draw it on the screen
for i in range(0, len(grid)):
grid[i].draw()
pygame.display.update()
while self.display_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.display_running = False
maze = Maze()
maze.init_cells()
maze.init_maze()
maze.draw()
I put some print methods in it for debugging purposes.
And am still a beginner in programming, I know it could probably be way cleaner or that some naming of methods could be better.
What I want to happen is that in def init_maze the maze blueprints gets written out and that in def draw the blueprint gets drawn on the screen.
I was unsure whether to post the full code or not, but here's what I have:
from tkinter import *
from random import randint
HEIGHT = 500
WIDTH = 800
MID_X = WIDTH/2
MID_Y = HEIGHT/2
SHIP_R = 15
SHIP_SPD = 10
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 6
GAP = 100
window = Tk()
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
def move_ship(event):
fixed = True
while fixed == True:
ship_x, ship_y = event.x, event.y
c.move(ship_id, ship_x, ship_y)
c.move(ship_id2, ship_x, ship_y)
sleep(0.01)
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x-r, y-r, x+r, y+r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def del_bubble(i):
del bub_r[i]
del bub_speed[i]
c.delete(bub_id[i])
del bub_id[i]
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[i])
if x < -GAP:
del_bubble(i)
from math import sqrt
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2-x1)**2 + (y2-y1)**2)
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R+bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
c.create_text(50, 30, text='TIME', fill='white')
c.create_text(150, 30, text='SCORE', fill='white')
time_text = c.create_text(50, 50, fill='white')
score_text = c.create_text (150, 50, fill='white')
def show_score(score):
c.itemconfig(score_text, text=str(score))
def show_time(time_left):
c.itemconfig(time_text, text=str(time_left))
from time import sleep, time
BUB_CHANCE = 20
TIME_LIMIT = 30
BONUS_SCORE = 1000
# MAIN GAME LOOP
c.bind("<B1_Motion>", move_ship)
score = 0
bonus = 0
end = time() + TIME_LIMIT
while time() < end:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
window.update()
sleep(0.01)
c.create_text(MID_X, MID_Y, \
text='PARTY TIME, EXCELLENT', fil='white', font=('Helvetica', 30))
c.create_text(MID_X, MID_Y + 30, \
text='Score: ' + str(score), fill='white')
c.create_text(MID_X, MID_Y + 45, \
text='BONU TIME: ' + str(bonus*TIME_LIMIT), fill='white')
I'm a complete beginner when it comes to python, and have been given an assignment to only use tkinter and the standard libraries to give mouse movement to this "game". I just can't seem to get the right grasp of it. Any suggestions would be appreciated!
The first step is to remove your while loop, and put most of your functionality into a function. Put everything you want to do in a single frame of animation into this function.
Next, call this function on a regular interval using the after command. This allows the event loop to run continuously, which is important for your UI to be responsive. You can do your time() < end calculation inside this function, and use the result to break the cycle once time is up.
It looks something like this:
def draw_one_frame():
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
# move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
if time() < end:
after(10, draw_one_frame)
You can use the first parameter to after to control how many frames per second you wish your program to run at.
Next, you need to handle mouse movement. You do this by creating a binding to the movement of the mouse. You do this outside of the draw_one_frame method. It looks something like this:
c.bind("<B1-Motion>", move_ship)
You also need to remove the infinite loop from move_ship, and also remove the sleep. You simply need to do all the calculations for the current mouse position. The function will be already be looping -- being called once each time the mouse moves.
Finally, you need to call window.mainloop() after all your other code, so that the program can process events. This should be the very last line of your program. It will run until the window is destroyed.