-- функция украдена со СтекОверфлова с небольшими изменениями
-- htt ps://stackoverflow.com/questions/16026055/lua-printing-specific-key-value-pairs-in-table
-- нужна для отладки, в финальном коде не нужна
function DeepPrint (e, indent)
    -- if e is a table, we should iterate over its elements
    if type(e) == "table" then
        for k,v in pairs(e) do -- for every element in the table
            print(indent or "", '[' .. k .. ']', v)
            if type(v) == "table" then
              DeepPrint(v, (indent or "") .. "\t")       -- recursively repeat the same procedure
            end
        end
    else -- if not, we can just print it
        print(indent or "", e)
    end
end

--[[
diceProb содержит функции для подсчёта вероятности бросков, которые описываются в лиспоподобном формате.

Входные данные ожидаются в таком формате:
{">=", {"+", "d10", "1"}, "7"})

У значений есть только два типа: список вариантов или константа

Константа может выражать вероятность (от 0 до 1) или просто произвольное число
Список вариантов — это бросок с описанием возможных исходов и их количества:
{ {value=2, repeats=1}, {value=3, repeats=2}, ...}


Функции типа >=, >, <, <= и т.п. берут список вариантов и возврщают вероятность.

Функции типа +, - работаают и с тем, и с тем: для констант они просто складывают константы,
а для списков вероятности они добваляют к каждому значению что-то. Операции над константами
чуть дешевле.

Пример использования:
diceProb.evaluate({">", {"+", "2d6", "+3"}, "10"})
-- => 0.41666666666667 (вероятность, что 2d6+3 > 10)

Можно передать список значений переменных как второй аргумент:
diceProb.evaluate({">", {"+", "2d6", "Прибавка"}, "10"}, {["Прибавка"]="+3"})
-- => 0.41666666666667 (вероятность, что 2d6+Прибавка > 10, где Прибавка=+3)
]]

local diceProb = { fn={} }

function diceProb.fn.sum(args)
  local probabilities = {}
  local constant = 0

  for k, v in ipairs(args) do
    if type(v) == 'table' then
      table.insert(probabilities, v)
    else
      constant = constant + v
    end
  end

  if #probabilities > 2 then
    local summed_probabilities = probabilities[1]
    for i=2,#probabilities do
      summed_probabilities = diceProb.sum_of_rolls(summed_probabilities, probabilities[i])
    end
    probabilities = summed_probabilities
  end

  if #probabilities > 0 then
    local summed = {}
    for k, v in ipairs(probabilities[1]) do
      table.insert(summed, {repeats=v.repeats, value=v.value + constant})
    end
    return summed
  else
    return constant
  end
end

function diceProb.negate(operand)
  if type(operand) == "table" then
    local negated = {}
    for k,v in ipairs(operand) do
      table.insert(negated, {value=-v.value, repeats=v.repeats})
    end
    return negated
  else
    return -operand
  end
end

function diceProb.fn.difference(args)
  if #args == 1 then return diceProb.negate(args[0]) end

  local sumArgs = {}
  for k,v in ipairs(args) do
    if k == 1 then
      table.insert(sumArgs, v)
    else
      table.insert(sumArgs, diceProb.negate(v))
    end
  end

  return diceProb.fn.sum(sumArgs)
end

function diceProb.fn.product(args)
  local probabilities = {}
  local constant = 1

  for k, v in ipairs(args) do
    if type(v) == 'table' then
      table.insert(probabilities, v)
    else
      constant = constant * v
    end
  end

  if #probabilities > 2 then
    error('multiplication of dice is not supported (yet?)')
  end

  if #probabilities > 0 then
    local multiplied = {}
    for k, v in ipairs(probabilities[1]) do
      table.insert(multiplied, {repeats=v.repeats, value=v.value * constant})
    end
    return multiplied
  else
    return constant
  end
end

function diceProb.compare_less_than(lower_range, variants, upper_range, include_equal)
  local total_repeats = 0
  local correct_repeats = 0

  for k, v in ipairs(variants) do
    local correct = true
    if lower_range ~= nil then
      if v.value < lower_range then correct = false end
      if v.value == lower_range and not include_equal then correct = false end
    end
    if upper_range ~= nil then
      if v.value > upper_range then correct = false end
      if v.value == upper_range and not include_equal then correct = false end
    end

    total_repeats = total_repeats + v.repeats
    if correct then correct_repeats = correct_repeats + v.repeats end
  end

  return correct_repeats / total_repeats
end

function diceProb.check_equality(variants, const)
  local total_repeats = 0
  local correct_repeats = 0
  for k, v in ipairs(variants) do
    total_repeats = total_repeats + v.repeats
    if v.value == const then correct_repeats = correct_repeats + v.repeats end
  end

  return correct_repeats / total_repeats
