Using the same updating random number in both a state file and a class file

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
apex2020
Prole
Posts: 1
Joined: Mon May 15, 2023 5:26 pm

Using the same updating random number in both a state file and a class file

Post by apex2020 »

I'm doing CS50's Intro to Game Development. In general, I want to generate a new random number each game update and use the value both in a State file and a Class file.

The assignment I'm working on is a Flappy Bird clone. If you're unfamiliar, the game has a bird flying through gaps between sets of upper and lower pipes. The students are given the assignment code for the complete game and then have to make modifications. I will post the code at the end.

Here are the relevant parts I'm dealing with. We have a class, Pipe.lua, that's used to generate a single pipe. Then we have a class, PipePair.lua, that puts two of the single pipes together into a pipe pair, turning one pipe upside down in the process and setting a gap between them. We also have a State Machine. One of the states, PlayState, governs the play of the game.

PipePair.lua, we set a constant GAP_HEIGHT to 90. This is for the gap between the upper and lower pipe. We then have the init() function where we instantiate a pair of pipes as follows:

self.pipes = {
['upper'] = Pipe('top', self.y),
['lower'] = Pipe('bottom', self.y + PIPE_HEIGHT + GAP_HEIGHT)
}

PIPE_HEIGHT is 288, the size of our pipe sprite.

In PlayState.lua. we have a variable self.y as a starting point to where we will draw each pipe. We set as follows:

self.lastY = -PIPE_HEIGHT + math.random(1,80) + 20
Then in the update method we set a local variable y to the following:

local y = math.max(-PIPE_HEIGHT + 10, math.min(self.lastY + math.random(-20, 20), VIRTUAL_HEIGHT - 90 - PIPE_HEIGHT))
self.lastY = y
table.insert(self.pipePairs, PipePair(y))

VIRTUAL_HEIGHT is 288, same as PIPE_HEIGHT. The purpose of this method is to change the location of the gap for each pipe pair somewhere between 20 pixels lower and 20 pixels higher than the last gap, in order to create a kind of flow. The purpose of the (+10) in the first value of math.max is to make sure the upper pipe is showing at least 10 pixels so you see the pipe. The hard-coded value 90 in the second value of math.min is supposed to represent the gap height. The purpose of this second value in math.min is to make sure the bottom pipe doesn't start being drawn below the screen and is thus not visible.

Finally, Pipe.lua has the render function for each pipe as follows:

love.graphics.draw(PIPE_IMAGE, self.x,
(self.orientation == 'top' and self.y + PIPE_HEIGHT or self.y),
0, 1, self.orientation == 'top' and -1 or 1)

I discovered a bug in the code. I noticed in the game that sometimes the bottom pipe was not visible. After doing some testing and calculating, I discovered that it was possible for the y of the bottom pipe to be set to 288, which is the virtual screen height. Unlike the top pipe, the bottom pipe is not set to be visible above the ground as a minimum height. The fix is to change the hard-coded 90 in PlayState's local y calculation to 110. But this only works if the gap height is 90. If the gap height is 60 the value needs to be 80, and if the gap height is 120, the value needs to be 140.

Here's the problem. Part of the assignment is for us to randomize the gap height in each spawn of pipe pairs. I'm trying to randomize the gap height between 60 and 120. But to make sure the bottom pipe is visible I need to provide the same value of gap height to both the PipePair class and the PlayState class. The random number needs to change on each update. But I can't figure out where to put a random number generation that will change for each update and share that value with both the PipePair and PlayState classes. How can I do that?

Here are the relevant files, minus comments:

PlayState.lua

Code: Select all

PlayState = Class{__includes = BaseState}

PIPE_SPEED = 60
PIPE_WIDTH = 70
PIPE_HEIGHT = 288

BIRD_WIDTH = 38
BIRD_HEIGHT = 24

function PlayState:init()
    self.bird = Bird()
    self.pipePairs = {}
    self.timer = 0
    self.score = 0

    self.lastY = -PIPE_HEIGHT + math.random(80) + 20
