Squares vertices in a 22x16 grid in pygame - python-3.x

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

Related

PyQt - Oriented Flow Layout

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)

Why is the circle moving in a straight line instead of at an angle in pygame?

I am learning the basics of pygame in my free time at work. I wanted to move the bottom boundary of my program up, but when I changed the boundary collision condition, the ball moves in a straight line instead of at an angle.
When I delete the 525 part of the condition in the bounceCircle definition, it moves as expected. When I place it back, it moves in a horizontal line.
import pygame
import sys
import random
import math
# Initalize the game engine
pygame.init()
# Define common colors:
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
# Set window size, title, and background color
(width, height) = (900, 600)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Ball Playground")
screen.fill(WHITE)
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Ball class
class Particles():
def __init__(self, position, radius):
self.x = position[0]
self.y = position[1]
self.radius = radius
self.color = (BLUE)
# thickness = 0 means filled
# thickness > 0 thicker border
# thickness < 0 nothing
self.thickness = 1
self.speed = 0
self.angle = 0
# Definition for drawing circle
def drawCircle(self):
pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius, self.thickness)
# Definition for moving the circle
def moveCircle(self):
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
# Definition for bouncing off of surfaces
def bounceCircle(self):
if (self.x > width - self.radius) or (self.x < self.radius):
self.angle = - self.angle
elif (self.y > height - self.radius) or (self.y < 525 - self.radius):
self.angle = math.pi - self.angle
ball = Particles((450, 300), 40)
ball.speed = 2
ball.angle = random.uniform(0, math.pi*2)
# --------- Main Program Loop ----------
while True:
# --- Main Event Loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
screen.fill(WHITE)
#--- Game Logic
ball.moveCircle()
ball.bounceCircle()
ball.drawCircle()
#--- Drawings
pygame.draw.line(screen, BLACK, [0, 525], [900, 525], 2)
# Prints tiny diaginal lines to mark surface
x1 = 0
x2 = 5
for i in range(0, width):
pygame.draw.line(screen, BLACK, [x1, 530], [x2, 525], 2)
x1 += 5
x2 += 5
pygame.display.flip()
clock.tick(60)
You need to edit the bounceCircle function to:
def bounceCircle(self):
if (self.x + self.radius > width ) or (self.x - self.radius < 0):
self.angle = - self.angle
elif (self.y + self.radius > (525)) or (self.y - self.radius < 0):
self.angle = math.pi - self.angle
Whole Code (fixed a few bugs):
import pygame
import sys
import random
import math
# Initalize the game engine
pygame.init()
# Define common colors:
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
# Set window size, title, and background color
(width, height) = (900, 600)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Ball Playground")
screen.fill(WHITE)
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Ball class
class Particles():
def __init__(self, position, radius):
self.x = position[0]
self.y = position[1]
self.radius = radius
self.color = (BLUE)
# thickness = 0 means filled
# thickness > 0 thicker border
# thickness < 0 nothing
self.thickness = 1
self.speed = 0
self.angle = 0
# Definition for drawing circle
def drawCircle(self):
pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius, self.thickness)
# Definition for moving the circle
def moveCircle(self):
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
print(self.angle, self.x, self.y)
# Definition for bouncing off of surfaces
def bounceCircle(self):
if (self.x > width - self.radius) or (self.x < self.radius):
self.angle = - self.angle
elif (self.y + self.radius > (height-100)) or (self.y - self.radius < 0):
self.angle = math.pi - self.angle
ball = Particles((450, 300), 40)
ball.speed = 20
ball.angle = random.uniform(0, math.pi*2)
print(ball.angle)
# --------- Main Program Loop ----------
while True:
# --- Main Event Loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
screen.fill(WHITE)
#--- Game Logic
ball.moveCircle()
ball.bounceCircle()
ball.drawCircle()
#--- Drawings
pygame.draw.line(screen, BLUE, [0, 525], [900, 525], 2)
# Prints tiny diaginal lines to mark surface
x1 = 0
x2 = 5
for i in range(0, width):
pygame.draw.line(screen, BLUE, [x1, 530], [x2, 525], 2)
x1 += 5
x2 += 5
pygame.display.flip()
clock.tick(60)

