CMU 15-112: Fundamentals of Programming and Computer Science
Class Notes: Event-Based Animations in Tkinter
Part 3: Animations and Lists


  1. Example: Adding and Deleting Shapes
  2. Example: Snake
  3. Snake and MVC

Note: The videos on this page use the lecture1/2 animation framework. Though the syntax of their animation framework is different, the explanations in the videos will still be helpful to you.
  1. Example: Adding and Deleting Shapes
    from cmu_graphics import * def onAppStart(app): app.circleCenters = [ ] def onMousePress(app, mouseX, mouseY): newCircleCenter = (mouseX, mouseY) app.circleCenters.append(newCircleCenter) def onKeyPress(app, key): if (key == 'd'): if (len(app.circleCenters) > 0): app.circleCenters.pop(0) else: print('No more circles to delete!') def redrawAll(app): # draw the circles for circleCenter in app.circleCenters: (cx, cy) = circleCenter r = 20 drawCircle(cx, cy, r, fill='cyan') # draw the text drawLabel('Example: Adding and Deleting Shapes', app.width / 2, 20) drawLabel('Mouse clicks create circles', app.width / 2, 40) drawLabel('Pressing "d" deletes circles', app.width /2, 60) runApp(width=400, height=400)

  2. Example: Snake
    Here is a 4-part video explaining how to write this version of Snake:
    1. Draw the board and the Snake
    2. Add motion and gameOver
    3. Add food and self-collision
    4. Add the timer and finish the game
    from cmu_graphics import * import random def onAppStart(app): app.rows = 10 app.cols = 10 app.margin = 5 # margin around grid app.stepsPerSecond = 4 initSnakeAndFood(app) app.waitingForFirstKeyPress = True def initSnakeAndFood(app): app.snake = [(0,0)] app.direction = (0, +1) # (drow, dcol) placeFood(app) app.gameOver = False # getCellBounds from grid-demo.py def getCellBounds(app, row, col): # aka 'modelToView' # returns (x0, y0, x1, y1) corners/bounding box of given cell in grid gridWidth = app.width - 2*app.margin gridHeight = app.height - 2*app.margin cellWidth = gridWidth / app.cols cellHeight = gridHeight / app.rows x0 = app.margin + cellWidth * col y0 = app.margin + cellHeight * row return (x0, y0, cellWidth, cellHeight) def onKeyPress(app, key): if (app.waitingForFirstKeyPress): app.waitingForFirstKeyPress = False elif (key == 'r'): initSnakeAndFood(app) elif app.gameOver: return elif (key == 'up'): app.direction = (-1, 0) elif (key == 'down'): app.direction = (+1, 0) elif (key == 'left'): app.direction = (0, -1) elif (key == 'right'): app.direction = (0, +1) # elif (key == 's'): # this was only here for debugging, before we turned on the timer # takeStep(app) def onStep(app): if app.gameOver or app.waitingForFirstKeyPress: return takeStep(app) def takeStep(app): (drow, dcol) = app.direction (headRow, headCol) = app.snake[0] (newRow, newCol) = (headRow+drow, headCol+dcol) if ((newRow < 0) or (newRow >= app.rows) or (newCol < 0) or (newCol >= app.cols) or ((newRow, newCol) in app.snake)): app.gameOver = True else: app.snake.insert(0, (newRow, newCol)) if (app.foodPosition == (newRow, newCol)): placeFood(app) else: # didn't eat, so remove old tail (slither forward) app.snake.pop() def placeFood(app): # Keep trying random positions until we find one that is not in # the snake. Note: there are more sophisticated ways to do this. while True: row = random.randint(0, app.rows-1) col = random.randint(0, app.cols-1) if (row,col) not in app.snake: app.foodPosition = (row, col) return def drawBoard(app): for row in range(app.rows): for col in range(app.cols): (left, top, width, height) = getCellBounds(app, row, col) drawRect(left, top, width, height, fill='white', border='black', borderWidth=1) def drawSnake(app): for (row, col) in app.snake: (left, top, width, height) = getCellBounds(app, row, col) cx = left + width / 2 cy = top + height / 2 drawOval(cx, cy, width, height, fill='blue') def drawFood(app): if (app.foodPosition != None): (row, col) = app.foodPosition (left, top, width, height) = getCellBounds(app, row, col) cx = left + width / 2 cy = top + height / 2 drawOval(cx, cy, width, height, fill='green') def drawGameOver(app): if (app.gameOver): drawLabel('Game over!', app.width / 2, app.height / 2, size=26, bold=True) drawLabel('Press r to restart!', app.width / 2, app.height / 2 + 40, size = 26, bold=True) def redrawAll(app): if (app.waitingForFirstKeyPress): drawLabel('Press any key to start!', app.width / 2, app.height / 2, size=26, bold=True) else: drawBoard(app) drawSnake(app) drawFood(app) drawGameOver(app) runApp(width=400, height=400)

  3. Snake and MVC
    Model View Controller
    app.rows redrawAll() onKeyPress()
    app.cols drawGameOver() onStep()
    app.margin drawFood() takeStep()
    app.waitingForFirstKeyPress drawSnake() placeFood()
    app.snake drawBoard() onAppStart()
    app.direction
    app.foodPosition
    + all game state + all drawing functions + all event-triggered actions