end

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

    if self.timer > 2 then
        local y = math.max(-PIPE_HEIGHT + 10, 
         math.min(self.lastY + math.random(-20,20), VIRTUAL_HEIGHT - 90 - PIPE_HEIGHT))
        self.lastY = y

        table.insert(self.pipePairs, PipePair(y))

        self.timer = 0
    end

    for k, pair in pairs(self.pipePairs) do
        if not pair.scored then
            if pair.x + PIPE_WIDTH < self.bird.x then
                self.score = self.score + 1
                pair.scored = true
                sounds['score']:play()
            end
        end

        pair:update(dt)
    end

    for k, pair in pairs(self.pipePairs) do
        if pair.remove then
            table.remove(self.pipePairs, k)
        end
    end

    for k, pair in pairs(self.pipePairs) do
        for l, pipe in pairs(pair.pipes) do
            if self.bird:collides(pipe) then
                sounds['explosion']:play()
                sounds['hurt']:play()

                gStateMachine:change('score', {
                    score = self.score
                })
            end
        end
    end

    self.bird:update(dt)

    if self.bird.y > VIRTUAL_HEIGHT - 15 then
        sounds['explosion']:play()
        sounds['hurt']:play()

        gStateMachine:change('score', {
            score = self.score
        })
    end
end

function PlayState:render()
    for k, pair in pairs(self.pipePairs) do
        pair:render()
    end

    love.graphics.setFont(flappyFont)
    love.graphics.print('Score: ' .. tostring(self.score), 8, 8)

    self.bird:render()
end

function PlayState:enter()
    scrolling = true
end

function PlayState:exit()
    scrolling = false
end
PipePair.lua

Code: Select all

PipePair = Class{}

local GAP_HEIGHT = 90

function PipePair:init(y)
    self.scored = false

    self.x = VIRTUAL_WIDTH + 32

    self.y = y

    self.pipes = {
        ['upper'] = Pipe('top', self.y),
        ['lower'] = Pipe('bottom', self.y + PIPE_HEIGHT + GAP_HEIGHT)
    }

    self.remove = false
end

function PipePair:update(dt)
    if self.x > -PIPE_WIDTH then
        self.x = self.x - PIPE_SPEED * dt
        self.pipes['lower'].x = self.x
        self.pipes['upper'].x = self.x
    else
        self.remove = true
    end
end

function PipePair:render()
    for l, pipe in pairs(self.pipes) do
        pipe:render()
    end
end
Pipe.lua

Code: Select all

Pipe = Class{}

local PIPE_IMAGE = love.graphics.newImage('pipe.png')

function Pipe:init(orientation, y)
    self.x = VIRTUAL_WIDTH + 64
    self.y = y

    self.width = PIPE_WIDTH
    self.height = PIPE_HEIGHT

    self.orientation = orientation
end

function Pipe:update(dt)

end

function Pipe:render()
    love.graphics.draw(PIPE_IMAGE, self.x, 
    (self.orientation == 'top' and self.y + PIPE_HEIGHT or self.y), 
    0, 1, self.orientation == 'top' and -1 or 1)
end
duaner
Prole
Posts: 41
Joined: Thu May 07, 2020 6:43 pm
Contact:

Re: Using the same updating random number in both a state file and a class file

Post by duaner »

If I understand you, the easiest way would be to set a global variable (just don't use local...) to a default value wherever you think is appropriate, and set and read it from anywhere.
User avatar
zorg
Party member
Posts: 3446
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Using the same updating random number in both a state file and a class file

Post by zorg »

>I need to provide the same value of gap height to both the PipePair class and the PlayState class.
Then provide it (to PipePair); you can just add it as another parameter to the init function, which is a way better solution than littering globals in your project. :/

The question about where the random generation should take place, it should be where your code decides to spawn another pipe, obviously.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 8 guests