Tkinter create shrinking circle on each mouse click, how to make it work with multiple clicks?

I am creating a simple program which draws a shrinking circle of random color on every clicked location by each mouse click. Each click creates a circle of diameter 50 which starts shrinking till 0 immediately. Each click is supposed to create new shrinking circle.
However, my program stops shrinking of first circle after I click and create another circle. It completely shrinks only the last created circle. All others remain still.
I believe the problem lies in function itself. It calls the same function which is not finished. How to make it run multiple times (on each click separately)? Or do I have it wrong with local and global variables?
Here is my code so far:
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
global x, y, farba, circ, r
r = 50 #circle diameter
x, y = event.x, event.y #clicked position
color = '#{:06x}'.format(random.randrange(256 ** 3)) #random color picker
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color) #print circle
print(x, y, farba) #check clicked coordinates, not important
if r < 50: #reset size after each circle
r = 50
shrink()
def shrink():
global circ, x, y, r
print(r) #check if countdown runs correctly
if r > 0:
r -= 1 #diameter shrinking
c.coords(circ, x-r, y-r, x+r, y+r) #changing circle size
c.after(100, shrink) #timer, size 1pt smaller until size is 0
c.bind('<Button-1>', klik)
tkinter.mainloop()
If you move everything into a class then each circle will be its own instance and will not interfere with each other.
Take a look at the below modified version of your code. It is probably not perfect but should be a good foundation for you to work with.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
class create_circles():
def __init__(self, event):
self.r = 50
self.x, self.y = event.x, event.y
self.color = '#{:06x}'.format(random.randrange(256 ** 3))
self.circ = c.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color)
self.shrink()
def shrink(self):
if self.r > 0:
self.r -= 1
c.coords(self.circ, self.x-self.r, self.y-self.r, self.x+self.r, self.y+self.r)
c.after(100, self.shrink)
c.bind('<Button-1>', create_circles)
tkinter.mainloop()
There is another way you can do this outside of a class.
You can use a nested function and avoid global. Your issues in your question is actually being caused because everything relies on global variables.
Try this below code for a non-class option.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
r = 50
x, y = event.x, event.y
color = '#{:06x}'.format(random.randrange(256 ** 3))
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color)
def shrink(r, x, y, color, circ):
if r > 0:
r -= 1
c.coords(circ, x-r, y-r, x+r, y+r)
c.after(100, shrink, r, x, y, color, circ)
shrink(r, x, y, color, circ)
c.bind('<Button-1>', klik)
tkinter.mainloop()
As noted, you do not need classes to solve this nor nested functions. The key, as #LioraHaydont was hinting at, is you need to use local, rather than global variables:
import tkinter as tk
from random import randrange
def klik(event):
r = 50 # circle radius
x, y = event.x, event.y # clicked position
color = '#{:06x}'.format(randrange(256 ** 3)) # random color picker
c = canvas.create_oval(x - r, y - r, x + r, y + r, fill=color) # print circle
canvas.after(100, shrink, c, x, y, r)
def shrink(c, x, y, r):
if r > 0:
r -= 1 # radius shrinking
canvas.coords(c, x - r, y - r, x + r, y + r) # changing circle size
canvas.after(100, shrink, c, x, y, r) # timer, size 1pt smaller until size is 0
canvas = tk.Canvas(width=400, height=300)
canvas.pack()
canvas.bind('<Button-1>', klik)
tk.mainloop()

Turtle inner circle boundary in python

