Pardon the mess, Play My Code is in beta!

READY TO PLAY?
CLICK TO LOG IN!

sign up - lost password

Preppie! »

You do not own this project, so changes will not be saved

/*
 ______                     _      _ 
| ___ \                   (_)    | |
| |_/ / __ ___ _ __  _ __  _  ___| |
|  __/ '__/ _ \ '_ \| '_ \| |/ _ \ |
| |  | | |  __/ |_) | |_) | |  __/_|
\_|  |_|  \___| .__/| .__/|_|\___(_)
              | |   | |             
              |_|   |_|     

 Title   : Preppie!
 Version : 1.0
 Author  : Andy White / Krakatomato
           Web:     https://www.fivesprites.com
           Twitter: @krakatomato
 
 Very quick remake of the Atari 8bit classic "Preppie!" 
      http://www.atarimania.com/game-atari-400-800-xl-xe-preppie_s4087.html
 
 Copyright remains with the original author (Russ Wetmore)
 
 ** Made with the kind permission of the original author - thanks Russ! :)
 

 This was all rushed together over the course of a few days so there's
 probably lots of bugs and features not quite right or missing. Sorry about
 that ;)
  
*/

// TODO:
//   - On-screen controls for mobile devices


// Set this to true so that you can get extra debug information displayed
// (Only when using the Code Editor - doesn't show in normal Play Mode)
$DEBUG   = false

// Set this to true to disable collisions with the player.  This is only
// helpful when debugging the game - a pointless cheat otherwise!
$GODMODE = false

showcursor(false)

// Images
$fsLogo       = new Image("fslogo.png")
$logoImg      = new Image("logo.png")
$logoShadow   = new Image("logoshadow.png")

$background   = new Image("background.png")

$timebarImg   = new Image("timebar.png")

$mower1Img    = new Image("mower_1.png")
$mower2Img    = new Image("mower_2.png")
$mowerbody    = new Image("mower_body.png")
$mowerarms    = new Image("mower_arms.png")

$cat1Img      = new Image("cat1.png")
$cat2Img      = new Image("cat2.png")

$cart1Img     = new Image("cart1.png")
$cart2Img     = new Image("cart2.png")
$cart3Img     = new Image("cart3.png")

$croc1Img     = new Image("crocopen.png")
$croc2Img     = new Image("crocshut.png")

$logLong      = new Image("loglong.png")
$logShort     = new Image("logshort.png")

$water1       = new Image("water1.png")
$water2       = new Image("water2.png")
$water3       = new Image("water3.png")

$frog1Img     = new Image("frog.png")
$frog2Img     = new Image("frogleap.png")

$boati        = new Image("boati.png")
$boatf        = new Image("boatf.png")
$boatb        = new Image("boatb.png")
    
$lboati       = new Image("lboati.png")
$lboatf       = new Image("lboatf.png")
$lboatb       = new Image("lboatb.png")

$sboat1       = new Image("speed1.png")
$sboat2       = new Image("speed2.png")

$plyr1Img     = new Image("plyr.png")
$plyr2Img     = new Image("plyr_du.png")
$plyr3Img     = new Image("plyr_lg.png")
$plyr4Img     = new Image("plyr_du_lg.png")

// Music and Sounds
$mainMusic    = new Sound("music.mp3")
$startMusic   = new Sound("levelstart.mp3")
$highMusic    = new sound("highscore.mp3")
$deathMusic   = new sound("death.mp3")
$zoneSound    = new sound("zone.mp3").setVolume(0.3)
$plopSound    = new sound("plop.mp3")
$ballgetSound = new Sound("ballget.mp3")
$ballretSound = new Sound("ballreturn.mp3").setVolume(0.5)
$splatSound   = new Sound("splat.mp3")
$extraLifeSound = new Sound("life.mp3")

// Easy access to controls class
$controls     = getControls()

// Various constants
$screenWidth  = getScreenWidth()
$screenHeight = getScreenHeight()
$halfWidth    = $screenWidth  / 2.0
$halfHeight   = $screenHeight / 2.0

$MAX_LIVES    = 3
$SAFETY_ZONE  = 333

// Temp:
// Split as high/low colours and align more to those
// in the game.  Then apply same logic to boat and lawnmower
$colours = [
    [179,  58,  32],
    [128,   3,  95],
    [  8,  47, 202],
    [  3, 107,   3],
    [255, 145,  26],
    [202,  77, 169],
    [255, 197,  31],
    [128, 145, 255],
    [ 44,  53,   0],
    [164, 164, 164]
]

// Different colours for the boats
$boatColours = [
    [ [203, 188, 100], [123, 123, 123], [ 46,  46,  46] ],
    [ [191, 144,  94], [ 46,  46,  46], [120,  42, 102] ],
    [ [203, 188, 100], [123, 123, 123], [ 93,  77,   0] ],
    [ [203, 129, 139], [ 96,  23,  33], [ 46,  46,  46] ],
    [ [215, 215, 215], [ 96,  23,  33], [ 46,  46,  46] ]

]

// Available ball positions on the rough (top)
$roughBallPositions = [
    [ 28,  77, 0],
    [172,  77, 0],
    [214,  77, 0],
    [360,  77, 0],
    [406,  77, 0],
    [572,  77, 0]
]

// Available ball positions on the river bank (middle)
$riverBankBallPositions = [
    [ 67, 210, 0],
    [144, 210, 0],
    [222, 210, 0],
    [299, 210, 0],
    [377, 210, 0],
    [454, 210, 0],
    [532, 210, 0]
]

// Font used in the game
$font  = new Font("font3.png", 32, 32)

// Generic font for debugging only
setFont( 'Arial', 12, 'italic' )

$crocOpen  = 0
$crocTimer = new Timer(100)

if (isEditor() == false)
    $DEBUG   = false
    $GODMODE = false
end

if ($DEBUG)
    showFPS()
end

// Currently logged in user
$user = new User()

// Instance of the main game class
$game = new game()

$osControls = false

showCursor(false)

// Main loop
onEachFrame() do |delta|
    fill(:black)
    setColor(:white)
    
    // Update the game and then draw everything
    $game.update(delta)
    $game.draw()
end

