Jump to content
IGNORED

A Game of Ball Explanations


Alkadian

Recommended Posts

Hi,

 

I am just going through the game logic of "A Game of Ball" by following the code on Oscar's book (page 32-34). With reference to the code below:

 

restart_ball: PROCEDURE

d = RAND % 8

ox = 80

oy = 48

ball = 0

GOSUB get_angle

END

 

I understand that d (direction?) contains a random number between 0 and 8 (RAND gives you a random number between 0 and 255 and then modulo 8). ox and oy contain the initial position of the ball, the sprite ball is disabled (until you press one of the side-buttons). Then it jumps to the subroutine get_angle:

 

'

' Get angle per paddle position

' Variable d contains the hit position.

' Returns dx and dy with new direction.

'

get_angle: PROCEDURE

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

IF d = 1 THEN #dy = -2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 2 THEN #dy = -1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 3 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 4 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 5 THEN #dy = 1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 6 THEN #dy = 2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 7 THEN #dy = 2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

END

 

This is where I am getting confused, as I don't understand why the variable d can only be from 0 to 7. Does that mean that we can only have 8 directions in total? Could you please explain the meaning of:

 

#dx = -2,-1,0,1,2 (2 pixels to the left, 1 pixel to left, neutral, 1 pixel to the right, 2 pixels to the right and based on these values we know the angle/direction? d=#dy/#dx)

#dy = -2,-1,0,1,2 (2 pixels up, 1 pixel up, neutral, 1 pixel down, 2 pixels  down and based on these values we know the angle/direction? d=#dx/#dx)

 

Also, let's say that we are in the scenario d=7 with #dy = 2 and #dx = -1. Then when we jump back to the code:

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

#dx  and #dy get both set to -2 overriding the previous values set in the get_angle PROCEDURE.

 

Sorry for my confusion but I really want to get these things right.

 

Thanks a lot!

 

Edited by Alkadian
Added another query
Link to comment
Share on other sites

4 hours ago, Alkadian said:

Hi,

 

I am just going through the game logic of "A Game of Ball" by following the code on Oscar's book (page 32-34). With reference to the code below:

 

restart_ball: PROCEDURE

d = RAND % 8

ox = 80

oy = 48

ball = 0

GOSUB get_angle

END

 

I understand that d (direction?) contains a random number between 0 and 8 (RAND gives you a random number between 0 and 255 and then modulo 8).

 

With modulo 8, you get a value from zero to seven — but, of course, that’s what you meant. ;)
 

In either case, you have a range of eight values in total.

 

4 hours ago, Alkadian said:

 

This is where I am getting confused, as I don't understand why the variable d can only be from 0 to 7. Does that mean that we can only have 8 directions in total?


Yes.  That’s a design choice, so perhaps there was a reason behind it, but it means that the ball can only go in eight directions — the four cardinal ones, and the four perfect diagonals, at 45 degrees each.

 

4 hours ago, Alkadian said:

Could you please explain the meaning of:

 

#dx = -2,-1,0,1,2 (2 pixels to the left, 1 pixel to left, neutral, 1 pixel to the right, 2 pixels to the right and based on these values we know the angle/direction? d=#dy/#dx)

#dy = -2,-1,0,1,2 (2 pixels up, 1 pixel up, neutral, 1 pixel down, 2 pixels  down and based on these values we know the angle/direction? d=#dx/#dx)

 

Let us graph these points.  In the illustration below, the ball is in position “X,” and there are eight directions in which it can move:

  • North
  • North-East
  • East
  • South-East
  • South
  • South-West
  • West
  • North-West

 

- - - -   + + + +
4 3 2 1 0 1 2 3 4
. . . . . . . . . -4
. . . . . . . . . -3
. . . . # . . . . -2
. . . # . # . . . -1
. . # . X . # . .  0
. . . # . # . . . +1
. . . . # . . . . +2
. . . . . . . . . +3
. . . . . . . . . +4


To move North, we need to move up two points along the Y axis, and not move at all on the X axis.

 

Likewise, to move North-East, we need to move 1 point right in the X axis, and 1 point up in the Y axis.

 

The same sort of displacement over the axes applies similarly to the other directions:  we either move 0, 1, or 2 pixels in one or the other direction across one or both axes.


Moving right and down is done by adding to the current position.  Conversely, moving left and up is done by subtracting from it.

 

4 hours ago, Alkadian said:

Also, let's say that we are in the scenario d=7 with #dy = 2 and #dx = -1. Then when we jump back to the code:

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

#dx  and #dy get both set to -2 overriding the previous values set in the get_angle PROCEDURE.


From the code snippet you provided, it is not clear to me if the return value from the get_angle() procedure is used at all before being reset.

 

Also, I see that there is a call to the restart_ball() procedure, but it is also not clear to me what transpires in there.


It all depends on what happens in between the call to get_angle() and the code that resets them to -2.

 

Unfortunately, I do not have the book at hand at the moment, so I can’t review it.

 

4 hours ago, Alkadian said:

Sorry for my confusion but I really want to get these things right.

 

Thanks a lot!

 


No worries.  I hope the above explanation at least helps a little.

 

     dZ.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

34 minutes ago, DZ-Jay said:

To move North, we need to move up two points along the Y axis, and not move at all on the X axis.

 

Likewise, to move North-East, we need to move 1 point right in the X axis, and 1 point up in the Y axis.

 