end

function diceProb.fn.more_or_equals(args)
  local left = args[1]
  local right = args[2]
  if #args ~= 2 then error('≥ operation must have 2 arguments') end

  if (type(left) == "table") and (type(right) == "table") then -- tbl ≥ tbl
    error("only one ≥ argument should be a table")
  elseif type(left) == "table" then -- tbl ≥ const
    return diceProb.compare_less_than(right, left, nil, true)
  elseif type(right) == "table" then --const ≥ tbl
    return diceProb.compare_less_than(nil, right, left, true)
  else -- const ≥ const
    if left >= right then return 1 else return 0 end
  end
end

function diceProb.fn.more(args)
  local left = args[1]
  local right = args[2]
  if #args ~= 2 then error('> operation must have 2 arguments') end

  if (type(left) == "table") and (type(right) == "table") then -- tbl ≥ tbl
    error("only one ≥ argument should be a table")
  elseif type(left) == "table" then -- tbl ≥ const
    return diceProb.compare_less_than(right, left, nil, false)
  elseif type(right) == "table" then --const ≥ tbl
    return diceProb.compare_less_than(nil, right, left, false)
  else -- const ≥ const
    if left > right then return 1 else return 0 end
  end
end

function diceProb.fn.less_or_equal(args)
  local left = args[1]
  local right = args[2]
  if #args ~= 2 then error('≤ operation must have 2 arguments') end

  if (type(left) == "table") and (type(right) == "table") then
    error("only one ≥ argument should be a table")
  elseif type(left) == "table" then
    return diceProb.compare_less_than(nil, left, right, true)
  elseif type(right) == "table" then
    return diceProb.compare_less_than(left, right, nil, true)
  else
    if left <= right then return 1 else return 0 end
  end
end

function diceProb.fn.less(args)
  local left = args[1]
  local right = args[2]
  if #args ~= 2 then error('< operation must have 2 arguments') end

  if (type(left) == "table") and (type(right) == "table") then
    error("only one ≥ argument should be a table")
  elseif type(left) == "table" then
    return diceProb.compare_less_than(nil, left, right, false)
  elseif type(right) == "table" then
    return diceProb.compare_less_than(left, right, false)
  else
    if left < right then return 1 else return 0 end
  end
end

function diceProb.fn.equal(args)
  local left = args[1]
  local right = args[2]
  if #args ~= 2 then error('= operation must have 2 arguments') end

  if (type(left) == "table") and (type(right) == "table") then
    error("only one = argument should be a table")
  elseif type(left) == "table" then
    return diceProb.check_equality(left, right)
  elseif type(right) == "table" then
    return diceProb.check_equality(right, left)
  else
    if left == right then return 1 else return 0 end
  end
end

diceProb.functionHandlers = {
  ['+']=diceProb.fn.sum,
  ['-']=diceProb.fn.difference,
  ['*']=diceProb.fn.product,
  ["≥"]=diceProb.fn.more_or_equals,
  [">="]=diceProb.fn.more_or_equals,
  ['>']=diceProb.fn.more,
  ['<=']=diceProb.fn.less_or_equal,
  ['≤']=diceProb.fn.less_or_equal,
  ['<']=diceProb.fn.less,
  ['=']=diceProb.fn.equal,
}

diceProb.die_roll_cache = {}

-- Список вариантов — это таблица типа
-- { {value=2, repeats=1}, {value=3, repeats=2}, ...}
-- Где value — значение, repeats — число повторений значения

-- Возвращает список вариантов
function diceProb.single_die_roll(num_sides)
  probabilities = {}
  for i=1, num_sides do
    table.insert(probabilities, {value=i, repeats=1})
  end
  return probabilities
end

-- Возвращет список вараиантов суммы двух списков вариантов
function diceProb.sum_of_rolls(first_probs, second_probs)
  local repeats_by_val = {}
  for _, first in ipairs(first_probs) do
    for _, second in ipairs(second_probs) do
      local val = first.value + second.value
      local repeats = first.repeats * second.repeats
      repeats_by_val[val] = (repeats_by_val[val] or 0) + repeats
    end
  end

  local values={}
  for k, v in pairs(repeats_by_val) do
    table.insert(values, {value=k, repeats=v})
  end
  return values
end

-- Возвращает список варинатов для XdY
function diceProb.complex_die_roll(repeats, num_sides)
  local basic_roll = diceProb.single_die_roll(num_sides)
  local result = basic_roll
  for i = 2,repeats do
    result = diceProb.sum_of_rolls(result, basic_roll)
  end
  return result
end

