CMU 15-112: Fundamentals of Programming and Computer Science
Class Notes: Event-Based Animations in Tkinter
Part 3: Animations and Lists
Notes:
- 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.
- As with Tkinter graphics, the examples here will not run using Brython in your browser.
- 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)
- Example: Snake
Here is a 4-part video explaining how to write this version of Snake:- Draw the board and the Snake
- Add motion and gameOver
- Add food and self-collision
- 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) - 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