Home > Software engineering >  Is it possible to know the coordinates of a line at a specific point in time in python Tkinter
Is it possible to know the coordinates of a line at a specific point in time in python Tkinter

Time:02-20

This is the game that I have made I want the circle that has been generated to move across the line without actually touching it, the circle moves to the right and downwards when no keypresses are made and is supposed to jump when the spacebar is pressed, when it collides with the line (logic I made for hitreg) it will restart with a brand new line and the circle at the starting position. When it goes from one end to the other it'll restart with a new line that has more divisions so that the game is difficult as you progress through the levels. The only issue I am having right now is that I am not able to start up the game correctly without it exiting the code immediately and I think the reason might be that the coordinates I have calculated are not accurate enough and sometimes the circle crosses the line and still doesn't register a collision.

Code for the game

from tkinter import *
from random import randint as rand
import keyboard, math

window = Tk()
window.geometry("1440x720")
canvas = Canvas(window, width=1440, height=720, 
bg="white")

l1, l2, l3 = [], [], []
x = 1
b = canvas.create_oval(0, 300, 10, 300   60)
c = b

def genLine():
    global x
    f = 0
    z = 360
    y = 1440 / x
    while x != 0:
        ran = rand(300, 420)
        u = canvas.create_line(f, z, y, ran)
        r = canvas.coords(u)
        l1.append(r)
        f = y
        x -= 1
        y  = y
        z = ran

def hitReg():
    global l2, l3
    for i in l1:
        grad = ((i[3] - i[1]) / (i[2] - i[0]))
        l2.append(grad)
    for i in l2:
        for f in l1:
            x = 0
            length = f[2] - f[0]
            while x != length:
                point = x * i   f[1]
                l3.append(math.ceil( point))
                x  = 0.25

def move_b():
    global l3, x
    canvas.move(c, 1, 1)
    y = canvas.coords(c)
    if keyboard.is_pressed('space'):
        canvas.move(b, 1, -5)
    elif y[1] in l3 or y[3] in l3:
        exit()
    elif y[2] >= 1440:
        x  = 1
        genLine()
        hitReg()
        move_b()
    else:
        pass
    window.after(10, move_b)

genLine()
hitReg()
move_b()

canvas.pack()
window.mainloop()

CodePudding user response:

Main problem is

elif y[1] in l3 or y[3] in l3:

because l3 has all y values which are on line and it compares y[1] and y[3] with all y values but it should compare only with the nearest - with point which has the same x as center of oval.

I was thinking to use canvas.find_overlapping() or canvas.find_closest() but it would need to create small rectange or small oval for every hit point on line.

Finally I created hit points every 1px instead of 0.25px so I could put them on list and easily get

point_y = points[ int(oval_center_x) ]

and later check oval_y1 < point_y < oval_y2.

I also used window.bind("<space>", function) to move oval so I don't need extra module keyboard. Besides, I use Linux and keyboard on Linux needs admin privilege.

I also changed names of variables and functions to make them more readable.

As for exit() I created function reset() which sets level, removes all lines canvas.delete(*lines), moves oval to beginning canvas.moveto(oval, 0, 330), and generates new line (using value level) and generates hit points. Using reset(level 1) I can generate next level, and using reset(1) I can go back to first level.

import tkinter as tk        # PEP8: `import *` is not preferred
from random import randint  # PEP8: every module in separted line

# --- functions ---  # PEP8: all functions directly after imports
                     # PEP8: `lower_case_names` for functions and variables

def generate_lines(level):
    
    regions = []
    lines = []
        
    x1 = 0
    y1 = 360
    
    step = 1440 / level 
    
    x2 = step
    
    for i in range(level):
        y2 = randint(300, 420)  # 360-60, 360 60
        
        region = (x1, y1, x2, y2)
        regions.append(region)

        line = canvas.create_line(x1, y1, x2, y2)
        lines.append(line)
        
        x1 = x2
        y1 = y2

        x2  = step

    return lines, regions
    
def generate_hit_points(regions):
    
    points = []

    for x1,y1,x2,y2 in regions:
        
        steps = int(x2 - x1)
        dy = (y2 - y1) / steps
        
        y = y1
        
        for _ in range(steps):   # cant use `range(y1, y2, dy)` because `dy` is `float`, not `int`

            points.append(y)  # I don't need `x`
            
            y  = dy

    return points
    
def move_oval():

    canvas.move(oval, 1, 1)
    
    x1, y1, x2, y2 = canvas.coords(oval)
    
    oval_center_x = int(x1 10)
    point_y = points[oval_center_x]
    #print(y1, '<', int(point_y), '<', y2, '?')

    #if y1 >= point_y or point_y >= y2:  # less readable
    if not (y1 < point_y < y2):          # more readable
        print('end')
        reset(1)
        
    elif x2 >= 1440:
        print('next level')
        reset(level 1)
        
    window.after(25, move_oval)

def on_press_space(event):
    canvas.move(oval, 0, -15)

def reset(new_level):
    # all globals moved to one function
    global lines
    global points
    global level
    
    level = new_level
    
    canvas.delete(*lines)        # remove all lines
    canvas.moveto(oval, 0, 330)  # move oval to the beginning
    
    lines, regions = generate_lines(level)
    points = generate_hit_points(regions)  

# --- main ---

lines  = []  # PEP8: every variable in separted line
points = []  # PEP8: every variable in separted line

level = 1

window = tk.Tk()

window.geometry("1440x720")

canvas = tk.Canvas(window, width=1440, height=720, bg="white")
canvas.pack()

oval = canvas.create_oval(0, 360-30, 10, 360 30)

window.bind('<space>', on_press_space)
window.bind('q', lambda event:window.destroy())

reset(1)

# start moving
move_oval()

window.mainloop()

PEP 8 -- Style Guide for Python Code

  • Related