-1

I have a program for a school project that is a game based around clicking a target that randomly spawns. Currently, when I am clicking the target nothing is happening and not directing to the hitcounter function. My laptop is a bit slow but whenever I try to click on the button there is no registration graphically of a click either.

I tried redundantly the use of lambda which did not solve the issue, but I saw two people request for my function that I call upon after button click, so I have provided it. If there are any further queries please ask, I will not be losing this account.

...
def hitcounter(*args):
    print("  wdwd ")
    hitcounter += 1
    canvas.delete("counter1_")
    counter1_ = canvas.create_text(screenwidth-50,screenheight/4,fill="Blue",font="Times 20",text="HiTS:"+'\n '+str(hitcounter), tag='counter1')
#as requested, I hope this helps thanks!

    spawntarget()

def spawntarget():
    print("spawntarget")
    x_target = random.randint(75,screenwidth-75-50)
    y_target = random.randint(75,screenheight-75-100)
    global clicktarget
    clicktarget=Button(root ,text='ENEMY', width=10, height=10, command = hitcounter)
    clicktarget.configure(fg='red', bg='gold')
    print("spawntarget2")
    while True:
        clicktarget.place(x=x_target,y=y_target)
        canvas.update()
        time.sleep(3)
        x_target = random.randint(75,screenwidth-75-50)
        y_target = random.randint(75,screenheight-75-100)


root.mainloop()

#I want my button to be clicked and 
#call upon the hitcounter function, 
#but currently it does not and there 
#is no error message. Thanks for looking at my problem.
Twinings
  • 71
  • 8

2 Answers2

1

You could use canvas items as a target that you move around, without needing to destroy and recreate it. In the following, the target is a circle, so I can determine if the hit was successful by measuring the distance from the target, and comparing it with the radius of the target. (you could also use other tk.canvas methods that can tell you if your click was on an item or not).

tkinter has special variables tk.IntVar that you can use in conjunction with a label in order to keep a tally of the hits and display it. This eliminates the need for ugly global declaration.

The use of time.sleep, and canvas.update is not recommended; you can use the mainloop to manage the logic of the game for you.

As an extension of your game, you could, reduce the size of the target and reduce the stationary time, as the hunter accumulates successful hits (I'll leave this for you to implement)

import tkinter as tk
import random

WIDTH, HEIGHT = 600, 600
TARGET_RADIUS = 10

def is_target_hit(x, y):
    x0, y0, x1, y1 = canvas.coords(target)
    xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
    return ((x - xc)**2 + (y - yc)**2)**0.5 < TARGET_RADIUS

def hitcounter(event):
    hits.set(hits.get() + 1)
    if is_target_hit(event.x, event.y):
        hits_on_target.set(hits_on_target.get() + 1)

def get_random_coords():
    x = random.randrange(TARGET_RADIUS, WIDTH-TARGET_RADIUS)
    y = random.randrange(TARGET_RADIUS, HEIGHT-TARGET_RADIUS)
    return x-TARGET_RADIUS, y-TARGET_RADIUS, x+TARGET_RADIUS, y+TARGET_RADIUS

def spawntarget():
    canvas.coords(target, *get_random_coords())
    canvas.after(1000, spawntarget)

root = tk.Tk()

hits, hits_on_target = tk.IntVar(root), tk.IntVar(root)
hits.set(0), hits_on_target.set(0)

tk.Label(root, text='hits:').pack()
hits_label = tk.Label(root, textvariable=hits)
hits_label.pack()
tk.Label(root, text='hits_on_target:').pack()
hits_on_target_label = tk.Label(root, textvariable=hits_on_target)
hits_on_target_label.pack()

canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
canvas.pack(expand=True, fill=tk.BOTH)

target = canvas.create_oval(*get_random_coords(), fill='red')
canvas.bind('<1>', hitcounter)

spawntarget()

root.mainloop()
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
  • Thank you for the swift answer. I want to enquire about a binding you applied in the script: – Twinings Sep 04 '19 at 14:02
  • target = canvas.create_oval(*get_random_coords(), fill='red') canvas.bind('<1>', hitcounter), what is the '<1>' referring to and how does it refer to it. I assume it is target? P.S. sorry for the previous comment I am quite a newbie and accidentally pressed enter – Twinings Sep 04 '19 at 14:03
  • the binding `'<1>'` is the same as `''` which is the left mouse button. See [here](https://stackoverflow.com/questions/32289175/list-of-all-tkinter-events) for a list of all `tkinter` events. – Reblochon Masque Sep 04 '19 at 14:12
0

You shouldn't be sleeping your program like that. Use tkinters after method like below. The after method inserts the function call into the mainloop.

def hitcounter(*args):
    print("  wdwd ")
    hitcounter += 1
    canvas.delete("counter1_")
    counter1_ = canvas.create_text(screenwidth-50,screenheight/4,fill="Blue",font="Times 20",text="HiTS:"+'\n '+str(hitcounter), tag='counter1')


def change_placement():
    clicktarget.place(x=x_target,y=y_target)
    canvas.update()
    x_target = random.randint(75,screenwidth-75-50)
    y_target = random.randint(75,screenheight-75-100)
    root.after(3000, change_placement)


def spawntarget():
    print("spawntarget")
    x_target = random.randint(75,screenwidth-75-50)
    y_target = random.randint(75,screenheight-75-100)
    global clicktarget
    clicktarget=Button(root ,text='ENEMY', width=10, height=10, command = hitcounter)
    clicktarget.configure(fg='red', bg='gold')
    change_placement()


spawntarget()

root.mainloop()

ShayneLoyd
  • 633
  • 1
  • 4
  • 9
  • Hey slr1337, your solution was quite helpful, and makes it graphically register a click. However, my program still does not print(" wdwd ") in the hitcounter(*args) function, and it does not update the text either. Could you please recommend what I should do next? – Twinings Sep 04 '19 at 13:54