
// Project: Arkanoid Tutorial 
// Created: 2017-01-13

// show all errors
SetErrorMode(2)

// set window properties
`Whilst developing, you can change the number assigned to Resolution to change the window size.

Resolution = 0

Select Resolution
  `If reosolution = 0, make the game fullscreen and as big as your monitor.
   Case 0 : SetWindowSize( GetDeviceWidth(), GetDeviceHeight(), 1 ) : EndCase
  `If reosolution = 1, make the game windowed, and as 3/4 of the monitor resolution.
   Case 1 : SetWindowSize( GetDeviceWidth() * 0.75, GetDeviceHeight() * 0.75, 0 ) : EndCase
  `If reosolution = 1, make the game windowed, and as 1/2 of the monitor resolution.
   Case 2 : SetWindowSize( GetDeviceWidth() * 0.50, GetDeviceHeight() * 0.50, 0 ) : EndCase
EndSelect
`Set the game to landscape only. (Fpr mobile devices)
SetOrientationAllowed( 0, 0, 1, 1 )

`Use a percentage based virtual resolution.
SetVirtualResolution( 100.0, 100.0 )

`If there are any black bars left at the left/right, tell the game to draw on them, so they're not unused.
SetScissor( GetScreenBoundsLeft(), 0.0, GetScreenBoundsRight(), 100.0 )

`Set the game to have the exact aspect ratio as your display.
SetDisplayAspect( ( GetDeviceWidth() + 0.0 ) / ( GetDeviceHeight() + 0.0 ) )

`Hide the mouse pointer on devices which have a mouse.
SetRawMouseVisible( 0 )

`Set the refresh rate to 30fps to save battery on mobile devices.
`Change 30 to 0, and the 0 for 1 for maximum framerate!
SetSyncRate( 0, 1 ) // 30fps instead of 60

`Change the text system to use TTF fonts, rather than bitmap fonts.
UseNewDefaultFonts( 1 )

`Loaad a ttf font, and store it ID in a variable for use later.
FontID = 1 : LoadFont( FontID, "fonts/neuropol x rg.ttf" )

`Changes the font of the print command to use the newly loaded ttf font.
SetPrintFont( FontID )



`This is the first user created function in the game.
`This function will load all the images and sounds we'll use in the game, as well as to draw the bricks.
Level_Initialise()

`This variable is used to control whether the computer wil lplay itself, or allow you to play.
AutoMode = 1

`This is your score.
Score = 0

`This is the ID for a new text object, used to display the score on screen.
ScoreTextID = 1

`Here, we create the text object for the score display.
CreateText( ScoreTextID, "Score: 0" )
`Tell the above text object to use our loaded ttf font.
SetTextFont( ScoreTextID, FontID )
`When we display the text onscreen, draw it centered.
SetTextAlignment( ScoreTextID, 1 )
`Set the text to be 10 units tall.
SetTextSize( ScoreTextID, 10.0 )



`This is the start of our main game loop.
Repeat
`Here we'll positino the score on screen, using triganometry to wiggle it a little.
`Lets explain this trig.
`50.0 + ( 5.0 * Sin( Timer() * 20.0 ) )
`The 50.0 is where we want to center our 'circle.' As it's a percentage system, this is 50% of the screen width, or the middle of the screen.
`The 5.0 is the radiius of the 'circle', or how far left/right we want the text to wiggle.
`Sin( Timer() * 20.0 ) will produce a number between -1.0 and 1.0, based off the Sine of Timer() multipled by 20.0. (We multiply it by 20 to speed the wiggle up.)
`So in all, We'll multiply the -1.0 to 1.0 sine value, by 5.0 to produce a number between -5.0 and 5.0, centered at 50.0 to produce our 'circle' of wiggle.
`Can you see how this applys to the Y axis just after the comma? It's the same principle, just using different numbers.
   SetTextPosition( ScoreTextID, 50.0 + ( 5.0 * Sin( Timer() * 20.0 ) ), 2.5 + ( 2.5 * Cos( Timer() * 40.0 ) ) )
