` Pong (gbark)
` DBPro Coding Challenge
` 01/11/08 - 01/20/08
 
` Screen settings
sync on : sync rate 60
set display mode 640, 480, 32
backdrop on : color backdrop 0
set ambient light 35 : hide light 0
autocam off
set text font "Terminal" : set text size 72
hide mouse
 
 
` Create textures for objects
gosub MakeTextures
 
 
` Create game objects
` (Player)
make object box 1, 30, 70, 16
texture object 1, 1
` (Cpu)
make object box 2, 30, 70, 16
texture object 2, 2
` (Ball)
make object sphere 3, 30
texture object 3, 3
` (Ball Light)
make light 1
set light range 1, 300
` (Ball Glow Object)
make object plain 4, 300, 300
set object ambient 4, 0
set object light 4, 0
texture object 4, 4
ghost object on 4
disable object zdepth 4
 
 
` Set camera
position camera 0, 0, -500
point camera 0, 0, 0
 
 
` Prepare for main menu
gameMode = 0
 
 
` Main loop
do
   ` Run menu or game loop, depending on current game mode
   if gameMode = 0 then gosub MenuLoop else gosub GameLoop
 
   sync
loop
 
 
` Main menu loop
MenuLoop:
   ` Show the title
   gosub DrawTitle
 
   ` Check for game start
   gosub StartGame
return
 
 
` Main game loop
GameLoop:
   ` Move player's paddle
   gosub MovePlayer
 
   ` Move cpu's paddle
   gosub MoveCpu
 
   ` Update the ball
   gosub UpdateBall
 
   ` Draw scores
   gosub DrawScore
return
 
 
` Prepare variables for a new game
NewGame:
   ` Reset scores
   playerScore = 0
   cpuScore = 0
 
   ` Prepare for first round
   gosub NextRound
 
   ` Spawn ball in the field center for the first round
   ballX = 0
   ballY = 0
   ballServe = 0
 
   ` Set the ball in a random direction
   ballXSpeed = rnd(2)+1 : if rnd(1)=1 then ballXSpeed = -ballXSpeed
   ballYSpeed = rnd(2)+1 : if rnd(1)=1 then ballYSpeed = -ballYSpeed
return
 
 
` Reset paddles and ball for a new round
NextRound:
   ` Reset paddle positions
   playerY = 0 : playerKick = 0
   cpuY = 0 : cpuKick = 0
 
   ` Reset ball speeds
   ballXSpeed = 0
   ballYSpeed = 0
 
   ` Set cpu difficulty based on score difference
   ` (1 = Easy, 5 = Normal, 10 = Hard)
   cpuLevel = 5 + (playerScore - cpuScore)
   if cpuLevel > 10 then cpuLevel = 10
   if cpuLevel < 1 then cpuLevel = 1
 
   ` Ball is moving slowly at the beginning of the round, so cpu should not miss
   cpuError = 0
return
 
 
` Control player's paddle
MovePlayer:
   ` Determine where to move based on arrow keys
   playerYSpeed = (upkey()-downkey())*5
 
   ` No movement if the ball went past
   if ballX < -300 then playerYSpeed = 0
 
   ` Move player paddle
   inc playerY, playerYSpeed
 
   ` Keep paddle onscreen
   if playerY > 260 then playerY = 260
   if playerY < -260 then playerY = -260
 
   ` Kick paddle with space
   sp = spacekey()
   if sp = 1 and keySpace = 0 and playerKick = 0 then playerKick = 1
   keySpace = sp
 
   ` Update paddle kick
   if playerKick > 0 then inc playerKick, 35
   if playerKick > 180 then playerKick = 0
 
   ` Update paddle position
   position object 1, -300+sin(playerKick)*40, playerY, 0
return
 
 
` Control cpu's paddle
MoveCpu:
   ` Guess where the ball will end up
   if ballXSpeed > 0 or cpuLevel >= 6 and ballServe = 0
      ` Move to where the ball will end up
      cpuGuessY = PredictBall(ballX, ballY, ballXSpeed, ballYSpeed, 270, (cpuLevel+10)*10)
      ` Add some error to the cpu's judgement (more error if the cpu is winning)
      inc cpuGuessY, cpuError
   else
      ` Return to center screen
      if ballServe = 0 then cpuGuessY = 0
   endif
 
   ` Determine where to move based on guess
   if cpuY > cpuGuessY then cpuYSpeed = -5
   if cpuY < cpuGuessY then cpuYSpeed = 5
   if abs(cpuY - cpuGuessY) < 5 then cpuYSpeed = 0
 
   ` No movement if the ball went past
   if ballX > 300 then cpuYSpeed = 0
 
   ` Move cpu paddle
   inc cpuY, cpuYSpeed
 
   ` Keep paddle onscreen
   if cpuY > 260 then cpuY = 260
   if cpuY < -260 then cpuY = -260
 
   ` Kick the ball if it is close (and the cpu is a high enough level)
   if cpuLevel >= 7 and ballXSpeed > 0 and ballX+ballXSpeed > 260 and ballY > cpuY-50 and ballY < cpuY+50 and cpuKick = 0 and ballServe = 0
      cpuKick = 1
   endif
 
   ` Also kick the ball if it is time to serve
   if ballServe = -1 and abs(cpuY-cpuGuessY)<5 and cpuKick = 0
      cpuKick = 1
   endif
 
   ` Update paddle kick
   if cpuKick > 0 then inc cpuKick, 35
   if cpuKick > 180 then cpuKick = 0
 
   ` Update paddle position
   position object 2, 300-sin(cpuKick)*40, cpuY, 0
return
 
 
` Move the ball around the field
UpdateBall:
   ` Check if a player is serving or not
   if ballServe <> 0
      ` A player is serving the ball
      gosub ServeBall
   else
      ` The ball is moving freely around the field
      gosub MoveBall
   endif
 
   ` Update ball position
   position object 3, ballX, ballY, 0
   position light 1, ballX, ballY, -50
   position object 4, ballX, ballY, 0
