tkinter polygons - python

I'm relatively new to tkinter, and I'm making a Game which uses only squares. the book I'm copying off only shows triangles. Here is the code:
# The tkinter launcher (Already complete)
from tkinter import *
HEIGHT = 500
WIDTH = 800
window = Tk()
window.title ('VOID')
c = Canvas (window, width=WIDTH, height=HEIGHT, bg='black')
c.pack()
# Block maker (Issue)
ship_id = c.create_polygon (5, 5, 5, 25, 30, 15, fill='red')
I don't get any errors, it is just the string of numbers, (5, 5, 5, 25, 30, 15) which I don't get, as I'm trying to make a square.

Abstract of Canvas.create_polygon definition:
As displayed, a polygon has two parts: its outline and its interior. Its geometry is specified as a series of vertices [(x0, y0), (x1, y1), … (xn, yn)] (...)
id = C.create_polygon(x0, y0, x1, y1, ..., option, ...)
So you need to pass the coordinates of the square in this specified order.
For example:
myCanvas.create_polygon(5, 5, 5, 10, 10, 10, 10, 5)
can be read as
myCanvas.create_polygon(5,5, 5,10, 10,10, 10,5)
will create a square whose vertices are (5, 5), (5, 10), (10, 10) and (10, 5).

Here's some info on the create_polygon function (official docs).
According to the nmt.edu page, the format of the function call is
id = C.create_polygon(x0, y0, x1, y1, ..., option, ...)
This means that the ship_id = c.create_polygon (5, 5, 5, 25, 30, 15, fill='red') call creates a polygon with the following vertices: (5,5), (5,25), (30, 15) and fills the polygon with red.
If you want to create a square, you'd have to do the following:
ship_id = c.create_polygon (5, 5, 5, 25, 25, 25, 25, 5, fill='red')
which creates a rectangle with vertices (5,5), (5,25), (25,25), (25,5).
If you wanted a more reproducible way to make ships, you could do something like
def ship (x,y):
return [x-5, y-5, x+5, y-5, x+5, y+5, x-5, y+5]
ship_id = c.create_polygon(*ship(100, 500), fill='red')
The above would create sort of a factory for ships (the lambda function) in which you specify the x and y for the center of the ship and then it gives a list of the vertices that can be used for the create_polygon function.
You could even take this a step further to specify ship size with a tweaked lambda function
def ship (x,y,w,h):
return [x-w/2, y-h/2, x+w/2, y-h/2, x+w/2, y+h/2, x-w/2, y+h/2]
ship_id = c.create_polygon(*ship(100, 500, 8, 8), fill='red')

Related

Best suitable approach to find nearest neighbour to (x, y, z) from list of triplets

