Close window when graphics.py object has reached the edge of window - python

Referring to John Zelle's graphics.py, I want the GraphWin to close right after the Circle object has reached the edge of the window and is out of sight.
Following code creates a circle and moves it:
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
for i in range(40):
c.move(30, 0) #speed=30
time.sleep(1)
#c should move until the end of the windows(100),
win.close() # then windows of title "My Circle" should close immediately
Is there any way to do this instead of using range and counting its exact number of 'steps'?

Compare the x position of the left side of the circle against the right side of the window:
from graphics import *
WIDTH, HEIGHT = 300, 300
RADIUS = 10
SPEED = 30
win = GraphWin("My Circle", WIDTH, HEIGHT)
c = Circle(Point(50, 50), RADIUS)
c.draw(win)
while c.getCenter().x - RADIUS < WIDTH:
c.move(SPEED, 0)
time.sleep(1)
win.close() # then windows of title "My Circle" should close immediately
In a speedier loop, we might move RADIUS to the other side of the equation and make a new constant of WIDTH + RADIUS.
If it was an Image object, how would you suggest to get the leftmost
position of the object to compare it to the width of the window?
An Image object would work similarly, using it's anchor, instead of center, and using its width instead of its radius:
from graphics import *
WIDTH, HEIGHT = 300, 300
SPEED = 30
win = GraphWin("My Image", WIDTH, HEIGHT)
image = Image(Point(50, 50), "file.gif")
image.draw(win)
image_half_width = image.getWidth() / 2
while image.getAnchor().x - image_half_width < WIDTH:
image.move(SPEED, 0)
time.sleep(1)
win.close() # the window of title "My Image" should close immediately

Related

Why is my Python turtle screen asymmetrical?

I created a pong game where I noticed the paddles are not placed equally at the screen edges.
I created an 800 pixel wide screen, and placed paddles at xcor = 380 and xcor = -380 but on the screen left paddle shows some gap but right paddle doesn't. Is my screen unsymmetrical? How do I fix it?
screen.setup(width=800, height=600)
screen.bgcolor("black")
screen.title("PONG")
screen.tracer(0)
l_paddle = Paddle()
l_paddle.create_paddle((-380, 0))
r_paddle = Paddle()
r_paddle.create_paddle((380, 0))
screenshot of screen
When we specify a window size to setup(), we're talking about total pixels used on the screen. Since there is chrome around the window (edges, title bar, etc.) the actual area we have to work with is slightly smaller. Trying to place two turtles at exactly the left and right edge, ignoring chrome:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
WIDTH, HEIGHT = 600, 400
screen = Screen()
screen.setup(WIDTH, HEIGHT)
l_paddle = Turtle('square')
l_paddle.fillcolor('white')
l_paddle.setx(CURSOR_SIZE/2 - WIDTH/2)
r_paddle = Turtle('square')
r_paddle.fillcolor('white')
r_paddle.setx(WIDTH/2 - CURSOR_SIZE/2)
screen.exitonclick()
We get a result similar to yours:
If we compensate for the internal and external chrome elements:
from turtle import Screen, Turtle
CURSOR_SIZE = 20
BORDER_SIZE = 2 # inside the window
CHROME_SIZE = 9 # around the window
WIDTH, HEIGHT = 600, 400
screen = Screen()
screen.setup(WIDTH, HEIGHT)
l_paddle = Turtle('square')
l_paddle.fillcolor('white')
l_paddle.setx(CURSOR_SIZE/2 - WIDTH/2 + BORDER_SIZE)
r_paddle = Turtle('square')
r_paddle.fillcolor('white')
r_paddle.setx(WIDTH/2 - CURSOR_SIZE/2 - BORDER_SIZE - CHROME_SIZE)
screen.exitonclick()
We can get a more precise result:
The problem here is that the amount of chrome is system dependent but turtle doesn't tell us how much to compensate. You might be able to find out from the underlying tkinter code.
My recommendation is you estimate the best you can, assume it's not accurate on all systems, and stay away from the edges so it's less of an issue. The error can be greater in the Y dimension when the title bar is part of the chrome.

Window shifts with Python Turtle setworldcoordinates()

