CS50G Breakout - Paddle Size Update Glitch?

61 Views Asked by At

So everything in my code is fine and everything was going as expected. But then I realized 2 things, 1, my paddle doesn't go past size 3 despite having self.paddle.size = math.min(4, self.paddle.size + 1), and 2, when my paddle is on size 1 or 2, its fine, but on size 3 and everything gets a funky. Sometimes the powerups cant collide with the paddle, and sometimes some balls cant either, they just go through. Everything should be fine, I dont understand why paddle size 1 is okay but 3 isn't or why it wont go to 4 despite it supposed to be updating based on the score.

--[[
    GD50
    Breakout Remake

    -- PlayState Class --

    Author: Colton Ogden
    [email protected]

    Represents the state of the game in which we are actively playing;
    player should control the paddle, with the ball actively bouncing between
    the bricks, walls, and the paddle. If the ball goes below the paddle, then
    the player should lose one point of health and be taken either to the Game
    Over screen if at 0 health or the Serve screen otherwise.
]]

PlayState = Class{__includes = BaseState}

--[[
    We initialize what's in our PlayState via a state table that we pass between
    states as we go from playing to serving.
]]
function PlayState:enter(params)
    self.paddle = params.paddle
    self.bricks = params.bricks
    self.health = params.health
    self.score = params.score
    self.highScores = params.highScores

    -- self.balls should now be a table to store multiple balls
    self.balls = { params.ball }

    self.level = params.level

    self.recoverPoints = 5000

    -- make 2 seperate tables for the ball and key powerups so they dont conflict. Keep track of whether they've spawned
    self.ballPowerups = {}
    self.keyPowerups = {}
    self.ballPowerupSpawn = false
    self.keyPowerupSpawn = false

    -- the powerups will spawn depending on the time. Set a timer and spawn interval
    self.timer = 0
    self.interval = 5

    -- keep track of whether theres a locked brick so we know when to stop spawning the key powerup
    self.lockedBrick = false

    -- make a table of bricks so that we can randomize what bricks the powerups spawn from
    self.randomBricks = {}

    -- set the global variable from brick.lua, hasKey to false to tell it when or when we dont have a key already
    hasKey = false

    -- give ball random starting velocity
    self.balls[1].dx = math.random(-200, 200)
    self.balls[1].dy = math.random(-50, -60)
end

