Related
I'm strugling with canvas.move in a simulation of a celestial system. How can I simultaneously move multiple objects in a defined Space instance? I guess I have to do something with the identity of the body objects. But I cannot find it out. Perhaps I should use repeating draw en delete methods in stead of canvas.move? See a simplified version of the coden below. Does some body has a suggestion? Many thanks
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border = 5)
frame.pack()
self.width, self.height = size[0], size[1]
self.canvas = tk.Canvas(frame,
width = self.width,
height = self.height,
borderwidth= 0,
highlightthickness= 0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc[0], body.loc[1]
x2, y2 = x1+body.size, y1+body.size
self.canvas.create_oval(x1,y1,x2,y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed[0], body.speed[1]
self.canvas.move(body, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x,y)
space = Space(root,size, bg = 'black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200,0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200,0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color = "yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color ="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()```
You need to keep track of the tag returned from canvas.create_oval(). See .tk_tag below. I had to slow down your speeds because the object immediately left the screen. Also note: instead of dx, dy = body.speed[0], body.speed[1], you can just do dx, dy = body.speed.
import tkinter as tk
class Space(tk.Frame):
def __init__(self, master, size, bg=None):
super().__init__(master)
frame = tk.Frame(master, border=5)
frame.pack()
self.width, self.height = size
self.canvas = tk.Canvas(frame,
width=self.width,
height=self.height,
borderwidth=0,
highlightthickness=0,
bg=bg)
self.canvas.pack()
def place_body(self, body):
x1, y1 = body.loc
x2, y2 = x1 + body.size, y1 + body.size
body.tk_tag = self.canvas.create_oval(x1, y1, x2, y2, fill=body.color)
def distance_step(self):
pass
def move_body(self, body):
# in stead of distance_step:
dx, dy = body.speed
dx, dy = dx/100, dy/100
self.canvas.move(body.tk_tag, dx, dy)
self.canvas.after(1, lambda: self.move_body(body))
class CelestialBody:
def __init__(self, name, size, mass, loc, speed, color="white"):
self.name = name
self.size = size
self.mass = mass
self.loc = loc
self.speed = speed
self.color = color
self.tk_tag = None
def __repr__(self):
return f"{self.name}"
class App:
def __init__(self):
x, y = 1000, 800
size = (x, y)
space = Space(root, size, bg='black')
sun1_size = 30
sun1_mass = 10
sun1_loc = (700, 450)
sun1_speed = (-200, 0)
sun2_size = 30
sun2_mass = 10
sun2_loc = (300, 350)
sun2_speed = (200, 0)
sun1 = CelestialBody("sun1", sun1_size, sun1_mass, sun1_loc, sun1_speed, color="yellow")
sun2 = CelestialBody("sun2", sun2_size, sun2_mass, sun2_loc, sun2_speed, color="yellow")
space.place_body(sun1)
space.place_body(sun2)
space.move_body(sun1)
space.move_body(sun2)
print(sun1, sun2)
root.mainloop()
root = tk.Tk()
root.title('UNIVERSE')
app = App()
The following program draws a rectangle in the center of the canvas. The rectangle is supposed to get wider when the right arrow key is pressed, and narrower when the left arrow key is pressed.
Here's the code:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=400, height=300, bg="#000000")
canvas.pack()
x1 = 150
y1 = 100
x2 = 250
y2 = 200
class ResizeRect:
def __init__(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.rect = canvas.create_rectangle(0,0,1,1)
def draw(self):
canvas.delete(self.rect)
self.rect = canvas.create_rectangle(x1, y1, x2, y2,
outline="#00B000", width=2)
def narrower(self):
self.x1 = self.x1 + 5
self.x2 = self.x2 - 5
def wider(self):
self.x1 = self.x1 - 5
self.x2 = self.x2 + 5
r = ResizeRect(150, 100, 250, 200)
r.draw()
def left(event):
r.narrower()
r.draw()
def right(event):
r.wider()
r.draw()
canvas.bind_all('<KeyPress-Left>', left)
canvas.bind_all('<KeyPress-Right>', right)
My teacher told me that I need to add the 'self' keyword to the parameters in the draw function but I don't know what he means. (I can't ask him more because he's in a bad mood right now.). Any help is much appreciated.
I'm working on a simulation in which some cubes of the same class are moving randomly. My aim is to give them another moving pattern, when they fulfill some characteristics (for example their object number).
My Problem:
If they fulfill the characteristics, how can I "switch off" the first moving pattern and activate the next?
Here a strongly simplified example of the simulation, and how it doesn't work:
from tkinter import *
from random import *
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
self.master = master
self.canvas = canvas
self.number = number
self.x1 = x1
self.y1 = y1
self.x2 = x1 + 15
self.y2 = y1 + 15
self.color = color
self.rectangle = canvas.create_rectangle(x1, y1, self.x2, self.y2, fill=color)
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.canvas.after(100, self.movementII)
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
canvas = Canvas(master, width=900, height=600)
canvas.pack()
master.title("Simulation")
cube = Cubes(master, canvas, 2, randint(50, 800), randint(25, 500), "black")
cube.movement()
mainloop()
how can I "switch off" the first moving pattern and activate the next?
When you call after, it returns a unique identifier. You can save that identifier and then later pass it to after_cancel to cancel the job if it hasn't already run.
I'm not entirely clear what you want, but if you want to turn off the old movement when you switch to the new, it would look something like this:
class Cubes:
def __init__(self, master, canvas, number, x1, y1, color):
...
self.after_id = None
...
def cancel(self):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = None
def movement(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
if self.number == 2:
def movementII(self):
canvas.move(self.rectangle, 0, 0)
self.cancel()
self.after_id = self.canvas.after(100, self.movementII)
self.after_id = self.canvas.after(100, self.movement)
A better way might be to have a single method that you call with after, and it simply calls the appropriate method.
For example, something like this:
def move(self):
if self.number == 1:
self.movement_1()
elif self.number == 2:
self.movement_2()
self.canvas.after(100, self.move)
def movement_1(self):
self.x = randint(-10, 10)
self.y = randint(-10, 10)
canvas.move(self.rectangle, self.x, self.y)
def movement_2(self):
canvas.move(self.rectangle, 0, 0)
Then, to switch the movement method, just change self.number and it will automatically be called at the appropriate time.
I want to draw a triangle from a class, so I call the function
pygame.draw.polygon()
Now, the problem is that I need to pass the points in a manner that will allow me to calculate the centre of the triangle.
I was trying to pass the tuples one by one in this way
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
so that I can then access the single tuple values.
Then pass the three points like this
self.position = [self.first_point, self.second_point, self.third_point]
But for some reason, it doesn't work.
This is the error I get
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 178, in <module>
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
[(100, 100), (200, 200), (300, 300)]
File "C:/Users/oricc/PycharmProjects/designAChessboardChallange/display.py", line 132, in __init__
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
TypeError: points must be number pairs
By number of pairs, the Pygame documentation gives as an example
e.g. [(x1, y1), (x2, y2), (x3, y3)]
In fact, when I print the position I pass I get, as you can see from the error above
[(100, 100), (200, 200), (300, 300)]
Anyone can help with this?
Is there another manner to calculate the centre without accessing the xs and ys like that?
Full code here
import pygame
import sys
from coordinator import coordinator
# set up the display
pygame.init()
window_size = (800, 800)
game_window = pygame.display.set_mode(size=window_size)
pygame.display.set_caption('My Game')
# defines classes and related methods
class WhiteSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.white_square = pygame.Surface((self.height, self.width))
self.white_square.fill((255, 255, 255))
class BlackSquare:
def __init__(self):
self.height = int(window_size[0] / 8)
self.width = int(window_size[1] / 8)
self.black_square = pygame.Surface((self.height, self.width))
self.black_square.fill((0, 0, 0))
class ChessBoard:
def __init__(self):
self.ws = ws
self.bs = bs
self.white_columns = white_columns
self.black_columns = black_columns
def draw(self):
for w_columns in self.white_columns:
game_window.blit(self.ws.white_square, w_columns)
for b_columns in self.black_columns:
game_window.blit(self.bs.black_square, b_columns)
# declare letters and numbers
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
numbers = ['1', '2', '3', '4', '5', '6', '7', '8']
# create coordinates
coordinates = []
for item_letter in letters:
letter = item_letter
for item_number in numbers:
number = item_number
coordinates.append(letter + number)
# create coordinates values components
x_values = []
for number in range(0, 800, 100):
x = number
x_values.append(x)
y_values = []
for number in range(0, 800, 100):
y = number
y_values.append(y)
# create coordinate values
coordinate_values = []
for x in x_values:
for y in y_values:
coordinate_values.append((x, y))
# assign values to coordinates
squares_coordinates = dict(zip(coordinates, coordinate_values))
# Background for units
class CircleSurface:
def __init__(self):
self.circle_surface = pygame.Surface((100, 100), flags=pygame.SRCALPHA)
pygame.draw.circle(self.circle_surface, (255, 0, 0), (50, 50), 45)
# define colours
black = (0, 0, 0)
white = (255, 255, 255)
gold = (153, 153, 0)
class Unit:
def __init__(self, colour, position):
# define Unit colour
self.colour = colour
# define Unit position
self.position = position
class Knight(Unit):
def __init__(self, colour, position):
# draw circle, inline, and outline
super().__init__(colour, position)
self.center_x = position[0]
self.center_y = position[1]
self.colour = colour
self.position = position
circle_radius = 40
self.circle = pygame.draw.circle(game_window, colour, self.position, circle_radius)
self.circle_outline = pygame.draw.circle(game_window, gold, self.position, circle_radius, 5)
self.circle_inline = pygame.draw.circle(game_window, gold, self.position, (circle_radius - 10), 5)
# draw letter
pygame.font.init()
my_font_size = 50
my_font = pygame.font.SysFont('Time New Roman', my_font_size)
text_surface = my_font.render('K', 1, gold)
center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
game_window.blit(text_surface, center_text)
class Archer(Unit):
def __init__(self, colour, position):
super().__init__(colour, position)
self.first_point = (int, int)
self.second_point = (int, int)
self.third_point = (int, int)
self.position = [self.first_point, self.second_point, self.third_point]
print(position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
# draw letter
# pygame.font.init()
# my_font_size = 50
# my_font = pygame.font.SysFont('Time New Roman', my_font_size)
# text_surface = my_font.render('A', 1, gold)
# center_text = text_surface.get_rect(center=(self.center_x, self.center_y))
# game_window.blit(text_surface, center_text)
# Sets and gets the coordinates for black and white squares
coordinator = coordinator()
black_columns = coordinator[2] + coordinator[3]
white_columns = coordinator[0] + coordinator[1]
# Creates needed objects
ws = WhiteSquare()
bs = BlackSquare()
cb = ChessBoard()
cs = CircleSurface()
# Event loop (outer)
while 1:
# Event loop (inner)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Draws the chessboard
cb.draw()
# Draws white pieces in their initial position
white_knight_1 = Knight(white, (150, 650))
white_knight_2 = Knight(white, (650, 650))
white_archer_3 = Archer(white, [(100, 100), (200, 200), (300, 300)])
pygame.display.update()
Thank you
OK, I managed to make it work.
You guys are both right, I should know by now that I can't pass placeholders, so I figured out the solution to my problem as follow:
class Archer(Unit):
def __init__(self, colour, first_point, second_point, third_point):
self.first_point = first_point
self.second_point = second_point
self.third_point = third_point
position = [self.first_point, self.second_point, self.third_point]
super().__init__(colour, position)
print(self.position)
self.triangle = pygame.draw.polygon(game_window, colour, self.position)
self.triangle_outline = pygame.draw.polygon(game_window, gold, self.position, 5)
self.triangle_inline = pygame.draw.polygon(game_window, gold, self.position, 5)
Basically I have to declare the three points self variables, as well as position before the super function so that I can then pass them as 'position' to the parent class initializer. This has been really useful!!
Thanks both
you could also do this: self.first_point = (int(0), int(0)) as int is not a placeholder but to declare that a variable is an integer str('0') will print '0' you could also input this
zero = 0
str(zero) #'0'
int(zero) #0
and you dont need to put rgb tupels because you can store them in a variable like this
black = (0, 0, 0)
Display = pygame.display.set_mode((800, 600))
Display.fill(black)
I have a problem with my project. I want my class Baustein to be a blueprint of an object I can place in my Display in the gui_backup.py. The program launches and everything works, but as I click the left button (button1) the same program opens again and now I have 2 programs going separately but I have no new Object in the Display created, just the old one which is being created in gui_backup in blue, but I want to create one in red with the class Baustein from Bauklotz_class.py. Here are my codes:
Bauklotz_class.py
from gui_backup import Display
class Baustein:
x1, y1, x2, y2 = 10,10,20,20
color = "red"
def __init__(self, x1, y1, x2, y2, color):
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
self.color = color
def show_new_object(self):
quadrat2 = Display.create_rectangle(40, 50, 60, 70, fill = color)
Display.coords(quadrat2, 40, 50, 60, 70)
gui_backup.py
from tkinter import *
import curses
x1 = 10 #initialise coordinates
y1 = 10
x2 = 20
y2 = 20
root = Tk() #create window
root.wm_title("Raspberry Pi GUI") #window title
root.config(background = "#FFFFFF") #background color
#The whole left frame and widgets involved
leftFrame = Frame(root, width=200, height = 400)
leftFrame.grid(row=0, column = 0, padx = 10, pady = 3)
leftLabel1 = Label(leftFrame, text = "Platzhalter Text")
leftLabel1.grid(row = 0, column = 0, padx = 10, pady = 3)
leftLabel2 = Label(leftFrame, text = "Dies ist ein Text\nmit mehreren Zeilen")
leftLabel2.grid(row = 1, column = 0, padx = 10, pady = 3)
#the whole right frame and widgets involved
rightFrame = Frame(root, width=400, height = 400)
rightFrame.grid(row = 0, column = 1, padx = 10, pady = 3)
E1 = Entry(rightFrame, width = 50)
E1.grid(row = 0, column = 0, padx = 10, pady = 60)
#The two functions for the 2 buttons created
def callback1():
import Bauklotz_class
test = Bauklotz_class.Baustein(20, 30, 40, 50, "red")
test.show_new_object()
def callback2():
print(1+1)
buttonFrame = Frame(rightFrame)
buttonFrame.grid(row = 1, column = 0, padx = 10, pady = 60)
B1 = Button(buttonFrame, text = "Button1", bg = "#FF0000", width = 15, command = callback1)
B1.grid(row = 0, column = 0, padx = 10, pady = 60)
B2 = Button(buttonFrame, text = "Button2", bg ="#FFFF00", width = 15, command = callback2)
B2.grid(row = 0, column = 1, padx = 10, pady = 60)
Slider = Scale(rightFrame, from_ = 0, to = 100, resolution = 0.1, orient = HORIZONTAL, length = 400)
Slider.grid(row = 2, column = 0, padx = 10, pady = 3)
Display = Canvas(rightFrame, width = 300, height = 300)
Display.configure(background = 'black')
Display.grid(row = 1, column = 3, padx = 10, pady = 3)
quadrat = Display.create_rectangle(20, 30, 40, 50, fill = "blue")
Display.coords(quadrat, x1, y1, x2, y2)
#following functions are for coordination of the square
#also you can find here the exceptions so that the object
#cant break out of the display widget
def down(event):
global x1, y1, x2, y2
if x2 == 290 or y2 == 300:
pass
else:
y1 += 10
y2 += 10
Display.coords(quadrat, x1, y1, x2, y2)
leftLabel1.config(text = "x1: " + str(x1) + ", x2:" + str(x2) + ", y1:" + str(y1) + ", y2:" + str(y2), width = "40" , )
def up(event):
global x1, y1, x2, y2
if x2 == 0 or y2 == 10:
pass
else:
y1 -= 10
y2 -= 10
Display.coords(quadrat, x1, y1, x2, y2)
leftLabel1.config(text = "x1: " + str(x1) + ", x2:" + str(x2) + ", y1:" + str(y1) + ", y2:" + str(y2), width = "40" , )
def left(event):
global x1, y1, x2, y2
if x1 == 0 or y1 == 10:
pass
else:
x1 -= 10
x2 -= 10
Display.coords(quadrat, x1, y1, x2, y2)
leftLabel1.config(text = "x1: " + str(x1) + ", x2:" + str(x2) + ", y1:" + str(y1) + ", y2:" + str(y2), width = "40" , )
def right(event):
global x1, y1, x2, y2
if x1 == 290 or y1 == 300:
pass
else:
x1 += 10
x2 += 10
Display.coords(quadrat, x1, y1, x2, y2)
leftLabel1.config(text = "x1: " + str(x1) + ", x2:" + str(x2) + ", y1:" + str(y1) + ", y2:" + str(y2), width = "40" , )
root.bind('<a>', left)
root.bind('<w>', up)
root.bind('<s>', down)
root.bind('<d>', right)
root.mainloop()
Here are also some screenshots for you to understand it better:
Does someone know why my project is duplicating itself?
The problem lies within your double import, i guess. You see, you import from gui_backup.py into Bauklotz_class.py, while also importing vice versa, which leads to weird and unexpected behaviour. Also, inside the definition of show_new_object, you should use self.color instead of color as an argument for Display.create_rectangle, because self.color is a class attribute and thus the proper object to use in a class method definition. One way to fix your Problem is to add an additional attribute, let's call it canvas, to Baustein, and removing the import Display, so Bauklotz_class.py now looks like this:
class Baustein():
x1, y1, x2, y2 = 10, 10, 20, 20
color = "red"
def __init__(self, x1, y1, x2, y2, color, canvas):
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
self.color = color
self.canvas=canvas
def show_new_object(self):
quadrat2 = self.canvas.create_rectangle(40, 50, 60, 70, fill=self.color)
self.canvas.coords(quadrat2, 40, 50, 60, 70)
And then call it like this in gui_backup.py:
...
def callback1():
import Bauklotz_class
test = Bauklotz_class.Baustein(20, 30, 40, 50, "red", Display)
test.show_new_object()
which will fix your problem, and create a red rectangle instead of duplicating your window.