progress in tutorial day 6

This commit is contained in:
sejo 2021-09-10 19:12:37 -05:00
parent afaa50ff82
commit 20b1777fb2
2 changed files with 577 additions and 38 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -4,7 +4,7 @@
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 snippets of code, we cover strategies for drawing and controlling multi-tile sprites, checking collisions, and drawing with a font.
we base our discussion in a recreation of the classic pong game. besides using previous snippets of code, we cover strategies for drawing and controlling multi-tile sprites, and for checking collisions.
# general logic
@ -15,8 +15,6 @@ we will tackle the following elements in order:
* drawing the background
* drawing and movement of the paddles
* drawing and bouncing of the ball
* drawing and counting of the score
* clickable start button
# drawing the background: repeating a tile
@ -28,10 +26,12 @@ here we will generalize that procedure into a draw-tiles subroutine that draws a
@draw-tiles ( x* y* width* height* addr* -- )
```
a reminder that we are using the convention of adding an asterisk (*) after the name of a value to indicate its a short.
a reminder that we are using the convention of adding an asterisk (*) after the name of a value to indicate it's a short.
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.
however, if you are more interested in the programming for the game, feel free to jump to the next section with the discussion of the paddles :)
## setting up
let's start with the following program as a template. it includes the data for a 1bpp sprite consisting of diagonal lines.
@ -50,6 +50,7 @@ let's start with the following program as a template. it includes the data for a
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
@ -63,7 +64,7 @@ BRK
## repeating a tile in a row
what's a procedure we could follow to repeat a tile starting from x, and ending at a limit corresponding to x+width?
what's a procedure we could follow to repeat the drawing of a tile starting from x, and ending at a limit corresponding to x+width?
one way would be something like:
@ -85,20 +86,22 @@ the first step, drawing the tile in x would be:
;tile-background .Screen/addr DEO2 ( set tile address )
#0008 .Screen/x DEO2 ( set initial x )
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
```
adding 8 to x we know already:
adding 8 to x, we know already:
```
.Screen/x DEI2 #0008 ADD2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
```
and checking if x is less than the limit, jumping if it is, would be something like:
checking if x is less than the limit, jumping if it is, would be something like:
```
.Screen/x DEI2 #0108 LTH2 ,&loop JCN ( jump if x is less than the limit )
.Screen/x DEI2
#0108 ( the limit )
LTH2 ,&loop JCN ( jump if x is less than the limit )
```
integrating all of it, we would be able to get:
@ -108,7 +111,7 @@ integrating all of it, we would be able to get:
#0008 .Screen/x DEO2 ( set initial x )
&loop-x
#03 .Screen/sprite DEO ( draw sprite with color 3 and 0 )
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEO2 ( store new x )
#0108 LTH2 ,&loop-x JCN ( jump if x is less than the limit )
@ -132,9 +135,10 @@ when starting the subroutine, the width is at the top of the stack, followed by
we can use these two values to calculate the limit, that we can stash in the return stack.
one way of achieving that could be:
one way of achieving that, noting the state of the working stack after each instruction, could be:
```
( initial state: ws: x* width* )
OVR2 ( ws: x* width* x* )
ADD2 ( ws: x* limit* )
STH2 ( ws: x* / rs: limit* )
@ -143,6 +147,7 @@ STH2 ( ws: x* / rs: limit* )
another one:
```
( initial state: ws: x* width* )
ADD2k ( ws: x* width* limit* )
STH2 ( ws: x* width* / rs: limit* )
POP2 ( ws: x* / rs: limit* )
@ -158,6 +163,7 @@ our subroutine would then look as follows:
```
@draw-tiles-in-a-row ( x* width* -- )
( calculate and save limit )
OVR2 ( ws: x* width* x* )
ADD2 ( ws: x* limit* )
STH2 ( ws: x* / rs: limit* )
@ -193,6 +199,7 @@ the following shows our program in context, completely filling the first row of
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
@ -240,7 +247,7 @@ in the case of x, let's start at 0000 and have a width corresponding to the scre
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 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:
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 )
@ -293,7 +300,9 @@ we could do the following:
```
ROT2 ( ws: x* width* height* y* )
DUP2 ( ws: x* width* height* y* y* )
.Screen/y DEO2 ( set initial y, ws: x* width* height* y* )
( set initial y: )
.Screen/y DEO2 ( ws: x* width* height* y* )
( calculate and stash vertical limit )
ADD2 ( ws: x* width* limit-y* )
STH2 ( ws: x* width* / rs: limit-y* )
```
@ -332,9 +341,9 @@ in theory, the first part of our subroutine could look like:
;draw-tiles-in-a-row JSR2
```
the problem is that inside the loop, both STH2r instructions retrieve and consume from the return stack the values for x and width.
the problem is that inside the loop, both STH2r instructions retrieve and consume from the return stack the values for x and width. therefore, in the next iteration we wouldn't be able to use them again, as they would be lost.
we can think we could replace them with STH2kr:
we can think we could replace these instructions with STH2kr:
```
&loop-y
@ -347,13 +356,13 @@ but then we can't retrieve the width because the x is still at the top!
oh, many difficulties, but for the sake of the stack wrangling example, let's continue solving this (?)
how can we put the width at the top of the return stack?
how can we put the width at the top of the return stack? maybe with a swap:
```
SWP2r ( ws: x* / rs: limit-y* x* width* )
```
then we can then retrieve it and use it:
then we can then retrieve the width and use it:
```
STH2kr ( ws: x* width* / rs: limit-y* x* width* )
@ -471,7 +480,9 @@ then, we just store the next values in relative addresses:
,&x STR2
```
note that we go in reverse order. after these operations the stacks are empty.
note that we go in reverse order.
after these operations the stacks are empty.
we can then set the initial y and calculate the vertical limit:
@ -479,6 +490,7 @@ we can then set the initial y and calculate the vertical limit:
( set initial y )
,&y LDR2 DUP2 ( ws: y* y* )
.Screen/y DEO2 ( ws: y* )
( calculate limit-y )
,&height LDR2 ( ws: y* height* )
ADD2 ( ws: limit-y* )
@ -506,7 +518,7 @@ our loop now would look as follows:
and that's it!
compare this with the "concrete" version we developed above, it's very similar!
compare this with the "concrete" version we developed above, it's very similar in its structure!
the complete subroutine would look like the following:
@ -547,7 +559,7 @@ the complete subroutine would look like the following:
RTN
( variables )
&height $2 &width $2 &y $2 &x $2 &limit-y $2
&height $2 &width $2 &y $2 &x $2 &limit-y $2
```
as i said before, we can find here some opportunities for optimization.
@ -558,7 +570,7 @@ 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, super optimized but probably unreadable code (write-only code, they say), or something in the middle.
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.
## draw-background
@ -633,7 +645,7 @@ one can get these numbers by reading the hexadecimal notation in nasu at the top
$ hexdump -C pong.chr
```
i drew the sprite using the blending mode 81, but i will instead use 85 to have color 0 being transparent.
i drew the sprite using the blending mode 85 as indicated by nasu, but i will change it to c5 to draw it in the foreground.
### paddle drawing subroutine
@ -730,10 +742,8 @@ additionally, i'll save the color in the return stack:
( draw tile 5 )
;paddle/tile5 .Screen/addr DEO2
STHkr .Screen/sprite DEO
( clear return stack )
POPr
( get and don't keep color from return stack: )
STHr .Screen/sprite DEO
RTN
```
@ -747,7 +757,13 @@ now we can call it in e.g. the following way and get our paddle drawn:
=> ./img/screenshot_uxn-pong-paddle.png screenshot of the paddle drawn over the background
it would be up to discussion if there are more efficient ways of drawing it.
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:
```
@draw-sprite ( x* y* width height addr* color )
```
it could work similar to the draw-tiles subroutine we created above. it could be a good exercise for you to try, but in this case i'll stay with the manual approach.
the good thing of this process being in a subroutine is that we can "forget" about its inner working and just use it :)
@ -786,7 +802,7 @@ finally, let's bring back our HALF2 macro:
### initialize positions
we can then initialize their positions.
we can then initialize the positions of the paddles.
for the left x, we can just assign a constant value:
@ -805,15 +821,15 @@ MARGIN SUB2 PADDLE-WIDTH SUB2
for centering the y coordinates we can subtract the paddle height from the screen height, and then divide over two:
```
.Screen/height DEI2 PADDLE-HEIGHT SUB2
HALF2 DUP2
.Screen/height DEI2 PADDLE-HEIGHT SUB2 HALF2
DUP2
.left/y STZ2
.right/y STZ2
```
### draw paddles
in order to draw each paddle, we could do the following inside our screen vector:
in order to draw each paddle, we could do the following inside our on-frame screen vector:
```
( draw paddles )
@ -853,6 +869,7 @@ omitting the definition of the subroutines, and just to clarify, right now our p
( main program )
|0100
@setup
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
@ -886,23 +903,545 @@ BRK
## paddle movement
for the paddle movement we can go back to previous examples of moving a sprite. the process we have followed is:
* clear sprite from current position
* update position
* draw sprite in new position
### clear or draw sprite
we have already the process for drawing our paddles:
```
( draw paddles )
.left/x LDZ2 .left/y LDZ2 PADDLE-COLOR ;draw-paddle JSR2
.right/x LDZ2 .right/y LDZ2 PADDLE-COLOR ;draw-paddle JSR2
```
in order to clear them, we can do the same but using a sprite byte corresponding to clear the tile in the foreground:
```
( clear paddles )
.left/x LDZ2 .left/y LDZ2 CLEAR-COLOR ;draw-paddle JSR2
.right/x LDZ2 .right/y LDZ2 CLEAR-COLOR ;draw-paddle JSR2
```
where CLEAR-COLOR in this case would be:
```
%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.
now we are able to move our paddles!
```
@on-frame ( -> )
( clear paddles )
.left/x LDZ2 .left/y LDZ2 CLEAR-COLOR ;draw-paddle JSR2
.right/x LDZ2 .right/y LDZ2 CLEAR-COLOR ;draw-paddle JSR2
( update paddles )
;update-paddles JSR2
( draw paddles )
.left/x LDZ2 .left/y LDZ2 PADDLE-COLOR ;draw-paddle JSR2
.right/x LDZ2 .right/y LDZ2 PADDLE-COLOR ;draw-paddle JSR2
BRK
```
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 :)
# the ball
now let's get the ball rolling!
here we'll work again with a multi-tile sprite drawn relative to x and y variables for its top left corner.
additionally, we'll use this section to talk about strategies for collision detection, with the walls and the paddles.
## drawing the ball
## ball movement and collisions
i used nasu to draw a ball composed of 2x2 2bpp tiles, ordered in the following way:
# the score
```
0 1
2 3
```
## counting
here's its data:
## drawing
```
@ball-sprite
&tile0 [ 03 0f 1f 39 70 70 f9 ff 00 00 00 06 0f 0f 06 00 ]
&tile1 [ c0 f0 f8 fc fe fe ff ff 00 00 00 00 08 0c 06 06 ]
&tile2 [ ff ff 7f 7f 3f 1f 0f 03 00 00 00 00 18 0f 01 00 ]
&tile3 [ ff ff fe fe fc f8 f0 c0 06 06 0c 1c 38 f0 c0 00 ]
```
# start button
we can define a couple of macros to refer to its parameters:
## drawing the start button
```
%BALL-SIZE { #0010 } ( 2 tiles per side )
%BALL-COLOR { #c5 }
```
## checking for mouse position
### 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:
```
( inside setup )
( initialize ball )
.Screen/width DEI2 BALL-SIZE SUB2
HALF2
.ball/x STZ2
.Screen/height DEI2 BALL-SIZE SUB2
HALF2
.ball/y STZ2
```
and then we can use them in our subroutine.
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:
```
@ball [ &x $2 &y $2 &speed-x $2 &speed-y $2 ]
```
### different directions
if, for example, we initialize speed-x with 1:
```
#0001 .ball/speed-x STZ2
```
we can make the ball move to the right by doing:
```
.ball/speed-x LDZ2 ( get speed-x )
.ball/x LDZ2 ( get x )
ADD2 ( add them together )
.ball/x STZ2 ( store new x )
```
in order to move to the left, we might think that we should replace ADD2 with SUB2. and yes, we could do that.
but, to leave our code as it is now: is there a value of speed-x that will make x get smaller when adding them together?
in other contexts, one might say, "-1"! but we haven't used negative signs here in uxn; we can't.
then, is there a positive value of speed-x that will make x get smaller when adding them together?
normally we could think there isn't, but here we are constrained by 8 or 16 bits. and what does that imply?
for example, if we have the number ffff (16 bits, all are ones), and we add 0001, what do we get?
```
1111 1111 1111 1111
+ 0000 0000 0000 0001
---------------------
1 0000 0000 0000 0000
```
it makes sense, but the 1 at the left sits outside the 16 bits; in other contexts it would be called the carry bit.
in uxn, the result of adding ffff and 0001 is 0000: we would say we are overflowing the 16 bits.
let's look at it the other way around: if we have 1, and we add ffff, we get 0, that is 1 less than 1.
if we have 2, and we add ffff:
```
0000 0000 0000 0010
+ 1111 1111 1111 1111
--------------------
1 0000 0000 0000 0001
```
we get 1, that is 1 less than 2.
in general, if we add ffff to a 16 bits number, we'll get a value that is 1 less than itself.
we can think then that ffff is like a "-1"!
to get other "negative numbers", let's observe the following: if we subtract 1 from ffff, we get fffe. what happens if we add it to 2?
```
0000 0000 0000 0010: 0002
+ 1111 1111 1111 1110: fffe
---------------------
1 0000 0000 0000 0000: 0000
```
we get 0! fffe works effectively as "-2"!
we could continue in that way getting more and more "negative" numbers that works thanks to the constrained size of computer memory.
going back to our code, if we initialize our speed with
```
#ffff .ball/speed-x STZ2
```
and then we use exactly the same code to update the position:
```
.ball/speed-x LDZ2 ( get speed-x )
.ball/x LDZ2 ( get x )
ADD2 ( add them together )
.ball/x STZ2 ( store new x )
```
we will have decreased the position by 1!
it might make sense to set these values as macros:
```
%BALL-POSITIVE-SPEED { #0001 } ( +1 )
%BALL-NEGATIVE-SPEED { #ffff } ( -1 )
```
### implemeting the ball movement
based on what we just discussed, we can start our update-ball subroutine with the following:
```
@update-ball ( -- )
( get speed-x and x )
.ball/speed-x LDZ2 .ball/x LDZ2 ( get x )
ADD2 ( add them together )
.ball/x STZ2 ( store new x )
( get speed-y and y )
.ball/speed-y LDZ2 .ball/y LDZ2 ( get y )
ADD2 ( add them together )
.ball/y STZ2 ( store new y )
RTN
```
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:
* check if the ball is hitting either wall
* if it is, then invert the speed
or another one:
* check if the ball is hitting the top wall
* if it is, then set a positive speed
* 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!
### top wall
if the ball is hitting the top wall, it means its y coordinate is less than the y coordinate of the wall.
considering that there's a margin at the top, we can do this check as follows:
```
( inside update-ball )
&check-top-wall
.ball/y LDZ2
WALL-MARGIN
LTH2 ( is ball-y less than the margin? )
,&set-positive-speed JCN
&check-bottom-wall JMP
&set-positive-speed
BALL-POSITIVE-SPEED .ball/speed-y STZ2
&check-bottom-wall
```
### bottom wall
here the procedure would be similar, but considering the size of the ball.
we'd like to know if the y coordinate, plus the size of the ball, is greater than the y coordinate of the bottom wall.
the y coordinate of the bottom wall would be the height of the screen, less the wall margin:
```
(inside update ball )
&check-bottom-wall
.ball/y LDZ2 BALL-SIZE ADD2 ( y + ball size )
.Screen/height DEI2 WALL-MARGIN SUB2 ( height - margin )
GTH2 ( is the ball y greater than the wall y? )
,&set-negative-speed JCN
,&continue JMP
&set-negative-speed
BALL-NEGATIVE-SPEED .ball/speed-y STZ2
&continue
```
### update-ball code so far
our update-ball subroutine looks like the following right now:
```
@update-ball ( -- )
( get speed-x and x )
.ball/speed-x LDZ2 .ball/x LDZ2 ( get x )
ADD2 ( add them together )
.ball/x STZ2 ( store new x )
( get speed-y and y )
.ball/speed-y LDZ2 .ball/y LDZ2 ( get y )
ADD2 ( add them together )
.ball/y STZ2 ( store new y )
( check collisions with walls )
&check-top-wall
.ball/y LDZ2
WALL-MARGIN
LTH2 ( is ball-y less than the margin? )
,&set-positive-speed JCN
,&check-bottom-wall JMP
&set-positive-speed
BALL-POSITIVE-SPEED .ball/speed-y STZ2
&check-bottom-wall
.ball/y LDZ2 BALL-SIZE ADD2 ( y + ball size )
.Screen/height DEI2 WALL-MARGIN SUB2 ( height - margin )
GTH2
,&set-negative-speed JCN
,&continue JMP
&set-negative-speed
BALL-NEGATIVE-SPEED .ball/speed-y STZ2
&continue
RTN
```
you can test it using different initial speed-y inside setup. the ball should be bouncing now! :)
## collisions with the paddles
let's work with what we just did, and adapt it for bouncing with the paddles!
### left paddle
first of all, we can identify if the x coordinate of the ball would be hitting the left paddle.
for this, we can check if x is less than the sum of the margin and paddle width.
```
( inside update-ball )
&check-left-paddle
.ball/x LDZ2
MARGIN PADDLE-WIDTH ADD2
LTH2 ( is ball-x less than the margin + paddle-width? )
,&x-in-left JCN
&check-right-paddle JMP
&x-in-left
( ... )
&check-right-paddle
```
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.
### right paddle
### borders
# the complete program
# more possibilities
here are some extra possibilities for you to practice and try to implement:
* change color of the ball and/or the paddle when they are colliding
* change the direction or type of bounce depending on the part of the paddle that hits the ball
* start the game when a button is pressed
* "random" initial speed of the ball
* variable speed of the game
# coming soon: day 7