// Main Game class
class Game
    def new()
        @score      = 0
        @highScore  = 0
        @lastScore  = 0
        @level      = 1
        @nlevel     = 0
        @lives      = $MAX_LIVES
        @time       = 60
        @haveBall   = false
        
        @e1e        = []
        @e2e        = []
        
        @balls      = []
        @enemies    = []
        @surfaces   = []
        @state      = :title
        @stateTimer = new Timer(100)
        @alf        = 1.0
        @alfv       = 0.05
        @flip       = 1
        @flipTimer  = new Timer(300)
        @wait       = false
        @extraLifeTimer = new Timer(100)
        
        // Set up a nice colour effect
        // Pinched from one of Jayenkai's projects
        //    http://www.playmycode.com/play/game/jayenkai/swirly2
        @col = []
        3.times() do |c|
            @col[c]     = rand(100, 255)
            @col[c + 3] = rand(  1,   2)
        end

        @highScore      = $user.loadGame()
        if (@highScore == null)
            @highScore = 0
        end

        @player = new Player()

        // Start playing the main music
        $mainMusic.seek(0).play()
    end
    attr(level, player)
    
    def BuildLevel()
        l = @level-1
        
        @enemies  = []
        @surfaces = []
        
        // --== RIVER ==--
        if ($levels[l][$D_L1T] == $T_CANOE)
            // Can have canoes or logs on first lane
            //                        x  l  d  speed               size
            @surfaces.push(new Boat(  0, 0, 1, $levels[l][$D_L1S], 0))
            @surfaces.push(new Boat(206, 0, 1, $levels[l][$D_L1S], 0))
            @surfaces.push(new Boat(400, 0, 1, $levels[l][$D_L1S], 1))
        elseif ($levels[l][$D_L1T] == $T_LOG)
            // Can have canoes or logs on first lane
            //                       x  l  d  speed               size
            @surfaces.push(new Log(  0, 0, 1, $levels[l][$D_L1S], 0))
            @surfaces.push(new Log(206, 0, 1, $levels[l][$D_L1S], 0))
            @surfaces.push(new Log(400, 0, 1, $levels[l][$D_L1S], 0))
        elseif ($levels[l][$D_L1T] == $T_CROC)
            @surfaces.push(new Crocodile(  0, 0, 1, $levels[l][$D_L1S]))
            @surfaces.push(new Crocodile(206, 0, 1, $levels[l][$D_L1S]))
            @surfaces.push(new Crocodile(400, 0, 1, $levels[l][$D_L1S]))
        end
        if ($levels[l][$D_L2T] == $T_CANOE)
            // Can have canoes or logs on second lane
            //                        x  l   d  speed               size
            @surfaces.push(new Boat(  0, 1, -1, $levels[l][$D_L2S], 0))
            @surfaces.push(new Boat(206, 1, -1, $levels[l][$D_L2S], 0))
            @surfaces.push(new Boat(400, 1, -1, $levels[l][$D_L2S], 0))
        elseif ($levels[l][$D_L2T] == $T_LOG)
            // Can have canoes or logs on second lane
            //                       x  l   d  speed            size
            if (@level > 8)
                @surfaces.push(new Log(  0, 1, -1, $levels[l][$D_L2S], 0))
                @surfaces.push(new Log(206, 1, -1, $levels[l][$D_L2S], 0))
                @surfaces.push(new Log(400, 1, -1, $levels[l][$D_L2S], 0))
            else
                @surfaces.push(new Log(  0, 1, -1, $levels[l][$D_L2S], 1))
                @surfaces.push(new Log(270, 1, -1, $levels[l][$D_L2S], 1))
            end
        end
        if ($levels[l][$D_L3T] == $T_CANOE)
            // Can have canoes or crocs on third lane
            //                        x  l  d  speed               size
            @surfaces.push(new Boat(  0, 2, 1, $levels[l][$D_L3S], 0))
            @surfaces.push(new Boat(206, 2, 1, $levels[l][$D_L3S], 0))
            @surfaces.push(new Boat(400, 2, 1, $levels[l][$D_L3S], 1))
        elseif ($levels[l][$D_L3T] == $T_CROC)
            // Can have canoes or crocs on third lane
            //                             x  l  d  speed
            @surfaces.push(new Crocodile(  0, 2, 1, $levels[l][$D_L3S]))
            @surfaces.push(new Crocodile(206, 2, 1, $levels[l][$D_L3S]))
            @surfaces.push(new Crocodile(400, 2, 1, $levels[l][$D_L3S]))
        elseif ($levels[l][$D_L3T] == $T_SPEED)
            @surfaces.push(new SBoat(  0, 2, 1, $levels[l][$D_L3S], 0))
            @surfaces.push(new SBoat(206, 2, 1, $levels[l][$D_L3S], 1))
            @surfaces.push(new SBoat(400, 2, 1, $levels[l][$D_L3S], 0))
        end
        
        // --== FAIRWAY ==--
        if ($levels[l][$D_L4T] == $T_MOWER)
            // Only have mowers on fourth lane
            //                        x  l  d  speed
            @enemies.push(new Mower(  0, 3, 1, $levels[l][$D_L4S]))
            @enemies.push(new Mower(206, 3, 1, $levels[l][$D_L4S]))
            @enemies.push(new Mower(412, 3, 1, $levels[l][$D_L4S]))
        end
        if ($levels[l][$D_L5T] == $T_MOWER)
            // Can have mowers or caterpillars on fifth lane
            //                        x  l   d  speed             
            @enemies.push(new Mower(  0, 4, -1, $levels[l][$D_L5S]))
            @enemies.push(new Mower(309, 4, -1, $levels[l][$D_L5S]))
            //@enemies.push(new Mower(206, 4, -1, $levels[l][$D_L5S]))
            //@enemies.push(new Mower(412, 4, -1, $levels[l][$D_L5S]))
        elseif ($levels[l][$D_L5T] == $T_CAT)
            // Can have mowers or caterpillars on fifth lane
            //                              x  l   d  speed
            @enemies.push(new Caterpillar(  0, 4, -1, $levels[l][$D_L5S]))
            @enemies.push(new Caterpillar(130, 4, -1, $levels[l][$D_L5S]))
            @enemies.push(new Caterpillar(270, 4, -1, $levels[l][$D_L5S]))
            @enemies.push(new Caterpillar(400, 4, -1, $levels[l][$D_L5S]))
        end
        if ($levels[l][$D_L6T] == $T_MOWER)
            // Can have mowers or golf carts on sixth lane
            //                        x  l  d  speed 
            @enemies.push(new Mower(  0, 5, 1, $levels[l][$D_L6S]))
            @enemies.push(new Mower(206, 5, 1, $levels[l][$D_L6S]))
            @enemies.push(new Mower(412, 5, 1, $levels[l][$D_L6S]))
        elseif ($levels[l][$D_L6T] == $T_GOLF)
            // Can have mowers or golf carts on sixth lane
            //                           x  l  d  speed
            @enemies.push(new GolfCart(  0, 5,  1, $levels[l][$D_L6S]))
            @enemies.push(new GolfCart(309, 5,  1, $levels[l][$D_L6S]))
            //@enemies.push(new GolfCart(206, 5,  1, $levels[l][$D_L6S]))
            //@enemies.push(new GolfCart(412, 5,  1, $levels[l][$D_L6S]))
        end
        
        // Add a frog for this level?
        if ($levels[l][$D_FROG] == 1)
            @enemies.push(new Frog())
        end
        
        // Reset croc animation timer
        $crocOpen = 0
        $crocTimer = new Timer(rand(5000, 12000).round())

        // Set up number of balls
        numBalls = $levels[l][$D_BALLS] + @nlevel
        $roughBallPositions.each() do |b|
            b[2] = 0
        end
        $riverBankBallPositions.each() do |b|
            b[2] = 0
        end
        @balls.clear()
        numBalls.times() do |b|
            @balls.push(new Ball())
        end
    end
    
    // Main update loop
    def update(delta)
        
        // Update a bouncing alpha effect
        @alf = @alf + @alfv
        if (@alf > 1.0)
            @alf = 1.0
            @alfv = -@alfv
        elseif (@alf < 0.3)
            @alf = 0.3
            @alfv = -@alfv
        end

        // Update the pretty colour effect
        // Pinched from one of Jayenkai's projects
        //    http://www.playmycode.com/play/game/jayenkai/swirly2
        3.times() do |c|
            @col[c] = @col[c] + @col[c + 3]
            if (@col[c] > 255)
                @col[c] = 255
                @col[c + 3] = -rand(1, 4)
            end
            if (@col[c] < 100)
                @col[c] = 100
                @col[c + 3] =  rand(1, 4)
            end
        end
        
        // Game State Machine
        if (@state == :title)           // --== TITLE PAGE ==--
            if ($mainMusic.isPlaying() == false)
                // If the music has finished then restart it
                // Note: Could have set title music to repeat instead
                $mainMusic.seek(0).play()
                @player = new Player()
            end