I am trying to obtain a triplet from list of triplets that is closest to my required triplet incase if it was not found.
For example:
# V_s,V_g,V_r
triplets = [(500, 12, 5),
(400, 15, 2.5),
(400, 15, 3),
(450, 12, 3),
... ,
(350, 14, 3)]
The triple that I am looking for is
req_triplet = (450, 15, 2) #(Vreq_s, Vreq_g, Vreq_r)
How can I achieve this in python, a best suitable strategy to achieve is what I am in need for.
As of now I am thinking to filter the list by finding nearest parameter V_s. From the resulting list filter further by finding nearest to V_g and finally by V_r.
You can compute Euclidean distance with numPy or you can use
numpy.linalg.norm.
Try this:
>>> import numpy as np
>>> def dist(x,y):
... return np.sqrt(np.sum((x-y)**2))
>>> triplets = [(500, 12, 5), (400, 15, 2.5), (400, 15, 3),(450, 12, 3)(350, 14, 3)]
>>> req_triplet = (450, 15, 2)
>>> arr_dst = [np.linalg.norm(np.array(tr) - np.array(req_triplet)) for tr in triplets]
>>> arr_dst = [dist(np.array(tr), np.array(req_triplet)) for tr in triplets]
>>> arr_dst
[50.17967716117751, 50.002499937503124, 50.00999900019995, 3.1622776601683795, 100.00999950005]
>>> idx = np.argmin(arr_dst)
>>> idx
3
>>> triplets[idx]
(450, 12, 3)
You have to define a metric ||.||, then the triplet T that is close to a fixed one F is the one that minimize ||T - F||. You can use a classic Euclidean distance:
import numpy as np
def dist(u, v):
return np.sqrt(np.sum((np.array(u)-np.array(v))**2))
The general strategy would be to Loop through the list, for each element calculate the distance and check if it is the minimum, otherwise keep going on.
In python this would look something like this-
from math import abs
triplets = [(500, 12, 5),
(400, 15, 2.5),
(400, 15, 3),
(450, 12, 3),
... ,
(350, 14, 3)]
req_triplet = (450, 15, 2)
def calc_dist(a,b):
return sum((abs(a[i]-b[i]) for i in range(3))
def find_closest_triple(req_triplet,triplets):
min_ind = None
min_dist = -1
for i,triplet in enumerate(triplets):
if e == req_triplet:
return i
dist = calc_dist(req_triplet,triplet)
if dist < min_dist:
min_ind = i
return min_ind

How to use tweening in Python, without losing accuracy?

I've been struggling to use tweening to make mouse movements smooth in Python, I am currently trying to automate some repetitive tasks.
I've tried to use tweening to remove some of the roughness that occurs without smoothing applied, however by doing so I am losing a noticeable amount of accuracy, after all my dy and dx values are getting split by a number I end up with remainders. This could possibly be solved by getting the greatest common factor on both my values (since both dx and dy need to be split by the same number) unfortunately this leads to a too small of a GCD.
Since the mouse cannot move the remainder of a pixel on a screen I end up a with noticeable loss of accuracy.
Question: How to apply tweening on mouse movements, without losing accuracy?
import pytweening
import win32api
import win32con
from time import sleep
dy = [50, 46, 42, 38, 33, 29, 24, 20, 15, 10, 10]
dx = [-35, 6, -55, -43, 0, 17, 29, 38, 42, 42, 38]
while True:
count = 0
values = [(pytweening.getPointOnLine(0, 0, x, y, 0.20)) for x, y in zip(dx, dy)]
while win32api.GetAsyncKeyState(win32con.VK_RBUTTON) and win32api.GetAsyncKeyState(win32con.VK_LBUTTON):
if count < len(dx):
for _ in range(5):
win32api.mouse_event(1, int(values[count][0]), int(values[count][1]), 0, 0)
sleep(0.134 / 5)
count += 1
The fundamental problem here is that you are using relative movement in integer amounts, which will not add up to the total movement you are looking for. If you only want to move linearly, you also don't need PyTweening at all. How about this solution?
import win32api
import win32con
from time import sleep
Npoints = 5
sleeptime = 0.134 / Npoints
dys = [50, 46, 42, 38, 33, 29, 24, 20, 15, 10, 10]
dxs = [-35, 6, -55, -43, 0, 17, 29, 38, 42, 42, 38]
x, y = win32api.GetCursorPos()
for dx, dy in zip(dxs, dys):
ddx = dx/Npoints
ddy = dy/Npoints
for _ in range(Npoints):
x += ddx
y += ddy
win32api.SetCursorPos(int(x), int(y))
sleep(sleeptime)
Note that there will still be some very small round-off error and that the cursor will move in a straight line between the points. If the cursor starts at (0, 0), this is the shape it will make (the red crosses are the points where the cursor will be set to):
If you wanted to move in smooth curves through the points and you're OK with using numpy and scipy, this will handle that:
import numpy as np
import scipy.interpolate as sci
totalpoints = 50 # you can set this to a larger number to get closer spaced points
x, y = win32api.GetCursorPos()
# work out absolute coordinates of new points
xs = np.cumsum([x, *dxs])
ys = np.cumsum([y, *dys])
# fit spline between the points (s=0 makes the spline hit all the points)
tck, u = sci.splprep([xs, ys], s=0)
# Evaluate the spline and move to those points
for x, y in zip(*sci.splev(np.linspace(0, 1, totalpoints), tck)):
win32api.SetCursorPos(int(x), int(y))
sleep(sleeptime)
This results in positions as shown below:
Question: Tweening, without losing accuracy?
Reference:
PyTweening - getLinePoint()
x, y = getLinePoint(startPoint x, startPoint y, endPoint x, endPoint y, intervall)
The getLinePoint() function finds a point on the provided line.
Cast your lists, dx anddy, into a list of tuple(x, y)
dx = [-35, 6, -55, -43, 0, 17, 29, 38, 42, 42, 38]
dy = [50, 46, 42, 38, 33, 29, 24, 20, 15, 10, 10]
points = list(zip(dx, dy))
print(points)
Output:
[(-35, 50), (6, 46), (-55, 42), (-43, 38), (0, 33), (17, 29), (29, 24), (38, 20), (42, 15), (42, 10), (38, 10)]
Process this list of points in a double for loop.
import pytweening
for startPoint in points:
for endPoint in points:
x, y = pytweening.getPointOnLine(startPoint[0], startPoint[1],
endPoint[0], endPoint[1],
0.20)
x, y = int(x), int(y)
print('{}, '.format((x, y)), end='')
# win32api.mouse_event(1, x, y, 0, 0)
# sleep(0.134)
Output: The End Points are allways reached!
First move from (-35, 50) to (6, 46):
(-35, 50), (-26, 49), (-39, 48), (-36, 47), (-28, 46), (-24, 45),(-22, 44),
(-20, 44), (-19, 43), (-19, 42), (-20, 42), (-2, 46), (6, 46)
... (omitted for brevity)
Last move from (42, 10) to (38, 10):
(42, 10), (41, 10), (23, 18), (31, 17), (19, 16), (21, 15), (30, 14),
(33, 13), (36, 12), (38, 12), (38, 11), (38, 10), (38, 10)
Tested with Python: 3.6 - pytweening: 1.0.3

"Sensibly" remove points in a Python list

Suppose I have two arrays indicating the x and y coordinates of a calibration curve.
X = [1,2,3,4,5,6,7,8,9,10,12,14,16,18,20,30,40,50]
Y = [2,4,6,8,10,12,14,16,18,20,24,28,32,36,40,60,80,100]
My example arrays above contain 18 points. You'll notice that the x values are not linearly spaced; there are more points at lower values of x.
Let's suppose I need to reduce the number of points in my calibration curve to 13 points. Obviously, I could just remove the first five or the last five points, but that would shorten my overall range of x values. To maintain range and minimise the space between x values I would preferentially remove values x= 2,4,6,8,10. Removing these x points and their respective y values would leave 13 points in the curve as required.
How could I do this point selection and removal automatically in Python? I.e. Is there an algorithm to pick the best x points from a list, where "best" is defined as keeping the points as close as possible while keeping the overall range and adhering to the new number of points.
Please note that the points remaining must be in the original lists, so I can't interpolate the 18 points on to a 13 point grid.
This would maximize the square root distances between the chosen points. It in some sense spreads the points as far as possible.
import itertools
list(max(itertools.combinations(sorted(X), 13), i
key=lambda l: sum((a - b) ** 2 for a, b in zip(l, l[1:]))))
Note that this is only feasible for small problems. The time complexity for selecting k points is O(k * (len(X) choose k)), so basically O(exp(len(X)). So don't even think about using this for, e.g., len(X) == 100 and k == 10.
X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 30, 40, 50]
Y = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 60, 80, 100]
assert len(X) == len(set(X)), "Duplicate X values found"
points = list(zip(X, Y))
points.sort() # sorts by X
while len(points) > 13:
# Find index whose neighbouring X values are closest together
i = min(range(1, len(points) - 1), key=lambda p: points[p + 1][0] - points[p - 1][0])
points.pop(i)
print(points)
Output:
[(1, 2), (3, 6), (5, 10), (7, 14), (10, 20), (12, 24), (14, 28), (16, 32), (18, 36), (20, 40), (30, 60), (40, 80), (50, 100)]
If you want the original series again:
X, Y = zip(*points)
An algorithm that would achieve that:
Convert each number into the sum of the absolute difference to the number to the left and to the right. If a number is missing, first or last cases, then use MAX_INT. For example, 1 would become MAX_INT; 2 would become 2, 10 would become 3.
Remove the first case with the lowest sum.
If you need to remove more numbers, go to 1.
This would remove 2,4,6,8,10,3,...
Here is a recursive approach that repeatedly removes the point which will be the least missed:
def mostRedundantPoint(x):
#returns the index, i, in the range 0 < i < len(x) - 1
#that minimizes x[i+1] - x[i-1]
#assumes len(x) > 2 and that x
#is sorted in ascending order
gaps = [x[i+1] - x[i-1] for i in range(1,len(x)-1)]
i = gaps.index(min(gaps))
return i+1
def reduceList(x,k):
if len(x) <= k:
return x
else:
i = mostRedundantPoint(x)
return reduceList(x[:i]+x[i+1:],k)
X = [1,2,3,4,5,6,7,8,9,10,12,14,16,18,20,30,40,50]
print(reduceList(X,13))
#prints [1, 3, 5, 7, 10, 12, 14, 16, 18, 20, 30, 40, 50]
This list essentially agrees with your intended output since 7 vs. 8 have the same net effect. It is reasonably quick in the sense that it is almost instantaneous in reducing sorted([random.randint(1,10**6) for i in range(1000)]) from 1000 elements to 100 elements. The fact that it is recursive implies that it will blow the stack if you try to remove many more points than that, but with what seems to be your intended problem size that shouldn't be an issue. If need be, you could of course replace the recursion by a loop.

