Gravity Maze
Updated on June 26th, 2021 at 1:11 am
What is it?
This was a game that I created in my mobile development class at my Junior College. The app was made with the Corona SDK and programmed in Lua. The concept of the game is similar to that of a marble labyrinth. There are two options on the bottom of the screen to place either blocks or balls. The blocks act as barriers or walls for the balls and do not move when you tilt the screen of the device. The balls, however, are reactive to the orientation of the device. Basically, the game is a marble labyrinth where you able place the walls of the maze.

What does it do?
This app was designed to be a 2k mobile game. It was created with the Corona SDK and as such, can be built for iOS and Android devices. It may be possible to build the game for macOS and Windows as well.
How does it work?
Games made with the Corona SDK are primarily made of Lua Scripts. Each script would generally equate to a single scene or level within the game. Since this game only has a single scene, there is only one script. The rest of the files in the project are made up of images, icons, backgrounds, and sprites.
main.lua
-----------------------------------------------------------------------------------------
-- Gravity Maze Lite
-- By Ryan Bains-Jordan
--
-- main.lua
-----------------------------------------------------------------------------------------
-- Load the Requisites
local physics = require("physics")
local widget = require( "widget" )
local json = require( "json" )
-- Get screen size
WIDTH = display.actualContentWidth
HEIGHT = display.actualContentHeight
xMin = display.screenOriginX
yMin = display.screenOriginY
xMax = xMin + WIDTH
yMax = yMin + HEIGHT
xCenter = (xMin + xMax) / 2
yCenter = (yMin + yMax) / 2
-- Game objects
local blocks = {}
local balls = {}
local goals = {}
local bombs = {}
local gameOver = {}
local options = {}
local button = {}
local text = {}
-----------------------------------------------------------------------------------------
-- File Functions
-----------------------------------------------------------------------------------------
-- Save the block locations
local function saveData()
local path = system.pathForFile( "myData.txt", system.DocumentsDirectory )
-- Make the dataTable
local dataTable = {}
for i = 1, #blocks do
local block = blocks[i]
dataTable[i] = { x = block.x, y = block.y }
end
-- Open the file for writing
local file = io.open( path, "w" )
-- Write the Data
local s = json.encode( dataTable )
print(s)
file:write(s)
-- Close the file
io.close(file)
end
-- Load the block locations
local function loadData()
local path = system.pathForFile( "myData.txt", system.DocumentsDirectory )
print( path )
local file = io.open( path, "r" )
-- Does the file exist?
if file then
-- Read our data from a file
local s = file:read( "*a" )
dataTable = json.decode(s)
-- Turn dataTable contents into real rectangles
for i = 1, #dataTable do
local xyData = dataTable[i]
local block = display.newRect( xyData.x, xyData.y, 20, 20 )
block.name = "block"
physics.addBody(block, "static", { density = 1.0, friction = 0.3, bounce = 0.2 })
blocks[#blocks + 1] = block -- add block to end of blocks array
end
-- Close the file (note: only do this if file is not nil)
io.close( file)
end
end
-----------------------------------------------------------------------------------------
-- Listener Functions
-----------------------------------------------------------------------------------------
local function onSystemEvent( event )
-- Should save data in response to either suspend or exit
if event.type == "applicationSuspend" or
event.type == "applicationExit" then
physics.pause( )
button.resume.isVisible = true
end
end
local function onOrientationChange( event )
print( event.type )
if event.type == "portrait" then
--button.options.rotation = 0
button.resume.rotation = 0
button.reset.rotation = 0
--text.portrait.isVisible = true
--text.landscape.isVisible = false
end
if event.type == "landscapeLeft" then
--button.options.rotation = -90
button.resume.rotation = -90
button.reset.rotation = -90
--text.landscape.isVisible = true
--text.portrait.isVisible = false
end
if event.type == "landscapeRight" then
--button.options.rotation = 90
button.resume.rotation = 90
button.reset.rotation = 90
--text.landscape.isVisible = true
--text.portrait.isVisible = false
end
end
local function resetButtonListener()
--optionsGroup.isVisible = false
for i = #blocks, 1, -1 do
blocks[i]:removeSelf( )
blocks[i] = nil
end
for i = #balls, 1, -1 do
balls[i]:removeSelf( )
balls[i] = nil
end
-- for i = #goals, 1, -1 do
-- goals[i]:removeSelf( )
-- goals[i] = nil
-- end
-- for i = #bombs, 1, -1 do
-- bombs[i]:removeSelf( )
-- bombs[i] = nil
-- end
text.blocksNum.text = 0
text.ballsNum.text = 0
-- text.goalsNum.text = 0
-- text.bombsNum.text = 0
physics.start( )
saveData()
return true
end
-- Listener for the Options Button ( Currently Disabled )
local function optionsButtonListener()
physics.pause( )
optionsGroup.isVisible = true
return true
end
-- Listener for the Segments on the Options Screen ( Currently Disabled )
local function optionsSegmentListener( event )
local target = event.target
if target.segmentNumber == 1 then
options.square.isVisible = true
options.circle.isVisible = false
elseif target.segmentNumber == 2 then
options.square.isVisible = false
options.circle.isVisible = true
end
end
-- Listener for the done button on the Options Screen ( Currently Disabled )
local function doneButtonListener()
physics.start( )
optionsGroup.isVisible = false
return true
end
-- Listener for the resume button that appears when game is suspended
local function resumeButtonListener()
physics.start( )
button.resume.isVisible = false
return true
end
-- Listener for the Segments on the main screen
local function segmentButtons( event )
local target = event.target
if target.segmentNumber == 1 then
button.segment.value = "block"
elseif target.segmentNumber == 2 then
button.segment.value = "ball"
elseif target.segmentNumber == 3 then
button.segment.value = "goal"
elseif target.segmentNumber == 4 then
button.segment.value = "bomb"
end
end
--[[ -- Listener for collisions collisions between the ball and other objects ( Currently Disabled )
local function onLocalCollision( event )
if event.phase == "began" then
if event.target.name ~= nil then
print( "collision by " .. event.target.name )
end
if event.other.name ~= nil then
print( "Ball hit " .. event.other.name)
end
end
end --]]
-----------------------------------------------------------------------------------------
-- Touch Function
-----------------------------------------------------------------------------------------
-- Touching the screen places a block
local block = nil
local ball = nil
-- local goal = nil
-- local bomb = nil
-- What happens when the screen is tocuched above settings
local function touch(event)
if event.phase == "began" and event.y < yMax - 125 then
-- Place block at initial touch position
if button.segment.value == "block" then
block = display.newRect(event.x, event.y, 20, 20)
block.name = "block"
block:toBack( )
elseif button.segment.value == "ball" then
ball = display.newCircle(event.x, event.y, 15)
ball.name = "ball"
ball:setFillColor(1, 0, 0)
ball:toBack()
-- elseif button.segment.value == "goal" then
-- goal = display.newCircle(event.x, event.y, 20)
-- goal.name = "goal"
-- goal:setFillColor(1, 1, 0.5, 0.6)
-- goal:toBack()
-- elseif button.segment.value == "bomb" then
-- bomb = display.newImageRect( "bomb.png", 20, 20 )
-- bomb.name = "bomb"
-- bomb.x = event.x
-- bomb.y = event.y
-- bomb:toBack()
end
elseif event.phase == "moved" and event.y < yMax - 125 then
-- Adjust block position while user drags
if button.segment.value == "block" then
block.x = event.x
block.y = event.y
block:toBack( )
elseif button.segment.value == "ball" then
ball.x = event.x
ball.y = event.y
ball:toBack( )
-- elseif button.segment.value == "goal" then
-- goal.x = event.x
-- goal.y = event.y
-- goal:toBack( )
-- elseif button.segment.value == "bomb" then
-- bomb.x = event.x
-- bomb.y = event.y
-- bomb:toBack( )
end
elseif block then
-- Make the block active in the final position and store it in the array
physics.addBody(block, "static", { density = 1.0, friction = 0.3, bounce = 0.2 })
blocks[#blocks + 1] = block -- add block to end of blocks array
print(#blocks .. " blocks")
text.blocksNum.text = #blocks
block = nil
saveData()
elseif ball then
physics.addBody(ball, { density = 1.0, friction = 0.3, bounce = 0.7, radius = 15 })
ball.collision = onLocalCollision
--ball:addEventListener( "collision", onLocalCollision )
ball.isSleepingAllowed = false
balls[#balls + 1] = ball
text.ballsNum.text = #balls
ball = nil
-- elseif goal then
-- goals[#goals + 1] = goal
-- text.goalsNum.text = #goals
-- goal = nil
-- elseif bomb then
-- bombs[#bombs + 1] = bomb
-- text.bombsNum.text = #bombs
-- bomb = nil
end
return true
end
-----------------------------------------------------------------------------------------
-- Convenience Functions
-----------------------------------------------------------------------------------------
-- Convenience Function for creating Buttons
function createButton( label, x, y, w, h, listener )
local b = widget.newButton {
label = label,
x = x, y = y, width = w, height = h,
shape="roundedRect",
cornerRadius = 5,
fillColor = { default={ 0, 0.5, 1 }, over={ 0, 0.3, 1 } },
labelColor = { default={ 1, 1, 1 } },
onEvent = listener
}
return b
end
-- Convenience Function for creating Segments
local function createSegment( x, y, segments, listener )
local s = widget.newSegmentedControl {
x = x, y = y,
segments = segments,
segmentWidth = 75,
defaultSegment = 1,
onPress = listener
}
return s
end
-- Convenience Function for creating Sliders
local function createSlider( x, y, width, listener )
local s = widget.newSlider {
x = x,
y = y,
width = width,
value = 0,
listener = listener
}
return s
end
-- Convenience Function for creating Text
local function createText( group, text, x, y, anchor )
local t = display.newText {
parent = group,
text = text,
x = x, y = y,
font = native.systemFont,
fontSize = 20
}
if anchor then
t.anchorX = anchor
end
return t
end
-----------------------------------------------------------------------------------------
-- Init Game
-----------------------------------------------------------------------------------------
function initGame()
-- Start physics engine
physics.start()
physics.setGravity( 0, 0 )
-- Game Over Screen
-- gameOver.pane = display.newRect( xCenter, yCenter, WIDTH - 50, HEIGHT - 50 )
-- gameOver.pane:setFillColor( 0.6, 0.6, 0.6 )
-- gameOver.pane.isVisible = false
-- Buttons
-- button.options = createButton( "Options", xMin + 45, yMax - 73, 70, 70, optionsButtonListener )
button.reset = createButton( "Reset", xMin + 45, yMax - 73, 70, 70, resetButtonListener ) -- Remove to see options button
button.resume = createButton( "Resume", xMin + 45, yMax - 73, 70, 70, resumeButtonListener )
button.resume.isVisible = false
--button.segment = createSegment( xCenter, yMax - 20, { "Block", "Ball", "Goal", "Bomb" }, segmentButtons )
button.segment = createSegment( xCenter, yMax - 20, { "Block", "Ball" }, segmentButtons )
--------------------
-- Object Numbers
--------------------
text.portrait = display.newGroup( )
text.blocks = createText( text.portrait, "Blocks:", xMax - 230, yMax - 90, 0 )
text.blocksNum = createText( text.portrait, 0, xMax - 160, yMax - 90, 0 )
text.balls = createText( text.portrait, "Balls:", xMax - 230, yMax - 60, 0 )
text.ballsNum = createText( text.portrait, 0, xMax - 160, yMax - 60, 0 )
-- text.goals = createText( text.portrait, "Goals:", xMax - 120, yMax - 90, 0 )
-- text.goalsNum = createText( text.portrait, 0, xMax - 40, yMax - 90, 0 )
-- text.bombs = createText( text.portrait, "Bombs:", xMax - 120, yMax - 60, 0 )
-- text.bombsNum = createText( text.portrait, 0, xMax - 40, yMax - 60, 0 )
--[[
text.landscape = display.newGroup( )
text.blocks = createText( text.landscape, "Blocks:", xMax - 30, yMax - 110 )
--text.blocks.rotation = 90
text.blocksNum = createText( text.landscape, 0, xMax - 50, yMax - 85 )
--text.blocksNum.rotation = 90
text.landscape.rotation = 10
text.landscape.isVisible = false
--]]
loadData()
--------------------
-- Walls
--------------------
-- Make walls around the borders of the screen
local thickness = 4
local wallPhysicsOptions = { density = 1.0, friction = 0.3, bounce = 0.2 }
local wall = display.newRect(xCenter, yMin, WIDTH, thickness) -- top
physics.addBody(wall, "static", wallPhysicsOptions)
wall = display.newRect(xCenter, yMax - 115, WIDTH, thickness) -- bottom
physics.addBody(wall, "static", wallPhysicsOptions)
wall = display.newRect(xMin, yCenter, thickness, HEIGHT) -- left
physics.addBody(wall, "static", wallPhysicsOptions)
wall = display.newRect(WIDTH, yCenter, thickness, HEIGHT) -- right
physics.addBody(wall, "static", wallPhysicsOptions)
wall.name = "wall"
--------------------
-- Options Screen
--------------------
--[[
optionsGroup = display.newGroup( )
-- Options Window
options.pane = display.newRect( optionsGroup, xCenter, yCenter, WIDTH, HEIGHT )
options.pane:setFillColor( 0, 0, 0 )
-- Done Button
options.done = createButton( "Done", xMin + 50, yMin + 50 , 75, 45, doneButtonListener )
-- Segment
options.segment = createSegment( xCenter, yCenter - 125, { "Block", "Ball" }, optionsSegmentListener)
-- Sliders
options.redSlider = createSlider( xCenter + 25, yCenter - 75, 150, redSliderListener )
options.greenSlider = createSlider( xCenter + 25, yCenter - 25, 150, greenSliderListener )
options.blueSlider = createSlider( xCenter + 25, yCenter + 25, 150, blueSliderListener )
-- Text
options.redSliderText = display.newText( optionsGroup, "Red", xCenter - 100, yCenter - 75, native.systemFontBold, 20 )
options.greenSliderText = display.newText( optionsGroup, "Green", xCenter - 100, yCenter - 25, native.systemFontBold, 20 )
options.blueSliderText = display.newText( optionsGroup, "Blue", xCenter - 100, yCenter + 25, native.systemFontBold, 20 )
options.redSliderText:setFillColor( 1, 0, 0 )
options.greenSliderText:setFillColor( 0, 1, 0 )
options.blueSliderText:setFillColor( 0, 0, 1 )
-- Square and Circle
options.square = display.newRect( optionsGroup, xCenter, yCenter + 100, 30, 30)
options.circle = display.newCircle( optionsGroup, xCenter, yCenter + 100, 15 )
-- Reset Button
options.reset = createButton( "Reset", xCenter, yMax - 50, 100, 60, resetButtonListener )
optionsGroup:insert( options.done )
optionsGroup:insert( options.segment )
optionsGroup:insert( options.redSlider )
optionsGroup:insert( options.greenSlider )
optionsGroup:insert( options.blueSlider )
optionsGroup:insert( options.reset )
optionsGroup.isVisible = false
--]]
end
-----------------------------------------------------------------------------------------
-- Other Functions and Listeners
-----------------------------------------------------------------------------------------
-- Handle accelerometer events. Simulate gravity in direction of device tilt.
function accelEvent(event)
print(event.xRaw, event.yRaw)
physics.setGravity( event.xRaw * 10, -event.yRaw * 10 )
end
-- Init the game, then add the event listeners
initGame()
Runtime:addEventListener( "accelerometer", accelEvent )
Runtime:addEventListener( "orientation", onOrientationChange )
Runtime:addEventListener( "touch", touch )
Runtime:addEventListener( "system", saveData )
Runtime:addEventListener( "system", onSystemEvent )
--Runtime:addEventListener( "system", onSystemEvent )
-- Load and show the joystick control if running on a simulator
if system.getInfo("environment") == "simulator" then
local joystick = require("joystick")
joystick:show(WIDTH - 50, yMin + 50)
end