Automatically align markdown tables using lua in Neovim - lua

I am using vim-easy-align plugin and full lua configuration for my Neovim
This is how we can auto align the markdown tables for every '|' we type using vim-tabular in VimScript
inoremap <silent> <Bar> <Bar><Esc>:call <SID>align()<CR>a
function! s:align()
let p = '^\s*|\s.*\s|\s*$'
if exists(':Tabularize') && getline('.') =~# '^\s*|' && (getline(line('.')-1) =~# p || getline(line('.')+1) =~# p)
let column = strlen(substitute(getline('.')[0:col('.')],'[^|]','','g'))
let position = strlen(matchstr(getline('.')[0:col('.')],'.*|\s*\zs.*'))
Tabularize/|/l1
normal! 0
call search(repeat('[^|]*|',column).'\s\{-\}'.repeat('.',position),'ce',line('.'))
endif
endfunction
Source: Tim Pope's Gist
I want something like the above but for vim-easy-align and in Lua

This is the best I could do
local set_cursor_to_nth_bar = function (row, count)
local line = vim.api.nvim_buf_get_lines(0, row, row + 1, false)[1]
local cur_bar_count = 0
local cur_col = 0 -- will be the col of the cursor + 1 by the end of the loop
while cur_bar_count < count do
cur_col = line:find('|', cur_col + 1)
cur_bar_count = cur_bar_count + 1
end
vim.api.nvim_win_set_cursor(0, {row + 1, cur_col})
vim.cmd [[startinsert!]]
end
local on_bar_inserted = function ()
local pos = vim.api.nvim_win_get_cursor(0)
local row = pos[1] - 1
local col = pos[2]
local before_line = vim.api.nvim_get_current_line()
-- record the number of bars in the line prior to the cursor
local _, pre_bar_count = before_line:sub(0, col):gsub("|", "|")
-- insert the |
vim.api.nvim_buf_set_text(0, row, col, row, col, { '|' })
-- Easy Align markdown table
vim.cmd [[ stopinsert ]]
vim.cmd [[ normal! vip ]] -- visually select the paragraph
vim.api.nvim_feedkeys('ga*|', 'v', false) -- EasyAlign all |s in the paragraph -- ga = keymap for <Plug>(EasyAlign)
-- place the cursor at the end of the entered | (pre_bar_count + 1)
-- we need to schedule this since the above nvim_feedkeys need to trigger EasyAlign and it needs to
-- update text before we try to set the cursor in the right place
vim.schedule(
function ()
set_cursor_to_nth_bar(row, pre_bar_count + 1)
end
)
end
-- set ga as keymap for EasyAlign in normal and visual models
vim.keymap.set('n', 'ga', '<Plug>(EasyAlign)', { desc = "Align", noremap = false })
vim.keymap.set('x', 'ga', '<Plug>(EasyAlign)', { desc = "Align", noremap = false })
local align_group = vim.api.nvim_create_augroup('AlignMDTable', { clear = true })
vim.api.nvim_create_autocmd('FileType', {
callback = function()
vim.keymap.set('i', '<Bar>', on_bar_inserted, { desc = "Align Tables", silent = true })
end,
group = align_group,
pattern = "markdown",
})
I think this could be improved. Especially this part, where we are having to use nvim_feedkeys and ga the keymap for <Plug>(EasyAlign)
-- Easy Align markdown table
vim.cmd [[ stopinsert ]]
vim.cmd [[ normal! vip ]] -- visually select the paragraph
vim.api.nvim_feedkeys('ga*|', 'v', false) -- EasyAlign all |s in the paragraph -- ga = keymap for <Plug>(EasyAlign)

Related

How do I keep the renderBlocks() function from going into an infinite loop?

