'OPTION _EXPLICIT
'-------------------------------------------------------------------------------------------------------------
'** Declare constants
'-------------------------------------------------------------------------------------------------------------
CONST FALSE
= 0, TRUE
= NOT FALSE
' truth detectors CONST BRIGHTWHITE
= _RGB32(255, 255, 255) ' define colors CONST SCREENWIDTH
= 768 ' screen width CONST SCREENHEIGHT
= 576 ' screen height CONST PUCKSIZE
= 10 ' puck square size CONST PADDLEWIDTH
= 10 ' paddle width CONST PADDLEHEIGHT
= 60 ' paddle height CONST PADDLESPEED
= 2 ' paddle Y speed CONST PADDLEOFFSET
= 20 ' paddle offset from side of screen CONST P1MISSED
= PUCKSIZE \
2 ' puck on left side edge of screen CONST P2MISSED
= SCREENWIDTH
- 1 - PUCKSIZE \
2 ' puck on right side edge of screen CONST PADDLEYMIN
= PADDLEHEIGHT \
2 + 30 ' min Y coordinate for paddles CONST PADDLEYMAX
= SCREENHEIGHT
- 30 - PADDLEHEIGHT \
2 ' max Y coordinate for paddles CONST PUCKXMIN
= PADDLEOFFSET
+ PADDLEWIDTH
+ PUCKSIZE \
2 ' min X coordinate for puck CONST PUCKXMAX
= SCREENWIDTH
- 1 - PADDLEOFFSET
- PADDLEWIDTH
- PUCKSIZE \
2 ' max X coordinate for puck CONST PUCKYMIN
= 30 + PUCKSIZE \
2 ' min Y coordinate for puck CONST PUCKYMAX
= SCREENHEIGHT
- 30 - PUCKSIZE \
2 ' max Y coordinate for puck CONST FPS
= 300 ' game frames per second
'-------------------------------------------------------------------------------------------------------------
'** Declare TYPE definitions
'-------------------------------------------------------------------------------------------------------------
TYPE RECTTYPE
' rectangle definition for collision detection
TYPE MOVINGOBJECT
' moving object definition rect
AS RECTTYPE
' rectangle coordinates for collision detection
'-------------------------------------------------------------------------------------------------------------
'** Declare global variables
'-------------------------------------------------------------------------------------------------------------
DIM PlayFieldCopy
AS LONG ' playing field image copy (without scores) DIM P1Paddle
AS MOVINGOBJECT
' player 1 paddle DIM P2Paddle
AS MOVINGOBJECT
' player 2 paddle DIM Puck
AS MOVINGOBJECT
' puck properties DIM Pscore
(1) AS INTEGER ' player scores (0=player1, 1=player2 DIM GameOver
AS INTEGER ' TRUE when a player reaches a score of 9
'-------------------------------------------------------------------------------------------------------------
'** Begin main program
'-------------------------------------------------------------------------------------------------------------
_TITLE "QB64 PONG!" ' give window a title Initialize ' initialize game settings
DO ' begin main program loop NewGame ' start a new game
PlayGame ' play the game
Cleanup ' clean player's computer memory and exit to operating system
'-------------------------------------------------------------------------------------------------------------
'** end main program
'-------------------------------------------------------------------------------------------------------------
'-------------------------------------------------------------------------------------------------------------
'** Declare subroutines and functions
'-------------------------------------------------------------------------------------------------------------
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED Puck
AS MOVINGOBJECT
' need access to puck
IF SGN(Puck.xv
) = 1 THEN ' puck was heading right? Puck.x = SCREENWIDTH * .25 ' yes, serve from player 1 side
Puck.xv = .5 ' puck will travel right
ELSE ' no, puck was travleing left Puck.x = SCREENWIDTH * .75 ' serve from player 2 side
Puck.xv = -.5 ' puck will travel left
Puck.y
= INT(RND(1) * (SCREENHEIGHT
- 100)) + 50 ' random puck Y coordinate Puck.yv = 0 '(RND(1) - RND(1)) / 2 ' random puck Y vector component
Missed = FALSE ' reset puck missed indicator
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED PlayField
AS LONG ' need access to playing field image SHARED Puck
AS MOVINGOBJECT
' need access to puck
ResetRound ' set up next game round
MovePaddles ' update player paddles
MovePuck ' update puck
IF SGN(Puck.xv
) = -1 THEN ' was puck heading left? Pscore(1) = Pscore(1) + 1 ' yes, increase player 2 score
ELSE ' no, puck was heading right Pscore(0) = Pscore(0) + 1 ' increase player 1 score
IF Pscore
(0) = 9 OR Pscore
(1) = 9 THEN GameOver
= TRUE
' set game over indicator when player reaches 9 UpdateScores ' update player scores
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED PlayField
AS LONG ' need access to playing field image SHARED PlayFieldCopy
AS LONG ' need access to playing field image copy
_PUTIMAGE , PlayFieldCopy
, PlayField
' restore playing field _DEST PlayField
' make playing field image the destination IF Pscore
(0) = 9 THEN Clr
= BRIGHTWHITE
ELSE Clr
= WHITE
' set color of font DrawScore SCREENWIDTH \ 2 - 100, 50, Pscore(0), Clr ' draw player 1's score
IF Pscore
(1) = 9 THEN Clr
= BRIGHTWHITE
ELSE Clr
= WHITE
' set color of font DrawScore SCREENWIDTH \ 2 + 55, 50, Pscore(1), Clr ' draw player 2's score
_DEST 0 ' set destination back to screen
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
p = 0 ' reset string position counter
FOR y
= 0 TO 4 ' cycle through 5 rows FOR x
= 0 TO 2 ' cycle through 3 columns p = p + 1 ' increment string position counter
IF MID$(Score
(n
), p
, 1) <> " " THEN ' character in this position? LINE (dx
+ x
* 15, dy
+ y
* 15)-(dx
+ x
* 15 + 15, dy
+ y
* 15 + 15), clr
, BF
' yes, draw cube NEXT x
, y
' leave when all rows and columns processed
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED P1Paddle
AS MOVINGOBJECT
' need access to player 1 paddle SHARED P2Paddle
AS MOVINGOBJECT
' need access to player 2 paddle SHARED Puck
AS MOVINGOBJECT
' need access to puck
Puck.x = Puck.x + Puck.xv ' add X vector component to X coordinate
Puck.y = Puck.y + Puck.yv ' add Y vector component to Y coordinate
Puck.rect.x1 = Puck.x - PUCKSIZE \ 2 ' calculate puck rectangle coordinates
Puck.rect.y1 = Puck.y - PUCKSIZE \ 2
Puck.rect.x2 = Puck.x + PUCKSIZE \ 2
Puck.rect.y2 = Puck.y + PUCKSIZE \ 2
IF Puck.x
< PUCKXMIN
OR Puck.x
> PUCKXMAX
THEN ' is puck within a paddle area? IF SGN(Puck.xv
) = -1 AND RectCollide
(Puck.rect
, P1Paddle.rect
) THEN ' going left and hit paddle?
'''
' Velocity vector using ordinary orientation.
vx = Puck.xv
vy = Puck.yv
' Find place on paddle where puck is hitting as fraction from midpoint. Result in interval [-1,1]
hy = 2 * (Puck.y - P1Paddle.y) / (P1Paddle.rect.y1 - P1Paddle.rect.y2)
' Convert fractional height to an angle. Result in interval [-pi/2,pi/2]
' Create unit vectors based on imaginary circle.
' Re-calculate velocity vector in new basis.
wr = vx * rux + vy * ruy
wt = vx * tux + vy * tuy
' Reflect the velocity about the normal vector
wt = -wt
' Re-calculate veloctiy in old basis.
vx
= wr
* COS(ang
) - wt
* SIN(ang
) vy
= wr
* SIN(ang
) + wt
* COS(ang
)
' Restore original variables
Puck.xv = vx
Puck.yv = vy
'''
Puck.xv = -Puck.xv + .025 ' yes, reverse X vector
Puck.x = PUCKXMIN ' place puck to right of paddle
'** change in Y vector component here
SOUND 440, 1 ' audio report ELSEIF SGN(Puck.xv
) = 1 AND RectCollide
(Puck.rect
, P2Paddle.rect
) THEN ' going right and hit paddle? Puck.xv = -Puck.xv - .025 ' yes, reverse X vector
Puck.x = PUCKXMAX ' place puck to left of paddle
'** change in Y vector component here
SOUND 440, 1 ' audio report IF Puck.y
< PUCKYMIN
OR Puck.y
> PUCKYMAX
THEN ' is puck hitting a side line? IF SGN(Puck.yv
) = -1 THEN Puck.y
= PUCKYMIN
ELSE Puck.y
= PUCKYMAX
' yes, place puck above/below Puck.yv = -Puck.yv ' reverse Y vector
SOUND 880, 1 ' audio report IF Puck.x
< P1MISSED
OR Puck.x
> P2MISSED
THEN ' did puck hit edge of screen? Missed = TRUE ' yes, set missed indicator
SOUND 220, 1 ' audio report LINE (Puck.rect.x1
, Puck.rect.y1
)-(Puck.rect.x2
, Puck.rect.y2
), BRIGHTWHITE
, BF
' draw puck
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED P1Paddle
AS MOVINGOBJECT
' need access to player 1 paddle SHARED P2Paddle
AS MOVINGOBJECT
' need access to player 2 paddle SHARED Puck
AS MOVINGOBJECT
' need access to puck
P1Paddle.y = P1Paddle.y - PADDLESPEED ' yes, move paddle up
IF P1Paddle.y
< PADDLEYMIN
THEN P1Paddle.y
= PADDLEYMIN
' keep paddle in playing field P1Paddle.y = P1Paddle.y + PADDLESPEED ' yes, move paddle down
IF P1Paddle.y
> PADDLEYMAX
THEN P1Paddle.y
= PADDLEYMAX
' keep paddle in playing field IF Computer
THEN ' is player 1 playing computer? IF SGN(Puck.xv
) = 1 THEN ' yes, is puck heading for computer? IF Puck.y
- P2Paddle.y
< 0 THEN ' yes, is puck higher than center of paddle? P2Paddle.y
= P2Paddle.y
- .5 - RND(1) / 3 ' yes, move computer paddle up IF P2Paddle.y
< PADDLEYMIN
THEN P2Paddle.y
= PADDLEYMIN
' keep paddle in playing field ELSE ' no, puck is below center of paddle P2Paddle.y
= P2Paddle.y
+ .5 + RND(1) / 3 ' move computer paddle down IF P2Paddle.y
> PADDLEYMAX
THEN P2Paddle.y
= PADDLEYMAX
' keep paddle in playing field P2Paddle.y = P2Paddle.y - PADDLESPEED ' yes, move paddle up
IF P2Paddle.y
< PADDLEYMIN
THEN P2Paddle.y
= PADDLEYMIN
' keep paddle in playing field P2Paddle.y = P2Paddle.y + PADDLESPEED ' yes, move paddle down
IF P2Paddle.y
> PADDLEYMAX
THEN P2Paddle.y
= PADDLEYMAX
' keep paddle in playing field P1Paddle.rect.y1 = P1Paddle.y - PADDLEHEIGHT \ 2 ' calc player 1 paddle rectangle
P1Paddle.rect.y2 = P1Paddle.y + PADDLEHEIGHT \ 2
P2Paddle.rect.y1 = P2Paddle.y - PADDLEHEIGHT \ 2 ' calc player 2 paddle rectangle
P2Paddle.rect.y2 = P2Paddle.y + PADDLEHEIGHT \ 2
LINE (P1Paddle.rect.x1
, P1Paddle.rect.y1
)-(P1Paddle.rect.x2
, P1Paddle.rect.y2
), BRIGHTWHITE
, BF
' draw LINE (P2Paddle.rect.x1
, P2Paddle.rect.y1
)-(P2Paddle.rect.x2
, P2Paddle.rect.y2
), BRIGHTWHITE
, BF
' paddles
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED P1Paddle
AS MOVINGOBJECT
' need access toplayer 1 paddle SHARED P2Paddle
AS MOVINGOBJECT
' need access to player 2 paddle SHARED Puck
AS MOVINGOBJECT
' need access to puck SHARED PlayField
AS LONG ' need access to playing field image
P1Paddle.y = SCREENHEIGHT \ 2 ' reset paddle locations
P2Paddle.y = P1Paddle.y
Puck.xv = -1 '.5 ' reset puck X speed
Puck.y = SCREENHEIGHT \ 2
Missed = FALSE ' reset missed indicator
GameOver = FALSE ' reset game over indicator
Computer = FALSE ' player 2 is human
SOUND 440, 1 ' new game audio _LIMIT FPS
' limit frame speed _PUTIMAGE , PlayField
' restore playing field MovePaddles ' allow paddle movement
Frames = Frames + 1 ' increment frame counter
IF Frames
= FPS
THEN ' max frames reached? Frames = 0 ' yes, reset frame counter
Blink = 1 - Blink ' toggle blink bit
LOCATE (SCREENHEIGHT \
16), ((SCREENWIDTH \
8) - 51) \
2 + 1 ' position cursor on screen IF NumPlayers
= 0 THEN ' number of players chosen? PRINT " SELECT (1) OR (2) PLAYERS NOW : ESC TO EXIT ";
' no, display player select PRINT "(1) OR (2) PLAYERS : SPACEBAR TO PLAY : ESC TO EXIT";
' display all instructions LOCATE (SCREENHEIGHT \
16) - 2, ((SCREENWIDTH \
16) - 27) \
2 + 1 ' position cursor on screen PRINT "PLAYER 1: W = UP / S = DOWN" ' display keys to player 1 LOCATE (SCREENHEIGHT \
16) - 2, ((SCREENWIDTH \
8) - 31) \
2 + (SCREENWIDTH \
32) + 1 'position cursor SELECT CASE NumPlayers
' how many players selected? CASE 0:
PRINT " SELECT (1) OR (2) PLAYERS NOW " ' ask number of players CASE 1:
PRINT " PLAYER 2: THE CPU HAS CONTROL " ' inform computer playing CASE 2:
PRINT "PLAYER 2: UP ARROW / DOWN ARROW" ' display keys player 2 IF NumPlayers
<> 1 THEN ' has one player been chosen? NumPlayers = 1 ' yes, set players to 1
Computer = TRUE ' remember AI now has control
SOUND 440, 1 ' play affirmation beep SOUND 880, 1 ' play affirmation beep IF NumPlayers
<> 2 THEN ' has two player been chosen? NumPlayers = 2 ' yes, set players to 2
Computer = FALSE ' human is playing human
SOUND 440, 1 ' play affirmation beep SOUND 880, 1 ' play affirmation beep Pscore(0) = 0 ' reset scores
Pscore(1) = 0
UpdateScores ' update on screen scores
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED PlayField
AS LONG ' need access to playing field image SHARED PlayFieldCopy
AS LONG ' need access to playing field image copy SHARED P1Paddle
AS MOVINGOBJECT
' need access to player 1 paddle SHARED P2Paddle
AS MOVINGOBJECT
' need access to player 2 paddle SHARED Puck
AS MOVINGOBJECT
' need access to puck
'** do as many precalculations as possible for values that will not change
P1Paddle.x = PADDLEOFFSET + PADDLEWIDTH \ 2 ' calculate player 1 paddle X coordinate
P2Paddle.x = SCREENWIDTH - 1 - PADDLEOFFSET - PADDLEWIDTH \ 2 ' calculate player 2 paddle X coordinate
P1Paddle.rect.x1 = P1Paddle.x - PADDLEWIDTH \ 2 ' calculate player 1 paddle rectangle
P1Paddle.rect.x2 = P1Paddle.x + PADDLEWIDTH \ 2
P2Paddle.rect.x1 = P2Paddle.x - PADDLEWIDTH \ 2 ' calculate player 2 paddle rectangle
P2Paddle.rect.x2 = P2Paddle.x + PADDLEWIDTH \ 2
'** draw default playing field
PlayField
= _NEWIMAGE(SCREENWIDTH
, SCREENHEIGHT
, 32) ' create image _DEST PlayField
' make it destination LINE (0, 19)-(SCREENWIDTH
- 1, 29), BRIGHTWHITE
, BF
' draw top side line LINE (0, SCREENHEIGHT
- 30)-(SCREENWIDTH
- 1, SCREENHEIGHT
- 20), BRIGHTWHITE
, BF
' draw bottom side line FOR y
= 40 TO SCREENHEIGHT
- 52 STEP 30 ' cycle top to bottom LINE ((SCREENWIDTH \
2) - 5, y
)-((SCREENWIDTH \
2) + 5, y
+ 15), GRAY
, BF
' draw center lines _DEST 0 ' destination back to screen PlayFieldCopy
= _COPYIMAGE(PlayField
) ' save a copy of the playing field image Score(0) = "0000 00 00 0000" ' create numeric font data
Score(1) = " 1 1 1 1 1"
Score(2) = "222 22222 222"
Score(3) = "333 3333 3333"
Score(4) = "4 44 4444 4 4"
Score(5) = "5555 555 5555"
Score(6) = "6666 6666 6666"
Score(7) = "777 7 7 7 7"
Score(8) = "8888 88888 8888"
Score(9) = "9999 9999 9999"
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
RectCollide = FALSE ' assume no collision
IF Rect1.x2
>= Rect2.x1
THEN ' rect 1 lower right X >= rect 2 upper left X ? IF Rect1.x1
<= Rect2.x2
THEN ' rect 1 upper left X <= rect 2 lower right x ? IF Rect1.y2
>= Rect2.y1
THEN ' rect 1 lower right Y >= rect 2 upper left Y ? IF Rect1.y1
<= Rect2.y2
THEN ' rect 1 upper left Y <= rect 2 lower right Y ? RectCollide = TRUE ' if all 4 IFs TRUE then a collision is occurring
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
SHARED PlayField
AS LONG ' need access to playing field image SHARED PlayFieldCopy
AS LONG ' need access to playing field image copy
SYSTEM ' return to operating system
'-------------------------------------------------------------------------------------------------------------
'---------------------------------------------------------------------------------------------------------
'** math here (ugh)