I'm writing a program for my raspberry pi4 for my Master's project which sounded so simple at first but for some reason Python is causing me grief.
In short - I have created a tkinter canvas with a polygon centred on the canvas. The shape of this polygon will depend upon the value of a counter.
The count is controlled by a blink event from a neurosky mindwave headset - this count is working (mostly).
What I want to then do is update the canvas to put the new points for the polygon into the pack but nothing I have tried seems to work. The closest I got was trying a .redraw() command which drew an infinite number of windows before I pulled the plug.
I am not a complete novice to coding having taught many languages in my time but have never used python before and am clearly missing a very simple step which will cause everything to fall out.
I will try to modify the code to use a keyboard press rather than a headset and add it below later if folk think it will help.
import keyboard
import time
from tkinter import *
count = 0
points = [250,250,350,250,350,350,250,350]
root = Tk()
while True:
# set window to middle of screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
xcoord = screen_width/2-300
ycoord = screen_height/2 - 300
root.geometry("%dx%d+%d+%d" % (600,600,xcoord,ycoord))
#set up canvas size and background colour
canvas1 = Canvas(root, relief = FLAT,width = 600, height = 600, background = "blue")
#set up buttons shape and colour
button = canvas1.create_polygon(points, fill="darkgreen", outline="yellow")
canvas1.pack()
if keyboard.is_pressed("f"):
if count < 4:
count += 1
elif count == 4:
count = 0
time.sleep(0.1)
if count == 0:
points = [250,250,350,250,350,350,250,350]
elif count == 1:
points = [300,100,500,500,100,500]
elif count == 2:
points = [200,100,400,100,300,500]
elif count == 3:
points = [100,300,500,100,500,500]
elif count == 4:
points = [100,100,100,500,500,300]
print(count)
root.update()
You need to delete the old polygon and create new one. Also don't use while loop in tkinter application. For your case, you can bind a callback on <Key> event and update the polygon in the callback:
import tkinter as tk
count = 0
points = [
[250,250,350,250,350,350,250,350],
[300,100,500,500,100,500],
[200,100,400,100,300,500],
[100,300,500,100,500,500],
[100,100,100,500,500,300],
]
root = tk.Tk()
# set window to middle of screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
xcoord = screen_width//2 - 300
ycoord = screen_height//2 - 300
root.geometry("%dx%d+%d+%d" % (600,600,xcoord,ycoord))
#set up canvas
canvas1 = tk.Canvas(root, relief=tk.FLAT, background="blue")
canvas1.pack(fill=tk.BOTH, expand=1)
# create the polygon with tag "button"
canvas1.create_polygon(points[count], fill="darkgreen", outline="yellow", tag="button")
def on_key(event):
global count
if event.char == 'f':
count = (count + 1) % len(points)
print(count)
canvas1.delete("button") # delete the old polygon
canvas1.create_polygon(points[count], fill="darkgreen", outline="yellow", tag="button")
root.bind("<Key>", on_key)
root.mainloop()
You can update any parameters of a shape.
canvas.itemconfigure(shape1_id_or_tag, fill="green")
canvas.itemconfigure(shape2_id_or_tag, fill="#900", outline="red", width=3)
But for your situation try '.coords()':
canvas1.coords("Button", points[count])
Source: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/canvas-methods.html
Related
I have some code which I am trying to run, basically the system should draw the tkinter canvas then constantly monitor the headset results - currently it seems to only run the code once rather than (as I expected) loop through the if statement.
Any suggestions would be most appreciated.
A
import Tkinter as tk
import serial
import mindwave
import time
headset = mindwave.Headset('/dev/ttyUSB0', 'AF66')
while headset.status != 'connected':
time.sleep(0.5)
if headset.status == 'standby':
headset.connect()
print ("Retrying connect...")
print(headset.status)
arduino = serial.Serial('/dev/ttyACM0', 9600)
count = 0
value = '0'
points = [
[250,250,350,250,350,350,250,350],
[300,100,500,500,100,500],
[100,100,100,500,500,300],
[100,100,500,100,300,500],
[100,300,500,100,500,500],
]
root = tk.Tk()
# set window to middle of screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
xcoord = screen_width//2 - 300
ycoord = screen_height//2 - 300
root.geometry("%dx%d+%d+%d" % (600,600,xcoord,ycoord))
#set up canvas
canvas1 = tk.Canvas(root, relief=tk.FLAT, background="blue")
canvas1.pack(fill=tk.BOTH, expand=1)
# create the polygon with tag "button"
canvas1.create_polygon(points[count], fill="darkgreen", outline="yellow", tag="button")
#monitor attention results
if headset.attention > 70:
print("Attention: %s, Meditation: %s" % (headset.attention, headset.meditation))
else:
print("attention too low")
time.sleep(.2)
I'm currently writing the John Conway's game of life in python and I'm stuck when it comes getting the cells to animate according to the rules. currently my code is compiling without any errors but I just can't figure out how I can get the code to animate. My code is:
code edit
from tkinter import *
from random import *
import time
import numpy as np
PIXEL_SIZE = 10
ROW = 910
COLUMN = 700
grid = []
updated_grid = [[]]
def create_grid():
for row in range(0, ROW):
grid2 = []
for column in range(0, COLUMN):
grid2.append(randint(0, 1))
grid.append(grid2)
def draw_grid():
for row in range(0, ROW):
for column in range(0, COLUMN):
if grid[row][column] == 1:
x0 = row*PIXEL_SIZE
y0 = column*PIXEL_SIZE
x1 = x0+PIXEL_SIZE
y1 = y0+PIXEL_SIZE
canvas.create_rectangle(x0, y0, x1, y1, fill='red')
def apply_rules():
for row in range(1, ROW - 1):
for column in range(1, COLUMN - 1):
neighbours_count = 0
# will count the neighbours for each cell
neighbours_count += grid[row-1][column-1] # top left
neighbours_count += grid[row][column-1] # top center
neighbours_count += grid[row+1][column-1] # top right
neighbours_count += grid[row-1][column] # middle left
neighbours_count += grid[row+1][column] # middle right
neighbours_count += grid[row-1][column+1] # bottom left
neighbours_count += grid[row][column+1] # bottom center
neighbours_count += grid[row+1][column+1] # bottom right
# Game Of Life rules:
# alive cell rules
if grid[row][column] == 1:
if neighbours_count < 2: # rule 1 any live cell with fewer than two live neighbours dies, as if by underpopulation
grid[row][column] = 0
elif neighbours_count == 2 | neighbours_count == 3: # rule 2 any live cell with two or three live neighbours lives on to the next generation
grid[row][column] = 1
elif neighbours_count > 3 & neighbours_count <= 8: # rule 3 any live cell with more than three live neighbours dies, as if by overpopulation
grid[row][column] = 0
else:
grid[row][column] = 0
elif grid[row][column] == 0: # dead cells rule 4 any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction
if neighbours_count == 3:
grid[row][column] = 1
else:
grid[row][column] = 0
def one_cycle():
apply_rules()
draw_grid()
window.after(1, one_cycle)
window = Tk() # creates the window for the game
window.title('Game Of Life Python') # is the game title written on the window
canvas_frame = Frame(window) # creates a frame on the window to hold the canvas
game_title = Frame(window) # creates a frame on the window to display the game title (which will be a label)
start_button = Button(window, text='Start Game', command=one_cycle) # creates a button which will be used to start the game
canvas = Canvas(canvas_frame, width=ROW, height=COLUMN, background='black') # creates the canvas used to the draw the game of life
game_title_label = Label(game_title, text='Game Of Life', font='Helvetica 20 bold', fg='grey') # creates the label for the game title which will be placed in a frame
canvas.grid(row=0, column=0) # places the canvas onto the canvas_frame
canvas_frame.grid(row=1, column=1) # places the canvas_frame onto the window
game_title_label.grid(rowspan=2, column=0) # places the title of the game onto the game_title frame
game_title.grid(row=0, columnspan=2) # places the frame for the game title onto the window
start_button.grid(rowspan=2, column=1) # places the start onto the window
create_grid()
window.mainloop()
I am new to python so please forgive me if you see any errors I've missed and thank you for any help you are able to give me.
I think you need a few modifications:
Change your Button to start the drawing: start_button = Button(window, text='Start Game', command=apply_rules)
Modify def apply_rules(): to have this addition at the end: window.after(0, apply_rules)
Don't call apply_rules() your self: apply_rules()
When you run the program you will need to press the Start Game button.
================
Update:
You may need to add another function to encapsulate the idea of running the rules and displaying the results continuously:
Keep def apply_rules(): as you have it in your posting and add a new function:
def one_cycle():
apply_rules()
draw_grid()
window.after(1, one_cycle) # use 1 to ease performance as Bryan suggests
Change the Button command to: command=one_cycle
Now you only need this at the bottom:
create_grid()
window.mainloop()
I've been working on translating a version of the game "Pong", that I built for an online Python class using CodeSkulptor, into a "desktop" python script using Tkinter, as a means to teach myself how to use Tkinter. I've managed to pretty much get the entire game working, except for the left (player 1) paddle. I think I have the key bindings correct, as the right (player 2) paddle works as expected, in that when you hold down the "Up" or "Down" arrow keys, the paddle moves until it hits the upper or lower bounds of the canvas, or stops when either key is released. I'm passing the key presses to the keydown and keyup handlers, where I check to see which key was pressed/released, and act accordingly. What's baffling to me is that if I map the left paddle movement to different keys (say "a" or "d", or the "Up" or "Down" arrows, for example), it works as expected, but it refuses to work when I have the "w" and "s" keys mapped. Does anyone have any idea why that might be, or what I may have done wrong?
The code I've provided below is a basic example I put together, that demonstrates this issue, and the paddle movement that I'm trying to achieve (it pretty much mirrors my Pong project). The right-side paddle moves correctly, where the left side paddle does not. Thanks in advance for your help!
from Tkinter import *
import random
WIDTH = 500
HEIGHT = 500
PAD_WIDTH = 10
PAD_HEIGHT = 80
HALF_PAD_WIDTH = PAD_WIDTH / 2
HALF_PAD_HEIGHT = PAD_HEIGHT / 2
class Example(Frame, object):
def __init__(self, master):
super(Example, self).__init__(master)
self._paddle1_pos = 200
self._paddle2_pos = 200
self._paddle1_vel = 0
self._paddle2_vel = 0
self.initUI()
def initUI(self):
scn_cent_height = self.master.winfo_screenheight() // 2 - HEIGHT // 2
scn_cent_width = self.master.winfo_screenwidth() // 2 - WIDTH // 2
self.master.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, scn_cent_width, scn_cent_height))
self.master.minsize(WIDTH, HEIGHT)
self.master.title("Example Pong Paddles")
self._canvasFrame = Frame(self.master)
self._canvasFrame.pack(expand=True, fill=BOTH)
self._canvas = Canvas(self._canvasFrame, bg="black", highlightthickness=0, bd=0)
self._canvas.pack(fill=BOTH, expand=True)
self.update_idletasks()
# Key handlers
self.master.bind("<KeyPress>", self.keydown)
self.master.bind("<KeyRelease>", self.keyup)
while True:
self._canvas.after(1)
self._canvas.delete("all")
self.draw()
self._canvas.update()
def draw(self):
self._cheight = self._canvasFrame.winfo_height()
self._cwidth = self._canvasFrame.winfo_width()
# Draw mid line and gutters
self._rline = self._canvas.create_line(self._cwidth / 2, 0, self._cwidth / 2, self._cheight, width=1, fill="White")
self._mline = self._canvas.create_line(PAD_WIDTH, 0, PAD_WIDTH, self._cheight, width=1, fill="White")
self._lline = self._canvas.create_line(self._cwidth - PAD_WIDTH, 0, self._cwidth - PAD_WIDTH, self._cheight, width=1, fill="White")
# Update paddle's vertical position, keep paddle on the screen
# Paddle 1 - Check height and update position
if self._paddle1_pos + self._paddle1_vel >= HALF_PAD_HEIGHT and self._paddle1_pos + self._paddle1_vel <= HEIGHT - HALF_PAD_HEIGHT:
self._paddle1_pos += self._paddle1_vel
# Paddle 2 - Check height and update position
if self._paddle2_pos + self._paddle2_vel >= HALF_PAD_HEIGHT and self._paddle2_pos + self._paddle2_vel <= HEIGHT - HALF_PAD_HEIGHT:
self._paddle2_pos += self._paddle2_vel
# Draw paddles
self._p1paddle = self._canvas.create_line([HALF_PAD_WIDTH, self._paddle1_pos - HALF_PAD_HEIGHT],
[HALF_PAD_WIDTH, self._paddle1_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
self._p2paddle = self._canvas.create_line([self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos - HALF_PAD_HEIGHT],
[self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
# Draw paddles
self._p1paddle = self._canvas.create_line([HALF_PAD_WIDTH, self._paddle1_pos - HALF_PAD_HEIGHT],
[HALF_PAD_WIDTH, self._paddle1_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
self._p2paddle = self._canvas.create_line([self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos - HALF_PAD_HEIGHT],
[self._cwidth - HALF_PAD_WIDTH, self._paddle2_pos + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
def keydown(self, key):
key = key.keysym
if key == "w":
self._paddle1_vel = -10
elif key == "s":
self._paddle1_vel = 10
elif key == "Up":
self._paddle2_vel = -10
elif key == "Down":
self._paddle2_vel = 10
def keyup(self, key):
key = key.keysym
if key == "w":
self._paddle1_vel = 0
elif key == "s":
self._paddle1_vel = 0
elif key == "Up":
self._paddle2_vel = 0
elif key == "Down":
self._paddle2_vel = 0
def main():
root = Tk()
example = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
I just wanted to add, for the benefit of anyone else who runs into this problem, that I was able to trace the source of it back to my Anaconda virtual environments (I have one each for Python 2 and Python 3). In testing, I was able to reproduce this issue when specifically using those environments, but was unable to reproduce it when using Python 2 (mac framework build) or Python 3 (installed via Brew) outside of Anaconda. I wiped out both Anaconda virtual environments in case something was messed up there, and rebuilt them both (i.e. clean install of both Python 2 and 3), and was still able to reproduce this issue (without installing any additional modules). I can only surmise that there is something different between the python(s) that is/are installed by Anaconda, and those that either are part of the system framework, or installed separately.
Also, just to note, it turns out when I successfully tested on Python 3 in #Novel's above comment, I inadvertently tested on the system Python 3, rather than in the Anaconda Python 3 virtual environment.
EDIT:
It turns out this problem is actually due to the version of Tkinter (Tcl/Tk) that my python installs were using. The python framework on my laptop was using Tk 8.5.9, whereas the Anaconda installs are using 8.6.8. In testing, I ran into this issue when running my Pong script in python 2.7.16 and 3.7.3 with Tk 8.6.8, but it ran perfectly fine on the same python versions with Tk 8.5.9. I'm not sure if this is an issue with Tk, or some incompatibility between Tk 8.6.8 and the MacOS framework (since the framework natively uses 8.5.9).
Your code is very messy and needs to be refactored, so I did that. Some notes:
The biggest note is that instead of clearing the screen and redrawing everything, it's much neater and performs much better if you draw objects once and then simply move them around.
Use tkinter's mainloop; don't make your own and update it manually. The update() or update_idletasks() methods are last resort; normal code does not have them. Use tkinter's mainloop via after() instead. This will make your window much more reactive.
Python2 Tkinter classes are old-style, do not force them to be new-style by adding an object inheritance.
the canvas frame is useless, so I removed that.
keeping the keys in a dictionary frees up a lot of repeated code.
Prefixing all your variable names with _ does nothing and makes it hard to read and type; leave that off.
-
try:
import tkinter as tk # python3 detected
except ImportError:
import Tkinter as tk # python2 detected
WIDTH = 500
HEIGHT = 500
PAD_WIDTH = 10
PAD_HEIGHT = 80
VELOCITY = 10
HALF_PAD_WIDTH = PAD_WIDTH // 2
HALF_PAD_HEIGHT = PAD_HEIGHT // 2
P1_UP = 111 # Up arrow key
P1_DOWN = 116 # Down arrrow key
P2_UP = 25 # 'w' key
P2_DOWN = 39 # 's' key
class Example(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.keys = {}
self.initUI()
# Key handlers
self.master.bind("<KeyPress>", self.keydown)
self.master.bind("<KeyRelease>", self.keyup)
self.draw() # add the game loop to the mainloop
def initUI(self):
scn_cent_height = self.master.winfo_screenheight() // 2 - HEIGHT // 2
scn_cent_width = self.master.winfo_screenwidth() // 2 - WIDTH // 2
self.master.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, scn_cent_width, scn_cent_height))
self.master.minsize(WIDTH, HEIGHT)
self.master.title("Example Pong Paddles")
self.canvas = tk.Canvas(self, bg="black", highlightthickness=0, bd=0, width=WIDTH, height=HEIGHT)
self.canvas.pack(fill=tk.BOTH, expand=True)
# Draw mid line and gutters
self.rline = self.canvas.create_line(WIDTH//2, 0, WIDTH//2, HEIGHT, width=1, fill="White")
self.mline = self.canvas.create_line(PAD_WIDTH, 0, PAD_WIDTH, HEIGHT, width=1, fill="White")
self.lline = self.canvas.create_line(WIDTH - PAD_WIDTH, 0, WIDTH - PAD_WIDTH, HEIGHT, width=1, fill="White")
# Draw paddles
self.p1paddle = self.canvas.create_line([HALF_PAD_WIDTH, HEIGHT//2 - HALF_PAD_HEIGHT],
[HALF_PAD_WIDTH, HEIGHT//2 + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
self.p2paddle = self.canvas.create_line([WIDTH - HALF_PAD_WIDTH, HEIGHT//2 - HALF_PAD_HEIGHT],
[WIDTH - HALF_PAD_WIDTH, HEIGHT//2 + HALF_PAD_HEIGHT], width=PAD_WIDTH, fill="White")
def draw(self):
if self.keys.get(P2_UP) and self.canvas.coords(self.p1paddle)[1] > 0:
self.canvas.move(self.p1paddle, 0, -VELOCITY)
if self.keys.get(P2_DOWN) and self.canvas.coords(self.p1paddle)[3] < HEIGHT:
self.canvas.move(self.p1paddle, 0, VELOCITY)
if self.keys.get(P1_UP) and self.canvas.coords(self.p2paddle)[1] > 0:
self.canvas.move(self.p2paddle, 0, -VELOCITY)
if self.keys.get(P1_DOWN) and self.canvas.coords(self.p2paddle)[3] < HEIGHT:
self.canvas.move(self.p2paddle, 0, VELOCITY)
self.after(10, self.draw)
def keydown(self, key):
self.keys[key.keycode] = True
def keyup(self, key):
self.keys[key.keycode] = False
def main():
root = tk.Tk()
example = Example(root)
example.pack()
root.mainloop()
if __name__ == '__main__':
main()
Perhaps, by chance, some improvement in there will fix your original problem too.
I'm making a little alien project to help to learn graphics in tkinter and I have come across a problem. I am trying to make the aliens eyeball stay inside the eye but still move around however that requires me to detect the edge of the eyeball which is a circle. Not really sure how coords work in tkinter (other than the basics) so any help appreciated. Thanks!
from tkinter import *
from threading import Timer
import random
import time
global canvas, root
root = Tk()
root.title("Alien")
root.attributes("-topmost", 1)
canvas = Canvas(root, height=300, width =400)
canvas.pack()
Blinking = False
class Alien:
def __init__(self):
global canvas, root
self.body = canvas.create_oval(100,150,300,250, fill = "green")
self.eye = canvas.create_oval(170,70,230,130, fill = "white")
self.eyeball = canvas.create_oval(190,90,210,110, fill = "black")
self.mouth = canvas.create_oval(150,220,250,240, fill = "red")
self.neck = canvas.create_line(200,150,200,130)
self.hat = canvas.create_polygon(180,75,220,75,200,20, fill = "blue")
self.words = canvas.create_text(200,800, text = "I'm an alien!", anchor="nw")
root.update()
def openMouth(self):
global canvas, root
canvas.itemconfig(self.mouth, fill = "black")
root.update()
def closeMouth(self):
global canvas, root
canvas.itemconfig(self.mouth, fill = "red")
root.update()
def burp(self, event):
self.openMouth()
canvas.itemconfig(self.words, text = "BURRRRP!")
time.sleep(0.5)
self.closeMouth()
def moveEye(self,event):
global root, canvas
canvas.move(alien.eyeball , random.randint(-1,1) , random.randint(-1,1))
root.update()
def blink(self,event):
canvas.itemconfig(self.eye, fill = "green")
canvas.itemconfig(self.eyeball, state=HIDDEN)
Blinking = True
root.update()
def unblink(self,event):
canvas.itemconfig(self.eye, fill = "white")
canvas.itemconfig(self.eyeball, state=NORMAL)
Blinking = False
root.update()
alien = Alien()
alien.openMouth()
time.sleep(1)
alien.closeMouth()
canvas.bind_all("<Button-1>", alien.burp)
canvas.bind_all("<KeyPress-a>", alien.blink)
Timer(2, alien.moveEye).start()
while not Blinking:
alien.moveEye(event)
if alien.moveEye.event.x > 190:
canvas.move(alien.eyeball, -1 , 0)
Small circle with radius r lies inside big circle with radius R, if
(BigCenter.X - SmallCenter.x)^2 + (BigCenter.Y - SmallCenter.Y)^2 < (R - r)^2
^2 denotes squared value
The program I am writing has a tkinter window that is constantly being fed with data manually rather than being part of a mainloop. It also needs to track mouse location. I havn't found a workaround for tracking the mouse outside of mainloop yet, but if you have one please do tell.
from Tkinter import *
import random
import time
def getCoords(event):
xm, ym = event.x, event.y
str1 = "mouse at x=%d y=%d" % (xm, ym)
print str1
class iciclePhysics(object):
def __init__(self, fallrange, speed=5):
self.speed = speed
self.xpos = random.choice(range(0,fallrange))
self.ypos = 0
def draw(self,canvas):
try:
self.id = canvas.create_polygon(self.xpos-10, self.ypos, self.xpos+10, self.ypos, self.xpos, self.ypos+25, fill = 'lightblue')
except:
pass
def fall(self,canvas):
self.ypos+=self.speed
canvas.move(self.id, 0, self.ypos)
root = Tk()
mainFrame = Frame(root, bg= 'yellow', width=300, height=200)
mainFrame.pack()
mainCanvas = Canvas(mainFrame, bg = 'black', height = 500, width = 500, cursor = 'circle')
mainCanvas.bind("<Motion>", getCoords)
mainCanvas.pack()
root.resizable(0, 0)
difficulty = 1500
#root.mainloop()
currentIcicles = [iciclePhysics(difficulty)]
root.update()
currentIcicles[0].draw(mainCanvas)
root.update_idletasks()
time.sleep(0.1)
currentIcicles[0].fall(mainCanvas)
root.update_idletasks()
tracker = 0
sleeptime = 0.04
while True:
tracker+=1
time.sleep(sleeptime)
if tracker % 3 == 0 and difficulty > 500:
difficulty -= 1
elif difficulty <= 500:
sleeptime-=.00002
currentIcicles.append(iciclePhysics(difficulty))
currentIcicles[len(currentIcicles)-1].draw(mainCanvas)
for i in range(len(currentIcicles)):
currentIcicles[i].fall(mainCanvas)
root.update_idletasks()
for i in currentIcicles:
if i.ypos >= 90:
currentIcicles.remove(i)
root.update_idletasks()
There is no way. Mouse movement is presented to the GUI as a series of events. In order to process events, the event loop must be running.
Also, you should pretty much never do a sleep inside a GUI application. All that does is freeze the GUI during the sleep.
Another hint: you only need to create an icicle once; to make it fall you can use the move method of the canvas.
If you are having problems understanding event based programming, the solution isn't to avoid the event loop, the solution is to learn how event loops work. You pretty much can't create a GUI without it.