Okay, I've been at this all day and haven't a clue. I need to get my turtle object to draw random lines outside of a circle.
I've made code that restricts the random lines within the boundaries before, so I thought all I had to do was change the sign, but that didn't work. I'm not allowed to use coordinate geometry - it has to be something more basic...
Here's my code in it's current format:
import turtle, random
mRoshi = turtle.Turtle()
def draw_any_shape(myTurtle, sideLength, numSides):
turnAng = 360/numSides
for i in range(numSides):
myTurtle.forward(sideLength)
myTurtle.right(turnAng)
def drawCircle(myTurtle, radius, startX, startY):
circumference = 2*3.1415*radius
sideLength = circumference/360
myTurtle.penup()
myTurtle.goto(startX, startY)
#myTurtle.dot()
myTurtle.goto(startX, startY+radius)
myTurtle.pendown()
draw_any_shape(myTurtle, sideLength, 360)
def stumblingTurtle(myTurtle, radius, startX, startY, paramN5):
circumference = 2*3.1415*radius
myTurtle.speed(6)
drawCircle(myTurtle, radius, startX, startY)
myTurtle.penup()
for i in range(paramN5):
drx = random.randint(-800, 800)
drw = random.randint(-800, 800)
if (drx**2 + drw**2) > radius**2:
myTurtle.goto(drx,drw)
crx = random.randint(-800, 800)
crw = random.randint(-800, 800)
xdif = crx-drx
ydif = crw-drw
for j in range(drx, crx):
for k in range(drw, crw):
if (xdif**2 + ydif**2) > radius**2:
myTurtle.goto(crx,crw)
Does this do what you want? It's also based on code that originally kept the turtle within a circle. It uses Python3 turtle's undo capability to allow the turtle to accidentally wander into the circle and then undo that accident as if it never happened:
import turtle
import random
RADIUS = 50
MAXIMUM_TURN = 45
STEP_SIZE = 10
BORDER = 20
def bounded_random_move():
yertle.forward(STEP_SIZE)
x, y = yertle.position()
if (x * x + y * y) < RADIUS * RADIUS or x < -window_width/2 or x > window_width/2 or y < -window_height/2 or y > window_height/2:
yertle.undo() # undo misstep
turn = random.randint(180 - MAXIMUM_TURN, 180 + MAXIMUM_TURN)
yertle.left(turn)
turtle.ontimer(bounded_random_move, 100)
turtle.setup(RADIUS * 10, RADIUS * 10)
window_width = turtle.window_width() - BORDER
window_height = turtle.window_height() - BORDER
magic_marker = turtle.Turtle(visible=False)
magic_marker.penup()
magic_marker.color("red")
magic_marker.sety(-RADIUS)
magic_marker.pendown()
magic_marker.circle(RADIUS)
yertle = turtle.Turtle(shape="turtle", visible=False)
yertle.speed("fastest")
yertle.penup()
yertle.goto(RADIUS * 2, RADIUS * 2) # start outside circle
yertle.pendown()
yertle.showturtle()
turtle.ontimer(bounded_random_move, 100)
turtle.exitonclick()
My undo trick might not be rigorous enough for everyone, however.

Draw a scale which ranges from red to green in Tkinter

I would like to draw a scale which ranges from red to green. I managed to do a scale which ranges from green to yellow. Now I have 2 possible solutions :
Either drawing 2 gradients : one from red to yellow and one from yellow to green. Then I can link the 2 drawings.
Or the better solution I think : Drawing one gradient from red to green with a checkpoint on the yellow.
How to implement this second solution ?
Here is my code from green to yellow. You can test it.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
f = GradientFrame(root)
f.pack(fill="both", expand=True)
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "green"
self._color2 = "yellow"
self._color3 = "red"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = 200
height = 50
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
(r3,g3,b3) = self.winfo_rgb(self._color3)
r_ratio = float((r2-r1)) / limit
g_ratio = float((g2-g1)) / limit
b_ratio = float((b2-b1)) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Without the generalization or general aesthetics of the above code, I offer you a simple solution (implemented against the red to green gradient) here:
import tkinter
def rgb(r, g, b):
return "#%s%s%s" % tuple([hex(c)[2:].rjust(2, "0") for c in (r, g, b)])
root = tkinter.Tk()
root.title("Gradient")
gradient1 = tkinter.Canvas(root, width=255*2, height=50)
gradient1.pack()
gradient2 = tkinter.Canvas(root, width=255*2, height=50)
gradient2.pack()
for x in range(0, 256):
r = x
g = 255-x
gradient1.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
for x in range(0, 256):
r = x*2 if x < 128 else 255
g = 255 if x < 128 else 255 - (x-128)*2
gradient2.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
I believe it's what you meant by "yellow checkpoints". Thanks for the challenge! :^)

Resources