How would I go about finding the current position of the mouse pointer in the turtle screen? I want it so I have the mouse position before I click and while im moving the cursor. I have searched google and this website can't find anything besides how to get the position after a click.
turtle.getcanvas() returns a Tkinter Canvas.
Like with a Tkinter window, you can get the current mouse pointer coordinates by winfo_pointerx and .winfo_pointery on it:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
# or
# x, y = canvas.winfo_pointerxy()
If you want to only react to movement instead of e.g. polling mouse pointer positions in a loop, register an event:
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
canvas = turtle.getcanvas()
canvas.bind('<Motion>', motion)
Note that events only fire while the mouse pointer hovers over the turtle canvas.
All these coordinates will be window coordinates (with the origin (0, 0) at the top left of the turtle window), not turtle coordinates (with the origin (0, 0) at the canvas center), so if you want to use them for turtle positioning or orientation, you'll have to do some transformation.
Following up on this answer, here's an example of one way to normalize the Tkinter coordinates for turtle using the canvas.bind('<Motion>', motion) approach (canvas.winfo_pointerxy() didn't provide values that made sense to me, although I didn't look into it much):
import turtle
def on_motion(event):
x = event.x - turtle.window_width() / 2
y = -event.y + turtle.window_height() / 2
turtle.goto(x, y)
turtle.stamp()
print(x, y)
turtle.tracer(0)
turtle.penup()
turtle.getcanvas().bind("<Motion>", on_motion)
turtle.exitonclick()
Using this in a real-time app rather than only on motion:
import turtle
def on_motion(event):
global mouse_x, mouse_y
mouse_x = event.x - turtle.window_width() / 2
mouse_y = -event.y + turtle.window_height() / 2
def tick():
print(mouse_x, mouse_y)
turtle.goto(mouse_x, mouse_y)
turtle.stamp()
win.ontimer(tick, frame_delay_ms)
turtle.tracer(0)
mouse_x, mouse_y = 0, 0
turtle.getcanvas().bind("<Motion>", on_motion)
turtle.shape("circle")
turtle.penup()
frame_delay_ms = 1000 // 30
win = turtle.Screen()
tick()
turtle.exitonclick()
Putting it into a more full-featured app without global:
import turtle
from math import sin
def add_mouse_listener():
def on_motion(event):
nonlocal x, y
x = event.x - turtle.window_width() / 2
y = -event.y + turtle.window_height() / 2
turtle.getcanvas().bind("<Motion>", on_motion)
x, y = 0, 0
return lambda: (x, y)
def color(c, a):
return sin(c + a) / 2 + 0.5
def colors(r, ra, g, ga, b, ba):
return color(r, ra), color(g, ga), color(b, ba)
def main():
ra = 0
ba = 0
ga = 0
r = 0.5
b = 0
g = 1
frame_delay_ms = 1000 // 30
turtle.tracer(0)
turtle.pensize(40)
mouse_pos = add_mouse_listener()
win = turtle.Screen()
def tick():
nonlocal ra, ba, ga
turtle.color(colors(r, ra, g, ga, b, ba))
ra += 0.03
ba += 0.0311
ga += 0.032
x, y = mouse_pos()
turtle.setheading(turtle.towards(x, y))
turtle.forward(10)
turtle.update()
win.ontimer(tick, frame_delay_ms)
tick()
turtle.exitonclick()
if __name__ == "__main__":
main()
Related
import turtle as t
from random import randint, random
def draw_star(points, size, col, x, y):
t.penup()
t.goto(x, y)
t.pendown()
angle = 180 - (180 / points)
t.color(col)
t.begin_fill()
for i in range(points):
t.forward(size)
t.right(angle)
t.end_fill()
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(-350, 300)
ranY = randint(-250, 250)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
Question:
How could I know the maximum values of coordinates of my screen? So I can have a better idea on how to set the values of ranX and ranY?
Thanks.
You could use t.setworldcoordinates(llx, lly, urx, ury)
The parameters:
llx = x of lower left corner
lly = y of lower left corner
urx= x of upper right corner
ury = y of upper right corner
You can create a function and find the values of coordinates yourself by clicking on the screen like this:
# turtle library
import turtle
#This to make turtle object
tess=turtle.Turtle()
# self defined function to print coordinate
def buttonclick(x,y):
print("You clicked at this coordinate({0},{1})".format(x,y))
#onscreen function to send coordinate
turtle.onscreenclick(buttonclick,1)
turtle.listen() # listen to incoming connections
turtle.speed(10) # set the speed
turtle.done() # hold the screen
This will print everytime you click on the screen and print the coordinates out.
The screensize() function returns the canvas width and the canvas height as a tuple.
You can use this to find the max coordinates of the canvas.
screenSize = t.screensize() #returns (width, height)
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(50-screenSize[0], screenSize[0] - 100)
ranY = randint(50-screenSize[1], screenSize[1] - 100)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
I found out this is what I need: t.window_width() and t.window_height().
I am trying to find the color of the canvas under a Python turtle. I use canvas.find_overlapping but it is only successful when I negate the ycor, implying that the y-axis is inverted in the canvas object, compared to what is shown. Is there a problem with my code or is the y-axis inverted?
import turtle
wn = turtle.Screen()
maze_drawer = turtle.Turtle()
maze_drawer.color("purple")
maze_drawer.speed("fastest")
path_width = 15
def get_pixel_color(x, y):
c = turtle.Screen().getcanvas()
# -y should not work??
items = c.find_overlapping(x, -y, x, -y)
if len(items) > 0:
return c.itemcget(items[0], "fill") # get 0 object (canvas)
# draw simplified maze
wall_len = 0
for i in range(10):
maze_drawer.left(90)
wall_len += path_width
maze_drawer.forward(wall_len)
# navigate maze from center
maze_runner = turtle.Turtle()
maze_runner.color("green")
maze_runner.penup()
maze_runner.goto(-path_width, -path_width)
# test in y dir: maze_runner.setheading(90)
clear = True
while(clear):
maze_runner.forward(1)
color_at_turtle = get_pixel_color(maze_runner.xcor(), maze_runner.ycor())
if (color_at_turtle == "purple"):
clear = False
wn.exitonclick()
Neat use of tkinter pixel detection within turtle! If the inverted Y coordinate is bothersome, you can flip it from turtle's perspective:
from turtle import Screen, Turtle
screen = Screen()
width, height = screen.window_width() / 2, screen.window_height() / 2
screen.setworldcoordinates(-width, height, width, -height) # flip Y coordinate
Then your code doesn't have to think about negating Y as long as you know you're drawing upside down:
from turtle import Screen, Turtle
PATH_WIDTH = 15
def get_pixel_color(x, y):
canvas = screen.getcanvas()
items = canvas.find_overlapping(x, y, x, y)
if items:
return canvas.itemcget(items[0], "fill") # get 0 object (canvas)
return None
screen = Screen()
width, height = screen.window_width() / 2, screen.window_height() / 2
screen.setworldcoordinates(-width, height, width, -height)
maze_drawer = Turtle(visible=False)
maze_drawer.color("purple")
maze_drawer.speed("fastest")
# draw simplified maze
wall_len = 0
for _ in range(20):
maze_drawer.left(90)
wall_len += PATH_WIDTH
maze_drawer.forward(wall_len)
# navigate maze from center
maze_runner = Turtle()
maze_runner.color("dark green", "green")
maze_runner.penup()
maze_runner.goto(-PATH_WIDTH, -PATH_WIDTH)
def run_maze():
maze_runner.forward(1)
x, y = maze_runner.position()
color_at_turtle = get_pixel_color(x, y)
if color_at_turtle == "purple":
maze_runner.backward(PATH_WIDTH - 1)
maze_runner.left(90)
x, y = maze_runner.position()
if -width < x < width and -height < y < height:
screen.ontimer(run_maze, 10)
run_maze()
screen.exitonclick()
This is for an extra credit assignment in Python. I've finished most until the last part where I have to determine the area of the tictactoe box chosen.
I can only detect diagonal boxes, used from combining both code reply's below.
I can detect those 3 boxes but the rest still show as none and most logic is used with loop so I can understand where I am going wrong.
import turtle
from time import sleep
import sys
CURSOR_SIZE = 20
SQUARE_SIZE = 99
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')
BOXES = {}
# TRACK BOX
pen = turtle.Turtle()
pen.penup()
def mouse(x, y):
print('|--------------X={0} Y={1}--------------|'.format(x, y))
for key in BOXES:
minx, miny, maxx, maxy = BOXES[key]
print(key, BOXES[key])
if (minx <= x <= maxx) and (miny <= y <= maxy):
print("Found", key)
return key
print('None')
return None # Not found.
class TicTacToe:
global BOXES
def __init__(self):
# CREATES 2D LIST FOR INTERROGATION
self.board = [['?'] * 3 for i in range(3)]
def minmax(self, points):
""" Find extreme x and y values in a list of 2-D coordinates. """
minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
for x, y in points[1:]:
if x < minx:
minx = x
if y < minx:
miny = y
if x > maxx:
maxx = x
if y > maxy:
maxy = y
return minx, miny, maxx, maxy
def drawBoard(self):
##############################################
turtle.shape('square')
turtle.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
turtle.color('black')
turtle.stamp()
turtle.hideturtle()
##############################################
for j in range(3):
for i in range(3):
# CREATES SHAPE AND STORES IN PLACEHOLDER
turtle.shape('square')
box = turtle.shape('square')
# CREATES SHAPE SIZE AND STORES IN PLACEHOLDER
turtle.shapesize(SQUARE_SIZE / CURSOR_SIZE)
boxsize = turtle.shapesize()
# CREATES SHAPE COLOR
turtle.color('white')
turtle.penup()
# CREATES SHAPE POS AND STORES IN PLACEHOLDER
turtle.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
boxpos = turtle.pos()
mypos = []
pen.goto(boxpos[0]-50,boxpos[1]+50)
##############################################
for line in range(0, 4):
pen.forward(SQUARE_SIZE)
pen.right(90)
mypos.append(pen.pos())
turtle.showturtle()
turtle.stamp()
##############################################
a = mypos[0]
b = mypos[1]
c = mypos[2]
d = mypos[3]
self.board[j][i] = [a, b, c, d]
##############################################
BOXES['BOX01'] = self.minmax(self.board[0][0])
BOXES['BOX02'] = self.minmax(self.board[0][1])
BOXES['BOX03'] = self.minmax(self.board[0][2])
##############################################
BOXES['BOX11'] = self.minmax(self.board[1][0])
BOXES['BOX12'] = self.minmax(self.board[1][1])
BOXES['BOX13'] = self.minmax(self.board[1][2])
##############################################
BOXES['BOX21'] = self.minmax(self.board[2][0])
BOXES['BOX22'] = self.minmax(self.board[2][1])
BOXES['BOX23'] = self.minmax(self.board[2][2])
##############################################
turtle.onscreenclick(mouse)
turtle.setup(800, 600)
wn = turtle.Screen()
z = TicTacToe()
z.drawBoard()
turtle.mainloop()
I believe you're making the problem harder than necessary by not taking full advantage of Python turtle. Instead of trying to find a square within the board when clicking on the screen, make the squares of the board themselves turtles that respond to mouse clicks. Then there's nothing to figure out, position-wise.
Here's a reimplementation that draws a board, allows you to click on it, alternately sets the clicked sections to 'X' or 'O':
from turtle import Turtle, Screen
CURSOR_SIZE = 20
SQUARE_SIZE = 50
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')
class TicTacToe:
def __init__(self):
self.board = [['?'] * 3 for i in range(3)] # so you can interrogate squares later
self.turn = 'X'
def drawBoard(self):
background = Turtle('square')
background.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
background.color('black')
background.stamp()
background.hideturtle()
for j in range(3):
for i in range(3):
box = Turtle('square', visible=False)
box.shapesize(SQUARE_SIZE / CURSOR_SIZE)
box.color('white')
box.penup()
box.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
box.showturtle()
box.stamp() # blank out background behind turtle (for later)
self.board[j][i] = box
box.onclick(lambda x, y, box=box, i=i, j=j: self.mouse(box, i, j))
def mouse(self, box, i, j):
box.onclick(None) # disable further moves on this square
# replace square/turtle with (written) X or O
box.hideturtle()
box.color('black')
box.sety(box.ycor() - FONT_SIZE / 2)
box.write(self.turn, align='center', font=FONT)
self.board[j][i] = self.turn # record move
self.turn = ['X', 'O'][self.turn == 'X'] # switch turns
screen = Screen()
game = TicTacToe()
game.drawBoard()
screen.mainloop()
You can use board to do scoring, or implement a smart computer player, or whatever you desire.
This should give you the basic idea, which is compute the minimum and maximum x and y values of each box and store them in BOXES. This makes it very easy to determine if the given x and y coordinates passed to the mouse() callback function are within any of them.
With the multiple boxes in your real code, make sure to apply the new minmax() function to the corners of each one of them.
import turtle
""" This will be based off 1 box instead of all 9"""
pen = turtle.Turtle()
corners = []
BOXES = {}
for line in range(0, 4):
pen.forward(50)
pen.left(90)
corners.append(pen.pos())
def minmax(points):
""" Find extreme x and y values in a list of 2-D coordinates. """
minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
for x, y in points[1:]:
if x < minx:
minx = x
if y < minx:
miny = y
if x > maxx:
maxx = x
if y > maxy:
maxy = y
return minx, miny, maxx, maxy
BOXES['MIDDLEBOX'] = minmax(corners)
for i in BOXES:
print(i, BOXES[i])
"""
POINTS FROM LEFT TO RIGHT
(1) - TOP LEFT CORNER
(2) - BOTTOM LEFT CORNER
(3) - BOTTOM RIGHT CORNER
(4) - TOP RIGHT CORNER
"""
def mouse(x, y):
""" Return key of box if x, y are within global BOXES
or None if it's not.
"""
for key in BOXES:
minx, miny, maxx, maxy = BOXES[key]
if (minx <= x <= maxx) and (miny <= y <= maxy):
print(key)
return key
print('None')
return None # Not found.
turtle.onscreenclick(mouse)
turtle.mainloop()
I am trying to move the green object called char relative to the mouse x and mouse y coordinates but I don't know how. Can anyone help me? In case you are wondering i am trying to make a version of single player agario.
from tkinter import *
import random
from random import uniform, randrange
import time
#left,top,right,bottom
tk = Tk()
canvas = Canvas(tk,width=600,height=600)
canvas.pack()
pointcount = 0
char = canvas.create_oval(400,400,440,440,fill="green")
pos1 = canvas.coords(char)
x = canvas.canvasx()
y = canvas.canvasy()
class Ball:#ball characteristics#
def __init__(self,color,size):
self.shape = canvas.create_oval(10,10,50,50,fill=color)
self.xspeed = randrange(-5,7)
self.yspeed = randrange(-5,7)
def move(self):#ball animation#
global pointcount
canvas.move(self.shape,self.xspeed,self.yspeed)
pos = canvas.coords(self.shape)
if pos[0] <= 0 or pos[2] >= 600:#if ball hits the wall#
self.xspeed = -self.xspeed
if pos[1] <= 0 or pos[3] >= 600:
self.yspeed = -self.yspeed
left_var = abs(pos[0] - pos1[0])
top_var = abs(pos[1] - pos1[1])
if left_var == 0 and top_var == 0:
pointcount = pointcount + 1
print(pointcount)
colors = ["red","blue","green","yellow","purple","orange"]
balls = []
for i in range(10):
balls.append(Ball(random.choice(colors),50))
while True:
for ball in balls:
ball.move()
tk.update()
time.sleep(0.01)
To move the green circle with the mouse, you need to bind the position of the circle to mouse motion event. Here is an example where the circle is always centered on the mouse when the mouse is in the canvas:
from tkinter import *
root = Tk()
canvas = Canvas(root)
canvas.pack(fill="both", expand=True)
char = canvas.create_oval(400,400,440,440,fill="green")
def follow_mouse(event):
""" the center of char follows the mouse """
x, y = event.x, event.y
canvas.coords(char, x - 20, y - 20, x + 20, y + 20)
# bind follow_mouse function to mouse motion events
canvas.bind('<Motion>', follow_mouse)
root.mainloop()
I am generating diagrams in Turtle, and as part of my program, I identify certain coordinates from my diagrams. I would like to be able to hide the complete turtle window, since I only care about the coordinates, is that possible?
Edit:
QUESTION 2:
This isn't really an answer, but a few other questions.
I got my program working to some extent, if you run it in IDLE and type "l" it will give you the list with the coordinates.
import Tkinter
import turtle
from turtle import rt, lt, fd # Right, Left, Forward
size = 10
root = Tkinter.Tk()
root.withdraw()
c = Tkinter.Canvas(master = root)
t = turtle.RawTurtle(c)
t.speed("Fastest")
# List entire coordinates
l = []
def findAndStoreCoords():
x = t.xcor()
y = t.ycor()
x = round(x, 0) # Round x to the nearest integer
y = round(y, 0) # Round y to the nearest integer
# Integrate coordinates into sub-list
l.append([x, y])
def hilbert(level, angle):
if level == 0:
return
t.rt(angle)
hilbert(level - 1, -angle)
t.fd(size)
findAndStoreCoords()
t.lt(angle)
hilbert(level - 1, angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, angle)
t.lt(angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, -angle)
t.rt(angle)
The problem is that Turtle is so SLOW! Is there any package that is just like Turtle but can do commands much faster?
I reimplemented the turtle class as suggested by thirtyseven. It is consistent with the api. (i.e. when you turn right in this class, it is the same as turning right in turtle.
This does not implement all the methods in the api, only common ones. (And the ones you used).
However, it's short and fairly straightforward to extend. Also, it keeps track of all of the points it has been to. It does this by adding an entry to pointsVisited every time you call forward, backward, or setpos (or any of the aliases for those functions).
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = y[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()
Yes, this is possible. The simplest way is to instantiate a root Tkinter window, withdraw it, and then use it as the master window for a RawTurtle's Canvas instance.
Example:
import Tkinter
import turtle
root=Tkinter.Tk()
root.withdraw()
c=Tkinter.Canvas(master=root)
t=turtle.RawTurtle(c)
t.fd(5)
print t.xcor() # outputs 5.0
Unfortunately, this still initiates the graphics system, but no window will appear.
The problem is that Turtle is so SLOW! Is there any package that is
just like Turtle but can do commands much faster?
Yes, turtle can. If we add a TurtleScreen to the tkinter implementation, and use it's tracer() functionality, we can speed things up more than turtle's speed() method. And we can simplify the code greatly by tossing the customizations and simply use turtle's own begin_poly(), end_poly() and get_poly() methods:
from tkinter import Tk, Canvas
from turtle import TurtleScreen, RawTurtle
SIZE = 10
def hilbert(level, angle):
if level == 0:
return
turtle.right(angle)
hilbert(level - 1, -angle)
turtle.forward(SIZE)
turtle.left(angle)
hilbert(level - 1, angle)
turtle.forward(SIZE)
hilbert(level - 1, angle)
turtle.left(angle)
turtle.forward(SIZE)
hilbert(level - 1, -angle)
turtle.right(angle)
root = Tk()
root.withdraw()
canvas = Canvas(master=root)
screen = TurtleScreen(canvas)
screen.tracer(False) # turn off turtle animation
turtle = RawTurtle(screen)
turtle.begin_poly() # start tracking movements
hilbert(5, 90)
turtle.end_poly() # end tracking movements
print(turtle.get_poly())
This prints all the points in a level 5 Hilbert curve in about 1/3 of a second on my system. Your posted code toke nearly 9 seconds to output a level 4.