`Similar to above, this next command will rotate the text based off a sine value.
   SetTextAngle( ScoreTextID, ( 5.0 * Sin( Timer() * 30.0 ) ) )
`And this line changes the text to reflect the changes to your score. (We don't need to call this every loop, we could just call it when the score changes below. ;) )
   SetTextString( ScoreTextID, "Score: " + Str( Score ) )
 
   
`Is the computer playing by itself?
   If AutoMode = 1
     `Yes the computer is playing, make the bat follow the ball. 
      SetSpritePositionByOffset( BatSprite, Clamp#( BallX#, 10.0, 90.0 ), 90.0 )
   Else
     `No, you're playing, so make the bat follow the mouse pointer.
      SetSpritePositionByOffset( BatSprite, Clamp#( GetPointerX(), 10.0, 90.0 ), 90.0 )
   EndIf
 

`This bit is where we actually control how the game is playing.   
   Select Ball.State
`Case 0 is before you start playing, the ball is stuck to the bat, ready to be launched.
      Case 0:
        `Make the ball follow the mouse pointer. (We control the ball, rather than the bat as if the computer is playing, the bat will automatically follow the ball.
        `If you're playing, then you control the bat anyway.)
        `Here, we use a command we haven't seen before, that isn't in blue. This is a user created function. (Like Level_Initialise we saw above)
        `Clamp# is a function which takes a number, and makes sure it stays between number 2 and number 3.
         SetSpritePositionByOffset( Ball.SpriteID, Clamp#( GetPointerX(), 10.0, 90.0 ), 85.0 )
        `This bit saves the X position of the ball to a value, so we can use it elsewhere. (Like when we moved the bat earlier.)
         BallX# = GetSpriteXByOffset( Ball.SpriteID )
         
         If GetPointerReleased() : `Have we pressed, and released the mouse pointer?
           `Start the ball moving 
            Ball.State = 1
           `Give the ball some left/right speed, based on where the mouse pointer is on the X axis. 
           `Over to the left, shoot the ball left. Over to the rightt, shoot the ball right.
            Ball.XSpeed# = 0.0 + ( 25.0 * ( ( GetPointerX() - 50.0 ) / 50.0 ) )
           `Give the ball some up/down speed. 
            Ball.YSpeed# = -25.0
         EndIf
      EndCase 


`Case 1 is when your currently playing. Balls are flying, and bats are swinging!
      Case 1:
        `Have we pressed, and released the mouse pointer? If so then switch between you and the computer playing.
         If GetPointerReleased() Then AutoMode = -Automode 

        `Get the balls current X position, after moving the ball by it's speed, multipled by the time it took between frames. This is a simple way of producing smooth movement.
        `This technique is called, timer based movement.
         BallX# = GetSpriteXByOffset( Ball.SpriteID ) + ( Ball.XSpeed# * GetFrameTime() )
        `As above, get the balls current Y position, after moving the ball by it's speed, multipled by the time it took between frames. 
         BallY# = GetSpriteYByOffset( Ball.SpriteID ) + ( Ball.YSpeed# * GetFrameTime() )
        `Now, we change the location of the ball image to it's new location 
         SetSpritePositionByOffset( Ball.SpriteID, BallX#, BallY# )
         
        `If the ball falls off the bottom of the screen, reset it to the bat, ready to be launched again.
        `If you have lives in your game, this would be where you remove one. Remember, in our percentage based system, 100.0 is 100% of the height, or the bottom of the screen
         If BallY# > 100.0
           `Change to state 0, explained above. 
            Ball.State = 0
         EndIf

        `If the ball hits the top of the screen, Make it bounce down. Since the ball is 5% wide, the center of the ball would be at position 2.5 when its top edge hits the screen edge.
         If ( BallY# < 2.5 )
           `This looks complicated, but it's really quite simple.
           `Since the ball speed could be anything, it's easier to invert it's value to change the balls direction, but since this is the top edge, we know the balls Y Movement should
           `be positive. So we take the balls YSpeed and make it a positive, or ABSOLUTE version of itself. -10.0 becomes 10.0, -3.0 becomes 3.0 and so on.
            Ball.YSpeed# = Abs( Ball.YSpeed# )
           `Play a random bounce sound, between 1, and 4)
            PlaySound( BuzzSound[ Random( 1, 4 ) ] )
         EndIf

         
        `Ok, this is starting to get complicated now. The next bit will control whether the ball hiots a brick or not.
        `This line will increment ID from 1, all the way up to whatever value is stored in CountID. This is setup in the Level_Initialise function below and will be explained
        `in more details there
         For ID = 1 To CountID
           `We stored every brick in a special data structure we called Bricks. We refer to a specific Brick object by the number within the square brackets. [ ID ]. (Remember, ID is a
           `number from 1 to CountID. Which in this game is 91) We're checking to see if this brick has a picture assigned to it. If it does, then it's being shown on screen.
            If Bricks[ ID ].SpriteID <> 0
              `Now we're checking if the ball has hot this single brick. If it has... 
               If GetSpriteCollision( Ball.SpriteID, Bricks[ ID ].SpriteID )
                  
                 `Create a new value called BallXPos to hold a number which describes if the balls X position is less than, equal to, or greater then the bricks X Position,
                  BallXPos = Within( BallX#, ( GetSpriteXByOffset( Bricks[ ID ].SpriteID ) - 3.25 ), ( GetSpriteXByOffset( Bricks[ ID ].SpriteID ) + 3.25 ) )
                 `As above, but on the Y axis. 
                  BallYPos = Within( BallY#, ( GetSpriteYByOffset( Bricks[ ID ].SpriteID ) - 2.5 ), ( GetSpriteYByOffset( Bricks[ ID ].SpriteID ) + 2.5 ) )
                  
                 `If the ball is directly under or above the brick, and is off to the left or right of it, make the ball bounce up/down. 
                  If ( BallXPos = 0 ) And ( BallYPos <> 0 ) Then Ball.YSpeed# = -Ball.YSpeed#
                 `If the ball is directly left or right of the brick, and is the same height of it, make the ball bounce left/right. 
                  If ( BallXPos <> 0 ) And ( BallYPos = 0 ) Then Ball.XSpeed# = -Ball.XSpeed#
                 `If the ball is both above/below and left/right of the brick, assume we hhit the side of teh brick and make it bounce the other way. (Either left/right)
                  If ( BallXPos <> 0 ) And ( BallYPos <> 0 ) Then Ball.XSpeed# = -Ball.XSpeed#

                 `Play a random bounce sound, between 1, and 4)
                  PlaySound( BuzzSound[ Random( 1, 4 ) ] )

                 `Remove the bricks on screen image..
                  DeleteSprite( Bricks[ ID ].SpriteID )
                 `Reset the bricks sprite id 
                  Bricks[ ID ].SpriteID = 0
                 `Increase the score by 10 
                  Score = Score + 10
                  
                 `Of note, we could do all kinds of processing here. Perhaps you need to hit a brick multiple times? We would do this checking in this section.

                 `The next line stops checking other bricks.
                  Exit
               EndIf
            EndIf
         Next ID
         
         
        `As with our top of the screen checking, this check is to see if the ball has hit the left side of the screen. 
         If ( BallX# < 2.5 )
            Ball.XSpeed# = Abs( -Ball.XSpeed# )
            PlaySound( BuzzSound[ Random( 1, 4 ) ] )
         EndIf

        `And again, this check is to see if the ball has hit the right side of the screen. 
         If ( BallX# > 97.5 )
            Ball.XSpeed# = 0.0 - ( Abs( Ball.XSpeed# ) )
            PlaySound( BuzzSound[ Random( 1, 4 ) ] )
         EndIf

        `Now we check if the ball has hit the bat. 
         If GetSpriteCollision( Ball.SpriteID, BatSprite )
           `If it has, change the balls Y Speed...
            Ball.YSpeed# = 0.0 - Abs( Ball.YSpeed# )
           `But also add a bit of variation to the balls XSpeed, depending on where on the bat yuo hit. (Much like we dit when launching bthe ball above.) 
            Ball.XSpeed# = Clamp#( Ball.XSpeed# + ( 25.0 * ( ( BallX# - GetPointerX() ) / 20.0 ) ), -25.0, 25.0 )
            
           `Play another random bounce sound. 
            PlaySound( BuzzSound[ Random( 1, 4 ) ] )
         EndIf
      EndCase
   EndSelect

  `This command updates everything on screen. 
   Sync()
`This is teh end of our main game loop, here we'll continue until you press the escape key, or the back button on your mobile device.
Until GetRawKeyReleased( 27 )
`Show the mouse pointer.
SetRawMouseVisible( 1 )
`Stop the game, and return to the desktop/home screen.
End


`Earlier , I talked about a special data structure we called Bricks, well here is where we set it up.
`This is a special TYPE of struction, and we've called this object tBricks.
Type tBricks
`It contains the Integer SriteID
   SpriteID
`The Integer Colour. (Which isn;t used in this example.)
   Colour
`And the Integer Strength, (Remember above when I talked about needing to hit a brick multiple times? We could use Strength to help with this. (Or any other variable name you choose)
   Strength
EndType

`Much like the Brick object above, here we create a structure to store ball details.
Type tBall
   XSpeed#
   YSpeed#
   SpriteID
   State
EndType

`This is where we setup our game environment, and potition the bricks on screen.
Function Level_Initialise()
`First off, load all the images, and store their IDs in Global variables. Global means they can be used anywhere in the program.
   Global BallImage : BallImage = LoadImage( "textures/ball.png" )
   Global BlockImage : BlockImage = LoadImage( "textures/brick.png" )
   Global BatImage : BatImage = LoadImage( "textures/bat.png" )
   Global BatSprite : BatSprite = CreateSprite( BatImage ) : SetSpritePositionByOffset( BatSprite, 50.0, 90.0 ) : SetSpriteSize( BatSprite, 20.0, 5.0 )

`This is where we create a ball object, and set it to be of type tBall, which we declared above.   
   Global Ball As tBall
`Now we have our Ball object, we can give it some properties, such as it's image
   Ball.SpriteID = CreateSprite( BallImage )
`Its speed
   Ball.XSpeed# = 0.0
   Ball.YSpeed# = 0.0
`And it's current state. Remember, 0 means stuch to the bat, ready for launch.
   Ball.State = 0
`This is where we set the size of the ball. The first part is the sprite object, the -1 means set the width depending on the height you choose, and we set the height to 5.0%.
   SetSpriteSize( Ball.SpriteID, -1.0, 5.0 )
`For the collision check, we set the shape og=f the ball. 0 means box, 1 means circle, and 2 means complex shape.
   SetSpriteShape( Ball.SpriteID, 1 )
   

`Initialise and load all of the sounds. beloe, we create an ARRAY of Integers called BuzzSound large enough for 5 sounds. (Arrays start at 0 ;) ) We saw earlier how we use arrays.
   Global BuzzSound As Integer[ 4 ]
   For Id = 1 To 4
     `Here, we load the WAV files in and store their IDs in our BuzzSound array.
      BuzzSound[ Id ] = LoadSound( "sounds/buzz0" + Str( Id ) + ".wav" )
   Next Id
   
`Next, we create a new Bricks array of type tBricks large enough to hold information on 100 bricks.
   Global Bricks As tBricks[ 100 ]

`This is where we position the bricks on screen.
`Each brick will be 6.5% wide.
   Width# = 6.5
`Here we create a new global variable, which we'll use to store the number of Bricks we create.   
   Global CountID : CountID = 0
   
`The next two lines will set the X# and Y# position of the bricks. Starting at 10% on the X and Y axis, and continuing up to 40 on teh Y, and 90 on the X axis.
`The Y axis will put a brick every 5% down, (The height of the brick), and the X axis will put one every Width# (6.5%).
   For Y# = 10 To 40 Step 5.0
      For X# = 10 To 90 Step Width#

        `We've already loaded the image from teh hard drive, here we give the sprite its on screen Sprite, using the loaded image. 
         Sprite = CreateSprite( BlockImage )
        `We position the brick in its correct place. 
         SetSpritePositionByOffset( Sprite, X#, Y# )
        `Make it the correct size. (6.5% x 5%) 
         SetSpriteSize( Sprite, Width#, 5.0 )
        `Finally, we give it a random Red, Green, and Blue color value) 
         SetSpriteColor( Sprite, Random( 64, 255 ), Random( 64, 255 ), Random( 64, 255 ), 255 )
         
        `We increment the number of bricks by 1 
         CountID = CountID + 1
        `And populate the Bricks objects with its properties. 
         Bricks[ CountID ].SpriteID = Sprite
         Bricks[ CountID ].Strength = 1 : `Remembering our multiplr hit check, this is where we set it up. We could have strength 1 for a single hit, 2 for 2 hits and so on.
         Bricks[ CountID ].Colour = 0
      Next X#
   Next Y#
EndFunction

`This is a FUNCTION that I created to ensure that Val# is never any less that MinVal# and never larger than MaxVal#
Function Clamp#( Val#, MinVal#, MaxVal# )
   If Val# < MinVal# Then Val# = MinVal#
   If Val# > MaxVal# Then Val# = MaxVal#
EndFunction Val#

`This FUNCTION will return -1 if Val is less that MinVal#, 0 if it's between Min and MaxVal#, and 1 if it's greater that MaxVal#
Function Within( Val#, MinVal#, MaxVal# )
   If Val# < MinVal# Then ExitFunction -1
   If Val# > MaxVal# Then ExitFunction 1
EndFunction 0