-- Читет строку и возвращает либо список вариаантов, либо nil (если это не нотация броска)
function diceProb.evaluate_dice_expression(s)
  if not s:match'%d*[dD]%d' then return nil end
  local from, to = s:find('[dD]')
  local repeats, dice = 1, tonumber(s:sub(to+1))
  if from > 1 then repeats = tonumber(s:sub(1, from-1) or "1") end

  local cache_id = repeats .. 'd' .. dice
  if diceProb.die_roll_cache[cache_id] == nil then
    diceProb.die_roll_cache[cache_id] = diceProb.complex_die_roll(repeats, dice)
  end
  return diceProb.die_roll_cache[cache_id]
end

function diceProb.evaluate_constant(s)
  return tonumber(s)
end

function diceProb.evaluate_form(form, environment)
  local fn_name = form[1]
  local computed_args = {}
  for k, v in ipairs(form) do
    if k > 1 then
      table.insert(computed_args, diceProb.evaluate(v, environment))
    end
  end

  local fn = diceProb.functionHandlers[fn_name]
  if fn == nil then error('Unknown function: ' .. fn_name) end
  return fn(computed_args)
end

function diceProb.evaluate(datum, environment)
  local t = type(datum)
  if t == "table" then
    return diceProb.evaluate_form(datum, environment)
  elseif t == "string" then
    local value = (environment and environment[datum]) or
      diceProb.evaluate_dice_expression(datum) or
      diceProb.evaluate_constant(datum)
    if value == nil then
      error('cannot evaluate datum: ' .. datum .. '[' .. t .. ']')
    end
    return value
  else
    error('cannot evaluate datum: ' .. datum .. '[' .. t .. ']')
  end
end

--[[
diceTable содержит рисование таблиц, оформленных в лиспоподобном формате:

{
  {"столбец", "ID", "тип", "данные1", "данные2", ... },
  ...
}

В качестве типа принимаются значения "список" и "вероятность".

Если тип — "список", то дальше идут элементы списка.

Если тип — "вероятность", то дальше идёт ровно одна форма, формула расчёта
вероятности. Эта формула такая же, как ввод для diceProb.evaluate, причём
ID столбцов будут заменяться на элемент списка.

Для ID и элемента списка можно задать альтернативное отображение:

{
  {"столбец", {"КостьУмение", "Кость умения"}, "список", {"0", "Нет"}, "d4", "d6", ... },
  ...
}
]]
local diceTable = {}


function diceTable.id_or_title(val_with_text, num)
	if type(val_with_text) == "table" then
		return val_with_text[num]
	else
		return val_with_text
	end
end

function diceTable.id(val_with_text)
	return diceTable.id_or_title(val_with_text, 1)
end

function diceTable.title(val_with_text)
	return diceTable.id_or_title(val_with_text, 2)
end

function diceTable.get_lists(columns)
  local lists = {}
  for _, column in ipairs(columns) do
    if column[3] == "список" then
        local items = {}
        for i = 4, #column do
        	table.insert(items, column[i])
        end
        local id = diceTable.id(column[2])
        lists[id] = items
    end
  end

  return lists
end

function diceTable.make_table_rows(columns, a_column_ids, a_current_idx, a_rows)
	local lists = diceTable.get_lists(columns)
	
	local column_ids = a_column_ids
	if column_ids == nil then
		column_ids = {}
		for _, column in ipairs(columns) do
			if column[3] == "список" then
				table.insert(column_ids, diceTable.id(column[2]))
			end
		end
	end

	local current_idx = a_current_idx or 1
	if current_idx > #column_ids then
		return a_rows or {}
	end
	
	local current_id = column_ids[current_idx]
	local prev_rows = a_rows or {{}}
	local new_rows = {}
	local items = lists[current_id]
	for _,prev_row in ipairs(prev_rows) do
		for _,item in ipairs(items) do
			-- TODO rowspan'ы
			local new_row = {}
			new_row[current_id] = item
			for k,v in pairs(prev_row) do
				new_row[k] = v
			end
			table.insert(new_rows, new_row)
		end
	end
	
	return diceTable.make_table_rows(columns, column_ids, current_idx+1, new_rows)
end

function diceTable.formatPercent(x)
	local num = math.floor(x * 10000 + 0.5) / 100
	return num .. '%'
end

function diceTable.get_cell_contents(columns)
	local rows = diceTable.make_table_rows(columns)
	local result = {}
	for _, row_data in pairs(rows) do
		local cells = {}
		for idx, column in pairs(columns) do
			if column[1] ~= "столбец" then
				error("unknown entity: " .. column[1])
			end
			
			if column[3] == "список" then
				cells[idx] = diceTable.title(row_data[diceTable.id(column[2])])
			elseif column[3] == "вероятность" then
				environment = {}
				for k,v in pairs(row_data) do
					environment[k] = diceProb.evaluate(diceTable.id(v))
				end
				cells[idx] = diceTable.formatPercent(diceProb.evaluate(column[4], environment))
			else
				error("unknown column type: " .. column[3])
			end
		end
		table.insert(result, cells)
	end

	return result
