`Started: 17/04/2005 -> 3:20am
`Description: short breakout program to help any new programmers to visualise other methods of detecting collision,
`             and how to use user defined types effectively.
`             not a particularly great game tho ^_^
 
`constant describing the force of gravity
#constant grav = 0.2
#constant padSize = 50.0
#constant blockSize = 30.0
#constant tailSize = 5
 
`type describing a 2D vector
type vector
   x as float
   y as float
endtype
 
`type describing an object with movement and position vectors
type object
   move as vector
   pos as vector
endtype
 
`type describing a surface with force and position vectors
type surface
   force as vector
   pos as vector
endtype
 
`type describing a entity which has a position a force and may be active or inactive
`i refuse to use DarkBasic boolean as it causes many problems in some functions where they wont work
`i strongly reccomend you do NOT use booleans in your program as it causes many internal bugs of darkbasic to surface
type entity
   isActive as integer
   pos as vector
endtype
 
type level
   levelNum as integer
   levelScore as integer
   levelLife as integer
   wallPos as integer
   brickColor as integer
   ballSpeed as integer
endtype
 
arena as level
   arena.levelNum = 1
   arena.levelScore = 0
   arena.levelLife = 10
   arena.wallPos = 5
   arena.brickColor = rgb(rnd(128)+127,rnd(128)+127,rnd(128)+127)
   arena.ballSpeed = 15
 
`variable to hold the position and movement vector of the ball
ball as object
   ball.move.x = 5
   ball.move.y = -10
   ball.pos.x = 400
   ball.pos.y = 400
 
`define an array to hold the positions and forces of the walls
dim wall(1) as surface
   `Left wall
   wall(0).pos.x = 10
   wall(0).force.x = 10
   `Right Wall
   wall(1).pos.x = 790
   wall(1).force.x = -10
   `Top Wall
   wall(0).pos.y = 10
   wall(0).force.y = 10
   `Lower Wall
   wall(1).pos.y = 590
   wall(1).force.y = 0
 
`define an array to hold the positions and forces of the pads
pad as surface
   `values for the 1st pad
   pad.pos.y = 500
   pad.force.x = -500
   pad.force.y = -1000
 
`define an array to hold the positions of the ball tail
dim tail(tailSize) as vector
   for tailNum = 0 to 5
      tail(tailNum).x = ball.pos.x
      tail(tailNum).y = ball.pos.y
   next tailNum
 
`define an array for the blocks
dim blocks(49) as entity
   for xPos = 0 to 9
   for yPos = 0 to 4
      blocks(blockNum).isActive = 1
      blocks(blockNum).pos.x = 130 + 60 * xPos
      blocks(blockNum).pos.y = 120 + 40 * yPos
      inc blockNum
   next yPos
   next xPos
 
dim highScore(9) as integer
   if file exist("tomuscore1.dat") = 0
      open to write 1,"tomuscore1.dat"
         for score = 0 to 9
            highScore(score) = 300000 / (score + 1)
            write file 1 , highScore(score)
         next score
      close file 1
   else
      open to read 1,"tomuscore1.dat"
         for score = 0 to 9
            read file 1,highScore(score)
         next score
      close file 1
   endif
 
 
`setup directX display
set display mode 800,600,32
 
`define the sychronisation rate and flip the buffers twice
sync on : sync rate 30
sync : sync
 
`hide the mouse cursor
hide mouse
 
do
   answer = startGame()
   arena.levelNum = 1
   arena.levelScore = 0
   arena.levelLife = 10
   arena.wallPos = 5
   arena.brickColor = rgb(rnd(128)+127,rnd(128)+127,rnd(128)+127)
   arena.ballSpeed = 15
   `Left wall
   wall(0).pos.x = 10
   wall(0).force.x = 10
   `Right Wall
   wall(1).pos.x = 790
   wall(1).force.x = -10
   `Top Wall
   wall(0).pos.y = 10
   wall(0).force.y = 10
   `Lower Wall
   wall(1).pos.y = 590
   wall(1).force.y = 0
   select answer
      case 1
         playGame()
      endcase
      case 2
         endGame()
      endcase
      case 3
         end
      endcase
   endselect
loop
 
