UnderwareDESIGN

PlayBASIC => Resources => Tutorials => Topic started by: ken tankerous on October 09, 2005, 05:08:10 PM

Title: YA Pong Game
Post by: ken tankerous on October 09, 2005, 05:08:10 PM
Another Pong game :P

Hopefully it will help someone. It's my first real attempt at programming
anything in PB and it was quite fun  :D

[pbcode]
; PROJECT : PbPong
; AUTHOR  : Ken Tankerous
; CREATED : 10/4/2005
; EDITED  : 10/9/2005
;
; ---------------------------------------------------------------------
; My version of Pong using PlayBasic - the main twist is the ball
; stays in play. Your opponent gains points if you allow the ball
; to hit the wall behind your paddle. You will also give your opponent
; more points if you do not get out of the way quick enough when the ball
; bounces off the wall behind you (the AI *tries* it's best to get out of
; the way). If the player or AI hits the ball with a moving paddle it will
; impart a bit of additional up/down movement rate to the ball, set with
; the Constant MOVEMULTIPLIER#, up to a point, at which the up/down
; movement rate is restored to the original rate. Starting movement rates
; (X and Y) are random and set by the min/max of Constants SPEEDX# and SPEEDY#.
; You can vary the match length with the Constant SCORELIMT.
;
; You can change the first 9 Constants to make the game easy or hard
; and the AI player better or useless.
;
; I purposely used sprites and sprite collisions so I could learn something
; about sprite functions in PB.
;
; I've tried to heavily comment the code with the intent of using my
; learning experience as a tutorial for others. This is my first try at
; doing something with PB.

; I'm open to suggestions/comments on things that could have been done
; better or if you would like me to clarify something.
; ---------------------------------------------------------------------

; =========================================================
; Set Global Variables
; =========================================================
Global Height = GetScreenHeight()
Global Width = GetScreenWidth()
Global MovX#, MovY#, MovYOrg#
Global LastCollision
Global LeftPaddlePos# = Width*0.10
Global RightPaddlePos# = Width*0.90