-- This is Minecraft, but 2D, and in Lua.
-- **************************************
-- ID 0: Air
-- ID 1: Stone
-- ID 2 Logs
-- ID 3: Leaves
-- ID 4: Planks
-- ID 5: Crafting Table
-- ID 6: Furnance
-- ID 7: Player position detector block
-- **************************************
blockTable = { -- Stores all the blocks
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6}
}
tableRow = 1 -- Y value for block render pointer
tableColumn = 1 -- X value for block render pointer
runOnce = true -- Run a loop once
tickNum = 0 -- Number of ticks since startup
function renderBlock(X, Y, K) -- Render a block
if K == 1 then -- if blockID == 1, draw a grey square
screen.setColor(100,100,100)
print(X, Y)
screen.drawRectF(X, Y, 8, 8)
end
if K == 6 then -- If the blockID == draw a grey square
screen.setColor(100,100,100)
screen.drawRectF(X, Y, 8, 8)
end
end
function renderBlocks() -- Render all the blocks (Scans though the blockTable in a raster pattern)
while runOnce == true -- Run this code once
do
while #blockTable >= tableRow
do
while #blockTable[tableRow] >= tableColumn
do
print("TC " .. tableColumn)
tableColumn = tableColumn + 1
blockID = blockTable[tableRow][tableColumn]
renderBlock(tableRow, tableColumn, blockID)
if tableColumn > #blockTable[tableRow] then tableColumn = 1 end
end
runOnce = false
end
print("TR " .. tableRow)
tableRow = tableRow + 1
end
end
function onTick()
print("Tick! Tick count: " .. tickNum) -- Print the current tick, as well as say that there has been a tick
tickNum = tickNum + 1
end
function onDraw()
while runOnce == true -- Run this code once
do
renderBlocks()
runOnce = false
end
end
Here's my code in the IDE for the framework: Link
I have no clue why this is happening, I'm at a loss here.
When doing loops over tables it is far better to use for loops than while or repeat loops, that are far more difficult to understand and bugfix.
Use for index,value in ipairs(yourtable) do if you want to get the indices (number in the table) and the values associated or for key,value in pairs(yourtable) do if you want to get the keys (generally strings that is used as key for a value: yourTable["key"] = 0) and the values associated with it.
for row,innerTable in ipairs(blockTable) do
for col,value in ipairs(innerTable) do
print("TC " .. col)
blockID = value
renderBlock(row, col, blockID)
end
end
The last should do it inside your renderBlocks function. But that:
while runOnce == true do
-- Doing something
runOnce = false
end
Is not very nice stuff. If it shall only be called once, do not put it into a loop!
Also it would help to have a little better code identation to understand what is happening faster. And add some Space between blocks of code that handle different concerns.
Edit: So your function could look like this:
function renderBlocks()
if runOnce then
runOnce = false
for row,innerTable in ipairs(blockTable) do
for col,value in ipairs(innerTable) do
blockID = value
renderBlock(row, col, blockID)
end
end
end
end
There are a couple issues in your renderBlocks() function
function renderBlocks()
while runOnce == true do
while #blockTable >= tableRow do
while #blockTable[tableRow] >= tableColumn do
print("TC " .. tableColumn)
tableColumn = tableColumn + 1
blockID = blockTable[tableRow][tableColumn]
renderBlock(tableRow, tableColumn, blockID)
if tableColumn > #blockTable[tableRow] then
tableColumn = 1
end
end
runOnce = false
end
print("TR " .. tableRow)
tableRow = tableRow + 1
end
end
First we can look at the inner most loop. Here we have a problem with the contrition used to evaluate the loop and how it is incremented.
while #blockTable[tableRow] >= tableColumn do
print("TC " .. tableColumn)
tableColumn = tableColumn + 1 -- incremented here
blockID = blockTable[tableRow][tableColumn]
renderBlock(tableRow, tableColumn, blockID)
if tableColumn > #blockTable[tableRow] then
tableColumn = 1 -- reset here
end
end
in the loop body you do tableColumn = tableColumn + 1 and later in the same body you do if tableColumn > #blockTable[tableRow] then tableColumn = 1 end so this is a problem when tableColumn is increased to a value that would end the loop we reset it to 1 preventing the loop from ever ending.
Now lets look at the next loop up. Here we never increment tableRow it is incremented just after the end of this loop so it is not possible for the loop to end.
while #blockTable >= tableRow do
while #blockTable[tableRow] >= tableColumn do
...
end
runOnce = false
end
I believe you intended
print("TR " .. tableRow)
tableRow = tableRow + 1
to occur within this loop not outside of it.
You're missing a few block definitions here. Don't think that's what's sending it into an infinite loop, but you might not see results, otherwise.
function renderBlock(X, Y, blockID) -- Render a block
if blockID == 0 then -- Air
-- screen.setColor(10, 20, 220, 0) -- is there an alpha channel?
screen.setColor(10, 20, 220)
elseif blockID == 1 then -- Stone
screen.setColor(100, 100, 100)
elseif blockID == 2 then -- Logs
screen.setColor(200, 50, 50)
elseif blockID == 3 then -- Leaves
screen.setColor(20, 220, 40)
elseif blockID == 4 then -- Planks
screen.setColor(180, 30, 10)
elseif blockID == 5 then -- Craft Table
screen.setColor(180, 30, 10)
elseif blockID == 6 then -- Furnace
screen.setColor(100, 100, 100)
elseif blockID == 7 then -- Player detect
screen.setColor(220, 30, 30)
end
print(X, Y)
screen.drawRectF(X, Y, 8, 8)
end
It's easy to use for loops. set initial condition, then the final, completed condition, and let it run the course.
function renderBlocks() -- Render all blocks (Scans though blockTable in a raster pattern)
for row = 1, #blockTable do
for col = 1, #blockTable[row] do
print("TC " ..col)
local blockID = blockTable[row][col]
renderBlock(row, col, blockID)
end
print("TR " ..row)
end
end
You likely don't need that runOnce. If you find you do for something else, go ahead, but it shouldn't be needed with a nested for loop.
function onDraw()
renderBlocks()
end