The same sort of displacement over the axes applies similarly to the other directions:  we either move 0, 1, or 2 pixels in one or the other direction across one or both axes.

@DZ-Jay,

 

Many thanks as usual for your very clear explanations. Very much appreciated. Would you please help me further with the above? Does that mean that I could also move 1 pixel along the Y axis instead of 2 pixels to move North like when we move to North-East? 

34 minutes ago, DZ-Jay said:

From the code snippet you provided, it is not clear to me if the return value from the get_angle() procedure is used at all before being reset.

 

Also, I see that there is a call to the restart_ball() procedure, but it is also not clear to me what transpires in there.


It all depends on what happens in between the call to get_angle() and the code that resets them to -2.

 

Unfortunately, I do not have the book at hand at the moment, so I can’t review it.

Please see below the whole code:

 

CLS

 

PRINT AT 0 COLOR 1

FOR c = 1 TO 20

PRINT "\256"

NEXT c

PRINT AT 220

FOR c = 1 to 20

PRINT "\257"

NEXT c

 

PRINT AT 9, "\258"

 

FOR c = 1 TO 10

PRINT AT c * 20 + 9, "\259"

NEXT c

 

PRINT AT 229, "\260"

 

GOSUB update_score

 

x1 = 21

y1 = 42

x2 = 139

y2 = 42

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

'

' Main game loop

'

ball_loop:

WAIT

 

IF sound_counter > 0 THEN

sound_counter = sound_counter - 1

IF sound_counter = 0 THEN SOUND 2, 1, 0

END IF

 

SPRITE 0, $0708 + x1, $0208 + y1, $0800 + 5 * 8 + 1

SPRITE 1, $0708 + x2, $0208 + y2, $0800 + 5 * 8 + 2

 

IF ball = 0 THEN

SPRITE 2, 0

ELSE

SPRITE 2, $0708 + x, $0208 + y, $0800 + 6 * 8 + 6

END IF

 

IF cont1.up THEN IF y1 > 6 THEN y1 = y1 - 2

IF cont1.down THEN IF y1 < 74 THEN y1 = y1 + 2

 

IF cont2.up THEN IF y2 > 6 THEN y2 = y2 - 2

IF cont2.down THEN IF y2 < 74 THEN y2 = y2 + 2

 

IF ball THEN

 

ball_current = ball_current + ball_speed

IF ball_current >= $40 THEN

 

ball_current = ball_current - $40

 

ox = x

oy = y

x = x + #dx

y = y + #dy

 

IF oy + #dy < 2 THEN

#dy = -#dy

oy = 2 - #dy

GOSUB ball

END IF

IF oy + #dy > 90 THEN

#dy = -#dy

oy = 90 - #dy

GOSUB ball

END IF

IF ox + #dx < 2 THEN

GOSUB ball_out

GOSUB restart_ball

END IF

IF ox + #dx > 158 THEN

GOSUB ball_out

GOSUB restart_ball

END IF

 

IF x > x1 - 4 AND x < x1 + 4 THEN

c = y1

GOSUB rebound

END IF

IF x > x2 - 4 AND x < x2 + 4 THEN

c = y2

GOSUB rebound

END IF

 

x = ox + #dx

y = oy + #dy

END IF

ELSE

c = cont AND $E0

IF (c = $a0) + (c = $c0) + (c = $60) THEN ball = 1

END IF

 

GOTO ball_loop

 

'

' Ball touched wall

'

ball: PROCEDURE

SOUND 2,500,48

SOUND 3,400,9

sound_counter = 10

END

 

'

' Ball came out of field

'

ball_out: PROCEDURE

IF x < 80 THEN

score2 = score2 + 1

ELSE

score1 = score1 + 1

END IF

GOSUB update_score

SOUND 2,330,48

SOUND 3,70,10

sound_counter = 15

END

 

'

' Update score for both players

'

update_score: PROCEDURE

' Show score at 2 digits aligned to right

PRINT AT 27 COLOR 1,<.2>score1

' Show score as digits aligned to left

PRINT AT 31 COLOR 2,<>score2

END

 

'

' Restart the ball

'

restart_ball: PROCEDURE

d = RAND % 8

ox = 80

oy = 48

ball = 0

GOSUB get_angle

END

 

'

' Rebound procedure

' Check if ball hits paddle (y coordinate, x already checked)

'

rebound: PROCEDURE

IF y < c - 3 THEN RETURN

IF y > c + 15 THEN RETURN

IF y < c THEN

d = 0

ELSE

d = (y - c) / 2

END IF

GOSUB get_angle

IF ball_speed < $40 THEN ball_speed = ball_speed + 1

SOUND 2,150,48

SOUND 3,300,9

sound_counter = 5

END

 

'

' Get angle per paddle position

' Variable d contains the hit position.

' Returns dx and dy with new direction.

'

get_angle: PROCEDURE

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

IF d = 1 THEN #dy = -2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 2 THEN #dy = -1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 3 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 4 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 5 THEN #dy = 1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 6 THEN #dy = 2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 7 THEN #dy = 2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

END

 

If I understand correctly starting from the section below:

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

we jump to the restart_ball() procedure which in sequence calls the get_angle() procedure, then we return to the restart_ball() procedure and then goes back to :

 

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

But at this point #dx and #dy get overridden.

 

Thanks a lot! :)

 