Building a set of tile coordinates

I have an image which I want to divide into tiles of specific size (and cropping tiles that don't fit).
The output of this operation should be a list of coordinates in tuples [(x, y, width, height),...]. For example, dividing a 50x50 image in tiles of size 20 would give: [(0,0,20,20),(20,0,20,20),(40,0,10,20),(0,20,20,20),(20,20,20,20),(40,20,10,20),...] etc.
Given a height, width and tile_size, it seems like I should be able to do this in a single list comprehension, but I can't wrap my head around it. Any help would be appreciated. Thanks!
Got it with:
output = [(x,y,w,h) for x,w in zip(range(width)[::tile_size],[tile_size]*(w_tiles-1) + [w_padding]) for y,h in zip(range(height)[::tile_size],[tile_size]*(h_tiles-1) + [h_padding])]
import itertools
def tiles(h, w, ts):
# here is the one list comprehension for list of tuples
return [tuple(list(ele) + [ts if w-ele[0] > 20 else w-ele[0], ts if h-ele[1] > 20 else h-ele[1]]) for ele in itertools.product(*[filter(lambda x: x % ts == 0, range(w)), filter(lambda x: x % ts == 0, range(h))])]
print tiles(50, 50, 20)
[(0, 0, 20, 20), (0, 20, 20, 20), (0, 40, 20, 10), (20, 0, 20, 20), (20, 20, 20, 20), (20, 40, 20, 1
0), (40, 0, 10, 20), (40, 20, 10, 20), (40, 40, 10, 10)]

Pairing images as np arrays into a specific format

So I have 2 images, X and Y, as numpy arrays, each of shape (3, 30, 30): that is, 3 channels (RGB), each of height and width 30 pixels. I'd like to pair them up into a numpy array to get a specific output shape:
my_pair = pair_up_images(X, Y)
my_pair.shape = (2, 3, 30, 30)
Such that I can get the original images by slicing:
my_pair[0] == X
my_pair[1] == Y
After a few attempts, I keep getting either:
my_pair.shape = (2,) #By converting the images into lists and adding them.
This works as well, but the next step in the pipeline just requires a shape (2, 3, 30, 30)
my_pair.shape = (6, 30, 30) # using np.vstack
my_pair.shape = (3, 60, 30) # using np.hstack
Thanks!
Simply:
Z = np.array([X, Y])
Z.shape
Out[62]: (2, 3, 30, 30)

Categories

Resources