return
 
 
` Ball is in SERVE MODE
ServeBall:
   ` Who is serving the ball?
   if ballServe = 1
      ` Player serving
      ballX = -270
      ballY = playerY
 
      ` When player kicks, ball has been served
      if playerKick > 0
         ` Ball should be in normal movement mode
         ballServe = 0
         ` Send the ball to the right
         ballXSpeed = 1
         ` Vertical direction should be the same as the paddle's
         ballYSpeed = rnd(3)+2
         if (rnd(1) = 1 or SignVal(playerYSpeed) = -1) and SignVal(playerYSpeed) <> 1 then ballYSpeed = -ballYSpeed
      endif
   else
      ` Cpu serving
      ballX = 270
      ballY = cpuY
 
      ` When cpu kicks, ball has been served
      if cpuKick > 0
         ` Ball should be in normal movement mode
         ballServe = 0
         ` Send the ball to the left
         ballXSpeed = -1
         ` Vertical direction should be the same as the paddle's
         ballYSpeed = rnd(3)+2
         if (rnd(1) = 1 or SignVal(cpuYSpeed) = -1) and SignVal(cpuYSpeed) <> 1 then ballYSpeed = -ballYSpeed
      endif
   endif
return
 
 
` Ball is in NORMAL MOVE MODE
MoveBall:
   ` Move ball
   inc ballX, ballXSpeed
   inc ballY, ballYSpeed
 
   ` Bounce off the top and bottom edges
   if ballY > 280 or ballY < -280
      ballYSpeed = -ballYSpeed
      dec ballY, ballY mod 280
   endif
 
   ` Bounce off player paddle
   if ballX < -270+sin(playerKick)*40 and ballX > -300 and ballY > playerY-50 and ballY < playerY+50
      ` Bounce towards the right
      ballX = -270+(playerKick>0)*40 : ballXSpeed = abs(ballXSpeed)
      ` Bounce faster if player is kicking
      if playerKick > 0 then inc ballXSpeed, SignVal(ballXSpeed)*5
      ` Bounce vertically if ball and paddle are moving in the same direction
      if playerYSpeed * ballYSpeed > 0
         dec ballXSpeed, SignVal(ballXSpeed)
         inc ballYSpeed, SignVal(ballYSpeed)
      endif
      ` Bounce horizontally if ball and paddle are moving in the opposite direction
      if playerYSpeed * ballYSpeed < 0
         inc ballXSpeed, SignVal(ballXSpeed)
         dec ballYSpeed, SignVal(ballYSpeed)
      endif
   endif
 
   ` Bounce off cpu paddle
   if ballX > 270-sin(cpuKick)*40 and ballX < 300 and ballY > cpuY-50 and ballY < cpuY+50
      ` Bounce towards the left
      ballX = 270-(cpuKick>0)*40 : ballXSpeed = -abs(ballXSpeed)
      ` Bounce faster if cpu is kicking
      if cpuKick > 0 then inc ballXSpeed, SignVal(ballXSpeed)*5
      ` Bounce vertically if ball and paddle are moving in the same direction
      if cpuYSpeed * ballYSpeed > 0
         dec ballXSpeed, SignVal(ballXSpeed)
         inc ballYSpeed, SignVal(ballYSpeed)
      endif
      ` Bounce horizontally if ball and paddle are moving in the opposite direction
      if cpuYSpeed * ballYSpeed < 0
         inc ballXSpeed, SignVal(ballXSpeed)
         dec ballYSpeed, SignVal(ballYSpeed)
      endif
      ` Set some error for the cpu so it's not "perfect"
      cpuError = (rnd(30-cpuLevel*2)-rnd(30-cpuLevel*2))*(3+rnd(abs(ballYSpeed)/3))
   endif
 
   ` Keep ball from moving too fast or too slow
   if abs(ballXSpeed) < 2 then inc ballXSpeed, ballXSpeed mod 2      ` Min X speed
   if abs(ballYSpeed) < 2 then inc ballYSpeed, ballYSpeed mod 2      ` Min Y speed
   if abs(ballXSpeed) > 20 then dec ballXSpeed, ballXSpeed mod 20    ` Max X speed
   if abs(ballYSpeed) > 20 then dec ballYSpeed, ballYSpeed mod 20    ` Max Y speed
 
   ` Check for a score
   if abs(ballX) > 550
      ` Check who scored
      if ballX > 550
         ` Player scored
         inc playerScore
         ` Cpu to serve
         ballServe = -1
         cpuGuessY = rnd(260)-rnd(260)
      else
         ` Cpu scored
         inc cpuScore
         ` Player to serve
         ballServe = 1
         ` Cpu to wait
         cpuGuessY = 0
      endif
 
      ` Update stats
      gosub DrawScore
 
      ` Update screen and sleep
      sync : sleep 1500
 
      ` Check if anybody has won yet
      if playerScore < 10 and cpuScore < 10
         ` No win yet, next round
         gosub NextRound
      else
         ` Game over, display who won
         if playerScore = 10
            ` Player won
            center text 320, 100, "You Win!"
         else
            ` Cpu won
            center text 320, 100, "You Lose."
         endif
         ` Update the screen and sleep
         sync : sleep 3000
         ` Return to title screen
         gameMode = 0
         set text size 72
      endif
   endif
return
 
 
` Guess where ball will end up when it reached the cpu
function PredictBall(x, y, xSpeed, ySpeed, targetX, maxLook)
   ` Assume the ball is moving to the right (from the player)
   xSpeed = abs(xSpeed)
 
   ` Determine distance from ball to target
   dist = abs(targetX-x)
   if dist > maxLook then dist = maxLook
 
   ` Guess where the ball will end up
   if xSpeed <> 0 then guessY = y + (ySpeed*dist/xSpeed)
 
   ` "Bounce" the guess off the screen edges
   if guessY > 280 then guessY = (280-guessY)+280
   if guessY < -280 then guessY = (-280-guessY)-280