Edited by Alkadian
Link to comment
Share on other sites

2 hours ago, Alkadian said:

@DZ-Jay,

 

Many thanks as usual for your very clear explanations. Very much appreciated. Would you please help me further with the above? Does that mean that I could also move 1 pixel along the Y axis instead of 2 pixels to move North like when we move to North-East?


Yes, you could.  You just change the value returned by the get_angle() procedure.

 

The problem is that … it would look like the ball moves faster diagonally than on a straight angle.

 

To understand why this is the case, consider that the compass of movement from a particular position, is a circle — i.e., you “spin” around a point, and move in the direction you face by a given amount.

 

Ideally, the displacement distance is the same in all directions, which means that if you plot the displacement of all directions as points, they outline a circle around your current position; that is, all landing points are equidistant from the origin (which is the definition of a circle).

 

But you are not operating in free space — you are constrained to the pixel grid of the screen in 2 dimensions.  This causes a problem.  It means that to make a diagonal move the same distance as a horizontal move, the diagonal needs to be equal to the square root of the sum of the squares of the displacement on both axes.

 

Before you freak out about that sentence, think back to middle school geometry and the Pythagoras Theorem:

 

“The square of the hypothenuse is equal to the sum of the square of the two legs.”

 

image.thumb.png.af274c3b238448e0bba3906b7a295ebd.png

 

That square root will probably give you a fractional pixel value, which is not very useful for our game using only whole pixels.

 

So … what do we do? We approximate:  we move 2 pixels on one axis at right angles, and 1 pixel on each axis for diagonals.  This gives us approximately 1.40 pixels (the result of Pythagoras’ theorem for our two sides of 1 pixel each).

 

That’s not exactly the same as the 2 pixels we move at right angles, but it is closer to it than the alternative of moving 2 pixels in both X and Y directions, which would yield 2.82.

 

I hope this helps.  I’ll post another message to answer the other question.

 

    dZ.

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

@DZ-Jay, that's great! Many thanks for your comprehensive clarifications! Very helpful indeed. I am now experimenting with different values for the pixels along X and Y to see how it works.

 

Looking forward to your next post regarding my other query. I would also appreciate a ton if you could also explain to me why we always check if #dx<0 in the formulas for computing d, expecially because #dx it is not even declared up to that point so I woinder how do we know if it is positive or negative? 🤔

 

Thanks for your patience! 

 

Edited by Alkadian
Link to comment
Share on other sites

10 hours ago, Alkadian said:

Also, let's say that we are in the scenario d=7 with #dy = 2 and #dx = -1. Then when we jump back to the code:

 

GOSUB restart_ball

x = ox

y = oy

#dx = -2

#dy = -2

ball_speed = $20

ball_current = 0

 

#dx  and #dy get both set to -2 overriding the previous values set in the get_angle PROCEDURE.

 

Sorry for my confusion but I really want to get these things right.

 

Thanks a lot!

 


I see why the confusion.  The initialization code (above the main loop) calls procedure restart_ball(), which calls procedure get_angle(), and upon return procedes to discard the values assigned to #dx and #dy, and initialize them to -2.

 

That does look like a weird thing to do.

 

If I were to hazard a guess, I would say that the programmer did that out of convenience, so I wouldn’t worry too much about it.

 

My programmer-spidey-sense suggests the following:

  • At the start of the game, we need to initialize the ball.
  • The rest of the game has already a neat routine that does this for when the ball goes out of bounds.
  • I can call this routine at the start of the program to initialize the ball, and avoid having to write extra code for it (programmers are sooooo lazy 😁).
  • Oh! But that routine calls get_angle(), which I don’t need to do right now.
  • Oh well, what is the harm in wasting the CPU’s time executing extra code (during initialization, which is not time critical)?
  • More work for the CPU, but less typing for me.  Win-Win! (Yup, lazy, I tell you. 😄)

Like I said, probably out of convenience, so I wouldn’t worry too much about it.

 

The extra call to get_angle() costs us nothing, since the initialization step happens before the game even begins, and if it takes two fractions of a second instead of one, the player won’t notice at all.

 

Let me know if this does not make sense.

 

    dZ.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

13 minutes ago, DZ-Jay said:


I see why the confusion.  The initialization code (above the main loop) calls procedure restart_ball(), which calls procedure get_angle(), and upon return procedes to discard the values assigned to #dx and #dy, and initialize them to -2.

 

That does look like a weird thing to do.

 

If I were to hazard a guess, I would say that the programmer did that out of convenience, so I wouldn’t worry too much about it.

 

My programmer-spidey-sense suggests the following:

  • At the start of the game, we need to initialize the ball.
  • The rest of the game has already a neat routine that does this for when the ball goes out of bounds.
  • I can call this routine at the start of the program to initialize the ball, and avoid having to write extra code for it (programmers are sooooo lazy 😁).
  • Oh! But that routine calls get_angle(), which I don’t need to do right now.
  • Oh well, what is the harm in wasting the CPU’s time executing extra code (during initialization, which is not time critical)?
  • More work for the CPU, but less typing for me.  Win-Win! (Yup, lazy, I tell you. 😄)

Like I said, probably out of convenience, so I wouldn’t worry too much about it.

 

The extra call to get_angle() costs us nothing, since the initialization step happens before the game even begins, and if it takes two fractions of a second instead of one, the player won’t notice at all.

 

