TUTORIAL · BREAKOUT · STAGE 2 OF 8BETA
Stage 2 · The bat
A game loop and a bat you steer with Z and X. This is also where the game stops redrawing the whole screen every frame, which is the single biggest reason it runs at a playable speed.

Type these 15 lines into the previous stage. Or load the whole stage from the disc with CHAIN "STAGE2".
Text after :REM and indented continuation lines are Beeb-dev notes and layout: the build strips them, so they cost the BBC nothing. Typing into a real BBC, leave the notes out and join each continuation line onto the line above.
50 p%=152 :REM bat X, left edge: always a multiple of 4
:b%=160:y%=192 :REM where the ball will sit (it arrives in stage 3)
:d%=4 :REM 3 or more = full redraw of both pages next frame
:u%=b%:v%=y%:w%=p% :REM positions last frame
:aa%=b%:bb%=y%:cc%=p% :REM and the frame before: what the hidden page shows
60 REPEAT :REM one pass of the loop is one frame
:IF d%>=3 PROCegbegin :REM first frame: the rest of line 60 runs only now.
REM BEGIN clears the hidden page...
:PROCbatdraw(p%):PROCegpresent :REM ...draws the bat and shows it at vsync
:PROCegbegin :REM the same again, because the Master has two
:PROCbatdraw(p%) :REM screen pages and both must hold the picture
:PROCegpresent :REM before dirty frames can start
:d%=0 :REM from here on, repaint only what changes
:aa%=p%:u%=p%:bb%=192:v%=192 :REM restart the erase history (used from stage 3)
65 PROCegdirtybegin :REM map the hidden page in WITHOUT clearing it: it
REM still holds the frame drawn two frames ago, so everything that has
REM moved since then must be rubbed out and drawn again
75 z%=INKEY(-98) :REM Z held right now? Negative INKEY asks the
REM keyboard hardware directly: no buffer, no auto-repeat, keys combine
:x%=INKEY(-67) :REM X held?
:sp%=INKEY(-99) :REM SPACE held?
80 IF z% p%=p%-12 :REM left 12 pixels: three screen bytes
90 IF x% p%=p%+12 :REM right 12 pixels
100 IF p%<8 p%=8 :REM stop at the left wall
110 IF p%>288 p%=288 :REM and the right (288+16 = 304)
155 IF cc%<>p% PROCbatclear(cc%) :REM same for the bat, by direct screen stores
180 IF cc%<>p% :REM redraw the bat if it moved...
OR (bb%<=205 AND bb%+7>=202 :REM ...or if the ball's eraser (line 150)
AND aa%<=p%+15 AND aa%+7>=p%) PROCbatdraw(p%) :REM overlapped the bat's rows 202-205
185 aa%=u%:bb%=v%:cc%=w% :REM last frame's positions move back one: the
REM page drawn next frame last showed them
:u%=b%:v%=y%:w%=p% :REM and this frame's become last frame's
190 PROCegpresent :REM wait for vsync and show the page just drawn
200 UNTIL INKEY(-17) :REM until Q is held
500 DEF PROCbatclear(x%) :REM rub out the 16x4 bat at x% by direct stores
:bt%=&6E82+2*x% :REM screen byte for row 202 at x%: &3000 + 25*640 +
REM 2 (row 202 is line 2 of character row 25), plus 8 bytes per 4 pixels
:FOR q%=0 TO 3 :REM four 4-pixel columns make 16 pixels
:!bt%=0 :REM one 4-byte store clears rows 202-205 of a column
:bt%=bt%+8:NEXT:ENDPROC :REM next column is 8 bytes along
510 DEF PROCbatdraw(x%) :REM the same, drawing instead of clearing
:bt%=&6E82+2*x%
:FOR q%=0 TO 3
:!bt%=&FF0F0FFF :REM lowest byte first: &FF white for row 202, &0F red
REM for rows 203 and 204, &FF white for row 205
:bt%=bt%+8:NEXT:ENDPROC
The loop
Line 60 opens a REPEAT and line 200 closes it when Q is held. Inside, every frame does the same things in the same order: start a frame (line 65), read the keys, move things, rub out what moved, draw it in its new place, and show the page (line 190).
Keys that do not wait
75 z%=INKEY(-98) :REM Z held right now? Negative INKEY asks the
REM keyboard hardware directly: no buffer, no auto-repeat, keys combine
:x%=INKEY(-67) :REM X held?
:sp%=INKEY(-99) :REM SPACE held?
80 IF z% p%=p%-12 :REM left 12 pixels: three screen bytes
90 IF x% p%=p%+12 :REM right 12 pixels
100 IF p%<8 p%=8 :REM stop at the left wall
110 IF p%>288 p%=288 :REM and the right (288+16 = 304)
The title used INKEY(0), which reads the keyboard buffer one character at a time. The game uses negative INKEY instead: INKEY(-98) asks the keyboard hardware whether Z is held right now, and returns TRUE (−1) or FALSE (0) straight away, ignoring the buffer and auto-repeat. You can hold two keys at once, and holding X moves the bat every frame for as long as it is held.
| Key | Call | Action |
|---|---|---|
| Z | INKEY(-98) | bat left 12 pixels |
| X | INKEY(-67) | bat right 12 pixels |
| SPACE | INKEY(-99) | serve (from stage 3) |
| Q | INKEY(-17) | quit to MODE 7 |
The bat moves 12 pixels a frame and is clamped to 8–288. Both are multiples of 4, so the bat's X is always a multiple of 4, and that is what makes the bat blit below possible.
Two pages, and only what moved
Line 60 draws the first frame twice, once on each of the Master's two screen pages. After that, line 65 starts each frame with PROCegdirtybegin, which maps the hidden page in for drawing without clearing it. So the game has to rub out the bat where it used to be (line 155) and draw it where it is now (line 180), and nothing else gets touched.
The catch is that the hidden page was last drawn two frames ago, not one. Line 185 keeps two frames of history: w% is the bat's position last frame and cc% the frame before, which is the one the hidden page is showing. The dirty-rendering appendix goes through this frame by frame.
The bat blit
500 DEF PROCbatclear(x%) :REM rub out the 16x4 bat at x% by direct stores
:bt%=&6E82+2*x% :REM screen byte for row 202 at x%: &3000 + 25*640 +
REM 2 (row 202 is line 2 of character row 25), plus 8 bytes per 4 pixels
:FOR q%=0 TO 3 :REM four 4-pixel columns make 16 pixels
:!bt%=0 :REM one 4-byte store clears rows 202-205 of a column
:bt%=bt%+8:NEXT:ENDPROC :REM next column is 8 bytes along
510 DEF PROCbatdraw(x%) :REM the same, drawing instead of clearing
:bt%=&6E82+2*x%
:FOR q%=0 TO 3
:!bt%=&FF0F0FFF :REM lowest byte first: &FF white for row 202, &0F red
REM for rows 203 and 204, &FF white for row 205
:bt%=bt%+8:NEXT:ENDPROC
A ROM call costs 1.7 cs before it draws anything, so the bat skips EGFX and writes straight into screen memory. !bt%=&FF0F0FFF stores four bytes at once, one into each of the bat's four scanlines, which paints a 4×4 block of white over red over red over white. Four of those make a 16-pixel bat. Erasing stores zero instead. The bat blit appendix works out the address.
Line 180 also redraws the bat when the ball's old position overlapped it. There is no ball yet, but the variables are already set up in line 50, so the test is simply never true until stage 3.
The whole of stage 2, ready to type in: the EGFX helpers merged and the notes stripped (47 BASIC lines)
5 MODE 1:HIMEM=&2400
20 PROCeginit
40 PROCstartscreen
50 p%=152:b%=160:y%=192:d%=4:u%=b%:v%=y%:w%=p%:aa%=b%:bb%=y%:cc%=p%
60 REPEAT:IF d%>=3 PROCegbegin:PROCbatdraw(p%):PROCegpresent:PROCegbegin:PROCbatdraw(p%):PROCegpresent:d%=0:aa%=p%:u%=p%:bb%=192:v%=192
65 PROCegdirtybegin
75 z%=INKEY(-98):x%=INKEY(-67):sp%=INKEY(-99)
80 IF z% p%=p%-12
90 IF x% p%=p%+12
100 IF p%<8 p%=8
110 IF p%>288 p%=288
155 IF cc%<>p% PROCbatclear(cc%)
180 IF cc%<>p% OR (bb%<=205 AND bb%+7>=202 AND aa%<=p%+15 AND aa%+7>=p%) PROCbatdraw(p%)
185 aa%=u%:bb%=v%:cc%=w%:u%=b%:v%=y%:w%=p%
190 PROCegpresent
200 UNTIL INKEY(-17)
210 MODE 7:END
220 DEF PROCstartscreen
230 PROCegbegin:FOR y%=24 TO 88 STEP 8:PROCegline(40,y%,280,y%,(y% DIV 8) MOD 4):NEXT:PROCegtext("BRICKFALL",120,48):PROCegline(64,80,256,80,3)
240 PROCegtext("PRESS SPACE",112,176):PROCegpresent
250 REPEAT:UNTIL INKEY(0)=32:*FX 15
260 ENDPROC
500 DEF PROCbatclear(x%):bt%=&6E82+2*x%:FOR q%=0 TO 3:!bt%=0:bt%=bt%+8:NEXT:ENDPROC
510 DEF PROCbatdraw(x%):bt%=&6E82+2*x%:FOR q%=0 TO 3:!bt%=&FF0F0FFF:bt%=bt%+8:NEXT:ENDPROC
1000 DEF PROCeginit
1010 IF HIMEM>&2400 OR TOP>&2200 PRINT "EGFX MEM":END
1020 DIM egb% 63:LOCAL egi%
1025 FOR egi%=0 TO 63:egb%?egi%=0:NEXT
1040 egb%?0=64:egb%?1=8:egb%?2=69:egb%?3=71:egb%?4=1
1045 PROCegcall(0):ENDPROC
1050 DEF PROCegcall(egf%)
1070 egb%?5=egf%:egb%?7=0
1080 A%=&C0:X%=egb% MOD 256:Y%=egb% DIV 256:CALL &FFF1
1090 IF egb%?7<>&A5 PRINT "NO EGFX ROM":END
1095 IF egf%>=11 AND egf%<>22 AND egf%<>23 AND egb%?6<>0 PRINT "EG ERROR":END
1100 ENDPROC
1110 DEF PROCegxy(egx%,egy%)
1120 egb%?8=egx%:egb%?9=egx% DIV 256
1130 egb%?10=egy%:egb%?11=egy% DIV 256:ENDPROC
1140 DEF PROCegbegin:PROCegcall(1):ENDPROC
1144 DEF PROCegdirtybegin:egb%?8=255:PROCegcall(38):ENDPROC
1150 DEF PROCegpresent:PROCegcall(6):ENDPROC
1180 DEF PROCegtext(egs$,egx%,egy%)
1190 PROCegxy(egx%,egy%):$(egb%+22)=egs$+CHR$0:PROCegcall(7):ENDPROC
1260 DEF PROCegline(egx%,egy%,egex%,egey%,egc%)
1270 PROCegxy(egx%,egy%):egb%?13=egc%:egb%?16=egex% AND 255:egb%?17=(egex% AND &FFFF) DIV 256
1280 egb%?20=egey% AND 255:egb%?21=(egey% AND &FFFF) DIV 256:PROCegcall(10):ENDPROCTry it: add the cursor keys with INKEY(-26) (left) and INKEY(-122) (right). Then change line 185 to cc%=p% and watch the bat leave copies of itself on alternate frames.