//            if ($controls.isLeftPressed())
//                $osControls = true
//            end
            
            if ($controls.isKeyPressed(:space) || $osControls == true)                
                // Game starting so set everything up for a new game
                @state = :gamestart
                @level = 1
                @lastScore = 0
                @score = 0
                @lives = $MAX_LIVES
                // Stop the main music
                $mainMusic.stop()
                // Start the new game music
                $startMusic.seek(0).play()
            end
        elseif (@state == :gamestart)   // --== LITTLE INTERVAL BEFORE GAME STARTS ==--
            if ($startMusic.isPlaying() == false)
                // Start music has finished, so restart main music and move to the
                // main game state
                BuildLevel()
                @player.reset()
                @state = :game
                @newhigh = false
                $mainMusic.seek(0).play()
            end
        elseif (@state == :game)        // --== MAIN GAME STATE ==--
            
            if ($mainMusic.isPlaying() == false)
                $mainMusic.seek(0).play()
            end
            
            // Update the player first
            @player.update(delta)
            // Update all of the 'enemies'
            @enemies.eachIndex() do |e|
                @enemies[e].update(delta)
            end
            // Update all the moving surfaces
            @surfaces.eachIndex() do |s|
                @surfaces[s].update(delta)
            end
            
            // ===== Check for collisions =====
            // TODO: Clean this up - it's a bit inefficient :)
            
            // Check Player->Ball
            if (@haveBall == false)
                @balls.eachIndex() do |b| 
                    // Has the player collected the ball?
                    if (CheckCollision(@player, @balls[b]))
                        @balls.deleteAt(b)
                        @haveBall = true
                        $ballgetSound.play()
                        @player.resetZones()
                        @score = @score + $levels[@level-1][$D_BALL]
                    end
                end
            else
                // Has the player returned the ball?
                if (@player.Y() >= $SAFETY_ZONE)
                    @haveBall = false
                    @score = @score + $levels[@level-1][$D_BALL]
                    $ballretSound.play()
                    @player.resetZones()
                    if (@balls.length() == 0)
                        @state = :nextlevel
                        $mainMusic.stop()
                    else
                        @player.resetZones()
                    end
                end
            end
            
            // Check Player->Enemies
            @enemies.eachIndex() do |e|
                if (CheckCollision(@player, @enemies[e]))
                    splatPlayer()
                end
            end
            
            // Check player against logs/canoes/crocs
            if (@player.isJumping() == false)
                @surfaces.eachIndex() do |s|
                    if (CheckCollision(@player, @surfaces[s]))
                        // If it's a croc with the mouth open then insta-death!
                        if (@surfaces[s].isDeadly())
                            plopPlayer()
                        else
                            // Player is safely on the surface
                            @player.setSurface(@surfaces[s])
                            
                            // At edge of screen?
                            if (@player.X() <= 5 || @player.X() > 585)
                                plopPlayer()
                            end
                        end
                    end
                end
                
                if (@player.isRiding())
                    if (CheckCollision(@player, @player.getSurface()) == false)
                        plopPlayer()
                    end
                end
                
                // Check if player is on water
                if (@player.isOnWater())
                    plopPlayer()
                end
                
                // Check Player->Ponds
                @e1e = @player.getCollisionEllipse()
                if (    isEllipseOverlap(
                            @e1e[0], @e1e[1], @e1e[2], @e1e[3],
                             62, 66, $water1.getWidth()-2, $water1.getHeight()-2) ||
                        isEllipseOverlap(
                            @e1e[0], @e1e[1], @e1e[2], @e1e[3],
                            245, 66, $water1.getWidth()-2, $water1.getHeight()-2) ||
                        isEllipseOverlap(
                            @e1e[0], @e1e[1], @e1e[2], @e1e[3],
                            443, 66, $water1.getWidth()-2, $water1.getHeight()-2))
                    plopPlayer()
                end
            end
            
            // Timer for crocs opening their mouths
            if ($crocTimer.isExpired())
                if ($crocOpen > 0)
                    $crocOpen = 0
                    $crocTimer = new Timer(rand(5000, 12000).round())
                else
                    $crocTimer = new Timer(3000)
                    $crocOpen = 1
                end
            end
            
            // Decrease timer
            @time = @time - $levels[@level-1][$D_TIMER]
            // Has player run out of time?
            if (@time < 0)
                @time = 0
                plopPlayer()
            end

        elseif (@state == :loselife)    // --== OH DEAR, PLAYER LOST A LIFE ==--
            @haveBall = false
             // Update all of the 'enemies'
            @enemies.eachIndex() do |e|
                @enemies[e].update(delta)
            end
            // Update all the moving surfaces
            @surfaces.eachIndex() do |s|
                @surfaces[s].update(delta)
            end           
            if ($crocTimer.isExpired())
                if ($crocOpen > 0)
                    $crocOpen = 0
                    $crocTimer = new Timer(rand(5000, 12000).round())
                else
                    $crocTimer = new Timer(3000)
                    $crocOpen = 1
                end
            end
            if ($deathMusic.isPlaying() == false)
                // Death music has stopped playing so reset the level if lives left
                @lives = @lives - 1
                if (@lives > 0)
                    @state = :gamestart
                    //ResetBalls()
                    @time = 60
                    $startMusic.seek(0).play()
                else
                    // No lives left so go to gameover state
                    @state = :gameover
                    @stateTimer = new Timer(3000)
                    $osControls = false
                end
            end
            @player.update(delta)
        elseif (@state == :nextlevel)
             // Update all of the 'enemies'
            @enemies.eachIndex() do |e|
                @enemies[e].update(delta)
            end
            // Update all the moving surfaces
            @surfaces.eachIndex() do |s|
                @surfaces[s].update(delta)
            end           
            if ($crocTimer.isExpired())
                if ($crocOpen > 0)
                    $crocOpen = 0
                    $crocTimer = new Timer(rand(5000, 12000).round())
                else
                    $crocTimer = new Timer(3000)
                    $crocOpen = 1
                end
            end
            
            if ($zoneSound.isStopped() && @wait == false)
                @time = @time - 2.0
                if (@time < 0.0)
                    @stateTimer = new Timer(3000)
                    @wait = true
                    @time = 0
                else
                    @score = @score + $levels[@level-1][$D_BALL]
                    $zoneSound.play()
                end
            elseif (@wait == true)
                if (@stateTimer.isExpired())
                    if (@level < $MAX_LEVELS)
                        @level = @level + 1
                    else
                        @nlevel = @nlevel + 1
                    end
                    @time = 60
                    @state = :gamestart
                    $startMusic.seek(0).play()
                    @wait = false
                end
            end
        elseif (@state == :gameover)
            if (@stateTimer.isExpired())
                if (@newhigh)
                    $highMusic.seek(0).play()
                    @state = :highscore
                    @highScore = @score
                    
                    // Record the high score in the user session
                    if (isEditor() == false)
                        $user.saveGame("" + @highScore)
                    end
                else
                    @state = :title
                end
            end
        elseif (@state == :highscore)
            if ($highMusic.isStopped())
                @state = :title
            end
        end

        // Get an extra life at 8000 points
        if ( (@score - @lastScore) > 7999)
            @lastScore = @lastScore + 100000 // Won't happen again
            @lives = @lives + 1
            $extraLifeSound.play()
            @extraLifeTimer = new Timer(4000)
        end
        
        if (@score > @highScore)
            @newhigh = true
        end
    end

    def splatPlayer()
        if (isEditor() == true && $GODMODE == true)
        else
            @player.splat()
            $splatSound.play()
            $mainMusic.stop()
            $deathMusic.seek(0).play()
            @state = :loselife
        end
    end

    def plopPlayer()
        if (isEditor() == true && $GODMODE == true)
        else
            @player.plop()
            $plopSound.play()
            $mainMusic.stop()
            $deathMusic.seek(0).play()
            @state = :loselife
        end
    end
    
    def HitZone()
        @score = @score + $levels[@level-1][$D_ZONE]
        $zoneSound.play()
    end
    
    def CheckCollision(e1, e2)
        @e1e = e1.getCollisionEllipse()
        @e2e = e2.getCollisionEllipse()

        c = isEllipseOverlap(
                @e1e[0], @e1e[1], @e1e[2], @e1e[3], 
                @e2e[0], @e2e[1], @e2e[2], @e2e[3], 
                false)
                
        // If there wasn't a collision then check for a collision
        // with the surface that's partial on the other side of the
        // play area
        if (c == false)
            if (e2.isPartial())
                @e2e = e2.getCollisionEllipsePartial()
                c = isEllipseOverlap(
                        @e1e[0], @e1e[1], @e1e[2], @e1e[3], 
                        @e2e[0], @e2e[1], @e2e[2], @e2e[3], 
                        false)
            end
        end
        
        return c
    end
    
    // Main Draw method
    def draw()
        if (@state == :title)           // --== DRAW TITLE SCREEN ==--
            fill(25, 25, 25)
            drawImage($logoShadow, ($screenWidth-$logoImg.getWidth())/2, 50, false)
            setColor(@col[0], @col[1], @col[2], @alf) do
                drawImage($logoImg, ($screenWidth-$logoImg.getWidth())/2, 49, false)
            end
            setColor(202, 202, 202) do $font.drawText(:n, "REMAKE BY", 110, 130, 0.6) end
            setColor(143, 213, 124) do $font.drawText(:n, "ANDY WHITE", 310, 130, 0.6) end
            setColor(182, 182, 182) do $font.drawText(:n, "ORIGINAL BY", 72, 160, 0.6) end
            setColor(143, 213, 124) do $font.drawText(:n, "RUSS WETMORE", 310, 160, 0.6) end
                
            setColor(233, 159, 169) do $font.drawText(:n, "HIGH SCORE:", 30, 210, 1) end
            setColor(150,  76,  86) do $font.drawText(:n, ""+padLeadingZeros(""+@highScore, 5), 415, 210, 1) end
                
            setColor(200, 134, 121) do $font.drawText(:n, "SPACE", 40, 280, 1) end
            if (@flipTimer.isExpired())
                @flipTimer.reset()
                @flip = -@flip
            end
            if (@flip > 0)
                setColor(202, 202, 202) do $font.drawText(:n, "BEGIN GAME", 250, 280, 1) end
            else
                setColor(72, 72, 72) do $font.drawText(:n, "BEGIN GAME", 250, 280, 1) end
            end
            
            setColor(@col[2],@col[1],@col[0]) do drawImage($fsLogo, 525, 340, 80, 50, false) end
            
        elseif (@state == :gamestart)   // --== DRAW THE GAME START INTERVAL SCREEN ==--
            fill(235, 235, 235 )
            setColor(60, 131, 41) do $font.drawCenteredText(:n, "PLAYER 1",  $halfHeight-70, 1) end
        elseif (@state == :game || @state == :loselife) // --== DRAW THE MAIN GAME SCREEN ==--
            drawImage($background, 0, 0, false)
            drawPonds()
            drawStatus()
        
            drawSurfaces()
            
            @balls.each() do |b|
                b.draw()
            end
            
            @player.draw()

            drawEnemies()

        elseif (@state == :nextlevel)
            drawImage($background, 0, 0, false)
            drawPonds()
            drawStatus()
        
            drawEnemies()
            drawSurfaces()            
        elseif (@state == :gameover)
            fill(15, 15, 15)
            setColor(221, 174, 124) do
                $font.drawCenteredText(:n, "GAME OVER", 100, 1)
                $font.drawCenteredText(:n, "PLAYER 1",  210, 1)
            end
        elseif (@state == :highscore)
            fill(15, 15, 15)
            setColor(115, 159, 214) do
                $font.drawCenteredText(:n, "NEW HIGH SCORE!!", 100, 1)
                $font.drawText(:n, padLeadingZeros("" + @score, 5), 400, 180, 1)
                $font.drawText(:n, padLeadingZeros("" + @highScore, 5), 400, 250, 1)
            end
            setColor(221, 174, 124) do
                $font.drawText(:n, "HIGH SCORE:", 40, 180, 1)
            end
            setColor(:white) do
                $font.drawText(:n, "PLAYER 1:", 70, 250, 1)
            end
            
        end
    end
    
    def drawEnemies()
        @enemies.each() do |e|
            e.draw()
        end
    end
    
    def drawSurfaces()
        @surfaces.each() do |s|
            s.draw()
        end
    end
    
    def drawPonds()
        drawImage($water1,  60, 66, false)
        drawImage($water2, 243, 66, false)
        drawImage($water3, 441, 66, false)
        if ($DEBUG)
            drawEllipse( 62, 66, $water1.getWidth()-2, $water1.getHeight()-2, false)
            drawEllipse(245, 66, $water2.getWidth()-2, $water2.getHeight()-2, false)
            drawEllipse(443, 66, $water3.getWidth()-2, $water3.getHeight()-2, false)
        end
    end

    // Draws the status bar at the top of the screen
    def drawStatus()
        // Normalize the time left so it's in the range (0.0 -> 1.0)
        // The result is a percentage of the time remaining which we
        // multiply against the width of the time bar.  This determines
        // how wide we draw the time left
        w = normalise(@time.round(), 0, 60)*$timebarImg.getWidth()
        if (@time > 17)
            setColor( 84, 154,  64) 
        elseif (@time > 10)
            setColor(230, 164, 151)
        else
            setColor(220,  42, 102, @alf)
        end
        // Draw the time left as a filled rectangle
        fillRect(210, 43, w, 14, false)
        setcolor(:white)
        setAlpha(1.0)
        // Now draw the time bar on top of the rectangle
        drawImage($timebarImg, 210, 43, false)
        
        // Rest of the status detail is simple font drawing
        sc = padLeadingZeros(""+@score.round(), 5)
        setColor(@col[0],@col[1],@col[2]) do $font.drawText(:n, "1 UP",      32,  5, 1.0) end
        setColor(221, 151, 241) do $font.drawText(:n, sc,          17, 33, 1.0) end
        setColor(121, 134, 224) do $font.drawText(:n, "TIME",     238,  5, 1.0) end
        setColor(215, 215, 215) do $font.drawText(:n, "MEN:",     437,  5, 1.0) end
        setColor(215, 215, 215) do $font.drawText(:n, "LVL:",     437, 33, 1.0) end
        setColor(121, 134, 224) do 
            if (@extraLifeTimer.isExpired() == false)
                setAlpha(@alf)
            end
            $font.drawText(:n, ""+@lives,  545,  5, 1.0)
        end
        setColor(121, 134, 224) do $font.drawText(:n, ""+(@level+@nlevel),  545, 33, 1.0) end    
    end