Let me know if this does not make sense.

 

    dZ.

Awesome, thanks a lot for clarifying and for your further comments which are always very helpful. It makes sense now.

 

One final question please, could you please let me know why we always check if #dx<0 in the if statements, for example:

 

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

 

Infact #dx it is not even declared up to that point so I wonder how do we know if it is positive or negative? 🤔

 

Thanks!

Edited by Alkadian
Link to comment
Share on other sites

Hmm … I just realized I got something wrong in my explanation.  Looking at the full code now, it looks like the 0..7 value of “d,” is not the direction of the disc, but the point on the paddle at which the ball collided.

 

Like in proper Breakout, the angle at which the ball will bounce depends on where on the paddle it touches.  If it hits on the center, it goes on a straight line.  The closer to the edge it hits, the more acute the angle is.

 

This is why the value goes up to seven.  It is also why we check the horizontal value to see whether the ball is going left (negative) or right (positive) to determine which way to bounce.

 

The vertical direction doesn’t need to be checked — the ball always bounces away from the paddle.

 

I am sorry for missing this.  I hope I didn’t cause much confusion.

 

The explanation about the angles and the circles and Pythagoras is still correct … it just does not apply to this game.  Sorry.

 

    dZ.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

14 minutes ago, Alkadian said:

Awesome, thanks a lot for clarifying and for your further comments which are always very helpful. It makes sense now.

 

One final question please, could you please let me know why we always check if #dx<0 in the if statements, for example:

 

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

 

Infact #dx it is not even declared up to that point so I wonder how do we know if it is positive or negative? 🤔

 

Thanks!


My last comment should elucidate this.  In any case, the answer is as follows:

 

The variables #dx and #dy always have a value.  Even before getting one assigned in code, the IntyBASIC runtime initializes all variables to zero.

 

The reason we check #dx for negative values and not #dy is because when the ball hits the paddle, it will bounce at an angle that is in the opposite horizontal direction.

 

Therefore, we need to know if it is going left (negative) or right (positive) in order to reflect the direction accordingly.

 

Conversely, the vertical direction of bounce is always the same:  away from the paddle.

 

    dZ.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi,

 

From a pure learning point of view and alway for having fun, I have decided to start from scratch this game based on my own interpretation of Pong logic game. Basically at the moment I am looking at the very start of the game when the ball is roughly in the centre of the screen and based on a random number d (between 1 and 6) it can only go into 6 directions. Then just for testing purposes, as soon as the ball hits the position y=8 or y=96, I would like to restart the game and get a new random number d so that the game starts with the ball moving to a different direction.

 

But I am facing the two issues below: 

 

1. Every time I run the .rom file the ball always gets the same direction. Obviously if I manually set a value for d then I do get different direction.

2. When the ball reaches y=8 for example nothing happens.

 

Please see the code below:

 

CLS

MODE 0,2,0,0,0

WAIT

DEFINE 1,1,bitmap1

 

x=85

y=50

 

d = rand % 6

 

loop:

 

  IF d=1 THEN dx=2 : dy=0 

  IF d=2 THEN dx=-2 : dy=0 

  IF d=3 THEN dx=1 : dy=1 

  IF d=4 THEN dx=-1 : dy=1

  IF d=5 THEN dx=1 : dy=-1

  IF d=6 THEN dx=-1 : dy=-1

 

  IF y=8 THEN GOSUB restart

  IF y=96 THEN GOSUB restart

 

  x=x+dx

  y=y+dy

 

  SPRITE 0, $0300+x, $0100+y, $0807 + 1 * 8

 

GOTO loop

 

bitmap1:

BITMAP "##......"

BITMAP "##......"

BITMAP "........"

BITMAP "........"

BITMAP "........"

BITMAP "........"

BITMAP "........"

BITMAP "........"

 

 

 

restart: PROCEDURE

d = rand % 6

END

 

Could you please help me out? :)

 

Thanks a lot!

 

my_pong.bas my_pong.rom

Edited by Alkadian
Added files
Link to comment
Share on other sites

I haven’t had a chance to inspect your code thoroughly, and I don’t have access to the book at the moment; but I wanted to point out a couple of things.

  • You need a WAIT somewhere in that loop, or else, it will loop for many, many times, updating the position in the variables, but not displaying the changes until the VBLANK interrupt arrives.

 

  • The Pseudo-Random Number Generator (PRNG) used by IntyBASIC is deterministic — that is, given the same conditions and same inputs, it will produce the exact same values in exactly the same sequence, all the time.  (If you are wondering how this ever works at all, is because the the numbers in the sequence appear to be in a random order, even though it is the same sequence.  The trick is to “seed” the PRNG by placing it at some “random-ish” initial position in that sequence.  Obviously to pick that position you need some sort of input that is not deterministic — like, for instance, user input, or the time it takes a user to press “start,” etc.)

    The IntyBASIC runtime “corrupts” the PRNG with some arbitrary noise in order to disrupt the sequence and make it seem even more random — but this only happens once the program gets running.

    Because you start the program and immediately pick a random number, it will always be the same one.

    We typically get around this by adding a title screen or some other interstitial that depends on user input — because the time it takes for the user to start the game from a cold boot is not deterministic, it is a good way to ensure your PRNG starts with a more “randomy” number.
     
  • Y=96 is the bottom of the screen.  Y=8 is the top.  If the ball never goes up, it won’t reach Y=8.
     
  • DY (the vertical displacement) only turns negative when “D” is 5 or 6.  This means that for every other value of “D,” the displacement will add to the vertical position, moving it downwards.

 