function playGame()
   `main loop
   repeat
      ink rgb(0,128,255),0
      text 10,0,"Level: " + str$(arena.levelNum)
      text 600,0,"Score: " + str$(arena.levelScore)
      text 700,0,"Lives: " + str$(arena.levelLife)
      center text 400,0,"Press "Q" to quit..."
      `user controls the pad and checks for collision with the wall
      pad.pos.x = mousex()
      pad.force.x = mousemovex()
      if abs(pad.pos.x - wall(0).pos.x) < padSize then pad.pos.x = wall(0).pos.x + padSize
      if abs(pad.pos.x - wall(1).pos.x) < padSize then pad.pos.x = wall(1).pos.x - padSize
      `allow user to reset the ball
      if ball.pos.y = wall(1).pos.y
         if spacekey()
            ball.move.x = rnd(10)-5
            ball.move.y = - arena.ballSpeed
            ball.pos.x = 400
            ball.pos.y = 400
            dec arena.levelLife
         else
            center text 400 , 400 , "Press Space To Reset Ball..."
         endif
      endif
      `calculate the users x force
      userX# = curveValue( (rightkey()-leftkey()) * 0.4 , userX# , 10 )
      `call ball control function to refresh the ball data
      controlBall()
      `draw walls
      drawBox( wall(0).pos.x , wall(0).pos.y , wall(1).pos.x , wall(1).pos.y , rgb(255,0,0) )
      drawBox( wall(0).pos.x + 10 , wall(0).pos.y + 10 , wall(1).pos.x - 10 , wall(1).pos.y - 10 , rgb(128,0,0) )
      drawBox( wall(0).pos.x + 20 , wall(0).pos.y + 20 , wall(1).pos.x - 20 , wall(1).pos.y - 20 , rgb(64,0,0) )
      drawBox( wall(0).pos.x + 30 , wall(0).pos.y + 30 , wall(1).pos.x - 30 , wall(1).pos.y - 30 , rgb(32,0,0) )
      `draw tail of ball first so the ball with have priority on screen display
      for tailPos = 0 to 5
         drawBall( tail(tailPos).x , tail(tailPos).y , 5 , 2 , rgb(255,51*tailPos,0) )
      next tailPos
      inc tailNum
      `control move the pointer to the next ball
      if tailNum > tailSize then tailNum = 0
      tail(tailNum).x = ball.pos.x
      tail(tailNum).y = ball.pos.y
      `draw ball
      drawBall( ball.pos.x , ball.pos.y , 10 , 3 , rgb(0,255,128) )
      `draw pads
      drawPad( pad.pos.x , pad.pos.y , padSize , rgb(255,128,0) )
      `draw blocks
      for blockNum = 0 to 49
         if blocks(blockNum).isActive = 1 then drawPad( blocks(blockNum).pos.x , blocks(blockNum).pos.y , blockSize , arena.brickColor )
      next blockNum
      `synchronise the screen
      sync
      `clear screen to avoid pixel dragging
      cls
      `end main loop
      if arena.levelLife < 0 || upper$(inkey$()) = "Q"
         endGame()
         mainmenu = 1
      endif
   until mainmenu = 1
endfunction
 
`function to start game
function startGame()
   cls
   size = text size()
   set text size size + 20
      ink RGB(0,0,255) , 0
      center text 400 , 100 , "Tomu Breakout!"
      drawPad(400,115,200,RGB(0,128,255))
   set text size size
      ink RGB(0,128,255) , 0
      center text 400 , 200 , "1 - Play Game"
      center text 400 , 250 , "2 - High Scores"
      center text 400 , 300 , "3 - Exit"
   sync
   repeat
      answer = val(inkey$())
   until answer > 0
endfunction answer
 
`function to end the game
function endGame()
   if arena.levelScore > highScore(9)
      highScore(9) = arena.levelScore
      place = 9
      for score = 0 to 8
         pos = 9 - score
         if highScore(pos) > highScore(pos - 1)
            temp = highScore(pos - 1)
            highScore(pos - 1) = highScore(pos)
            highScore(pos) = temp
            dec place
         endif
      next score
      delete file "tomuscore1.dat"
      open to write 1 , "tomuscore1.dat"
         for score = 0 to 9
            write file 1 , highScore(score)
         next score
      close file 1
   endif
   cls
   center text 400 , 100 , "Scores:"
   for score = 0 to 9
      if score = place
         ink RGB(0,0,255),0o
      else
         if score = 0
            ink RGB(255,128,0),0
         else
            ink RGB(0,128,255),0
         endif
      endif
      center text 400 , 150 + score * 30 , str$(score+1) + ": " + str$(highScore(score))
   next score
   if arena.levelScore > 0 then center text 400 , 100 + (score + 2) * 30 , "Your Score: " + str$(arena.levelScore)
   center text 400 , 100 + (score + 3) * 30 , "press any key to end..."
   sync
   wait key