function PlayState:update(dt)
    -- update the timer
    self.timer = self.timer + dt

    if self.timer >= self.interval then
        -- if theres a locked brick in this map, set lockedBrick to true
        for k, brick in pairs(self.bricks) do
            if brick.isLocked then
                self.lockedBrick = true
            end

            -- decide if this brick can be used or not randomly
            if love.math.random(1, 2) == 1 then
                table.insert(self.randomBricks, brick)
            end
        end

        if #self.randomBricks > 0 then
            -- choose a random brick
            local chosenBallBrick = self.randomBricks[love.math.random(#self.randomBricks)]
            local chosenKeyBrick = self.randomBricks[love.math.random(#self.randomBricks)]

            -- always spawn ball powerup 
            local ballPowerup = Powerups(chosenBallBrick.x + chosenBallBrick.width / 2, chosenBallBrick.y + chosenBallBrick.height / 2, 9)
            table.insert(self.ballPowerups, ballPowerup)
            
            -- spawn a key powerup if theres a locked brick and we dont have a key. Then set keyPowerupSpawn to true
            if self.lockedBrick and hasKey == false then
                local keyPowerup = Powerups(chosenKeyBrick.x + chosenKeyBrick.width / 2, chosenKeyBrick.y + chosenKeyBrick.height / 2, 10)
                table.insert(self.keyPowerups, keyPowerup)
                self.keyPowerupSpawn = true
            end

            -- set ballPowerupSpawn to true and reset the timer
            self.ballPowerupSpawn = true
            self.timer = 0
        end
    end


    if self.paused then
        if love.keyboard.wasPressed('space') then
            self.paused = false
            gSounds['pause']:play()
        else
            return
        end
    elseif love.keyboard.wasPressed('space') then
        self.paused = true
        gSounds['pause']:play()
        return
    end

    -- update positions based on velocity
    self.paddle:update(dt)

    -- update all the balls in the table
    for k, ball in pairs(self.balls) do
        ball:update(dt)
    end

    -- update all the ball powerups in the table and check if any of them have gone past the screen, if so, remove them
    for k, ballPowerup in pairs(self.ballPowerups) do
        ballPowerup:update(dt)

        if ballPowerup.y > VIRTUAL_HEIGHT then
            table.remove(self.ballPowerups, k)
        end
    end

    -- update all the key powerups in the table, also check if they've gone past the screen
    for k, keyPowerup in pairs(self.keyPowerups) do
        keyPowerup:update(dt)

        if keyPowerup.y > VIRTUAL_HEIGHT then
            table.remove(self.keyPowerups, k)
        end
    end

    -- if there has been a ball powerup spawn, check if theres a collision with the paddle with every ball powerup in the table
    if self.ballPowerupSpawn == true then
        for k, ballPowerup in pairs(self.ballPowerups) do
            if ballPowerup:collision(self.paddle) == true then
                -- if there is a collision, spawn 2 new balls. Make them spawn near the paddle, and give them random velocities
                for i = 0, 1 do
                    local ball = Ball(self.balls[1].skin)
                    ball.x = self.paddle.x + self.paddle.width / 2 - ball.width / 2
                    ball.y = self.paddle.y - ball.height
                    ball.dx = math.random(-200, 200)
                    ball.dy = math.random(-50, -60)
                    table.insert(self.balls, ball)
                end
                -- since it collided with the paddle, remove it and set its spawn to false                
                table.remove(self.ballPowerups, k)
                self.ballPowerupSpawn = false
            end
        end
    end

    -- if theres been a key powerup spawn, go through each key powerup in the table
    if self.keyPowerupSpawn == true then
        for k, keyPowerup in pairs(self.keyPowerups) do
            -- if theres been a collision with the paddle, we now have a key, 
            -- the brick isn't locked anymore, 
            -- and we can remove the key powerup from the table and set its spawn to false
            if keyPowerup:collision(self.paddle) == true then
                hasKey = true
                self.lockedBrick = false
                table.remove(self.keyPowerups, k)
                self.keyPowerupSpawn = false
            end
        end
    end
    
    for k, ball in pairs(self.balls) do
        if ball:collides(self.paddle) then
            -- raise ball above paddle in case it goes below it, then reverse dy
            ball.y = self.paddle.y - 8
            ball.dy = -ball.dy

            --
            -- tweak angle of bounce based on where it hits the paddle
            --

            -- if we hit the paddle on its left side while moving left...
            if ball.x < self.paddle.x + (self.paddle.width / 2) and self.paddle.dx < 0 then
                ball.dx = -50 + -(8 * (self.paddle.x + self.paddle.width / 2 - ball.x))
            
            -- else if we hit the paddle on its right side while moving right...
            elseif ball.x > self.paddle.x + (self.paddle.width / 2) and self.paddle.dx > 0 then
                ball.dx = 50 + (8 * math.abs(self.paddle.x + self.paddle.width / 2 - ball.x))
            end

            gSounds['paddle-hit']:play()
        end
    end

    -- detect collision across all bricks with the ball
    for k, brick in pairs(self.bricks) do
        for i, ball in pairs(self.balls) do
            -- only check collision if we're in play
            if brick.inPlay and ball:collides(brick) then

                -- add to score
                self.score = self.score + (brick.tier * 200 + brick.color * 25)

                -- trigger the brick's hit function, which removes it from play
                brick:hit()

                -- if we have enough points, recover a point of health
                if self.score > self.recoverPoints then
                    -- can't go above 3 health
                    self.health = math.min(3, self.health + 1)

                    -- make the paddle bigger, not going above size 4
                    self.paddle.size = math.min(4, self.paddle.size + 1)

                    -- multiply recover points by 2
                    self.recoverPoints = self.recoverPoints + math.min(100000, self.recoverPoints * 2)

                    -- play recover sound effect
                    gSounds['recover']:play()
                end

                -- go to our victory screen if there are no more bricks left
                if self:checkVictory() then
                    gSounds['victory']:play()

                    gStateMachine:change('victory', {
                        level = self.level,
                        paddle = self.paddle,
                        health = self.health,
                        score = self.score,
                        highScores = self.highScores,
                        balls = { self.balls[1] },
                        recoverPoints = self.recoverPoints
                    })
                end

                --
                -- collision code for bricks
                --
                -- we check to see if the opposite side of our velocity is outside of the brick;
                -- if it is, we trigger a collision on that side. else we're within the X + width of
                -- the brick and should check to see if the top or bottom edge is outside of the brick,
                -- colliding on the top or bottom accordingly 
                --

                -- left edge; only check if we're moving right, and offset the check by a couple of pixels
                -- so that flush corner hits register as Y flips, not X flips
                if ball.x + 2 < brick.x and ball.dx > 0 then
                    
                    -- flip x velocity and reset position outside of brick
                    ball.dx = -ball.dx
                    ball.x = brick.x - 8
                
                -- right edge; only check if we're moving left, , and offset the check by a couple of pixels
                -- so that flush corner hits register as Y flips, not X flips
                elseif ball.x + 6 > brick.x + brick.width and ball.dx < 0 then
                    
                    -- flip x velocity and reset position outside of brick
                    ball.dx = -ball.dx
                    ball.x = brick.x + 32
                
                -- top edge if no X collisions, always check
                elseif ball.y < brick.y then
                    
                    -- flip y velocity and reset position outside of brick
                    ball.dy = -ball.dy
                    ball.y = brick.y - 8
                
                -- bottom edge if no X collisions or top collision, last possibility
                else
                    
                    -- flip y velocity and reset position outside of brick
                    ball.dy = -ball.dy
                    ball.y = brick.y + 16
                end

                -- slightly scale the y velocity to speed up the game, capping at +- 150
                if math.abs(ball.dy) < 150 then
                    ball.dy = ball.dy * 1.02
                end

                -- only allow colliding with one brick, for corners
                break
            end
        end
    end

    -- if ball goes below bounds, revert to serve state and decrease health
    for k, ball in pairs(self.balls) do
        if ball.y >= VIRTUAL_HEIGHT then
            table.remove(self.balls, k)
        end
    end

    -- only remove health, reduce paddle size, and change the state of the game if all the balls have been removed.
    if #self.balls == 0 then

        self.health = self.health - 1

        -- make the paddle a size smaller
        self.paddle.size = math.max(1, self.paddle.size - 1)

        gSounds['hurt']:play()

        if self.health == 0 then
            gStateMachine:change('game-over', {
                score = self.score,
                highScores = self.highScores
            })
        else
            gStateMachine:change('serve', {
                paddle = self.paddle,
                bricks = self.bricks,
                health = self.health,
                score = self.score,
                highScores = self.highScores,
                level = self.level,
                recoverPoints = self.recoverPoints
            })
        end
    end

    -- for rendering particle systems
    for k, brick in pairs(self.bricks) do
        brick:update(dt)
    end

    if love.keyboard.wasPressed('escape') then
        love.event.quit()
    end
end

function PlayState:render()
    -- render bricks
    for k, brick in pairs(self.bricks) do
        brick:render()
    end

    -- render balls
    for k, ball in pairs(self.balls) do
        ball:render()
    end

    -- render ball powerups
    for k, ballPowerup in pairs(self.ballPowerups) do
        ballPowerup:render(dt)
    end

    -- render key powerups
    for k, keyPowerup in pairs(self.keyPowerups) do
        keyPowerup:render(dt)
    end

    -- render all particle systems
    for k, brick in pairs(self.bricks) do
        brick:renderParticles()
    end

    self.paddle:render()

    renderScore(self.score)
    renderHealth(self.health)

    -- pause text, if paused
    if self.paused then
        love.graphics.setFont(gFonts['large'])
        love.graphics.printf("PAUSED", 0, VIRTUAL_HEIGHT / 2 - 16, VIRTUAL_WIDTH, 'center')
    end
end

function PlayState:checkVictory()
    for k, brick in pairs(self.bricks) do
        if brick.inPlay then
            return false
        end 
    end

    return true
end```
0

There are 0 best solutions below