I’ll take a closer look later and provide more feedback.  Hope the above makes sense.

 

   dZ.

 

Link to comment
Share on other sites

21 minutes ago, DZ-Jay said:

I haven’t had a chance to inspect your code thoroughly, and I don’t have access to the book at the moment; but I wanted to point out a couple of things.

  • You need a WAIT somewhere in that loop, or else, it will loop for many, many times, updating the position in the variables, but not displaying the changes until the VBLANK interrupt arrives.

 

  • The Pseudo-Random Number Generator (PRNG) used by IntyBASIC is deterministic — that is, given the same conditions and same inputs, it will produce the exact same values in exactly the same sequence, all the time.  (If you are wondering how this ever works at all, is because the the numbers in the sequence appear to be in a random order, even though it is the same sequence.  The trick is to “seed” the PRNG by placing it at some “random-ish” initial position in that sequence.  Obviously to pick that position you need some sort of input that is not deterministic — like, for instance, user input, or the time it takes a user to press “start,” etc.)

    The IntyBASIC runtime “corrupts” the PRNG with some arbitrary noise in order to disrupt the sequence and make it seem even more random — but this only happens once the program gets running.

    Because you start the program and immediately pick a random number, it will always be the same one.

    We typically get around this by adding a title screen or some other interstitial that depends on user input — because the time it takes for the user to start the game from a cold boot is not deterministic, it is a good way to ensure your PRNG starts with a more “randomy” number.
     
  • Y=96 is the bottom of the screen.  Y=8 is the top.  If the ball never goes up, it won’t reach Y=8.
     
  • DY (the vertical displacement) only turns negative when “D” is 5 or 6.  This means that for every other value of “D,” the displacement will add to the vertical position, moving it downwards.

 

I’ll take a closer look later and provide more feedback.  Hope the above makes sense.

 

   dZ.

 

Thanks a lot for looking at it for me! I have just attached the files in my previous post.

 

I will go though every single point you made above.

 

Looking forward to your further feedback :)

 

 

Edited by Alkadian
Link to comment
Share on other sites

Well, I have made some progress here! It is sort of working :)

 

I have added a title screen at the beginning as you kindly suggested, added a few WAIT and then I have changed the restart procedure by adding the initial ball coordinates, so it starts from the same original position again! I have had missed that before.

 

restart: PROCEDURE

d = rand(255) % 6

x=85

y=50

END

 

Hopefully with your feedback it will run even smoother :)

 

 

 

 

my_pong.bas my_pong.rom

Edited by Alkadian
Added more notes
Link to comment
Share on other sites

19 hours ago, Alkadian said:

Well, I have made some progress here! It is sort of working :)

 

I have added a title screen at the beginning as you kindly suggested, added a few WAIT and then I have changed the restart procedure by adding the initial ball coordinates, so it starts from the same original position again! I have had missed that before.

 

restart: PROCEDURE

d = rand(255) % 6

x=85

y=50

END

 

Hopefully with your feedback it will run even smoother :)

 

 

 

 

my_pong.bas 857 B · 1 download my_pong.rom 3.56 kB · 1 download


Sorry for the delay.  I’ll try to catch up with this on the week-end.

 

A couple of things stand out:

  • Rand by itself is the same as Rand(255).  (Actually, it’s Rand(256), which yields a number from 0 to 255 — but, of course, that’s what you meant above. 😉)

 

  • The Rand function gives you a pseudo random number generated during the last interrupt.  Therefore, it will be the same value for an entire frame, no matter how many times you call it.

    Therefore, you should always call your restart procedure after a WAIT, or at most once per frame.

    Alternatively, you could use the Random() function, which works similarly, but advances the number generator on every invocation.

 

   dZ.

Link to comment
Share on other sites

41 minutes ago, DZ-Jay said:

Sorry for the delay.  I’ll try to catch up with this on the week-end.

Hi,

Sure, no probs at all. Really, whenever you get a chance. Thanks :)

 

41 minutes ago, DZ-Jay said:

Rand by itself is the same as Rand(255).  (Actually, it’s Rand(256), which yields a number from 0 to 255 — but, of course, that’s what you meant above. 😉)

Oh! My bad, you are right about that! 

 

42 minutes ago, DZ-Jay said:

The Rand function gives you a pseudo random number generated during the last interrupt.  Therefore, it will be the same value for an entire frame, no matter how many times you call it.

Thanks for clarifying that.

 

43 minutes ago, DZ-Jay said:

Alternatively, you could use the Random() function, which works similarly, but advances the number generator on every invocation.

Thanks! I have already implemented your suggestion. Cool stuff!

 

Also I would really appreciate, when you get a chance, if you could explain to me the code on page 32:

 

IF ball THEN

 

ball_current = ball_current + ball_speed

IF ball_current >= $40 THEN

 

ball_current = ball_current - $40

 

I don't get why the value $40.

 

Thanks a lot!

 

 

 

 

Link to comment
Share on other sites

5 hours ago, Alkadian said:

Hi,

Sure, no probs at all. Really, whenever you get a chance. Thanks :)

 

Oh! My bad, you are right about that! 

 

Thanks for clarifying that.

 