; =========================================================
; Set Game Constants
; =========================================================
; This first group of Constants effect how hard the game is
; by changing the ball speed with SPEEDX#, SPEEDY# and MOVEMULTIPLIER#
; and how good the AI player is by changing AIMOVESCALE#,
; R_REACTZONE# and L_REACTZONE#
Constant AIMOVESCALE# = 4;how fast AI moves paddle
Constant PLAYERMOVESCALE# = 8; how fast player moves paddle
Constant SPEEDX# = 7;max X movement rate for ball
Constant SPEEDY# = 4;max Y movement rate for ball & Y multiplier for paddle hit
Constant SCALEBALL = 5;ball size (small # - big ball)
Constant R_REACTZONE# = 0.60; % board length where AI player start to react
Constant L_REACTZONE# = 0.40; % board length where AI player start to react
Constant MOVEMULTIPLIER# = 1.25; multiplier for ball Y movement rate
Constant SCORELIMT = 5; game ends when a player reaches the number
;----------------------------------------------------------
Constant MANUAL = 1
Constant AUTO = 0
Constant RED = RGB(255,0,0)
Constant WHITE = RGB(255,255,255)
;----------------------------------------------------------
; Makes reading the sprite index easy
Constant BALL = 1
Constant LSIDE = 10
Constant RSIDE = 11
Constant TOP = 12
Constant BOTTOM = 13
Constant R_PADDLE  = 14
Constant L_PADDLE = 15
Constant SCOREBOX = 16
;----------------------------------------------------------


; =========================================================
; Main Program Loop (can leave at anytime with Esc Key)
; =========================================================
While EscKey() = 0
   
; =========================================================
; Display the Main Menu
; =========================================================
   Match = MainMenu()
; Match variable is returned with AI vs. AI or Human vs. AI
; if Human vs. AI then set the ReactZoneL to 100 which allows
; the human player to move paddle freely up and down any time
   If Match = MANUAL
 ReactZoneL# = 100
 ReactZoneR# = R_REACTZONE#
   Else
 ReactZoneL# = L_REACTZONE#
 ReactZoneR# = R_REACTZONE#
   EndIf
   
; =========================================================
; Create Array to hold paddle Y positions used in VaryBallY()
; =========================================================
   Dim PaddleMove#(3)
; =========================================================
; Create Array to hold each players score in PlayerScore(0)
; & PlayerScore(0)
; =========================================================
   Dim PlayerScore(1)
   
; =========================================================
; Create Ball (send data to MakeSprite Function)
; =========================================================
;scale the ball size To the screen
   Size=(Width-Height)/SCALEBALL
   MakeSprite(Size/2,Size/2,Size/2,0,Size,Size,BALL,1,Width/2,Height/2)

; =========================================================
; Create Sides (send data to MakeSprite Function)
; =========================================================
   MakeSprite(0,0,10,Height,10,Height,LSIDE,2,1,Height/2)
   MakeSprite(0,0,10,Height,10,Height,RSIDE,2,Width-1,Height/2)
   MakeSprite(0,0,Width,10,Width,10,TOP,3,Width/2,1)
   MakeSprite(0,0,Width,10,Width,10,BOTTOM,3,Width/2,Height-5)

; =========================================================
; Create Paddles (send data to MakeSprite Function)
; =========================================================
   MakeSprite(0,0,15,70,15,70,R_PADDLE,4,RightPaddlePos#,Height/2)
   MakeSprite(0,0,15,70,15,70,L_PADDLE,4,LeftPaddlePos#,Height/2)

; =========================================================
; Create Score Box (send data to MakeSprite Function)
; =========================================================
   MakeSprite(0,0,Width,50,200,50,SCOREBOX,0,Width/2,50)

; =========================================================
; Limit speed of the program To 60 Frames per second
; =========================================================
   SetFPS 60

; =========================================================
; Get a random start movement For ball between + and - speed constants
; =========================================================
 While ok = 0
   MovX#= RndRange(Neg(SPEEDX#),SPEEDX#)
   MovY#= RndRange(Neg(SPEEDY#),SPEEDY#)
  ; Speed can't be 0 For x or y direction
   If MovX# = 0 Or MovY# = 0
      ok = 0
 Else
    ok = 1
 EndIf
   EndWhile

; Store original Y movement variable For use in VaryBallY() function
   MovYOrg# = MovY#

; =========================================================
; Main Game Loop (can leave at anytime with Esc Key)
; =========================================================

   While EscKey() = 0
   
   ; Clear the Screen To back (0 = black)
      Cls 0

  ; Draw all the sprites now
 DrawAllSprites
 
  ; Loop To check collision events for setting movement/direction
  ; Loop will exit early if a collision is detected
   For n = 10 To 15
      If CollisonCheck(n) Then Exit
   Next
   
   ; get input For Right paddle (always AI player)
 MovePaddle(ReactZoneR#,AUTO, R_PADDLE)
   
   ; get input For left paddle (can be AI or Human based on Match variable
 MovePaddle(ReactZoneL#,Match, L_PADDLE)
   
   ; move ball sprite
 MoveSprite BALL,MovX#,MovY#
   
   ; display score board with score
 ScoreBoard()
   
   ; check score and see if player wants to try again or quit
   ; if SCORELIMT is reached by either player
 If PlayerScore(0) => SCORELIMT Or PlayerScore(1) => SCORELIMT
    ShowExit()
 ;check For key press - looks for either the 1 or 2 key
    RawKeyCode = ScanCode()
    If RawKeyCode = 2
   PlayerScore(0) = 0
   PlayerScore(1) = 0
    ; Exit the game
   End
    EndIf
    If RawKeyCode = 3
   PlayerScore(0) = 0
   PlayerScore(1) = 0
    ; Exit the game while loop and start new game
   ExitWhile
    EndIf
 EndIf
 
  ; Display the screen And Loop back To the While statement To continue the loop
 Sync
   
   EndWhile

EndWhile


; =========================================================
; Paddle movement Function For player and AI
; AI and Player response are scaled using the AIMOVESCALE#
; and PLAYERMOVESCALE# Constants
; (larger number - better/faster response)
; the prior and new Y pos of the paddle are held in PaddleMove#()
; array and used in the VaryBallY function
; =========================================================
Function MovePaddle(Reactzone#, mode, paddle)

; used to set the approproiate index for the PaddleMove# array
; and to check to see if ball x pos is within the paddles
; permited reaction zone, set with passed variable Reactzone#
   If paddle = R_PADDLE
 If GetSpriteX(BALL) > Width*Reactzone# Then Move = 1
 index0 = 0
 index1 = 1
   Else
 If GetSpriteX(BALL) < Width*Reactzone# Then Move = 1
 index0 = 2
 index1 = 3
   EndIf

; player paddle
   If mode = MANUAL And Move = 1
   ; store the paddle Y pos before moving in PaddleMove#() array
 PaddleMove#(index0) = GetSpriteY(paddle)
   ; Checks For up arrow key press & stops paddle movement at top
 If UpKey() = 1 And GetSpriteY(paddle) > (Abs(GetSpriteHandleY(paddle))+5)
    MoveSpriteY paddle, Neg(PLAYERMOVESCALE#)
 EndIf
   ; Checks For down arrow key press & stops paddle movement at bottom
 If DownKey() = 1 And GetSpriteY(paddle) < ((Height-5)-(Abs(GetSpriteHandleY(paddle))))
    MoveSpriteY paddle, PLAYERMOVESCALE#
 EndIf
   ; store the new paddle Y pos in the next element in PaddleMove#() array
 PaddleMove#(index1) = GetSpriteY(paddle)
   EndIf
   
;  AI paddle (check against Ball Y position)
   If mode = AUTO And Move = 1
   ; store the paddle Y pos before moving in PaddleMove#() array
 PaddleMove#(index0) = GetSpriteY(paddle)
 BallXPos = GetSpriteX(BALL)
 BallY = GetSpriteY(BALL)
 
   ; AI paddles are moved based on the ball position -
   ; If in front move up/down toward ball Y pos
 If  BallXPos > LeftPaddlePos# And BallXPos < RightPaddlePos#
    If paddle = L_Paddle
    ; if the ball is moving away from the left paddle move paddle toward center
   If MovX# > 0
      If PaddleMove#(index0)-Height/2 > 0 Then MoveSpriteY paddle, Neg(AIMOVESCALE#)
      If PaddleMove#(index0)-Height/2 < 0 Then MoveSpriteY paddle, AIMOVESCALE#
    ; if the ball is moving toward the left paddle move paddle toward ball
   Else
      If PaddleMove#(index0)-BallY > 0 Then MoveSpriteY paddle, Neg(AIMOVESCALE#)
      If PaddleMove#(index0)-BallY < 0 Then MoveSpriteY paddle, AIMOVESCALE#
   EndIf
    EndIf
    If paddle = R_Paddle
    ; if the ball is moving away from the right paddle move paddle toward center
   If MovX# < 0
      If PaddleMove#(index0)-Height/2 > 0 Then MoveSpriteY paddle, Neg(AIMOVESCALE#)
      If PaddleMove#(index0)-Height/2 < 0 Then MoveSpriteY paddle, AIMOVESCALE#
    ; if the ball is moving toward the right paddle move paddle toward ball
   Else
      If PaddleMove#(index0)-BallY > 0 Then MoveSpriteY paddle, Neg(AIMOVESCALE#)
      If PaddleMove#(index0)-BallY < 0 Then MoveSpriteY paddle, AIMOVESCALE#
   EndIf
    EndIf
   ; If ball is behind move up/down away from ball Y pos (avoids repeat collision with back wall)
 Else
    If PaddleMove#(index0)-BallY > 0 Then MoveSpriteY paddle, (AIMOVESCALE#)
    If PaddleMove#(index0)-BallY < 0 Then MoveSpriteY paddle, Neg(AIMOVESCALE#)
 EndIf
   ; store the new paddle Y pos in the next element in PaddleMove#() array
 PaddleMove#(index1) = GetSpriteY(paddle)
   EndIf
    
EndFunction
   
; =========================================================
;  Checks For collisions between ball And paddles/walls
;  Sets movement direction based On collision
;  (i.e. If ball collides with bottom Or top X movement does
;  Not change, but Y movement is reversed, If ball collides
;  with left Or right wall Or paddle Y movement does Not change
;  but X movement reverses)
; =========================================================  
Function CollisonCheck(SpriteIndex)
   ThisSprite = SpriteHit(BALL,SpriteIndex,GetSpriteCollisionClass(SpriteIndex))
   
; Insures that a repeated collision with the same object does not happen
; (i.e. ball movement should only change If the last collision object is
; different than the current collision object)
   If ThisSprite <> 0 And ThisSprite <> LastCollision
 Select ThisSprite
    Case LSIDE
    ;Inc(PlayerScore(0))
   PlayerScore(0) = PlayerScore(0) + 1
   MovX# = Neg(MovX#)
    Case RSIDE
    ;Inc(PlayerScore(1))
   PlayerScore(1) = PlayerScore(1) + 1
   MovX# = Neg(MovX#)
    Case TOP
   MovY# = Neg(MovY#)
    Case BOTTOM
   MovY# = Neg(MovY#)
    Case R_PADDLE
   MovX# = Neg(MovX#)
   VaryBallY(ThisSprite)
    Case L_PADDLE
   MovX# = Neg(MovX#)
   VaryBallY(ThisSprite)
 EndSelect
   EndIf
 
 ; There was a hit, No need To check anymore this cycle
   If ThisSprite > 1
 LastCollision = ThisSprite
 Exitfunction True
   EndIf
   
EndFunction False

; =========================================================
; Used To add some variability to the ball movement by checking
; If paddle was moved since last cycle - If yes use multiplier on current
; ball Y movement rate as long as it is below the originial Y move rate
; times the max Y speed set with constant SPEEDY# (will get real fast
; if SPEEDY# or MOVEMULTIPLIER# are large)
; =========================================================
Function VaryBallY(ThisSprite)
   
; used to set the approproiate index for the PaddleMove# array testing
   If ThisSprite = R_PADDLE
 index0 = 0
 index1 = 1
   Else
 index0 = 2
 index1 = 3
   EndIf

   If Abs(MovY#) < Abs(MovYOrg# * SPEEDY#)
 If (PaddleMove#(index0)-PaddleMove#(index1) <> 0)
    MovY# = MovY# * MOVEMULTIPLIER#
 EndIf
   Else
 If MovY# < 0
    MovY# = Neg(Abs(MovYOrg#))
 Else
    MovY# = Abs(MovYOrg#)
 EndIf
   EndIf
   
EndFunction

; =========================================================
; Do all the sprite creation stuff For walls, ball And paddles
; =========================================================
Function MakeSprite(Tx,Ty,Bx,By,ImgW,ImgH,SpriteNum,CollisionClass,Xpos,Ypos)
   
; Get the Image Index that is currently free for use
   Image = GetFreeImage()
; Creates a blank image in memory read for use using Image
   CreateImage Image,ImgW,ImgH
; directs drawing operations to Image rather than the screen
   RenderToImage Image
   
;BoxC and CircleC draw a color filled box and circle
   If By > 0
 BoxC Tx,Ty,Bx,By,1,WHITE
   Else
 CircleC Tx,Ty,Bx,1,WHITE
   EndIf
   
; directs all of drawing operations to the screen
   RenderToScreen

   CreateSprite SpriteNum
   ; Center this sprite's handle for its assigned image
   AutoCenterSpriteHandle SpriteNum,True
   ; Set each Sprite To use image for its artwork
   SpriteImage SpriteNum,Image
   ; Enable Collision for Sprite
   SpriteCollision SpriteNum,On
   ;set the position of a sprite to specific X And Y position
   PositionSprite SpriteNum,Xpos,Ypos
   ; Set Sprite's collision Class
   SpriteCollisionClass SpriteNum,CollisionClass
   ; Display outline of Sprite's collision mode region (testing only)
   SpriteCollisionDebug SpriteNum, 0

EndFunction

; =========================================================
; Score Board Display
; Uses PlayerScore1 And PlayerScore2 variables that are
; set in the CollisonCheck() Function each time ball hits
; wall On left Or right side (i.e. missed by paddle)
; =========================================================
Function ScoreBoard()
   
   Ink RED
   Text (Width/2)-80, 30, "Player 1"
   Text (Width/2)+20, 30, "Player 2"
   Text (Width/2)-55, 55, Str$(PlayerScore(1))
   Text (Width/2)+45, 55, Str$(PlayerScore(0))
   
EndFunction

; =========================================================
; Final Score Display
; Displays the final score And the winner
; =========================================================
Function ShowExit()
   
; use ScoreBoard() function to set the display
   ScoreBoard()
   If PlayerScore(0) < PlayerScore(1)
 Text (Width/2)-79, 15, "Winner"
   Else
 Text (Width/2)+21, 15, "Winner"
   EndIf
   Text (Width/2)-60, Height/2, "Press 1 To Exit"
   Text (Width/2)-60, Height/2+20, "Press 2 To Try Another"
;delete all sprites (ball, paddles and walls)
   DeleteALLSprites

EndFunction

; =========================================================
; Main Menu Display
; Asks user to choose between Human vs. AI or AI vs. AI
; using mouse click in the appropriate box
; Returns the choice to the main program loop using the variable Match
; =========================================================
Function MainMenu()
   Cls 0
   
; set the 2 box locations based on screen size
   Box1X1 = Width*0.20
   Box1Y1 = Height*0.45
   Box1X2 = Width*0.40
   Box1Y2 = Height*0.55
   
   Box2X1 = Width*0.60
   Box2Y1 = Height*0.45
   Box2X2 = Width*0.80
   Box2Y2 = Height*0.55
   
; display the 2 boxes in white
   BoxC Box1X1, Box1Y1, Box1X2, Box1Y2, 1, WHITE
   BoxC Box2X1, Box2Y1, Box2X2, Box2Y2, 1, WHITE
   
; place red text inside the boxes and as title and user help
   Ink RED
   CenterText Width/2, Height/2*(0.25), "PLAYBASIC PONG"
   CenterText ((Box1X2-Box1X1)/2)+Box1X1, ((Box1Y2-Box1Y1)/2)+Box1Y1, "Human vs AI"
   CenterText ((Box2X2-Box2X1)/2)+Box2X1, ((Box2Y2-Box2Y1)/2)+Box2Y1, "AI vs AI"
   CenterText Width/2, Height/2*(1.75), "Choose the type of Game"
   Sync
; loop until left mouse click is detected inside one of the boxes
   While (Result1 = 0 And Result2 = 0)
 If MouseButton()=1
    Xpos = MouseX()
    Ypos = MouseY()
 EndIf
 Result1 = PointInBox(Xpos, Ypos, Box1X1, Box1Y1, Box1X2, Box1Y2)
 Result2 = PointInBox(Xpos, Ypos, Box2X1, Box2Y1, Box2X2, Box2Y2)
   EndWhile
   
; store the result of the player choice in the match variable & return in to the main loop
   If Result1 = 1 Then Match = MANUAL
   If Result2 = 1 Then Match = AUTO
   
EndFunction Match
[/pbcode]