end

// Ball Class
class Ball
    def new()
        // Find a position that hasn't already been taken
        // Note: Bit crap as there is the potential to loop forever, however that
        // can only happen with a lot of balls - something that won't occur as the
        // max balls to collect is a lot less
        avail = false
        while (avail == false)
            r = 0
            // Choose riverbank or rough
            p = rand(0, 1).round()
            if ($game.Level() == 1)
                // Only on riverbank for first level
                p = 1
            elseif ($game.Level() > 5)
                // Only on rough!
                p = 0
            end
            if (p == 0)
                // Pick a random position
                r = rand(0, $roughBallPositions.length()-1).round()
                // The position [n][2] indicates if that ball position is used
                if ($roughBallPositions[r][2] == 0)
                    $roughBallPositions[r][2] = 1
                    @x = $roughBallPositions[r][0]
                    @y = $roughBallPositions[r][1]
                    avail = true
                end
            else
                // Pick a random position
                r = rand(0, $riverBankBallPositions.length()-1).round()
                // The position [n][2] indicates if that ball position is used
                if ($riverBankBallPositions[r][2] == 0)
                    $riverBankBallPositions[r][2] = 1
                    @x = $riverBankBallPositions[r][0]
                    @y = $riverBankBallPositions[r][1]
                    avail = true
                end
            end
        end
        @collisionEllipse = [@x-2, @y-2, 10, 10]
    end
    
    def isPartial()
        return false
    end
    
    def getCollisionEllipse()
        return @collisionEllipse
    end

    def draw()
        setColor(:white)
        fillrect(@x, @y, 6, 6, false)
        if ($DEBUG)
            drawEllipse(@collisionEllipse[0], @collisionEllipse[1], @collisionEllipse[2], @collisionEllipse[3], false)
        end
    end
end