Thanks! I have already implemented your suggestion. Cool stuff!

 

Also I would really appreciate, when you get a chance, if you could explain to me the code on page 32:

 

IF ball THEN

 

ball_current = ball_current + ball_speed

IF ball_current >= $40 THEN

 

ball_current = ball_current - $40

 

I don't get why the value $40.

 

Thanks a lot!

 

 

 

 

 

I just took a look at the book and see that the code you quoted is explained in page 35:

Quote

How does this work?  Essentially the ball position is contained in variables x and y, and the current direction goes into variables #dx and #dy.  We also have the current speed of the ball in variable ball_speed, and if the ball is active in variable ball (non-zero or zero for inactive).

 

The ball is enabled by pressing one of the side-buttons of the controllers.  Once the ball is enabled, ball_current increases with the current speed (ball_speed) and after crossing the value of $40 (64 in decimal), it moves the ball in its current direction.

 

This means the ball can move by fractional positions, making it smoother to the player's eyes.

 

The last part is the important bit.

 

You can think of "ball_current" variable as being the fractional portion of the player's position value.  As the ball moves, its position changes in fractional increments; and when the fraction reaches the threshold of a full pixel, the position of the sprite is adjusted.  In this case, the threshold is set to the value 64.  What this means is that the unit of displacement is 1/64th of a pixel.

 

In other words, the following code keeps track of how many 64th of a pixel the ball has moved:

ball_current = ball_current + ball_speed

 

When a whole pixel is moved (64 units, or $40 in Hexadecimal), the sprite position is adjusted.

 

It is a clever way to implement sub-pixel movement.  In general terms, because the display can only show whole pixels, the movement of the ball is tracked logically in separate variables as a fraction.

 

I hope this makes sense.

 

       -dZ.

  • Thanks 1
Link to comment
Share on other sites

9 hours ago, DZ-Jay said:

 

I just took a look at the book and see that the code you quoted is explained in page 35:

 

The last part is the important bit.

 

You can think of "ball_current" variable as being the fractional portion of the player's position value.  As the ball moves, its position changes in fractional increments; and when the fraction reaches the threshold of a full pixel, the position of the sprite is adjusted.  In this case, the threshold is set to the value 64.  What this means is that the unit of displacement is 1/64th of a pixel.

 

In other words, the following code keeps track of how many 64th of a pixel the ball has moved:

ball_current = ball_current + ball_speed

 

When a whole pixel is moved (64 units, or $40 in Hexadecimal), the sprite position is adjusted.

 

It is a clever way to implement sub-pixel movement.  In general terms, because the display can only show whole pixels, the movement of the ball is tracked logically in separate variables as a fraction.

 

I hope this makes sense.

 

       -dZ.

Hi,

 

Thanks a lot. That helped me a lot indeed. I didn't think that it was related to sub-mixel movement!  

 

I am now stuck with something else on the book. I don't understand why the ball in its old position is drawn elongated when checking collision of the ball with the top and bottom walls and when checking if the ball is exiting the screen. Then the ball is drawn fatter when checking if the ball crosses one of the paddles. 

 

Thanks so much for helping out! Much appreciated ;)

 

 

 

Link to comment
Share on other sites

10 hours ago, Alkadian said:

Hi,

 

Thanks a lot. That helped me a lot indeed. I didn't think that it was related to sub-mixel movement!  

 

Sub-mixel!  LOL! 😆

 

Sounds like the name of a DJ.  😄

 

10 hours ago, Alkadian said:

I am now stuck with something else on the book. I don't understand why the ball in its old position is drawn elongated when checking collision of the ball with the top and bottom walls and when checking if the ball is exiting the screen. Then the ball is drawn fatter when checking if the ball crosses one of the paddles. 

 

In order to understand why, we need to first review a few concepts related to sprites and how the game handles them.

 

First, remember that the coordinate plane of sprites is offset by an entire row and column, above and to the left of the coordinate plane of the display screen:

+--------------------------------------------+
|  OBJECT FIELD outer area                   |
|   +---------------------------------------+|
|   |  DISPLAYED AREA of object field       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   |                                       ||
|   +---------------------------------------+|
+--------------------------------------------+

 

This means that a sprite which is positioned on the very first column of the display (i.e., flush to the left of the screen), is actually eight pixels from the minimum sprite position, or at X coordinate 8.

 

Second, we can make more sense of the game's handling of sprite positions by replacing some of the magic numbers with constants, and turn this:

SPRITE 2, $0708 + x, $0208 + y, $0800 + 6 * 8 + 6 

 

into this:

' X: HIT + VISIBLE + ZOOMX2 + 8 + x
' Y: ZOOMY4 + 8 + y
' A: SPR06 + SPR_WHITE
SPRITE 2, (HIT + VISIBLE + ZOOMX2 + 8 + x), (ZOOMY4 + 8 + y), (SPR06 + SPR_WHITE)

 

Third, let us take a look at the bitmaps for the paddle ...

11000000
11000000
11000000
11000000
11000000
11000000
11000000
11000000

 

... and the ball:

11000000
11000000
00000000
00000000
00000000
00000000
00000000
00000000

 

As you can see from the above, the ball and the paddle are both two pixels wide.  Plus, as you can also see from the constants in the code above it, the sprites are "zoomed" to double magnification on the horizontal axis (ZOOMX2).  This makes each of those sprites four pixels wide, in practice.

 

