- Example: Grids (with modelToView and viewToModel)
from cmu_112_graphics import *
def appStarted(app):
app.rows = 4
app.cols = 8
app.margin = 5 # margin around grid
app.selection = (-1, -1) # (row, col) of selection, (-1,-1) for none
def pointInGrid(app, x, y):
# return True if (x, y) is inside the grid defined by app.
return ((app.margin <= x <= app.width-app.margin) and
(app.margin <= y <= app.height-app.margin))
def getCell(app, x, y):
# aka "viewToModel"
# return (row, col) in which (x, y) occurred or (-1, -1) if outside grid.
if (not pointInGrid(app, x, y)):
return (-1, -1)
gridWidth = app.width - 2*app.margin
gridHeight = app.height - 2*app.margin
cellWidth = gridWidth / app.cols
cellHeight = gridHeight / app.rows
# Note: we have to use int() here and not just // because
# row and col cannot be floats and if any of x, y, app.margin,
# cellWidth or cellHeight are floats, // would still produce floats.
row = int((y - app.margin) / cellHeight)
col = int((x - app.margin) / cellWidth)
return (row, col)
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 + col * cellWidth
x1 = app.margin + (col+1) * cellWidth
y0 = app.margin + row * cellHeight
y1 = app.margin + (row+1) * cellHeight
return (x0, y0, x1, y1)
def mousePressed(app, event):
(row, col) = getCell(app, event.x, event.y)
# select this (row, col) unless it is selected
if (app.selection == (row, col)):
app.selection = (-1, -1)
else:
app.selection = (row, col)
def redrawAll(app, canvas):
# draw grid of cells
for row in range(app.rows):
for col in range(app.cols):
(x0, y0, x1, y1) = getCellBounds(app, row, col)
fill = "orange" if (app.selection == (row, col)) else "cyan"
canvas.create_rectangle(x0, y0, x1, y1, fill=fill)
canvas.create_text(app.width/2, app.height/2 - 15, text="Click in cells!",
font="Arial 26 bold", fill="darkBlue")
runApp(width=400, height=400)
- Optional Example: Pong!
# 112_pong.py
# This is a simplified version of Pong, one of the earliest
# arcade games. We have kept it simple for learning purposes.
from cmu_112_graphics import *
def appStarted(app):
# This is a Controller
app.waitingForKeyPress = True
resetApp(app)
def resetApp(app):
# This is a helper function for Controllers
# This initializes most of our model (stored in app.xyz)
# This is called when they start the app, and also after
# the game is over when we restart the app.
app.timerDelay = 50 # milliseconds
app.dotsLeft = 2
app.score = 0
app.paddleX0 = 20
app.paddleX1 = 40
app.paddleY0 = 20
app.paddleY1 = 80
app.margin = 5
app.paddleSpeed = 10
app.dotR = 15
app.gameOver = False
app.paused = False
resetDot(app)
def resetDot(app):
# This is a helper function for Controllers
# Get the dot ready for the next round. Move the dot to
# the center of the screen and give it an initial velocity.
app.dotCx = app.width//2
app.dotCy = app.height//2
app.dotDx = -10
app.dotDy = -3
def movePaddleDown(app):
# This is a helper function for Controllers
# Move the paddle down while keeping it inside the play area
dy = min(app.paddleSpeed, app.height - app.margin - app.paddleY1)
app.paddleY0 += dy
app.paddleY1 += dy
def movePaddleUp(app):
# This is a helper function for Controllers
# Move the paddle up while keeping it inside the play area
dy = min(app.paddleSpeed, app.paddleY0 - app.margin)
app.paddleY0 -= dy
app.paddleY1 -= dy
def keyPressed(app, event):
# This is a Controller
if app.gameOver:
resetApp(app)
elif app.waitingForKeyPress:
app.waitingForKeyPress = False
app.dotsLeft -= 1
elif (event.key == 'Down'):
movePaddleDown(app)
elif (event.key == 'Up'):
movePaddleUp(app)
elif (event.key == 'p'):
app.paused = not app.paused
elif (event.key == 's') and app.paused:
doStep(app)
def timerFired(app):
# This is a Controller
if (not app.paused):
doStep(app)
def doStep(app):
# This is a helper function for Controllers
# The dot should move only when we are not waiting for
# a key press or in the game-over state
if not app.waitingForKeyPress and not app.gameOver:
moveDot(app)
def dotWentOffLeftSide(app):
# This is a helper function for Controllers
# Called when the dot went off the left side of the screen,
# so the round is over. If there are no dots left, then
# the game is over.
if app.dotsLeft == 0:
app.gameOver = True
else:
app.waitingForKeyPress = True
resetDot(app)
def dotIntersectsPaddle(app):
# This is a helper function for Controllers
# Check if the dot intersects the paddle. To keep this
# simple here, we will only test that the center of the dot
# is inside the paddle. We could be more precise here
# (that's an interesting exercise!).
return ((app.paddleX0 <= app.dotCx <= app.paddleX1) and
(app.paddleY0 <= app.dotCy <= app.paddleY1))
def moveDot(app):
# This is a helper function for Controllers
# Move the dot by the current velocity (dotDx and dotDy).
# Then handle all the special cases:
# * bounce the dot if it went off the top, right, or bottom
# * bounce the dot if it went off the paddle
# * lose the round (or the game) if it went off the left side
app.dotCx += app.dotDx
app.dotCy += app.dotDy
if (app.dotCy + app.dotR >= app.height):
# The dot went off the bottom!
app.dotCy = app.height - app.dotR
app.dotDy = -app.dotDy
elif (app.dotCy - app.dotR <= 0):
# The dot went off the top!
app.dotCy = app.dotR
app.dotDy = -app.dotDy
if (app.dotCx + app.dotR >= app.width):
# The dot went off the right!
app.dotCx = app.width - app.dotR
app.dotDx = -app.dotDx
elif dotIntersectsPaddle(app):
# The dot hit the paddle!
app.score += 1 # hurray!
app.dotDx = -app.dotDx
app.dotCx = app.paddleX1
dToMiddleY = app.dotCy - (app.paddleY0 + app.paddleY1)/2
dampeningFactor = 3 # smaller = more extreme bounces
app.dotDy = dToMiddleY / dampeningFactor
elif (app.dotCx - app.dotR <= 0):
# The dot went off the left side
dotWentOffLeftSide(app)
def drawAppInfo(app, canvas):
# This is a helper function for the View
# This draws the title, the score, and the dots left
font = 'Arial 18 bold'
title ='112 Pong!'
canvas.create_text(app.width/2, 20, text=title, font=font, fill='black')
canvas.create_text(app.width-70, 20,
text=f'Score: {app.score}',
font=font, fill='black')
canvas.create_text(app.width-70, app.height-20,
text=f'Dots Left: {app.dotsLeft}',
font=font, fill='black')
def drawPaddle(app, canvas):
# This is a helper function for the View
canvas.create_rectangle(app.paddleX0, app.paddleY0,
app.paddleX1, app.paddleY1,
fill='black')
def drawDot(app, canvas):
# This is a helper function for the View
cx, cy, r = app.dotCx, app.dotCy, app.dotR
canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='black')
def drawGameOver(app, canvas):
# This is a helper function for the View
canvas.create_text(app.width/2, app.height/2,
text='Game Over!',
font='Arial 18 bold', fill='black')
canvas.create_text(app.width/2, app.height/2 + 50,
text='Press any key to restart',
font='Arial 16 bold', fill='black')
def drawPressAnyKey(app, canvas):
# This is a helper function for the View
canvas.create_text(app.width/2, app.height/2,
text='Press any key to start!',
font='Arial 18 bold', fill='black')
def redrawAll(app, canvas):
# This is the View
drawAppInfo(app, canvas)
drawPaddle(app, canvas)
if app.gameOver:
drawGameOver(app, canvas)
elif app.waitingForKeyPress:
drawPressAnyKey(app, canvas)
else:
drawDot(app, canvas)
def main():
# This runs the app
runApp(width=400, height=300)
if __name__ == '__main__':
main()