Window shifts with Python Turtle setworldcoordinates() - python

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()

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.

Is there a way to avoid the recursion limit in my Turtle-program?

import turtle
from turtle import Turtle
WIDTH = 1000
HEIGHT = 1000
#Screen setup
screen = turtle.Screen()
screen.setup(WIDTH, HEIGHT)
screen.title(" " *150 + "Test_GIU")
screen.bgcolor("black")
screen.setup(1000, 1000)
#Pen
pen = Turtle("circle")
pen.pensize = 5
pen.color("green")
pen.speed(-1)
def dragging(x, y): # These parameters will be the mouse position
pen.ondrag(None)
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
pen.ondrag(dragging)
def click_on_c():
screen.reset()
pen = Turtle("circle")
pen.pensize = 5
pen.color("green")
pen.speed(-1)
pen.ondrag(dragging)
def main(): # This will run the program
turtle.listen()
pen.ondrag(dragging) # When we drag the turtle object call dragging
turtle.onkeypress(click_on_c, "c")
screen.mainloop() # This will continue running main()
main()
This is my code, im pretty new to it, so its not very good, but its my first real project. I´ve already tried to increase the recursin limit, but it crashes even if I set it to 10000. I also tried to catch the error with an try and exept block, but it also doesnt work.
Let's try a simpler design where instead of calling screen.reset() and recreating the turtle, we instead call pen.reset() to clear the drawing:
from turtle import Screen, Turtle
WIDTH = 1000
HEIGHT = 1000
def dragging(x, y): # Parameters are the mouse position
pen.ondrag(None)
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
pen.ondrag(dragging)
def click_on_c():
pen.reset()
pen.pensize = 5
pen.color("green")
pen.speed('fastest')
# Screen setup
screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.title("Test_GUI")
screen.bgcolor("black")
# Pen
pen = Turtle("circle")
pen.pensize = 5
pen.color("green")
pen.speed('fastest')
pen.ondrag(dragging)
screen.onkeypress(click_on_c, "c")
screen.listen()
screen.mainloop()
We have to reset some aspects of the pen after calling reset() as that call clears the settings back to the defaults.

How to make font change its size to fit in the window using turtle?

I wrote code with Python's turtle module to display text in a 600x600 window.
import turtle
import tkinter
win = turtle.Screen()
win.title("test")
win.bgcolor("white")
win.setup(width=600, height=600)
win.tracer(0)
pen1 = turtle.Turtle()
pen1.speed(0)
pen1.shape("square")
pen1.color("black")
pen1.penup()
pen1.hideturtle()
pen1.goto(-2, -2)
pen1.color("black")
pen1.write("test", align="center", font=("Helvetica Narrow", 68, "bold"))
while True:
win.update()
It works, but I want it to change the font size when it doesn't fit the window.
When I use small words like "test", it works:
But when I use large words it overflows like this:
Is there any way I can make so that it automatically changes the font size to fit the window?
One way of doing this is by:
Setting up a function that calculates the intended font size based on text to be shown and the width of the window
Passing that font size to another function that erases and re-writes the message at every update with the intended font size and the message.
Code for that implementation is below. I doubt that the while True: update approach to screen refresh is robust enough for many things beyond showing some text on the screen, however.
import turtle as t
win = t.Screen()
win.title("test")
win.bgcolor("white")
win.setup(width=600, height=600)
win.tracer(0)
pen1 = t.Turtle() # define the pen turtle to be globally accessible
pen1.speed(0)
pen1.shape("square")
pen1.color("black")
def write_text(text):
pen1.clear() # clear previously written text
pen1.penup()
pen1.hideturtle()
pen1.goto(-2, -2)
font_size = scale_font(text) # calculate the fitting font size
pen1.write(text, align="center", font=("Helvetica Narrow", font_size , "bold"))
def scale_font(text):
font_size = int(win.window_width()/len(text)) # use window width and length of text to calculate font size
return font_size
while True:
write_text("hi there") # continuously rewrite the message at every update
win.update()
I believe the following does what you describe. It pulls in the tkinter font interface a la this earlier post about vertically centering fonts. I only adjust the font size based on width -- if you need to also adjust based on height, you'll need to add to the code. Just run it, sit back, and watch:
from turtle import Screen, Turtle
from tkinter import font
FONT_FAMILY = "Helvetica Narrow"
FONT_SIZE_ESTIMATE = 60
# from https://www.wikihow.com/Sample/Diamante-Poem
lines = [
"Summer",
"Warm, bright",
"Laughing, running, playing",
"A time to be outside and feel the sun",
"Enjoying, swimming, eating",
"Peaceful, nice",
"Heat",
]
font_size = FONT_SIZE_ESTIMATE
def display():
global font_size
text = lines.pop(0)
while True:
font_config = font.Font(font=(FONT_FAMILY, font_size))
width = font_config.measure(text)
if width > window_width * 0.9:
font_size -= 1
elif width < window_width * 0.8:
font_size += 1
else:
break
offset = font_config.metrics('ascent') / 2
pen.sety(-offset)
pen.clear()
pen.write(text, align="center", font=font_config)
if lines:
screen.ontimer(display, 1500)
screen = Screen()
screen.setup(width=600, height=600)
window_width = screen.window_width()
pen = Turtle()
pen.hideturtle()
pen.penup()
display()
screen.exitonclick()