Remove a object from a table listed inside a different table

So i have 2 tables
local table1 = {1,2,3,4,5,6,7,8,9}
local table2 = {2,4,6,8}
I want remove the number in table 2 from table 1, to then use table one with the numbers removed in more code. How would i go about doing this?
When you read the data into the table, set the value of the index of whatever you are reading in as the key in the table. This avoids collisions and allows easy comparisons against another table.
local table1 = {}
local table2 = {}
-- read in your values as keys into the two tables
-- just an example I have no idea how you are populating the tables but hope it helps
table1 = {['input1'] = 0, ['input6'] = 0, ['input3'] = 0}
table2 = {['input1'] = 0, ['input3'] = 0}
newTable = {}
function tableHasKey(table, key)
return table[key] ~= nil
end
for key, _ in pairs(table1) do
if not tableHasKey(table2, key) then
table.insert(newTable, key)
end
end
for _, value in pairs(newTable) do
print(value)
end
The result is 'input6' when I run this. Which means all values not in table2 that are in table1 are now in newTable. If the values are unique between tables it's easier to compare keys than values. The final result is an indexed table where the result is stored in the value of the hash object.
#! /usr/bin/env lua
local table1 = {1,2,3,4,5,6,7,8,9}
local table2 = {2,4,6,8}
for number = 1, #table1 do
for delete = 1, #table2 do
if table1[number] == table2[delete] then
for i = number, #table1 -1 do
table1[i] = table1[i +1]
end -- shuffle every entry in table down
table1 [#table1] = nil -- erase last entry
end -- number == delete
end -- loop through table2
end -- loop through table1
for i = 1, #table1 do print( table1[i] ) end
1
3
5
7
9
The other answers listed here seem correct, but they seem a little too aggressive on memory or time complexity. Here's my take on a filter function optimized for lists of numbers.
-- t1 : table, the original set of values
-- t2 : table, the set of values to remove from t1
-- returns : table, a subset of elements from t1 not found in t2
local function filter(t1, t2)
-- Assumptions :
-- 1) t1 and t2 are arrays, not dictionaries
-- 2) t1 and t2 do not have mixed indices or mixed values
-- 3) t1 and t2 are sorted
assert(type(t1) == "table", "t1 expected to be a table")
assert(type(t2) == "table", "t2 expected to be a table")
assert(type(next(t1)) == "number" or type(next(t1)) == "nil", "t1 expected to be an array")
assert(type(next(t2)) == "number" or type(next(t2)) == "nil", "t2 expected to be an array")
-- Early Outs :
if #t1 == 0 then
return {}
elseif #t2 == 0 then
return t1
end
-- step through each list and compare each index as you go
local filteredT = {}
local i = 1
local j = 1
local sizeT1 = #t1
local sizeT2 = #t2
while (i <= sizeT1) and (j <= sizeT2) do
if t1[i] == t2[j] then
-- found a match, exclude from output
i = i + 1
j = j + 1
elseif t1[i] < t2[j] then
-- no match, add elements from t1
table.insert(filteredT, t1[i])
i = i + 1
else -- t1[i] > t2[j]
-- no match, ignore elements from t2
j = j + 1
end
end
-- we've made it to the end of one of the lists, add the rest of t1
for i = i, sizeT1, 1 do
table.insert(filteredT, t1[i])
end
return filteredT
end
This solution doesn't have unnecessary loop iterations and a number of early outs for optimization.
local a = {1,2,3,4,5,6,7,8,9}
local b = {2,4,6,8}
local result1 = filter(a, b)
print(table.concat(result1, ", ")) -- 1, 3, 5, 7, 9
local c = {"a", "b", "c", "d"}
local d = {"c", "d"}
local result2 = filter(c, d)
print(table.concat(result2, ", ")) -- "a", "b"

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.

Parsing Lua strings, more specifically newlines