Now, let us put this all together to understand how the game works.

 

The constants in the code above make it clear that whatever values we have in "x1" and "y1," when updating the sprite positions, it adds eight to each axis.  This is because "x1" and "y1" are merely the logical position of the sprites within the display screen -- we know that the sprites positions start actually eight pixels to the left and to the top, so we need to add that extra eight pixels.  To illustrate:

ball-x2-position.thumb.png.6b71a07e3050e9fbe1d8555c9179d1fe.png
* that sprite formula should say "x = x + 8," not "x1."  DOH!

On top of that, consider that the paddle is 4 pixels wide (the two pixels of the picture in GRAM, magnified x2).  Therefore, if we want to check for collisions, we need to check not only that the position of the ball coincides with the position of the paddle, but whether the position of the ball intersects with any of the pixels along the width of the paddle.  In other words, we need to check if the ball and the paddle overlap.

 

Why is it necessary to check for overlap instead of just for touching?  Because the displacement of the ball can be one or two pixels wide, so it could be that when adjusting its position internally (prior to updating the sprite object itsefl), the ball went a bit farther than the leading edge of the four-pixel-wide paddle.  See the illustration below:

ball-x2-overlap.thumb.png.4703fde691692cd6d3449bf7fe2300b1.png

 

If the ball moves more than one pixel, it'll be inside the solid space of the paddle.  Moreover, the ball itself is four pixels wide as well, so its position cannot be just measured from its origin (its top-right corner).  The game tries to account for this by checking if the sprite position is within any of the four pixels along the width of the paddle:

Quote

Notice that the ball is slightly "fat" so there is a range to check.

 

Thus, the following code:

IF x > x1 - 4 AND x < x1 + 4 THEN
  c = y1
  GOSUB rebound
END IF

 

It may not be clear, but consider that "x1 - 4" is the same as x1 minus eight (the actual position of the paddle sprite), minus the four pixels of the width of the ball:  (x1 - 8 - 4) = (x1 - 4).

 

To make it more clear, we could re-write that conditional statement like this:

' (x  - 8): actual sprite object coordinate of ball.
' (x1 - 8): actual sprite object coordinate of paddle.
IF (x - 8) > ((x1 - 8) - 4) AND (x - 8) < ((x1 - 8) + 4) THEN
    c = y1
    GOSUB rebound
END IF

 

However, because subtracting 8 from either side of the comparison does not change the outcome, we can avoid the extra computation and write it as the book shows.  The illustrations on page #36 of the book, although perhaps not very clear, suggest that the ball can fall within the paddle, so that we need to check if any of the four pixels of the ball are within the paddle's range.

 

I know that it is not very easy to describe this in text, and I am not very good at drawing illustrations that attempt to explain dynamic concepts of moving objects in static pictures.  Nonetheless, I hope the above helps you at least a little.

 

Unfortunately, the codes in the books take quite a lot of shortcuts and apply a level of sophisticated optimizations that are not very clear to the novice; and the accompanying text does not try too hard to explain how it is accomplishing what it is trying to do.  Still, they show what is possible, and give you a leg up with some tricks of the trade, even if they are not perfectly transparent to the reader at first.

 

10 hours ago, Alkadian said:

Thanks so much for helping out! Much appreciated ;)

 

My pleasure.

 

     -dZ.

 

  • Thanks 2
Link to comment
Share on other sites

1 hour ago, DZ-Jay said:

Sounds like the name of a DJ.  😄

Haha, sorry about that 😉

 

1 hour ago, DZ-Jay said:

Nonetheless, I hope the above helps you at least a little.

Absolutely! Thank you very much indeed! Very clear explanations. Very, very helpful! 

 

1 hour ago, DZ-Jay said:

My pleasure

Many thanks again for such a thorough review!

 

  • Like 1
Link to comment
Share on other sites

On 9/30/2023 at 8:40 PM, DZ-Jay said:

Moreover, the ball itself is four pixels wide as well, so its position cannot be just measured from its origin (its top-right corner). 

Hi @DZ-Jay, I was reading again your notes and I have just realised that you have mentioned that the origin of the ball is its top-right corner? I thought that the origin of any sprites was actually the top-left. Would you please confirm it?

 

Also assuming that that's the case, am I right in saying that we can only check if x>x1 and not x>x1-4. This would apply to the second player as well so we could use:

 

IF x > x1 AND x < x1 + 4 'for player 1 on the left

 

IF x > x2 AND x < x2 + 4 'for player 2 on the right

 

Actually looking at the player on the right, would you agree that we could use the condition below:

 

X2 < X+4 < X2 +4 

 

image.thumb.png.419c36ce6fea747da523b1fedb8353b7.png

Apologies if I am missing the obvious! :)

 

Many thanks!

 

 

Edited by Alkadian
Added picture
Link to comment
Share on other sites

54 minutes ago, Alkadian said:

Hi @DZ-Jay, I was reading again your notes and I have just realised that you have mentioned that the origin of the ball is its top-right corner? I thought that the origin of any sprites was actually the top-left. Would you please confirm it?

 

You are correct, of course.  That was a stupid typo.  Sorry for the confusion.

 

54 minutes ago, Alkadian said:

Also assuming that that's the case, am I right in saying that we can only check if x>x1 and not x>x1-4.


