My Lua maze builder that is braiding - lua

I am building a Lua script that generates a maze using a version of the Recursive Backtracker implemented with a stack rather than recursion. Presently the maze is coming out braided and I can't seem to figure out where in my logic this is happening. The function below takes in x and y as a starting point for generating the maze which is a 2d structure (table of tables):
local function buildMazeInternal(x,y,maze)
local stack = {}
local directions = {'North','East','South','West'}
table.insert(stack,{x=x,y=y})
while #stack > 0 do
local index = 1
local nextX = x
local nextY = y
local braid = false
for i = #directions, 2, -1 do -- backwards
local r = calc:Roll(1,i) -- select a random number between 1 and i
directions[i], directions[r] = directions[r], directions[i] -- swap the randomly selected item to position i
end
while index <= #directions and nextX == x and nextY == y do
if directions[index] == 'North' and y > 1 and not maze[y-1][x].Visited then
maze[y][x].North = true
maze[y-1][x].South = true
nextY = y-1
elseif directions[index] == 'East' and x < width and not maze[y][x+1].Visited then
maze[y][x].East = true
maze[y][x+1].West = true
nextX = x+1
elseif directions[index] == 'South' and y < height and not maze[y+1][x].Visited then
maze[y][x].South = true
maze[y+1][x].North = true
nextY = y+1
elseif directions[index] == 'West' and x > 1 and not maze[y][x-1].Visited then
maze[y][x].West = true
maze[y][x-1].East = true
nextX = x-1
else
index = index + 1
end
end
if nextX ~= x or nextY ~= y then
x = nextX
y = nextY
maze[y][x].Visited = true
table.insert(stack,{x=x,y=y})
else
x = stack[#stack].x
y = stack[#stack].y
table.remove(stack)
end
end
end
I know I'm overlooking something but I can't seem to nail it down. Note that the calc:Roll(1,100) method is a .net method in my app used to simulate rolling dice, in this case 1 * 100 sided die, it could be replaced with a call to math.Random(1,100) for use outside of my application.

I see at least one problem. When you go "want to go up", you check whether the "cell which is up" was visited, and if it is, you "skip" going up.
This does not seem to be correct IMHO. If you want to go up, but the cell which is "up" from the current cell was visited but has a "down" exit, you should still be able to go up (instead of skipping because it is visited).
The same applies to the other directions.
That's all I got.

I found the answer after posting on Reddit. I wasn't setting the initial cell to visited allowing it to be passed through twice. The correction was to add maze[y][x].Visited = true immediately before table.insert(stack,{x=x,y=y}).

Related

Attempt to index field (a nil value), for an Object

I have a problem with trying to add things to a LOVE2D version of Match-3. (From the CS50 course)
I added a function swapTiles() into my Board class and made a class object called self.board in a Class called PlayState. Then when I try to access the new function, it says this error:
Error
src/states/PlayState.lua:155: attempt to index field 'board' (a nil value)
I'll provide my Board and PlayState class below:
Board: (keep in mind the new function is literally in the code)
Board = Class{}
function Board:init(x, y, level) -- Added "level" as an integer for the block variations.
self.x = x
self.y = y
self.matches = {}
self.level = level
self:initializeTiles()
end
function Board:swapTiles(tile1, tile2)
-- swap grid positions of tiles
local tempX = tile1.gridX
local tempY = tile1.gridY
tile1.gridX = tile2.gridX
tile1.gridY = tile2.gridY
tile2.gridX = tempX
tile2.gridY = tempY
-- swap tiles in the tiles table
self.tiles[tile1.gridY][tile1.gridX] = tile1
self.tiles[tile2.gridY][tile2.gridX] = tile2
end
function Board:initializeTiles()
self.tiles = {}
-- There should only be two shiny tiles.
for tileY = 1, 8 do
-- empty table that will serve as a new row
table.insert(self.tiles, {})
for tileX = 1, 8 do
self.isPowerup = false
if math.random(1, 25) == 4 then
self.isPowerup = true
end
-- create a new tile at X,Y with a random color and variety
table.insert(self.tiles[tileY], Tile(tileX, tileY, math.random(18), math.min(8, math.random(1, self.level)), self.isPowerup))
end
end
while self:calculateMatches() do
-- recursively initialize if matches were returned so we always have
-- a matchless board on start
self:initializeTiles()
end
end
--[[
Goes left to right, top to bottom in the board, calculating matches by counting consecutive
tiles of the same color. Doesn't need to check the last tile in every row or column if the
last two haven't been a match.
]]
function Board:calculateMatches()
local matches = {}
-- how many of the same color blocks in a row we've found
local matchNum = 1
-- horizontal matches first
for y = 1, 8 do
local colorToMatch = self.tiles[y][1].color
matchNum = 1
-- every horizontal tile
for x = 2, 8 do
-- if this is the same color as the one we're trying to match...
if self.tiles[y][x].color == colorToMatch then
matchNum = matchNum + 1
else
-- set this as the new color we want to watch for
colorToMatch = self.tiles[y][x].color
-- if we have a match of 3 or more up to now, add it to our matches table
if matchNum >= 3 then
local match = {}
-- go backwards from here by matchNum
for x2 = x - 1, x - matchNum, -1 do
-- add each tile to the match that's in that match
table.insert(match, self.tiles[y][x2])
-- Shiny Check
if self.tiles[y][x2].isShiny == true then
for i = 1, 8 do
table.insert(match, self.tiles[y][i])
end
end
end
-- add this match to our total matches table
table.insert(matches, match)
end
-- don't need to check last two if they won't be in a match
if x >= 7 then
break
end
matchNum = 1
end
end
-- account for the last row ending with a match
if matchNum >= 3 then
local match = {}
-- go backwards from end of last row by matchNum
for x = 8, 8 - matchNum + 1, -1 do
table.insert(match, self.tiles[y][x])
end
table.insert(matches, match)
end
end
-- vertical matches
for x = 1, 8 do
local colorToMatch = self.tiles[1][x].color
matchNum = 1
-- every vertical tile
for y = 2, 8 do
if self.tiles[y][x].color == colorToMatch then
matchNum = matchNum + 1
else
colorToMatch = self.tiles[y][x].color
if matchNum >= 3 then
local match = {}
for y2 = y - 1, y - matchNum, -1 do
table.insert(match, self.tiles[y2][x])
if self.tiles[y2][x].isShiny == true then
for i = 1, 8 do
table.insert(match, self.tiles[i][x])
end
end
end
table.insert(matches, match)
end
matchNum = 1
-- don't need to check last two if they won't be in a match
if y >= 7 then
break
end
end
end
-- account for the last column ending with a match
if matchNum >= 3 then
local match = {}
-- go backwards from end of last row by matchNum
for y = 8, 8 - matchNum, -1 do
table.insert(match, self.tiles[y][x])
end
table.insert(matches, match)
end
end
-- store matches for later reference
self.matches = matches
-- return matches table if > 0, else just return false
return #self.matches > 0 and self.matches or false
end
--[[
Remove the matches from the Board by just setting the Tile slots within
them to nil, then setting self.matches to nil.
]]
function Board:removeMatches()
for k, match in pairs(self.matches) do
for k, tile in pairs(match) do
self.tiles[tile.gridY][tile.gridX] = nil
end
end
self.matches = nil
end
--[[
Shifts down all of the tiles that now have spaces below them, then returns a table that
contains tweening information for these new tiles.
]]
function Board:getFallingTiles()
-- tween table, with tiles as keys and their x and y as the to values
local tweens = {}
-- for each column, go up tile by tile till we hit a space
for x = 1, 8 do
local space = false
local spaceY = 0
local y = 8
while y >= 1 do
-- if our last tile was a space...
local tile = self.tiles[y][x]
if space then
-- if the current tile is *not* a space, bring this down to the lowest space
if tile then
-- put the tile in the correct spot in the board and fix its grid positions
self.tiles[spaceY][x] = tile
tile.gridY = spaceY
-- set its prior position to nil
self.tiles[y][x] = nil
-- tween the Y position to 32 x its grid position
tweens[tile] = {
y = (tile.gridY - 1) * 32
}
-- set space back to 0, set Y to spaceY so we start back from here again
space = false
y = spaceY
spaceY = 0
end
elseif tile == nil then
space = true
if spaceY == 0 then
spaceY = y
end
end
y = y - 1
end
end
-- create replacement tiles at the top of the screen
for x = 1, 8 do
for y = 8, 1, -1 do
local tile = self.tiles[y][x]
-- if the tile is nil, we need to add a new one
if not tile then
local tile = Tile(x, y, math.random(18), math.random(1, self.level))
tile.y = -32
self.tiles[y][x] = tile
tweens[tile] = {
y = (tile.gridY - 1) * 32
}
end
end
end
return tweens
end
function Board:getNewTiles()
return {}
end
function Board:testForMatches()
for y = 1, 8 do
for x = 1, 8 do
-- Test for left swap
if x > 1 then
end
end
end
end
function Board:render()
for y = 1, #self.tiles do
for x = 1, #self.tiles[1] do
self.tiles[y][x]:render(self.x, self.y)
end
end
end
Here's my PlayState: (look for PlayState:swapTiles(), keep in mind that the self.board is being used several times for and works fine, except when i try calling self.board:swapTiles().)
PlayState = Class{__includes = BaseState}
function PlayState:init()
-- start our transition alpha at full, so we fade in
self.transitionAlpha = 255
-- position in the grid which we're highlighting
self.boardHighlightX = 0
self.boardHighlightY = 0
-- timer used to switch the highlight rect's color
self.rectHighlighted = false
-- flag to show whether we're able to process input (not swapping or clearing)
self.canInput = true
-- tile we're currently highlighting (preparing to swap)
self.highlightedTile = nil
self.score = 0
self.timer = 60
-- set our Timer class to turn cursor highlight on and off
Timer.every(0.5, function()
self.rectHighlighted = not self.rectHighlighted
end)
-- subtract 1 from timer every second
Timer.every(1, function()
self.timer = self.timer - 1
-- play warning sound on timer if we get low
if self.timer <= 5 then
gSounds['clock']:play()
end
end)
end
function PlayState:enter(params)
-- grab level # from the params we're passed
self.level = params.level
-- spawn a board and place it toward the right
self.board = params.board or Board(VIRTUAL_WIDTH - 272, 16)
-- grab score from params if it was passed
self.score = params.score or 0
-- score we have to reach to get to the next level
self.scoreGoal = self.level * 1.25 * 1000
end
function PlayState:update(dt)
if love.keyboard.wasPressed('escape') then
love.event.quit()
end
-- go back to start if time runs out
if self.timer <= 0 then
-- clear timers from prior PlayStates
Timer.clear()
gSounds['game-over']:play()
gStateMachine:change('game-over', {
score = self.score
})
end
-- go to next level if we surpass score goal
if self.score >= self.scoreGoal then
-- clear timers from prior PlayStates
-- always clear before you change state, else next state's timers
-- will also clear!
Timer.clear()
gSounds['next-level']:play()
-- change to begin game state with new level (incremented)
gStateMachine:change('begin-game', {
level = self.level + 1,
score = self.score
})
end
if self.canInput then
-- move cursor around based on bounds of grid, playing sounds
if love.keyboard.wasPressed('up') then
self.boardHighlightY = math.max(0, self.boardHighlightY - 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('down') then
self.boardHighlightY = math.min(7, self.boardHighlightY + 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('left') then
self.boardHighlightX = math.max(0, self.boardHighlightX - 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('right') then
self.boardHighlightX = math.min(7, self.boardHighlightX + 1)
gSounds['select']:play()
end
-- if we've pressed enter, to select or deselect a tile...
if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then
-- if same tile as currently highlighted, deselect
local x = self.boardHighlightX + 1
local y = self.boardHighlightY + 1
-- if nothing is highlighted, highlight current tile
if not self.highlightedTile then
self.highlightedTile = self.board.tiles[y][x]
-- if we select the position already highlighted, remove highlight
elseif self.highlightedTile == self.board.tiles[y][x] then
self.highlightedTile = nil
-- if the difference between X and Y combined of this highlighted tile
-- vs the previous is not equal to 1, also remove highlight
elseif math.abs(self.highlightedTile.gridX - x) + math.abs(self.highlightedTile.gridY - y) > 1 then
gSounds['error']:play()
self.highlightedTile = nil
else
self:swapTiles(self.highlightedTile, self.board.tiles[y][x], true)
end
end
end
Timer.update(dt)
end
function PlayState:swapTiles(tile1, tile2, swapBackAtNoMatch)
local tile1 = tile1
local tile2 = tile2
local swapBackAtNoMatch = swapBackAtNoMatch
self.board:swapTiles(tile1, tile2) -- Causes the nil error.
if swapBackAtNoMatch then
-- tween coordinates between two swapping tiles
Timer.tween(0.1, {
[tile1] = {x = tile2.x, y = tile2.y},
[tile2] = {x = tile1.x, y = tile1.y}
})
-- once they've swapped, tween falling blocks
:finish(function ()
local matches = self.board:calculateMatches()
if matches then
self.calculateMatches(matches)
else
-- swap back if there's no match
self.swapTiles(tile1, tile2, false)
gSounds['error']:play()
end
end)
else
-- tween coordinates between the two so they swap
Timer.tween(0.1, {
[tile1] = {x = tile2.x, y = tile2.y},
[tile2] = {x = tile1.x, y = tile1.y}})
end
end
--[[
Calculates whether any matches were found on the board and tweens the needed
tiles to their new destinations if so. Also removes tiles from the board that
have matched and replaces them with new randomized tiles, deferring most of this
to the Board class.
]]
function PlayState:calculateMatches()
self.highlightedTile = nil
-- if we have any matches, remove them and tween the falling blocks that result
local matches = self.board:calculateMatches()
if matches then
gSounds['match']:stop()
gSounds['match']:play()
-- add score for each match
for k, match in pairs(matches) do
local varietyPoints = 0 -- We'll keep track of the bonus variety points here
-- We'll use vareity to calculate points for each tile within a match
for j, tiles in pairs(match) do
varietyPoints = varietyPoints + tiles.variety * 25
end
self.score = self.score + (#match * 50) + varietyPoints
-- Also add one second times the number of match to the timer
self.timer = self.timer + #match * 1
end
-- remove any tiles that matched from the board, making empty spaces
self.board:removeMatches()
-- gets a table with tween values for tiles that should now fall
local tilesToFall = self.board:getFallingTiles()
-- first, tween the falling tiles over 0.25s
Timer.tween(0.25, tilesToFall):finish(function()
local newTiles = self.board:getNewTiles()
-- then, tween new tiles that spawn from the ceiling over 0.25s to fill in
-- the new upper gaps that exist
Timer.tween(0.25, newTiles):finish(function()
-- recursively call function in case new matches have been created
-- as a result of falling blocks once new blocks have finished falling
self:calculateMatches()
end)
end)
-- if no matches, we can continue playing
else
self.canInput = true
end
end
Honestly doesn't make much sense. Please help!
function PlayState:swapTiles(tile1, tile2, swapBackAtNoMatch) end
is syntactic sugar for
PlayState.swaptiles = function(self, tile1, tile2, swapBackAtNoMatch) end
That's the reason why you can work with self inside that function.
A function defined like that needs to be called using the colon operator as well to make this work. Or you explicitly provide the table as first parameter.
Hence in your call self.swapTiles(tile1, tile2, false)
self is going to be tile1
tile1.board is nil so self.board is nil in this function call which causes the error
You have to call self:swapTiles(tile1, tile2, false) or self.swapTiles(self, tile1, tile2, false)
Please make sure you fully understand the colon syntax.

How do I drag and drop specific objects in Love2d?

I'm new to Lua and coding in general so I decided to write a Chess Program to learn. I have setup a class and created objects from it to represent the pieces. Now I want to begin moving the pieces with my mouse. I looked at a tutorial, but it only handled one rectangle. My first though was to use a "for" loop in the love.mousePressed() function to go though each of the objects until it found an object with a matching x, y coordinate. This obviously did not work the way I did it. Instead, it only goes to the next object every time the mouse is pressed or at least it would if the program didn't immediately crash once the button was released. So my question is, what is the right way to be going about this?
local blackPawn = love.graphics.newImage("Textures/Blackpawn.png")
local blackRook = love.graphics.newImage("Textures/Blackrook.png")
local blackKnight = love.graphics.newImage("Textures/Blackknight.png")
local blackBishop = love.graphics.newImage("Textures/Blackbishop.png")
local blackQueen = love.graphics.newImage("Textures/Blackqueen.png")
local blackKing = love.graphics.newImage("Textures/BlackKing.png")
local whitePawn = love.graphics.newImage("Textures/Whitepawn.png")
local whiteRook = love.graphics.newImage("Textures/Whiterook.png")
local whiteKnight = love.graphics.newImage("Textures/Whiteknight.png")
local whiteBishop = love.graphics.newImage("Textures/Whitebishop.png")
local whiteQueen = love.graphics.newImage("Textures/Whitequeen.png")
local whiteKing = love.graphics.newImage("Textures/WhiteKing.png")
local chessboard = love.graphics.newImage("Textures/ChessBoard.png")
local register = {}
local id = 0
piece = {
xSquare = 0, ySquare = 0,
x = 0, y = 0,
height = 64, width = 64,
pawn = false,
Rook = false,
Knight = false,
Bishop = false,
Queen = false,
King = false,
color = "",
texture = whitePawn,
dragging = {active = false, diffx = 0, diffy = 0}
}
function piece.new()
newPiece = {}
for k, v in pairs(piece) do
newPiece[k] = v
end
return newPiece
end
function piece:draw()
end
function getMouse()
local x, y = love.mouse.getPosition()
local isDown = love.mouse.isDown(1,2)
return x, y, isDown
end
function createBoard(id)
for x = 1, 8 do
for y = 1, 8 do
if y ~= 3 and y ~= 4 and y ~=5 and y ~= 6 then
id = id + 1
register[id] = piece.new()
register[id].x = x * 64 - 48
register[id].y = (y - 1) * 64
if y == 2 then
register[id].pawn = true
register[id].color = "white"
register[id].texture = whitePawn
print("item " .. id .. " is here x = " .. register[id].x .. " y = " .. register[id].y .. " Is pawn = " .. tostring(register[id].pawn) ..
" Color is " .. register[id].color)
elseif y == 7 then
register[id].pawn = true
register[id].color = "black"
register[id].texture = blackPawn
print("item " .. id .. " is here x = " .. register[id].x .. " y = " .. register[id].y .. " Is pawn = " .. tostring(register[id].pawn) ..
" Color is " .. register[id].color)
elseif y == 1 then
register[id].color = "white"
if x == 1 or x == 8 then
register[id].Rook = true
register[id].texture = whiteRook
elseif x == 2 or x == 7 then
register[id].Knight = true
register[id].texture = whiteKnight
print("knight is here")
elseif x == 3 or x == 6 then
register[id].Bishop = true
register[id].texture = whiteBishop
elseif x == 5 then
register[id].King = true
register[id].texture = whiteKing
elseif x == 4 then
register[id].Queen = true
register[id].texture = whiteQueen
end
elseif y == 8 then
register[id].color = "black"
if x == 1 or x == 8 then
register[id].Rook = true
register[id].texture = blackRook
elseif x == 2 or x == 7 then
register[id].Knight = true
register[id].texture = blackKnight
elseif x == 3 or x == 6 then
register[id].Bishop = true
register[id].texture = blackBishop
elseif x == 5 then
register[id].King = true
register[id].texture = blackKing
elseif x == 4 then
register[id].Queen = true
register[id].texture = blackQueen
end
end
end
end
end
end
function drawBoard(id, register)
love.graphics.draw(chessboard, 0, 0)
for id = 1, 32 do
love.graphics.draw(register[id].texture, register[id].x, register[id].y)
end
end
function love.load()
createBoard(id)
end
function love.update(dt)
for id = 1, 32 do
if register[id].dragging.active == true then
register[id].x = love.mouse.getX() - register[id].dragging.diffx
register[id].y = love.mouse.getY() - register[id].dragging.diffy
end
end
end
function love.draw()
drawBoard(id, register)
end
function love.mousepressed(x, y, button)
for id = 1, 32 do
if (button == 1 or button == 2)
and x > register[id].x and x < register[id].x + register[id].width
and y > register[id].y and y < register[id].y + register[id].height
then
register[id].dragging.active = true
register[id].dragging.diffx = x - register[id].x
register[id].dragging.diffy = y - register[id].y
end
end
end
function love.mousereleased(x, y, button)
for id = 1, 32 do
if button == 1 or button == 2 then register[id].dragging.active = false end
end
end
function love.keypressed(key, unicode)
end
function love.keyreleased(key)
end
function love.focus(bool)
end
function love.quit()
end
Update:
I fixed the crashing, but I still have the weird bug where it changes the dragged piece into a different piece
Update 2: After a little more debugging I have figured out that the major issue is that it for some reason does not correctly check if the dragging is active. As the code stands right now I need an else dragging.active = false after to correctly set it, but now that it is correctly set it won't drag anything at all despite the correct object have dragging set to active (unless I try and drag the object with value 32 where it drags everything at once). I am very confused as to what's wrong. Why isn't Lua able to check value like this?
First, I'd create a global boolean for if a piece has been selected and then a variable to hold the piece selected
local selected = false
local selectedPiece = {}
Then create a playing board and split it into a grid, with each square being of equal size. Something like this
board = {
size = { 8, 8 }, -- 8x8 grid
squareSize = 40, -- 40 pixels long sides
pieces = {
{ -- First row contains which pieces?
Piece:Rook(),
Piece:Bishop(),
...
},
{ -- Second row
Piece:Pawn(),
...
},
{ -- etc.
Piece:Empty(),
...
}
}
}
I advise not using nil in your table for empty squares due to the odd behavior of tables with nil indexes.
In your love.mousepressed() method, you check where the click was based on its position (this is assuming the board takes up the whole window)
function love.mousepressed(x, y, btn)
-- If a piece hasn't been clicked on.
if (not selected) then
-- This line is assuming that since all board squares are equal size, then the mouse click has to be in at least one square.
-- Therefore, if we take the floor of the position/board.squareSize, we will always get a value from 0 - 7 (8 values) on the board.
local piece = board.pieces[math.floor(x/board.squareSize)][math.floor(y/board.squareSize)]
-- If there is a piece here.
if (piece:isNotAnEmpty()) then
selectedPiece = piece -- Select the piece.
selected = not selected -- Notify program that a piece is selected to handle such things accordingly in other methods.
end
else
-- Assuming you wrote a method that determines if a piece can be moved to a certain spot on the board.
if (board:CanMovePieceHere(selectedPiece, x/board.size, y/board.size)) then
-- Do your stuff here.
...
-- Eventually, reset your variables.
selected = not selected
selectedPiece = {}
end
end
end
This is how I'd approach it, but your question is very open to interpretation.

How would I get my script to reset its score upon button press?

I need some help understanding how would I reset the score of numMiss, numHit, and numPercent back to 0 as soon as I tap the "reset.png" button and while doing so will also start the game from the beginning again.
Also, let me know if there are any corrections to be made within my code.
Heres what I have of the code so far
--width and height
WIDTH = display.contentWidth --320
HEIGHT = display.contentHeight --480
--display background
local p = display.newImageRect("park.png" ,500, 570)
p.x = WIDTH/2
p.y = HEIGHT/2
--display bouncing dog
local RADIUS = 5
local d = display.newImageRect("dogeball.png", 70, 70)
d.x = 50
d.y = 100
--display treat
local t = display.newImageRect("treat.png", 50, 50)
t.x = 245
t.y = math.random(HEIGHT)
--displays the reset button
local r = display.newImageRect("reset.png", 100,100)
r.x = 280
r.y = 480
--starting value of gravity and bounce(will change)
local GRAVITY = 0.3
local BOUNCE = 0.75
--downward force
local velocity = 0
--Tells the score to reset when true
local reset = false
--shows number of hits
local numHit = 0
--shows number of misses
local numMiss = 0
--Gets Percentage score
local numPercent = 0
--make hits and misses display
scoreHits = display.newText("Hits = " .. numHit, WIDTH/7, 1, native.systemFont, 18)
scoreMisses = display.newText("Misses = " .. numMiss, WIDTH/2.1, 1, native.systemFont, 18)
scorePercent = display.newText("Hit % = " .. numPercent, WIDTH/1.2, 1, native.systemFont, 18)
function enterFrame()
d.y = d.y + velocity
velocity = velocity + GRAVITY
local HIT_SLOP = RADIUS * 8 -- Adjust this to adjust game difficulty
if math.abs(t.x - d.x) <= HIT_SLOP
and math.abs(t.y - d.y) <= HIT_SLOP then
numHit = numHit + 1
scoreHits.text = "Hits = " .. numHit
--count 1 hit once dog and treat hit eachother
if (t.x - d.x) <= HIT_SLOP and (t.y - d.y) <= HIT_SLOP then
t.x = 400 --resets treat postioning
t.y = math.random(HEIGHT) --gives treat a random y coordinate
end
end
--puts the barrier at the bottom of the screen and tells dog to bounce from there
if (d.y > HEIGHT) then
d.y = HEIGHT
velocity = -velocity * BOUNCE
end
t.x = t.x - 5 --speed treat goes
if t.x < -350 then--position of the treat
t.x = 400
scoreMisses.text = "Misses = " .. numMiss
else if t.x < -100 then
t.y = math.random(HEIGHT) --random height after treat goes past dog
else if t.x < -99 then
numMiss = numMiss + 1 --calculates misses when goes past screen
scoreMisses.text = "Misses = " .. numMiss
end
end
end
--calculate percentage hits
numPercent = 100 * numHit / (numHit + numMiss)
scorePercent.text = "Hit % = " .. math.round(numPercent) --prints and rounds percentage
function tapped(event) --when tapped on reset, score gets reset
--reset function goes here
end
end
r:addEventListener( "tap", tapped )
end
function touched(event)
-- print(event.phase)
if event.phase == "began" then
velocity = velocity - 6 -- thrusts dog
end
return true
end
Runtime:addEventListener( "enterFrame" , enterFrame )
Runtime:addEventListener( "touch", touched )
To make your image a button you need to add an event listener that responds to touch or tap events.
see http://docs.coronalabs.com/api/event/touch/index.html
Or you use the widget library which gives you the possibility to use a blank button background and set only the label for each button what will be very handy when you include translations for other languages.
see http://docs.coronalabs.com/api/library/widget/newButton.html
In my game I have a function gameInit() that sets the hole game and all variables. This function is called when the game starts and also when the player wants to do a reset as it over writes the old variables. (there are other technics, depending on the complexity of your game and if you for example want to store the game settings for the next time the player starts the game)

Corona Lua display object y property out by three ten-millionths of a pixel (!)

When I run this code (which is based on the "DragMe" sample app) I get very unexpected results in very particular circumstances.
My "snapToGrid" function sets x and y to the nearest round 100 value after a drag.
The "examine" function outputs the x and y values after an object is created, moved or rotated (by clicking on it).
If you place an object in row 5 (so y = 500) and rotate it you will see that the y value changes to 499.99996948242 but this does not happen in any other row.
How can this be explained? I notice this does not happen if the physics bodies are not added to the display object.
I know I could call snapToGrid after rotation or round the value before I use it for anything else but I think there is an important opportunity here for me to learn something useful about Corona.
local function snapToGrid(t)
modHalf = t.x % t.width
if modHalf > t.width/2 then
t.x = t.x + (t.width-modHalf)
end
if modHalf < t.width/2 then
t.x = t.x - modHalf
end
modHalfY = t.y % t.height
if modHalfY > t.height/2 then
t.y = t.y + (t.height-modHalfY)
end
if modHalfY < t.height/2 then
t.y = t.y - modHalfY
end
display.getCurrentStage():setFocus( nil )
t.isFocus = false
return true
end
function rotatePiece(target)
if target.rotation == 270 then
target.rotation = 0
else
target.rotation = target.rotation + 90
end
end
local function dragBody(event)
local target = event.target
local phase = event.phase
local halfWidth = target.width/2
local halfHeight = target.height/2
--get tileX and tileY relative to tile centre
local tileX = event.x-target.x
local tileY = event.y-target.y
local modHalf = ""
local snap = 15
if phase == "began" then
-- Make target the top-most object
display.getCurrentStage():setFocus( target )
-- Spurious events can be sent to the target, e.g. the user presses
-- elsewhere on the screen and then moves the finger over the target.
-- To prevent this, we add this flag. Only when it's true will "move"
-- events be sent to the target.
target.isFocus = true
-- Store initial position
target.x0 = event.x - target.x
target.y0 = event.y - target.y
elseif target.isFocus then
if phase == "moved" then
-- Make object move (we subtract target.x0,target.y0 so that moves are
-- relative to initial grab point, rather than object "snapping").
target.x = event.x - target.x0
target.y = event.y - target.y0
if target.x > display.contentWidth - (target.width/2) then target.x = display.contentWidth - (target.width/2) end
if target.y > display.contentHeight - (target.height/2) then target.y = display.contentHeight - (target.height/2) end
if target.x < 0 + (target.width/2) then target.x = 0 + (target.width/2) end
if target.y < 0 + (target.height/2) then target.y = 0 + (target.height/2) end
modHalf = target.x % target.width
if modHalf < snap then
target.x = target.x - modHalf
end
if modHalf > ((target.width) - snap) then
target.x = target.x + ((target.width)-modHalf)
end
modHalfY = target.y % target.height
if modHalfY < snap then
target.y = target.y - modHalfY
end
if modHalfY > ((target.height) - snap) then
target.y = target.y + ((target.height)-modHalfY)
end
hasMoved = true
return true
elseif phase == "ended" then
if hasMoved then
hasMoved = false
snapToGrid(target)
--tile has moved
examine(target)
return true
else
--rotate piece
rotatePiece(target)
display.getCurrentStage():setFocus( nil )
target.isFocus = false
--tile has rotated
examine(target)
return true
end
end
end
-- Important to return true. This tells the system that the event
-- should not be propagated to listeners of any objects underneath.
return true
end
local onTouch = function(event)
if event.phase == "began" then
local tile = {}
img = display.newRect(event.x,event.y,100,100)
img:addEventListener( "touch", dragBody )
snapToGrid(img)
--top right corner and top middle solid
topRight = {16,-16,16,50,50,50,50,-16}
--top left and left middle solid
topLeft = {-16,-16,-16,-50,50,-50,50,-16}
--bottom right and right middle solid
bottomRight = {16,16,16,50,-50,50,-50,16}
--bottom left and bottom middle solid
bottomLeft = {-16,16,-16,-50,-50,-50,-50,16}
physics.addBody( img, "static",
{shape=topRight},
{shape=topLeft},
{shape=bottomLeft},
{shape=bottomRight}
)
--new tile created
examine(img)
return true
end
end
function examine(img)
print("--------------------------------------------------")
if img ~= nil then
print("X: "..img.x..", Y: "..img.y)
end
print("--------------------------------------------------")
end
local img
local physics = require( "physics" )
physics.setDrawMode( "hybrid" )
--draw gridlines
for i = 49, display.contentHeight, 100 do
local line = display.newLine(0,i,display.contentWidth,i)
local line2 = display.newLine(0,i+2,display.contentWidth,i+2)
end
for i = 49, display.contentWidth, 100 do
local line = display.newLine(i,0,i,display.contentHeight )
local line2 = display.newLine(i+2,0,i+2,display.contentHeight )
end
--init
physics.start()
Runtime:addEventListener("touch", onTouch)
There several possibilities. Firstly, since there are no integers in Lua, all numbers are double floating point values. According to FloatingPoint on Lua wiki,
Some vendors' printf implementations may not be able to handle
printing floating point numbers accurately. Believe it or not, some
may incorrectly print integers (that are floating point numbers). This
can manifest itself as incorrectly printing some numbers in Lua.
Indeed, try the following:
for i=1,50,0.01 do print(i) end
You will see that a lot of numbers print exactly as you would expect, by many print with an error of 10^-12 or even 2 x 10^-12.
However, your x prints fine when you don't give it to physics module. So surely the physics module does some computation on object position and changes it. I would certainly expect that for dynamic objects (due to collision detection), but here your object seems to be "static". So it must be that physics adjusts x even for static objects. The adjustment is so small that it would not be visible on the screen (you can't perceive any motion less than a pixel), but you're right that it is interesting to ask why.
The SO post Lua: subtracting decimal numbers doesn't return correct precision is worth reading; it has some links you might find interesting and gives the neat example that decimal 0.01 cannot be represented exactly in base 2; just like 1/3 can't be represented exactly in base 10 (but it can in base 3: it would be 0.1 base 3!). The question shows that, although Lua (or possibly, the underlying C) is smart enough to print 0.01 as 0.01, it fails to print 10.08-10.07 as 0.01.
If you really want to shake your understanding of floating point values, try this:
> a=0.3
> b=0.3
> print(a==b)
true
> -- so far so good; now this:
> a=0.15 + 0.15
> b=0.1 + 0.2
> print(a,b)
0.3 0.3
> print(c==d)
false -- woa!
This is explained by the fact that 0.15 in binary has a small error which is different from that of 0.1 or 0.2, so in terms of bits they are not identical; although when printed, the difference is too small to show. You may want to read the Floating Point Guide.
Actually this problem only occurs if you add the physics, so I think it is fair to say that the issue is caused by transferring control to box2d, not by Corona handling of the number alone. I asked on Corona forums and got this answer.
http://forums.coronalabs.com/topic/46245-display-object-that-has-static-physics-body-moves-very-slightly-when-rotated/

How to do tile based collision

I am trying to make a simple platform game, and obviously I need tile collision. The problem with the code I have so far is that it moves the character first, then checks to see if it is colliding with something, but sometimes it thinks its colliding at the wrong times depending on if I check the x-axis for collisions first or the y-axis first. Am I going about this the wrong way? Here's some code.
function checkCollision(val, axis, oldPos)
if axis == "x" and char.tX then
local tileX = math.ceil(val/absoluteTileSize)
local tileY = math.floor(oldPos/absoluteTileSize)
local tl, tr, bl ,br = getTouchingTiles(tileX, tileY)
local isOnFlatSurface = math.abs(oldPos/absoluteTileSize-tileY) <= .00001--might not be a good i
if isOnFlatSurface then
if tr.canCollide then
char.tX = nil
char.x = tileX * absoluteTileSize - absoluteTileSize
end
else
if br.canCollide then
char.tX = nil
char.x = tileX * absoluteTileSize - absoluteTileSize
end
end
elseif axis == "y" then
local tileX = math.ceil(oldPos/absoluteTileSize)
local tileY = math.floor(val/absoluteTileSize)
local tl, tr, bl ,br = getTouchingTiles(tileX, tileY)
if bl.canCollide or br.canCollide then
char.tY = nil
char.y = tileY * absoluteTileSize --// - absoluteTileSize
--/////////////idk why i don't need to subtract that but it works
elseif not char.tY then--start falling if walk off something
char.tY = love.timer.getTime()
char.yi = char.y
char.vyi = 0
end
end
end
local tileX = math.ceil(val/absoluteTileSize)
local tileY = math.floor(oldPos/absoluteTileSize)
It seems strange that you would use math.ceil for the x values and math.floor for the y. This may be why you are getting some strange occurrences. I would recommend this little debugging trick that may help you:
-- Since you are using LÖVE, this is what you would use:
love.graphics.setColor( 255, 0, 0, 255 )
love.graphics.rectangle( 'line', ( tileX - 1 ) * absoluteTileSize, ( tileY - 1 ) * absoluteTileSize, absoluteTileSize, absoluteTileSize )
-- assuming absoluteTileSize represents the width/height of the tiles?
This would go at the end of your drawing function and would draw a red box in the "tile" your player is currently inside.

Resources