I'm trying to parse Lua 5.3 strings. However, I encountered an issue. For example,
$ lua
Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
> print(load('return "\\z \n\r \n\r \r\n \n \n \\x"', "#test"))
nil test:6: hexadecimal digit expected near '"\x"'
>
> print(load('return "\\z\n\r\n\r\r\n\n\n\\x"', "#test"))
nil test:6: hexadecimal digit expected near '"\x"'
Both of these error on line 6, and the logic behind that is pretty simple: eat newline characters (\r or \n) if they're different from the current one (I believe this to be an accurate description of how the lua lexer works, but I may be wrong).
I have this code, which should do it:
local ln = 1
local skip = false
local mode = 0
local prev
for at, crlf in eaten:gmatch('()[\r\n]') do
local last = eaten:sub(at-1, at-1)
if skip and prev == last and last ~= crlf then
skip = false
else
skip = true
ln = ln + 1
end
prev = crlf
end
It decides whether to eat newlines based on the previous char. Now, from what I can tell, this should work, but no matter what I do it doesn't seem to work. Other attempts have made it report 5 lines, while this one makes it report 9(!). What am I missing here? I'm running this on Lua 5.2.4.
This is part of a routine for parsing \z:
local function parse52(s)
local startChar = string.sub(s,1,1)
if startChar~="'" and startChar~='"' then
error("not a string", 0)
end
local c = 0
local ln = 1
local t = {}
local nj = 1
local eos = #s
local pat = "^(.-)([\\" .. startChar .. "\r\n])"
local mkerr = function(emsg, ...)
error(string.format('[%s]:%d: ' .. emsg, s, ln, ...), 0)
end
local lnj
repeat
lnj = nj
local i, j, part, k = string.find(s, pat, nj + 1, false)
if i then
c = c + 1
t[c] = part
if simpleEscapes[v] then
--[[ some code, some elseifs, some more code ]]
elseif v == "z" then
local eaten, np = s:match("^([\t\n\v\f\r ]*)%f[^\t\n\v\f\r ]()", nj+1)
local p=np
nj = p-1
--[[ the newline counting routine above ]]
--[[ some other elseifs ]]
end
else
nj = nil
end
until not nj
if s:sub(-1, -1) ~= startChar then
mkerr("unfinished string near <eof>")
end
return table.concat(t)
end
Compact code for iterating lines of Lua script:
local text = "First\n\r\n\r\r\n\n\nSixth"
local ln = 1
for line, newline in text:gmatch"([^\r\n]*)([\r\n]*)" do
print(ln, line)
ln = ln + #newline:gsub("\n+", "\0%0\0"):gsub(".%z.", "."):gsub("%z", "")
end
Efficient code for iterating lines of Lua script:
local text = "First\n\r\n\r\r\n\n\nSixth"
local sub = string.sub
local ln = 1
for line, newline in text:gmatch'([^\r\n]*)([\r\n]*)' do
print(ln, line)
local pos, max_pos = 1, #newline
while pos <= max_pos do
local crlf = sub(newline, pos, pos + 1)
if crlf == "\r\n" or crlf == "\n\r" then
pos = pos + 2
else
pos = pos + 1
end
ln = ln + 1
end
end

Get a certain value from a concatenated table

Trying to allow a concatenated table to be referenced as such:
local group = table.concat(arguments, ",", 1)
where arguments = {"1,1,1"}
Currently, doing group[2] gives me the comma. How do I avoid that while still allowing for two-digit numbers?
(snippet of what I'm trying to use it for)
for i = 1, #group do
target:SetGroup(i, tonumber(group[i]))
end
Maybe you want something like
local i = 1
for v in string.gmatch(s, "(%w+),*") do
group[i] = v
i = i + 1
end
Revised version in response to comment, avoiding the table altogether:
local i = 1
for v in string.gmatch(s, "(%w+),*") do
target:SetGroup(i, tonumber(v))
i = i + 1
end
split function (you have to add it to code)
split = function(str, delim)
if not delim then
delim = " "
end
-- Eliminate bad cases...
if string.find(str, delim) == nil then
return { str }
end
local result = {}
local pat = "(.-)" .. delim .. "()"
local nb = 0
local lastPos
for part, pos in string.gfind(str, pat) do
nb = nb + 1
result[nb] = part
lastPos = pos
end
-- Handle the last field
result[nb + 1] = string.sub(str, lastPos)
return result
end
so
local arguments = {"1,1,1"};
local group = split(arguments[1], ",");
for i = 1, #group do
target:SetGroup(i, tonumber(group[i]))
end
also note that
local arguments = {"1,1,1"};
local group = split(arguments[1], ",");
local group_count = #group;
for i = 1, group_count do
target:SetGroup(i, tonumber(group[i]))
end
is faster code ;)

Resources