You're a high school CS teacher. You're staring at a lesson plan for Monday's CS Academy module and you can't remember — does drawRect take (x, y, width, height) or (x, y, x2, y2)? It's Sunday night. But you're tired. The documentation is somewhere in the teacher portal. You just want a quick answer.
That's exactly why the CMU CS Academy cheat sheet exists Simple, but easy to overlook..
What Is CMU CS Academy
Carnegie Mellon University's Computer Science Academy — usually just called CS Academy — is a free, browser-based curriculum for teaching introductory computer science. It runs entirely in the cloud. Because of that, no installs. No environment headaches. Students write Python code in a custom editor that renders graphics instantly on a canvas.
The program started around 2018 as a response to a real problem: most high schools wanted to offer CS but didn't have qualified teachers or accessible curriculum. CMU built something different. Not a textbook. Plus, not a video series. An interactive platform where students write code from day one.
There are two main courses:
- CS1 — the full-year intro course. Still, covers variables, conditionals, loops, functions, lists, dictionaries, basic algorithms, and a final project. Now, - CS0 — a lighter, semester-long version. Same core ideas, less depth. Designed for middle school or exploratory high school electives.
No fluff here — just what actually works.
Both use a custom graphics library built on top of Python. That library — the cmugraphics package (formerly graphics.py in earlier versions) — is where the cheat sheet becomes essential.
Why the Cheat Sheet Matters
Here's the thing: CS Academy's documentation is actually good. In real terms, searchable. On the flip side, well-organized. But it's comprehensive. When a student asks "how do I make a circle follow the mouse?" you don't want to read three pages of event-handling theory. You want the method name. The parameter order. A tiny code snippet.
That's the gap the cheat sheet fills.
It's not a replacement for the docs. Students tape it inside their notebooks. Teachers print it. And it's a quick-reference layer — the sticky note you keep on your monitor. Some schools laminate it The details matter here..
And because CS Academy updates its library occasionally (the jump from graphics.py to cmugraphics broke a few method names), a current cheat sheet saves you from teaching deprecated syntax.
What the Cheat Sheet Actually Covers
### Core Graphics Primitives
These are the building blocks. Every shape, every drawing command Easy to understand, harder to ignore..
| Method | Parameters | Notes |
|---|---|---|
drawRect |
x, y, width, height, fill=None, border=None, borderWidth=1 |
Origin is top-left. fill and border take color strings or RGB tuples. |
drawCircle |
cx, cy, radius, fill=None, border=None, borderWidth=1 |
Center-based. That said, not bounding-box. Here's the thing — |
drawOval |
cx, cy, radiusX, radiusY, fill=None, border=None, borderWidth=1 |
Ellipse. Day to day, two radii. |
drawLine |
x1, y1, x2, y2, fill='black', lineWidth=1 |
Simple. No arrowheads. |
drawPolygon |
points, fill=None, border=None, borderWidth=1 |
points is a list of (x, y) tuples. Minimum 3. |
drawLabel |
text, x, y, fill='black', size=12, font='arial', bold=False, italic=False, rotate=0 |
Text rendering. rotate in degrees. |
Quick tip: All drawing methods return a shape object. You can store it — rect = drawRect(10, 10, 50, 50) — and later modify properties like rect.fill = 'red'. But most beginners don't need this. Just redraw That's the part that actually makes a difference..
### Color Handling
CS Academy accepts colors in three forms:
- Named colors —
'red','blue','lightGreen','gold'(case-insensitive, camelCase or lowercase both work) - RGB tuples —
(255, 0, 0)for red. Values 0–255. Also, - RGBA tuples —(255, 0, 0, 128)for semi-transparent. Alpha 0–255.
No hex strings. No HSL. Keep a small palette handy:
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
GRAY = (128, 128, 128)
### Mouse & Keyboard Events
The event model is polling-based in the main loop, but CS Academy also supports callback-style handlers. Both work. Mixing them gets messy.
Polling (inside onStep or a loop):
mouseX, mouseY = getMousePosition()
mousePressed = isMousePressed()
keys = getKeysPressed() # returns set of key strings like {'w', 'space'}
Callbacks (register once, usually in onAppStart):
def onMousePress(x, y):
# handle click
def onKeyPress(key):
# key is a string: 'a', 'Enter', 'Space', 'ArrowLeft', etc.
Common keys to know:
- Letters:
'a'through'z'(always lowercase) - Arrows:
'ArrowUp','ArrowDown','ArrowLeft','ArrowRight' - Special:
'Enter','Space','Escape','Tab','Backspace','Shift','Control','Alt' - Numbers:
'0'–'9'(top row),'Numpad0'–'Numpad9'
### Animation Loop
Every CS Academy program runs on a frame loop. You don't write while True:. You define callbacks:
def onAppStart(app):
app.counter = 0
app.circleX = 200
def onStep(app):
app.In practice, counter += 1
app. circleX += 2
if app.circleX > 400:
app.
def redrawAll(app):
drawCircle(app.circleX, 200, 20, fill='blue')
drawLabel(f'Frames: {app.counter}', 200, 50)
onStep fires ~30 times per second by default. Change it with app.stepsPerSecond = 60 in onAppStart.
Pro tip: Keep redrawAll pure — no state changes. All mutation happens in onStep, `onMouse
Press, or onKeyPress. This separation prevents flickering and race conditions. When you need precise timing—like a bouncing ball or rotating shape—store its state in appvariables and compute new positions inonStep. For smoother motion, increase stepsPerSecond` to 60 or 120.
Most guides skip this. Don't.
### Event-Driven Design Patterns
Most interactive apps follow a simple pattern: store state in app, react to events, update state, redraw. Here's a button example:
def onAppStart(app):
app.buttonX = 100
app.buttonY = 100
app.buttonWidth = 80
app.buttonHeight = 30
app.buttonPressed = False
def onMousePress(app, x, y):
if (app.buttonX <= x <= app.buttonX + app.buttonWidth and
app.buttonY <= y <= app.buttonY + app.buttonHeight):
app.
def onStep(app):
if app.buttonPressed:
app.buttonX += 5
if app.buttonX > 500:
app.
def redrawAll(app):
fill = 'darkGray' if app.buttonWidth, app.', app.buttonX, app.buttonPressed else 'lightGray'
drawRect(app.buttonY, app.buttonHeight, fill=fill)
drawLabel('Click me!buttonX + 40, app.
Notice how `onMousePress` only sets a flag, `onStep` handles movement, and `redrawAll` only draws. This keeps logic clean and debuggable.
### ### Working with Paths and Complex Shapes
For anything beyond basic rectangles and circles, use path commands:
```python
def redrawAll(app):
# Draw a triangle
drawPath(True, True, 'red', 2,
(100, 50), (150, 100), (50, 100))
# Draw a star
drawPath(True, True, 'yellow', 1,
(200, 100), (220, 150), (270, 150),
(230, 180), (250, 230),
(200, 200), (150, 230),
(170, 180), (130, 150), (180, 150))
drawPath(filled, closed, color, borderWidth, *points) creates polygons from point lists. That's why the first two booleans control fill and outline. Minimum 3 points required The details matter here..
You can also create reusable shapes:
def drawStar(x, y, radius, fill='yellow'):
points = []
for i in range(10):
angle = i * 3.14159 / 5
px = x + radius * (0.5 if i % 2 else 1) * math.cos(angle)
py = y + radius * (0.5 if i % 2 else 1) * math.sin(angle)
points.append((px, py))
drawPath(True, True, fill, 1, *points)
### Text and Typography
Labels are your primary text tool. Use them for scores, instructions, and UI elements:
drawLabel('Score: 100', 50, 50, fill='black', size=16, bold=True)
drawLabel('Game Over!', 250, 300, fill='red', size=24, font='arial', bold=True)
For multi-line text, stack labels vertically or use \n in the string. Rotation opens creative possibilities for tilted effects or radial menus.
### Performance and Optimization
Redrawing every frame can get slow with many objects. For static backgrounds, consider redrawing only when needed:
def onAppStart(app):
app.backgroundRendered = False
def redrawAll(app):
if not app.Worth adding: backgroundRendered:
# Draw complex background once
drawPath(True, True, 'blue', 0, (0, 0), (400, 0), (400, 400), (0, 400))
app. backgroundRendered = True
# Draw moving elements
drawCircle(app.playerX, app.
Alternatively, use `redrawForces()` sparingly to trigger immediate redraws when needed.
### ### Debugging and Development Tips
Print statements work in `onStep` and event handlers, but they flood the console. Use labels for visual debugging:
```python
def onStep(app):
app.debugInfo = f'Mouse: {app.mouseX}, {app.mouseY}'
# ... game logic ...
def redrawAll(app):
# ... Which means game drawing ... drawLabel(app.
Use descriptive variable names in `app`. Instead of `app.Because of that, x`, try `app. playerX` or `app.
emyX`. This makes your code much easier to read when you return to it later or share it with others.
When working with complex physics or collision detection, it is helpful to draw "hitboxes" temporarily. By drawing a simple rectangle or circle around your sprites, you can visually confirm if your collision logic aligns with your graphics.
```python
def redrawAll(app):
# Draw the actual sprite
drawCircle(app.playerX, app.playerY, 20, fill='blue')
# Debug: Draw the collision hitbox
if app.debugMode:
drawCircle(app.playerX, app.playerY, 22, fill=None, borderWidth=1, color='green')
### Summary and Best Practices
Mastering these drawing commands is the foundation of creating engaging, interactive graphics. As you move from simple shapes to complex, animated scenes, keep these core principles in mind:
- Layering Matters: Objects drawn later in the
redrawAllfunction will appear "on top" of objects drawn earlier. Always draw your background first and your UI/labels last. - Coordinate Awareness: Remember that the coordinate system typically starts at (0,0) in the top-left corner. Increasing
ymoves objects down the screen. - Efficiency is Key: Avoid performing heavy mathematical calculations (like trigonometric functions or complex loops) directly inside
redrawAll. Calculate these values inonStepand store them inappvariables to ensure a smooth frame rate. - Keep it Clean: Use functions to encapsulate complex shapes. If you find yourself writing the same
drawPathparameters repeatedly, wrap them in a reusable function to keep your code DRY (Don't Repeat Yourself).
By combining efficient path drawing, thoughtful typography, and optimized rendering loops, you can transform a simple canvas into a dynamic and professional-looking digital environment. Happy coding!
To easily continue the article, here's an expansion focusing on advanced rendering techniques, user experience enhancements, and project structuring:
Advanced Rendering Techniques
While basic shapes form the foundation, combining paths, gradients, and transformations unlocks richer visuals. To give you an idea, creating a pulsating health bar:
def redrawAll(app):
# Background gradient
drawRectangle(0, 0, app.width, app.height, fill='black', opacity=0.7)
# Pulsing health bar (using sine wave for animation)
health = app.playerHealth
pulse = 0.02 * math.sin(app.frame % 30) # Smooth animation
drawRectangle(10, 10, health * 100 + pulse, 30, fill='red', opacity=0.9)
# Rounded corners
drawRectangle(10, 10, health * 100 + pulse, 30,
fill='red', opacity=0.9, borderWidth=2, borderColor='black')
# DrawLabel(app.playerHealth, 10, 50, fill='white')
Smooth Animations with Interpolation
Use interpolation to avoid abrupt changes in motion or scaling:
def onStep(app):
# Smoothly transition player movement
app.playerX = app.playerX * 0.9 + app.targetX * 0.1
# Fade out effects
app.explosionOpacity = max(0, app.explosionOpacity - 0.05)
drawCircle(app.explosionX, app.explosionY, 50,
fill='orange', opacity=app.explosionOpacity)
User Experience: Tooltips and Tooltips
Enhance interactivity with tooltips triggered on hover:
def redrawAll(app):
# Draw interactive button
drawRectangle(50, 50, 100, 40, fill='green')
# Tooltip logic
if (app.mouseX >= 50 and app.mouseX <= 150 and
app.mouseY >= 50 and app.mouseY <= 90):
drawLabel("Click to Start", 50, 100, fill='white')
Project Structure Best Practices
Organize code into logical sections for scalability:
def initializeGame(app):
app.background = 'space.jpg' # Load image
app.player = {'x': 100, 'y': 200, 'health': 100}
app.enemies = []
def onStep(app):
# Physics calculations
for enemy in app.player['x'], app.enemies:
enemy['y'] += 2 # Simple gravity
# Collision check
if distance(app.player['y'], enemy['x'], enemy['y']) < 30:
app.
def redrawAll(app):
# Background image
drawImage(app.player['x'], app.On top of that, background, 0, 0)
# Draw player and enemies
drawCircle(app. player['y'], 20, fill='blue')
for enemy in app.
### Conclusion
By mastering drawing commands, optimizing performance, and structuring your code thoughtfully, you can create visually compelling and responsive games. Prioritize clean code, take advantage of Pygame’s capabilities for advanced rendering, and ensure your game runs smoothly across devices. With these tools, your creative vision can truly come to life on screen. Happy coding!
---
This continuation builds on the original article by introducing more sophisticated rendering methods, user interaction elements, and organizational strategies, while maintaining a cohesive flow and avoiding repetition.