Example: OOPy Dots Demo
# oopyDotsDemo.py
# starts with betterDotsDemo and adds:
# * a dotCounter that counts all the instances of Dot or its subclasses
# * a MovingDot subclass of Dot that scrolls horizontally
# * a FlashingMovingDot subclass of MovingDot that flashes and moves
from tkinter import *
import random
####################################
# customize these functions
####################################
def distance(x0, y0, x1, y1):
return ((y1-y0)**2 + (x1-x0)**2)**0.5
class Dot(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.r = random.randint(20, 50)
self.color = random.choice(['cyan', 'green', 'yellow'])
self.score = 0
def getBounds(self):
return self.x-self.r, self.y-self.r, self.x+self.r, self.y+self.r
def draw(self, canvas):
x0, y0, x1, y1 = self.getBounds()
canvas.create_oval(x0, y0, x1, y1, fill=self.color)
canvas.create_text((x0+x1)/2, (y0+y1)/2, text=self.score)
def clicked(self, x, y):
return distance(self.x, self.y, x, y) <= self.r
def onTimerFired(self, canvasWidth):
pass
class MovingDot(Dot):
def onTimerFired(self, canvasWidth):
speed = 5
self.x = (self.x + speed) % canvasWidth
class FlashingMovingDot(MovingDot):
def __init__(self, x, y):
super().__init__(x, y)
self.timer = 0
self.isFlashing = False
def onTimerFired(self, canvasWidth):
super().onTimerFired(canvasWidth) # do the regular moving part
self.timer += 1
if self.timer % 5 == 0:
self.isFlashing = not self.isFlashing
def draw(self, canvas):
if self.isFlashing:
x0, y0, x1, y1 = self.getBounds()
canvas.create_rectangle(x0, y0, x1, y1, fill="light gray")
# still do the regular drawing
super().draw(canvas)
def init(data):
data.dots = []
def mousePressed(event, data):
clicked = False
for dot in data.dots:
if dot.clicked(event.x, event.y):
dot.score += 1
clicked = True
if clicked: return
dotType = len(data.dots) % 3
if dotType == 0:
data.dots.append(Dot(event.x, event.y))
elif dotType == 1:
data.dots.append(MovingDot(event.x, event.y))
else:
data.dots.append(FlashingMovingDot(event.x, event.y))
def keyPressed(event, data):
# use event.char and event.keysym
pass
def timerFired(data):
for dot in data.dots:
dot.onTimerFired(data.width)
def redrawAll(canvas, data):
for dot in data.dots:
dot.draw(canvas)
####################################
# use the run function as-is
####################################
def run(width=300, height=300):
def redrawAllWrapper(canvas, data):
canvas.delete(ALL)
canvas.create_rectangle(0, 0, data.width, data.height,
fill='white', width=0)
redrawAll(canvas, data)
canvas.update()
def mousePressedWrapper(event, canvas, data):
mousePressed(event, data)
redrawAllWrapper(canvas, data)
def keyPressedWrapper(event, canvas, data):
keyPressed(event, data)
redrawAllWrapper(canvas, data)
def timerFiredWrapper(canvas, data):
timerFired(data)
redrawAllWrapper(canvas, data)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
class Struct(object): pass
data = Struct()
data.width = width
data.height = height
data.timerDelay = 100 # milliseconds
root = Tk()
init(data)
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
# set up events
root.bind("", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(400, 400)