- Mode Demo
# mode-demo.py
from tkinter import *
####################################
# init
####################################
def init(data):
# There is only one init, not one-per-mode
data.mode = "splashScreen"
data.score = 0
####################################
# mode dispatcher
####################################
def mousePressed(event, data):
if (data.mode == "splashScreen"): splashScreenMousePressed(event, data)
elif (data.mode == "playGame"): playGameMousePressed(event, data)
elif (data.mode == "help"): helpMousePressed(event, data)
def keyPressed(event, data):
if (data.mode == "splashScreen"): splashScreenKeyPressed(event, data)
elif (data.mode == "playGame"): playGameKeyPressed(event, data)
elif (data.mode == "help"): helpKeyPressed(event, data)
def timerFired(data):
if (data.mode == "splashScreen"): splashScreenTimerFired(data)
elif (data.mode == "playGame"): playGameTimerFired(data)
elif (data.mode == "help"): helpTimerFired(data)
def redrawAll(canvas, data):
if (data.mode == "splashScreen"): splashScreenRedrawAll(canvas, data)
elif (data.mode == "playGame"): playGameRedrawAll(canvas, data)
elif (data.mode == "help"): helpRedrawAll(canvas, data)
####################################
# splashScreen mode
####################################
def splashScreenMousePressed(event, data):
pass
def splashScreenKeyPressed(event, data):
data.mode = "playGame"
def splashScreenTimerFired(data):
pass
def splashScreenRedrawAll(canvas, data):
canvas.create_text(data.width/2, data.height/2-20,
text="This is a splash screen!", font="Arial 26 bold")
canvas.create_text(data.width/2, data.height/2+20,
text="Press any key to play!", font="Arial 20")
####################################
# help mode
####################################
def helpMousePressed(event, data):
pass
def helpKeyPressed(event, data):
data.mode = "playGame"
def helpTimerFired(data):
pass
def helpRedrawAll(canvas, data):
canvas.create_text(data.width/2, data.height/2-40,
text="This is help mode!", font="Arial 26 bold")
canvas.create_text(data.width/2, data.height/2-10,
text="How to play:", font="Arial 20")
canvas.create_text(data.width/2, data.height/2+15,
text="Do nothing and score points!", font="Arial 20")
canvas.create_text(data.width/2, data.height/2+40,
text="Press any key to keep playing!", font="Arial 20")
####################################
# playGame mode
####################################
def playGameMousePressed(event, data):
data.score = 0
def playGameKeyPressed(event, data):
if (event.keysym == 'h'):
data.mode = "help"
def playGameTimerFired(data):
data.score += 1
def playGameRedrawAll(canvas, data):
canvas.create_text(data.width/2, data.height/2-40,
text="This is a fun game!", font="Arial 26 bold")
canvas.create_text(data.width/2, data.height/2-10,
text="Score = " + str(data.score), font="Arial 20")
canvas.create_text(data.width/2, data.height/2+15,
text="Click anywhere to reset score", font="Arial 20")
canvas.create_text(data.width/2, data.height/2+40,
text="Press 'h' for help!", font="Arial 20")
####################################
# 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
init(data)
# create the root and the canvas
root = Tk()
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(300, 300)
- Grid Demo
# grid-demo.py
from tkinter import *
def init(data):
data.rows = 4
data.cols = 8
data.margin = 5 # margin around grid
data.selection = (-1, -1) # (row, col) of selection, (-1,-1) for none
def pointInGrid(x, y, data):
# return True if (x, y) is inside the grid defined by data.
return ((data.margin <= x <= data.width-data.margin) and
(data.margin <= y <= data.height-data.margin))
def getCell(x, y, data):
# aka "viewToModel"
# return (row, col) in which (x, y) occurred or (-1, -1) if outside grid.
if (not pointInGrid(x, y, data)):
return (-1, -1)
gridWidth = data.width - 2*data.margin
gridHeight = data.height - 2*data.margin
cellWidth = gridWidth / data.cols
cellHeight = gridHeight / data.rows
row = (y - data.margin) // cellHeight
col = (x - data.margin) // cellWidth
# triple-check that we are in bounds
row = min(data.rows-1, max(0, row))
col = min(data.cols-1, max(0, col))
return (row, col)
def getCellBounds(row, col, data):
# aka "modelToView"
# returns (x0, y0, x1, y1) corners/bounding box of given cell in grid
gridWidth = data.width - 2*data.margin
gridHeight = data.height - 2*data.margin
columnWidth = gridWidth / data.cols
rowHeight = gridHeight / data.rows
x0 = data.margin + col * columnWidth
x1 = data.margin + (col+1) * columnWidth
y0 = data.margin + row * rowHeight
y1 = data.margin + (row+1) * rowHeight
return (x0, y0, x1, y1)
def mousePressed(event, data):
(row, col) = getCell(event.x, event.y, data)
# select this (row, col) unless it is selected
if (data.selection == (row, col)):
data.selection = (-1, -1)
else:
data.selection = (row, col)
def keyPressed(event, data):
pass
def timerFired(data):
pass
def redrawAll(canvas, data):
# draw grid of cells
for row in range(data.rows):
for col in range(data.cols):
(x0, y0, x1, y1) = getCellBounds(row, col, data)
fill = "orange" if (data.selection == (row, col)) else "cyan"
canvas.create_rectangle(x0, y0, x1, y1, fill=fill)
canvas.create_text(data.width/2, data.height/2 - 15, text="Click in cells!",
font="Arial 26 bold", fill="darkBlue")
####################################
# 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
init(data)
# create the root and the canvas
root = Tk()
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(300, 300)
- Undo/Redo Demo
# undo-redo-demo.py
from tkinter import *
def init(data):
data.points = [ ]
data.undoList = [ ]
def mousePressed(event, data):
data.points.append((event.x, event.y))
data.undoList = [ ]
def keyPressed(event, data):
if (event.keysym == "u"):
if (len(data.points) > 0):
data.undoList.append(data.points.pop())
elif (event.keysym == "r"):
if (len(data.undoList) > 0):
data.points.append(data.undoList.pop())
elif (event.keysym == "c"):
data.points = [ ]
data.undoList = [ ]
def timerFired(data):
pass
def redrawAll(canvas, data):
if (data.points != []):
canvas.create_polygon(data.points, fill="gold", outline="black")
canvas.create_text(data.width/2, 20,
text="click to add point. u=undo. r=redo. c=clear.")
canvas.create_text(data.width/2, 40,
text=str(len(data.points)) + " point(s) in polygon")
canvas.create_text(data.width/2, 60,
text=str(len(data.undoList)) + " point(s) on undoList")
####################################
# 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
init(data)
# create the root and the canvas
root = Tk()
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(300, 300)
- Images Demo
To run this demo, first download
playing-card-gifs.zip and unzip
that file, so the folder playing-card-gifs is at the same level as this code.
# images-demo.py
from tkinter import *
def init(data):
data.step = 0
loadPlayingCardImages(data) # always load images in init!
def loadPlayingCardImages(data):
cards = 55 # cards 1-52, back, joker1, joker2
data.cardImages = [ ]
for card in range(cards):
rank = (card%13)+1
suit = "cdhsx"[card//13]
filename = "playing-card-gifs/%s%d.gif" % (suit, rank)
data.cardImages.append(PhotoImage(file=filename))
def getPlayingCardImage(data, rank, suitName):
suitName = suitName[0].lower() # only car about first letter
suitNames = "cdhsx"
assert(1 <= rank <= 13)
assert(suitName in suitNames)
suit = suitNames.index(suitName)
return data.cardImages[13*suit + rank - 1]
def getSpecialPlayingCardImage(data, name):
specialNames = ["back", "joker1", "joker2"]
return getPlayingCardImage(data, specialNames.index(name)+1, "x")
def mousePressed(event, data):
pass
def keyPressed(event, data):
pass
def timerFired(data):
data.step += 1
def redrawAll(canvas, data):
suitNames = ["Clubs", "Diamonds", "Hearts", "Spades", "Xtras"]
suit = (data.step//10) % len(suitNames)
suitName = suitNames[suit]
cards = 3 if (suitName == "Xtras") else 13
margin = 10
(left, top) = (margin, 40)
for rank in range(1,cards+1):
image = getPlayingCardImage(data, rank, suitName)
if (left + image.width() > data.width):
(left, top) = (margin, top + image.height() + margin)
canvas.create_image(left, top, anchor=NW, image=image)
left += image.width() + margin
canvas.create_text(data.width/2, 20, text=suitName, font="Arial 28 bold")
####################################
# 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)
# Create root before calling init (so we can create images in init)
root = Tk()
# Set up data and call init
class Struct(object): pass
data = Struct()
data.width = width
data.height = height
data.timerDelay = 250 # milliseconds
init(data)
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(420, 360)
- Snake Demo
# snake-demo.py
# Note: there is a snake tutorial from previous semesters here:
# http://www.kosbie.net/cmu/fall-11/15-112/handouts/snake/snake.html
# But this is different in two key ways:
# 1) This uses this semester's framework (run function, and in Python3)
# 2) This uses a list of tuples to represent the snake
# You should understand both solutions, and be able to adapt that
# tutorial to use this semester's framework.
from tkinter import *
import random
def init(data):
data.rows = 10
data.cols = 10
data.margin = 5 # margin around grid
data.snake = [(data.rows/2, data.cols/2)]
data.direction = (0, +1) # (drow, dcol)
placeFood(data)
data.timerDelay = 250
data.gameOver = False
data.paused = True
# getCellBounds from grid-demo.py
def getCellBounds(row, col, data):
# aka "modelToView"
# returns (x0, y0, x1, y1) corners/bounding box of given cell in grid
gridWidth = data.width - 2*data.margin
gridHeight = data.height - 2*data.margin
x0 = data.margin + gridWidth * col / data.cols
x1 = data.margin + gridWidth * (col+1) / data.cols
y0 = data.margin + gridHeight * row / data.rows
y1 = data.margin + gridHeight * (row+1) / data.rows
return (x0, y0, x1, y1)
def mousePressed(event, data):
data.paused = False
def keyPressed(event, data):
if (event.keysym == "p"): data.paused = True; return
elif (event.keysym == "r"): init(data); return
if (data.paused or data.gameOver): return
if (event.keysym == "Up"): data.direction = (-1, 0)
elif (event.keysym == "Down"): data.direction = (+1, 0)
elif (event.keysym == "Left"): data.direction = (0, -1)
elif (event.keysym == "Right"): data.direction = (0, +1)
# for debugging, take one step on any keypress
takeStep(data)
def timerFired(data):
if (data.paused or data.gameOver): return
takeStep(data)
def takeStep(data):
(drow, dcol) = data.direction
(headRow, headCol) = data.snake[0]
(newRow, newCol) = (headRow+drow, headCol+dcol)
if ((newRow < 0) or (newRow >= data.rows) or
(newCol < 0) or (newCol >= data.cols) or
((newRow, newCol) in data.snake)):
data.gameOver = True
else:
data.snake.insert(0, (newRow, newCol))
if (data.foodPosition == (newRow, newCol)):
placeFood(data)
else:
# didn't eat, so remove old tail (slither forward)
data.snake.pop()
def placeFood(data):
data.foodPosition = None
row0 = random.randint(0, data.rows-1)
col0 = random.randint(0, data.cols-1)
for drow in range(data.rows):
for dcol in range(data.cols):
row = (row0 + drow) % data.rows
col = (col0 + dcol) % data.cols
if (row,col) not in data.snake:
data.foodPosition = (row, col)
return
def drawBoard(canvas, data):
for row in range(data.rows):
for col in range(data.cols):
(x0, y0, x1, y1) = getCellBounds(row, col, data)
canvas.create_rectangle(x0, y0, x1, y1, fill="white")
def drawSnake(canvas, data):
for (row, col) in data.snake:
(x0, y0, x1, y1) = getCellBounds(row, col, data)
canvas.create_oval(x0, y0, x1, y1, fill="blue")
def drawFood(canvas, data):
if (data.foodPosition != None):
(row, col) = data.foodPosition
(x0, y0, x1, y1) = getCellBounds(row, col, data)
canvas.create_oval(x0, y0, x1, y1, fill="green")
def drawGameOver(canvas, data):
if (data.gameOver):
canvas.create_text(data.width/2, data.height/2, text="Game over!",
font="Arial 26 bold")
def redrawAll(canvas, data):
drawBoard(canvas, data)
drawSnake(canvas, data)
drawFood(canvas, data)
drawGameOver(canvas, data)
####################################
# 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
init(data)
# create the root and the canvas
root = Tk()
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(300, 300)
- Side Scroller Demo
# side-scroller-demo.py
from tkinter import *
def init(data):
data.scrollX = 0 # amount view is scrolled to the right
data.scrollMargin = 50 # closest player may come to either canvas edge
data.playerX = data.scrollMargin # player's left edge
data.playerY = 0 # player's bottom edge (distance above the base line)
data.playerWidth = 10
data.playerHeight = 20
data.walls = 5
data.wallPoints = [0]*data.walls
data.wallWidth = 20
data.wallHeight = 40
data.wallSpacing = 90 # wall left edges are at 90, 180, 270,...
data.currentWallHit = -1 # start out not hitting a wall
def getPlayerBounds(data):
# returns absolute bounds, not taking scrollX into account
(x0, y1) = (data.playerX, data.height/2 - data.playerY)
(x1, y0) = (x0 + data.playerWidth, y1 - data.playerHeight)
return (x0, y0, x1, y1)
def getWallBounds(wall, data):
# returns absolute bounds, not taking scrollX into account
(x0, y1) = ((1+wall) * data.wallSpacing, data.height/2)
(x1, y0) = (x0 + data.wallWidth, y1 - data.wallHeight)
return (x0, y0, x1, y1)
def getWallHit(data):
# return wall that player is currently hitting
# note: this should be optimized to only check the walls that are visible
# or even just directly compute the wall without a loop
playerBounds = getPlayerBounds(data)
for wall in range(data.walls):
wallBounds = getWallBounds(wall, data)
if (boundsIntersect(playerBounds, wallBounds) == True):
return wall
return -1
def boundsIntersect(boundsA, boundsB):
# return l2<=r1 and t2<=b1 and l1<=r2 and t1<=b2
(ax0, ay0, ax1, ay1) = boundsA
(bx0, by0, bx1, by1) = boundsB
return ((ax1 >= bx0) and (bx1 >= ax0) and
(ay1 >= by0) and (by1 >= ay0))
def movePlayer(dx, dy, data):
data.playerX += dx
data.playerY += dy
# scroll to make player visible as needed
if (data.playerX < data.scrollX + data.scrollMargin):
data.scrollX = data.playerX - data.scrollMargin
if (data.playerX > data.scrollX + data.width - data.scrollMargin):
data.scrollX = data.playerX - data.width + data.scrollMargin
# and check for a new wall hit
wall = getWallHit(data)
if (wall != data.currentWallHit):
data.currentWallHit = wall
if (wall >= 0):
data.wallPoints[wall] += 1
def mousePressed(event, data):
pass
def keyPressed(event, data):
if (event.keysym == "Left"): movePlayer(-5, 0, data)
elif (event.keysym == "Right"): movePlayer(+5, 0, data)
elif (event.keysym == "Up"): movePlayer(0, +5, data)
elif (event.keysym == "Down"): movePlayer(0, -5, data)
def timerFired(data):
pass
def redrawAll(canvas, data):
# draw the base line
lineY = data.height/2
lineHeight = 5
canvas.create_rectangle(0, lineY, data.width, lineY+lineHeight,fill="black")
# draw the walls
# (Note: should optimize to only consider walls that can be visible now!)
sx = data.scrollX
for wall in range(data.walls):
(x0, y0, x1, y1) = getWallBounds(wall, data)
fill = "orange" if (wall == data.currentWallHit) else "pink"
canvas.create_rectangle(x0-sx, y0, x1-sx, y1, fill=fill)
(cx, cy) = ((x0+x1)/2 - sx, (y0 + y1)/2)
canvas.create_text(cx, cy, text=str(data.wallPoints[wall]))
cy = lineY + 5
canvas.create_text(cx, cy, text=str(wall), anchor=N)
# draw the player
(x0, y0, x1, y1) = getPlayerBounds(data)
canvas.create_oval(x0 - sx, y0, x1 - sx, y1, fill="cyan")
# draw the instructions
msg = "Use arrows to move, hit walls to score"
canvas.create_text(data.width/2, 20, text=msg)
####################################
# 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
init(data)
# create the root and the canvas
root = Tk()
canvas = Canvas(root, width=data.width, height=data.height)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(300, 300)
- Tetris
This (see
here) technically is not a worked example, but rather a step-by-step
tutorial that we use as a hw assignment. But given the detailed
tutorial, it makes sense to include it here.