// Player Class
class Player
    def new()
        @x = 0
        @y = 0
        @returning = false
        @animSpeed = 8
        @sDiff = 0
        @sDir = 1
        // Ellipse defining collision region of player
        @colEllipse = [2, 3, 22, 22]
        
        // Animation when player drowns!
        @plopAnim = new Animation(new Image("plop.png"), 4, 0, -1, 26, 26, 120, false, 0)
    end
    attr(x, y)
    
    def isJumping()
        if (@state == :jumping || @state == :crouch || @state == :stand)
            return true
        else
            return false
        end
    end
    
    def getSurface()
        return @surface
    end
    
    def isRiding()
        return @riding
    end
    
    def isOnWater()
        if (@riding == true)
            return false
        end
        if (@y > 81 && @y < 182)
            return true
        end
        return false
    end
    
    def getCollisionEllipse()
        return [@x+@colEllipse[0], @y+@colEllipse[1], @colEllipse[2], @colEllipse[3]]
    end
    
    def clearSurface()
        @riding = false
    end
    
    def setSurface(surface)
        if (@riding == false)
            @surface = surface
            @riding = true
            @sDir = @surface.getDir()
            @sDiff = @x-@surface.getAbsX()
        end
    end
    
    def returning()
        @returning = true
    end
    
    // Player got hit by something
    def splat()
        @state = :splat
        @splatWidth = 0
        @splatTimer.reset()
    end
    
    // Player drowned!
    def plop()
        @state = :plop
        @plopAnim.restart()
        $plopSound.play()
    end
    
    // Reset player back to ready
    def reset()
        @x = $halfWidth-13
        @y = $SAFETY_ZONE
        @riding = false
        @splatWidth = 0
        @moving = false
        @splatTimer = new Timer(1000)
        @xspeed = $levels[$game.Level()-1][$D_PLYR][0]
        @yspeed = $levels[$game.Level()-1][$D_PLYR][1]
        @state = :waiting
        @waitTimer = new Timer(3000)
        @jumping = false
        // quick hack!
        if ($game.Level() < 3)
            @crouchTimer = new Timer(160)
            @standTimer = new Timer(160)
        elseif ($game.Level() < 10)
            @crouchTimer = new Timer(100)
            @standTimer = new Timer(100)
        else
            @crouchTimer = new Timer(60)
            @standTimer = new Timer(60)
        end
        
        resetZones()

        @dir = :down
        @frame  = 1
        @frameCount = 0
        if (@yspeed > 1)
            @animSpeed = 4
        else
            @animSpeed = 8
        end
    end
    
    def resetZones()
        @zones = [0, 0, 0, 0, 0, 0, 0, 0]
        @returning = false
    end
    
    // Update method for the player
    def update(delta)
        // State Machine for the player
        if (@state == :waiting)     // --== Player doesn't appear straight away ==--
            if (@waitTimer.isExpired())
                @state = :active
            end
        elseif (@state == :active)  // --== Main player state ==--
            // On a surface, so position of player moves relative to it
            if (@riding == true)
                ox = @x
                @x = @surface.getAbsX()+@sDiff
                // If the surface has transition to the other side then the player
                // position could be wrong - adjust for this
                if (@x > $screenWidth || @x < 0)
                    @sDiff = ox-@surface.getAbsX()
                    @x = @surface.getAbsX()+@sDiff
                end
            end

            // Movement control
            @moving = true
            if (isLeft())
                @x = @x - @xspeed * delta
                // Movement of player on a surface
                @sDiff = @sDiff - (@xspeed * delta)
                @dir = :left
            elseif (isRight())
                @x = @x + @xspeed * delta
                // Movement of player on a surface
                @sDiff = @sDiff + (@xspeed * delta)
                @dir = :right
            elseif (isUp())
                @dir = :up
                // Is player jumping?
                if (isButton() && @y <= 200 && @y > 68)
                    @state = :crouch
                    @crouchTimer.reset()
                    @moving = false
                    @cy = @y
                elseif (@riding == false) 
                    @y = @y - @yspeed * delta
                end
            elseif (isDown())
                @dir = :down
                if (isButton() && @y < 200)
                    @state = :crouch
                    @moving = false
                    @crouchTimer.reset()
                    @cy = @y
                elseif (@riding == false)
                    @y = @y + @yspeed * delta
                end
            else
                @moving = false
            end
            
            // Animate the player 
            if (@moving)
                if (@riding && @surface.isCroc() && (@dir == :left || @dir == :right))
                    $game.plopPlayer()
                end
                if (@frameCount > @animSpeed)
                    @frame = @frame + 1
                    if (@frame == 3)
                        @frame = 1
                    end
                    @frameCount = 0
                else
                    @frameCount = @frameCount + 1
                end
            end

            // Check if the player has hit one of the zones
            if (@y < 301 && @y > 297 && @zones[0] == 0)
                @zones[0] = 1
                $game.HitZone()
            elseif (@y < 266 && @y > 262 && @zones[1] == 0)
                @zones[1] = 1
                $game.HitZone()
            elseif (@y < 233 && @y > 229 && @zones[2] == 0)
                @zones[2] = 1
                $game.HitZone()
            elseif (@y < 205 && @y > 201 && @zones[3] == 0)
                @zones[3] = 1
                $game.HitZone()
            elseif (@y < 169 && @y > 162 && @zones[4] == 0)
                @zones[4] = 1
                $game.HitZone()
            elseif (@y < 136 && @y > 129 && @zones[5] == 0)
                @zones[5] = 1
                $game.HitZone()
            elseif (@y < 103 && @y > 96 && @zones[6] == 0)
                @zones[6] = 1
                $game.HitZone()
            elseif (@y < 68 && @y > 65 && @zones[7] == 0)
                @zones[7] = 1
                $game.HitZone() 
            end
            
            // Keep player out of the water
            if (@jumping == false && @riding == false)
                if (@y.round() <=  69)
                    @y = 66
                elseif (@y < 200.round())
                    @y = 200
                end
            end

            // Keep player inside play area
            if (@y > $SAFETY_ZONE)
                @y = $SAFETY_ZONE
            end

            if (@x < 5)
                @x = 5
            elseif (@x > 585)
                @x = 585
            end
        elseif (@state == :jumping)
            // Player is currently jumping
            if (@dir == :up)
                if ($game.Level() > 9)
                    @y = @y - 2
                else
                    @y = @y - 1
                end
                if (@cy-@y > 32)
                    @state = :stand
                    @standTimer.reset()
                end
            elseif (@dir == :down)
                if ($game.Level() > 9)
                    @y = @y + 2
                else
                    @y = @y + 1
                end
                if (@y-@cy > 32)
                    @state = :stand
                    @standTimer.reset()
                end
            end
        elseif (@state == :crouch)
            if (@crouchTimer.isExpired())
                @state = :jumping
            end
        elseif (@state == :stand)
            if (@standTimer.isExpired())
                @state = :active
                @riding = false
            end
        elseif (@state == :splat)   // --== Player got splatted ==--
            // Increase width of sprite
            if (@splatTimer.isExpired() && @splatWidth == 0)
                @splatTimer.reset()
                @splatWidth = 1
            elseif (@splatTimer.isExpired())
                if (@splatWidth == 1)
                    @splatTimer.reset()
                    @splatWidth = 2
                end
            end
        elseif (@state == :plop)    // --== Player drowned! ==--
            // Update plop animation until finished and then set player inactive
            if (@plopAnim.isFinished())
                @state = :inactive
            end
        elseif (@state == :inactive) // --== Inactive state - player is dead! ==--
            // Don't do anything until main game resets the player
        end
    end
    
    // Draw the player
    def draw()
        if (@state == :stand || @state == :crouch)
            drawImage($plyr1Img, 26*3, 0, 26, 26, @x, @y, false)
            
            if (@dir == :up)
                drawImage($plyr2Img, 26, 0, 26, 26, @x, @y, false)
            elseif (@dir == :down)
                drawImage($plyr2Img, 0, 0, 26, 26, @x, @y, false)            
            end
        elseif (@state == :active || @state == :jumping)
            // Draw standard player animation based on current movement
            if (@moving == false)
                if (@dir == :up)
                    drawImage($plyr1Img,  0, 0, 26, 26, @x, @y, false)
                    drawImage($plyr2Img, 26, 0, 26, 26, @x, @y, false)
                elseif (@dir == :down)
                    drawImage($plyr1Img, 0, 0, 26, 26, @x, @y, false)
                    drawImage($plyr2Img, 0, 0, 26, 26, @x, @y, false)
                elseif (@dir == :left)
                    drawRotatedImage($plyr1Img, 90.toRadians(), 0, 0, 26, 26, @x, @y, false)
                    drawRotatedImage($plyr2Img, 90.toRadians(), 0, 0, 26, 26, @x, @y, false)
                elseif (@dir == :right)
                    drawRotatedImage($plyr1Img, 270.toRadians(), 0, 0, 26, 26, @x, @y, false)
                    drawRotatedImage($plyr2Img, 270.toRadians(), 0, 0, 26, 26, @x, @y, false)
                end
            else
                if (@dir == :up)
                    drawImage($plyr1Img, 26*@frame, 0, 26, 26, @x, @y, false)
                    drawImage($plyr2Img, 26, 0, 26, 26, @x, @y, false)
                elseif (@dir == :down)
                    drawImage($plyr1Img, 26*@frame, 0, 26, 26, @x, @y, false)
                    drawImage($plyr2Img, 0, 0, 26, 26, @x, @y, false)
                elseif (@dir == :left)
                    drawRotatedImage($plyr1Img, 90.toRadians(), 26*@frame, 0, 26, 26, @x, @y, false)
                    drawRotatedImage($plyr2Img, 90.toRadians(), 0, 0, 26, 26, @x, @y, false)
                elseif (@dir == :right)
                    drawRotatedImage($plyr1Img, 270.toRadians(), 26*@frame, 0, 26, 26, @x, @y, false)
                    drawRotatedImage($plyr2Img, 270.toRadians(), 0, 0, 26, 26, @x, @y, false)
                end                
            end
            if ($DEBUG)
                drawEllipse(@x+@colEllipse[0], @y+@colEllipse[1], @colEllipse[2], @colEllipse[3], false)
                drawtext("Pos: (" + @x.round() + "," + @y.round() + ")", 20, $SAFETY_ZONE+5, false)
            end
        elseif (@state == :splat)
            // Draw the player - stretching out
            o = 26; w = 26; xo = 0
            img1 = $plyr1Img; img2 = $plyr2Img
            if (@splatWidth == 1)
                o = 78; w = 52; xo = 13
                img1 = $plyr3Img; img2 = $plyr4Img
            elseif (@splatWidth == 2)
                o = 78; w = 104; xo = 39
                img1 = $plyr3Img; img2 = $plyr4Img
            end
            if (@moving == false)
                if (@dir == :up)
                    drawImage(img1,  0, 0, o, o, @x-xo, @y, w, 26, false)
                    drawImage(img2, o, 0, o, o, @x-xo, @y, w, 26, false)
                elseif (@dir == :down)
                    drawImage(img1, 0, 0, o, o, @x-xo, @y, w, 26, false)
                    drawImage(img2, 0, 0, o, o, @x-xo, @y, w, 26, false)
                elseif (@dir == :left)
                    drawRotatedImage(img1, 90.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                    drawRotatedImage(img2, 90.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                elseif (@dir == :right)
                    drawRotatedImage(img1, 270.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                    drawRotatedImage(img2, 270.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                end
            else
                if (@dir == :up)
                    drawImage(img1, o*@frame, 0, o, o, @x-xo, @y, w, 26, false)
                    drawImage(img2, o, 0, o, o, @x-xo, @y, w, 26, false)
                elseif (@dir == :down)
                    drawImage(img1, o*@frame, 0, o, o, @x-xo, @y, w, 26, false)
                    drawImage(img2, 0, 0, o, o, @x-xo, @y, w, 26, false)
                elseif (@dir == :left)
                    drawRotatedImage(img1, 90.toRadians(), o*@frame, 0, o, o, @x, @y-xo, 26, w, false)
                    drawRotatedImage(img2, 90.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                elseif (@dir == :right)
                    drawRotatedImage(img1, 270.toRadians(), o*@frame, 0, o, o, @x, @y-xo, 26, w, false)
                    drawRotatedImage(img2, 270.toRadians(), 0, 0, o, o, @x, @y-xo, 26, w, false)
                end
            end
        elseif (@state == :plop)
            // Don't draw the player but instead, draw the plop animation
            @plopAnim.draw(@x, @y, false)
            @plopAnim.update()
        end
    end

    // Temporary Controls
    // Will be expanded for alternate control for use with mobile devices
    def isLeft()
        if ($osControls)
            return isCircleOverlap($controls.getMouseX(), $controls.getMouseY(), 10,
                    30, 350, 30, true)
        else
            return $controls.isKeyDown(:left)
        end
    end
    
    def isRight()
        if ($osControls)
            return isCircleOverlap($controls.getMouseX(), $controls.getMouseY(), 10,
                    90, 350, 30, true)
        else
            return $controls.isKeyDown(:right)
        end
    end
    
    def isUp()
        if ($osControls)
            if ($controls.isLeftDown() &&
                    isCircleOverlap($controls.getMouseX(), $controls.getMouseY(), 10,
                            60, 320, 30, true))
                return true
            end
        else
            return $controls.isKeyDown(:up)
        end
        return false
    end
    
    def isDown()
        if ($osControls)
            if ($controls.isLeftDown() &&
                    isCircleOverlap($controls.getMouseX(), $controls.getMouseY(), 10,
                            60, 380, 30, true))
                return true
            end
        else
            return $controls.isKeyDown(:down)
        end
        return false
    end
    
    def isButton()
        if ($osControls)
            if ($controls.isRightPressed() &&
                    isCircleOverlap($controls.getMouseX(), $controls.getMouseY(), 10,
                            560, 350, 70, true))
                return true
            end
        else
            return $controls.isKeyPressed(:space)
        end
        return false
    end
end

// Mower Class
// Parent class is Mover so it inherits the behaviour from that
class Mower < Mover
    def new(x, lane, dir, spd)
        @img1 = new Image(73, 27)
        @img1.clear()
        
        @img2 = new Image(73, 27)
        @img2.clear()
        
        @img1.drawImage($mower1Img, 0, 0, false)
        @img2.drawImage($mower2Img, 0, 0, false)
        
        r = rand(0, $colours.length()-1).round()
        @img1.setColor($colours[r][0], $colours[r][1], $colours[r][2])
        @img2.setColor($colours[r][0], $colours[r][1], $colours[r][2])
        
        @img1.drawImage($mowerBody, 0, 0, false)
        @img2.drawImage($mowerBody, 0, 0, false)
        
        r = rand(0, 1).round()
        if (r == 0)
            @img1.setColor(200, 122, 183)
            @img2.setColor(200, 122, 183)
        else
            @img1.setColor(138,  91,  40)
            @img2.setColor(138,  91,  40)
        end
        @img1.drawImage($mowerArms, 0, 0, false)
        @img2.drawImage($mowerArms, 0, 0, false)
        
        @flip = 1
        @flipTimer = new Timer(100)
        
        super(@img1, x, lane, dir, spd, [0,0, @img1.getWidth(), @img1.getHeight()])
    end
    
    def update(delta)
        if (@flipTimer.isExpired())
            @flipTimer.reset()
            @flip = -@flip
        end
        updateBase(delta)
    end
    
    def draw()
        if (@flip > 0)
            setImg(@img1)
        else
            setImg(@img2)
        end
        drawBase()
    end
end

class SBoat < Mover
    def new(x, lane, dir, spd, alt)
        if (alt == 0)
            super($sboat1, x, lane, dir, spd, [10,0, $sboat1.getWidth()-30, $sboat1.getHeight()])
        else
            super($sboat2, x, lane, dir, spd, [10,0, $sboat2.getWidth()-30, $sboat2.getHeight()])
        end
    end
    
    def update(delta)
        updateBase(delta)
    end
    
    def draw()
        drawBase()
    end
end


// Boat Class
// Parent class is Mover so it inherits the behaviour from that
class Boat < Mover
    def new(x, lane, dir, spd, size)
        if (size == 0)
            @img = new Image(137, 27)
        else
            @img = new Image(183, 27)
        end
        // Set up a random boat and draw it to the temporary image (@img)
        @img.clear()
        
        i = rand(0, $boatColours.length()-1).round()
        @img.setcolor($boatColours[i][0][0], $boatColours[i][0][1], $boatColours[i][0][2]) do 
            if (size == 0)
                @img.drawImage($boati, 0, 0, false) 
            elseif (size == 1)
                @img.drawImage($lboati, 0, 0, false)
            end
        end
        
        i = rand(0, $boatColours.length()-1).round()
        @img.setcolor($boatColours[i][1][0], $boatColours[i][1][1], $boatColours[i][1][2]) do
            if (size == 0)
                @img.drawImage($boatb, 0, 0, false) 
            elseif (size == 1)
                @img.drawImage($lboatb, 0, 0, false)
            end
        end
        
        i = rand(0, $boatColours.length()-1).round()
        @img.setcolor($boatColours[i][2][0], $boatColours[i][2][1], $boatColours[i][2][2]) do
            if (size == 0)
                @img.drawImage($boatf, 0, 0, false) 
            elseif (size == 1)
                @img.drawImage($lboatf, 0, 0, false)
            end
        end

        // Now pass all the parameters and the new image to the parent object
        super(@img, x, lane, dir, spd, [35, 0, @img.getWidth()-70, @img.getHeight()])
    end
    
    def update(delta)
        updateBase(delta)
    end
    
    def draw()
        drawBase()
    end
end

// Radioactive Frog Class
class Frog
    def new()
        @x = -($frog2Img.getWidth()+46)
        @y = 200
        @state = :wait
        @waitTimer = new Timer(3000)
        @collisionEllipse1 = [2, 2, $frog1Img.getWidth()-2, $frog1Img.getHeight()-2]
        @collisionEllipse2 = [2, 2, $frog2Img.getWidth()-2, $frog2Img.getHeight()-2]
    end
    
    def isPartial()
        return false
    end
    
    def getCollisionEllipse()
        if (@state == :wait)
            return [@x+@collisionEllipse1[0], @y+@collisionEllipse1[1], @collisionEllipse1[2], @collisionEllipse1[3]]
        elseif (@state == :leap)
            return [@x+@collisionEllipse2[0], @y+@collisionEllipse2[1], @collisionEllipse2[2], @collisionEllipse2[3]]
        end
    end
    
    def update(delta)
        // Frog logic is pretty simple... wait, leap, wait, leap etc :)
        if (@state == :wait)
            // Sat waiting to leap
            if (@waitTimer.isExpired())
                // Had enough of sitting here, so time to jump
                @state = :leap
                @cx = @x
            end
        elseif (@state == :leap)
            // Leap leap leap Mr. Monster Frog - but only by 3 pixels each frame!
            @x = @x + 3
            
            // If the frog has lept far enough then go back to waiting again
            if (@x-@cx > 88)
                // Note the offset to compensate for the difference in sprite sizes
                @x = @x + 20
                @state = :wait
                @waitTimer = new Timer(3000)
            end
        end
        // If the frog has gone past the screen bounds, then wait for a longer period
        // before allowing him to appear on the left of the screen again
        if (@x > $screenWidth+20)
            @waitTimer = new Timer(8000)
            @state = :wait
            @x = -($frog2Img.getWidth()+46)
        end
    end
    
    def draw()
        if (@state == :wait)
            // Draw the stationary frog
            drawImage($frog1Img, @x, @y, false)
        elseif (@state == :leap)
            // Draw the leaping frog
            drawImage($frog2Img, @x, @y, false)
        end
        
        if ($DEBUG)
            if (@state == :wait)
                drawEllipse(@x+@collisionEllipse1[0], @y+@collisionEllipse1[1], @collisionEllipse1[2], @collisionEllipse1[3], false)
            elseif (@state == :leap)
                drawEllipse(@x+@collisionEllipse2[0], @y+@collisionEllipse2[1], @collisionEllipse2[2], @collisionEllipse2[3], false)
            end
        end
    end
end

// Golf cart Class
// Parent class is Mover so it inherits the behaviour from that
class GolfCart < Mover
    def new(x, lane, dir, spd)
        @img = new Image(74, 26)

        // Set up a random cart and draw it to the temporary image (@img)
        @img.clear()
        
        i = rand(0, $colours.length()-1).round()
        @img.setcolor($colours[i][0], $colours[i][1], $colours[i][2]) do 
            @img.drawImage($cart1Img, 0, 0, false) 
        end
        
        j = i
        while (j == i)
            j = rand(0, $colours.length()-1).round()
        end
        @img.setcolor($colours[j][0], $colours[j][1], $colours[j][2]) do
            @img.drawImage($cart2Img, 0, 0, false)
        end
        
        @img.setColor(:white)
        @img.drawImage($cart3Img, 0, 0, false)

        // Now pass all the parameters and the new image to the parent object
        super(@img, x, lane, dir, spd, [2, 2, @img.getWidth()-2, @img.getHeight()-2])
    end
    
    def update(delta)
        updateBase(delta)
    end
    
    def draw()
        drawBase()
    end
end


// Crocodile Class
// Parent class is Mover so it inherits the behaviour from that
class Crocodile < Mover
    def new(x, land, dir, spd)
        super($croc1Img, x, land, dir, spd, [2, 6, $croc1Img.getWidth()-49, $croc1Img.getHeight()-6])
        setCroc(true)
    end
    
    def update(delta)
        if ($crocOpen > 0)
            setDeadly(true)
        else
            setDeadly(false)
        end
        updateBase(delta)
    end
    
    def draw()
        if ($crocOpen > 0)
            setImg($croc1Img)
        else
            setImg($croc2Img)
        end
        drawBase()
    end
end

// Caterpillar Class
// Parent class is Mover so it inherits the behaviour from that
class Caterpillar < Mover
    def new(x, land, dir, spd)
        @flip = 1
        @flipTimer = new Timer(100)
        super($cat1Img, x, land, dir, spd, [2, 2, $cat1Img.getWidth()-2, $cat1Img.getHeight()-2])
    end
    
    def update(delta)
        if (@flipTimer.isExpired())
            @flipTimer.reset()
            @flip = -@flip
        end
        updateBase(delta)
    end
    
    def draw()
        if (@flip > 0)
            setImg($cat1Img)
        else
            setImg($cat2Img)
        end
        drawBase()
    end
end

// Log Class
// Parent class is Mover so it inherits the behaviour from that
class Log < Mover
    def new(x, lane, dir, spd, size)
        if (size == 0)
            super($logShort, x, lane, dir, spd, [35, 2, $logShort.getWidth()-65, $logShort.getHeight()-2])
        else
            super($logLong, x, lane, dir, spd, [35, 2, $logLong.getWidth()-65, $logLong.getHeight()-2])
        end
    end
    
    def update(delta)
        updateBase(delta)
    end
    
    def draw()
        drawBase()
    end
end

// Mover Class
// This is the base class for objects that just move across the screen
// e.g. the mowers, boats, logs, crocs etc
class Mover
    def new(img, x, lane, dir, spd, collisionEllipse)
        @deadly = false
        @img = img
        @x = x
        @nx = -1000
        @croc = false
        if (lane == 0)
            @y = 100
        elseif (lane == 1)
            @y = 133
        elseif (lane == 2)
            @y = 166
        elseif (lane == 3)
            @y = 233
        elseif (lane == 4)
            @y = 266
        elseif (lane == 5)
            @y = 300
        end
        @dir = dir
        @vx = spd * @dir
        @collisionEllipse = collisionEllipse
        if (@dir < 1)
            // Need to shift collision area along if sprite is reflected for
            // the other direction
            @collisionEllipse[0] = @collisionEllipse[0] - @img.getWidth()
        end
    end
    attr(x, y)
    getset(x, y, nx, vx, img, deadly, dir)

    def getAbsX()
        if (@dir < 0)
            if (isPartial())
                return @nx - @img.getWidth()
            else
                return @x - @img.getWidth()
            end
        else
            if (isPartial())
                return @nx
            else
                return @x
            end
        end
    end
    
    def isPartial()
        if (@nx < -999)
            return false
        else
            return true
        end
    end
    
    def setCroc(croc)
        @croc = croc
    end
    
    def isCroc()
        return @croc
    end
    
    def isDeadly()
        // Only crocs can be deadly
        return @deadly
    end
    
    def getCollisionEllipse()
        return [@x+@collisionEllipse[0], @y+@collisionEllipse[1], @collisionEllipse[2], @collisionEllipse[3]]
    end
    def getCollisionEllipsePartial()
        return [@nx+@collisionEllipse[0], @y+@collisionEllipse[1], @collisionEllipse[2], @collisionEllipse[3]]
    end
    
    // Main update for this object
    def updateBase(delta)
        // Update the horizontal position only
        @x = @x + @vx * delta

        // If the object goes out of screen bounds, then move it to the opposite
        if (@dir > 0)
            if (@x > $screenWidth)
                @x = 0
            end
        else
            if (@x < 0)
                @x = $screenWidth
            end
        end
    end
    
    // Main draw method for this object
    def drawBase()
        // First, draw the object at the desired position.  However, note how the
        // image width is multiplied by the direction.  If the direction is (1) then
        // nothing changes.  However, if the direction is (-1) then the image is
        // "flipped" horizontally
        drawImage(@img, @x, @y, @img.getWidth()*@dir, @img.getHeight(), false)
        
        // If the object starts to move off the screen then we want it to start showing
        // again on the opposite side.  For example, if a boat starts to move off the
        // right side of the screen then the amount of boat now drawn 'off the edge'
        // should be shown at the start of the opposite side of the screen.
        
        // So, determine where the object is and how much space it's taking up
        d = @x+@img.getWidth()
        
        // If the direction is to the right
        if (@dir > 0)
            if (d > $screenWidth)
                // Only draw something if the object is past the right side of the screen!
                    
                // 'diff' is how much of the object has gone past the right side
                diff = d-$screenWidth
                // 'l' is how much we need to draw of the  of the boat on the left
                // of the screen
                @nx = -@img.getWidth()
                @nx = @nx + diff
                // Finally, draw the new part of the boat
                drawImage(@img, @nx, @y, @img.getWidth()*@dir, @img.getHeight(), false)
            else
                @nx = -1000
            end
        elseif (@x-@img.getWidth() < 0) // If the direction is to the left
            // Similarly, we only draw something if the object is past the left side of the screen
            
            // Again, 'diff' is how much of the object has gone past the left side
            diff = @x-@img.getWidth()
            
            // And 'l' is how much we need to draw of the  of the boat on the right
            // of the screen
            @nx = (@img.getWidth()+$screenWidth)+diff
            
            // Finally, draw the new part of the boat
            drawImage(@img, @nx, @y, @img.getWidth()*@dir, @img.getHeight(), false)
        else
            @nx = -1000
        end
        
        if ($DEBUG)
            drawEllipse(@x+@collisionEllipse[0], @y+@collisionEllipse[1], @collisionEllipse[2], @collisionEllipse[3], false)
            drawEllipse(@nx+@collisionEllipse[0], @y+@collisionEllipse[1], @collisionEllipse[2], @collisionEllipse[3], false)
            setColor(:red) do drawCircle(@x, @y, 5, false) end
        end
    end
end

// --== Font class ==--
class Font
    def new(font, w, h)
        // Configure this font.  Note: the ~ character signifies a character
        // in the bitmap that is of no interest.
        @font   = new Image(font)
        @chars  = " !~#$%~~[]*+,-./0123456789:;<=>?~ABCDEFGHIJKLMNOPQRSTUVWXYZ~\~^_"
        @width  = w
        @height = h
        @images = []
        preLoad()
    end
    
    def preLoad()
        @chars.each() do |char|
            img = new Image(@width, @height)
            idx = @chars.indexOf(char)
            col = (idx % 8)
            row = (idx / 8).floor()
            fx = col * @width
            fy = row * @height
            @height.times() do |y|
                @width.times() do |x|
                    pixel = @font.getPixel(fx+x, fy+y)
                    a = (pixel >> 24) & 0xff
                    r = (pixel >> 16) & 0xff
                    g = (pixel >>  8) & 0xff
                    b =         pixel & 0xff
                    img.setPixel(x, y, r,g,b,a)
                end
            end
            @images.push(img)
        end     
    end

    def drawChar(g, char, x, y)
        idx = @chars.indexOf(char)
        if (g == :n)
            drawImage(@images[idx], x, y, false)
        else
            g.drawImage(@images[idx], x, y, false)
        end
    end
    
    def getCentrePos(string, scale)
        x = $halfWidth - ((string.length() * @width) * scale)/2
        return x
    end
    
    // Draw text centered on the x-axis using this font at the given y coordinate.  Scale
    // is applied once the origin has been transalated to the required coordinates
    def drawCenteredText(g, string, y, scale)
        x = $halfWidth - ((string.length() * @width) * scale)/2
        drawText(g, string, x, y, scale)
    end

    // Draw text using this font at the given (x,y) coords. Scale is applied
    // once the origin has been translated to the required coordinates
    def drawText(g, string, x, y, scale)
        if (g == :n)
            translate(x, y)
            scale(scale)
        else
            g.translate(x, y)
            g.scale(scale)
        end
        
        i = 0
        string.each() do |c|
            drawChar(g, c, (i * @width), 0)
            i = i + 1
        end
        
        if (g == :n)
            clearTransforms()
        else
            g.clearTransforms()
        end
    end
     
    def drawRotatedText(g, string, angle, x, y, scale)
        if (g == :n)
            translate(x, y)
            scale(scale)
            rotate(angle)
        else
            g.translate(x, y)
            g.scale(scale)
            g.rotate(angle)
        end
        
        i = 0
        string.each() do |c|
            drawChar(g, c, (i * @width), 0)
            i = i + 1
        end
        
        if (g == :n)
            clearTransforms()
        else
            g.clearTransforms()
        end
    end
end

// Helper method to 'pad' zero characters (0) to the
// start of a string (val).  Then (len) parameter determines
// how long the resulting string should be.
// E.g. padLeadingZeros("123", 5) will produce "00123"
def padLeadingZeros(val, len)
    if (val.length() >= len)
        return val
    end

    j = 0
    l = len - val.length()
    s = new Array(len+1)
    l.times() do
        s[j] = "0"
        j = j + 1
    end
    val.each() do |v|
        s[j] = v
        j = j + 1
    end
        
    return s.join('')
end

// Normalises the given value (v) which is in the range (minV,maxV)
// The output will be in the range (0.0, 1.0)
def normalise(v, minV, maxV)
    return (v - minV) / (maxV - minV)
end

// Simple Animation Class
class Animation
    /*
     * Constructor
     * Image:       Sprite Strip (assumed one continuous strip rather than a sheet of columns
     * NumFrames:   Number of frames within the strip
     * FrameStart:  Offset indicating which frame to start from
     * FrameEnd:    Offset indicating which frame to end with (if last, then give -1)
     * FrameWidth:  Width of one frame
     * FrameHeight: Height of one frame
     * FrameTime:   Time between each frame
     * NumLoops:    Continuous (-1), Single shot (1), Many (n)
     */
    def new(image, numFrames, frameStart, frameEnd, frameWidth, frameHeight, frameTime, bounce, numLoops)
        @image        = image
        @numFrames    = numFrames
        @frameStart   = frameStart
        @frameEnd     = frameEnd
        @bounce       = bounce
        @frameInc     = 1
        if (@frameEnd == -1)
            @frameEnd = @numFrames
        end
        @width        = @image.getWidth()
        @height       = @image.getHeight()
        @frameWidth   = frameWidth
        @frameHeight  = frameHeight
        @frameTime    = frameTime
        @numLoops     = numLoops
        @currentLoop  = 0
        @currentFrame = 0
        @frameTimer   = -1
        @expired      = false
        @srcX         = 0
        @paused       = false
    end
    attr(currentFrame)
    
    def update()
        if (@paused)
            return
        end
        
        if (@frameTimer == -1)
            @frameTimer = new Timer(@frameTime)
        end
        
        if (@expired == false)
            if (@frameTimer.isExpired())
                @frameTimer = new Timer(@frameTime)
                @currentFrame = @currentFrame + @frameInc
                if (@currentFrame >= @frameEnd || @currentFrame > @numFrames - 1)
                    
                    if (@bounce)
                        @frameInc = -@frameInc
                        @currentFrame = @numFrames-1
                    else
                        @currentFrame = @frameStart
                    end                    
                    if (@numLoops > -1)
                        @currentLoop = @currentLoop + 1
                        if (@currentLoop >= @numLoops)
                            @expired = true
                        end
                    end
                elseif (@currentFrame < 0)
                    @frameInc = -@frameInc
                    @currentFrame = 0
                end
                defineFrame()
            end        
        end
    end
    
    /*
     * Restart the animation
     * Can be done at any point - even if expired 
     */
    def restart()
        @paused       = false
        @expired      = false
        @currentFrame = @frameStart
        defineFrame()
        @frameTimer   = new Timer(@frameTime)
    end
    
    def defineFrame()
        @srcX = @currentFrame * @frameWidth
    end
    
    /*
     * Start the animation
     * If the animation was previously stopped (paused) then it 
     * will continue from where it left off
     */
    def start()
        if (@paused == false)
            @currentFrame = 0
            @frameTimer   = new Timer(@frameTime)
        else
            @paused       = false
        end
    end
    
    /*
     * Stop (pause) animation in progress
     */
    def stop()
        @paused = true
    end

    def draw(x, y, centered)
        drawImage(@image, @srcX, 0, @frameWidth, @frameHeight, x, y, centered)
    end
  
    def draw(x, y, width, height, centered)
        drawImage(@image, @srcX, 0, @frameWidth, @frameHeight, x, y, width, height, centered)
    end
    
    def drawRotated(angle, x, y, centered)
        drawRotatedImage(@image, angle, @srcX, 0, @frameWidth, @frameHeight, x, y, centered)
    end
    
    def drawRotated(angle, x, y, width, height, centered)
        drawRotatedImage(@image, angle, @srcX, 0, @frameWidth, @frameHeight, x, y, width, height, centered)
    end

    def isFinished()
        return @expired
    end
end

$D_TIMER = 0
$D_PLYR  = 1
$D_BALLS = 2
$D_BALL  = 3
$D_ZONE  = 4
$D_FROG  = 5
$D_L1S   = 6
$D_L2S   = 7
$D_L3S   = 8
$D_L4S   = 9
$D_L5S   = 10
$D_L6S   = 11
$D_L1T   = 12
$D_L2T   = 13
$D_L3T   = 14
$D_L4T   = 15
$D_L5T   = 16
$D_L6T   = 17

$T_CANOE = 0
$T_LOG   = 1 
$T_CROC  = 2
$T_MOWER = 3
$T_CAT   = 4
$T_GOLF  = 5
$T_SPEED = 6

// Define how the levels play
$levels = {
    //                                             [--        LANES         --]  [--        LANES          --] 
    //    TIMER    PLYR      NUM  BALL  ZONE  FRG   L1   L2   L3   L4   L5   L6   L1   L2   L3   L4   L5   L6
    //    SPEED   SPEED    BALLS   SCR   SCR       SPD  SPD  SPD  SPD  SPD  SPD  TYP  TYP  TYP  TYP  TYP  TYP
    0 : [ 0.006, [1.1,0.8],    1,   10,   10,   0, 0.5, 0.6, 0.4, 0.4, 0.5, 0.6,   0,   0,   0,   3,   3,   3],
    1 : [ 0.006, [1.1,0.8],    2,   20,   20,   0, 0.5, 0.6, 1.0, 0.4, 0.5, 0.6,   0,   0,   0,   3,   3,   3],
    2 : [ 0.006, [1.1,0.8],    2,   30,   30,   0, 0.7, 0.8, 1.0, 0.6, 1.0, 1.5,   0,   1,   0,   3,   3,   5],
    3 : [ 0.006, [1.7,1.4],    3,   40,   40,   0, 0.9, 1.5, 0.7, 0.6, 0.8, 1.5,   0,   1,   0,   3,   3,   5],
    4 : [ 0.012, [1.7,1.4],    3,   50,   50,   0, 0.9, 1.5, 0.9, 0.8, 0.8, 2.0,   0,   1,   2,   3,   4,   5],
    5 : [ 0.009, [1.7,1.4],    3,   60,   60,   1, 0.9, 1.5, 1.3, 0.8, 0.8, 2.0,   0,   1,   2,   3,   4,   5],                                                                                                                                                                         // Andy Was Here ;)
    6 : [ 0.010, [1.7,1.4],    3,   70,   70,   1, 0.7, 2.0, 1.3, 0.8, 0.8, 2.0,   1,   1,   2,   3,   4,   5],
    7 : [ 0.011, [1.7,1.4],    3,   80,   80,   1, 0.7, 2.0, 1.3, 0.8, 0.8, 5.0,   1,   1,   2,   3,   4,   5],
    8 : [ 0.010, [1.7,1.4],    3,   90,   90,   1, 0.7, 3.0, 1.3, 0.8, 0.8, 5.0,   1,   1,   2,   3,   4,   5],
    9 : [ 0.009, [3.4,2.8],    4,  100,  100,   1, 1.7, 1.5, 2.7, 1.0, 1.4, 4.0,   2,   1,   6,   3,   4,   5]
}
$MAX_LEVELS = 10


ERRORS

YOUR BROWSER DOES NOT SUPPORT HTML5!

Please use one of these instead

Our games cannot run in your browser