I'm hoping somebody experienced can point out where/if I am missing some crucial syntax.
My program works fine (because 95% of it was created by a CS50 professor), with one exception - I cannot seem to get the player and flag files/classes to reference each other to show that my Mario character has reached the final flag.
Specifically, the function "Flag:victory(player)" does not appear to be pulling information from the player file/class, while player has a similar function that is able to access Flag, although I don't see any difference between the two files as to why Flag is not behaving properly.
Flag = Class{}
function Flag:init(map)
-- reference to map for checking tiles
self.texture = map.spritesheet
-- animation frames
self.frames = {}
-- current animation frame
self.currentFrame = nil
self.player = Player(map)
-- used to determine behavior and animations
self.state = 'waving'
-- x and y velocity
self.dy = 0
-- position on top of map tiles
self.y = map.tileHeight * ((map.mapHeight / 2) - 4)
self.x = (map.mapWidth - 3) * map.tileWidth
-- initialize all player animations
self.animations = {
['waving'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(0, 48, 16, 16, self.texture:getDimensions()),
love.graphics.newQuad(16, 48, 16, 16, self.texture:getDimensions()),
love.graphics.newQuad(0, 48, 16, 16, self.texture:getDimensions()),
},
interval = 0.30
}),
['falling'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(32, 48, 16, 16, self.texture:getDimensions())
}
})
}
-- initialize animation and current frame we should render
self.animation = self.animations['waving']
self.currentFrame = self.animation:getCurrentFrame()
end
function Flag:update(dt)
self.animation:update(dt)
self.currentFrame = self.animation:getCurrentFrame()
self:victory()
end
function Flag:render()
local scaleX = - 1
-- draw sprite with scale factor and offsets
love.graphics.draw(self.texture, self.currentFrame, math.floor(self.x + 8 / 2),
math.floor(self.y + 8 / 2), 0, scaleX, 1, 8 / 2, 8 / 2)
end
function Flag:victory(player)
if self.player.victory == true then -- when player reaches flag
self.state = 'falling' -- change flag animation to down sprite
self.animation = self.animations['falling']
self.y = self.y + 2 -- descend the flag
end
end
--[[
Represents our player in the game, with its own sprite.
]]
Player = Class{}
local WALKING_SPEED = 140
local JUMP_VELOCITY = 400
function Player:init(map)
self.x = 0
self.y = 0
self.width = 16
self.height = 20
-- offset from top left to center to support sprite flipping
self.xOffset = 8
self.yOffset = 10
-- reference to map for checking tiles
self.map = map -- just a convenience because map comes in as a param/arg so the self should be dropped
self.texture = love.graphics.newImage('graphics/blue_alien.png')
-- sound effects
self.sounds = {
['jump'] = love.audio.newSource('sounds/jump.wav', 'static'),
['hit'] = love.audio.newSource('sounds/hit.wav', 'static'),
['coin'] = love.audio.newSource('sounds/coin.wav', 'static'),
}
-- variable for playing sound just once when game ends
self.musicplayed = false
-- animation frames
self.frames = {}
-- current animation frame
self.currentFrame = nil
-- used to determine behavior and animations
self.state = 'idle'
-- determines sprite flipping
self.direction = 'left'
-- x and y velocity
self.dx = 0
self.dy = 0
-- position on top of map tiles
self.y = map.tileHeight * ((map.mapHeight - 2) / 2) - self.height
self.x = map.tileWidth * 10
self.xmax = self.x -- farthest that mario has ever gone to the right
self.xmin = 1 -- farthest mario may go to the left, based on xmax and left border of map
-- initialize all player animations
self.animations = {
['idle'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(0, 0, 16, 20, self.texture:getDimensions())
}
}),
['walking'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(128, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(144, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(160, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(144, 0, 16, 20, self.texture:getDimensions()),
},
interval = 0.15
}),
['jumping'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(32, 0, 16, 20, self.texture:getDimensions())
}
}),
['victory'] = Animation({
texture = self.texture,
frames = {
love.graphics.newQuad(0, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(48, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(0, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(160, 0, 16, 20, self.texture:getDimensions()),
love.graphics.newQuad(0, 0, 16, 20, self.texture:getDimensions()),
},
interval = .25
})
}
-- initialize animation and current frame we should render
self.animation = self.animations['idle']
self.currentFrame = self.animation:getCurrentFrame()
-- behavior map we can call based on player state
self.behaviors = {
['idle'] = function(dt)
-- add spacebar functionality to trigger jump state
if love.keyboard.wasPressed('space') then
self.dy = -JUMP_VELOCITY
self.state = 'jumping'
self.animation = self.animations['jumping']
self.sounds['jump']:play()
elseif love.keyboard.isDown('left') then
self.direction = 'left'
self.dx = -WALKING_SPEED
self.state = 'walking'
self.animations['walking']:restart()
self.animation = self.animations['walking']
self:checkLeftBoundary()
elseif love.keyboard.isDown('right') then
self.direction = 'right'
self.dx = WALKING_SPEED
self.state = 'walking'
self.animations['walking']:restart()
self.animation = self.animations['walking']
else
self.dx = 0
end
self:checkLeftBoundary()
self:checkRightBoundary()
end,
['walking'] = function(dt)
-- keep track of input to switch movement while walking, or reset
-- to idle if we're not moving
if love.keyboard.wasPressed('space') then
self.dy = -JUMP_VELOCITY
self.state = 'jumping'
self.animation = self.animations['jumping']
self.sounds['jump']:play()
elseif love.keyboard.isDown('left') then
self.direction = 'left'
self.dx = -WALKING_SPEED
elseif love.keyboard.isDown('right') then
self.direction = 'right'
self.dx = WALKING_SPEED
else
self.dx = 0
self.state = 'idle'
self.animation = self.animations['idle']
end
self:checkLeftBoundary()
self:checkRightBoundary()
-- check for collisions moving left and right
self:checkRightCollision()
self:checkLeftCollision()
-- check if there's a tile directly beneath us
if not self.map:collides(self.map:tileAt(self.x, self.y + self.height)) and
not self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then
-- if so, reset velocity and position and change state
self.state = 'jumping'
self.animation = self.animations['jumping']
end
end,
['jumping'] = function(dt)
if love.keyboard.isDown('left') then
self.direction = 'left'
self.dx = -WALKING_SPEED
elseif love.keyboard.isDown('right') then
self.direction = 'right'
self.dx = WALKING_SPEED
end
-- apply map's gravity before y velocity
self.dy = self.dy + self.map.gravity
-- check if there's a tile directly beneath us
if self.map:collides(self.map:tileAt(self.x, self.y + self.height)) or
self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then
-- if so, reset velocity and position and change state
self.dy = 0
self.state = 'idle'
self.animation = self.animations['idle']
self.y = (self.map:tileAt(self.x, self.y + self.height).y - 1) * self.map.tileHeight - self.height
end
self:checkLeftBoundary()
self:checkRightBoundary()
-- check for collisions moving left and right
self:checkRightCollision()
self:checkLeftCollision()
end,
['victory'] = function(dt)
self.dx = 0
-- check if there's a tile directly beneath us
if self.map:collides(self.map:tileAt(self.x, self.y + self.height)) or
self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then
-- if so, reset velocity and position and change state
self.dy = 0
self.y = (self.map:tileAt(self.x, self.y + self.height).y - 1) * self.map.tileHeight - self.height
end
end
}
end
function Player:update(dt)
self.behaviors[self.state](dt)
self.animation:update(dt)
self.currentFrame = self.animation:getCurrentFrame()
self.x = self.x + self.dx * dt
self.xmax = math.max(self.x, self.xmax) -- tester to update xmax if player moves further to right than ever before
self.xmin = math.max(1, math.min(self.xmax - VIRTUAL_WIDTH / 2, map.mapWidthPixels - VIRTUAL_WIDTH)) -- farthest mario may go to the left, based on xmax and left border of map
self:calculateJumps()
self:gameover()
self:reachflag()
if self.map:victory() == true then
self.victory = true
end
-- apply velocity
self.y = self.y + self.dy * dt
end
function Player:gameover()
if self.y > 300 then
self.game_over = true
end
end
function Player:reachflag(Flag)
if self.x >= flag.x then
self.victory = true
end
end
-- jumping and block hitting logic
function Player:calculateJumps()
-- if we have negative y velocity (jumping), check if we collide
-- with any blocks above us
if self.dy < 0 then
if self.map:tileAt(self.x, self.y).id ~= TILE_EMPTY or
self.map:tileAt(self.x + self.width - 1, self.y).id ~= TILE_EMPTY then
-- reset y velocity
self.dy = 0
-- change block to different block
local playCoin = false
local playHit = false
if self.map:tileAt(self.x, self.y).id == JUMP_BLOCK then
self.map:setTile(math.floor(self.x / self.map.tileWidth) + 1,
math.floor(self.y / self.map.tileHeight) + 1, JUMP_BLOCK_HIT)
playCoin = true
else
playHit = true
end
if self.map:tileAt(self.x + self.width - 1, self.y).id == JUMP_BLOCK then
self.map:setTile(math.floor((self.x + self.width - 1) / self.map.tileWidth) + 1,
math.floor(self.y / self.map.tileHeight) + 1, JUMP_BLOCK_HIT)
playCoin = true
else
playHit = true
end
if playCoin then
self.sounds['coin']:play()
elseif playHit then
self.sounds['hit']:play()
end
end
end
end
-- checks two tiles to our left to see if a collision occurred
function Player:checkLeftCollision()
if self.dx < 0 then
-- check if there's a tile directly beneath us
if self.map:collides(self.map:tileAt(self.x - 1, self.y)) or
self.map:collides(self.map:tileAt(self.x - 1, self.y + self.height - 1)) then
-- if so, reset velocity and position and change state
self.dx = 0
self.x = self.map:tileAt(self.x - 1, self.y).x * self.map.tileWidth
end
end
end
-- checks two tiles to our right to see if a collision occurred
function Player:checkRightCollision()
if self.dx > 0 then
-- check if there's a tile directly beneath us
if self.map:collides(map:tileAt(self.x + self.width, self.y)) or
self.map:collides(map:tileAt(self.x + self.width, self.y + self.height - 1)) then
-- if so, reset velocity and position and change state
self.dx = 0
self.x = (self.map:tileAt(self.x + self.width, self.y).x - 1) * map.tileWidth - self.width
end
end
end
function Player:checkLeftBoundary()
if self.x <= self.xmin then -- went too far left
self.x = self.xmin
if self.direction == 'left'then
self.dx = 0 -- set speed to zero but keep state, ie walking, jumping/falling
end
end
end
function Player:checkRightBoundary()
if self.x >= map.mapWidthPixels - 16 then -- went too far right
self.x = map.mapWidthPixels - 16
if self.direction == 'right'then
self.dx = 0 -- set speed to zero but keep state, ie walking, jumping/falling
end
end
end
function Player:render()
local scaleX
-- set negative x scale factor if facing left, which will flip the sprite
-- when applied
if self.direction == 'right' then
scaleX = 1
else
scaleX = -1
end
-- draw sprite with scale factor and offsets
love.graphics.draw(self.texture, self.currentFrame, math.floor(self.x + self.xOffset),
math.floor(self.y + self.yOffset), 0, scaleX, 1, self.xOffset, self.yOffset)
if self.game_over == true then
self.state = 'idle'
self.dy = 0
self.dx = 0
map.music:stop()
if self.musicplayed == false then
love.audio.newSource('sounds/Laser_Shoot2.wav', 'static'):play()
self.musicplayed = true
end
love.graphics.print('Oh Snap! Try again? (y/n)', self.xmin + 40, 40)
if love.keyboard.isDown('y') then
self.map:init()
self.map:render()
elseif love.keyboard.isDown('n')then
love.event.quit()
end
end
if self.victory == true then
self.state = 'victory'
self.animation = self.animations['victory']
if self.musicplayed == false then
love.audio.newSource('sounds/Powerup23.wav', 'static'):play()
self.musicplayed = true
end
love.graphics.print('Congratulations! Play again? (y/n)', self.xmin + 35, 40)
if love.keyboard.isDown('y') then
self.map:init()
self.map:render()
map.music:stop()
elseif love.keyboard.isDown('n')then
love.event.quit()
end
end
end
--[[
Super Mario Bros. Demo
Author: Colton Ogden
Original Credit: Nintendo
Demonstrates rendering a screen of tiles.
]]
Class = require 'class'
push = require 'push'
require 'Animation'
require 'Map'
require 'Player'
require 'Flag'
-- close resolution to NES but 16:9
VIRTUAL_WIDTH = 432
VIRTUAL_HEIGHT = 243
-- actual window resolution
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
-- seed RNG
math.randomseed(os.time())
-- makes upscaling look pixel-y instead of blurry
love.graphics.setDefaultFilter('nearest', 'nearest')
-- an object to contain our map data
map = Map()
flag = Flag(map)
player = Player(map)
-- performs initialization of all objects and data needed by program
function love.load()
-- sets up a different, better-looking retro font as our default
love.graphics.setFont(love.graphics.newFont('fonts/font.ttf', 16))
-- sets up virtual screen resolution for an authentic retro feel
push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
fullscreen = false,
resizable = true
})
love.window.setTitle('Super Mario 50')
love.keyboard.keysPressed = {}
love.keyboard.keysReleased = {}
end
-- called whenever window is resized
function love.resize(w, h)
push:resize(w, h)
end
-- global key pressed function
function love.keyboard.wasPressed(key)
if (love.keyboard.keysPressed[key]) then
return true
else
return false
end
end
-- global key released function
function love.keyboard.wasReleased(key)
if (love.keyboard.keysReleased[key]) then
return true
else
return false
end
end
-- called whenever a key is pressed
function love.keypressed(key)
if key == 'escape' then
love.event.quit()
end
love.keyboard.keysPressed[key] = true
end
-- called whenever a key is released
function love.keyreleased(key)
love.keyboard.keysReleased[key] = true
end
-- called every frame, with dt passed in as delta in time since last frame
function love.update(dt)
map:update(dt)
-- reset all keys pressed and released this frame
love.keyboard.keysPressed = {}
love.keyboard.keysReleased = {}
end
-- called each frame, used to render to the screen
function love.draw()
-- begin virtual resolution drawing
push:apply('start')
-- clear screen using Mario background blue
love.graphics.clear(108/255, 140/255, 255/255, 255/255)
-- renders our map object onto the screen
love.graphics.translate(math.floor(-map.camX + 0.5), math.floor(-map.camY + 0.5))
map:render()
-- end virtual resolution
push:apply('end')
end
--[[
Contains tile data and necessary code for rendering a tile map to the
screen.
]]
require 'Util'
Map = Class{}
TILE_BRICK = 1
TILE_EMPTY = -1
-- cloud tiles
CLOUD_LEFT = 6
CLOUD_RIGHT = 7
-- bush tiles
BUSH_LEFT = 2
BUSH_RIGHT = 3
-- mushroom tiles
MUSHROOM_TOP = 10
MUSHROOM_BOTTOM = 11
-- jump block
JUMP_BLOCK = 5
JUMP_BLOCK_HIT = 9
-- flagpole
FLAGPOLE_TOP = 8
FLAGPOLE_MIDDLE = 12
FLAGPOLE_BOTTOM = 16
PYRAMID_BLOCK = 17
-- a speed to multiply delta time to scroll map; smooth value
local SCROLL_SPEED = 62
-- constructor for our map object
function Map:init()
self.spritesheet = love.graphics.newImage('graphics/spritesheet.png')
self.sprites = generateQuads(self.spritesheet, 16, 16)
self.music = love.audio.newSource('sounds/music.wav', 'static')
self.pyramid_block = love.graphics.newImage('graphics/pyramidblock.png')
self.tileWidth = 16
self.tileHeight = 16
self.mapWidth = 300
self.mapHeight = 28
self.tiles = {}
-- applies positive Y influence on anything affected
self.gravity = 15
-- associate player with map
self.player = Player(self)
self.flag = Flag(self)
-- camera offsets
self.camX = 0
self.camY = -3
-- cache width and height of map in pixels
self.mapWidthPixels = self.mapWidth * self.tileWidth
self.mapHeightPixels = self.mapHeight * self.tileHeight
-- first, fill map with empty tiles
for y = 1, self.mapHeight do
for x = 1, self.mapWidth do
-- support for multiple sheets per tile; storing tiles as tables
self:setTile(x, y, TILE_EMPTY)
end
end
-- begin generating the terrain using vertical scan lines
local x = 1
while x < self.mapWidth - 20 do
-- 2% chance to generate a cloud
-- make sure we're 2 tiles from edge at least
if x < self.mapWidth - 2 then
if math.random(20) == 1 then
-- choose a random vertical spot above where blocks/pipes generate
local cloudStart = math.random(self.mapHeight / 2 - 6)
self:setTile(x, cloudStart, CLOUD_LEFT)
self:setTile(x + 1, cloudStart, CLOUD_RIGHT)
end
end
-- 5% chance to generate a mushroom
if math.random(20) == 1 then
-- left side of pipe
self:setTile(x, self.mapHeight / 2 - 2, MUSHROOM_TOP)
self:setTile(x, self.mapHeight / 2 - 1, MUSHROOM_BOTTOM)
-- creates column of tiles going to bottom of map
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
-- next vertical scan line
x = x + 1
-- 10% chance to generate a bush, being sure to generate away from edge
elseif math.random(10) == 1 and x < self.mapWidth - 3 then
local bushLevel = self.mapHeight / 2 - 1
-- place bush component and then column of bricks
self:setTile(x, bushLevel, BUSH_LEFT)
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
x = x + 1
self:setTile(x, bushLevel, BUSH_RIGHT)
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
x = x + 1
-- 10% chance to not generate anything, creating a gap
elseif math.random(10) ~= 1 then
-- creates column of tiles going to bottom of map
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
-- chance to create a block for Mario to hit
if math.random(15) == 1 then
self:setTile(x, self.mapHeight / 2 - 4, JUMP_BLOCK)
end
-- next vertical scan line
x = x + 1
else
-- increment X so we skip two scanlines, creating a 2-tile gap
x = x + 2
end
end
while x <= self.mapWidth do
-- draw in pyramid
if x == self.mapWidth - 13 then
local row = 1 -- how wide pyramid to be
while row < 7 do
local column = 1 -- might need to not say local everytime
while column <= row do
self:setTile(x, self.mapHeight / 2 - column, PYRAMID_BLOCK)
column = column + 1
end
-- creates column of tiles going to bottom of map
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
row = row + 1
x = x + 1
end
end
-- draw in flagpole
if x == self.mapWidth - 2 then
self:setTile(x, self.mapHeight / 2 - 3, FLAGPOLE_TOP)
self:setTile(x, self.mapHeight / 2 - 2, FLAGPOLE_MIDDLE)
self:setTile(x, self.mapHeight / 2 - 1, FLAGPOLE_BOTTOM)
end
-- creates column of tiles going to bottom of map
for y = self.mapHeight / 2, self.mapHeight do
self:setTile(x, y, TILE_BRICK)
end
-- next vertical scan line
x = x + 1
end
-- start the background music
self.music:setLooping(true)
self.music:play()
end
-- return whether a given tile is collidable
function Map:collides(tile)
-- define our collidable tiles
local collidables = {
TILE_BRICK, JUMP_BLOCK, JUMP_BLOCK_HIT,
MUSHROOM_TOP, MUSHROOM_BOTTOM, PYRAMID_BLOCK
}
-- iterate and return true if our tile type matches
for _, v in ipairs(collidables) do
if tile.id == v then
return true
end
end
return false
end
-- this function doesn't appear to be doing anything...
function Map:victory(player, flag)
if self.player.x >= self.flag.x * map.tileWidth then
return true
else
return false
end
end
-- function to update camera offset with delta time
function Map:update(dt)
self.player:update(dt)
self.flag:update(dt)
-- keep camera's X coordinate following the player, preventing camera from
-- scrolling past 0 to the left and the map's width
self.camX = math.min(self.mapWidthPixels - VIRTUAL_WIDTH, math.max(self.camX, math.min(self.player.x - VIRTUAL_WIDTH / 2,
math.min(self.mapWidthPixels - VIRTUAL_WIDTH, self.player.x))))
end
-- gets the tile type at a given pixel coordinate
function Map:tileAt(x, y)
return {
x = math.floor(x / self.tileWidth) + 1,
y = math.floor(y / self.tileHeight) + 1,
id = self:getTile(math.floor(x / self.tileWidth) + 1, math.floor(y / self.tileHeight) + 1)
}
end
-- returns an integer value for the tile at a given x-y coordinate
function Map:getTile(x, y)
return self.tiles[(y - 1) * self.mapWidth + x]
end
-- sets a tile at a given x-y coordinate to an integer value
function Map:setTile(x, y, id)
self.tiles[(y - 1) * self.mapWidth + x] = id
end
-- renders our map to the screen, to be called by main's render
function Map:render()
for y = 1, self.mapHeight do
for x = 1, self.mapWidth do
local tile = self:getTile(x, y)
if tile ~= TILE_EMPTY then
if tile > 16 then
love.graphics.draw(self.pyramid_block,
(x - 1) * self.tileWidth, (y - 1) * self.tileHeight)
else
love.graphics.draw(self.spritesheet, self.sprites[tile],
(x - 1) * self.tileWidth, (y - 1) * self.tileHeight)
end
end
end
end
self.player:render()
self.flag:render()
end
You're using 'self.player' which is a brand new player at the init() (which is a different player than the one you want) You need to use 'player' that you passed as an argument to check if player.victory is true.