endfunction
 
`function to control movement of the ball
function controlBall()
   null = make vector2(1)
      set vector2 1 , ball.move.x , ball.move.y
      magnitude# = length vector2(1)
      divide vector2 1 , magnitude#
      repeat
         for blockNum = 0 to 49
            if blocks(blockNum).isActive = 1
               if ( abs(ball.pos.x - blocks(blockNum).pos.x) <= blockSize ) && ( abs(ball.pos.y - blocks(blockNum).pos.y) <= blockSize / 4 )
                  blocks(blockNum).isActive = 0
                  inc arena.levelScore , 100 * arena.levelNum
                  ball.move.y = - ball.move.y
               endif
               inc totalBlocks
            endif
         next blockNum
         if totalBlocks = 0
            for blockNum = 0 to 49
               blocks(blockNum).isActive = 1
            next blockNum
            if arena.levelNum < 13
               inc wall(0).pos.x , arena.wallPos
               inc wall(0).pos.y , arena.wallPos
               dec wall(1).pos.x , arena.wallPos
               dec wall(1).pos.y , arena.wallPos
            endif
            inc arena.ballSpeed , 2
            inc arena.levelNum , 1
            arena.brickColor = rgb(rnd(128)+127,rnd(128)+127,rnd(128)+127)
         endif
         if ( abs(ball.pos.x - pad.pos.x) <= padSize ) && ( abs(ball.pos.y - pad.pos.y) <= padSize / 4 )
            ball.move.y = - arena.ballSpeed
            inc ball.move.x , ( ball.pos.x - pad.pos.x ) / magnitude#
            `swept collision hack :D
            inc ball.move.x , pad.force.x / magnitude# / 12
         endif
         `work out the inverse square law of the forces on the walls and add pad force
         `inverse square law = ( 1 / distance^2 ) * force
         xForce# = ((1 / (3+abs(ball.pos.x - wall(0).pos.x))^2) * wall(0).force.x) + ((1 / (1+abs(ball.pos.x - wall(1).pos.x))^2) * wall(1).force.x / magnitude#)
         yForce# = ((3 / (1+abs(ball.pos.y - wall(0).pos.y))^2) * wall(0).force.y) + ((1 / (1+abs(ball.pos.y - wall(1).pos.y))^2) * wall(1).force.y / magnitude#)
         `add the wall force to the ball movement vectors. add gravity
         inc ball.move.x , xForce#
         inc ball.move.y , yForce# + grav / magnitude#
         `add movement vector and user input to the ball position
         inc ball.pos.x , ball.move.x / magnitude#
         inc ball.pos.y , ball.move.y / magnitude#
         `check for actual wall collision
         if ball.pos.x < wall(0).pos.x
            ball.pos.x = wall(0).pos.x
         endif
         if ball.pos.x > wall(1).pos.x
            ball.pos.x = wall(1).pos.x
         endif
         if ball.pos.y < wall(0).pos.y
            ball.pos.y = wall(0).pos.y
         endif
         if ball.pos.y > wall(1).pos.y
            ball.pos.y = wall(1).pos.y
         endif
         inc count# , 1 / magnitude#
      until count# > 1
   null = delete vector2(1)
endfunction
 
`function to draw the ball
function drawBall( xPos , yPos , size# , segments , color)
   increment# = size# / segments
   for seg = 1 to segments
      ink rgb( rgbr(color)/seg , rgbg(color)/seg , rgbb(color)/seg ) , 0
      circle xPos , yPos , increment# * seg
   next seg
endfunction
 
`function to draw a box
function drawBox( lf , up , rt , dn , color)
   ink color , 0
   line lf , up , rt , up
   line rt , up , rt , dn
   line rt , dn , lf , dn
   line lf , dn , lf , up
endfunction
 
`function to draw a pad
function drawPad( xPos , yPos , size# , color )
   ink color , 0
   drawBox( xPos - size# , yPos - size# / 4 , xPos + size# , yPos + size# / 4 , rgb( rgbr(color)/2 , rgbg(color)/2 , rgbb(color)/2 ) )
   drawBox( xPos - (size# * 0.5) , yPos - (size# * 0.5) / 4 , xPos + (size# * 0.5) , yPos + (size# * 0.5) / 4 , color )
endfunction