Qbasicnews.com

Full Version: Breakout-like collision and bouncing algorithm
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Hello, I'm new to this forum...
I moved a thread from the qbasic.com forum (network54.com) to this one.
Here it is:


I'm coding a breakout-like game (with a ball and a paddle)
I am not satisfied with the way I programmed the bouncing algorithm, when the balls meets the paddle.
Note that the ball moves only 1 pixel at once.


1) If the paddle moves towards the ball at the same speed or faster, then after the bouncing the ball will still be in contact with the paddle. So the ball bounces again, moving back towards the paddle, bouncing again and again.

2) If the paddle manages to corner the ball against a wall, like this:
Io-
(where I is the wall, o is the ball and - is the paddle)

then the ball is stuck, the paddle finished its movement towards the wall, and the ball is wether pushed inside the wall or inside the paddle.


For now, my algorythm is like this:

Each time the ball moves or the paddle moves, make a collision check:

Collision check:
IF ABS(xball-xpaddle)<=(width_paddle + width_ball)/2
AND ABS(yball-ypaddle)<=(height_paddle + height_ball)/2
THEN collision

In case of collision:
IF ABS(xball-xpaddle)=(width_paddle+width_ball)/2
THEN xxball=-xxball 'Horizontal bounce

IF ABS(yball-ypaddle)=(height_paddle+height_ball)/2
THEN yyball=-yyball 'Vertical bounce


Does anybody know a better way?
Can you help me?





Here are the answers I got:
-------------------------------------------------------------------------------
here:
by agamemnus

have the collision check every time any of them move one pixel, and each frame have several collision checks, so you can move more than one pixel per object at a time.
-------------------------------------------------------------------------------
Thank you but...
by Zyx

Thank you for your quick reply Agamemnus!
However the problem I am facing is about the bouncing and not the detection of the collision.
I already did as you suggested: checking every time an object moves, one pixel at a time.

My problem is how the ball should bounce when the paddle hit it.
Example:
The paddle is moving leftwards. It hits the ball.
1)Should I move the ball one pixel leftwards?
2)Should I reverse the horizontal direction of the ball?
3) Should I add some "leftwards direction" to the current horizontal direction of the ball?

The paddle keeps moving leftwards, so it hits AGAIN the ball.
1) If I keep pushing the ball to the left, the movement of the ball will not look natural.
2) If I reverse again the horizontal direction of the ball, it will go TOWARDS the paddle
3) If I keep adding "leftwards direction" to the ball, it will obtain accelerations of more than 1 pixel/frame, which is not impossible but will complexify the code. Should I do that?
-------------------------------------------------------------------------------
oh...
by agamemnus

1) you need to make the ball be able to go more than 1 pixel per frame, otherwise it is too slow. Smile

2) Don't add anything..

Assuming that the paddle cannot be affected by the ball (equal and opposite force when one objects hits another), you should do the following.

dx is the ball's x direction
dy is the y direction.

if dx < 0 then
if dy < 0 then
dx = -dx: dy = -dy
else
dx = dx: dy = dy

end if
else
if dy < 0 then
dx = dx: dy = -dy
else
dx = -dx: dy = dy
end if

I'm not sure which ones to put where, but you get the picture. Just test all 4 cases and move them around if it doesn't work right. Also you could consider swapping dx and dy as well as doing negatives on them. ALSO consider the angle the ball hits.. somehow. Bleh, I'm not so good at this. Just keep trying. Smile
-------------------------------------------------------------------------------
I'll have a try
by Zyx

>1) you need to make the ball be able to go more than
>1 pixel per frame, otherwise it is too slow. Smile

This is a big issue here, as I want the movement to be very smooth. I'd rather have a lot of frames.

2) Hmmm... I am a little confused with your example...
Not that I was very clear with my question either... Smile

I think I got the bouncing solved for a NON MOVING PADDLE.
But I still need help in the case the paddle is moving and then hit the ball.

I'll have a go at home tomorrow, and I'll post the program here, so you can see directly the problems, instead of hearing my bad explanations.
-------------------------------------------------------------------------------
Is there a convenient way to upload a file in these forums?
The way the first break-out programs bounced the ball off the paddle was moddelled the same as if it had hit a wall.

More recent B-O games (late 80's early 90's) decide the amount of horazontal movement by the distance the ball hit from the centre of the paddle.

To get around the ball "speed" i.e. pixels per game loop, insted of doing 1p, 2p... etc. to change the speed of the ball do fractions of a pixel. With 1pixel/loop as the maximum speed.

As for 'Uploading your code' if it's not too big. Just post it here in the forum. Preferably in tags. Otherwise you could get some free webspace from a free web provider, upload your file there, and stick the URL in a post.
Quote:change the speed of the ball do fractions of a pixel. With 1pixel/loop as the maximum speed

Yes, thank you Phydaux, it's an excellent idea! I should have started this way first!


Quote:More recent B-O games (late 80's early 90's) decide the amount of horazontal movement by the distance the ball hit from the centre of the paddle.
Yes, I will do that too. Thank you again.

----------------------------------------------------------------------
Now, back to my principal problem. I know, I know, I'm stubborn!

Let's suppose this situation:
The ball ( o ) is moving vertically upwards the paddle.
The paddle ( --- ) is moving vertically downwards the ball.
The paddle moves FASTER than the ball.
Like this:
---

o

a) The ball and the paddle collide.
b) The ball bounces downwards.
c) BUT the paddle is STILL moving downwards!
d) So it will hit the ball AGAIN!
What should I do then?