Canvas.Move doesn't work

def moveR(amount):
global x
global y
x = x+amount
can.itemconfig(player, image = playerImageL)
can.move("player", x, y)
root.update()
##SETTING##
can = Canvas(width = 850, height = 550, bg = "black")
can.pack(expand = YES, fill = BOTH)
player = can.create_image(x, y, image = playerImageL, anchor = NW)
root.update()
Hey, i am trying to create a mini game using tkinter and canvas however the move command doesn't work. As you see the SETTING is the setup later in the code i am calling
moveR(100)
however it doesn't work and seems to completly destroy my sprite/image.
Text "player" and variable player are two different things.
Use variable player in move()
can.move(player, x, y)
BTW: you don't need itemconfig()
Please read https://stackoverflow.com/help/mcve. To make the code easily verifiable, use something like a rectangle instead of an image. (Your code fails the same for any item.)
Canvas.move(item, delta_x, delta_y) moves the item a given x and y amount. It does the x + dy calculation itself, so you should not. If you want to move to a given position, use Canvas.coords(item, x0, y0, x1, y1). If the size of the new bounding box is different from what it was, it will also change the shape. The following example uses both methods. As a bonus, it also shows how to use root.after to make repeated changes.
import tkinter as tk
root = tk.Tk()
can = tk.Canvas(root, width=800, height=500)
can.pack()
rec = can.create_rectangle(0, 0, 100, 100, fill='red')
def rmove():
box = can.bbox(rec)
if box[0] < 700:
can.move(rec, 100, 30)
root.after(1000, rmove)
else:
can.coords(rec, 0, 400, 50, 500) # position with new shape
root.after(1000, rmove)
root.mainloop()

Remove canvas widgets in Tkinter

from Tkinter import *
import random
root = Tk()
width = 700
height = 600
canvas = Canvas(root, width = width, height = height, bg = "light blue")
canvas.pack()
pipes = []
class NewPipe:
def __init__(self, pipe_pos, pipe_hole):
self.pipe_pos = list(pipe_pos)
def update(self):
self.pipe_pos[0] -= 3
self.pipe_pos[2] -= 3
def draw(self):
canvas.create_rectangle(self.pipe_pos, fill = "green")
def get_pos(self):
return self.pipe_pos
def generate_pipe():
pipe_hole = random.randrange(0, height)
pipe_pos = [width - 100, 0, width, pipe_hole]
pipes.append(NewPipe(pipe_pos, pipe_hole))
draw_items()
canvas.after(2000, generate_pipe)
def draw_items():
for pipe in pipes:
if pipe.get_pos()[2] <= 0 - 5:
pipes.remove(pipe)
else:
pipe.draw()
pipe.update()
canvas.after(100, draw_items)
def jump(press):
pass
canvas.bind("<Button-1>", jump)
canvas.after(2000, generate_pipe)
draw_items()
mainloop()
Right now I am trying to make a game where you have to dodge rectangles, which are pipes. It is basically Flappy Bird, but on Tkinter. In this code I am trying to generate pipes and move them, but the pipes I have drawn before do not leave and they just stay there. This means that when the pipe moves, the position it was just in doesnt change and that shape stays there. Is there any way to delete past shapes, or another way to move them?
canvas.create_rectangle(self.pipe_pos, fill = "green") returns an ID.
You can use this ID to put it into methods like
canvas.coords
canvas.delete
canvas.itemconfigure
canvas.scale
canvas.type
...
Have a look at help(canvas).
The canvas is not a framebuffer on which you paint stuff for one frame. The painted stuff does not go away and you can move it and change all the parameters you can use when creating.

Categories