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