end

function diceTable.make_wikitable_contents(columns)
	local result = ''
	local rows = diceTable.get_cell_contents(columns)
	for row_idx, row in ipairs(rows) do
		local cell_text = "\n|-\n| "
		for cell_idx, cell in ipairs(row) do
			cell_text = cell_text .. cell
			if cell_idx < #row then
				cell_text = cell_text .. " || "
			end
		end
		result = result .. cell_text
	end
	return result
end

function diceTable.make_wikitable_header(columns)
	-- TODO: colspan/rowspan
	local result = '! '
	for idx, column in ipairs(columns) do
		result = result .. diceTable.title(column[2])
		if idx < #columns then
			result = result .. ' !! '
		end
	end
	return result
end

function diceTable.make_wikitable(columns)
	return '{| class="wikitable" style="margin:auto"\n' ..
		diceTable.make_wikitable_header(columns) ..
		diceTable.make_wikitable_contents(columns) ..
		'\n|}'
end

--[[
Тут нужен парсер S-выражений, но я его пока что не сделал.

На входе я хочу что-то лиспообразное, типа такого:
(
  (столбец Атрибут список d6 d8 d10 d12)
  (столбец Умение список (0 —) d6 d8 d10 d12)
  (столбец "Вероятность без переброски" (- 1 (* (< Атрибут 6) (< Умение 6))))
  (столбец "Вероятность с переброской" (- 1 (* (< Атрибут 6) (< Умение 6) (< Атрибут 6) (< Умение 6))))
)

Пока что вместо S-выражений просто массивы Lua.
]]


print(diceTable.make_wikitable({
  {"столбец", "Атрибут", "список", "d6", "d8", "d10", "d12"},
  {"столбец", "Умение", "список", {"0", "—"}, "d6", "d8", "d10", "d12"},
  {"столбец", "Вероятность без переброски", "вероятность", {"-", "1", {"*", {"<", "Атрибут", "6"}, {"<", "Умение", "6"}}}},
  {"столбец", "Вероятность с переброской", "вероятность", {"-", "1", {"*", {"<", "Атрибут", "6"}, {"<", "Умение", "6"}, {"<", "Атрибут", "6"}, {"<", "Умение", "6"}}}}
}))
 

Lua online compiler

Write, Run & Share Lua code online using OneCompiler's Lua online compiler for free. It's one of the robust, feature-rich online compilers for Lua language, running the latest Lua version 5.4. Getting started with the OneCompiler's Lua editor is easy and fast. The editor shows sample boilerplate code when you choose language as Lua and start coding.

Taking inputs (stdin)

OneCompiler's Lua online editor supports stdin and users can give inputs to programs using the STDIN textbox under the I/O tab. Following is a sample Lua program which takes name as input and prints hello message with your name.

name = io.read("*a")
print ("Hello ", name)

About Lua

Lua is a light weight embeddable scripting language which is built on top of C. It is used in almost all kind of applications like games, web applications, mobile applications, image processing etc. It's a very powerful, fast, easy to learn, open-source scripting language.

Syntax help

Variables

  • By default all the variables declared are global variables
  • If the variables are explicitly mentioned as local then they are local variables.
  • Lua is a dynamically typed language and hence only the values will have types not the variables.

Examples

-- global variables
a = 10

-- local variables

local x = 30
Value TypeDescription
numberRepresents numbers
stringRepresents text
nilDifferentiates values whether it has data or not
booleanValue can be either true or false
functionRepresents a sub-routine
userdataRepresents arbitary C data
threadRepresents independent threads of execution.
tableCan hold any value except nil

Loops

1. While:

While is also used to iterate a set of statements based on a condition. Usually while is preferred when number of iterations are not known in advance.

while(condition)
do
--code
end

2. Repeat-Until:

Repeat-Until is also used to iterate a set of statements based on a condition. It is very similar to Do-While, it is mostly used when you need to execute the statements atleast once.

repeat
   --code
until( condition )

3. For:

For loop is used to iterate a set of statements based on a condition.

for init,max/min value, increment
do
   --code
end

Functions

Function is a sub-routine which contains set of statements. Usually functions are written when multiple calls are required to same set of statements which increase re-usuability and modularity.

optional_function_scope function function_name( argument1, argument2, argument3........, argumentn)
--code
return params with comma seperated
end