endfunction guessY
 
 
` Draw each player's score
DrawScore:
   ink rgb(200, 200, 200), 0
 
   ` Player score
   text 200, 30, str$(playerScore)
 
   ` Cpu score
   text 400, 30, str$(cpuScore)
return
 
 
` Draw the title screen
DrawTitle:
   ` Title
   ink rgb(255, 255, 255), 0
   center text 320, 50, "Pong"
 
   ` Space to start
   ink rgb(200, 200, 200), 0
   center text 320, 400, "SPACE To Start"
 
   ` Position paddles and ball
   position object 1, -300, 0, 0    ` Player paddle
   position object 2, 300, 0, 0     ` Cpu paddle
   position object 3, 0, 0, 0       ` Ball
   position object 4, 0, 0, 0       ` Ball glow
   position light 1, 0, 0, 0        ` Ball light
return
 
 
` Start the game when space is pressed
StartGame:
   ` Check for space key
   sp = spacekey()
   if sp = 1 and keySpace = 0
      ` Prepare main game
      gameMode = 1
      set text size 48
      gosub NewGame
   endif
   keySpace = sp
return
 
 
` Make textures for game objects
MakeTextures:
   ` Player paddle
   create bitmap 1, 30, 70
   ` Green background
   ink rgb(0, 255, 0), 0
   box 0, 0, 30, 70
   ` Dark green lines
   ink rgb(0, 128, 0), 0
   line 5, 5, 25, 5 : line 25, 5, 25, 65 : line 25, 65, 5, 65 : line 5, 65, 5, 5
   line 10, 10, 20, 10 : line 20, 10, 20, 60 : line 20, 60, 10, 60 : line 10, 60, 10, 10
   ` Store image
   get image 1, 0, 0, 30, 70
   delete bitmap 1
 
   ` Cpu paddle
   create bitmap 1, 30, 70
   ` Red background
   ink rgb(255, 0, 0), 0
   box 0, 0, 30, 70
   ` Dark red lines
   ink rgb(128, 0, 0), 0
   line 5, 5, 25, 5 : line 25, 5, 25, 65 : line 25, 65, 5, 65 : line 5, 65, 5, 5
   line 10, 10, 20, 10 : line 20, 10, 20, 60 : line 20, 60, 10, 60 : line 10, 60, 10, 10
   ` Store image
   get image 2, 0, 0, 30, 70
   delete bitmap 1
 
   ` Ball
   create bitmap 1, 8, 8
   ` Yellow background
   ink rgb(255, 255, 0), 0
   box 0, 0, 8, 8
   ` Store image
   get image 3, 0, 0, 8, 8
   delete bitmap 1
 
   ` Ball glow
   create bitmap 1, 64, 64
   ` Black background
   ink 0, 0
   box 0, 0, 64, 64
   ` Add glow
   for x = 0 to 64
      for y = 0 to 64
         d# = sqrt((32-x)*(32-x) + (32-y)*(32-y))
         c = 255-(d#/32)*255
         if d# < 32 then dot x, y, rgb(c, c, 0)
      next y
   next x
   ` Store image
   get image 4, 0, 0, 64, 64
   delete bitmap 1
return
 
 
` Return the positive/negative sign of any integer
function SignVal(n)
   if n < 0 then exitfunction -1
   if n > 0 then exitfunction 1
endfunction 0