I've noticed a strange blip with Python Turtle Graphics when using Screen.setworldcoordinates(). For some reason when I click on the window title bar after running the code below, there is a small but perceptible shift of the contents of the window. Can anyone please explain this phenomenon, and let me know if there is way to avoid it? I'm on Windows 10 with Python 3.8.
import turtle
screen = turtle.Screen()
screen.setup(500, 500) # Set the dimensions of the Turtle Graphics window.
screen.setworldcoordinates(0, screen.window_height(), screen.window_width(), 0)
my_turtle = turtle.Turtle(shape="circle")
my_turtle.color("red")
my_turtle.forward(10)
turtle.done()
turtle comes with it's own default config, that uses 50% of your monitor width, and 75% of it's height.
_CFG = {"width" : 0.5, # Screen
"height" : 0.75,
"canvwidth" : 400,
"canvheight": 300,
...
}
There's some interplay between various elements during construction.
self._canvas = TK.Canvas(master, width=width, height=height,
bg=self.bg, relief=TK.SUNKEN, borderwidth=2)
Setting borderwidth to any value above 3 alleviates it; believe that's because it calls for a complete screen redraw.
/usr/lib/python3.9/turtle.py is read-only, though.
Made a local copy with write privs, then modded it for debugging purposes. Named turtel.py instead, just to keep changes seperate. Put in a few print() statements to figure out when functions were running.
Method 1:
You can fix it by forcing new values, before screen construction.
Either by supplying your own turtle.cfg in the same directory as your script, or
Method 2:
overriding those values:
import turtle
Width, Height = 500, 500
turtle._CFG['canvwidth'], turtle._CFG['canvheight'] = Width, Height
screen = turtle.Screen()
Method 3:
_setscrollregion(self, srx1, sry1, srx2, sry2) calls _rescale(self, xscalefactor, yscalefactor)
but doesn't call adjustScrolls(self) until onResize(self, event) is called, after you drag the titlebar.
So you can force a redraw after screen.setworldcoordinates(0, Height, Width, 0)
cv = screen.getcanvas()
cv.adjustScrolls()
Uncomment ## to enable lines, and try.
#! /usr/bin/python3
import turtle
## print( turtle._CFG['width'], turtle._CFG['height'] ) ## 0.5 0.75
## print( turtle._CFG['canvwidth'], turtle._CFG['canvheight'] ) ## 400 300
Width, Height = 500, 500
## turtle._CFG['width'], turtle._CFG['height'] = Width, Height
## turtle._CFG['canvwidth'], turtle._CFG['canvheight'] = Width, Height
screen = turtle.Screen()
screen.setworldcoordinates(0, Height, Width, 0)
## cv = screen.getcanvas()
## cv.adjustScrolls()
my_turtle = turtle.Turtle(shape='circle')
my_turtle.color('red')
my_turtle.forward(10)
def click_callback( x, y ):
cv = screen.getcanvas()
print( cv.width, cv.height, screen.screensize(), cv.winfo_width(), cv.winfo_height() )
print('<< initialized >>')
screen.onclick( click_callback )
screen.mainloop()

pygame "Paint" program - drawing empty rectangle on canvas