Maybe disallow the paddles to move faster than the ball.
Maybe push the ball downwards until the paddle stops moving downwards.
Accelerate the ball so it moves at least as fast as the paddle.

Any idea?
If the ball has a vertical speed
b=-0.3

and the paddle has a vertical speed of
p=0.5

could the ball's new speed be
b=p-b
0.8=0.5-(-0.3)

then the ball would be going faster than the paddle, and in the opposite direction than it was before.

Quote:Maybe disallow the paddles to move faster than the ball.
Maybe push the ball downwards until the paddle stops moving downwards.
Accelerate the ball so it moves at least as fast as the paddle.
All of these are good ideas, it just depends on the "feel" of them in the game of which to use.
adding the absolute speeds:
Not a bad idea, but then the ball will almost always be faster than the paddles... unless we count the resistance of the air that would slow down it to a minimal speed...
Hmm... have to investigate that! Thank you again for your ideas, Phydaux
!
Quote:adding the absolute speeds:
The speeds are not absolute values. the +/- value depends on the direction the objects are moving. (Negative = up, positive = down)
This is assuming:
*Your screen co-ordinate system is (0,0) at the top left, and (xmax,ymax) depending on screen size, at the bottom right of the screen.
*you have something like this: BallY = BallY + b that moves your ball each game loop.
Therefor to move up the screen the value would be negative. And to move down, positive.

