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

Notes:
  1. To run these examples, first download cmu_112_graphics.py and be sure it is in the same folder as the file you are running.
  2. As with Tkinter graphics, the examples here will not run using Brython in your browser.

  1. Example: Adding and Deleting Shapes
    from cmu_112_graphics import * def appStarted(app): app.circleCenters = [ ] def mousePressed(app, event): newCircleCenter = (event.x, event.y) app.circleCenters.append(newCircleCenter) def keyPressed(app, event): if (event.key == 'd'): if (len(app.circleCenters) > 0): app.circleCenters.pop(0) else: print('No more circles to delete!') def redrawAll(app, canvas): # draw the circles for circleCenter in app.circleCenters: (cx, cy) = circleCenter r = 20 canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='cyan') # draw the text canvas.create_text(app.width/2, 20, text='Example: Adding and Deleting Shapes', fill='black') canvas.create_text(app.width/2, 40, text='Mouse clicks create circles', fill='black') canvas.create_text(app.width/2, 60, text='Pressing "d" deletes circles', fill='black') 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_112_graphics import * import random def appStarted(app): app.rows = 10 app.cols = 10 app.margin = 5 # margin around grid app.timerDelay = 250 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 x0 = app.margin + gridWidth * col / app.cols x1 = app.margin + gridWidth * (col+1) / app.cols y0 = app.margin + gridHeight * row / app.rows y1 = app.margin + gridHeight * (row+1) / app.rows return (x0, y0, x1, y1) def keyPressed(app, event): if (app.waitingForFirstKeyPress): app.waitingForFirstKeyPress = False elif (event.key == 'r'): initSnakeAndFood(app) elif app.gameOver: return elif (event.key == 'Up'): app.direction = (-1, 0) elif (event.key == 'Down'): app.direction = (+1, 0) elif (event.key == 'Left'): app.direction = (0, -1) elif (event.key == 'Right'): app.direction = (0, +1) # elif (event.key == 's'): # this was only here for debugging, before we turned on the timer # takeStep(app) def timerFired(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, canvas): for row in range(app.rows): for col in range(app.cols): (x0, y0, x1, y1) = getCellBounds(app, row, col) canvas.create_rectangle(x0, y0, x1, y1, fill='white', outline='black') def drawSnake(app, canvas): for (row, col) in app.snake: (x0, y0, x1, y1) = getCellBounds(app, row, col) canvas.create_oval(x0, y0, x1, y1, fill='blue') def drawFood(app, canvas): if (app.foodPosition != None): (row, col) = app.foodPosition (x0, y0, x1, y1) = getCellBounds(app, row, col) canvas.create_oval(x0, y0, x1, y1, fill='green') def drawGameOver(app, canvas): if (app.gameOver): canvas.create_text(app.width/2, app.height/2, text='Game over!', font='Arial 26 bold', fill='black') canvas.create_text(app.width/2, app.height/2+40, text='Press r to restart!', font='Arial 26 bold', fill='black') def redrawAll(app, canvas): if (app.waitingForFirstKeyPress): canvas.create_text(app.width/2, app.height/2, text='Press any key to start!', font='Arial 26 bold', fill='black') else: drawBoard(app, canvas) drawSnake(app, canvas) drawFood(app, canvas) drawGameOver(app, canvas) runApp(width=400, height=400)

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