So I'm making an MS-Paint-like program using pygame (to which I'm completely new), which has been going well so far, except for this annoying problem that I have.
I want the user to have the ability to draw a rectangle by dragging the mouse along the canvas, similar to how you do in MS-Paint, and I want it to look as if the rectangle is "moving" as long as the mouse is being dragged.
I managed to have it work perfectly yet inefficiently until I was advised that I should use some other pygame methods to make it more efficient. So I did, and the one problem I can't solve is that previous images of the rectangle remain on the screen. Like so:
Top-left is where I started to draw
Here's the relevant portion of my code:
if canvas.collidepoint(cursor) and pygame.mouse.get_pressed()[0] and mode == 'Square' and not draw_square:
start_x, start_y = find_pixel(mouse_x, mouse_y)
width, height = find_pixel(mouse_x, mouse_y)
save_last = screen.subsurface(start_x, start_y, width - start_x, height - start_y)
square = pygame.draw.rect(screen, current_color, (start_x, start_y, width - start_x, height - start_y), 5)
temp_square = square
draw_square = True
if draw_square and canvas.collidepoint(cursor) and pygame.mouse.get_pressed()[0]:
# Mouse is being held down outside canvas, show square progress
prev_width, prev_height = width, height
save_last = screen.subsurface(temp_square)
width, height = find_pixel(mouse_x, mouse_y)
if width != prev_width or height != prev_height:
square = temp_square.inflate(width - start_x, height - start_y)
square.topleft = start_x, start_y
pygame.draw.rect(screen, current_color, square, 5)
if not canvas.collidepoint(cursor) and draw_square:
# Mouse is being held down outside canvas, show square progress
width, height = find_pixel(mouse_x, mouse_y)
if width < 150: # Cursor is left of the canvas
width = 150
if width > 980: # Cursor is right of the canvas
width = 980
if height < 20: # Cursor is above the canvas
height = 20
if height > 580: # Cursor if below the canvas
height = 580
square = temp_square.inflate(width - start_x, height - start_y)
square.topleft = start_x, start_y
pygame.draw.rect(screen, current_color, square, 5)
if draw_square and not pygame.mouse.get_pressed()[0]:
draw_square = False
pygame.display.flip()
I've tried blitting save_last onto the screen but it gives me this error:
Traceback (most recent call last):
File "C:/Users/yargr/PycharmProjects/Paint/PaintApp.py", line 548, in <module>
main()
File "C:/Users/yargr/PycharmProjects/Paint/PaintApp.py", line 435, in main
screen.blit(save_last, (mouse_x, mouse_y))
pygame.error: Surfaces must not be locked during blit
I've tried using screen.unlock() but it didn't work (I guess I don't completely understand how that works). Anyway, if anyone has any idea, I;d love to hear suggestions :)
The issue is that save_last is a subsurface of screen, because of
save_last = screen.subsurface(start_x, start_y, width - start_x, height - start_y)
respectively
save_last = screen.subsurface(temp_square)
subsurface() creates a surface which references to the other surface. The new Surface shares its pixels with the other parent. The new surface has no own data.
If you do
screen.blit(save_last, (mouse_x, mouse_y))
then it seems that the screen gets temporary looked, when save_last is read, because it is referenced. Finally writing to screen fails.
But you can solve the issue by directly copying a region of screen to screen by .blit(). Do not create a subsurface, just notice the rectangular region (pygame.Rect) and copy the region form the screen Surface to another position on screen. e.g:
save_rect = pygame.Rect(start_x, start_y, width - start_x, height - start_y)
# [...]
save_rect = pygame.Rect(temp_square)
# [...]
screen.blit(screen, (mouse_x, mouse_y), area=save_rect)
Alternatively you can crate a copy of the rectangular region and blit the copy to screen:
save_rect = pygame.Rect(start_x, start_y, width - start_x, height - start_y)
save_last = pygame.Surface(save_rect.size)
save_last.blit(screen, (0, 0), save_rect)
# [...]
save_rect = pygame.Rect(temp_square)
save_last = pygame.Surface(save_rect.size)
save_last.blit(screen, (0, 0), save_rect)
# [...]
screen.blit(save_last, (mouse_x, mouse_y))

Why object won't stop moving in Zelle graphics?

from graphics import *
win = GraphWin("Circle Race", 500, 500)
#red circle position
RcenterX = 50
RcenterY = 100
Rright = RcenterX+25
Rleft = RcenterY
# The Red circle
cr = Circle(Point(RcenterX,RcenterY), 25)
cr.setFill('red')
cr.setOutline('black')
cr.draw(win)
if RcenterX<=400:
win.getMouse()
cr.move(50 ,0)
elif RcenterX>300:
win.getMouse()
win.close()
I'm trying to move the red circle 50 pixels after each click, and stop when the right edge of the circle reaches 400.
But it keeps moving after it reaches 400?
How can I make it stop at 400?
Two basic changes: your if statement needs to be a while loop; you need to recalculate RcenterX, either by updating it yourself or interrogating your circle object:
from graphics import *
win = GraphWin("Circle Race", 500, 500)
# red circle position
RcenterX, RcenterY = 50, 100
Rradius = 25
# The Red circle
cr = Circle(Point(RcenterX, RcenterY), Rradius)
cr.setFill('red')
cr.setOutline('black')
cr.draw(win)
while cr.getCenter().getX() + Rradius/2 < 400: # right edge stops at 400
win.getMouse()
cr.move(50, 0)
win.getMouse()
win.close()

Graphics.py Bouncing Cube

I am trying to create a program that "bounces" a cube in a window up and down. Everything is created properly but the cube will not bounce.
The code is as follows:
from graphics import *
import time # Used for slowing animation if needed
i=0
def create_win():
win= GraphWin("Animation",500,500)
cornerB1= Point(235,235)
cornerB2= Point(265,265)
Bob= Rectangle(cornerB1, cornerB2)
Bob.setFill('blue')
Bob.draw(win)
win.getMouse()
win.close()
create_win()
def main():
cornerB1= Point(235,235)
cornerB2= Point(265,265)
Bob= Rectangle(cornerB1, cornerB2)
center= Rectangle.getCenter(Bob)
center_point= Point.getX(center)
for i in range(500):
Bob.move(0,5)
if center_point<15:
dy= -dy
elif center_point>485:
dy= -dy
main()
Any input would be greatly appreciated.
This seems to be too much code with too little planning. Specific issues: you create Bob twice, once in each function -- the blue Bob you see isn't the Bob you're moving; too many numbers -- figure out your base dimensions and calculate everything else from them; you extract the center outside the loop so it never changes -- do it inside the loop so it's changing as Bob moves.
Below is a rework of your code that bounces Bob up and down as intended:
from graphics import *
WIDTH, HEIGHT = 500, 500
BOB_SIZE = 30
BOB_DISTANCE = 5
def main():
win = GraphWin("Animation", WIDTH, HEIGHT)
# Create Bob in the middle of the window
cornerB1 = Point(WIDTH/2 + BOB_SIZE/2, HEIGHT/2 + BOB_SIZE/2)
cornerB2 = Point(WIDTH/2 - BOB_SIZE/2, HEIGHT/2 - BOB_SIZE/2)
Bob = Rectangle(cornerB1, cornerB2)
Bob.setFill('blue')
Bob.draw(win)
dy = BOB_DISTANCE
for _ in range(500):
Bob.move(0, dy)
center = Rectangle.getCenter(Bob)
centerY = Point.getY(center)
# If too close to edge, reverse direction
if centerY < BOB_SIZE/2 or centerY > HEIGHT - BOB_SIZE/2:
dy = -dy
win.close()
main()

Categories