Computer Science Essentials · Unit 1 Project · Build guides
Build Guides
One guide per game. Do the Designer part first, then the Blocks steps in order — each one has a test. If the test fails, fix it before moving on. Your must-haves and rubric are on the project page.
Before you start (every game)
Go to ai2.appinventor.mit.edu, sign in, Projects → Start new project. Name it with no spaces: MoleMash, CatchIt, RockPaperScissors, GuessMyNumber, TicTacToe.
Rename every component the moment you drop it in (select it → Rename, bottom of the Components panel). The guide uses the names below. If yours are different, your blocks will have different names — that's fine, just be consistent.
Connect the phone with Connect → AI Companion the same way you did for Digital Doodle. Test after every step. Not at the end.
Blocks are found two ways: Built-in drawers (Control, Logic, Math, Text, Variables, Procedures) on the left, or by clicking a component's name to open its own drawer (that's where when Mole.Touched lives).
To add else if / else to an if block, click the little blue gear on it and drag the pieces in. To give a procedure an input, same gear on the procedure block.
Drag these in, in this order. Rename each one. Set the properties listed; leave everything else alone.
Rename to
Component (palette)
Properties to set
ScoreLabel
Label (User Interface)
Text: Score: 0 · FontSize 20
MissLabel
Label (User Interface)
Text: Misses: 0 · FontSize 20
Canvas1
Canvas (Drawing and Animation)
Width: Fill parent · Height: 320 pixels · BackgroundColor: any light color
Mole
ImageSprite (Drawing and Animation) — drop it inside the Canvas
Picture: upload any small PNG (Media → Upload File; a 60×60 image is good; happy.png from Happy Accelerometer works to start)
ResetButton
Button (User Interface)
Text: Reset · Width: Fill parent
Clock1
Clock (Sensors) — it goes below the phone as a "non-visible component"
TimerInterval: 1000 · TimerEnabled: ✓ checked
Check: the Components tree on the right shows Mole indented under Canvas1. If it's beside Canvas1, drag it onto the canvas again.
Part BBlocks — what the app does
1
Make the two counters
Find it: Variables drawer → initialize global name to. Click "name" to rename. Plug a Math number block (0) into each.
initialize global score to0initialize global misses to0
Initialize before you update — no box, nothing to add to. Same rule as Happy Accelerometer.
2
The procedure that moves the mole
Find it: Procedures → to procedure do. Rename to moveMole. Click Mole → call Mole.MoveTo. Math → random integer from … to … (twice) and the − block. Click Canvas1 → Canvas1.Width / Canvas1.Height getters; click Mole → Mole.Width / Mole.Height.
to moveMole
do call Mole.MoveTo
x random integer from0 to ( Canvas1.Width−Mole.Width )
y random integer from0 to ( Canvas1.Height−Mole.Height )
Why subtract the mole's width? If x could be all the way at Canvas1.Width, the mole would start at the right edge and hang off it. The biggest x where the whole mole still fits is Width − Mole.Width.
3
Make the Clock call it
Find it: click Clock1 → when Clock1.Timer. Procedures → call moveMole (it appears there once you made the procedure).
when Clock1.Timer
do call moveMole
Test: on the phone, the mole should hop to a new spot every second and never hang off the edge.
Doesn't move? Clock1's TimerEnabled is unchecked in the Designer, or the call block isn't snapped inside the Timer.
4
Hitting the mole
Find it: click Mole → when Mole.Touched. Variables → set global score to and get global score. Math → +. Click ScoreLabel → set ScoreLabel.Text to. Text → join.
when Mole.Touched (x, y)
do set global score to ( get global score+1 )
set ScoreLabel.Text tojoin "Score: " get global scorecall moveMole
Read → change → write. Then the Label is updated separately — changing the variable never changes the screen by itself.
Test: tap the mole. Score goes up by exactly 1 and the mole jumps away.
5
Missing the mole (the tricky one)
Find it: click Canvas1 → when Canvas1.Touched. It gives you three values: x, y, and touchedAnySprite. Hover over touchedAnySprite on the event block and drag out its get. Logic → not. Control → if then.
when Canvas1.Touched (x, y, touchedAnySprite)
do ifnotget touchedAnySprite
then set global misses to ( get global misses+1 )
set MissLabel.Text tojoin "Misses: " get global misses
Tapping the mole fires both Mole.Touched and Canvas1.Touched. touchedAnySprite is true when the tap landed on a sprite — so "if not" means "only count it when they hit grass."
Test: tap grass → Misses +1. Tap the mole → Score +1 and Misses stays the same.
Every hit also counts a miss? The not block is missing, or the set blocks are outside the if.
6
Reset
Find it: Procedures → new procedure, rename reset. Click ResetButton → when ResetButton.Click.
to reset
do set global score to0set global misses to0set ScoreLabel.Text to "Score: 0"
set MissLabel.Text to "Misses: 0"
when ResetButton.Click
do call reset
Test: play a bit, tap Reset, both labels read 0, and the next hit reads 1 (not 8).
That's all five must-haves. Now go break it: tap the very edge of the mole, tap fast twice, rotate the phone. Write down what happened for your bug log. Then stretch goals: speed up every 5 hits with set Clock1.TimerInterval to (Clock1.TimerInterval − 100) inside an if score mod 5 = 0 — but don't go below 300.
🧺 Catch It!
Intermediate · about 2–3 class periods
Part ADesigner — what the app has
Rename to
Component (palette)
Properties to set
CaughtLabel
Label
Text: Caught: 0 · FontSize 18
LostLabel
Label
Text: Lost: 0 · FontSize 18
Canvas1
Canvas (Drawing and Animation)
Width: Fill parent · Height: 400 pixels · light BackgroundColor
Basket
ImageSprite — inside the Canvas
Picture: a basket/bowl PNG · Y: 340 (near the bottom) · X: 120
Gift
ImageSprite — inside the Canvas
Picture: a gift/star PNG · X: 100 · Y: 0 (top)
ResetButton
Button
Text: Reset
Clock1
Clock (Sensors)
TimerInterval: 50 · TimerEnabled ✓
AccelerometerSensor1
AccelerometerSensor (Sensors)
Enabled ✓ — skip this row if you'll move the basket by dragging instead
Check: both sprites are indented under Canvas1. Gift is near the top, Basket near the bottom.
Part BBlocks
1
Variables
Find it: Variables → three initialize global blocks.
initialize global caught to0initialize global lost to0initialize global speed to4
speed lives in one place so you can tune the game later with one change — that's the whole point of a variable instead of typing 4 everywhere.
2
Move the basket (pick ONE way)
Tilt: click AccelerometerSensor1 → when AccelerometerSensor1.AccelerationChanged, hover xAccel for its get. Drag: click Canvas1 → when Canvas1.Dragged, hover currentX.
TILT:
when AccelerometerSensor1.AccelerationChanged (xAccel, yAccel, zAccel)
do set Basket.X to ( Basket.X+ ( get xAccel×4 ) )
— or — DRAG:
when Canvas1.Dragged (startX, startY, prevX, prevY, currentX, currentY, draggedAnySprite)
do set Basket.X to ( get currentX− ( Basket.Width/2 ) )
Test: basket slides left/right. It will fly off the edge for now — that's step 3.
Tilt goes the wrong way? Change the × 4 to × −4. Too slow? Make the 4 bigger.
3
Keep the basket on screen
Find it: click Basket → when Basket.EdgeReached and call Basket.MoveTo. Control → if then, click its gear and add an else. Math → compare block, change the dropdown to <.
when Basket.EdgeReached (edge)
do ifBasket.X<1
then call Basket.MoveTo x: 1 y: Basket.Y
else call Basket.MoveTo x: ( Canvas1.Width−Basket.Width−1 ) y: Basket.Y
EdgeReached fires the instant the sprite touches any edge. The if asks "which side?" — tiny X means the left edge, so pin it there; otherwise pin it to the right.
Test: push the basket hard left and right. It stops at the walls.
4
The procedure that drops a new gift
Find it: Procedures → rename dropNewGift. Click Gift → call Gift.MoveTo. Math → random integer.
to dropNewGift
do call Gift.MoveTo
x random integer from0 to ( Canvas1.Width−Gift.Width )
y 0
Y = 0 is the top of the canvas. Screen Y grows downward — the trap from Happy Accelerometer.
5
Make it fall, and catch the miss
Find it: click Clock1 → when Clock1.Timer. Click Gift → set Gift.Y to, Gift.Y, Gift.Height. Control → if then. Math → >.
when Clock1.Timer
do set Gift.Y to ( Gift.Y+get global speed )
ifGift.Y> ( Canvas1.Height−Gift.Height )
then set global lost to ( get global lost+1 )
set LostLabel.Text tojoin "Lost: " get global lostcall dropNewGift
Test: the gift falls. When it reaches the bottom, Lost goes up by 1 and a new gift appears at the top somewhere else.
Gift falls up? You subtracted speed. Lost jumps by 5 at once? Clock fires 20× a second — the gift sat at the bottom through several ticks. Make sure call dropNewGift is inside the then.
6
Catch it
Find it: click Gift → when Gift.CollidedWith.
when Gift.CollidedWith (other)
do set global caught to ( get global caught+1 )
set CaughtLabel.Text tojoin "Caught: " get global caughtcall dropNewGift
Calling dropNewGift right away moves the gift out of the basket, so CollidedWith can't keep firing while they overlap.
Test: catch one. Caught +1, exactly once. Miss one. Lost +1.
7
Reset
to reset
do set global caught to0set global lost to0set CaughtLabel.Text to "Caught: 0"
set LostLabel.Text to "Lost: 0"
call dropNewGiftwhen ResetButton.Click
do call reset
Test: Reset zeroes both labels and restarts the gift from the top.
All five must-haves done. Bug log ideas: what happens if you hold the phone flat? What if two gifts overlap the basket? Stretch: every 5 catches, set global speed to (speed + 1) — one line, because speed is a variable.
✊ Rock, Paper, Scissors
Beginner · about 2 class periods · needs if / else if (1.1.3)
Part ADesigner — what the app has
Rename to
Component (palette)
Properties to set
ScoreLabel
Label
Text: You 0 — Phone 0 · FontSize 22 · TextAlignment center · Width Fill parent
PhoneLabel
Label
Text: Phone picks… · FontSize 40 · TextAlignment center · Width Fill parent
ResultLabel
Label
Text: Pick one! · FontSize 18 · TextAlignment center · Width Fill parent
Row
HorizontalArrangement (Layout)
Width: Fill parent · AlignHorizontal: Center
RockButton
Button — inside Row
Text: ✊ Rock · FontSize 18
PaperButton
Button — inside Row
Text: ✋ Paper · FontSize 18
ScissorsButton
Button — inside Row
Text: ✌️ Scissors · FontSize 18
NewMatchButton
Button
Text: New match · Width Fill parent
Part BBlocks
1
Scores
initialize global you to0initialize global phone to0
2
A procedure that shows the phone's pick
Find it: Procedures → to procedure do, rename showPick. Click its blue gear, drag input: x into the inputs slot, rename x to n. Control → if, gear → add two else if. Math → = compare.
to showPick (n)
do ifget n=1
then set PhoneLabel.Text to "✊"
else ifget n=2
then set PhoneLabel.Text to "✋"
elseset PhoneLabel.Text to "✌️"
1 = rock, 2 = paper, 3 = scissors everywhere in this app. Write that on your plan sheet so you don't flip it later.
3
The main procedure: play(mine)
Find it: new procedure play with input mine. Variables → initialize local name to … in (the one with a "do" slot), rename to phonePick. Math → random integer from 1 to 3. Logic → and, or.
to play (mine)
do initialize local phonePick torandom integer from1 to 3
in call showPick n: get phonePickifget mine=get phonePick
then set ResultLabel.Text to "Tie!"
else if ( get mine=1andget phonePick=3 )
or ( get mine=2andget phonePick=1 )
or ( get mine=3andget phonePick=2 )
then set ResultLabel.Text to "You win the round!"
set global you to ( get global you+1 )
elseset ResultLabel.Text to "Phone wins the round."
set global phone to ( get global phone+1 )
set ScoreLabel.Text tojoin "You " get global you " — Phone " get global phone
Three ways to win, so three and pairs joined by or. Rock(1) beats scissors(3). Paper(2) beats rock(1). Scissors(3) beats paper(2). Everything else that isn't a tie is a loss — that's what else is for.
The or block only has two slots? Click its gear and add a third, or nest one or inside another.
4
Wire the three buttons
Find it: click each button → its .Click event. Procedures → call play.
when RockButton.Click do call play mine: 1when PaperButton.Click do call play mine: 2when ScissorsButton.Click do call play mine: 3
Test — all nine: tap Rock until the phone shows each of ✊ ✋ ✌️ and check the result each time. Then Paper, then Scissors. Write down any combo that's wrong. That's your bug log.
Phone always shows the same thing? The random block is outside play, so it ran once. Scores jump by 2? A + 1 is in two branches.
5
First to five, and New Match
Add this at the bottom of play, after the score label line. Click each button → set Button.Enabled to; Logic → false / true.
(inside play, last thing in the "in" slot)
ifget global you=5orget global phone=5
then set ResultLabel.Text to "MATCH OVER — tap New match"
set RockButton.Enabled tofalseset PaperButton.Enabled tofalseset ScissorsButton.Enabled tofalseto reset
do set global you to0set global phone to0set ScoreLabel.Text to "You 0 — Phone 0"
set ResultLabel.Text to "Pick one!"
set PhoneLabel.Text to "Phone picks…"
set RockButton.Enabled totrueset PaperButton.Enabled totrueset ScissorsButton.Enabled totruewhen NewMatchButton.Click
do call reset
Test: play to 5. The three buttons grey out. New match brings them back at 0–0.
All five must-haves done. Stretch: a streak variable that goes +1 on a win and back to 0 on a loss, shown in a fourth label. Or swap the emoji for Image components.
🔢 Guess My Number
Beginner · about 1–2 class periods · needs if / else if (1.1.3)
Part ADesigner — what the app has
Rename to
Component (palette)
Properties to set
TitleLabel
Label
Text: I'm thinking of a number from 1 to 100 · FontSize 18 · Width Fill parent
GuessBox
TextBox (User Interface)
NumbersOnly ✓ · Hint: your guess · Width Fill parent
GuessButton
Button
Text: Guess · Width Fill parent
FeedbackLabel
Label
Text: (blank) · FontSize 22 · TextColor: a dark green
CountLabel
Label
Text: Guesses: 0
NewGameButton
Button
Text: New game
Notifier1
Notifier (User Interface) — non-visible
nothing to set
Part BBlocks
1
Variables
initialize global secret to0initialize global guesses to0
secret starts at 0 only so the box exists. The real number gets picked in step 2.
2
newGame — and call it at startup
Find it: Procedures → rename newGame. Click Screen1 (top of the component list) → when Screen1.Initialize. Click GuessButton → set GuessButton.Enabled to.
to newGame
do set global secret torandom integer from1 to 100set global guesses to0set FeedbackLabel.Text to ""
set CountLabel.Text to "Guesses: 0"
set GuessBox.Text to ""
set GuessButton.Enabled totruewhen Screen1.Initialize
do call newGamewhen NewGameButton.Click
do call newGame
Screen1.Initialize runs once when the app opens — same block Happy Accelerometer used. Two buttons, one procedure: define once, call twice.
Test (sneaky): temporarily add set TitleLabel.Text to get global secret at the end of newGame so you can see the number. Open the app — a number shows. Tap New game — a different number. Remove that line before you demo.
3
Reject a bad guess first
Find it: click GuessButton → when GuessButton.Click. Control → if, gear → add else. Math → is number?. Logic → not. Click GuessBox → GuessBox.Text.
when GuessButton.Click
do ifnotis number?GuessBox.Text
then set FeedbackLabel.Text to "Type a number first."
else
… step 4 goes here …
Everything that counts as a real guess goes in the else. If the counter were above the if, blank taps would count — that's must-have M4.
Test: tap Guess with the box empty. "Type a number first." Guesses stays 0.
4
Too low, too high, got it
Find it: inside the else from step 3. Control → if, gear → one else if and one else. Math → compare with < and >. Click Notifier1 → call Notifier1.ShowAlert.
elseset global guesses to ( get global guesses+1 )
set CountLabel.Text tojoin "Guesses: " get global guessesifGuessBox.Text<get global secret
then set FeedbackLabel.Text to "Too low ⬆"
else ifGuessBox.Text>get global secret
then set FeedbackLabel.Text to "Too high ⬇"
elseset FeedbackLabel.Text tojoin "You got it in " get global guesses " guesses!"
call Notifier1.ShowAlert notice: FeedbackLabel.Textset GuessButton.Enabled tofalse
Use the Math compare blocks (blue), not the Text compare (pink). The text version compares "9" and "10" letter by letter and says 9 is bigger.
Test: with the sneaky title still on, guess below, above, then exactly. Three different messages, counter goes 1 → 2 → 3, the Guess button greys out, New game wakes it up.
Always "Too high"? secret is still 0 — newGame isn't called from Initialize. Counter counts blanks? The + 1 is above the first if.
All five must-haves done. Remove the sneaky title line. Stretch: a best variable — after a win, if guesses < best (start best at 999) then set best, and show it in TitleLabel. Or "warmer / colder" with a lastDistance variable.
❌ Tic-Tac-Toe
Beginner+ · about 2 class periods · needs if / else (1.1.3)
This is what you're building — play it
X's turn
X: 0 · O: 0 · Draws: 0
Part ADesigner — what the app has
Rename to
Component (palette)
Properties to set
StatusLabel
Label
Text: X's turn · FontSize 22 · TextAlignment center · Width Fill parent
Grid
TableArrangement (Layout)
Columns 3 · Rows 3 · Width Fill parent
B1 … B9
Button × 9 — drop each one into a cell of Grid, left to right, top to bottom
Text: blank (delete the word "Text for Button1") · Width 100 pixels · Height 100 pixels · FontSize 40 · FontBold ✓
ScoreLabel
Label
Text: X: 0 · O: 0 · TextAlignment center · Width Fill parent
NewGameButton
Button
Text: New game · Width Fill parent
Check: the Components tree shows B1–B9 indented under Grid, in order. Numbering matters for the win lines: B1 B2 B3 is the top row, B1 B5 B9 is a diagonal.
Part BBlocks
1
Variables
Find it: Variables → four initialize global blocks. For turn, plug in a Text block and type X.
initialize global turn to"X"initialize global moves to0initialize global xWins to0initialize global oWins to0
2
One square, done right — then copy it
Find it: click B5 → when B5.Click, B5.Text, set B5.Text to. Control → if then. Math → = (it compares text too). Text → the empty " " block. Make a Procedures block named afterMove now (leave it empty) so you can call it.
when B5.Click
do ifB5.Text=""
then set B5.Text toget global turncall afterMove
The if is must-have M1: a square that already has a letter ignores the tap. Without it, O could overwrite X.
Test: tap the middle square → "X" appears. Tap it again → nothing changes.
Now copy it 8 times: right-click the when B5.Click block → Duplicate. On the copy, use the dropdown on the block to change B5 → B1, and change all three B5's inside it to B1. Repeat for B2, B3, B4, B6, B7, B8, B9. Nine handlers, each one identical except its number.
A copy still says B5 inside? Then tapping B3 writes into B5. Every block inside a handler must match the handler's number — check all three.
3
A procedure that answers a question: three(a, b, c)
Find it: Procedures → the block that says to procedure result (the second one in the drawer — it has a socket on the right, not a "do" slot). Rename three. Blue gear → add three inputs, rename them a, b, c. Logic → and (gear → 3 slots). Math → = twice and ≠ once (change the dropdown).
to three (a, b, c) result
( get a=get b ) and ( get b=get c ) and ( get a≠"" )
This procedure doesn't do anything — it answers true or false. "Are all three the same, and not blank?" The ≠ "" part is the one everyone forgets, and without it three empty squares count as a win.
4
checkWin — ask the question eight times
Find it: another to procedure result, rename checkWin. Logic → or, gear → 8 slots. Procedures → call three (the one with a plug on the left, since it returns a value) × 8. Click each button → its .Text getter.
to checkWinresultthree( B1.Text, B2.Text, B3.Text ) or ← top row
three( B4.Text, B5.Text, B6.Text ) or ← middle row
three( B7.Text, B8.Text, B9.Text ) or ← bottom row
three( B1.Text, B4.Text, B7.Text ) or ← left column
three( B2.Text, B5.Text, B8.Text ) or ← middle column
three( B3.Text, B6.Text, B9.Text ) or ← right column
three( B1.Text, B5.Text, B9.Text ) or ← diagonal ↘
three( B3.Text, B5.Text, B7.Text ) ← diagonal ↙
Eight lines, one question each. Because three exists, this is eight short calls instead of 24 comparisons. Draw the 3×3 grid on paper, number it 1–9, and check your eight triples against it before you build them.
5
lockBoard and reset
Find it: two ordinary to procedure do blocks. Click each button → set B1.Enabled to and set B1.Text to. Logic → false / true.
to lockBoard
do set B1.Enabled tofalseset B2.Enabled tofalse
… all nine …
to reset
do set B1.Text to""set B1.Enabled totrueset B2.Text to""set B2.Enabled totrue
… all nine …
set global moves to0set global turn to"X"set StatusLabel.Text to"X's turn"when NewGameButton.Click
do call reset
Yes, eighteen set blocks in reset. That's the honest cost of nine separate buttons — and exactly why the "any Button" stretch goal exists. Scores are not zeroed here on purpose: New game starts a new round, not a new match.
6
afterMove — the brain
Find it: fill in the empty afterMove from step 2. Control → if, gear → one else if and one else. Inside the win branch you need a second if / else to see who won. Procedures → call checkWin (the one with a plug), call lockBoard. Text → join.
to afterMove
do set global moves to ( get global moves+1 )
ifcheckWin
then set StatusLabel.Text tojoinget global turn " wins!"
call lockBoardifget global turn="X"
then set global xWins to ( get global xWins+1 )
else set global oWins to ( get global oWins+1 )
set ScoreLabel.Text tojoin "X: " get global xWins " · O: " get global oWinselse ifget global moves=9
then set StatusLabel.Text to"Draw!"elseifget global turn="X"
then set global turn to"O"
else set global turn to"X"set StatusLabel.Text tojoinget global turn "'s turn"
Order matters. Win is checked first — a ninth move can win, and if you checked "moves = 9" first you'd call it a draw. The turn only swaps in the else, so the winner's letter is still in turn when you name them.
Test: X top row → "X wins!", board locks, score X: 1. New game → board clears, score stays. Fill a board with no line (X O X / X O O / O X X) → "Draw!". Tap a taken square mid-game → nothing.
"Draw" after a win on move 9? Your checks are in the wrong order. Winner named wrong? You swapped turn before naming them. Three blanks win at the start? Step 3 is missing ≠ "".
All five must-haves done. Bug log: find the draw board above and play it — did your app call it? Stretch: open the Any component drawer at the bottom of the Blocks list, use when any Button.Click with set Button.Text of component, and delete eight of your nine handlers. Same game, a third of the blocks.