this is the sixth section of the <(uxn tutorial)>! here we talk about how we can integrate everything that we have covered in order to create more complex subroutines and programs for the varvara computer.
we base our discussion in a recreation of the classic pong game.
besides using previous strategies and snippets of code, we cover strategies for drawing and controlling multi-tile sprites, and for checking collisions.
even though pong might look simple and easy to program, once we analyze it we might find there are several aspects to account for. fortunately, most of them can be divided as different subroutines that we are able to discuss separately.
we will tackle the following elements in order:
* drawing the background
* drawing and movement of the paddles
* drawing and bouncing of the ball
# drawing the background: repeating a tile
in the previous section of the tutorial we discussed a way of creating a loop in order to repeat a 1bpp tile multiple times in a row.
here we will generalize that procedure into a draw-tiles subroutine that draws a rectangle filled with a given tile. it will receive the x,y coordinates of the top left corner of the rectangle, its width and height in pixels, and the address of the tile:
we will detail how to get to two versions of this subroutine, one that relies on heavy stack wrangling, and other one that uses variables. this in order to compare both approaches and give us clues of the procedures that we will develop afterwards today.
remember that we are showing the top of the stacks at their right.
after these steps, the initial x is at the top of the stack, so we can send it directly to the screen.
the last change that we would need is to replace our hardcoded limit with a STH2kr instruction (copy limit from the return stack into the working stack), and end our routine with a POP2r (remove limit from return stack).
;tile-background .Screen/addr DEO2 ( set tile address )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
BRK
@draw-tiles-in-a-row ( x* width* -- )
OVR2 ( ws: x* limit* x* )
ADD2 ( ws: x* limit* )
STH2 ( ws: x* / rs: limit* )
.Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
STH2kr ( copy limit from rs into ws )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
POP2r ( pop limit from rs )
RTN
@tile-background 1122 4488 1122 4488
```
## repeating a row
similar to what we just did: what's a procedure we could follow to repeat vertically a row starting from y, and ending at a limit corresponding to y+height?
following the same strategy, we could do
* draw row in y
* add 8 (the size of the tile) to y
* is y less than the limit? if it is, repeat procedure, otherwise end
### concrete version
let's use the same numbers as before, assuming that our initial y is 0008, our height is 0100, and therefore our limit would be 0108.
in the case of x, let's start at 0000 and have a width corresponding to the screen width.
as the address wouldn't change in the process, we can set it up at the top and forget about it.
the following code is based on the previous x loop, but it now draws a row in a given y coordinate, adds 8 to it and then checks if it's less than the limit:
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/y ( set initial y )
&loop-y
( prepare and draw row )
#0000 ( initial x )
.Screen/width DEI2 ( get screen width )
;draw-tiles-in-a-row JSR2
.Screen/y DEI2 #0008 ADD2 DUP2 ( add 8 to y )
.Screen/y DEO2 ( store new y )
#0108 ( put limit in top of the stack )
LTH2 ,&loop-y JCN ( jump if x is less than the limit )
```
### abstract version
now, before jumping right into emulating the solution for drawing the row, let's note that in this case it is not that easy.
why? because the idea for our draw-tiles subroutine is that it should be able to receive the initial x and the width of the rectangle, and right now these values are hardcoded inside the loop.
this should be the signature for our subroutine:
```
@draw-tiles ( x* y* width* height* addr* -- )
```
we can approach this problem either with some "stack wrangling", or with "variables".
also note that we will do that because we are trying to get to a generalized subroutine.
if we just wanted to cover all the screen with a sprite, we have all the required code already: we would only need to adapt the vertical limit of the loop to correspond to the height of the screen, and that would be it!
we could then just jump to the section regarding the paddles. however, what follows can be interesting as a way of looking at possible approach to write more complex uxntal code :)
the problem is that inside the loop, both STH2r instructions retrieve and consume the values for x and width from the return stack. therefore, in the next iteration we wouldn't be able to use them again, as they would be lost.
but ah, before doing the comparison and jumping, we should rearrange the return stack so that it corresponds to the ordering we had at the beginning of the loop:
as i said before, we can find here some opportunities for optimization.
maybe the vertical limit can be stashed in the return stack like in the draw-tiles-in-a-row loop, or maybe the variable for the height and for the initial y are not needed.
i'll let you figure them out :)
note that this subroutine as-is, requires 24 bytes of program memory more than the stack wrangling version.
in our case that's not much of a problem, but it's a good way of evaluating our priorities: super readable but probably inefficient code (like this last subroutine), super optimized but probably unreadable code (write-only code, they say), or something in the middle.
in practice, that might be a little bit inconvenient, because the sprite byte that determines the layer and color of our tile was left hardcoded inside our subroutine: if we reuse it, we'll end up drawing with the same color and in the same layer as the background texture!
&tile5 [ ff ff ff ff ff ff fe fc 06 06 06 06 06 1e 3c 00 ]
```
one can get these numbers by reading the hexadecimal notation in nasu at the top right, first the left column and then the right column, or by using a tool like hexdump with the corresponding chr file:
let's build a subroutine that draws the 6 tiles of the paddle in the corresponding order.
we could have the subroutine receiving as argument the x and y position of its top left corner:
```
@draw-paddle ( x* y* -- )
```
but let's add the color byte as well:
```
@draw-paddle ( x* y* color -- )
```
on one hand this would allow us to change colors when e.g. hitting the ball, but more importantly this will allow us to clear the paddle before moving it, as we have done in previous days.
in principle the subroutine should be straightforward: we have to set the x and y coordinates of each of the tiles, relative to the given x and y coordinates, and draw them with the given color.
there are many ways to do it, depending on taste.
we could for example draw the tiles in the following order, with the following operations:
* draw tile 0, then add 8 to x
* draw tile 1, then add 8 to y
* draw tile 3, then subtract 8 to x
* draw tile 2, then add 8 to y
* draw tile 4, then add 8 to x
* draw tile 5
or we could do it more traditionally:
* draw tile 0, then add 8 to x
* draw tile 1, then subtract 8 to x, and add 8 to y
* draw tile 2, then add 8 to x
* draw tile 3, then subtract 8 to x, and add 8 to y
* draw tile 4, then add 8 to x
* draw tile 5
instead of subtracting we could recover x from the return stack, or from a relative variable.
a possible advantage of going in order is that we can increment the address of the sprite by 10 (16 in decimal) to get to the address of the next tile. for this, and/or for the changes in coordinates, we can take advantage of the screen auto byte.
it would be up to discussion if there are more efficient ways of drawing it. for example, we could have a generalized draw-sprite that receives the initial address of a set of tiles, and the width and height in terms of number of tiles:
the good thing of this process being in a subroutine is that we can "forget" about its inner working and just use it :)
## variables and constants for the paddles
let's reserve some space in the zero page for the x and y coordinates of each paddle.
```
( zero page )
|0000
@left [ &x $2 &y $2 ]
@right [ &x $2 &y $2 ]
```
we mentioned early that the x coordinate is constant; however, if we make it a variable then we can dinamically assign the x position of the paddles (especially the right one) depending on the size of the screen.
%CLEAR-COLOR { #40 } ( clear sprite from the foreground )
```
this is a good reminder to review the tables for the sprite bytes in <(uxn tutorial day 2)>!
### update position
to update the position of our paddles, we can resort to the hello-moving-sprite.tal example from <(uxn tutorial day 4)>.
we can use up and down arrows to change the position of the left paddle, and ctrl and alt (A and B) buttons to change the position of the right paddle.
we can have a macro to define the paddle speed, i.e. how much we'll add or subtract when moving each frame:
```
%PADDLE-SPEED { #0001 }
```
all of this can go inside its own subroutine for readability purposes:
```
@update-paddles ( -- )
&left
( left paddle: up and down buttons )
.Controller/button DEI
DUP #10 AND ( check bit for up )
,&left-up JCN
DUP #20 AND ( check bit for down )
,&left-down JCN
,&right JMP ( jump if neither of them were pressed )
&left-up
.left/y LDZ2 PADDLE-SPEED SUB2 .left/y STZ2
,&right JMP
&left-down
.left/y LDZ2 PADDLE-SPEED ADD2 .left/y STZ2
,&right JMP
&right
( right paddle: ctrl/A and alt/B buttons )
DUP #01 AND ( check bit for A )
,&right-up JCN
DUP #02 AND ( check bit for B )
,&right-down JCN
,&end JMP ( jump if neither of them were pressed )
&right-up
.right/y LDZ2 PADDLE-SPEED SUB2 .right/y STZ2
,&end JMP
&right-down
.right/y LDZ2 PADDLE-SPEED ADD2 .right/y STZ2
&end
POP ( pop duplicate value of button )
RTN
```
### complete procedure
integrating everything, our on-frame subroutine would look like the following.
note that we are able to move the paddles beyond the limits of the screen.
i invite you to modify the update-paddles subroutine so that there's a limit in the paddles movement. in <(uxn tutorial day 4)> we discussed some possible strategies for achieving it :)
&tile3 [ ff ff fe fe fc f8 f0 c0 06 06 0c 1c 38 f0 c0 00 ]
```
we can define a couple of macros to refer to its parameters:
```
%BALL-SIZE { #0010 } ( 2 tiles per side )
%BALL-COLOR { #c5 }
```
### subroutine for drawing the ball
as we'll be drawing a single ball, we can embed in its drawing subroutine the use of its own zero-page variables for the coordinates, instead of getting them as arguments in the stack.
in our zero page we can define them:
```
@ball [ &x $2 &y $2 ]
```
in our setup subroutine we can assign values to them, e.g. at the middle of the screen:
let's have the subroutine receive the color as an argument, so that we can clear the ball like we do with the paddles:
```
@draw-ball ( color -- )
( set initial x and y )
.ball/x LDZ2 .Screen/x DEO2
.ball/y LDZ2 .Screen/y DEO2
( draw tile 0 )
;ball-sprite/tile0 .Screen/addr DEO2
( color byte was in the stack already )
DUP .Screen/sprite DEO
( move right )
.Screen/x DEI2 #0008 ADD2 .Screen/x DEO2
( draw tile 1 )
;ball-sprite/tile1 .Screen/addr DEO2
DUP .Screen/sprite DEO
( move down )
.Screen/y DEI2 #0008 ADD2 .Screen/y DEO2
( draw tile 3 )
;ball-sprite/tile3 .Screen/addr DEO2
DUP .Screen/sprite DEO
( move left )
.Screen/x DEI2 #0008 SUB2 .Screen/x DEO2
( draw tile 2 )
;ball-sprite/tile2 .Screen/addr DEO2
.Screen/sprite DEO
RTN
```
in order to draw it, we'd just need to do:
```
( draw ball )
BALL-COLOR ;draw-ball JSR2
```
=> ./img/screenshot_uxn-pong-paddles-and-ball.png screenshot of the screen showing the paddles in their horizontal position but at different heights, and the ball completely centered in the screen.
## ball movement
for the movement of the ball, we'll follow the same structure as before:
* clear the ball in the current position
* update its position
* draw the ball in the new position
it would look something like the following, and could sit along the equivalent procedures for the paddles inside the on-frame subroutine:
```
( inside on-frame )
( clear ball )
CLEAR-COLOR ;draw-ball JSR2
( update ball )
;update-ball JSR2
( draw ball )
BALL-COLOR ;draw-ball JSR2
```
now let's discuss how to build that update-ball subroutine :)
### accounting for the change of direction
besides our variables for keeping track of the position of the ball, we should be able to keep track of the per-axis direction it is moving.
one approach could be to have a flag for each x and y, that indicate if we should increment or decrement them.
another approach could be to have a speed variable for each x and y, that gets changed according to the direction we want the ball to go.
we will use this latter approach as it will help us discuss some perks of unsigned integer arithmetic!
we include these variables in our zero page, complementing the x and y we had already:
if we complement our initilization with the speeds, we'll be able to see the ball moving:
```
( inside setup )
( initialize ball )
.Screen/width DEI2 BALL-SIZE SUB2
HALF2 .ball/x STZ2
.Screen/height DEI2 BALL-SIZE SUB2
HALF2 .ball/y STZ2
( initialize ball speed )
BALL-POSITIVE-SPEED .ball/speed-x STZ2
BALL-POSITIVE-SPEED .ball/speed-y STZ2
```
## collisions with the walls
we have defined the general way of updating the position of the ball given its speed in x and y.
now let's see how to implement the classical "bounce"!
first, let's start with the walls at the top and bottom of the screen. let's consider that there is some margin between the actual border of the screen, and the walls:
```
%WALL-MARGIN { #0010 }
```
in order to perform these collision detections, we'd need to check the y coordinate of the ball only.
as always, there are many ways to achieve this. one could be:
* if not, check if the ball is hitting the bottom wall
* if it is, then set a negative speed
in other languages it is probably easier to write the first one, but here we'll stick with the second: for clarity and because of the way we'll have to do the checks.
in any case, it can be a good exercise for you to try to figure out how to "invert" the speed with a single arithmetic or logic operation!
once we know that's true, we can see if the ball is within vertical reach of the paddle; the y coordinate of the ball has to be within a certain range relative to the y coordinate of the ball.
specifically, if we want the ball to be able to bounce when any part of the ball hits any part of the paddle, the y coordinate of the ball has to be:
* greater than the paddle y coordinate minus the ball height AND
* less than the paddle y coordinate plus the paddle height
if those two conditions are met, then we can set a positive speed for x:
```
( inside update-ball )
&x-in-left
.ball/y LDZ2 DUP2
.left/y LDZ2 BALL-SIZE SUB2 GTH2 ( first flag ) STH
.left/y LDZ2 PADDLE-HEIGHT ADD2 LTH2 ( second flag )
STHr ( retrieve first flag )
AND ( AND the two flags together )
,&bounce-left JCN
```
where bounce-left would be:
```
&bounce-left
BALL-POSITIVE-SPEED .ball/speed-x STZ2
,&finish JMP
```
and what happens if both conditions are not met at the same time?
we can let the ball keep moving, but checking that it hasn't crossed the left wall, by comparing with 0000.
the whole x-in-left code would end up looking like:
```
( inside update-ball )
&x-in-left
.ball/y LDZ2 DUP2
.left/y LDZ2 BALL-SIZE SUB2 GTH2 ( first flag ) STH
.left/y LDZ2 PADDLE-HEIGHT ADD2 LTH2 ( second flag )
STHr ( retrieve first flag )
AND ( AND the two flags together )
,&bounce-left JCN
.ball/x LDZ2 #0000 NEQ2 ( has it reached the wall ? )
,&finish JCN
&reset-left
( here you can add a point to the left paddle )
;reset JSR2
,&finish JMP
&bounce-left
BALL-POSITIVE-SPEED .ball/speed-x STZ2
,&finish JMP
&check-right-paddle
```
finish would be a label at the end of the subroutine, and reset is a subroutine that we will discuss later.
this approach of comparing with 0000 is the easiest, but keep in mind that it might not work if you change the ball speed: it could happen that it crosses the wall but with an x coordinate that is never equal to 0.
we can't really check if the x coordinate is less than 0, because as we discussed above, that would actually be a number close to ffff.
if we checked for the x coordinate being less than ffff, then every possible value would turn on the comparison flag!
for the right paddle we will do the same as above, but changing the comparisons relative to the x coordinate of the ball: we will use the screen width as a reference for the right wall, and from there we'll subtract the margin and widths.
it would be interesting to have some mechanism to also change the speed: maybe based on the framecount, in the position of the paddles, or whatever else you choose.
=> ./img/screencap_uxn-pong.gif animated gif showing pong in action: the paddles move, the ball bounces in the top and bottom walls and in the paddles, and the ball restarts from the middle when the ball hits any side.