minetest-numeracy/update.lua

1196 lines
35 KiB
Lua

local ADJ_LEFT = 1
local ADJ_RIGHT = 2
local ADJ_FRONT = 3
local ADJ_BACK = 4
local ADJ_UP = 5
local ADJ_DOWN = 6
local ADJ_HORIZ = 4
local adjacent_vectors = {
{ x = -1, y = 0, z = 0 },
{ x = 1, y = 0, z = 0 },
{ x = 0, y = 0, z = -1 },
{ x = 0, y = 0, z = 1 },
{ x = 0, y = 1, z = 0 },
{ x = 0, y = -1, z = 0 },
}
local NODE_NONE = 0
local NODE_BLOCK = 1
local NODE_NUMBER = 2
-- Maximum supported number
local max_number = 10000
local function nodes_test(nodes, pos)
if not nodes[pos.y] then
return NODE_NONE
end
if not nodes[pos.y][pos.x] then
return NODE_NONE
end
if not nodes[pos.y][pos.x][pos.z] then
return NODE_NONE
end
return nodes[pos.y][pos.x][pos.z].t
end
local function nodes_set(nodes, pos, node_type)
if not nodes[pos.y] then
nodes[pos.y] = {}
end
if not nodes[pos.y][pos.x] then
nodes[pos.y][pos.x] = {}
end
nodes[pos.y][pos.x][pos.z] = {
t = node_type,
o = 0,
}
end
local function get_node_type(node)
if string.sub(node.name, 1, string.len("numeracy:block")) == "numeracy:block" then
return NODE_BLOCK
elseif string.sub(node.name, 1, string.len("numeracy:number")) == "numeracy:number" then
return NODE_NUMBER
else
return NODE_NONE
end
end
-- index by param2, get adj
local adj_number_lookup = {
[32 + 0] = ADJ_LEFT, [64 + 0] = ADJ_RIGHT,
[32 + 1] = ADJ_BACK, [64 + 1] = ADJ_FRONT,
[32 + 2] = ADJ_RIGHT, [64 + 2] = ADJ_LEFT,
[32 + 3] = ADJ_FRONT, [64 + 3] = ADJ_BACK,
}
local function adj_node_connected(node_type, adj, adj_node_type, param2)
-- block to block
return (node_type == NODE_BLOCK and adj_node_type == NODE_BLOCK) or
-- upwards, block to number
(adj == ADJ_UP and node_type == NODE_BLOCK and adj_node_type == NODE_NUMBER) or
-- horizontally, to number
(adj <= ADJ_HORIZ and node_type ~= NODE_NONE and adj_node_type == NODE_NUMBER and
adj_number_lookup[param2] and adj_number_lookup[param2] == adj)
end
local function find_blocks(pos)
-- Construct a list of block nodes
local nodes = {}
-- Assume pointed_thing is a node
local node = minetest.get_node(pos)
local node_type = get_node_type(node)
local count = 0;
local allcount = 1;
if node_type == NODE_NONE then
return nodes, 0
elseif node_type == NODE_BLOCK then
count = 1
end
nodes_set(nodes, pos, node_type)
local unhandled = { { pos, node_type } }
-- start at the pointed node and expand out looking for more connected nodes
-- have a list of unfinished nodes
while #unhandled > 0 do
local max = table.maxn(unhandled)
pos = unhandled[max][1]
node_type = unhandled[max][2]
table.remove(unhandled, max)
-- Look in all directions from this node for more
for i = 1, 6 do
local pos2 = vector.add(pos, adjacent_vectors[i])
if nodes_test(nodes, pos2) == NODE_NONE then
node = minetest.get_node(pos2)
local adj_node_type = get_node_type(node);
if adj_node_connected(node_type, i, adj_node_type, node.param2) then
if adj_node_type == NODE_BLOCK then
count = count + 1
end
-- don't exceed max_number blocks
if adj_node_type == NODE_NUMBER or count <= max_number then
nodes_set(nodes, pos2, adj_node_type)
end
table.insert(unhandled, { pos2, adj_node_type })
allcount = allcount + 1
-- allow enough extra numbers to be detected for them to be
-- removed beyond max_number
if allcount >= max_number + 4 then
return nodes, count
end
end
end
end
end
return nodes, count
end
-- pad = nil: center
-- pad = ' ': right
-- pad = '0': zero pad
local function numeracy_add_number(pos, number, facedir, pad)
if number < 10 and pad == nil then
minetest.set_node(pos, {
name = "numeracy:number_centre_"..tostring(number),
param2 = facedir
})
elseif number < 10 and pad == ' ' then
minetest.set_node(pos, {
name = "numeracy:number_right_"..tostring(number),
param2 = facedir
})
elseif number < 100 then
local str = tostring(number)
if string.len(str) < 2 then
str = pad..str
end
minetest.set_node(pos, {
name = "numeracy:number_"..str,
param2 = facedir
})
end
end
local numeracy_left_vec_by_facedir = {
[0] = vector.new(-1, 0, 0),
[1] = vector.new( 0, 0, 1),
[2] = vector.new( 1, 0, 0),
[3] = vector.new( 0, 0, -1),
}
local function numeracy_add_numbers(pos, number, facedir)
if number < 100 then
return numeracy_add_number(pos, number, facedir)
end
-- get segments of 2 digits (IN REVERSE ORDER!)
local segs = {}
local i = 1
while number > 0 do
segs[i] = number % 100
number = math.floor(number / 100)
i = i + 1
end
local left_vec = numeracy_left_vec_by_facedir[facedir]
local mid = math.ceil(#segs / 2)
-- from middle to most significant segment
for i = mid,#segs do
local node_pos = vector.add(pos, vector.multiply(left_vec, i-mid))
if minetest.get_node(node_pos).name ~= "air" then
break
end
local pad = '0'
if i == #segs then
pad = ' '
end
local param2 = facedir
if i ~= mid then
param2 = param2 + 32
end
numeracy_add_number(node_pos, segs[i], param2, pad)
end
-- from middle to least significant segment
for i = mid-1,1,-1 do
local node_pos = vector.add(pos, vector.multiply(left_vec, i-mid))
if minetest.get_node(node_pos).name ~= "air" then
break
end
numeracy_add_number(node_pos, segs[i], facedir + 64, '0')
end
end
local function nodes_size(nodes, range_min, range_max)
if range_min == nil then
range_min = { x = nil, y = nil, z = nil }
end
if range_max == nil then
range_max = { x = nil, y = nil, z = nil }
end
-- Calculate the range of x, y and z
local min = vector.new(0, 0, 0)
local max = vector.new(0, 0, 0)
local count = 0
local first = true
for y, xs in pairs(nodes) do
if (range_min.y == nil or y >= range_min.y) and
(range_max.y == nil or y <= range_max.y) then
for x, zs in pairs(xs) do
if (range_min.x == nil or x >= range_min.x) and
(range_max.x == nil or x <= range_max.x) then
for z, info in pairs(zs) do
if (range_min.z == nil or z >= range_min.z) and
(range_max.z == nil or z <= range_max.z) then
if info.t == NODE_BLOCK then
count = count + 1
if first then
min = vector.new(x, y, z)
max = vector.new(min)
first = false
else
if z < min.z then
min.z = z
end
if z > max.z then
max.z = z
end
if x < min.x then
min.x = x
end
if x > max.x then
max.x = x
end
if y < min.y then
min.y = y
end
if y > max.y then
max.y = y
end
end
end
end
end
end
end
end
end
return vector.subtract(max, min), min, max, count
end
-- Arbitrary sort
-- e.g. numeracy_sort({...}, {{'x', 1}, {'y', -1}})
local function numeracy_sort(nodes, ordering, numbering)
if not ordering then
ordering = {}
end
local reverse_ordering = {}
for k = 1, #ordering do
reverse_ordering[ordering[k][1]] = k
end
local default_sort = { 'y', 'x', 'z' }
for k = 1, 3 do
local v = default_sort[k]
if not reverse_ordering[v] then
local idx = table.maxn(ordering) + 1
ordering[idx] = { v, 1 }
reverse_ordering[v] = idx
end
end
local snodes = {}
for y, xs in pairs(nodes) do
for x, zs in pairs(xs) do
for z, info in pairs(zs) do
if info.t == NODE_BLOCK then
local pos = { x = x, y = y, z = z }
local spos = {
pos[ordering[1][1]],
pos[ordering[2][1]],
pos[ordering[3][1]]
}
if not snodes[spos[1]] then
snodes[spos[1]] = {}
end
if not snodes[spos[1]][spos[2]] then
snodes[spos[1]][spos[2]] = {}
end
snodes[spos[1]][spos[2]][spos[3]] = true
end
end
end
end
local i = 0
for a, bs in numeracy_ordered_pairs(snodes, ordering[1][2]) do
for b, cs in numeracy_ordered_pairs(bs, ordering[2][2]) do
for c in numeracy_ordered_pairs(cs, ordering[3][2]) do
local spos = { a, b, c }
local pos = {
x = spos[reverse_ordering.x],
y = spos[reverse_ordering.y],
z = spos[reverse_ordering.z]
}
if numbering then
nodes[pos.y][pos.x][pos.z].o = numbering[i + 1] - 1;
else
nodes[pos.y][pos.x][pos.z].o = i;
end
i = i + 1
end
end
end
end
local dimention_names = { 'x', 'z', 'y' }
local function numeracy_size_dimentions(size)
local dimentions = 0
for i, d in ipairs(dimention_names) do
if size[d] > 0 then
dimentions = dimentions + 1
end
end
return dimentions
end
-- size is 1 based
-- returns sort dimension, sort direction
-- returns nil on failure
local function numeracy_is_triangle(nodes, size, min, max)
for di, d in ipairs(dimention_names) do
if size[d] > 1 then
local d2
for ddi, dd in ipairs(dimention_names) do
if dd ~= d and size[dd] > 1 then
d2 = dd
end
end
-- indexed first by 1 for negative, 2 for positive in d direction
-- indexed second by 1 for extending negative, 2 for positive
local directions = { { true, true }, { true, true } }
for i = min[d],max[d] do
local range_min = { x = nil, y = nil, z = nil }
local range_max = { x = nil, y = nil, z = nil }
range_min[d] = i;
range_max[d] = i;
local size2, min2, max2, count2 = nodes_size(nodes, range_min, range_max)
-- reject any gaps
if count2 ~= max2[d2] - min2[d2] + 1 then
return
end
-- check if not growing d2 with increasing d
if directions[2][1] or directions[2][2] then
if (i ~= max[d] and size2[d2] ~= i-min[d]) or
size2[d2] > i-min[d] then
directions[2] = { false, false }
end
end
-- check if not growing d2 with decreasing d
if directions[1][1] or directions[1][2] then
if (i ~= min[d] and size2[d2] ~= max[d]-i) or
size2[d2] > max[d]-i then
directions[1] = { false, false }
end
end
-- check if max d2 is aligned
if directions[1][2] or directions[2][2] then
if max2[d2] ~= max[d2] then
directions[1][2] = false
directions[2][2] = false
end
end
-- check if min d2 is aligned
if directions[1][1] or directions[2][1] then
if min2[d2] ~= min[d2] then
directions[1][1] = false
directions[2][1] = false
end
end
end
if directions[1][1] or directions[1][2] or directions[2][1] or directions[2][2] then
local dir = -1
if directions[2][1] or directions[2][2] then
dir = 1
end
return d, dir
end
end
end
end
-- [count][width] = { top to bottom, left to right }
local numeracy_rect_specials = {
[12] = {
[3] = { 8, 9, 10,
6, 12, 7,
4, 11, 5,
1, 2, 3 },
},
[16] = {
[4] = { 7, 8, 9, 10,
3, 4, 5, 6,
1, 15, 16, 2,
11, 12, 13, 14 },
},
[18] = {
[3] = { 9, 10, 18,
7, 8, 17,
5, 6, 16,
3, 4, 15,
1, 2, 14,
11, 12, 13 },
},
[21] = {
[3] = { 18, 19, 20,
15, 16, 17,
12, 13, 14,
11, 21, 10,
7, 8, 9,
4, 5, 6,
1, 2, 3 },
},
[24] = {
[3] = { 18, 19, 20,
15, 16, 17,
13, 24, 14,
11, 23, 12,
9, 22, 10,
7, 21, 8,
4, 5, 6,
1, 2, 3 },
},
[25] = {
[5] = { 1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
11, 12, 13, 14, 15,
16, 17, 18, 19, 20,
21, 22, 23, 24, 25 },
},
[32] = {
[4] = { 21, 22, 23, 24,
25, 31, 32, 26,
27, 28, 29, 30,
1, 6, 11, 16,
2, 7, 12, 17,
3, 8, 13, 18,
4, 9, 14, 19,
5, 10, 15, 20 },
},
[36] = {
[6] = { 1, 2, 11, 12, 21, 22,
3, 4, 13, 14, 23, 24,
5, 6, 15, 16, 25, 26,
7, 8, 17, 18, 27, 28,
9, 10, 19, 20, 29, 30,
31, 32, 33, 34, 35, 36 },
},
[49] = {
[7] = { 1, 2, 11, 12, 13, 14, 15,
3, 4, 16, 17, 18, 19, 20,
5, 6, 47, 48, 49, 21, 22,
7, 8, 44, 45, 46, 23, 24,
9, 10, 41, 42, 43, 25, 26,
31, 32, 33, 34, 35, 27, 28,
36, 37, 38, 39, 40, 29, 30 },
},
[64] = {
[8] = { 1, 2, 3, 4, 11, 12, 13, 14,
5, 6, 7, 8, 15, 16, 17, 18,
21, 22, 9, 10, 19, 20, 31, 32,
23, 24, 25, 63, 64, 33, 34, 35,
26, 27, 28, 61, 62, 36, 37, 38,
29, 30, 41, 42, 51, 52, 39, 40,
43, 44, 45, 46, 53, 54, 55, 56,
47, 48, 49, 50, 57, 58, 59, 60 },
},
[81] = {
[9] = { 1, 6, 11, 16, 21, 22, 23, 24, 25,
2, 7, 12, 17, 26, 27, 28, 29, 30,
3, 8, 13, 18, 31, 32, 33, 34, 35,
4, 9, 14, 19, 36, 37, 38, 39, 40,
5, 10, 15, 20, 81, 41, 46, 51, 56,
61, 62, 63, 64, 65, 42, 47, 52, 57,
66, 67, 68, 69, 70, 43, 48, 53, 58,
71, 72, 73, 74, 75, 44, 49, 54, 59,
76, 77, 78, 79, 80, 45, 50, 55, 60 },
},
[121] = {
[11] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 101, 102, 37, 103, 104, 38, 39, 40,
41, 42, 43, 105, 106, 107, 108, 109, 44, 45, 46,
47, 48, 49, 50, 110, 121, 111, 51, 52, 53, 54,
55, 56, 57, 112, 113, 114, 115, 116, 58, 59, 60,
61, 62, 63, 117, 118, 64, 119, 120, 65, 66, 67,
68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 },
},
[144] = {
[12] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 101, 28, 29, 30, 31, 111, 32, 33, 34,
35, 36, 102, 103, 104, 105, 112, 113, 114, 115, 37, 38,
39, 40, 41, 106, 107, 108, 116, 117, 118, 42, 43, 44,
45, 46, 47, 109, 110, 141, 142, 119, 120, 48, 49, 50,
51, 52, 53, 121, 122, 143, 144, 131, 132, 54, 55, 56,
57, 58, 59, 123, 124, 125, 133, 134, 135, 60, 61, 62,
63, 64, 126, 127, 128, 129, 136, 137, 138, 139, 65, 66,
67, 68, 69, 130, 70, 71, 72, 73, 140, 74, 75, 76,
77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 },
},
}
-- transposed
local numeracy_tri_specials = {
[28] = { 1,
2, 3,
4, 5, 6,
7, 8, 9, 10,
11, 12, 13, 14, 15,
21, 16, 17, 18, 19, 20,
22, 23, 24, 25, 26, 27, 28 },
[36] = { 1,
2, 3,
4, 5, 6,
7, 8, 9, 10,
11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 31,
21, 22, 23, 24, 25, 32, 33,
26, 27, 28, 29, 30, 34, 35, 36 },
[45] = { 1,
2, 3,
4, 5, 6,
7, 8, 9, 10,
11, 12, 13, 14, 31,
15, 16, 17, 18, 32, 33,
19, 20, 21, 22, 34, 35, 36,
23, 24, 25, 26, 37, 38, 39, 40,
27, 28, 29, 30, 41, 42, 43, 44, 45 },
[55] = { 1,
2, 3,
4, 5, 6,
7, 8, 9, 10,
11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 41,
21, 22, 23, 24, 25, 42, 43,
26, 27, 28, 29, 30, 44, 45, 46,
31, 32, 33, 34, 35, 47, 48, 49, 50,
36, 37, 38, 39, 40, 51, 52, 53, 54, 55 },
}
local numeracy_cube_specials = {
[3] = { -- 27
-- 27 is a cube, with corners and edges part of 20
-- Number should probably be further forward
sorting = { { 'y', -1 }, { 'x', 1 }, { 'z', 1 } },
-- f left b f b f right b
numbering = { 13, 14, 15, 16, 21, 17, 18, 19, 20, -- top
9, 22, 11, 23, 27, 24, 12, 25, 10, -- mid
1, 2, 3, 4, 26, 5, 6, 7, 8 } -- bottom
},
[4] = { -- 64
sorting = { { 'y', -1 }, { 'z', 1 }, { 'x', 1 } },
-- l front r l r l r l back r
numbering = { 1, 2, 11, 12, 7, 41, 51, 17, 45, 46, 55, 56, 21, 22, 31, 32, -- top
3, 61, 62, 13, 8, 42, 52, 18, 29, 47, 57, 39, 23, 24, 33, 34,
4, 63, 64, 14, 9, 43, 53, 19, 30, 48, 58, 40, 25, 26, 35, 36,
5, 6, 15, 16, 10, 44, 54, 20, 49, 50, 59, 60, 27, 28, 37, 38 } -- bottom
},
}
-- [count][width][height][i] = { top to bottom, left to right, 0 for gap }
local numeracy_irregulars = {
[13] = {
[3] = {
[6] = {
{ 0, 11, 12,
1, 6, 13,
2, 7, 0,
3, 8, 0,
4, 9, 0,
5, 10, 0 },
},
},
},
[17] = {
[4] = {
[7] = {
{ 0, 0, 0, 17,
13, 14, 15, 16,
12, 1, 6, 0,
11, 2, 7, 0,
0, 3, 8, 0,
0, 4, 9, 0,
0, 5, 10, 0 },
},
},
},
[19] = {
[7] = {
[5] = {
-- monster
{ 11, 0, 0, 0, 0, 0, 19,
12, 13, 14, 15, 16, 17, 18,
0, 1, 2, 3, 4, 5, 0,
0, 0, 6, 7, 8, 0, 0,
0, 0, 9, 0, 10, 0, 0 },
},
},
},
[31] = {
[7] = {
[5] = {
-- Calendar
{ 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 0, 0, 0, 0 },
},
},
},
[55] = {
[5] = {
[21] = {
{ 0, 0, 55, 0, 0,
0, 0, 54, 0, 0,
0, 0, 53, 0, 0,
0, 0, 52, 0, 0,
0, 0, 51, 0, 0,
0, 0, 30, 0, 0,
0, 0, 29, 0, 0,
0, 0, 28, 0, 0,
0, 20, 27, 40, 0,
0, 19, 26, 39, 0,
0, 18, 25, 38, 0,
10, 17, 24, 37, 50,
9, 16, 23, 36, 49,
8, 15, 22, 35, 48,
7, 14, 21, 34, 47,
6, 13, 0, 33, 46,
5, 12, 0, 32, 45,
4, 11, 0, 31, 44,
3, 0, 0, 0, 43,
2, 0, 0, 0, 42,
1, 0, 0, 0, 41 },
},
},
},
[80] = {
[10] = {
[15] = {
-- Roboctoblock squatting
{ 0, 0, 0, 0, 1, 2, 0, 0, 0, 0,
0, 0, 0, 3, 4, 5, 6, 0, 0, 0,
0, 0, 0, 0, 7, 8, 0, 0, 0, 0,
13, 12, 11, 21, 9, 10, 31, 41, 42, 43,
14, 0, 22, 23, 24, 32, 33, 34, 0, 44,
15, 0, 25, 26, 27, 35, 36, 37, 0, 45,
16, 0, 28, 29, 30, 38, 39, 40, 0, 46,
17, 0, 51, 52, 53, 54, 55, 56, 0, 47,
18, 0, 0, 0, 57, 58, 0, 0, 0, 48,
19, 0, 0, 61, 59, 60, 71, 0, 0, 49,
20, 0, 0, 62, 63, 72, 73, 0, 0, 50,
0, 0, 0, 64, 65, 74, 75, 0, 0, 0,
0, 0, 0, 66, 0, 0, 76, 0, 0, 0,
0, 0, 67, 68, 0, 0, 77, 78, 0, 0,
0, 0, 69, 70, 0, 0, 79, 80, 0, 0 },
},
[16] = {
-- Roboctoblock standing
{ 0, 0, 0, 0, 1, 2, 0, 0, 0, 0,
0, 0, 0, 3, 4, 5, 6, 0, 0, 0,
0, 0, 0, 0, 7, 8, 0, 0, 0, 0,
13, 12, 11, 21, 9, 10, 31, 41, 42, 43,
14, 0, 22, 23, 24, 32, 33, 34, 0, 44,
15, 0, 25, 26, 27, 35, 36, 37, 0, 45,
16, 0, 28, 29, 30, 38, 39, 40, 0, 46,
17, 0, 51, 52, 53, 54, 55, 56, 0, 47,
18, 0, 0, 0, 57, 58, 0, 0, 0, 48,
19, 0, 0, 0, 59, 60, 0, 0, 0, 49,
20, 0, 0, 61, 62, 71, 72, 0, 0, 50,
0, 0, 0, 63, 64, 73, 74, 0, 0, 0,
0, 0, 0, 65, 0, 0, 75, 0, 0, 0,
0, 0, 0, 66, 0, 0, 76, 0, 0, 0,
0, 0, 67, 68, 0, 0, 77, 78, 0, 0,
0, 0, 69, 70, 0, 0, 79, 80, 0, 0 },
}
},
[12] = {
[17] = {
-- Spidoctoblock
{ 0, 0, 1, 2, 0, 0, 0, 0, 11, 12, 0, 0,
0, 0, 3, 0, 0, 0, 0, 0, 0, 13, 0, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 14, 0, 0,
21, 0, 5, 0, 0, 10, 20, 0, 0, 15, 0, 31,
22, 0, 6, 7, 8, 9, 19, 18, 17, 16, 0, 32,
23, 0, 0, 0, 0, 30, 40, 0, 0, 0, 0, 33,
24, 25, 26, 27, 28, 29, 39, 38, 37, 36, 35, 34,
0, 0, 0, 0, 0, 50, 60, 0, 0, 0, 0, 0,
0, 45, 46, 47, 48, 49, 59, 58, 57, 56, 55, 0,
0, 44, 0, 0, 0, 70, 80, 0, 0, 0, 54, 0,
0, 43, 0, 67, 68, 69, 79, 78, 77, 0, 53, 0,
0, 42, 0, 66, 0, 0, 0, 0, 76, 0, 52, 0,
0, 41, 0, 65, 0, 0, 0, 0, 75, 0, 51, 0,
0, 0, 0, 64, 0, 0, 0, 0, 74, 0, 0, 0,
0, 0, 0, 63, 0, 0, 0, 0, 73, 0, 0, 0,
0, 0, 0, 62, 0, 0, 0, 0, 72, 0, 0, 0,
0, 0, 0, 61, 0, 0, 0, 0, 71, 0, 0, 0 },
},
},
[16] = {
[15] = {
-- Dinoctoblock
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 11, 12, 13, 14,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 15, 16, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 17, 18, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 22, 19, 20, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 24, 25, 26, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 51, 56, 27, 28, 29, 30,
0, 0, 0, 0, 0, 0, 0, 0, 42, 47, 52, 57, 61, 65, 0, 0,
0, 0, 0, 0, 0, 0, 37, 39, 43, 48, 53, 58, 62, 66, 69, 70,
31, 32, 33, 34, 35, 36, 38, 40, 44, 49, 54, 59, 63, 67, 0, 0,
0, 0, 0, 0, 0, 0, 0, 41, 45, 50, 55, 60, 64, 68, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 71, 72, 75, 76, 77, 78, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 0, 0, 0, 79, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 80, 0, 0 },
},
},
},
}
-- Process irregulars
for count, a in pairs(numeracy_irregulars) do
for width, b in pairs(a) do
for height, c in pairs(b) do
for i, d in ipairs(c) do
d.order = {}
for j, v in ipairs(d) do
if v > 0 then
table.insert(d.order, v)
end
end
end
end
end
end
-- We only need rotation around y+ axis for now
local numeracy_rotation = {
[0] = { x = { 'x', 1 }, y = { 'y', 1 }, z = { 'z', 1 } },
[1] = { x = { 'z', -1 }, y = { 'y', 1 }, z = { 'x', 1 } },
[2] = { x = { 'x', -1 }, y = { 'y', 1 }, z = { 'z', -1 } },
[3] = { x = { 'z', 1 }, y = { 'y', 1 }, z = { 'x', -1 } },
}
local function numeracy_rotate_sorting(sorting, facedir)
local new_sorting = {}
for i, s in ipairs(sorting) do
local new_s = { s[1], s[2] }
if numeracy_rotation[facedir] then
new_s[1] = numeracy_rotation[facedir][s[1]][1]
new_s[2] = s[2] * numeracy_rotation[facedir][s[1]][2]
end
new_sorting[i] = new_s
end
return new_sorting
end
-- e.g. numeracy_match_irregular({...}, {...}, width, height, min, max, {{'x', 1}, {'y', -1}})
local function numeracy_match_irregular(nodes, layout, width, height, min, max, ordering)
for y = 1, height do
for x = 1, width do
local index = x + (y - 1)*width
if layout[index] > 0 then
local pos = {x = min.x, y = min.y, z = min.z}
local lpos = {y, x}
for i, order in ipairs(ordering) do
if order[2] == nil or order[2] >= 0 then
pos[order[1]] = min[order[1]] + (lpos[i] - 1)
else
pos[order[1]] = max[order[1]] - (lpos[i] - 1)
end
end
-- expect a block here
if nodes_test(nodes, pos) ~= NODE_BLOCK then
return false
end
end
end
end
return true
end
-- Assign an ordering to the nodes
-- returns facedir of blocks
local function numeracy_sort_blocks(nodes, count, doer)
local size, min, max = nodes_size(nodes)
local dimentions = numeracy_size_dimentions(size)
-- offset by 1 for convenience, since zero based sizes are less intuitive
size = vector.add(size, vector.new(1, 1, 1))
local doer_pos = doer:get_pos()
local doer_dir = vector.subtract(vector.divide(vector.add(min, max), 2), doer_pos)
local facedir = minetest.dir_to_facedir(doer_dir)
if dimentions == 1 then
-- 1 dimentional, can't go wrong
numeracy_sort(nodes)
if size.x > 1 then
doer_dir.x = 0
elseif size.z > 1 then
doer_dir.z = 0
end
facedir = minetest.dir_to_facedir(doer_dir)
elseif dimentions == 2 then
-- 2 dimentional, this is where all the interesting stuff is
local d1, d2
if size.x == 1 then
d1 = 'z'
d2 = 'y'
elseif size.y == 1 then
d1 = 'x'
d2 = 'z'
else
d1 = 'x'
d2 = 'y'
end
if size.y > 1 then
doer_dir[d1] = 0
doer_dir[d2] = 0
facedir = minetest.dir_to_facedir(doer_dir)
end
-- rectangles
local rectangle_class = 0
if count == size[d1] * size[d2] then
rectangle_class = 2
if numeracy_rect_specials[count] then
if not numeracy_rect_specials[count][size[d1]] and numeracy_rect_specials[count][size[d2]] then
-- swap to match representation in numeracy_rect_specials
local tmp = d1
d1 = d2
d2 = tmp
end
if numeracy_rect_specials[count][size[d1]] then
numeracy_sort(nodes, { { d2, -1 }, { d1, 1} },
numeracy_rect_specials[count][size[d1]])
return facedir
end
end
end
-- irregular shapes
if rectangle_class == 0 and numeracy_irregulars[count] then
local orientations = { { d1, d2 }, { d2, d1 } }
for io, dims in ipairs(orientations) do
if numeracy_irregulars[count][size[dims[1]]] and numeracy_irregulars[count][size[dims[1]]][size[dims[2]]] then
for i, layout in ipairs(numeracy_irregulars[count][size[dims[1]]][size[dims[2]]]) do
for dir1=-1,1,2 do
for dir2=-1,1,2 do
local ordering = { { dims[2], dir1 }, { dims[1], dir2 } }
if numeracy_match_irregular(nodes, layout, size[dims[1]], size[dims[2]], min, max, ordering) then
numeracy_sort(nodes, ordering, layout.order)
return facedir
end
end
end
end
end
end
end
-- default rectangles and almost rectangles
local dims = { d1, d2 }
for i, dd in ipairs(dims) do
local dd2 = dims[3 - i]
local range_min = vector.new(min)
local range_max = vector.new(max)
local d2_dir = 0
local size2, min2, max2, count2
if rectangle_class == 2 then
-- already a perfect rectangle
size2 = size
min2 = min
max2 = max
count2 = count
d2_dir = 1
else
-- find if a rectangle up to last row
range_max[dd2] = range_max[dd2] - 1
local size2, min2, max2, count2 = nodes_size(nodes, range_min, range_max)
size2 = vector.add(size2, vector.new(1, 1, 1))
if size2[dd] == size[dd] and count2 == size2[dd] * size2[dd2] then
d2_dir = 1
else
-- find if a rectangle down to first row
range_max[dd2] = range_max[dd2] + 1
range_min[dd2] = range_min[dd2] + 1
size2, min2, max2, count2 = nodes_size(nodes, range_min, range_max)
size2 = vector.add(size2, vector.new(1, 1, 1))
if size2[dd] == size[dd] and count2 == size2[dd] * size2[dd2] then
d2_dir = -1
end
end
end
if d2_dir ~= 0 then
local numbering = nil
numeracy_sort(nodes, { { dd2, d2_dir } }, numbering)
return facedir
end
end
-- triangular numbers
local tri_dim, tri_dir = numeracy_is_triangle(nodes, size, min, max)
if tri_dim then
numeracy_sort(nodes, { { tri_dim, tri_dir } }, numeracy_tri_specials[count])
return facedir
end
numeracy_sort(nodes)
elseif dimentions == 3 then
-- 3 dimentional
-- Special cases
if numeracy_cube_specials[size.x] and
size.x == size.y and size.y == size.z and
count == size.x * size.x * size.x then
local s = numeracy_cube_specials[size.x]
numeracy_sort(nodes, numeracy_rotate_sorting(s.sorting, facedir), s.numbering);
return facedir
end
numeracy_sort(nodes)
end
return facedir
end
-- Change the colour of blocks depending on the number of them
local function numeracy_restyle_blocks(nodes, count, doer)
local facedir = 0
if count > 0 then
facedir = numeracy_sort_blocks(nodes, count, doer)
end
-- find best place for number nodes
local max_y = -31000
local sum_pos = { x = 0, y = 0, z = 0 }
local sum_count = 0
for y, xs in numeracy_ordered_pairs(nodes) do
for x, zs in numeracy_ordered_pairs(xs) do
for z, info in numeracy_ordered_pairs(zs) do
local node_type = info.t
local pos = {x = x, y = y, z = z};
if node_type == NODE_NUMBER then
minetest.remove_node(pos)
elseif node_type == NODE_BLOCK then
-- if new highest block, discard average XZ state
if y > max_y then
max_y = y
sum_pos = { x = 0, y = 0, z = 0 }
sum_count = 0
end
sum_pos = vector.add(sum_pos, pos)
sum_count = sum_count + 1
local order = info.o
local count_in_10000 = count % 10000
local order_in_10000 = order % 10000
local count_in_1000 = count_in_10000 % 1000
local order_in_1000 = order_in_10000 % 1000
local count_in_100 = count_in_1000 % 100
local order_in_100 = order_in_1000 % 100
local count_in_10 = count_in_100 % 10
local order_in_10 = order_in_100 % 10
local count_1000s_in_10000 = math.floor(count_in_10000/1000)*1000
local order_1000s_in_10000 = math.floor(order_in_10000/1000)*1000
local count_100s_in_1000 = math.floor(count_in_1000/100)*100
local order_100s_in_1000 = math.floor(order_in_1000/100)*100
local count_10s_in_100 = math.floor(count_in_100/10)*10
local order_10s_in_100 = math.floor(order_in_100/10)*10
if count > max_number then
-- Unsupported, don't change any blocks, just leave it there
elseif count == 10000 then
-- SURELY 10000 is enough for anybody!
minetest.set_node(pos, {
name = "numeracy:block_"..tostring(count).."_"..tostring(order_1000s_in_10000/1000),
param2 = facedir
})
elseif order_1000s_in_10000 < count_1000s_in_10000 then
-- Blocks of 1000
if count_1000s_in_10000 <= 9000 then
local thousand_in_10000 = order_1000s_in_10000/1000
-- checkerboard pattern
local colour_index
local block_name = "numeracy:block_"..tostring(count_1000s_in_10000).."_"..tostring(thousand_in_10000)
if count_1000s_in_10000 == 7000 then
block_name = "numeracy:block_"..tostring(1000 + order_1000s_in_10000).."_0"
colour_index = order_1000s_in_10000/1000
elseif count_1000s_in_10000 == 9000 then
local three_thousand = math.floor(order_1000s_in_10000/3000)
local thousand_in_3000 = thousand_in_10000 % 3
block_name = "numeracy:block_"..tostring(9000 + three_thousand).."_"..tostring(thousand_in_3000)
colour_index = 8 + three_thousand
else
colour_index = count_1000s_in_10000/1000 - 1
end
minetest.set_node(pos, {
name = block_name,
param2 = colour_index
})
end
elseif order_100s_in_1000 < count_100s_in_1000 then
-- Blocks of 100
if count_100s_in_1000 <= 900 then
local hundred_in_1000 = order_100s_in_1000/100
-- checkerboard pattern
local colour_index = ((math.abs(x) % 2) ~= (math.abs(y) % 2)) ~=
((math.abs(z) % 2) ~= 0)
if colour_index then
colour_index = 1
else
colour_index = 0
end
local block_name = "numeracy:block_"..tostring(count_100s_in_1000).."_"..tostring(hundred_in_1000)
if count_100s_in_1000 == 700 then
block_name = "numeracy:block_"..tostring(100 + order_100s_in_1000).."_0"
if order_100s_in_1000 <= 400 then
colour_index = colour_index + 2*(order_100s_in_1000/100)
else
colour_index = colour_index + 2*(order_100s_in_1000/100 - 4)
end
elseif count_100s_in_1000 == 900 then
local three_hundred = math.floor(order_100s_in_1000/300)
local hundred_in_300 = hundred_in_1000 % 3
block_name = "numeracy:block_"..tostring(900 + three_hundred).."_"..tostring(hundred_in_300)
colour_index = colour_index + 2*three_hundred
elseif count_100s_in_1000 <= 400 then
colour_index = colour_index + 2*(count_100s_in_1000/100 - 1)
elseif count_100s_in_1000 <= 800 then
colour_index = colour_index + 2*(count_100s_in_1000/100 - 5)
end
minetest.set_node(pos, {
name = block_name,
param2 = colour_index*32 + facedir
})
end
elseif order_10s_in_100 < count_10s_in_100 then
-- Blocks of 10
if count_10s_in_100 == 70 then
local tens_for_70 = 10 + order_10s_in_100
if tens_for_70 == 10 then
tens_for_70 = 71
elseif tens_for_70 == 70 then
tens_for_70 = 77
end
minetest.set_node(pos, {
name = "numeracy:block_"..tostring(tens_for_70).."_0",
param2 = facedir
})
elseif count_10s_in_100 == 90 then
local thirties_for_90 = math.floor(order_10s_in_100/30)
local tens_in_thirty = math.floor((order_10s_in_100%30) / 10)
minetest.set_node(pos, {
name = "numeracy:block_"..tostring(90 + thirties_for_90).."_"..tostring(tens_in_thirty),
param2 = facedir
})
else
local ten_in_100 = order_10s_in_100/10
minetest.set_node(pos, {
name = "numeracy:block_"..tostring(count_10s_in_100).."_"..tostring(ten_in_100),
param2 = facedir
})
end
elseif count_in_10 == 7 then
minetest.set_node(pos, { name = "numeracy:block", param2 = order_in_10 })
elseif count_in_10 == 9 then
minetest.set_node(pos, { name = "numeracy:block", param2 = 8 + math.floor(order_in_10/3) })
else
minetest.set_node(pos, { name = "numeracy:block", param2 = count_in_10 - 1 })
end
end
end
end
end
if sum_count > 0 then
-- find average XZ of max Y blocks
sum_pos = vector.divide(sum_pos, sum_count)
local found_best = false
local best_pos
local best_dist2 = -1
-- find closest block at max_y
for x, zs in numeracy_ordered_pairs(nodes[max_y]) do
for z, info in numeracy_ordered_pairs(zs) do
local node_type = info.t
if node_type == NODE_BLOCK then
-- Check space above is unoccupied
local pos_above = {x = x, y = max_y+1, z = z}
if nodes_test(nodes, pos_above) == NODE_NUMBER or
minetest.get_node(pos_above).name == "air" then
local pos = {x = x, y = max_y, z = z}
local disp = vector.subtract(pos, sum_pos)
local dist2 = vector.dot(disp, disp)
if best_dist2 < 0 or dist2 < best_dist2 then
best_pos = pos
best_dist2 = dist2
found_best = true
end
end
end
end
end
if found_best and count <= max_number then
best_pos.y = best_pos.y + 1
numeracy_add_numbers(best_pos, count, facedir)
end
end
end
function numeracy_block_on_place(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
-- FIXME check if under is a number first and replace that
local pos = pointed_thing.above
local node = minetest.get_node(pos)
local node_type = get_node_type(node)
if node_type == NODE_NUMBER then
minetest.remove_node(pos)
end
local stack, success = minetest.item_place_node(itemstack, placer, pointed_thing)
if success then
local nodes, count = find_blocks(pos)
numeracy_restyle_blocks(nodes, count, placer)
end
return stack, success
end
return itemstack
end
function numeracy_block_after_dig_node(pos, oldnode, oldmetadata, digger)
-- Look in each direction for a broken one
local skips = {}
local positions = {}
for i = 1, 6 do
positions[i] = vector.add(pos, adjacent_vectors[i])
local node = minetest.get_node(positions[i])
local adj_node_type = get_node_type(node);
skips[i] = not (node and adj_node_connected(NODE_BLOCK, i, adj_node_type, node.param2))
end
for i = 1, 6 do
if skips[i] == false then
local nodes, count = find_blocks(positions[i])
numeracy_restyle_blocks(nodes, count, digger)
-- Skip other adjacent nodes part of this numeracy block
for j = i+1, 6 do
if nodes_test(nodes, positions[j]) ~= NODE_NONE then
skips[j] = true
end
end
end
end
end