If “x > x1” then the ball is to the right of the left paddle.  You then need to check if “x < x1 + 4” — that is, that the origin of the ball is inside the paddle.

 

However, as the book suggests, this is not enough.  What if the origin of the ball passed through the paddle, i.e., the ball position was adjusted in a way that it ended up on the other side?

 

To account for this, we then need to check if the right edge of the ball is within the paddle:  (x + 4 > x1) And (x + 4 < x1 + 4).

 

That would work, but can it be simplified?

 

Since we know the ball is only 4 pixels wide, we can make the assumption that if it’s origin is either 4 pixels to the left or 4 pixels to the right of the left edge of the left paddle (it’s origin), then the ball itself overlaps with the paddle.

 

Here is an illustration:

image.thumb.png.25c3b739a08c3b122fae3d5afe9aec91.png

 

54 minutes ago, Alkadian said:

 

This would apply to the second player as well so we could use:

 

IF x > x1 AND x < x1 + 4 'for player 1 on the left

 

IF x > x2 AND x < x2 + 4 'for player 2 on the right

 

Actually looking at the player on the right, would you agree that we could use the condition below:

 

X2 < X+4 < X2 +4 

 

image.thumb.png.419c36ce6fea747da523b1fedb8353b7.png

Apologies if I am missing the obvious! :)

 

Many thanks!

 

 


You are correct, but the book simplifies the test to avoid multiple comparisons.  It relies on the relative position of the paddle and ball respective origins, and the fact that they are both 4 pixels wide.

 

I hope this makes sense.

 

    dZ.

  • Thanks 1
Link to comment
Share on other sites

Hi,

I am back again for seeking further help!

It is just that I really want to understand each line of the code before moving on!

With reference to the procedure below:

 

rebound: PROCEDURE

IF y < c - 3 THEN RETURN

IF y > c + 15 THEN RETURN

IF y < c THEN

d = 0

ELSE

d = (y - c) / 2

END IF

...

END

 

I understand the following:

1. We first check if y<c-3 because we want to see if the y position of the ball is above the y position of the player (in fact it is c-3 and not c-4 because we want to see if there is a collision between the top pixels of the player and the bottom pixels of the ball).

image.thumb.png.50a11936fdbed937f9db24f486b673f9.png

2. We then check if y>c+15 because we want to see if the y position of the ball is below the y position of the player (in fact it is c+15 and not c+16 because we want to see if there is a collision between the bottom pixels of the player and the top pixels of the ball).

image.thumb.png.87c5c65bbda3c2263455908f36a176c6.png

3. I don't understand why when we check if y<c we then set d=0. In this case when y<c the ball should still be  above the player and we should return to the point of the code where  we called the procedure

4. I don't understand why we then set d = (y - c) / 2. Is this because d can only be 16 values in total and we are only considering 8 to be in one of the 8 scenarios in the procedure below?

 

get_angle: PROCEDURE

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

IF d = 1 THEN #dy = -2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 2 THEN #dy = -1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 3 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 4 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 5 THEN #dy = 1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 6 THEN #dy = 2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 7 THEN #dy = 2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

END

 

Many thanks in advance :)

 

Edited by Alkadian
Added info
Link to comment
Share on other sites

5 hours ago, Alkadian said:

1. We first check if y<c-3 because we want to see if the y position of the ball is above the y position of the player (in fact it is c-3 and not c-4 because we want to see if there is a collision between the top pixels of the player and the bottom pixels of the ball).

 

2. We then check if y>c+15 because we want to see if the y position of the ball is below the y position of the player (in fact it is c+15 and not c+16 because we want to see if there is a collision between the bottom pixels of the player and the top pixels of the ball).

image.thumb.png.87c5c65bbda3c2263455908f36a176c6.png

3. I don't understand why when we check if y<c we then set d=0. In this case when y<c the ball should still be  above the player and we should return to the point of the code where  we called the procedure

4. I don't understand why we then set d = (y - c) / 2. Is this because d can only be 16 values in total and we are only considering 8 to be in one of the 8 scenarios in the procedure below?

 

get_angle: PROCEDURE

IF d = 0 THEN #dy = -2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

IF d = 1 THEN #dy = -2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 2 THEN #dy = -1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 3 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 4 THEN #dy = 0:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 5 THEN #dy = 1:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 6 THEN #dy = 2:IF #dx < 0 THEN #dx = 2 ELSE #dx = -2

IF d = 7 THEN #dy = 2:IF #dx < 0 THEN #dx = 1 ELSE #dx = -1

END

 

Many thanks in advance :)

 

1. You're right.

2. You're right.

3. The ball Y-coordinate is y, and the paddle Y-coordinate is c. There is a case where the ball hits barely the top edge of the paddle, so when (y < c) then d = 0 to force a rebound direction.

4. Now that the ball Y-coordinate is equal or greater than the paddle Y-coordinate (y >= c) then we subtract the ball Y-coordinate from the paddle Y-coordinate to get a value between 0 and 15, and we divide it by 2 to get a value between 0 and 7. So yes, the value generated is to be processed by the get_angle procedure.

 

The first two statements return if the ball doesn't touch the paddle.

 

The third IF statement handles the cases -3, -2, and -1 setting d to zero for a hit in the corner of the paddle.

 

And the ELSE statement handles the cases 0..15, setting d to a value between 0 to 7 to select the rebound direction.

 

  • Thanks 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...