Breakout

TUTORIAL · BREAKOUT · STAGE 3 OF 8BETA

Stage 3 · The ball

A ball that sits on the bat until you press SPACE, bounces off the walls and the bat, and comes back to the bat if you miss. Integer physics in a dozen lines: work out where the ball will be, test that position, then commit it.

The ball in flight above the bat on an empty playfield
Stage 3: ball and bat.

Type these 14 lines into the previous stage. Line 50 replaces a line you already have: type it again in full. Or load the whole stage from the disc with CHAIN "STAGE3".

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.

30 e%=4:f%=-4                                    :REM ball velocity in pixels a frame: right and up
50 p%=152                                        :REM bat X, left edge: always a multiple of 4
    :b%=160:y%=192                               :REM ball top-left, sitting on the bat
    :sv%=1                                       :REM 1 = ball waits on the bat for SPACE
    :d%=4                                        :REM 3 or more = full redraw of both pages next frame
    :u%=b%:v%=y%:w%=p%                           :REM ball and bat last frame
    :aa%=b%:bb%=y%:cc%=p%                        :REM and the frame before: what the hidden page shows
120 IF sv%=1 b%=p%+8:y%=192                      :REM waiting to serve: keep the ball on the bat
    :IF sp% sv%=0                                :REM SPACE launches it
130 IF sv%=0 PROCmoveball                        :REM physics only while the ball is in play
150 IF aa%<>b% OR bb%<>y%                        :REM if the ball has moved since this page last
     PROCegcompact(aa%,bb%,1,0,7,&87)            :REM drew it, rub it out there: frame 1 of the
    REM 8x8 pack at &87 in slot 7 is an opaque black square
181 IF sv%<>2 PROCegcompact(b%,y%,0,0,7,&87)     :REM draw the ball (frame 0),
    REM unless the game is over and it is hidden
300 DEF PROCmoveball                             :REM work out where the ball would go, test it, commit it
310 nx%=b%+e%:ny%=y%+f%                          :REM the proposed position
320 IF nx%<=8 nx%=8:e%=-e%                       :REM left wall: clamp and bounce
325 IF nx%>=288 nx%=288:e%=-e%                   :REM right wall
330 IF ny%<24 ny%=24:f%=-f%                      :REM ceiling, just below where the HUD will go
340 IF ny%>240 sv%=1:f%=-4                       :REM off the bottom: missed. Serve again...
    :nx%=p%+8:ny%=192                            :REM ...from the bat (no lives to lose yet)
350 IF ny%<=205 AND ny%+7>=202                   :REM overlapping the bat's rows 202-205...
     AND f%>0                                    :REM ...moving down...
     AND nx%+3>=p% AND nx%+3<p%+16               :REM ...and the ball's centre is over the bat
     ny%=194:f%=-f%                              :REM sit it on top and bounce
    :e%=e%+SGN(nx%+3-p%-8)                       :REM steer: one pixel a frame towards the side
    REM of the bat it struck
380 b%=nx%:y%=ny%                                :REM commit the position (a hit has already changed it)
    :ENDPROC

State

VariableMeaning
b%, y%ball top-left in pixels
e%, f%ball velocity: 4 pixels a frame to start (line 30)
sv%1 = ball resting on the bat, 0 = in play
nx%, ny%where the ball will be this frame, before it is accepted

While sv%=1 line 120 keeps the ball on the bat, and SPACE launches it. Line 130 only runs the physics while the ball is in play.

Moving the ball

300 DEF PROCmoveball                             :REM work out where the ball would go, test it, commit it
310 nx%=b%+e%:ny%=y%+f%                          :REM the proposed position
320 IF nx%<=8 nx%=8:e%=-e%                       :REM left wall: clamp and bounce
325 IF nx%>=288 nx%=288:e%=-e%                   :REM right wall
330 IF ny%<24 ny%=24:f%=-f%                      :REM ceiling, just below where the HUD will go
340 IF ny%>240 sv%=1:f%=-4                       :REM off the bottom: missed. Serve again...
    :nx%=p%+8:ny%=192                            :REM ...from the bat (no lives to lose yet)
350 IF ny%<=205 AND ny%+7>=202                   :REM overlapping the bat's rows 202-205...
     AND f%>0                                    :REM ...moving down...
     AND nx%+3>=p% AND nx%+3<p%+16               :REM ...and the ball's centre is over the bat
     ny%=194:f%=-f%                              :REM sit it on top and bounce
    :e%=e%+SGN(nx%+3-p%-8)                       :REM steer: one pixel a frame towards the side
    REM of the bat it struck
380 b%=nx%:y%=ny%                                :REM commit the position (a hit has already changed it)
    :ENDPROC

The new position goes into nx%/ny% first. The walls clamp it and reverse the matching velocity. Falling below Y=240 puts the ball back on the bat (stage 7 makes that cost a life). The bat test on line 350 only fires while the ball is moving down and overlapping the bat's scanlines 202–205, using the ball's centre (nx%+3) against the bat's 16 pixels. A hit reverses f% and nudges e% by one towards the side of the bat that was struck, which is the classic way of letting the player steer. Line 380 commits the position, after the bricks in stage 4 have had their say.

Drawing the ball

The ball is a proper masked sprite. Line 150 rubs out the old ball by drawing an opaque black 8×8 frame over it (frame 1 of the pack at &87), and line 181 draws frame 0 in the new position. Both come from the EGASSETS ROM in slot 7 through PROCegcompact, which names the slot and pack in each call, so there is nothing to load first. The companion ROM appendix has the details. As with the bat, the erase uses the position from two frames ago (aa%, bb%).

The whole of stage 3, ready to type in: the EGFX helpers merged and the notes stripped (62 BASIC lines)
5 MODE 1:HIMEM=&2400
20 PROCeginit
30 e%=4:f%=-4
40 PROCstartscreen
50 p%=152:b%=160:y%=192:sv%=1: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
120 IF sv%=1 b%=p%+8:y%=192:IF sp% sv%=0
130 IF sv%=0 PROCmoveball
150 IF aa%<>b% OR bb%<>y% PROCegcompact(aa%,bb%,1,0,7,&87)
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%)
181 IF sv%<>2 PROCegcompact(b%,y%,0,0,7,&87)
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
300 DEF PROCmoveball
310 nx%=b%+e%:ny%=y%+f%
320 IF nx%<=8 nx%=8:e%=-e%
325 IF nx%>=288 nx%=288:e%=-e%
330 IF ny%<24 ny%=24:f%=-f%
340 IF ny%>240 sv%=1:f%=-4:nx%=p%+8:ny%=192
350 IF ny%<=205 AND ny%+7>=202 AND f%>0 AND nx%+3>=p% AND nx%+3<p%+16 ny%=194:f%=-f%:e%=e%+SGN(nx%+3-p%-8)
380 b%=nx%:y%=ny%: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):ENDPROC
2110 DEF PROCegcompact(x%,y%,i%,f%,b%,p%)
2120 PROCegxy(x%,y%):egb%?12=i%:egb%?14=f%:egb%?16=b%:egb%?17=p%:PROCegcall(37):ENDPROC

Try it: clamp e% to ±8 after line 350: nothing stops a long rally from making the ball very fast. Or make a miss cost nothing but speed the ball up.

← Stage 2: The bat Stage 4: Bricks →