15-112 Fundamentals of Programming

Notes - Lecture 4.4


Content

  1. Animations - Snake demo
  2. OOP - Part 1

Animations: Snake demo

Approach 1: Using (headRow, headCol) to represent the snake

# snake-demo.py

from tkinter import *
import random

def init(data):
    data.rows = 8
    data.cols = 16
    data.margin = 5
    data.direction = (0, -1)
    loadSnakeBoard(data)
    placeFood(data)
    data.gameOver = False
    data.paused = False
    data.debugMode = False

def loadSnakeBoard(data):
    data.board = []
    for row in range(data.rows): data.board += [[0]*data.cols]
    headRow = (len(data.board)) // 2
    headCol = len(data.board[0]) // 2
    data.board[headRow][headCol] = 1
    data.headRow = headRow
    data.headCol = headCol

def mousePressed(event, data):
    pass

def keyPressed(event, data):
    if (event.char == "r"): init(data); return
    elif (event.char == "p"): data.paused = not data.paused; return
    elif (event.char == "d"):
        data.debugMode = not data.debugMode
    if (data.gameOver or data.paused): return
    if (event.keysym == "Left"):    data.direction = (0, -1)
    elif (event.keysym == "Right"): data.direction = (0,  1)
    elif (event.keysym == "Up"):    data.direction = (-1, 0) 
    elif (event.keysym == "Down"):  data.direction = ( 1, 0)
    takeStep(data)

def timerFired(data):
    if (data.paused or data.gameOver): return
    takeStep(data)

def takeStep(data):
    (drow, dcol) = data.direction
    (headRow, headCol) = (data.headRow, data.headCol)
    (newHeadRow, newHeadCol) = (headRow + drow, headCol + dcol)
    if ((newHeadRow < 0) or (newHeadRow >= data.rows) or 
        (newHeadCol < 0) or (newHeadCol >= data.cols) or 
        data.board[newHeadRow][newHeadCol] > 0):
        data.gameOver = True
    elif data.board[newHeadRow][newHeadCol] == -1:
        # eat food
        data.board[newHeadRow][newHeadCol]= data.board[headRow][headCol] + 1
        (data.headRow, data.headCol) = (newHeadRow, newHeadCol)
        placeFood(data)
    else:
        # didn't eat, so remove old tail (slither forward)
        data.board[newHeadRow][newHeadCol] = data.board[headRow][headCol] + 1
        (data.headRow, data.headCol) = (newHeadRow, newHeadCol)
        removeTail(data)

def placeFood(data):
    row = random.randint(0, data.rows - 1)
    col = random.randint(0, data.cols - 1)
    while data.board[row][col] != 0:
        row = random.randint(0, data.rows - 1)
        col = random.randint(0, data.cols - 1)
    data.board[row][col] = -1

def removeTail(data):
    for row in range(data.rows):
        for col in range(data.cols):
            if data.board[row][col] > 0:
                data.board[row][col] -= 1

def drawBoard(canvas, data):
    for row in range(data.rows):
        for col in range(data.cols):
            drawSnakeCell(canvas, data, row, col)   

def drawSnakeCell(canvas, data, row,col):
    gridWidth  = data.width - 2*data.margin
    gridHeight = data.height - 2*data.margin
    cellWidth = gridWidth / data.cols
    cellHeight = gridHeight / data.rows
    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

    canvas.create_rectangle(x0, y0, x1, y1, fill="white", outline="black")
    if data.board[row][col] > 0:
        # draw snake body
        canvas.create_oval(x0, y0, x1, y1, fill="blue")
    elif data.board[row][col] == -1:
        # draw food
        canvas.create_oval(x0, y0, x1, y1, fill="green")
    if (data.debugMode):
        canvas.create_text(x0 + cellWidth/2, y0 + cellHeight/2,
                           text=str(data.board[row][col]),
                           font=("Helvatica", 14, "bold"))

def redrawAll(canvas, data):
    drawBoard(canvas, data)
    if (data.gameOver):
        canvas.create_text(data.width/2, data.height/2, text="Game Over!", 
                           font=("Helvetica", 32, "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)
    # 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)



Approach 2: Using a list of tuples to represent the snake

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(600, 300)

Introduction to Object-Oriented Programming (OOP)

Examples

  • Rectangle class
  • Employee class
  • Cat class
  • Animation framework with data being a Struct object


  • Valid CSS! Valid XHTML 1.0 Strict