(Excuse me if I'm being patronising)

Quote:Not a bad idea, but then the ball will almost always be faster than the paddles...
This - I would assume - would be down to the user, if the paddle wasn't moving when the ball hit it the ball would remain at the same speed.
e.g. (using the same terminology as before)
b=-0.3
p=0

b=p-b
0.3=0-(-0.3)

And if the paddle was moving away from the ball (but not faster than it) then it would be like a tennis-player's drop-shot, and slow the ball down.
e.g.
b=-0.8
p=-0.3

N.B. both ball and paddle are moving in same direction

b=p-b
0.5=-0.3-(-0.8)

HTH :)
Thank you again for your reply, Phydaux.
We agree exactly about the way and the consequences of adding the speeds (but not with the same words Smile.
I still have to find a way of limitation for the accelerations. There will be no game if you can, with only one hit, give to the ball a speed faster than the maximum speed for the paddles.
For now, the paddles only give a fraction of their own speed.
Later I will try some "air resistance"

I still have another problem: how should the ball bounce when hitting the very corner of the paddle?
Maybe I should try a symetric bounce along a 45° line?


If anybody is interested in running my code, I will post it in the next post.
It needs at least supplementary two files to work: the scripts for robots 1 and 2. I'll explain that later.
Code:
' Defipong v0.1      by Zyx          coded in QB45           july 2003
' Use V to switch between automatic mode and manual mode
' + and - to adjust display speed
' 2,4,6,8 to move the paddle in Manual mode
' P to pause
' ESC to quit

'---------------------- CONSTANTS ----------------------
' Choose you language here:
'CONST LANGUAGE = "ENGLISH"
CONST LANGUAGE = "FRENCH"
'CONST LANGUAGE = "SPANISH"

'step of paddles when they move (in pixel):
CONST PADDLESTEP = 20
' moves between each instruction of the script:
CONST MOVES = 10
' ball will bounce accordingly to distance from the center of the paddle
' the lower the value the greater the effect
CONST EFFECTPADDLE = 2000
' moving paddle will accelerate the ball
' the lower the value the greater the effect
CONST IMPULSEPADDLE = 10000
' ball will accelerate with time
' the lower the value the greater the effect
CONST ACCELERATION = 2000
'HALF-width of the padle:
CONST PADDLEWIDTH = 25

'---------------------- INITIALIZATION SUBS ----------------------
DECLARE SUB init ()
DECLARE SUB new.game ()
DECLARE SUB new.set ()
DECLARE SUB load.robots ()

'---------------------- ENGINE SUBS ----------------------
DECLARE SUB show.rules.english ()
DECLARE SUB show.rules.french ()
DECLARE SUB show.rules.spanish ()
DECLARE SUB show.info ()
DECLARE SUB show.ball ()
DECLARE SUB move.ball ()
DECLARE SUB move.paddle (numero AS INTEGER)
DECLARE SUB check.collision ()
DECLARE SUB check.limits.paddle ()
DECLARE SUB check.limits.ball ()
DECLARE SUB check.keyboard ()
DECLARE SUB win ()


'---------------------- SCRIPT SUBS ----------------------
DECLARE SUB execute.script ()
DECLARE SUB parser.ifthen ()
DECLARE SUB parser.myvariable ()
DECLARE FUNCTION calculate (paratexte AS STRING)
DECLARE FUNCTION operateur$ (paratexte2 AS STRING, op AS STRING)
DECLARE SUB replace.litterals ()
DECLARE SUB remplace (old AS STRING, new AS LONG)

'declarations

'---------------------- PADDLES ----------------------
' paddle coordinates
DIM SHARED x(2) AS SINGLE, y(2) AS SINGLE
'paddle aimed coordinates
DIM SHARED xobj(2) AS INTEGER, yobj(2) AS INTEGER

'---------------------- BALL ----------------------
' ball coordinates
DIM SHARED xball AS SINGLE, yball AS SINGLE
' ball direction
DIM SHARED xxball AS SINGLE, yyball AS SINGLE
' provisory ball coordinates
DIM SHARED xtmp AS SINGLE, ytmp AS SINGLE

'---------------------- ROBOTS ----------------------
' scripts (litteral)
REDIM SHARED script(2, 1) AS STRING
' scripts (interpreted)
REDIM SHARED command(2, 1) AS INTEGER
' size of scripts (and of paddle)
DIM SHARED taille(2) AS INTEGER
' current instruction
DIM SHARED ligne(2) AS INTEGER
' internal variable used for the script
DIM SHARED myvariable(2) AS LONG

' score of robots
DIM SHARED score(2) AS INTEGER
' number of the robot (used to look for in Robot#.txt)
DIM SHARED robnum(2) AS INTEGER


'---------------------- VARIABLES FOR SCRIPT ----------------------
' if the paddle is horizontally in front of the ball    (0=wrong,-1=true)
DIM SHARED Enfacedelaballe AS INTEGER

'---------------------- GAME VARIABLES ----------------------
' current set
DIM SHARED manche AS INTEGER
' delay between each loop
DIM SHARED vitesse AS INTEGER
' current mode:   1=player vs robot   2=robot vs robot
DIM SHARED manualmode AS INTEGER
' time of play and acceleration
DIM SHARED compteur AS INTEGER

'---------------------- PARSER VARIABLES ----------------------
DIM SHARED texte AS STRING, test AS STRING

'---------------------- FLAGS and DUMMY VARIABLES ----------------------
DIM SHARED a AS INTEGER, B AS INTEGER

' set to 1 for debugging
DIM SHARED debug AS INTEGER
debug = 0


'------------------------------------------------------------------
' MAIN:

new.game
          
          
timer1 = TIMER
DO
    check.keyboard
  
    'robots
    FOR a = 1 TO 2
        execute.script
        check.limits.paddle
    NEXT

    'will executes all the MOVES before next order
    DO
            'delay
            '(vitesse=1 will limit the speed of the ball to MOVES*100 pixels/second)
            IF TIMER - timer1 >= vitesse / 100 THEN
                timer1 = TIMER
                move.ball
                check.collision
                FOR B = 1 TO 2: move.paddle (B): NEXT
                check.collision
                show.ball

                'ball coordinates are now definitive
                xball = xtmp
                yball = ytmp
            END IF

            'acceleration
            compteur = compteur + 1
            IF compteur MOD MOVES = 0 THEN EXIT DO
            IF compteur > ACCELERATION THEN
                compteur = 0
                xxball = xxball * 1.1
                yyball = yyball * 1.1
                check.limits.ball
            END IF
        LOOP
LOOP



FUNCTION calculate (paratexte AS STRING)
' calculate the numeric value of a string
DIM ptexte AS STRING


ptexte = paratexte

'   First look for ()
DO
    paren1 = 0  'last  (
    paren2 = 0  'first )
    FOR e = 1 TO LEN(ptexte)
        IF MID$(ptexte, e, 1) = "(" THEN paren1 = e
        IF MID$(ptexte, e, 1) = ")" THEN paren2 = e: EXIT FOR
    NEXT
    IF paren1 > 0 THEN
        ptexte = LEFT$(ptexte, paren1 - 1) + LTRIM$(STR$(calculate(MID$(ptexte, paren1 + 1, paren2 - paren1 - 1)))) + MID$(ptexte, paren2 + 1)
    ELSE
        EXIT DO
    END IF
LOOP

'now evaluate the operands and operators
ptexte = operateur$(ptexte, "*")
ptexte = operateur$(ptexte, "/")
ptexte = operateur$(ptexte, "MOD")
ptexte = operateur$(ptexte, "+")
ptexte = operateur$(ptexte, "-")

result = VAL(ptexte)

'truncating
'calculate = FIX(result + .99999 * SGN(result))
calculate = FIX(result)

END FUNCTION

SUB check.collision
'checks if the ball collides with a paddle
DIM dify AS INTEGER, difx AS INTEGER

FOR B = 1 TO 2
    'relative coordinates between ball and paddle
    dify = ytmp - y(B)
    difx = xtmp - x(B)

    'dify>0 if paddle lower than ball
    'dify<0 if paddle upper than ball
    'difx>0 if paddle to the left of the ball
    'difx>0 if paddle to the right of the ball

    'impulse
    impulsex = (xobj(B) - x(B)) / IMPULSEPADDLE
    impulsey = (yobj(B) - y(B)) / IMPULSEPADDLE
  
    'collision
    IF ABS(dify) < 3 + taille(B) / 2 THEN
        IF ABS(difx) < 3 + PADDLEWIDTH THEN

    'type of bouncing
                SELECT CASE ABS(difx)

                'the ball hit the horizontal edge
                CASE IS < 2 + PADDLEWIDTH
                        'vertical rebond + some x angle
                        yyball = ABS(yyball) * SGN(dify) + impulsey
                        xxball = xxball + difx / EFFECTPADDLE

                ' the ball hit the extremity
                CASE IS = 2 + PADDLEWIDTH

                        'have to check the vertical edge
                        SELECT CASE ABS(dify)

                        'extremity too: the ball hit the corner!
                        CASE 2 + taille(B) / 2
                                ' *TO DO*

                        'the ball hit the vertical edge normally
                        CASE ELSE
                                'horizontal rebond + some y angle
                                xxball = ABS(xxball) * SGN(difx) + impulsex
                                yyball = yyball + dify / EFFECTPADDLE

                        END SELECT
                END SELECT

                'new position of the ball:
                DO WHILE ABS(y(B) - ytmp) < 3 + taille(B) / 2 AND ABS(x(B) - xtmp) < 3 + PADDLEWIDTH
                    xtmp = xtmp + xxball
                    ytmp = ytmp + yyball
                LOOP

                check.limits.ball
        END IF
    END IF

NEXT

END SUB

SUB check.keyboard

k$ = INKEY$
IF k$ > "" THEN
    SELECT CASE UCASE$(k$)
    CASE "+"
        vitesse = vitesse + 1

    CASE "-"
        vitesse = vitesse - 1

    CASE " "    'new game
        new.game

    CASE CHR$(27)
        END

    CASE "4"
        IF manualmode = 1 THEN xobj(2) = x(2) - PADDLESTEP

    CASE "6"
        IF manualmode = 1 THEN xobj(2) = x(2) + PADDLESTEP

    CASE "2"
        IF manualmode = 1 THEN yobj(2) = y(2) + PADDLESTEP / 2

    CASE "8"
        IF manualmode = 1 THEN yobj(2) = y(2) - PADDLESTEP / 2

    CASE "D"
        debug = 1 - debug

    CASE "P"
        SLEEP

    CASE "V"
        manualmode = 3 - manualmode

    END SELECT

    IF vitesse < 0 THEN vitesse = 0
    IF vitesse > 100 THEN vitesse = 100

    show.info
END IF

END SUB

SUB check.limits.ball


' check a collision with the walls
'left and right walls
IF xtmp < 3 OR xtmp > 557 THEN
        xtmp = xball
        xxball = -xxball
END IF
'upper and lower walls
IF ytmp < 3 OR ytmp > 457 THEN
    IF ytmp < 3 THEN score(2) = score(2) + 1 ELSE score(1) = score(1) + 1
    ytmp = yball
    yyball = -yyball
  
    win
END IF

' check that the movement stays UNDER 1 pixel/frame
IF ABS(xxball) > 1 THEN yyball = yyball / ABS(xxball): xxball = SGN(xxball)
IF ABS(yyball) > 1 THEN xxball = xxball / ABS(yyball): yyball = SGN(yyball)
END SUB

SUB check.limits.paddle
' check that the paddle stays in its zone

IF xobj(a) < 1 + PADDLEWIDTH THEN xobj(a) = 1 + PADDLEWIDTH
IF xobj(a) > 560 - 1 - PADDLEWIDTH THEN xobj(a) = 560 - 1 - PADDLEWIDTH
IF a = 1 THEN
    IF yobj(a) > 201 - taille(a) / 2 THEN yobj(a) = 201 - taille(a) / 2
    IF yobj(a) < 1 + taille(a) / 2 THEN yobj(a) = 1 + taille(a) / 2
ELSE
    IF yobj(a) < 259 - taille(a) / 2 THEN yobj(a) = 259 - taille(a) / 2
    IF yobj(a) > 459 - taille(a) / 2 THEN yobj(a) = 459 - taille(a) / 2
END IF

END SUB

SUB execute.script
' execute the current instruction of the robot

'unselect previous instruction
COLOR a
IF ligne(a) < 30 THEN
    IF a = 1 THEN LOCATE ligne(a) + 1, 72 ELSE LOCATE ligne(a) + 1, 77
    PRINT LEFT$(script(a, ligne(a)), 4)
END IF

IF a > manualmode THEN EXIT SUB


SELECT CASE command(a, ligne(a))
CASE 0  'wait
    xobj(a) = x(a): yobj(a) = y(a)

CASE 1  'left
    xobj(a) = xobj(a) - PADDLESTEP

CASE 2  'right
    xobj(a) = xobj(a) + PADDLESTEP

CASE 3  'front
    IF a = 1 THEN yobj(a) = yobj(a) + PADDLESTEP ELSE yobj(a) = yobj(a) - PADDLESTEP

CASE 4  'back
    IF a = 1 THEN yobj(a) = yobj(a) - PADDLESTEP ELSE yobj(a) = yobj(a) + PADDLESTEP

CASE 5  'goto
    ligne(a) = VAL(MID$(script(a, ligne(a)), 5)) - 1

CASE 6  'if then goto
    parser.ifthen

CASE 7  'set myvariable
    parser.myvariable

CASE ELSE
    STOP
END SELECT

'next instruction
ligne(a) = ligne(a) MOD taille(a) + 1

'select it
COLOR 4
IF ligne(a) < 30 THEN
    IF a = 1 THEN LOCATE ligne(a) + 1, 72 ELSE LOCATE ligne(a) + 1, 77
    PRINT LEFT$(script(a, ligne(a)), 4)
END IF

END SUB

SUB load.robots
'open robot1.txt and robot2.txt and memorize the script

DIM order AS STRING

' check the maximum size of the scripts
'------------------------------------------------------------
FOR a = 1 TO 2
    k$ = "robot" + LTRIM$(STR$(robnum(a))) + ".txt"
    OPEN k$ FOR INPUT AS #1
    DO WHILE NOT EOF(1)
        LINE INPUT #1, order
        order = LTRIM$(order)
        IF LEFT$(order, 1) <> ";" AND order > "" THEN taille(a) = taille(a) + 1
    LOOP
    CLOSE #1
NEXT

' dimension the arrays according to the maximum size
IF taille(1) > taille(2) THEN
    REDIM script(1, taille(1)), command(1, taille(1))
    REDIM script(2, taille(1)), command(2, taille(1))
ELSE
    REDIM script(1, taille(2)), command(1, taille(2))
    REDIM script(2, taille(2)), command(2, taille(2))
END IF

taille(1) = 0
taille(2) = 0

' Now read and memorize the scripts!
'------------------------------------------------------------
FOR a = 1 TO 2
    k$ = "robot" + LTRIM$(STR$(robnum(a))) + ".txt"
    OPEN k$ FOR INPUT AS #1
    DO WHILE NOT EOF(1)
        LINE INPUT #1, order
        order = LTRIM$(order)
        IF LEFT$(order, 1) <> ";" AND order > "" THEN
                taille(a) = taille(a) + 1
                script(a, taille(a)) = order

                'Translate the command to a number

                SELECT CASE LEFT$(UCASE$(order), 3)
                CASE "WAI"
                    command(a, taille(a)) = 0
                CASE "LEF"
                    command(a, taille(a)) = 1
                CASE "RIG"
                    command(a, taille(a)) = 2
                CASE "FRO"
                    command(a, taille(a)) = 3
                CASE "BAC"
                    command(a, taille(a)) = 4
                CASE "GOT"
                    command(a, taille(a)) = 5
                CASE "IF "
                    command(a, taille(a)) = 6
                CASE "SET"
                    command(a, taille(a)) = 7
                CASE ELSE   'error:  unknown command
                        PRINT LEFT$(UCASE$(order), 3)
                    STOP
                END SELECT
        END IF
    LOOP
    CLOSE #1
NEXT

END SUB

SUB move.ball
xtmp = xball + xxball
ytmp = yball + yyball

check.limits.ball

'*TO DO* later I'll add a trail here




END SUB

SUB move.paddle (numero AS INTEGER)
'moves and displays the paddles

DIM dirx AS SINGLE, diry AS SINGLE, stepping AS SINGLE

' direction
dirx = INT(xobj(numero) - x(numero))
diry = INT(yobj(numero) - y(numero))

IF dirx = 0 AND diry = 0 THEN EXIT SUB

'I normalize to a step of .5 pixel maximum
IF ABS(dirx) > ABS(diry) THEN
        IF ABS(dirx) > 2 * PADDLESTEP THEN stepping = 2 ELSE stepping = 4 * PADDLESTEP / ABS(dirx)
        diry = diry / ABS(dirx) / stepping
        dirx = SGN(dirx) / stepping
ELSE
        IF ABS(diry) > 2 * PADDLESTEP THEN stepping = 2 ELSE stepping = 4 * PADDLESTEP / ABS(diry)
        dirx = dirx / ABS(diry) / stepping
        diry = SGN(diry) / stepping
END IF

'display the paddle
' size/2 up, size/2 down

LINE (x(numero) - PADDLEWIDTH, y(numero) - taille(numero) / 2)-(x(numero) + PADDLEWIDTH, y(numero) + taille(numero) / 2), 0, BF
x(numero) = x(numero) + dirx
y(numero) = y(numero) + diry
LINE (x(numero) - PADDLEWIDTH, y(numero) - taille(numero) / 2)-(x(numero) + PADDLEWIDTH, y(numero) + taille(numero) / 2), numero, BF

END SUB

SUB new.game
SCREEN 12

SELECT CASE LANGUAGE
CASE "FRENCH"
        show.rules.french
CASE "ENGLISH"
        show.rules.english
CASE "SPANISH"
        show.rules.spanish
CASE ELSE
        STOP
END SELECT

'I set a command by default
script(1, 1) = "WAIT": command(1, 1) = 0
script(2, 1) = "WAIT": command(2, 1) = 0
load.robots

'delay by default
vitesse = 0

'by default, Robot vs Robot
manualmode = 2
score(1) = 0: score(1) = 0
manche = 0

new.set
END SUB

SUB new.set
'game is stopped, ready for service

' redraw of the screen
CLS
LINE (0, 0)-(560, 460), 4, B   'je laisse 4 caractÅ res de chaque c“t‚

'paddles in position
x(1) = 280: x(2) = 280
y(1) = 20: y(2) = 440

'I force a slight move to ensure display
xobj(1) = 279: xobj(2) = 279
yobj(1) = 20: yobj(2) = 440

manche = manche + 1
ligne(1) = 0
ligne(2) = 0

' ball in position
xball = 275
xxball = -.1
vball = 1
IF manche MOD 2 = 0 THEN    'service player 1
    yball = 26 + taille(1) / 2
    yyball = .05
ELSE                        'service player 2
    yball = 434 - taille(2) / 2
    yyball = -.05
END IF
xtmp = xball: ytmp = yball

show.info

' I show the 29 first lines of script
LOCATE 1, 72: COLOR 1
PRINT "Up"
FOR a = 1 TO taille(1)
    LOCATE a + 1, 72
    PRINT LEFT$(script(1, a), 4)
    IF taille(1) > 29 THEN EXIT FOR
NEXT a

LOCATE 1, 77: COLOR 2
PRINT "Down"
FOR a = 1 TO taille(2)
    LOCATE a + 1, 77
    PRINT LEFT$(script(2, a), 4)
    IF taille(2) > 29 THEN EXIT FOR
NEXT a

'display ball and paddles
show.ball
FOR B = 1 TO 2: move.paddle (B): NEXT

k$ = INKEY$: DO WHILE k$ = "": k$ = INKEY$: LOOP

END SUB

FUNCTION operateur$ (paratexte2 AS STRING, op AS STRING)
'identify operands for a given operator (op) and calculate the operation

DIM ptexte2 AS STRING, calcul AS SINGLE

ptexte2 = paratexte2

FOR f = 1 TO LEN(ptexte2)

        ' look for the operator
    IF MID$(ptexte2, f, LEN(op)) = op THEN

        'left operand: first character must be 0-9 or . or -
        gauche = 0
        DO
            gauche = gauche + 1
            IF f - gauche < 1 THEN EXIT DO
            k$ = MID$(ptexte2, f - gauche, 1)

            'include the minus sign
            IF ASC(k$) = 45 THEN gauche = gauche + 1

            IF k$ = "" OR ASC(k$) = 47 OR ASC(k$) < 46 OR ASC(k$) > 57 THEN EXIT DO
        LOOP
        gauche = gauche - 1
      

        'right operand: last character must be 0-9 or .
        droite = 1
        DO
            droite = droite + 1
            IF f + LEN(op) + droite > LEN(ptexte2) THEN droite = droite + 1: EXIT DO
            k$ = MID$(ptexte2, f + LEN(op) + droite - 1, 1)
            IF k$ = "" OR ASC(k$) = 47 OR ASC(k$) < 46 OR ASC(k$) > 57 THEN EXIT DO
        LOOP
        droite = droite - 1

        'value of each operand
        valeur1 = VAL(MID$(ptexte2, f - gauche, gauche))
        valeur2 = VAL(MID$(ptexte2, f + LEN(op), droite + 1))

        'execute the operation
        SELECT CASE op
        CASE "*"
            calcul = valeur1 * valeur2
        CASE "/"
            calcul = valeur1 / valeur2
        CASE "MOD"
            calcul = valeur1 MOD valeur2
        CASE "+"
            calcul = valeur1 + valeur2
        CASE "-"
            calcul = valeur1 - valeur2
        CASE ELSE
                STOP
        END SELECT
      
        'replace the operation by its result
        ptexte2 = LEFT$(ptexte2, f - gauche - 1) + LTRIM$(STR$(calcul)) + MID$(ptexte2, f + LEN(op) + droite)

        'for debugging
        IF debug = 1 AND a = 2 THEN
                LOCATE 7, 1: PRINT SPACE$(400): LOCATE 7, 1
                PRINT "Formula:"; paratexte2
                PRINT "Operation:"; op
                PRINT "left value:"; valeur1, "right value:"; valeur2
                PRINT "Result:"; calcul
                PRINT "New formula:"; ptexte2
                SLEEP
        END IF

    END IF
NEXT

operateur = ptexte2
END FUNCTION

SUB parser.ifthen
' evaluate the tests.
'If a test is TRUE, then execute the GOTO of the script

'take out "IF " and "THEN"
test = MID$(UCASE$(script(a, ligne(a))), 4)
FOR c = 3 TO LEN(test)
    IF MID$(test, c, 4) = "THEN" THEN EXIT FOR
NEXT

'memorize the line referenced by the GOTO
gotoligne = VAL(MID$(test, c + 10))
test = LEFT$(test, c - 1)

replace.litterals

'identify the kind of test (=,>,>=,<,<=)
FOR c = LEN(test) TO 1 STEP -1
    ope$ = MID$(test, c, 1)
    IF ope$ = "=" OR ope$ = ">" OR ope$ = "<" THEN EXIT FOR
NEXT

'memorize the two parts of the test
test1$ = LEFT$(test, c - 1)
test2$ = MID$(test, c + 1)

'in case of <= or >= (2 characters long)
IF ope$ = "=" THEN
    ope2$ = MID$(test, c - 1, 1)
    IF ope2$ = "<" OR ope2$ = ">" THEN
        ope$ = ope2$ + ope$
        test1$ = LEFT$(test, c - 2)
    END IF
END IF

'evaluate each part
t1 = calculate(test1$)
t2 = calculate(test2$)

'evaluate the test
flag = 0
SELECT CASE ope$
CASE "="
    IF t1 = t2 THEN flag = 1
CASE ">"
    IF t1 > t2 THEN flag = 1
CASE "<"
    IF t1 < t2 THEN flag = 1
CASE ">="
    IF t1 >= t2 THEN flag = 1
CASE "<="
    IF t1 <= t2 THEN flag = 1
CASE ELSE
    STOP
END SELECT


'for debugging:
IF debug = 1 AND a = 2 THEN
        LOCATE 1, 1: PRINT SPACE$(1200): LOCATE 1, 1
        PRINT "IF ... THEN ... GOTO ..."
        PRINT "Line:"; ligne(a)
        PRINT script(a, ligne(a))
        PRINT "Test:"; test
        PRINT "Left value:"; t1, "Right value:"; t2
        PRINT "Result:"; flag
        SLEEP
END IF

'if TRUE, then go to the line
IF flag = 1 THEN ligne(a) = gotoligne - 1

END SUB

SUB parser.myvariable
' sets the internal variable

'take out "set myvariable="
test = MID$(UCASE$(script(a, ligne(a))), 16)

replace.litterals

'for debugging:
IF debug = 1 AND a = 2 THEN
        LOCATE 1, 1: PRINT SPACE$(1200): LOCATE 1, 1
        PRINT "SET MYVARIABLE"
        PRINT "Line:"; ligne(a)
        PRINT script(a, ligne(a))
        PRINT "Formula:"; test
END IF

'evaluate and sets the variable
t1 = calculate(test)
myvariable(a) = t1

'for debugging:
IF debug = 1 AND a = 2 THEN
        LOCATE 5, 1
        PRINT "New Value:"; myvariable(a)
        SLEEP
END IF

END SUB

SUB remplace (old AS STRING, new AS LONG)
'replace a string by another

FOR f = 1 TO LEN(test)
    IF MID$(test, f, LEN(old)) = old THEN
        test = LEFT$(test, f - 1) + LTRIM$(STR$(new)) + MID$(test, f + LEN(old))
    END IF
NEXT

END SUB

SUB replace.litterals
'replaces variables by numeric values

' ball variables: KEEP THIS ORDER!
CALL remplace("XXBALL", INT(xxball * 100))
CALL remplace("YYBALL", INT(yyball * 100))
CALL remplace("XBALL", INT(xball))
CALL remplace("YBALL", INT(yball))

'generic variables
CALL remplace("HEIGHT", 201 - 1)
CALL remplace("WIDTH", 599 - 41)
CALL remplace("PADDLEWIDTH", PADDLEWIDTH)

'robot-dependent variables
CALL remplace("MYVARIABLE", myvariable(a))
CALL remplace("XOPPONENT", INT(x(3 - a)))
CALL remplace("YOPPONENT", INT(y(3 - a)))
CALL remplace("XMYSELF", INT(x(a)))
IF a = 1 THEN
        CALL remplace("BALLAPPROACHING", (yyball < 0))
        CALL remplace("INFRONTOFBALL", (ABS(x(a) - xtmp) < PADDLEWIDTH + 2 + 1 AND y(a) < ytmp AND ytmp - y(a) < PADDLESTEP * 4))
        CALL remplace("MINY", 1)
        CALL remplace("MAXY", 201)
        CALL remplace("YMYFRONT", INT(y(a) + taille(a) / 2))
        CALL remplace("YMYBACK", INT(y(a) - taille(a) / 2))
ELSE
        CALL remplace("BALLAPPROACHING", (yyball > 0))
        CALL remplace("INFRONTOFBALL", (ABS(x(a) - xtmp) < PADDLEWIDTH + 2 + 1 AND y(a) > ytmp AND y(a) - ytmp < PADDLESTEP * 4))
        CALL remplace("MINY", 259)
        CALL remplace("MAXY", 459)
        CALL remplace("YMYFRONT", INT(y(a) - taille(a) / 2))
        CALL remplace("YMYBACK", INT(y(a) + taille(a) / 2))
END IF

'Remove the empty spaces:
FOR f = 1 TO LEN(test)
    IF MID$(test, f, 1) = " " THEN
        test = LEFT$(test, f - 1) + MID$(test, f + 1)
    END IF
NEXT


END SUB

SUB show.ball
'affichage balle

CIRCLE (xball, yball), 2, 0
CIRCLE (xtmp, ytmp), 2, 2 + vball / 2

END SUB

SUB show.info
' show info at line 30

LOCATE 30, 1
COLOR 15: PRINT "Set "; manche,
COLOR 5: PRINT "Delay (+/-):"; vitesse;
LOCATE 30, 35
COLOR 6
IF manualmode = 1 THEN PRINT "Player (V)s Robot";  ELSE PRINT "Robot (V)s Robot ";
LOCATE 30, 55
COLOR 15: PRINT "Scores:";
COLOR 1: PRINT " "; score(1);
COLOR 2: PRINT " "; score(2); "   ";

END SUB

SUB show.rules.english
CLS
COLOR 15
PRINT "                         DEFI PONG       (A game of robots and paddles)"
PRINT " The program reads the scripts of the robots from the robot#.txt files."
COLOR 4
PRINT " Commands recognized for the script: "
COLOR 15
PRINT "(use lines starting with ';' for comments)"
PRINT " 0 - WAIT        1 - LEFT 2 - RIGHT      3 - FRONT 4 - BACK"
PRINT " 5 - GOTO line"
PRINT " 6 - IF test THEN GOTO line"
PRINT " 7 - SET MYVARIABLE=expression"
COLOR 4
PRINT " Possible tests:"
COLOR 15
PRINT " (no AND nor OR nor NOT)"
PRINT " > >=  <   <=  ="
COLOR 4
PRINT " Possible operators (by order of priority):"
COLOR 15
PRINT " ()    *   /   mod     +   -"
COLOR 4
PRINT " Possible variables:"
COLOR 15
PRINT " Xmyself   Ymyfront      Ymyback       Xopponent Yopponent"
PRINT " Xball     Yball         XXball (*100)     YYball (*100)"
PRINT " Infrontofball   (Boolean)       Ballapproaching(Boolean)"
PRINT " Myvariable  (internal use)"
PRINT " Width       Height      MinY    MaxY"
PRINT "Paddlewidth"
COLOR 4
PRINT " Special Rules:"
COLOR 15
PRINT "Each turn, an instruction is executed for each robot,then the ball moves"
PRINT "In function of the vertical movement of the paddle before hitting the ball, the ball can be accelerated OR slowed down"
PRINT "Each set has 5 points"
COLOR 4
PRINT "ball dimensions: a circle of 4 pixels"
PRINT "Zone limits: x from 1 to 559    y from 1 to 459"
COLOR 7
INPUT "Select robot numbers (example: 1,2) :", a, B
robnum(1) = a: robnum(2) = B

END SUB

SUB show.rules.french
CLS
COLOR 15
PRINT "                         DEFI PONG       (Jeu de robots et de raquettes)"
PRINT "Le programme lit les scripts des robots depuis les fichiers robot#.txt"
COLOR 4
PRINT " Commandes accept‚es pour le script:"
COLOR 15
PRINT "(Utilisez des lignes commen‡ant par ';' pour les commentaires)"
PRINT " 0 - WAIT        1 - LEFT 2 - RIGHT      3 - FRONT 4 - BACK"
PRINT " 5 - GOTO ligne"
PRINT " 6 - IF test THEN GOTO ligne"
PRINT " 7 - SET MYVARIABLE=expression"
COLOR 4
PRINT " Tests possibles:"
COLOR 15
PRINT " (pas de AND ni de OR ni de NOT)"
PRINT " > >=  <   <=  ="
COLOR 4
PRINT " Op‚rateurs possibles (par ordre de priorit‚):"
COLOR 15
PRINT " ()    *   /   mod     +   -"
COLOR 4
PRINT " Variables possibles:"
COLOR 15
PRINT " Xmyself   Ymyfront      Ymyback       Xopponent Yopponent"
PRINT " Xball     Yball         XXball (*100)     YYball (*100)"
PRINT " Infrontofball   (Boolean)       Ballapproaching(Boolean)"
PRINT " Myvariable  (usage interne)"
PRINT " Width       Height      MinY    MaxY"
PRINT "Paddlewidth"
COLOR 4
PRINT " RŠgles sp‚ciales:"
COLOR 15
PRINT "Chaque tour, une instruction est ex‚cut‚e pour chaque robot, puis la balle bouge"
PRINT "En fonction du mouvement vertical de la raquette juste avant le contact avec la balle la balle peut-ˆtre acc‚l‚r‚e ou ralentie"
PRINT "Chaque manche se joue en 5 points"
COLOR 4
PRINT " Dimensions de la balle: cercle de 4 pixels"
PRINT " Limites du terrain: x de 1 … 559       y de 1 … 459"
COLOR 7
INPUT "Seleccionez les num‚ros de robots (exemple: 1,2) :", a, B
robnum(1) = a: robnum(2) = B

END SUB

SUB show.rules.spanish
CLS
COLOR 15
PRINT "                         DEFI PONG       (Juego de robots y de paletas)"
PRINT "El programa lee los scripts de los robots desde los archivos robot#.txt"
COLOR 4
PRINT " Comandos aceptados para el script:"
COLOR 15
PRINT "(use lineas empezando por ';' para commentarios)"
PRINT " 0 - WAIT        1 - LEFT 2 - RIGHT      3 - FRONT 4 - BACK"
PRINT " 5 - GOTO linea"
PRINT " 6 - IF test THEN GOTO linea"
PRINT " 7 - SET MYVARIABLE=expresion"
COLOR 4
PRINT " Tests posibles:"
COLOR 15
PRINT " (no AND ni OR ni NOT)"
PRINT " > >=  <   <=  ="
COLOR 4
PRINT " Op‚radores posibles (por orden de prioridad):"
COLOR 15
PRINT " ()    *   /   mod     +   -"
COLOR 4
PRINT " Variables posibles:"
COLOR 15
PRINT " Xmyself   Ymyfront      Ymyback       Xopponent Yopponent"
PRINT " Xball     Yball         XXball (*100)     YYball (*100)"
PRINT " Infrontofball   (Boolean)       Ballapproaching(Boolean)"
PRINT " Myvariable  (uso interno)"
PRINT " Width       Height      MinY    MaxY"
PRINT "Paddlewidth"
COLOR 4
PRINT " Reglas especiales:"
COLOR 15
PRINT "Cada turno, se ejecuta una instruccion para cada robot, luego se mueve la pelota"
PRINT "En funcion del movimiento vertical de la paleta justo antes de pegar la pelota, esta puede ser acelerada o frenada"
PRINT "Cada set se juega en 5 puntos"
COLOR 4
PRINT " Dimensiones de la pelota: circulo de 4 pixeles"
PRINT " Limites de la cancha: x de 1 a 559       y de 1 a 459"
COLOR 7
INPUT "Seleccione los numeros de los robots (ejemplo: 1,2) :", a, B
robnum(1) = a: robnum(2) = B

END SUB

SUB win
'for now, each 5 points means a new set. That's all

show.info
FOR B = 1 TO 2
    IF score(B) = manche * 5 THEN
        LOCATE 15, 15: COLOR B
        PRINT "Player" + STR$(B) + " won the set #"; manche;
        k$ = INKEY$: DO WHILE k$ = "": k$ = INKEY$: LOOP
        new.set
    END IF
NEXT

END SUB
Here's two sample files for a script:
Copy this script and save it in a file called:
Robot1.txt

Code:
; young beginner: shaky robot

if xmyself>xball+paddlewidth then goto 4
right
goto 1
left

Copy this script and save it in a file called:
Robot2.txt

Code:
; old veteran:    long range robot...

;1
;    myvariable will hold the future x position of the ball
;
set myvariable=(xball+(ymyfront-yball)*xxball/yyball) mod (width*2)

;2
if myvariable<0 then goto 7

;3
if myvariable>width then goto 9

;4, 5 and 6
if xmyself>myvariable then goto 11
right
goto 1

;7 and 8
set myvariable=-myvariable
goto 3

;9 and 10
set myvariable=2*width-myvariable
goto 4

;11
left


When you play the game, choose "1,2" at the input. Voilà
Pages: 1 2